diff --git a/docs/discovery/dcy-007_20260616_daily-trade-history-and-realized-pnl-query-api.md b/docs/discovery/dcy-007_20260616_daily-trade-history-and-realized-pnl-query-api.md new file mode 100644 index 0000000..125d9e6 --- /dev/null +++ b/docs/discovery/dcy-007_20260616_daily-trade-history-and-realized-pnl-query-api.md @@ -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 diff --git a/docs/discovery/index.md b/docs/discovery/index.md index 60d9950..cbbc575 100644 --- a/docs/discovery/index.md +++ b/docs/discovery/index.md @@ -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 |