docs(dcy-007): confirm-discovery — daily trade history + realized PnL API

일일 거래내역(GET /trades) + 실현 PnL(GET /pnl/realized) 조회 API.
운영 모니터링 목적으로 issue #11에서 제기된 필요성.
사장님 결정: OQ-1 실현 PnL=weighted average cost(WAC),
OQ-2 전체 portfolio 집계=Out of Scope.
상태 confirmed, Ready for REQ Drafting: true.
M1(6/22) fast-track 대상.

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 15:58:54 +09:00
co-authored by Claude Opus 4.8
parent 63e4ceffac
commit b3186f14a0
2 changed files with 207 additions and 0 deletions
@@ -0,0 +1,206 @@
# 0. 문서 상태
이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다.
- 상태: confirmed
- 문서 ID: dcy-007_20260616_daily-trade-history-and-realized-pnl-query-api
- 이슈명: 일일 거래내역 + 실현 PnL 조회 API (운영 리포트/프론트 데이터 소스)
- 작성 시각(KST): 2026-06-16 14:05 KST
- 마지막 갱신 시각(KST): 2026-06-18 (confirm-discovery)
- 대체됨: 없음
- 후속 Discovery 참조: 없음
- 생성된 REQ 참조: 없음
# 1. 계획 상태 요약
현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다.
- 연계 문서 현황:
- 관련 점수 파일: 없음
- 관련 TODO/메모: GitHub issue #11 (https://github.com/fourblocks2020/portfolio-hub/issues/11)
- 해석:
- GitHub issue #11(2026-06-15, P1)에서 제기 — portfolio-hub가 체결 기록의 시스템 오브 레코드임에도 조회 API가 없어 운영 리포트 작성 시 trex_core ledger_outbox를 수기 계산해야 하는 상황이다.
- portfolio_ledger(TimescaleDB hypertable)에 side·quantity·price·fee_amount·tax_amount·executed_at이 이미 적재되어 있으므로 조회 엔드포인트와 PnL 집계 로직 추가로 해결 가능하다. 단, 실현 PnL 산출 공식(평균취득가 vs FIFO)과 cost_basis 저장 방식은 REQ 초안 이전에 확정이 필요하다.
# 2. 요구사항 판정 결과
이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다.
- 판정: 신규
- 근거
- dcy-006(ledger trade write 회계 책임 단일화)은 체결 *쓰기* 경로(POST /v1/portfolios/{id}/ledger/trades) 문제 정의이고, 이번은 체결 *조회/집계*(GET) 경로로 문제 정의가 완전히 다르다.
- 기존 dcy-001~006 모두 confirmed 상태이며 PnL 조회 관련 FR/NFR을 포함한 Discovery가 없다.
# 3. 문제점의 요약
문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다.
- 현재 상태
- portfolio_ledger에는 체결 기록(side, quantity, price, fee_amount, tax_amount, executed_at 등)이 적재되어 있으나, 외부에서 조회할 수 있는 GET 엔드포인트가 존재하지 않는다. 현재 `list_recent_trades(portfolio_id, limit=5)` 내부 메서드만 있으며 날짜 필터 없이 최근 5건만 반환한다.
- 일일 실현 PnL을 확인하려면 trex_core의 ledger_outbox를 직접 조회해 수기 계산해야 하며, portfolio-hub가 시스템 오브 레코드임에도 운영 가시성이 없다.
- 기대 상태
- 운영자 및 프론트엔드가 portfolio-hub API를 통해 일자별 체결 내역(매수/매도, 수량, 체결가, 수수료, 세금)을 직접 조회할 수 있다.
- 기간·종목별 실현 PnL(수수료·세금 반영)을 portfolio-hub API 단일 호출로 조회할 수 있어 trex_core 의존 수기 계산이 불필요해진다.
- 이해관계자
- 운영자 / 리포트 담당자: 일일 PnL 보고서 자동화 목적으로 API 사용
- 프론트엔드 / 대시보드: PnL 데이터 소스로 portfolio-hub API 호출
- trex-core daemon: 현재 ledger_outbox를 PnL 산출 근거로 사용 중 — portfolio-hub 조회 API 도입 후 역할 분담 재정리 대상
- 제약사항
- portfolio_ledger는 TimescaleDB hypertable로, 파티션 키(recorded_at) 기반 범위 쿼리는 효율적이나 cost_basis(평균취득가)는 테이블에 직접 저장되어 있지 않다.
- 미실현 PnL 계산에는 현재가(kis-data-hub 연동)가 필요하며 이번 Discovery 범위 밖이다.
## 주요 사용자 시나리오
사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다.
- 운영자가 2026-06-15 체결된 포트폴리오 거래 내역(매도 2건, 매수 1건)을 조회하고 수수료·세금 포함 순 실현 손익을 확인한다.
- 프론트엔드 대시보드가 특정 포트폴리오의 월간 실현 PnL 합계를 API 한 번 호출로 렌더링한다.
- 운영 리포트 스크립트가 매일 오전 portfolio-hub API를 호출해 전일 체결 내역 CSV를 자동 생성하고, trex_core ledger_outbox 수기 조회를 대체한다.
# 4. 이번에 정의할 변경
이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다.
- 일자별 체결 내역 조회 엔드포인트 신설 — `GET /v1/portfolios/{portfolio_id}/trades` (날짜·방향 필터 지원)
- 기간별 실현 PnL 집계 엔드포인트 신설 — `GET /v1/portfolios/{portfolio_id}/pnl/realized` (수수료·세금 반영, 종목 단위 집계)
- portfolio_ledger 기반 PnL 산출 로직 구현 — 평균취득가 산출 방식 또는 FIFO cost_basis 추적 방식 선택 및 repository/service 계층 추가
## 영향 범위
변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다.
- 코드 영향:
- `app/api/v1_portfolios.py` — GET 엔드포인트 2개 추가
- `app/repositories/postgres.py` — `list_trades_by_date`, `aggregate_realized_pnl` 메서드 추가
- `app/schemas/v1.py` — 응답 스키마(TradeHistoryResponse, RealizedPnlResponse) 추가
- `app/services/portfolio_service.py` — PnL 산출 로직(cost_basis 계산) 추가 가능성
- 문서 영향:
- `docs/discovery/index.md` — 이번 Discovery 행 추가
- REQ 문서(draft-req 이후 생성)
- 운영 영향:
- trex-core와 운영 리포트의 PnL 산출 역할 분담 재정리 필요(별도 조율, 이번 배포 범위 밖)
- DB 마이그레이션 불필요 — 기존 portfolio_ledger 스키마로 조회 가능
# 5. 요구사항 목록
실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다.
## 기능 요구사항
- FR-1: 시스템은 `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)을 필터링해 반환해야 한다.
- FR-2: 시스템은 `GET /v1/portfolios/{portfolio_id}/pnl/realized` 엔드포인트를 제공해야 하며, 지정 기간 내 매도(side='sell') 체결에 대해 수수료·세금을 반영한 실현 PnL 합계 및 건별 내역을 반환해야 한다.
- FR-3: 실현 PnL 산출은 portfolio_ledger의 buy/sell 기록에서 평균취득가(weighted average cost) 방식으로 cost_basis를 계산해야 하며, 매도 실현 손익 = (매도가 − 평균취득가) × 수량 − 수수료 − 세금으로 정의한다.
- FR-4: 두 엔드포인트 모두 기존 인증 체계(read_token 이상 권한)로 접근 가능해야 한다.
## 비기능 요구사항
- NFR-1: 조회 응답 시간은 단일 포트폴리오 기준 30일 범위 쿼리에서 p95 500ms 이하여야 한다. (portfolio_ledger TimescaleDB idx_portfolio_ledger_portfolio_executed 인덱스 활용)
- NFR-2: 엔드포인트는 OpenAPI 스키마에 문서화되어야 하며, 날짜 파라미터 누락 시 명확한 422 응답을 반환해야 한다.
- NFR-3: PnL 산출 로직은 단위 테스트로 검증 가능해야 한다 (repository가 아닌 service/domain 계층에 격리).
## 범위 경계
이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다.
- In Scope
- `GET /v1/portfolios/{portfolio_id}/trades` — 체결 내역 조회 (날짜 필터)
- `GET /v1/portfolios/{portfolio_id}/pnl/realized` — 실현 PnL 집계 (기간·종목 필터)
- portfolio_ledger 기반 weighted average cost PnL 산출 로직
- read_token 인증 통합
- Out of Scope
- 미실현 PnL (현재가 연동 — kis-data-hub 연동 필요, 별도 Discovery로 분리)
- trex_core ledger_outbox 데이터 마이그레이션 또는 연동
- 포트폴리오 전체 목록 PnL 일괄 조회 (단일 포트폴리오 단위만 이번 범위)
- 전체 portfolio 집계 PnL 뷰: **Out of Scope 확정(Answered, OQ-2)** — 이번 반복 제외, 필요 시 후속 Discovery
# 6. 리스크/가정 목록
불확실성과 전제 조건을 분리해서 적는다.
## 리스크
- R-1 (High): 매도 시점의 평균취득가 산출을 위해 portfolio_ledger의 전체 buy 이력을 집계해야 하는데, 포트폴리오 생성 초기부터의 누적 기록이 완전하지 않을 경우 PnL 계산 결과가 부정확할 수 있다.
- 완화 방안: 조회 시 cost_basis 산출 범위(집계 시작 시점)를 명시하고, 이력이 불완전한 경우 경고 필드를 응답에 포함한다. REQ 단계에서 데이터 완전성 확인 조건을 Acceptance Criteria에 명시한다.
- R-2 (Medium): TimescaleDB hypertable 기반 집계 쿼리(GROUP BY + SUM across partitions)가 대용량 이력에서 응답 시간 NFR-1을 초과할 수 있다.
- 완화 방안: 기존 인덱스(idx_portfolio_ledger_portfolio_executed) 활용 + EXPLAIN ANALYZE 검증. 필요 시 materialized view 또는 배치 집계 고려(이번 범위 밖, R-2가 현실화되면 후속 Discovery로 분리).
- R-3 (Low): trex-core와 portfolio-hub의 PnL 산출 역할 분담이 정리되지 않으면 운영 혼선이 발생할 수 있다.
- 완화 방안: 이번 Discovery는 portfolio-hub 단독 조회 API 제공에 집중하고, trex-core 역할 조율은 별도 협의 아이템으로 분리(이번 REQ 범위 밖).
## 가정
- A-1: portfolio_ledger의 buy/sell 체결 기록은 완전하고 신뢰할 수 있으며, portfolio 생성 이후 모든 체결이 portfolio_ledger에 기록되었다고 가정한다.
- A-2: 실현 PnL 산출 방식은 weighted average cost(가중평균 취득단가)를 사용한다고 가정한다. FIFO 방식이 필요하다면 OQ-1 확정 후 FR-3을 수정한다.
- A-3: 조회 엔드포인트는 기존 read_token 이상 권한으로 접근 가능하며, 별도 권한 체계 추가는 불필요하다.
## 오픈 질문
- OQ-1: 실현 PnL 산출 방식 — weighted average cost와 FIFO 중 어느 방식을 사용할 것인가? 현재 FR-3은 weighted average cost를 기본안으로 제시하나, 세무·회계 요건에 따라 FIFO가 필요할 수 있다.
- 상태: Answered
- 처리 방안: [결정] **weighted average cost(WAC)** — 사장님 2026-06-18. FR-3 기본안이 이미 WAC라 본문 변경 없음(A-2 가정 확정).
- 종료 조건: FR-3에 산출 방식이 확정 문장으로 반영되면 Answered로 처리.
- OQ-2: 전체 포트폴리오 집계 뷰(여러 portfolio_id에 걸친 PnL 합산) 필요 여부 — 이번 범위 Out of Scope 제안이지만, 운영 리포트 요건에 따라 P1 필요성이 있을 수 있다.
- 상태: Answered
- 처리 방안: [결정] 이번 반복 **Out of Scope 확정**. 운영 리포트가 전체 집계를 요구하면 후속 Discovery로 분리.
- 종료 조건: Out of Scope 확정 또는 In Scope 변경과 함께 FR 추가 시 Answered/Deferred 처리.
### 오픈 질문 처리 기준
- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다.
- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다.
- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다.
- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다.
- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다.
- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다.
- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다.
# 7. 초기 성공 기준
이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다.
- S-1: `GET /v1/portfolios/{id}/trades?date=2026-06-15` 호출 시 해당일 체결 기록(side, quantity, price, fee_amount, tax_amount, executed_at) 전체가 HTTP 200으로 반환된다.
- S-2: `GET /v1/portfolios/{id}/pnl/realized?from=2026-06-01&to=2026-06-15` 호출 시 수수료·세금 반영 실현 PnL 합계와 건별 내역이 HTTP 200으로 반환된다.
- S-3: 운영 담당자가 trex_core ledger_outbox를 수기 조회하지 않고 portfolio-hub API 단독으로 일일 PnL 리포트를 생성할 수 있다.
## 측정 방식 및 데이터 출처
각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다.
- S-1 측정 방식: 통합 테스트 — 알려진 체결 데이터 삽입 후 GET 호출, 응답 항목 수 및 필드 값 일치 확인 / 데이터 출처: portfolio_ledger 삽입 fixture
- S-2 측정 방식: 단위 테스트(PnL 산출 로직) + 통합 테스트(엔드포인트 응답) — 매수·매도 시나리오별 PnL 기댓값 검증 / 데이터 출처: portfolio_ledger 삽입 fixture, 수기 계산 대조값
- S-3 측정 방식: 운영 리포트 스크립트 또는 운영자 확인 — trex_core 수기 조회 절차 제거 여부 / 데이터 출처: 운영 리포트 프로세스 문서 또는 운영자 확인
# 8. REQ로 넘기기 전 확인 체크
REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다.
- [ ] 범위(In Scope/Out of Scope) 확정
- 기준: 전체 portfolio 집계 뷰(OQ-2)와 미실현 PnL이 명시적으로 Out of Scope로 확정되거나 In Scope로 변경됨
- [ ] 성공 지표의 측정 방식/데이터 출처 확정
- 기준: S-1·S-2는 통합 테스트로 측정 가능한 fixture 데이터 설계가 완료됨
- [ ] High 영향 리스크 우선순위 및 해소 책임자 지정
- 기준: R-1(이력 완전성) 처리 방식 — 경고 필드 포함 여부 — 이 REQ Acceptance Criteria에 반영됨
- [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인
- 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료
- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정
- 기준: Justin Ko가 이 Discovery 내용을 검토하고 confirm-discovery로 승인
# 9. 파일 처리 결과
이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다.
- 처리 결과: 생성
- 생성 경로: docs/discovery/dcy-007_20260616_daily-trade-history-and-realized-pnl-query-api.md
- 참조 문서:
- GitHub issue #11: https://github.com/fourblocks2020/portfolio-hub/issues/11
- docs/discovery/dcy-006_20260605_ledger-trade-write-portfolio-state-return.md (연관 선행 Discovery — write 경로)
- app/repositories/postgres.py:1317 (list_recent_trades 현행 구현)
# 10. 사용자 결정 필요 항목 요약
사용자나 승인자가 결정해야 하는 항목만 모아 정리한다.
## DECIDE
- PnL 산출 방식 → **결정: weighted average cost(WAC)** (사장님 2026-06-18, FR-3 확정) (OQ-1)
- 전체 portfolio 집계 PnL 뷰 → **결정: Out of Scope** (이번 반복 제외, 필요 시 후속 Discovery) (OQ-2)
## CONFIRM
- {{CONFIRM_AUTH_LEVEL}}: 조회 엔드포인트 접근 권한이 read_token으로 충분한지, 또는 별도 scope 필요한지 확인.
- {{CONFIRM_HISTORY_COMPLETENESS}}: portfolio_ledger 이력이 포트폴리오 생성 시점부터 완전하게 기록되어 있는지 확인. R-1 완화 방안 적용 범위에 영향.
## DATA
- {{DATA_COST_BASIS_SAMPLE}}: 실제 운영 portfolio(005930.KS)의 buy/sell 이력 샘플 — PnL 기댓값 수기 계산 대조에 사용.
# 11. Discovery Freeze
## 섹션 참조
- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록)
- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록)
- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준)
- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크)
- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약)
## Freeze 플래그
- Handoff Decision: 확정 (confirm-discovery, 2026-06-18)
- Handoff Rationale: OQ-1=WAC(사장님 결정), OQ-2=Out of Scope. FR-3 기본안(WAC) 그대로라 본문 변경 없음. M1 fast-track으로 즉시 REQ 초안 작성 진행. R-1(이력 완전성)은 REQ AC에서 경고 필드로 완화.
- Ready for REQ Drafting: true
- Confirmed By: 사장님 (Justin Ko)
- Confirmed At (KST): 2026-06-18
+1
View File
@@ -12,3 +12,4 @@
| dcy-004_20260520_token-issuance-and-lifecycle-overhaul | 20260520 | 토큰 발급 및 관리 전면 정비 — client token 폐지, service-token rotate 정상화, 발급 권한 매트릭스, 만료·회전·자동 revoke 정책 | [Discovery](./dcy-004_20260520_token-issuance-and-lifecycle-overhaul.md) | confirmed |
| dcy-005_20260601_rel-001-feedback-cleanup | 20260601 | REL-001 Feedback 정합화 — ledger_trades.asset_id 명명 / session no_expiry enforcement / health db_enabled | [Discovery](./dcy-005_20260601_rel-001-feedback-cleanup.md) | confirmed |
| dcy-006_20260605_ledger-trade-write-portfolio-state-return | 20260605 | ledger trade write 회계 책임 단일화 — 응답에 portfolio 상태 포함 + portfolio_ledger/ledger_trades 테이블 통합 (issue #8) | [Discovery](./dcy-006_20260605_ledger-trade-write-portfolio-state-return.md) | confirmed |
| dcy-007_20260616_daily-trade-history-and-realized-pnl-query-api | 20260616 | 일일 거래내역 + 실현 PnL 조회 API (운영 리포트/프론트 데이터 소스, issue #11) | [Discovery](./dcy-007_20260616_daily-trade-history-and-realized-pnl-query-api.md) | confirmed |