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
@@ -0,0 +1,64 @@
# BAT-017 Planning
## Included REQ
| REQ | Type | Summary |
| --- | --- | --- |
| REQ-039 | Interface | GET /v1/portfolios/{id}/trades — 날짜/범위 필터 체결 내역 |
| REQ-040 | Interface | GET /v1/portfolios/{id}/pnl/realized — WAC 실현 PnL 집계 |
| REQ-041 | Integration | WAC cost_basis 산출 로직 (service/domain 격리) |
| REQ-042 | Testing | 테스트 커버리지 (WAC 기댓값 대조, 날짜 필터, 422, R-1 경고) |
## Delivery Plan
### 구현 순서
1. **REQ-041 먼저** — WAC 도메인 로직(`app/domain/pnl.py`) 신규. 순수 Python, DB 무의존. REQ-040 API가 이에 의존하므로 선행 필수.
2. **REQ-039** — GET /trades 엔드포인트 + repository 메서드(`list_trades_by_date`) + 스키마.
3. **REQ-040** — GET /pnl/realized 엔드포인트 + repository 메서드(`get_all_trades_for_pnl`) + 스키마.
4. **REQ-042** — 테스트 파일 3종 신규 작성.
### 파일 변경 목록
- **신규 생성**:
- `app/domain/pnl.py` — WAC 계산 함수 (REQ-041)
- `tests/test_wac_calculation.py` — WAC 순수 단위 테스트 (REQ-042)
- `tests/test_v1_portfolios_trades.py` — GET /trades 통합 테스트 (REQ-042)
- `tests/test_v1_portfolios_pnl.py` — GET /pnl/realized 통합 테스트 (REQ-042)
- **수정**:
- `app/schemas/v1.py` — TradeHistoryItem, TradeHistoryResponse, RealizedPnlItem, RealizedPnlResponse 추가
- `app/api/v1_portfolios.py` — GET /trades, GET /pnl/realized 엔드포인트 추가
- `app/repositories/postgres.py` — list_trades_by_date, get_all_trades_for_pnl 메서드 추가
- `app/repositories/portfolio_repository.py` — abstract method 추가
- `docs/batches/index.md` — BAT-017 register 행 추가
## Dependencies
- 기존 portfolio_ledger 테이블(TimescaleDB hypertable) — 스키마 변경 없음
- idx_portfolio_ledger_portfolio_executed 인덱스 — 날짜 범위 쿼리 활용
- 기존 require_read 인증 의존성 — 변경 없음
- REQ-041 WAC 로직은 REQ-040 엔드포인트의 service 계층 호출 대상
## Design Gate
**Design 필요: YES**
근거: 신규 엔드포인트 2개(HTTP 계약 확장), 신규 스키마 4종, 신규 domain 계층 모듈. 인터페이스 계약 및 계층 경계를 design.md에 명시해야 한다.
그러나 M1 fast-track 지시로 planning → design → implementation을 연속 수행한다. 설계는 simple하고 기존 코드 패턴을 그대로 따른다:
- WAC 함수: 순수 Python 함수, 입/출력 타입 명확, DB 무의존
- 두 엔드포인트: 기존 `_get_portfolio_or_404` + `require_read` 패턴 재사용
- Repository: 기존 `list_recent_trades` 패턴 확장
## Milestones
1. app/domain/pnl.py + test_wac_calculation.py 구현 및 단위 테스트 통과
2. GET /trades 엔드포인트 + 통합 테스트 구현
3. GET /pnl/realized 엔드포인트 + 통합 테스트 구현
4. 전체 pytest 실행 — 신규 3개 파일 + 기존 회귀 확인
## Risks
| Risk | 완화 |
| --- | --- |
| repository 메서드가 WAC 산출용 집계기간 이전 buy도 조회해야 함 | get_all_trades_for_pnl은 집계 기간과 무관하게 해당 portfolio+symbol의 전체 이력을 조회해 WAC 정확성 확보 |
| TestClient 통합 테스트가 DB 의존 | MagicMock 기반 repo 주입 패턴(기존 test_v1_portfolios_ledger.py 참조)으로 DB 없이 실행 |
| R-1 이력 불완전 경고 조건 | running_qty < sell_qty 또는 running_qty=0에서 sell 발생 시 data_completeness_warning 반환 (REQ-041 AC-3/AC-4) |