Add class-based ErrorBoundary component that catches rendering errors
and shows a user-friendly fallback with retry/reload buttons. Wrap in
root layout to protect against full-page crashes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace silent console.error-only handling with user-visible toast notifications
using sonner, while keeping console.error for debugging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 10 indexes across prices, etf_prices, financials, valuations,
holdings, transactions, signals, portfolio_snapshots, and etfs tables.
Fix N+1 query in list_backtests by eager-loading backtest results
with joinedload.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add dc_only parameter to all strategy endpoints. When true, filters
results to include only tickers present in the ETF table, supporting
DC pension investment constraints where only ETFs are allowed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add min_trade_amount parameter (default 10,000 KRW) to rebalance/calculate
endpoint. Trades below this threshold are converted to hold actions to avoid
inefficient micro-trades during rebalancing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a compare page at /strategy/compare that runs MultiFactor, Quality,
and ValueMomentum strategies simultaneously and displays results side-by-side
with common ticker highlighting and factor score comparison table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add realized_pnl column to transactions table with alembic migration
- Calculate realized PnL on sell transactions: (sell_price - avg_price) * quantity
- Show total realized/unrealized PnL in portfolio detail summary cards
- Show per-transaction realized PnL in transaction history table
- Add position sizing API endpoint (GET /portfolios/{id}/position-size)
- Show position sizing guide in signal execution modal for buy signals
- 8 new E2E tests, all 88 tests passing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1 (Critical):
- Add bulk rebalance apply API + UI with confirmation modal
- Add strategy results to portfolio targets flow (shared component)
Phase 2 (Important):
- Show current holdings in signal execute modal with auto-fill
- Add DC pension risk asset ratio warning (70% limit)
- Add KOSPI benchmark comparison to portfolio returns
- Track signal execution details (price, quantity, timestamp)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expert team identified 12 gaps across 6 scenarios. Prioritized into
3 phases: Critical (rebalance apply, strategy-to-portfolio link),
Important (signal holdings, DC limits, benchmark, signal PnL),
and Nice-to-have (comparison UI, walk-forward, undo).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Define 6 core usage scenarios and expert team composition for
evaluating feature completeness of the quant portfolio management system.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow users to execute active KJB signals by selecting a portfolio,
entering quantity and price, then creating the corresponding transaction
and updating holdings. Signal status changes to 'executed' after completion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full system: signal generator, daily backtest engine, trading portfolio,
signal API, scheduler job, and frontend dashboard.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Validates trading day count, benchmark coverage, per-date ticker
density, and date gaps before running simulation. Logs warnings for
holdings with missing prices during execution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Port make-quant-py's FnGuide scraping logic into galaxy-po's
BaseCollector pattern. Collects annual and quarterly financial
statements (revenue, net income, total assets, etc.) and maps
Korean account names to English keys for FactorCalculator.
Scheduled weekly on Monday 19:00 KST since data updates quarterly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The seed script was incorrectly using the latest snapshot's market price
as avg_price, resulting in inflated average costs. Now computes avg_price
from actual total invested amounts per ticker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
For holdings with quantity > 0, the input now accepts the total
valuation amount and derives the current price by dividing by quantity.
Holdings with quantity 0 (target-only) still accept price directly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CronTrigger had no explicit timezone, defaulting to system timezone
(UTC in Docker containers), causing jobs to run at KST 03:00/03:30
instead of the intended 18:00/18:30.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "데이터 탐색" menu item to sidebar with Search icon
- Add "수집 데이터 조회" link button on data management page
- Fix sidebar active state to correctly distinguish /admin/data
from /admin/data/explorer
- Add page title mapping for data explorer in header
- Fix .gitignore: add negation for frontend/src/app/admin/data/
so admin data pages are tracked without needing git add -f
- Fix dashboard loading state (return null → skeleton with layout)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two issues caused DB reset on every deploy:
1. docker-compose.prod.yml used bind mount (./data/postgres) with
PostgreSQL 18's incompatible /var/lib/postgresql/data path.
2. The Gitea CI runner shares Docker socket with the host, but
./data/postgres resolves to a temp path inside the runner container.
Each deploy creates a fresh workspace, so the bind mount always
points to an empty directory on the host.
Fix: Use a named Docker volume (same as docker-compose.yml dev config).
Named volumes are managed by Docker daemon directly, survive container
recreation, and don't depend on working directory resolution.
Also fix deploy.yml: remove unnecessary mkdir for data dirs, write
backup to /tmp instead of relative path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The portfolio API was returning only ticker symbols (e.g., "095570")
without stock names. The Stock table already has Korean names
(e.g., "AJ네트웍스") from data collection.
Backend: Add name field to HoldingWithValue schema, fetch stock names
via RebalanceService.get_stock_names() in the portfolio detail endpoint.
Frontend: Show Korean stock name as primary label with ticker as
subtitle in portfolio detail, donut charts, and target vs actual
comparison. Dashboard donut chart also shows names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The collect endpoints were defined as async def but called synchronous
collector.run() directly, blocking the single uvicorn event loop for
up to 15+ minutes during price collection. This caused all other
requests (including auth/login) to hang, making the app unusable.
Backend: Run each collector in a daemon thread with its own DB session,
returning HTTP 200 immediately. The collector logs status to JobLog as
before, which the frontend can poll.
Frontend: Auto-poll job status every 3s while any job is "running",
with a visual indicator. Disable collect buttons during active jobs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- Fix Decimal serialization in data_explorer.py (Decimal → FloatDecimal)
- Fix Optional type hints for query parameters in admin.py
- Fix authentication bypass in market.py search_stocks endpoint
Frontend:
- Fix 404 page: link to "/" instead of "/dashboard", proper back button
- Rewrite dashboard with real API data instead of hardcoded samples
- Implement actual equity curve and drawdown charts in backtest detail
- Remove mock data from backtest list, load real results from API
- Fix null dividend_yield display in quality strategy page
- Add skeleton loading states to 7 pages that returned null during load
Infrastructure:
- Fix PostgreSQL 18 volume mount compatibility in docker-compose.yml
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pydantic v2's model_dump(mode="json") serializes Decimal as strings (e.g.,
"33.33" instead of 33.33), causing frontend crashes when calling .toFixed()
on string values. Introduced FloatDecimal type alias with PlainSerializer
to ensure Decimal fields are serialized as floats in JSON responses.
Also fixed frontend Transaction interface to match backend field names
(created_at → executed_at, transaction_type → tx_type).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>