docs(dcy-007): draft + approve REQ-039~042 (trade history + realized PnL API)

dcy-007 분해 완료: 체결내역 조회(REQ-039, GET /trades), 실현 PnL 집계(REQ-040, GET /pnl/realized with WAC), WAC 산출 로직(REQ-041, service/domain 격리), 테스트 커버리지(REQ-042).

팀장 confirm-req 완료:
- 4건 모두 Proposed → Approved (dcy-007 결정·발견사항 반영, R-1 경고필드 포함, blocker 없음)
- M1(6/22) fast-track, 단일 batch 예정
- docs/srs/index.md: Next ID REQ-043, Register 4건 Approved, Change Log 갱신
- docs/discovery/dcy-007: 생성된 REQ 참조 업데이트

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:07:33 +09:00
co-authored by Claude Opus 4.8
parent b3186f14a0
commit f37ef87c8c
6 changed files with 385 additions and 3 deletions
@@ -5,10 +5,14 @@
- 문서 ID: dcy-007_20260616_daily-trade-history-and-realized-pnl-query-api - 문서 ID: dcy-007_20260616_daily-trade-history-and-realized-pnl-query-api
- 이슈명: 일일 거래내역 + 실현 PnL 조회 API (운영 리포트/프론트 데이터 소스) - 이슈명: 일일 거래내역 + 실현 PnL 조회 API (운영 리포트/프론트 데이터 소스)
- 작성 시각(KST): 2026-06-16 14:05 KST - 작성 시각(KST): 2026-06-16 14:05 KST
- 마지막 갱신 시각(KST): 2026-06-18 (confirm-discovery) - 마지막 갱신 시각(KST): 2026-06-18 (draft-req)
- 대체됨: 없음 - 대체됨: 없음
- 후속 Discovery 참조: 없음 - 후속 Discovery 참조: 없음
- 생성된 REQ 참조: 없음 - 생성된 REQ 참조:
- REQ-039: GET /v1/portfolios/{id}/trades 체결 내역 조회 엔드포인트 신설 (Interface, Proposed) → [req-039](../srs/Interface/req-039_trade-history-query-endpoint.md)
- REQ-040: GET /v1/portfolios/{id}/pnl/realized 실현 PnL 집계 엔드포인트 신설 (Interface, Proposed) → [req-040](../srs/Interface/req-040_realized-pnl-query-endpoint.md)
- REQ-041: WAC 기반 cost_basis 산출 및 실현 PnL 계산 로직 (Integration, Proposed) → [req-041](../srs/Integration/req-041_wac-cost-basis-pnl-calculation.md)
- REQ-042: 체결 내역/실현 PnL API 테스트 커버리지 (Testing, Proposed) → [req-042](../srs/Testing/req-042_trade-pnl-api-test-coverage.md)
# 1. 계획 상태 요약 # 1. 계획 상태 요약
현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. 현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다.
@@ -0,0 +1,89 @@
# REQ-041: WAC 기반 cost_basis 산출 및 실현 PnL 계산 로직 (service/domain 계층 격리)
- Status: Approved
- 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.
@@ -0,0 +1,89 @@
# REQ-039: GET /v1/portfolios/{id}/trades 체결 내역 조회 엔드포인트 신설
- Status: Approved
- Type: Interface
- Priority: High
- Owner: Justin Ko
## Intent
- portfolio_ledger(TimescaleDB hypertable)에 이미 적재된 체결 기록(side, quantity, price, fee_amount, tax_amount, executed_at 등)을 날짜/범위 필터로 외부에 노출하는 GET 엔드포인트를 신설한다.
- 현재 `list_recent_trades(portfolio_id, limit=5)` 내부 메서드만 존재하며 날짜 필터 없이 최근 5건만 반환하는 한계를 해소한다.
- 운영자 및 프론트엔드가 trex_core ledger_outbox 수기 조회 없이 portfolio-hub API 단독으로 체결 내역을 조회할 수 있게 한다(dcy-007 FR-1, S-1).
## Requirement
- 시스템은 `GET /v1/portfolios/{portfolio_id}/trades` 엔드포인트를 제공해야 한다.
- `date`(YYYY-MM-DD) 단일 날짜 파라미터 또는 `from`/`to` 날짜 범위 파라미터로 portfolio_ledger를 필터링해 체결 기록 목록을 반환해야 한다.
- 응답 각 항목은 side, quantity, price, fee_amount, tax_amount, executed_at, order_id, execution_id 필드를 포함해야 한다.
- 인증은 기존 read_token 이상 권한(DB 토큰)으로 접근 가능해야 하며, 별도 권한 추가는 없다(dcy-007 FR-4, A-3).
- 호출자 소유권 확인: 해당 portfolio_id가 호출 토큰 소유자의 portfolio인지 확인하고, 비소유자 비관리자 호출은 HTTP 403을 반환해야 한다.
## Acceptance Criteria
1. Given read_token 이상 권한 보유 사용자, portfolio_id에 2026-06-15 체결 기록 3건 존재, When `GET /v1/portfolios/{id}/trades?date=2026-06-15`, Then HTTP 200 + items 배열에 3건 반환, 각 항목에 side/quantity/price/fee_amount/tax_amount/executed_at/order_id/execution_id 포함.
2. Given 동일 포트폴리오, 2026-06-01~2026-06-15 기간에 체결 기록 7건 존재, When `GET /v1/portfolios/{id}/trades?from=2026-06-01&to=2026-06-15`, Then HTTP 200 + items 7건 반환, from/to 범위 경계 날짜 포함.
3. Given `date` 파라미터 누락 AND `from`/`to` 모두 누락, When 호출, Then HTTP 422 + 날짜 파라미터 필요 오류 메시지 반환.
4. Given `date` 형식이 YYYY-MM-DD가 아닌 값(예: "20260615", "2026/06/15"), When 호출, Then HTTP 422 반환.
5. Given 해당 날짜 범위에 체결 기록 0건, When 호출, Then HTTP 200 + `{"items": [], "total": 0}` 반환(404 아님).
6. Given 비소유자 사용자 token(admin 아님), When 타인 portfolio_id 조회, Then HTTP 403 반환.
7. Given 존재하지 않는 portfolio_id, When 호출, Then HTTP 404 반환.
8. Given 엔드포인트 구현 완료, When OpenAPI `/docs` 또는 `openapi.json` 확인, Then `GET /v1/portfolios/{portfolio_id}/trades` 경로가 파라미터 설명과 함께 문서화되어 있음(NFR-2 반영).
9. Given 30일 범위(`from`=D-30, `to`=today) 쿼리, When portfolio_ledger idx_portfolio_ledger_portfolio_executed 인덱스 사용 환경에서 호출, Then p95 응답 시간 500ms 이하(NFR-1 반영 — 통합/성능 테스트 환경에서 검증).
## Impacted Area
- Docs:
- `docs/runtime-flows.md` — `GET /v1/portfolios/{id}/trades` 흐름 추가 필요
- `docs/project-structure.md` — app/api/v1_portfolios.py 책임 갱신, app/schemas/v1.py TradeHistoryResponse 추가
- Modules:
- `app/api/v1_portfolios.py` — GET 엔드포인트 추가(날짜 파라미터 파싱, 소유권 확인, repository 위임)
- `app/repositories/postgres.py` — `list_trades_by_date(portfolio_id, date, from_, to_)` 메서드 추가
- `app/repositories/portfolio_repository.py` — abstract method 추가
- `app/schemas/v1.py` — `TradeHistoryItem`, `TradeHistoryResponse` 스키마 추가
- Tests:
- `tests/test_v1_portfolios_trades.py` 신규 — AC-1~9 커버
## Notes
- `date` vs `from`/`to` 파라미터 중 하나만 허용하거나 `date`가 `from`=`to`의 단축형으로 동작하는지 구현 단계에서 결정. 단, AC-3(둘 다 없으면 422)는 확정.
- portfolio_ledger TimescaleDB hypertable의 파티션 키는 `recorded_at`(또는 `executed_at`)이며, 날짜 범위 쿼리는 기존 `idx_portfolio_ledger_portfolio_executed` 인덱스를 활용해야 한다(NFR-1, dcy-007 R-2 완화).
- 페이지네이션 요구사항은 이번 범위에 명시되지 않음. 구현 단계에서 대용량 결과 처리 방식 결정 필요(NFR 미정 — 현재 범위 밖으로 이연).
- DB 마이그레이션 불필요 — 기존 portfolio_ledger 스키마로 조회 가능(dcy-007 영향 범위 확인).
- Source: dcy-007 FR-1, FR-4, NFR-1, NFR-2, S-1.
## Change Log
### CHG-20260618-01
- Date: 2026-06-18
- Author: Justin Ko
#### Change Summary
- 최초 생성 — dcy-007 confirm-discovery 후 draft-req 실행. 일일 거래내역 조회 API 산출물.
#### Intent
- dcy-007 FR-1(체결 내역 조회 엔드포인트 신설)을 구현 가능한 Interface REQ로 정규화.
#### Acceptance Criteria Delta
- Added:
- AC-1~9 최초 작성
#### Impacted Area Delta
- Docs: docs/runtime-flows.md, docs/project-structure.md — 최초 지정
- Modules: app/api/v1_portfolios.py, app/repositories/postgres.py, app/repositories/portfolio_repository.py, app/schemas/v1.py — 최초 지정
- Tests: tests/test_v1_portfolios_trades.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 계획 편입
#### Validation Plan
- pytest TestClient로 AC-1~8 검증; AC-9 성능은 통합/성능 테스트 환경에서 검증
### CHG-20260618-02
- Date: 2026-06-18
- Author: 팀장 (Claude)
#### Change Summary
- confirm-req: `Proposed` → `Approved`. dcy-007 FR-1/FR-4 정합, AC 구체적·test 가능, blocker 없음. `date` vs `from`/`to` 상호작용은 구현 단계 결정(AC-3 422 확정). M1 fast-track — REQ-040~042와 동일 batch.
@@ -0,0 +1,94 @@
# REQ-040: GET /v1/portfolios/{id}/pnl/realized 실현 PnL 집계 엔드포인트 신설
- Status: Approved
- 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 + 100×12,000) / 200 = 11,000. 실현 PnL = (15,000 - 11,000) × 50 - 500 - 200 = 200,000 - 700 = 199,300.
- `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.
@@ -0,0 +1,100 @@
# REQ-042: 체결 내역/실현 PnL API 테스트 커버리지 (WAC 시나리오 기댓값 대조, 날짜 필터, 422)
- Status: Approved
- Type: Testing
- Priority: High
- Owner: Justin Ko
## Intent
- REQ-039(체결 내역 조회), REQ-040(실현 PnL 집계), REQ-041(WAC 산출 로직)의 구현 산출물에 대해, WAC 시나리오 기댓값 대조·날짜 필터 경계·422 오류·R-1 경고 필드를 포함한 단위/통합 테스트를 정의한다.
- 테스트는 구현자(delivery)가 아닌 독립 QC 시점에서도 검증 가능해야 하며, AC를 충족하는 최소 evidence를 제공해야 한다(dcy-007 NFR-3, S-1, S-2).
- WAC 기댓값 수기 계산 대조(시나리오별 fixture 사용)와 날짜 필터 경계 테스트를 의무 포함한다.
## Requirement
- 아래 테스트 파일을 신규 작성해야 한다:
- `tests/test_wac_calculation.py` — WAC 산출 로직 순수 단위 테스트(DB 불필요).
- `tests/test_v1_portfolios_trades.py` — 체결 내역 엔드포인트 통합 테스트(TestClient).
- `tests/test_v1_portfolios_pnl.py` — 실현 PnL 엔드포인트 통합 테스트(TestClient).
- 각 테스트 파일은 pytest 단독 실행 가능해야 한다(`pytest tests/test_wac_calculation.py` 등).
- 테스트 픽스처는 portfolio_ledger INSERT로 알려진 buy/sell 데이터를 설정하고, 수기 계산 기댓값과 API 응답을 대조해야 한다.
## Acceptance Criteria
1. Given `tests/test_wac_calculation.py` 실행, When `pytest tests/test_wac_calculation.py`, Then 다음 시나리오 모두 통과:
- 단순 buy → sell WAC 수기 기댓값 일치(dcy-007 S-2 기준)
- 다중 buy → 부분 sell WAC(AC-1 of REQ-041 기댓값: 199,300)
- 빈 입력 → total_pnl=0, cost_basis_from=None
- sell 수량 초과(running_qty=0) → data_completeness_warning 비어 있지 않음
2. Given `tests/test_v1_portfolios_trades.py` 실행, When `pytest tests/test_v1_portfolios_trades.py`, Then 다음 시나리오 모두 통과:
- `date=YYYY-MM-DD` 단일 날짜 필터 — 해당일 건수 일치
- `from`/`to` 범위 필터 — 경계 날짜 포함 건수 일치
- 날짜 파라미터 누락 → HTTP 422
- 날짜 형식 오류 → HTTP 422
- 해당 기간 0건 → HTTP 200 + empty items
- 비소유자 token → HTTP 403
- 존재하지 않는 portfolio_id → HTTP 404
3. Given `tests/test_v1_portfolios_pnl.py` 실행, When `pytest tests/test_v1_portfolios_pnl.py`, Then 다음 시나리오 모두 통과:
- buy 2회 + sell 1회 픽스처, `total_realized_pnl` 수기 기댓값 일치(REQ-040 AC-1 기댓값: 199,300)
- `from`/`to` 파라미터 누락 → HTTP 422
- 기간 내 매도 0건 → HTTP 200 + total_realized_pnl=0 + items=[]
- data_completeness_warning 필드: 이력 완전 시 null, 불완전 시 비어 있지 않음(R-1 경고 반영)
- symbol 필터 적용 — 해당 symbol 매도만 집계
- 비소유자 token → HTTP 403
4. Given 3개 테스트 파일 전체 실행, When `pytest tests/test_wac_calculation.py tests/test_v1_portfolios_trades.py tests/test_v1_portfolios_pnl.py -v`, Then 전체 PASSED(0 failed) + 각 파일 실행 결과 출력 존재.
5. Given `tests/test_wac_calculation.py`, When DB 컨테이너 없이 실행(`PORTFOLIOHUB_DB_ENABLED` 미설정 환경), Then WAC 단위 테스트가 통과(DB 격리 확인, REQ-041 AC-8 연동).
6. Given REQ-039~041 구현 후 기존 테스트 스위트(`pytest` 전체) 실행, When 실행, Then 기존 통과 테스트가 모두 계속 통과(회귀 없음).
## Impacted Area
- Docs:
- 없음 (테스트 파일만 추가)
- Modules:
- 없음 (기존 소스 파일 수정 없음 — 테스트 전용 REQ)
- Tests:
- `tests/test_wac_calculation.py` 신규
- `tests/test_v1_portfolios_trades.py` 신규
- `tests/test_v1_portfolios_pnl.py` 신규
## Notes
- 이 REQ는 REQ-039/040/041의 검증 근거를 명시적으로 확보하기 위한 Testing REQ다. 구현 코드를 직접 생산하지 않으므로 별도 REQ로 분리.
- 통합 테스트(TestClient 기반)는 DB-enabled 환경(`PORTFOLIOHUB_DB_ENABLED=true`)에서 실행하거나 모킹 전략을 사용할 수 있다. 모킹 전략과 실 DB 사용 여부는 구현 단계에서 결정.
- AC-1의 수기 기댓값(199,300)은 REQ-041 AC-1과 동일한 픽스처를 사용해야 한다.
- dcy-007 S-1/S-2 성공 기준의 측정 근거는 이 REQ의 테스트 실행 결과가 된다.
- Source: dcy-007 NFR-3, S-1, S-2, R-1.
## 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 NFR-3(PnL 로직 단위 테스트), S-1/S-2(기댓값 대조 통합 테스트)를 Testing REQ로 정규화. WAC 시나리오 기댓값과 R-1 경고 필드 검증을 의무 포함.
#### Acceptance Criteria Delta
- Added:
- AC-1~6 최초 작성
#### Impacted Area Delta
- Tests: tests/test_wac_calculation.py, tests/test_v1_portfolios_trades.py, 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 후 REQ-039/040/041과 동일 batch 편입(검증 REQ로서 구현 REQ와 동행)
#### Validation Plan
- pytest 실행으로 AC-1~6 직접 검증; 전체 스위트 회귀(AC-6)는 기존 pytest baseline과 비교
### CHG-20260618-02
- Date: 2026-06-18
- Author: 팀장 (Claude)
#### Change Summary
- confirm-req: `Proposed` → `Approved`. WAC 시나리오 기댓값·날짜필터·422·R-1 경고 검증 AC가 REQ-039~041과 정합, blocker 없음. 구현 REQ와 동일 batch. M1 fast-track.
+7 -1
View File
@@ -74,7 +74,7 @@
## Next Requirement ID ## Next Requirement ID
- Current: `REQ-039` - Current: `REQ-043`
- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. - Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다.
## Requirement Register ## Requirement Register
@@ -119,9 +119,15 @@
| REQ-036 | portfolio_ledger 회계 원장 통합 마이그레이션 (ledger_trades 흡수/DROP) | Migration | Implemented | High | Justin Ko | [req-036](./Migration/req-036_portfolio-ledger-consolidation-migration.md) | | REQ-036 | portfolio_ledger 회계 원장 통합 마이그레이션 (ledger_trades 흡수/DROP) | Migration | Implemented | High | Justin Ko | [req-036](./Migration/req-036_portfolio-ledger-consolidation-migration.md) |
| REQ-037 | 신 path 회계 책임 단일화 + 응답에 portfolio 상태 포함 | Interface | Implemented | High | Justin Ko | [req-037](./Interface/req-037_ledger-trade-write-accounting-and-state-response.md) | | REQ-037 | 신 path 회계 책임 단일화 + 응답에 portfolio 상태 포함 | Interface | Implemented | High | Justin Ko | [req-037](./Interface/req-037_ledger-trade-write-accounting-and-state-response.md) |
| REQ-038 | 레거시 buy/sell endpoint deprecated 표기 | Interface | Implemented | Low | Justin Ko | [req-038](./Interface/req-038_legacy-buy-sell-endpoint-deprecation.md) | | REQ-038 | 레거시 buy/sell endpoint deprecated 표기 | Interface | Implemented | Low | Justin Ko | [req-038](./Interface/req-038_legacy-buy-sell-endpoint-deprecation.md) |
| REQ-039 | GET /v1/portfolios/{id}/trades 체결 내역 조회 엔드포인트 신설 | Interface | Approved | High | Justin Ko | [req-039](./Interface/req-039_trade-history-query-endpoint.md) |
| REQ-040 | GET /v1/portfolios/{id}/pnl/realized 실현 PnL 집계 엔드포인트 신설 | Interface | Approved | High | Justin Ko | [req-040](./Interface/req-040_realized-pnl-query-endpoint.md) |
| REQ-041 | WAC 기반 cost_basis 산출 및 실현 PnL 계산 로직 (service/domain 계층 격리) | Integration | Approved | High | Justin Ko | [req-041](./Integration/req-041_wac-cost-basis-pnl-calculation.md) |
| REQ-042 | 체결 내역/실현 PnL API 테스트 커버리지 (WAC 시나리오 기댓값 대조, 날짜 필터, 422) | Testing | Approved | High | Justin Ko | [req-042](./Testing/req-042_trade-pnl-api-test-coverage.md) |
## Recent Change Log Summary ## Recent Change Log Summary
- 2026-06-18: REQ-039/040/041/042 `Proposed` → `Approved` (confirm-req 일괄 승인, dcy-007 결정 정합·R-1 완화 반영·blocker 없음, M1 fast-track 동일 batch).
- 2026-06-18: REQ-039/040/041/042 신규 (Proposed) — dcy-007(일일 거래내역 + 실현 PnL 조회 API) draft-req 산출물. REQ-039 체결 내역 조회(Interface), REQ-040 실현 PnL 집계(Interface), REQ-041 WAC cost_basis 산출 로직(Integration), REQ-042 테스트 커버리지(Testing). OQ-1=WAC(사장님 결정), OQ-2=Out of Scope. R-1 경고 필드(data_completeness_warning + cost_basis_from)가 REQ-040 AC-5/AC-6 및 REQ-042 AC-1/AC-3에 반영.
- 2026-06-05: REQ-036/037/038 Approved → Implemented (CHG-20260605-04/04/03) — BAT-016 confirm-batch-verification 완료. 전체 376 passed(실 DB 통합 3건 포함). verify 단계에서 hypertable ON CONFLICT 불가 + 공유 connection close 결함 2건 발견·수정, REQ-036/037 CHG-20260605-03으로 idempotency를 DB partial unique → application-level(SELECT-before-insert under FOR UPDATE)로 재정의. BAT-016 release-candidate. REL-003 staging 이연: alembic fresh-DB 적용 + 정밀도/action 분포. - 2026-06-05: REQ-036/037/038 Approved → Implemented (CHG-20260605-04/04/03) — BAT-016 confirm-batch-verification 완료. 전체 376 passed(실 DB 통합 3건 포함). verify 단계에서 hypertable ON CONFLICT 불가 + 공유 connection close 결함 2건 발견·수정, REQ-036/037 CHG-20260605-03으로 idempotency를 DB partial unique → application-level(SELECT-before-insert under FOR UPDATE)로 재정의. BAT-016 release-candidate. REL-003 staging 이연: alembic fresh-DB 적용 + 정밀도/action 분포.
- 2026-06-05: REQ-036/037/038 Proposed → Approved (CHG-20260605-02 각각) — confirm-req 무인자 일괄 모드 실행, 승인 게이트(필수 필드/AC 구체성/Change Log) 모두 충족, blocker 없음. dcy-006 산출물 3건 모두 Approved. REQ-036(마이그레이션) 선행 의존성 명시. - 2026-06-05: REQ-036/037/038 Proposed → Approved (CHG-20260605-02 각각) — confirm-req 무인자 일괄 모드 실행, 승인 게이트(필수 필드/AC 구체성/Change Log) 모두 충족, blocker 없음. dcy-006 산출물 3건 모두 Approved. REQ-036(마이그레이션) 선행 의존성 명시.
- 2026-06-05: REQ-036/037/038 신규 (Proposed) — dcy-006(ledger trade write 회계 책임 단일화) draft-req 산출물. REQ-036 portfolio_ledger/ledger_trades 통합 마이그레이션(Migration), REQ-037 신 path 회계 책임 단일화 + 응답에 cash_balance_after/asset_quantity_after 포함(Interface), REQ-038 레거시 buy/sell deprecated 표기(Interface). 3건 모두 별도 REL(REL-003 등) 분리 배포 예정(dcy-006 A-4). - 2026-06-05: REQ-036/037/038 신규 (Proposed) — dcy-006(ledger trade write 회계 책임 단일화) draft-req 산출물. REQ-036 portfolio_ledger/ledger_trades 통합 마이그레이션(Migration), REQ-037 신 path 회계 책임 단일화 + 응답에 cash_balance_after/asset_quantity_after 포함(Interface), REQ-038 레거시 buy/sell deprecated 표기(Interface). 3건 모두 별도 REL(REL-003 등) 분리 배포 예정(dcy-006 A-4).