feat(pnl): trade history + realized PnL API (WAC) — BAT-017, REQ-039~042 Implemented

- GET /v1/portfolios/{id}/trades and GET /v1/portfolios/{id}/pnl/realized endpoints
- Weighted Average Cost (WAC) realized PnL calculation in app/domain/pnl.py
- cost_basis and data_completeness_warning returned in pnl API response
- Test coverage: 50 passed (WAC 20 + trades 12 + pnl 18), full 427 passed regression ✓
- WAC validated against 199,300 unit e2e test cases
- DB migration: none required
- QC: qc-reviewer PASS-WITH-NOTES; team lead confirm-batch-verification → release-candidate
- Remaining non-blocking: symbol filter edge case testing, HTTP_422 deprecation

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
2026-06-18 16:45:01 +09:00
co-authored by Claude Opus 4.8
parent f37ef87c8c
commit bd34450ee9
19 changed files with 2034 additions and 10 deletions
+48 -1
View File
@@ -2,7 +2,7 @@
from __future__ import annotations
from datetime import datetime
from datetime import date, datetime
from decimal import Decimal
from typing import Any, Literal
@@ -249,3 +249,50 @@ class AssetCatalogResponse(BaseModel):
class AssetCatalogListResponse(BaseModel):
items: list[AssetCatalogResponse]
total: int
# ---------------------------------------------------------------------------
# GET /v1/portfolios/{id}/trades (REQ-039)
# ---------------------------------------------------------------------------
class TradeHistoryItem(BaseModel):
ledger_entry_id: int
portfolio_id: str
order_id: str | None = None
execution_id: str | None = None
side: str
quantity: float
price: float
fee_amount: float
tax_amount: float
executed_at: datetime
recorded_at: datetime | None = None
class TradeHistoryResponse(BaseModel):
items: list[TradeHistoryItem]
total: int
# ---------------------------------------------------------------------------
# GET /v1/portfolios/{id}/pnl/realized (REQ-040)
# ---------------------------------------------------------------------------
class RealizedPnlItem(BaseModel):
symbol: str | None = None
executed_at: datetime | None = None
quantity: float
sell_price: float
cost_basis_per_unit: float
fee_amount: float
tax_amount: float
realized_pnl: float
order_id: str | None = None
execution_id: str | None = None
class RealizedPnlResponse(BaseModel):
total_realized_pnl: float
items: list[RealizedPnlItem]
cost_basis_from: datetime | None = None
data_completeness_warning: str | None = None