跳轉到

投資操作績效 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 2026072026-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=Grouptype=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_partial
  • period: 該 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_pnl
  • day_trade_pnl
  • cash_dividend
  • expected_cash_dividend
  • realized_total
  • unrealized_pnl
  • capital_employed
  • opened_lots_count
  • closed_lots_count
  • total_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 valuedetail_value 是 backend 對全部未分頁 items 的對帳結果;不得 sum 當頁 itemscapital_employedbasis=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=standaloneevent_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 用 daterealized_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 * 100equity_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

  1. 先從 running backend capture status 200 fixture,不以手寫 shape 開始。
  2. Web/Mobile/Management 分別依自己的 workflow 提供 rendered UI/UX approval artifact。
  3. 所有 money/percentage formatter 必須保留 null、負數與 incomplete warning。
  4. transaction mutation 後 invalidate single-period、timeseries、summary queries。
  5. 三專案共享 OpenAPI 與本文件,避免各自重新推導公式。