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
161 lines
12 KiB
Markdown
161 lines
12 KiB
Markdown
# 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](../../discovery/dcy-008_20260625_current-holding-wac-cost-basis-read.md) — 사장님 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 확인.
|