WAC 공식에 buy row의 fee_amount를 분자에 포함해 실현 PnL이 당일 net과 정확히 일치하도록 수정. calculate_realized_pnl + calculate_current_wac 양 함수 동일 적용. 신규 단위 테스트 9개(TestBuyFeeInCostBasis 5 + TestCurrentWacBuyFeeInCostBasis 4) 추가. change-req: REQ-041 AC-9/AC-10 추가, REQ-040/REQ-043 Notes 갱신, index 갱신. 전체 456 passed / 0 failed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
12 KiB
12 KiB
REQ-043: GET /v1/portfolios/{id}/holdings/cost-basis 현재 보유 평단(WAC) read 엔드포인트 신설
- Status: Implemented
- Type: Interface
- Priority: High
- Owner: Justin Ko
Intent
- 지정 포트폴리오의 현재 미매도 보유분에 대한 WAC(weighted average cost, 가중평균 취득단가) 와 보유수량을 반환하는 GET 엔드포인트를 신설한다.
- trex-core dcy-034 cold-start onboarding(FR-7)에서, 실계좌에 이미 보유 중인 종목을 슬롯에 등록할 때 슬롯의 평단(cost basis)을 seed하기 위해 이 엔드포인트를 소비한다.
- WAC 산출은 기존
calculate_realized_pnl의 running_wac 로직(REQ-041)을 재사용하며, 데이터 불완전 시data_completeness_warning경고 패턴(REQ-040 관례)도 동일 적용한다. - 이 엔드포인트는 trex-core dcy-034 FR-7의 선행 의존이다. trex-core cold-start가 이 엔드포인트의 구현 완료 전에는 평단 seed를 수행할 수 없다.
Requirement
- 시스템은
GET /v1/portfolios/{portfolio_id}/holdings/cost-basis엔드포인트를 제공해야 한다. - 응답은 다음 필드를 포함해야 한다:
portfolio_id(str): 요청한 포트폴리오 IDsymbol(str | None): 포트폴리오에 연결된 종목 심볼current_quantity(float): 현재 보유수량 (portfolios.asset_quantity 기준)cost_basis_per_unit(float | None): WAC로 산출한 현재 평단 (보유수량이 0인 경우 None)total_cost_basis(float | None): cost_basis_per_unit × current_quantity (보유수량이 0인 경우 None)cost_basis_from(datetime | None): WAC 산출에 사용된 최초 buy 기록 시점 (REQ-041 관례)data_completeness_warning(str | None): 이력 불완전 감지 시 경고 문자열, 없으면 None (REQ-040/041 관례)
- WAC 산출 방식: 해당 portfolio_id의 전체 buy/sell 원장 이력(portfolio_ledger)을 시간 순으로 replay하여 현재 running_wac를 도출한다. 이는
calculate_realized_pnl함수(app/domain/pnl.py)의 WAC 누적 로직을 재사용하거나, 동일 규칙을 확장한 신규 domain 함수로 구현한다. - 인증/역할:
require_read의존성(Permission.READ 이상) 적용. service 토큰(token_type="service")도 허용한다 — trex-core daemon이 DB service token으로 호출한다(OQ-1 참조). - 소유권 확인: admin이 아닌 user token 호출자는 자신이 소유한 portfolio만 조회 가능(비소유자 HTTP 403). service token(daemon)은 자신의 daemon_id가
portfolio.managed_by_daemon_id와 일치하는 portfolio만 조회 가능(불일치 HTTP 403). - 존재하지 않는 portfolio_id 요청은 HTTP 404를 반환해야 한다.
- 보유수량이 0인 portfolio를 요청한 경우 HTTP 200 +
current_quantity=0.0,cost_basis_per_unit=null,total_cost_basis=null을 반환한다(404 아님).
Acceptance Criteria
- Given service token(daemon_id=D, portfolio.managed_by_daemon_id=D)으로 인증, portfolio에 buy 100주@10,000 + buy 100주@12,000 이력 존재(sell 없음), When
GET /v1/portfolios/{id}/holdings/cost-basis, Then HTTP 200 +current_quantity=200.0+cost_basis_per_unit=11000.0(WAC = (100×10,000 + 100×12,000) / 200) +total_cost_basis=2,200,000.0+data_completeness_warning=null. - Given service token(daemon_id=D, portfolio.managed_by_daemon_id=D)으로 인증, portfolio에 buy 200주@10,000 후 sell 150주 이력 존재, When 호출, Then HTTP 200 +
current_quantity=50.0+cost_basis_per_unit=10000.0(WAC 불변) +total_cost_basis=500,000.0. - Given user read_token(portfolio 소유자), When 호출, Then HTTP 200 + 정상 응답.
- Given user read_token(비소유자, admin 아님), When 타인 portfolio_id 조회, Then HTTP 403 반환.
- Given service token(daemon_id=D1, portfolio.managed_by_daemon_id=D2, D1≠D2), When 호출, Then HTTP 403 반환.
- Given service token(managed_by_daemon_id=null인 portfolio), When 호출, Then HTTP 403 반환 (daemon 미배정 portfolio에 daemon 접근 불가).
- Given 존재하지 않는 portfolio_id, When 호출, Then HTTP 404 반환.
- Given portfolio에 buy/sell 이력이 전혀 없음(current_quantity=0), When 호출, Then HTTP 200 +
current_quantity=0.0+cost_basis_per_unit=null+total_cost_basis=null+data_completeness_warning=null. - Given portfolio에 buy 이력 없이 sell 기록이 존재하는 이력 불완전 상황, When 호출, Then HTTP 200 +
data_completeness_warning필드에 비어 있지 않은 경고 문자열 포함. - Given 인증 토큰 없음, When 호출, Then HTTP 401 반환.
- Given 구현 완료, When OpenAPI
/docs또는openapi.json확인, ThenGET /v1/portfolios/{portfolio_id}/holdings/cost-basis경로가 응답 스키마(data_completeness_warning,cost_basis_per_unit,total_cost_basis포함)와 함께 문서화되어 있음.
Impacted Area
- Docs:
docs/runtime-flows.md—GET /v1/portfolios/{id}/holdings/cost-basis흐름 추가docs/project-structure.md— app/api/v1_portfolios.py 및 app/schemas/v1.py 책임 갱신
- Modules:
app/api/v1_portfolios.py— GET 엔드포인트 추가 (소유권 확인 분기: user token vs service token)app/domain/pnl.py— WAC-only(매도 결과 수집 없이 running_wac만 반환하는) 신규 함수 추가, 또는calculate_realized_pnl반환값에서 running_wac 직접 추출하도록 확장 (구현 단계에서 결정)app/repositories/postgres.py—get_all_trades_for_pnl재사용 (신규 SQL 메서드 불필요할 수 있음 — 구현 단계 확인)app/repositories/portfolio_repository.py— 신규 abstract method 추가 여부 구현 단계 판단app/schemas/v1.py—HoldingsCostBasisResponse스키마 신규 추가
- Tests:
tests/test_v1_portfolios_holdings.py신규 — AC-1~11 커버
Notes
- 근거 Discovery: dcy-008_20260625_current-holding-wac-cost-basis-read — 사장님 confirmed 2026-06-25. OQ-1/OQ-2/OQ-3 전부 Answered. 본 REQ는 dcy-008의 파생 요구사항이며 양방향 링크(dcy-008 "생성된 REQ 참조: REQ-043" ↔ 본 REQ "근거 Discovery: dcy-008").
- 선행 의존(upstream blocker): trex-core dcy-034 FR-7. 이 REQ가 Implemented가 되기 전에 dcy-034 구현이 완료될 수 없다. batch 계획 시 trex-core 팀에 의존 관계 고지 필요.
- OQ-1(service token 소유권 확인 정책) — 해소(팀장 confirm-req, 2026-06-25): service token 소유권 정책을
daemon_id == portfolio.managed_by_daemon_id일치 시만 허용, 불일치/null은 HTTP 403으로 확정한다. 해소 근거: trex-core cold-start는snapshot_scheduler.run_once에서whoami → get_portfolios(daemon_id)순으로 이미 자기 daemon에 배정된 포트폴리오만 조회한다. 그 포트폴리오들은managed_by_daemon_id == 호출 daemon이 성립하므로 소유권 정책(REQ-029 선례)이 그대로 적용된다.managed_by_daemon_id=null미배정 케이스는 cold-start 경로에 없어 blocker 아님. 구현 시 추가 완화 불필요. - OQ-2(domain 함수 확장 vs 신규) — 구현 단계 위임(팀장 confirm-req, 2026-06-25):
calculate_current_wac신규 함수 분리(단일책임 원칙) 를 권고하되, 구현 옵션((a) 기존 함수 파라미터 확장 vs (b) 신규 함수 분리) 선택은 구현 단계(design/implementation) 재량으로 위임한다. REQ에서 강제하지 않는다. domain 계층 순수성 유지는 필수. - current_quantity 출처:
portfolios.asset_quantity(테이블 컬럼)를 직접 읽는다. 이 값은 ledger_trade 기록 시asset_quantity_after로 동기화되므로 신뢰 가능하다. WAC는 ledger replay로 독립 계산하므로, 두 값의 수량 불일치 시data_completeness_warning조건이 트리거된다. - GET /pnl/realized와의 차이: REQ-040은 지정 기간 내 매도 완료된 실현 손익을 반환. 이 REQ는 현재 보유 중인 미매도분의 현재 평단을 반환. 개념적으로 보완 관계이며 상호 독립적이다.
- WAC 정의 변경(2026-06-26): REQ-041 CHG-20260626-01에 따라 WAC 공식에 매수 수수료가 편입됨.
running_wac = (running_wac*qty + price*qty + buy_fee) / new_total_qty.calculate_current_wac함수도 동일 정의를 따르므로 이 엔드포인트의cost_basis_per_unit역시 매수 fee 포함값이다. AC-1/AC-2의 기댓값은 buy fee=0인 케이스라 불변. - Source: trex-core dcy-034 FR-7 (cold-start cost basis seed 선행 의존). 근거 Discovery: dcy-008 (portfolio-hub측 정식화).
Change Log
CHG-20260626-01
- Date: 2026-06-26
- Author: Justin Ko (portfolio-hub-dev)
Change Summary
- 상태
Approved→Implemented— BAT-018 구현 완료, PR #14 squash-merge(d93c0bc) 후 팀장 QC PASS 및 origin/main 머지 확인.
Intent
- BAT-018(batch-lite) 구현·검증 완료: AC-1~11 전부 PASS, 신규 테스트 20개 + 기존 회귀 427개 0 실패(총 447 passed).
calculate_current_wac신규 domain 함수,HoldingsCostBasisResponse스키마, GET 엔드포인트 additive 추가. 배포는 별도(미배포 — release-candidate 상태).
Acceptance Criteria Delta
- No change (AC-1~11 유지, 전부 검증 완료).
Impacted Area Delta
- No change.
Delivery Trace Delta
- Batches: BAT-018 → release-candidate
- Releases: none (미배포)
Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
- Follow-up Action: 배포 시 release 문서 작성.
Validation Plan
- BAT-018 verification.md 참조 (AC-1~11 PASS, 447 passed / 0 failed).
CHG-20260625-02
- Date: 2026-06-25
- Author: Justin Ko (portfolio-hub-dev)
Change Summary
- 팀장 confirm-req 결정 반영 — 상태
Proposed→Approved.
Intent
- dcy-008 근거 Discovery 양방향 링크 명시. OQ-1(service token 소유권 blocker) 해소 확정(trex-core cold-start 경로 분석으로
managed_by_daemon_id=null케이스 cold-start 경로에 없음 확인, daemon scope 소유권 정책 유지). OQ-2(domain 함수) 구현 단계 재량 위임 확정.
Acceptance Criteria Delta
- No change (AC-1~11 유지).
Impacted Area Delta
- No change.
Delivery Trace Delta
- Batches: none (미정)
- Releases: none (미정)
Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Approved
- Follow-up Action: suggest-batch-reqs → batch 계획 편입.
Validation Plan
- 이전 CHG-20260625-01 validation plan 유지 (pytest TestClient AC-1~10, AC-11 OpenAPI 확인).
CHG-20260625-01
- Date: 2026-06-25
- Author: Justin Ko (portfolio-hub-dev)
Change Summary
- 최초 생성 — trex-core dcy-034 cold-start onboarding(FR-7) 선행 의존으로 draft-req 실행. 현재 미매도 보유 평단(WAC) read 엔드포인트 신설.
Intent
- trex-core가 실계좌 보유 종목을 슬롯 onboarding 시 portfolio-hub에서 평단을 seed받기 위한 Interface REQ 신설. REQ-040/041의 WAC·경고 패턴 재사용. service token 소유권 정책은 REQ-029 선례 따름.
Acceptance Criteria Delta
- Added:
- AC-1~11 최초 작성
Impacted Area Delta
- Docs: docs/runtime-flows.md, docs/project-structure.md — 최초 지정
- Modules: app/api/v1_portfolios.py, app/domain/pnl.py, app/repositories/postgres.py, app/schemas/v1.py — 최초 지정
- Tests: tests/test_v1_portfolios_holdings.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 계획 편입. OQ-1(service token 정책) 팀장 확인 필요. dcy-034 trex-core 팀 의존 관계 고지 필요.
Validation Plan
- pytest TestClient로 AC-1~10 검증(DB 불필요 단위 + mock repo). AC-11 OpenAPI 확인.