Files
portfolio-hub/docs/srs/Interface/req-040_realized-pnl-query-endpoint.md
T
genichinandClaude Sonnet 4.6 73414817e0 fix(pnl): 매수 수수료를 WAC cost basis에 편입 (REQ-041 CHG-20260626-01)
WAC 공식에 buy row의 fee_amount를 분자에 포함해 실현 PnL이 당일 net과 정확히 일치하도록 수정.
calculate_realized_pnl + calculate_current_wac 양 함수 동일 적용.
신규 단위 테스트 9개(TestBuyFeeInCostBasis 5 + TestCurrentWacBuyFeeInCostBasis 4) 추가.
change-req: REQ-041 AC-9/AC-10 추가, REQ-040/REQ-043 Notes 갱신, index 갱신.
전체 456 passed / 0 failed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
2026-06-26 20:34:03 +09:00

103 lines
7.7 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-040: GET /v1/portfolios/{id}/pnl/realized 실현 PnL 집계 엔드포인트 신설
- Status: Implemented
- Type: Interface
- Priority: High
- Owner: Justin Ko
## Intent
- 지정 기간 내 매도(side='sell') 체결에 대해 수수료·세금을 반영한 실현 PnL 합계 및 건별 내역을 반환하는 GET 엔드포인트를 신설한다.
- WAC(weighted average cost, 가중평균 취득단가) 방식으로 산출된 cost_basis 기반 실현 손익을 API 단일 호출로 제공하여 trex_core ledger_outbox 수기 계산 의존을 제거한다(dcy-007 FR-2, S-2).
- R-1(이력 완전성) 리스크를 완화하기 위해, cost_basis 산출 시작 시점을 응답에 포함하고 이력이 불완전할 경우 경고 필드(`data_completeness_warning`)를 응답에 포함한다(dcy-007 R-1 완화 방안).
## Requirement
- 시스템은 `GET /v1/portfolios/{portfolio_id}/pnl/realized` 엔드포인트를 제공해야 한다.
- `from`/`to` 날짜 범위 파라미터(YYYY-MM-DD)로 집계 기간을 지정하며, 두 파라미터 모두 필수다.
- 선택적 `symbol` 파라미터로 특정 종목 필터링을 지원한다.
- 응답은 다음을 포함해야 한다: 기간 합계 실현 PnL(`total_realized_pnl`), 건별 매도 내역 배열(`items`), cost_basis 산출 시작 시점(`cost_basis_from`), 이력 불완전 경고 필드(`data_completeness_warning: str | null`).
- 건별 항목은 symbol(또는 portfolio 식별 가능 필드), executed_at, quantity, sell_price, cost_basis_per_unit, fee_amount, tax_amount, realized_pnl을 포함해야 한다.
- 인증은 기존 read_token 이상 권한(DB 토큰)으로 접근 가능해야 하며, 별도 권한 추가는 없다(dcy-007 FR-4, A-3).
- 호출자 소유권 확인: 비소유자 비관리자 호출은 HTTP 403을 반환해야 한다.
- PnL 산출은 REQ-041(WAC 산출 로직)에 위임하며, 이 엔드포인트는 service 계층을 통해 산출 결과를 수신하고 HTTP 응답으로 직렬화한다.
## Acceptance Criteria
1. Given read_token 이상 권한, portfolio에 buy 100주@10,000 + buy 100주@12,000 이력 존재 후 sell 50주@15,000 fee=500 tax=200 실행, When `GET /v1/portfolios/{id}/pnl/realized?from=2026-06-01&to=2026-06-30`, Then HTTP 200 + `total_realized_pnl`이 WAC 공식((15,000 - 11,000) × 50 - 500 - 200 = 199,300)과 일치 + `items` 배열에 해당 매도 1건 포함 + 각 항목에 cost_basis_per_unit=11,000 포함(WAC = (100×10,000 + 100×12,000) / 200 = 11,000).
2. Given 동일 포트폴리오, When `GET /v1/portfolios/{id}/pnl/realized?from=2026-06-01&to=2026-06-30&symbol=005930.KS`, Then symbol 필터가 적용되어 해당 symbol의 매도 건만 집계.
3. Given `from` 파라미터 누락 또는 `to` 파라미터 누락, When 호출, Then HTTP 422 반환.
4. Given 지정 기간에 매도 체결 0건, When 호출, Then HTTP 200 + `total_realized_pnl=0` + `items=[]` 반환(404 아님).
5. Given portfolio_ledger 이력이 포트폴리오 생성 초기부터 불완전한 경우(buy 이력 일부 누락 감지), When 호출, Then 응답 `data_completeness_warning` 필드에 경고 메시지 포함 + `cost_basis_from`에 집계 시작 시점 명시(R-1 완화 반영).
6. Given portfolio_ledger 이력이 완전한 경우, When 호출, Then 응답 `data_completeness_warning` 필드가 null.
7. Given 비소유자 사용자 token(admin 아님), When 타인 portfolio_id 조회, Then HTTP 403 반환.
8. Given 존재하지 않는 portfolio_id, When 호출, Then HTTP 404 반환.
9. Given 엔드포인트 구현 완료, When OpenAPI `/docs` 또는 `openapi.json` 확인, Then `GET /v1/portfolios/{portfolio_id}/pnl/realized` 경로가 파라미터 설명 및 응답 스키마(`data_completeness_warning` 포함)와 함께 문서화되어 있음(NFR-2 반영).
10. Given 30일 범위 쿼리, When portfolio_ledger 인덱스 활용 환경에서 호출, Then p95 응답 시간 500ms 이하(NFR-1 반영 — 통합/성능 테스트 환경에서 검증).
## Impacted Area
- Docs:
- `docs/runtime-flows.md` — `GET /v1/portfolios/{id}/pnl/realized` 흐름 추가 필요
- `docs/project-structure.md` — app/api/v1_portfolios.py 책임 갱신, app/schemas/v1.py RealizedPnlResponse 추가
- Modules:
- `app/api/v1_portfolios.py` — GET 엔드포인트 추가(날짜/symbol 파라미터 파싱, 소유권 확인, service 계층 위임)
- `app/services/portfolio_service.py` 또는 신규 — REQ-041 WAC 산출 로직 호출 + data_completeness_warning 판단
- `app/repositories/postgres.py` — `aggregate_realized_pnl(portfolio_id, from_, to_, symbol)` 메서드 추가 (매도 이력 + WAC 산출용 buy 이력 조회)
- `app/repositories/portfolio_repository.py` — abstract method 추가
- `app/schemas/v1.py` — `RealizedPnlItem`, `RealizedPnlResponse` 스키마 추가(`data_completeness_warning: str | None`, `cost_basis_from: date | None` 포함)
- Tests:
- `tests/test_v1_portfolios_pnl.py` 신규 — AC-1~10 커버
## Notes
- AC-1의 수치 근거: WAC = (100×10,000 + 0 + 100×12,000 + 0) / 200 = 11,000(buy fee=0이므로 기존과 동일). 실현 PnL = (15,000 - 11,000) × 50 - 500 - 200 = 200,000 - 700 = 199,300.
- **WAC 정의 변경(2026-06-26)**: REQ-041 CHG-20260626-01에 따라 WAC 공식에 매수 수수료가 편입됨. `running_wac = (running_wac*qty + price*qty + buy_fee) / new_total_qty`. 이 엔드포인트가 반환하는 `cost_basis_per_unit` 및 `realized_pnl`은 해당 정의를 따른다. AC-1 수치는 buy fee=0인 케이스라 기댓값 불변.
- `data_completeness_warning` 트리거 조건(이력 불완전 감지 기준)은 REQ-041 구현 단계에서 구체화. 현재 가정: portfolio 생성 시점보다 이른 buy 이력이 없거나, cost_basis 산출 대상 buy 합산 수량이 매도 수량보다 적을 경우.
- 미실현 PnL(현재가 연동)은 Out of Scope(dcy-007 범위 경계 확인).
- 전체 portfolio 집계 PnL 뷰(OQ-2 결정: Out of Scope) — 단일 portfolio 단위만.
- Source: dcy-007 FR-2, FR-3, FR-4, NFR-1, NFR-2, R-1, S-2.
## Change Log
### CHG-20260618-01
- Date: 2026-06-18
- Author: Justin Ko
#### Change Summary
- 최초 생성 — dcy-007 confirm-discovery 후 draft-req 실행. 실현 PnL 집계 API 산출물.
#### Intent
- dcy-007 FR-2/FR-4(실현 PnL 엔드포인트 신설 + read_token 인증)를 구현 가능한 Interface REQ로 정규화. R-1 완화 방안(경고 필드 + cost_basis_from)을 AC-5/AC-6에 반드시 반영.
#### Acceptance Criteria Delta
- Added:
- AC-1~10 최초 작성
#### Impacted Area Delta
- Docs: docs/runtime-flows.md, docs/project-structure.md — 최초 지정
- Modules: app/api/v1_portfolios.py, app/services/portfolio_service.py, app/repositories/postgres.py, app/repositories/portfolio_repository.py, app/schemas/v1.py — 최초 지정
- Tests: tests/test_v1_portfolios_pnl.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-041 의존성 확인
#### Validation Plan
- pytest TestClient로 AC-1~9 검증; AC-10 성능은 통합/성능 테스트 환경에서 검증
### CHG-20260618-02
- Date: 2026-06-18
- Author: 팀장 (Claude)
#### Change Summary
- confirm-req: `Proposed` → `Approved`. WAC 수치 AC-1(199,300)·R-1 경고 필드 AC-5/AC-6 확인, OQ-2 OoS 반영, blocker 없음. REQ-041 의존(동일 batch). M1 fast-track.
### CHG-20260618-03
- Date: 2026-06-18
- Author: 팀장 (Claude)
#### Change Summary
- confirm-req-implemented: `Approved` → `Implemented`. Evidence: BAT-017 release-candidate, 독립 QC PASS-WITH-NOTES, WAC 199,300 단위+e2e 검증, R-1 경고필드 정상, AC-1~9 PASS(AC-10 환경제약). 잔여(비차단): symbol 필터 edge 테스트 공백.