My Portfolio — Spec¶
Overview¶
Upload Mirae Asset (미래에셋) M-STOCK app screenshots to extract and track personal holdings. Uses a vision model to parse brokerage app screenshots into structured data, and computes portfolio-level risk metrics from the snapshot history.
Parsing¶
- Screenshots sent to Gemini 2.5 Flash (vision) via the Google Generative AI SDK
- Extracts: Korean holdings, US holdings, summary totals, cash positions
- Returns structured JSON with
summary,kr_holdings, andus_holdingskeys - Korean stock names are mapped to KRX 6-digit codes via a static lookup table (
portfolio/parser.py:KR_NAME_TO_TICKER) - Token usage tracked per parse:
tokens_input,tokens_output,tokens_totalstored onPortfolioSnapshot
Storage¶
- Each upload creates a timestamped
PortfolioSnapshot+PortfolioHoldingrows - Safe to upload multiple times — each creates a new snapshot; history is preserved
Risk Metrics¶
Computed server-side in portfolio/risk.py from the full snapshot history. Served via GET /api/portfolio/risk.
| Metric | Source | Notes |
|---|---|---|
| Sharpe Ratio | Inter-snapshot returns derived from total_assets_krw |
Annualized using frequency inferred from median inter-snapshot gap (×√(365/median_gap_days)); risk-free = 0; requires ≥4 return observations |
| Historical VaR 95% / 99% | Same inter-snapshot return series | Worst observed percentile of period-over-period returns |
| Parametric VaR 95% / 99% | Normal distribution fit to return series | μ − z × σ; z₉₅ = 1.6449, z₉₉ = 2.3263 |
| Max Drawdown | total_assets_krw series |
Peak-to-trough % decline; expressed as negative % |
| Sector Concentration | Latest snapshot holdings | Buckets holdings by sector using config.py sector maps; adds cash bucket |
Returns are computed as (assets[i] / assets[i-1] - 1) × 100 between consecutive snapshots with non-null total_assets_krw. This produces a series that is independent of how the brokerage computes its own same-day P&L figure. The most recent 180 snapshots are used; older snapshots are dropped to bound memory and computation.
All KRW values use latest.usd_krw_rate for USD→KRW conversion when computing the cash bucket.
Dashboard Sections¶
| Section | Content |
|---|---|
| Summary cards | Total assets, today P&L, KR return %, US return % — from latest snapshot |
| Holdings table | All holdings from latest snapshot with ticker, qty, avg cost, current price, P&L |
| Portfolio Trends | Total assets (line), daily P&L (bar), KR vs US allocation (stacked area), return % (dual-line) — requires ≥2 snapshots |
| Portfolio Risk | Sharpe, VaR 95%/99%, max drawdown cards + sector concentration bar chart — requires ≥2 snapshots with non-empty risk payload |
| Token bar | Gemini token usage today vs 500k/day reference |
Constraints¶
- Requires
GOOGLE_API_KEYin.env; portfolio parser fails if key is absent - Supported image formats: JPEG, PNG, WebP, HEIC
portfolio/parser.py:parse_screenshots()encodes images as base64 before sending- Risk metrics require ≥2 snapshots to compute; the Risk section is hidden until data is available
- Sharpe / VaR calculations require ≥4 data points; they return
Nonewhen below threshold