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:
@@ -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.
|
||||
Reference in New Issue
Block a user