投資操作績效 Frontend API Integration Summary
此文件是 Cortex Web、Cortex Mobile、Cortex Management 三個 consumer agents 的共同 API SSOT;不規定各專案 UI/UX。
Endpoint matrix
| Method/path | Purpose |
|---|---|
GET /api/portfolio/performance |
既有「目前持倉市場變化」,contract 不變 |
GET /api/portfolio/investment-performance |
全時或指定日/週/月/季/年的操作績效 |
GET /api/portfolio/investment-performance/details |
單一績效數字的可對帳逐筆明細 |
GET /api/portfolio/investment-performance/timeseries |
chart 用 period + cumulative 序列 |
GET /api/portfolio/summary |
既有總覽;股利 entitlement 修正但 shape 不變 |
所有 endpoint 需要 Bearer access token。
單期 request
GET /api/portfolio/investment-performance?period=month&value=202607
Authorization: Bearer <token>
| Location | Field | Type | Required | Nullable | Default | Notes |
|---|---|---|---|---|---|---|
| query | period |
enum | no | no | all |
all/day/week/month/quarter/year |
| query | value |
string | non-all yes | yes | null | normalized response value uses hyphen form |
| query | ticker |
string | no | yes | null | uppercase echo;closed ticker allowed |
| query | type |
Group/ETF | no | yes | null | mutually exclusive with ticker |
目前 month compact 202607 與 2026-07 都接受;day 接受 compact/ISO;quarter 接受 compact/hyphen。
單期 response example
{
"period": "month",
"value": "2026-07",
"start_date": "2026-07-01",
"end_date": "2026-07-10",
"valuation_date": "2026-07-09",
"is_partial": true,
"ticker": null,
"opened_lots_count": 4,
"closed_lots_count": 0,
"realized": {
"regular_pnl": 0.0,
"day_trade_pnl": 0.0,
"cash_dividend": 0.0,
"total": 0.0
},
"expected_cash_dividend": 3000.0,
"unrealized_pnl": -12525.5,
"total_pnl": -12525.5,
"capital_employed": 225916.0,
"return_pct": -5.5443173569,
"equity_multiple": 0.9445568264,
"data_quality": {
"is_complete": true,
"unmatched_sell_count": 0,
"unmatched_sell_tickers": [],
"missing_price_tickers": [],
"dividend_basis": "ex_dividend_entitlement",
"dividend_history_complete": true,
"dividend_missing_coverage": [],
"standalone_cash_dividend_count": 0,
"unattributed_cash_dividend_count": 0,
"classification_basis": null
}
}
數字來自 2026-07-10 task runtime 的去識別化 contract capture;consumer fixture 應固定 shape,不應固定真實金額。
Grouped response
type=Group 或 type=ETF top-level 改為:
{
"period": "all",
"value": null,
"start_date": "2021-01-29",
"end_date": "2026-07-10",
"valuation_date": "2026-07-09",
"is_partial": false,
"type": "Group",
"classification_basis": "current_metadata",
"overall": { "...metric fields": "same as ungrouped" },
"groups": [
{
"category": "半導體業",
"category_source": "industry",
"tickers": ["2330.TW"],
"...metric fields": "same as overall"
}
],
"data_quality": { "...": "overall quality" }
}
Group fallback 固定為 industry → sector → Unclassified;ETF 分類為 etf/stock。歷史分類採 current metadata。每個 group 的 capital_employed/return_pct 已由 backend 重算,不可在 frontend 平均百分比。
groups[].tickers 為該期間內實際有操作的個股子集(產生該 group cohort 數字的 closures/dividends/lots 標的,加上區間內未配對賣出),非該分類的全歷史成員;期間內完全無操作的分類仍回傳該 group(tickers=[]、數字為 0)。period=all 時等同全部成員。
Timeseries
GET /api/portfolio/investment-performance/timeseries?start_date=2026-01-01&end_date=2026-07-10&interval=month
Response:series[] { key, label, category_source, tickers, points[] };每個 point:
period_start/period_end/valuation_date/is_partialperiod: 該 bucket 開倉 lots 截至 point 的績效cumulative: inception 至 point 的績效data_quality: point cumulative quality
day 只回有市場估值日的點;其他 interval 每個有估值日的 bucket 一點。過去點 expected_cash_dividend=0,只有 current terminal point 可以顯示已公告預計股利。超過 2000 points 回 422。
逐筆明細
GET /api/portfolio/investment-performance/details?period=month&value=2026-07&metric=cash_dividend&offset=0&limit=100
Authorization: Bearer <token>
metric 必填:
regular_pnlday_trade_pnlcash_dividendexpected_cash_dividendrealized_totalunrealized_pnlcapital_employedopened_lots_countclosed_lots_counttotal_pnl
period/value/ticker 與單期 API 相同。分類 drill-down 必須同時傳 type/category/category_source,例如:
GET /api/portfolio/investment-performance/details?metric=total_pnl&type=Group&category=半導體業&category_source=industry
三個分類欄位缺任一欄、與 ticker 同時出現、分類不存在或 source 不匹配均回 422。分類 ticker 清單由 backend current metadata 解析,client 不傳 ticker 清單。offset 預設 0;limit 預設 100、最大 1000。
共用 response
{
"window": {
"period": "month",
"value": "2026-07",
"start_date": "2026-07-01",
"end_date": "2026-07-11",
"valuation_date": "2026-07-10",
"is_partial": true
},
"scope": {
"kind": "overall",
"ticker": null,
"type": null,
"category": null,
"category_source": null,
"tickers": ["2330.TW"]
},
"metric": "cash_dividend",
"unit": "TWD",
"value": 5000.0,
"reconciliation": {
"basis": "sum",
"detail_value": 5000.0,
"status": "matched"
},
"pagination": {
"total_items": 2,
"offset": 0,
"limit": 100,
"has_more": false
},
"items": [],
"data_quality": { "...": "與單期 API 相同" }
}
重要:畫面數字一律使用 top-level value。detail_value 是 backend 對全部未分頁 items 的對帳結果;不得 sum 當頁 items。capital_employed 的 basis=max_capital_required,不是 sum。count metrics 的 unit 為 count,其餘為 TWD。
reconciliation.status:
matched:全量明細與 aggregate 相等。incomplete:缺 valuation price 等資料,value/detail_value可為 null;不得轉 0。mismatch:非預期計算不一致,frontend 必須顯示 data-quality warning。
Closure item
{
"item_type": "closure",
"closure_id": "uuid",
"classification": "regular",
"ticker": "2330.TW",
"quantity": 100,
"buy_transaction_id": "uuid",
"buy_date": "2026-01-02",
"buy_price": 1000.0,
"sell_transaction_id": "uuid",
"sell_date": "2026-07-03",
"sell_price": 1100.0,
"day_trade_pair_id": null,
"allocated_buy_gross": 100000.0,
"allocated_buy_fee": 142.0,
"allocated_buy_tax": 0.0,
"allocated_cost": 100142.0,
"allocated_sell_gross": 110000.0,
"allocated_sell_fee": 156.0,
"allocated_sell_tax": 330.0,
"allocated_proceeds": 109514.0,
"pnl": 9372.0
}
fee/tax 依 closure quantity 比例配置。當沖分類採 closure.is_day_trade OR sell.day_trade_pair_id,因此 legacy pair id null 仍可能是 day trade。
Dividend item
{
"item_type": "dividend",
"status": "realized",
"attribution": "event",
"event_key": {
"ticker": "2330.TW",
"ex_dividend_date": "2026-07-01"
},
"ticker": "2330.TW",
"ex_dividend_date": "2026-07-01",
"position_quantity": 1000,
"per_share_dividend": 5.0,
"amount": 5000.0,
"source": "TWSE",
"explicit_override": false,
"transaction_ids": [],
"selected_entitlement_quantity": 1000,
"total_entitlement_quantity": 1000
}
attribution=standalone 時 event_key=null 且 transaction IDs 會包含實際 cash-dividend transaction。event explicit override、event 推算與 standalone 不會重複;未來除息事件只出現在 expected metric。
Lot item
{
"item_type": "lot",
"transaction_id": "uuid",
"ticker": "2330.TW",
"action": "buy",
"opened_date": "2026-07-01",
"original_quantity": 100,
"closed_quantity": 40,
"remaining_quantity": 60,
"status": "partial",
"unit_cost": 1001.42,
"remaining_cost_basis": 60085.2,
"valuation_date": "2026-07-10",
"valuation_price": 1050.0,
"market_value": 63000.0,
"unrealized_pnl": 2914.8
}
opened_lots_count 回 cohort 全部 lots;closed_lots_count 只回 fully closed;unrealized/total 只回 remaining lots。缺價格時 valuation price/value/unrealized 都是 null。
Capital-flow item
{
"item_type": "capital_flow",
"date": "2026-07-01",
"transaction_ids": ["uuid"],
"buy_outflow": 100142.0,
"sell_proceeds": 0.0,
"realized_dividend_inflow": 0.0,
"net_cash_flow": -100142.0,
"cumulative_cash_flow": -100142.0,
"day_trade_open_cost_floor": 0.0,
"capital_required": 100142.0,
"is_peak": true
}
所有 items 均依主要日期由新到舊:closure 用 sell_date、dividend 用 ex_dividend_date、lot 用 opened_date、capital flow 用 date。realized_total 會混合 closure + realized dividend;total_pnl 再加入 remaining lot,請用 item_type 判斷 component。Capital flow 的 cumulative/peak 仍由 backend 按日期正向計算,只有 response items 的顯示順序反轉。
Null、empty、error
- 必要 close 缺失:
unrealized_pnl/total_pnl/return_pct/equity_multiple=null,ticker 出現在missing_price_tickers;不可轉成 0。 - 無交易/找不到 ticker:HTTP 200、counts/realized/capital 為 0,無分母欄位 null。
- 無效 value、future-only period、ticker+type、timeseries future end、超過 2000 points:HTTP 422,FastAPI
{"detail": ...}。 data_quality.is_complete=false時仍可顯示已知值,但必須顯示 warning,不可宣稱完整績效。
Product semantics
all是所有歷史操作;非 all 只選期間內開倉 lots。- 舊持股本期上漲/除息不進本期操作績效,只進 all/cumulative。
ex_date <= end_date(圖表點為period_end)已實現,即使最新股價仍停在前一交易日也不延後;更晚 event 是 expected,不進 realized/total。return_pct = total_pnl / capital_employed * 100;equity_multiple = 1 + total_pnl / capital_employed。- A 10→30、再以 30 買 B 並 50 賣出:total P&L=40、capital=10、return=400%、multiple=5x。
Consumer action items
- 先從 running backend capture status 200 fixture,不以手寫 shape 開始。
- Web/Mobile/Management 分別依自己的 workflow 提供 rendered UI/UX approval artifact。
- 所有 money/percentage formatter 必須保留 null、負數與 incomplete warning。
- transaction mutation 後 invalidate single-period、timeseries、summary queries。
- 三專案共享 OpenAPI 與本文件,避免各自重新推導公式。