Files
portfolio-hub/docs/srs/Interface/req-043_holdings-cost-basis-read-endpoint.md
T
genichinandClaude Sonnet 4.6 73414817e0 fix(pnl): 매수 수수료를 WAC cost basis에 편입 (REQ-041 CHG-20260626-01)
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
2026-06-26 20:34:03 +09:00

12 KiB
Raw Blame History

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): 요청한 포트폴리오 ID
    • symbol (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

  1. 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.
  2. 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.
  3. Given user read_token(portfolio 소유자), When 호출, Then HTTP 200 + 정상 응답.
  4. Given user read_token(비소유자, admin 아님), When 타인 portfolio_id 조회, Then HTTP 403 반환.
  5. Given service token(daemon_id=D1, portfolio.managed_by_daemon_id=D2, D1≠D2), When 호출, Then HTTP 403 반환.
  6. Given service token(managed_by_daemon_id=null인 portfolio), When 호출, Then HTTP 403 반환 (daemon 미배정 portfolio에 daemon 접근 불가).
  7. Given 존재하지 않는 portfolio_id, When 호출, Then HTTP 404 반환.
  8. 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.
  9. Given portfolio에 buy 이력 없이 sell 기록이 존재하는 이력 불완전 상황, When 호출, Then HTTP 200 + data_completeness_warning 필드에 비어 있지 않은 경고 문자열 포함.
  10. Given 인증 토큰 없음, When 호출, Then HTTP 401 반환.
  11. Given 구현 완료, When OpenAPI /docs 또는 openapi.json 확인, Then GET /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 확인.