# 0. 문서 상태 이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. - 상태: confirmed - 문서 ID: dcy-008_20260625_current-holding-wac-cost-basis-read - 이슈명: 현재 보유 종목별 평단(WAC) 조회 API — trex-core 중장기 cold-start 슬롯 평단 seed 의존 - 작성 시각(KST): 2026-06-25 (KST) - 마지막 갱신 시각(KST): 2026-06-25 (KST, confirm-discovery — 사장님 승인, OQ-1/OQ-2 Answered) - 대체됨: 없음 - 후속 Discovery 참조: 없음 - 교차 서비스 발원(driver): trex-core `dcy-034_20260625_slot-account-blocking-reconcile-sync-coldstart` FR-7 — trex-core가 cold-start 시 슬롯 평단을 seed하려면 portfolio-hub의 현재 보유분 평단 read가 필요하다. 본 Discovery는 그 의존을 portfolio-hub측 변경 단위로 정식화한다. - 생성된 REQ 참조: REQ-043 (GET /v1/portfolios/{id}/holdings/cost-basis 현재 보유 평단 read, Interface) → [req-043](../srs/Interface/req-043_holdings-cost-basis-read-endpoint.md) (Implemented — BAT-018 PR #14 origin/main 머지, 팀장 QC PASS, 배포 별도) # 1. 계획 상태 요약 - 연계 문서 현황: - 관련 점수 파일: 없음 - 관련 TODO/메모: 교차 서비스 의존 — trex-core dcy-034(M3 중장기 cold-start) FR-7. GitHub 보드 #7 dcy-034 항목. - 직계 선행: dcy-007(일일 거래내역 + 실현 PnL 조회 API) — WAC cost basis 산출 로직(REQ-041)을 이미 도입한 직계 형제. - 해석: - trex-core 중장기 인스턴스가 실계좌에 이미 보유 중인 종목 위에서 cold-start 할 때, 슬롯에 보유분을 onboarding하며 **슬롯 평단(cost basis)을 seed**해야 한다. 그 평단의 단일 정본 출처는 원장(ledger)을 보유한 portfolio-hub다. - 그러나 portfolio-hub는 WAC를 내부 계산만 하고(미매도 보유분의 현재 평단을 주는 read가 없음), 외부 노출은 매도 시점 `cost_basis_per_unit`(dcy-007/REQ-040)뿐이다. 즉 "현재 보유분의 평단"을 조회할 표면이 비어 있다. - 본 Discovery는 그 read 표면을 portfolio-hub 자체 변경(신규 조회 엔드포인트 + 현재 보유 WAC 집계)으로 정의한다. # 2. 요구사항 판정 결과 - 판정: 신규 - 근거 - dcy-007은 **매도 기준 실현 PnL 집계**(GET /pnl/realized, 매도행 cost_basis_per_unit)다. 이번은 **미매도 현재 보유분의 평단 조회**로, 데이터 대상(미청산 포지션)과 용도(외부 seed)가 다르다. - 기존 dcy-001~007 중 "현재 보유 평단 read"를 정의한 Discovery가 없다. WAC 산출 로직(REQ-041)은 재사용 가능하나 이를 노출하는 read 계약은 신규다. # 3. 문제점의 요약 - 현재 상태 - `app/domain/pnl.py`의 `calculate_realized_pnl`이 매수 누적으로 `running_wac`를 계산하지만, 이 값은 **매도 이벤트 행(`cost_basis_per_unit`)으로만** 응답에 노출된다(`GET /v1/portfolios/{id}/pnl/realized`, REQ-040). 매도가 0건이면 보유분 평단을 알 길이 없다. - portfolio_ledger(TimescaleDB)에 매수/매도 체결이 적재돼 있어 보유 수량·WAC는 산출 가능하나, **"현재 보유 종목별 (수량, 평단)"을 반환하는 GET 엔드포인트가 없다.** - 결과: trex-core cold-start가 슬롯 평단을 seed할 외부 read가 없어, 브로커 평균가(dcy-032에서 0/None 케이스 확인) 등 불안정한 폴백에 의존해야 한다. - 기대 상태 - 포트폴리오의 **현재 보유 종목별 보유수량 + 현재 평단(WAC, cost_basis_per_unit)** 을 반환하는 read 엔드포인트가 존재한다. - WAC 산출은 dcy-007/REQ-041의 검증된 `running_wac` 로직을 재사용하고, 원장 이력이 불완전하면 dcy-007과 동일하게 `data_completeness_warning`으로 표면화한다. - 인증/역할은 기존 portfolio read API와 동일 정책이라 daemon 호출자(trex-core)가 소비할 수 있다. - 이해관계자 - 사장님 — 교차 서비스 계약·범위 승인자 - 팀장(Claude) — Discovery/게이트 관리, 교차 의존 추적자 - portfolio-hub-dev — delivery 담당 - 소비자: trex-core(중장기 cold-start seed), 운영자(보유 평단 가시성) - 제약사항 - portfolio-hub는 **시세(현재가)를 소유하지 않는다**(그건 kis-data-hub). 따라서 본 read는 **평단/취득원가(cost basis)만** 다루고 미실현 손익(현재가 결합)은 범위 밖이다. - WAC 산출 정본은 원장(portfolio_ledger)이며, 원장 이력 불완전 시 추정 한계가 dcy-007과 동일하게 존재한다. - 응답 스키마/인증은 기존 v1 portfolio read 계약과 일관해야 한다(프론트/daemon 회귀 방지). ## 주요 사용자 시나리오 - trex-core(daemon): 중장기 인스턴스 cold-start에서 보유 종목 onboarding 시, 이 엔드포인트로 종목별 현재 평단을 읽어 슬롯 cost basis를 seed한다. - 운영자: 매도 전이라도 특정 포트폴리오의 현재 보유 평단/취득원가를 조회해 손익 기준선을 확인한다. # 4. 이번에 정의할 변경 - 변경 1 — 현재 보유 평단 조회 엔드포인트 신설: 포트폴리오의 미청산 보유 종목별 (symbol, 보유수량, WAC, 보유 cost basis 합)을 반환하는 GET 엔드포인트. - 변경 2 — 현재 보유 WAC 집계 로직: dcy-007/REQ-041의 `running_wac`(매수 누적 WAC, 매도 시 수량 차감) 로직을 재사용해 "매도 시점 PnL"이 아니라 "현재 잔여 보유분의 평단"을 산출. 원장 불완전 시 `data_completeness_warning`. ## 영향 범위 - 코드 영향: - `app/api/v1_portfolios.py` — 신규 GET 핸들러(기존 `/pnl/realized` 핸들러 :710 인증 패턴 재사용). - `app/domain/pnl.py` — 현재 보유 WAC 산출 함수(기존 `calculate_realized_pnl` running_wac 로직 재사용/추출). - `app/schemas/v1.py` — 응답 스키마 신설(RealizedPnl* 인근 패턴). - `app/repositories/postgres.py` — 보유 집계 조회(필요 시). - 문서 영향: `docs/discovery/index.md` 신규 행, srs/index.md(REQ 생성 시). - 운영 영향: 신규 read 엔드포인트 1개 추가(쓰기·마이그레이션 없음). 인증 정책 변화 없음. # 5. 요구사항 목록 ## 기능 요구사항 - FR-1: 포트폴리오의 **현재 보유(미청산) 종목별** `symbol`, `quantity`, `cost_basis_per_unit`(WAC)을 반환하는 GET 엔드포인트를 제공한다. - FR-2: WAC는 매수 누적 가중평균(매도 시 수량 차감, 단가 불변)으로 산출한다(dcy-007/REQ-041 `running_wac` 재사용). 매수만 한 보유분은 WAC=가중평균 매수가. - FR-3: 원장 이력이 불완전하면 `data_completeness_warning`을 응답에 포함한다(dcy-007 패턴 일관). - FR-4: 인증/역할은 기존 portfolio read API(REQ-040/041)와 동일 정책으로, daemon scope 호출자가 사용 가능해야 한다. ## 비기능 요구사항 - NFR-1: 응답 스키마·인증은 기존 v1 portfolio read 계약과 일관(프론트/daemon 회귀 0). - NFR-2: 본 엔드포인트는 read-only, 쓰기/스키마 마이그레이션 없음. - NFR-3: 시세 의존 없음 — 평단/취득원가만(미실현 손익 비포함). ## 범위 경계 - In Scope - 현재 보유 종목별 (수량, WAC, cost basis 합) read 엔드포인트 + 산출 로직 + 테스트 + data_completeness_warning. - Out of Scope - 미실현 손익(현재가 결합) — portfolio-hub가 시세 비소유. - trex-core 슬롯 seed 로직 자체(소비측 — trex-core dcy-034). - 원장 소급 보정(데이터 정합화 별건). # 6. 리스크/가정 목록 ## 리스크 - R-1 (Low): 현재 보유 WAC 산출이 dcy-007 매도 기준 계산과 미세하게 갈라질 수 있다(잔여 수량 0 처리 등). - 완화: `running_wac` 로직을 단일 출처로 추출/재사용하고, 매수만/부분매도 후 시나리오 테스트로 dcy-007과 정합 확인. - R-2 (Low): 응답 스키마가 daemon/프론트 양 소비자 요구와 어긋날 수 있다. - 완화: 기존 RealizedPnl* 스키마 패턴 답습 + trex-core 소비 형태(symbol→평단 맵)를 design에서 확정. ## 가정 - A-1: portfolio_ledger에 cold-start 대상 종목의 매수 이력이 적재돼 있어 WAC 산출이 가능하다(없으면 data_completeness_warning). - A-2: trex-core는 포트폴리오 단위(portfolio_id)로 평단을 소비한다(슬롯↔portfolio_id 1:1, trex-core dcy-020). - A-3: dcy-007/REQ-041의 WAC 로직은 현재 보유 평단 산출에 그대로 재사용 가능하다. ## 오픈 질문 (전부 Answered — confirm-discovery 사장님 승인 2026-06-25) - OQ-1 (Answered): 집계 단위 → **포트폴리오별** `GET /v1/portfolios/{id}/holdings/cost-basis`. trex-core 슬롯↔portfolio_id 1:1 + 기존 `/v1/portfolios/{id}/` 패턴 일관. (REQ-043 반영.) - OQ-2 (Answered): 응답 스키마 → 종목별 (symbol, current_quantity, cost_basis_per_unit=WAC, total_cost_basis, cost_basis_from, data_completeness_warning). 보유 cost basis 합계·최초 매수시각 포함. (REQ-043 반영.) - OQ-3 (Answered, 교차 서비스 — REQ-043 draft-req 제기): service token 소유권 — cold-start가 `managed_by_daemon_id=null` 미배정 포트폴리오 평단을 읽으면 403 blocker 우려. **해소: trex-core cold-start는 `snapshot_scheduler.run_once`에서 whoami→get_portfolios(daemon_id)로 이미 자기 daemon에 배정된 포트폴리오만 가져오므로, 그 포트폴리오들은 `managed_by_daemon_id==daemon`이라 소유권 정책(REQ-029 선례) 성립. null 케이스는 cold-start 경로에 없음(팀장 2026-06-25).** REQ-043은 daemon scope 소유권 정책 유지. ### 오픈 질문 처리 기준 - `Open`: 다음 단계 진행에 영향이 있는 미결. 유지. - `Answered`: 확정. 본문/REQ 반영 후 정리. - `Deferred`: 다음 단계로 이월(대상·이유 명시). - `Dropped`: 무효(사유 명시). # 7. 초기 성공 기준 - S-1: 매수만 한 보유분에 대해 엔드포인트가 WAC=가중평균 매수가, 보유수량을 정확히 반환한다. - S-2: 부분 매도 후 잔여 보유분의 WAC가 매도로 변하지 않고(수량만 감소) 정확히 반환된다. - S-3: 원장 이력 불완전 시 `data_completeness_warning`이 응답에 포함된다. - S-4: 인증/역할이 기존 portfolio read와 동일하게 적용된다(무권한 거부, daemon scope 허용). ## 측정 방식 및 데이터 출처 - S-1/S-2: 매수·부분매도 픽스처 단위/통합 테스트로 WAC·수량 검증 / 신규 테스트 + portfolio_ledger. - S-3: 불완전 이력 주입 테스트로 warning 단언 / 응답 페이로드. - S-4: 인증 테스트(무헤더/무권한/daemon scope) / 기존 auth 테스트 패턴. # 8. REQ로 넘기기 전 확인 체크 - [x] 범위(In/Out) 확정 — 미실현 손익 제외·평단만 제공 합의(사장님 2026-06-25) - [x] 성공 지표 측정 방식 확정 — S-1~S-4 이견 없음 - [x] High 리스크 — 현재 없음(R-1/R-2 Low) - [x] 모든 OQ `Open` 없음 — OQ-1/OQ-2/OQ-3 전부 Answered - [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 — 팀장(Claude) # 9. 파일 처리 결과 - 처리 결과: 생성 - 생성 경로: docs/discovery/dcy-008_20260625_current-holding-wac-cost-basis-read.md - 참조 문서: - docs/discovery/dcy-007_20260616_daily-trade-history-and-realized-pnl-query-api.md (WAC cost basis 산출 직계) - trex-core docs/discovery/dcy-034_...slot-account-blocking-reconcile-sync-coldstart.md (교차 서비스 발원, FR-7) - app/domain/pnl.py, app/api/v1_portfolios.py(:710), app/schemas/v1.py (현재 구현 근거) # 10. 사용자 결정 필요 항목 요약 ## DECIDE (해소됨 — 사장님 2026-06-25) - (확정) OQ-1 집계 단위 → 포트폴리오별(`/v1/portfolios/{id}/holdings/cost-basis`). ## CONFIRM (확정) - 본 read 엔드포인트를 trex-core dcy-034 FR-7의 portfolio-hub측 의존 단위로 채택 — 사장님 2026-06-25. - 미실현 손익 제외(평단/취득원가만) 범위 — 확정. ## DATA - 없음(원장 이미 적재, WAC 로직 기존 보유). # 11. Discovery Freeze ## 섹션 참조 - 범위: [# 5 > 범위 경계](#5-요구사항-목록) - 리스크/오픈 질문: [# 6](#6-리스크가정-목록) - 성공 기준: [# 7](#7-초기-성공-기준) - 인계 전 확인: [# 8](#8-req로-넘기기-전-확인-체크) - 사용자 결정 필요: [# 10](#10-사용자-결정-필요-항목-요약) ## Freeze 플래그 - Handoff Decision: REQ Drafting 진행 가능 - Handoff Rationale: 사장님 승인(2026-06-25). OQ-1(포트폴리오별)·OQ-2(스키마)·OQ-3(소유권 정책) 전부 Answered. REQ-043이 이미 본 Discovery에서 파생 작성됨(confirm-req 대기). 미실현 손익 제외 범위 합의. Open OQ 0건. - Ready for REQ Drafting: true - Confirmed By: 팀장(Claude) - Confirmed At (KST): 2026-06-25 (KST) # 12. 작성 가이드 - 이 문서는 portfolio-hub dcy 템플릿 기반이며 Open Questions는 가정으로 대체하지 않는다. - confirm-discovery 시 OQ-1/OQ-2 처리와 Freeze 플래그를 갱신한다.