Files
portfolio-hub/docs/srs/Integration/req-041_wac-cost-basis-pnl-calculation.md
T
genichinandClaude Opus 4.8 bd34450ee9 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
2026-06-18 16:45:01 +09:00

97 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# REQ-041: WAC 기반 cost_basis 산출 및 실현 PnL 계산 로직 (service/domain 계층 격리)
- Status: Implemented
- Type: Integration
- Priority: High
- Owner: Justin Ko
## Intent
- portfolio_ledger의 buy/sell 기록에서 weighted average cost(WAC, 가중평균 취득단가) 방식으로 cost_basis를 계산하고, 매도 실현 손익 = (매도가 − 평균취득가) × 수량 − 수수료 − 세금 공식을 service 또는 domain 계층에 격리 구현한다(dcy-007 FR-3, OQ-1=WAC 확정).
- repository(SQL) 계층과 PnL 계산 로직을 분리하여 단위 테스트가 DB 없이 가능하도록 한다(dcy-007 NFR-3).
- R-1(이력 완전성) 완화를 위해 cost_basis 산출 시작 시점과 이력 불완전 감지 조건을 이 계층에서 판단하고 경고 신호를 반환한다.
## Requirement
- WAC cost_basis 계산 함수는 `app/services/` 또는 `app/domain/` 계층에 위치해야 하며, repository(psycopg/SQL) 직접 의존성 없이 순수 Python으로 구현해야 한다.
- 입력: portfolio_ledger 행 목록(side, quantity, price, fee_amount, tax_amount, executed_at, order_id, execution_id 포함) — 시간 순 정렬.
- 출력: 건별 매도에 대한 실현 PnL 계산 결과 목록, cost_basis_from(첫 buy 기록 시점), data_completeness_warning(이력 불완전 감지 시 경고 문자열, 없으면 None).
- WAC 계산 규칙:
- buy 시: `running_wac = (running_wac × running_qty + price × qty) / (running_qty + qty)`, `running_qty += qty`.
- sell 시: `realized_pnl = (sell_price − running_wac) × qty − fee − tax`, `running_qty -= qty`.
- 매도 수량이 보유 수량을 초과(running_qty < qty)하는 경우: 이력 불완전으로 판단하고 `data_completeness_warning` 반환 + 최선 추정으로 계속 계산(중단하지 않음).
- cost_basis_from은 입력 행 목록에서 가장 이른 buy 기록의 executed_at으로 설정. buy 기록이 없으면 None.
- 이 함수는 repository를 통해 얻은 원시 행 목록을 받으므로, repository는 관련 buy/sell 이력을 충분한 범위로 조회해 전달해야 한다(repository와 계층 경계 명시).
## Acceptance Criteria
1. Given buy 100주@10,000 → buy 100주@12,000 → sell 50주@15,000 fee=500 tax=200 순 행 목록 입력, When WAC 계산 함수 호출, Then cost_basis_per_unit=11,000(WAC = (100×10,000 + 100×12,000) / 200) + realized_pnl=199,300((15,000-11,000)×50 - 500 - 200) 반환.
2. Given buy 100주@10,000만 이력이 있고 sell 없음, When 함수 호출, Then items=[] + total_realized_pnl=0 + cost_basis_from=buy 시각 + data_completeness_warning=None 반환.
3. Given 이력이 sell부터 시작(buy 이력 없음 = running_qty=0에서 sell 발생), When 함수 호출, Then data_completeness_warning에 비어 있지 않은 경고 문자열 반환 + 계산은 중단하지 않고 최선 추정 계속.
4. Given 매도 수량이 현재 running_qty를 초과하는 경우, When 함수 호출, Then data_completeness_warning에 비어 있지 않은 경고 문자열 반환.
5. Given 동일 포트폴리오에 여러 번 buy/sell 반복(시간 순 정렬 입력 10건), When 함수 호출, Then 각 sell 시점 WAC가 해당 시점까지의 buy 누적 가중평균을 정확히 반영.
6. Given 입력 행 목록이 빈 배열, When 함수 호출, Then items=[] + total_realized_pnl=0 + cost_basis_from=None + data_completeness_warning=None 반환.
7. Given 이 계산 함수가 `app/services/` 또는 `app/domain/` 계층에 위치, When `grep -rn "psycopg\|asyncpg\|psycopg2" app/services/ app/domain/` 실행, Then WAC 계산 함수 파일에서 DB 라이브러리 직접 import가 0건(순수 Python 격리 확인, NFR-3 반영).
8. Given 계산 함수가 구현된 모듈, When 단위 테스트(`tests/test_wac_calculation.py`)를 pytest로 실행, Then DB 컨테이너 없이 통과(--no-db 마커 또는 DB 의존성 없는 pytest 실행).
## Impacted Area
- Docs:
- `docs/project-structure.md` — app/services/ 또는 app/domain/ 책임 갱신(WAC PnL 산출 로직 추가 반영)
- Modules:
- `app/services/portfolio_service.py` 또는 `app/domain/pnl.py`(신규) — WAC cost_basis 계산 함수 구현
- `app/repositories/postgres.py` — WAC 산출을 위한 buy/sell 이력 조회 메서드(`get_all_trades_for_pnl(portfolio_id, from_, to_, symbol)`) 추가 — service 계층이 consume
- Tests:
- `tests/test_wac_calculation.py` 신규 — AC-1~8 순수 단위 테스트(DB 불필요)
## Notes
- 구현 위치 결정(app/services/ vs app/domain/): 도메인 규칙으로서 domain 계층이 적절하나, 현행 코드 패턴(app/domain/services.py)을 따르는 것이 권장. 구현 단계에서 팀장 확인 필요.
- repository는 PnL 집계 기간(`from`~`to`) 이전의 buy 이력도 cost_basis 산출을 위해 조회해야 한다. 즉, 집계 기간 내 매도가 있을 경우 그 이전 buy 이력 전부를 시간 순으로 함께 전달해야 WAC가 정확하다. 이 쿼리 범위 설계는 구현 단계에서 확인.
- 이력 불완전 감지 조건은 현재 AC-3/AC-4의 2가지로 정의. 추가 조건은 구현 단계에서 확인.
- FIFO 방식은 OQ-1에서 명시적으로 제외(WAC 확정). 이 REQ는 WAC 전용이다.
- Source: dcy-007 FR-3, NFR-3, R-1, OQ-1(WAC 확정).
## Change Log
### CHG-20260618-01
- Date: 2026-06-18
- Author: Justin Ko
#### Change Summary
- 최초 생성 — dcy-007 confirm-discovery 후 draft-req 실행. WAC PnL 산출 로직 격리 산출물.
#### Intent
- dcy-007 FR-3(WAC cost_basis 계산) + NFR-3(service/domain 계층 격리, 단위 테스트 가능)을 Integration REQ로 정규화. R-1 완화(경고 필드 조건)를 AC-3/AC-4에 반영.
#### Acceptance Criteria Delta
- Added:
- AC-1~8 최초 작성
#### Impacted Area Delta
- Docs: docs/project-structure.md — 최초 지정
- Modules: app/services/portfolio_service.py 또는 app/domain/pnl.py, app/repositories/postgres.py — 최초 지정
- Tests: tests/test_wac_calculation.py — 최초 지정
#### Delivery Trace Delta
- Batches: none (미정)
- Releases: none (미정)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 계획 편입; REQ-040이 이 REQ에 의존하므로 동일 batch 편입 권장
#### Validation Plan
- pytest tests/test_wac_calculation.py (DB 불필요 단위 테스트) + AC-7 grep 검증
### CHG-20260618-02
- Date: 2026-06-18
- Author: 팀장 (Claude)
#### Change Summary
- confirm-req: `Proposed` → `Approved`. WAC 공식 확정(FIFO 제외), NFR-3 격리 AC-7/AC-8·R-1 완화 AC-3/AC-4 확인, blocker 없음. 구현 위치(domain vs service)는 구현 단계 결정. M1 fast-track.
### CHG-20260618-03
- Date: 2026-06-18
- Author: 팀장 (Claude)
#### Change Summary
- confirm-req-implemented: `Approved` → `Implemented`. Evidence: app/domain/pnl.py 구현, WAC 누적 정확성+199,300 검증, NFR-3 격리(psycopg import 0) QC 확인, R-1 경고 동작, test_wac_calculation.py 20 passed.