diff --git a/docs/discovery/dcy-035_20260626_multiday-realized-pnl-report-wac.md b/docs/discovery/dcy-035_20260626_multiday-realized-pnl-report-wac.md new file mode 100644 index 0000000..c7952cc --- /dev/null +++ b/docs/discovery/dcy-035_20260626_multiday-realized-pnl-report-wac.md @@ -0,0 +1,154 @@ +# 0. 문서 상태 +- 상태: confirmed +- 문서 ID: dcy-035_20260626_multiday-realized-pnl-report-wac +- 이슈명: 중장기 운영 리포트 — portfolio-hub WAC 실현PnL(/pnl/realized) 연동 (멀티데이 정확 실현손익) +- 작성 시각(KST): 2026-06-26 (KST) +- 마지막 갱신 시각(KST): 2026-06-26 (KST) +- 대체됨: 없음 +- 후속 Discovery 참조: 없음 +- 생성된 REQ 참조: REQ-109, REQ-110, REQ-111 + +# 1. 계획 상태 요약 +- 연계 문서 현황: + - 관련 TODO/메모: GitHub 보드 #7 "중장기 운영 리포트 — portfolio-hub WAC 실현PnL API(/pnl/realized) 연동 (멀티데이, M3)". M3 중장기 인스턴스 운영 도구(슬롯 동기화 dcy-034의 리포트 짝). + - 선행: dcy-029(운영 관측성·실현 PnL 뷰, REQ-088 operator report), dcy-032(체결가 누락→cost basis 오염), portfolio-hub dcy-007/REQ-040(WAC 실현 PnL API). +- 해석: + - 현행 trex-core 운영 리포트(`application/operator_report.py`, REQ-088/dcy-029)는 실현 PnL을 **당일 net 매매 현금흐름**(`Σ매도(qty·price−fee−tax) − Σ매수(qty·price+fee)`)으로 계산한다. **일중 진입·청산 전략에선 net ≈ 실현손익**이라 충분했으나(M1/M2 단기), **중장기(수일~수개월 보유)에선 cost basis(WAC)를 무시한 당일 net이 실현손익과 다르다**. + - portfolio-hub는 이미 **WAC 기반 실현 PnL**(`GET /v1/portfolios/{id}/pnl/realized`, REQ-040/041: `(매도가−running_wac)·qty−fee−tax`)을 제공한다. M3 중장기 인스턴스의 운영 리포트는 이 WAC API를 소비해 **멀티데이 정확 실현손익**을 보여야 한다. + - 본 Discovery는 그 연동을 trex-core 변경 단위로 정식화한다(portfolio-hub는 기존 API 그대로 — 무변경). + +# 2. 요구사항 판정 결과 +- 판정: 신규 +- 근거 + - dcy-029(REQ-088)는 **당일 net** 실현 PnL을 정의했다(`operator_report.py` 주석: "일중 진입·청산 전략에서는 net ≈ 실현손익"). 멀티데이 WAC 실현손익은 다른 계산 정의이며, **외부(portfolio-hub) WAC API 소비**라는 신규 연동이다. + - trex-core에 portfolio-hub `/pnl/realized` 클라이언트 메서드/포트가 없다(신규). + +# 3. 문제점의 요약 +- 현재 상태 + - `operator_report.py`(REQ-088): `realized_pnl(trades)`가 `ledger_outbox` 당일 거래로 net(매도대금−매수대금−수수료−세금)을 계산. 자산별 분해 포함. `trex-core report` CLI로 노출. + - 이 정의는 **당일 매수→당일 매도(일중)** 에서만 실현손익과 일치한다. **전일 이전 매수분을 오늘 매도**하면, 당일 net은 "오늘 매도대금 − (오늘 없는 매수)"가 되어 cost basis가 빠진 채 실현손익이 과대/왜곡된다(중장기 보유의 본질). + - portfolio-hub `/pnl/realized`(REQ-040)는 매수 누적 WAC를 cost basis로 써 매도별 정확 실현손익을 주지만, trex-core 리포트가 이를 소비하지 않는다. +- 기대 상태 + - 중장기 인스턴스 운영 리포트의 실현 PnL이 **portfolio-hub WAC API(/pnl/realized) 기준의 멀티데이 정확 실현손익**으로 표시된다(매수 cost basis 반영). + - portfolio-hub 이력 불완전 시(`data_completeness_warning`)가 리포트에 표면화된다. + - portfolio-hub 미가용 시 리포트가 안전하게 degrade(에러 표시 또는 당일 net 폴백)한다. +- 이해관계자 + - 사장님 — 리포트 정의(멀티데이 기준)·범위 결정 + - 팀장(Claude) — Discovery/게이트 + - trex-core-dev — delivery + - 운영자 — 중장기 인스턴스 손익 리포트 소비자 +- 제약사항 + - portfolio-hub `/pnl/realized`는 **매도 기준 date-filter, 전 매수 WAC 참여**(REQ-041): 즉 기간 내 *매도*의 실현손익을 주고, 그 cost basis는 기간 밖 매수도 포함. (멀티데이 정의에 부합.) + - **매수만 한 날**은 매도가 없어 realized=0 — 사장님 정의("매수만 한 경우 수수료 비용")를 충족하려면 리포트가 **수수료(비용)를 별도 표시**해야 함(realized와 분리). + - trex-core wire 계약(slots RPC) 무관(리포트는 read-only 운영 표면). portfolio-hub HTTP 호출 추가(read). + +## 주요 사용자 시나리오 +- 운영자: 중장기 인스턴스의 `trex-core report`(또는 동등)로 기간 실현손익을 본다 → 전일 매수분을 오늘 매도한 손익이 WAC 기준으로 정확히 표시(당일 net 왜곡 아님). +- 운영자: 매수만 한 날 → 실현손익 0 + 수수료 비용 표시. 매도한 날 → WAC 실현손익. 둘 다 → 매수(WAC) 반영 후 매도 실현손익. + +# 4. 이번에 정의할 변경 +- 변경 1 — portfolio-hub `/pnl/realized` 소비 포트/어댑터 신규: `PortfolioHubReadPort`(또는 신규 포트)에 realized-pnl read 메서드 + HTTP 어댑터(`GET /v1/portfolios/{id}/pnl/realized`, from/to/symbol). +- 변경 2 — 운영 리포트의 실현 PnL을 WAC 기반으로 **전 인스턴스 통일**(OQ-1 결정): `operator_report.py`가 당일 net 대신 portfolio-hub WAC 실현손익을 사용(단·중·장기 공통 — WAC는 일중에도 정확). data_completeness_warning 표면화. 매수만 한 날의 수수료 비용 별도 표시. +- 변경 3 — degrade: portfolio-hub 미가용 시 **실현손익 섹션에 에러 명시**(OQ-5 결정 — 당일 net 폴백 안 함). 나머지 리포트 섹션(보유/체결/대기주문)은 정상 출력. + +## 영향 범위 +- 코드 영향: `trex_core/adapters/portfolio/*`(realized-pnl read 어댑터 신규), `trex_core/application/portfolio.py`(포트), `trex_core/application/operator_report.py`(실현 PnL 소스 분기), `trex_core/runtime/cli.py`(report 명령 — 기간/모드 옵션 필요시). +- 문서 영향: `docs/discovery/index.md`, srs/batches. `docs/operations.md`(리포트 해석 갱신). +- 운영 영향: 리포트 실현손익 정의가 중장기 모드에서 WAC로 바뀜 — 런북에 명시. portfolio-hub 호출 의존 추가. + +# 5. 요구사항 목록 +## 기능 요구사항 +- FR-1: trex-core가 portfolio-hub `GET /v1/portfolios/{id}/pnl/realized`(from/to/symbol)를 호출하는 read 포트+어댑터를 갖는다. +- FR-2: 운영 리포트의 실현 PnL을 **전 인스턴스 공통으로** portfolio-hub WAC 실현손익 기준으로 산출·표시한다(당일 net 대체, 멀티데이 cost basis 반영). 자산별 분해 유지. +- FR-3: 매수만 한 기간(매도 0)은 실현손익 0 + **수수료 비용을 별도 항목으로 표시**(사장님 정의). +- FR-4: portfolio-hub `data_completeness_warning`(이력 불완전)을 리포트에 표면화한다. +- FR-5: portfolio-hub 미가용 시 **실현손익 섹션에 에러를 명시**하고(폴백 없음), 나머지 리포트 섹션은 정상 출력한다. + +## 비기능 요구사항 +- NFR-1: trex-core wire 계약(slots RPC) 불변 — 리포트는 read-only 운영 표면. +- NFR-2: 전 인스턴스 통일이므로, **일중 매수+매도(같은 날) 케이스에서 WAC 실현손익이 기존 당일 net과 일치**함을 회귀 테스트로 보장한다(단기 라이브 의미 보존 — WAC(=당일 매수가)·매도가 동일 정의). +- NFR-3: portfolio-hub 호출 실패가 데몬 본업(슬롯/집행)에 영향 주지 않는다(리포트 격리). + +## 범위 경계 +- In Scope + - portfolio-hub /pnl/realized 소비(포트+어댑터) + 중장기 운영 리포트 WAC 실현손익 + 매수일 수수료 표시 + completeness 경고 + degrade. +- Out of Scope + - portfolio-hub 측 변경(이미 REQ-040 제공 — 무변경). + - 미실현 손익(현재가 결합), 세무 리포트. + - 슬롯↔계좌 동기화(dcy-034 별건). + +# 6. 리스크/가정 목록 +## 리스크 +- R-1 (Medium): WAC 실현손익 소스 전환이 단기 인스턴스 리포트(당일 net)에 회귀를 줄 수 있다. + - 완화: 모드/인스턴스로 분리(단기=net 유지 or WAC 통일은 OQ-1), 회귀 테스트. +- R-2 (Low): portfolio-hub 호출 추가로 리포트가 외부 의존·지연. 미가용 시 리포트 실패. + - 완화: degrade(폴백/에러 명시, OQ-5) + 본업 격리(NFR-3). + +## 가정 +- A-1: portfolio-hub `/pnl/realized`(REQ-040/041)는 멀티데이 WAC 실현손익을 정확히 제공한다(매도 date-filter + 전 매수 WAC). +- A-2: 중장기 인스턴스는 portfolio_id별로 실현손익을 조회한다(슬롯↔portfolio_id 1:1). +- A-3: trex-core는 portfolio-hub를 세션/토큰으로 호출 가능하다(기존 whoami/get_portfolios 경로 재사용). + +## 오픈 질문 +- OQ-1: WAC 실현손익을 전 인스턴스 통일인지, 중장기 모드만인지. + - 상태: **Answered (2026-06-26, 사장님)** — **전 인스턴스 WAC 통일**(당일 net 폐기). WAC는 일중에도 정확하므로 단기 라이브도 WAC로 통일하되, NFR-2 회귀(일중 케이스 net 일치)로 의미 보존. +- OQ-2: 리포트 기간 정의 — 당일/지정 기간(from~to). /pnl/realized는 from_dt/to_dt(매도 기준). 멀티데이 리포트의 기본 기간·옵션. + - 상태: Open / 처리: design, 팀장 / 종료: 기간 모델 확정 시 Answered +- OQ-3: 매수만 한 날 "수수료 비용" 표시 방식 — 실현손익과 별도 라인(비용)으로. 수수료 출처(portfolio-hub 거래내역 REQ-039 또는 trex-core ledger). + - 상태: Open / 처리: design, 팀장 / 종료: 표시·출처 확정 시 Answered +- OQ-4: trex-core가 realized PnL을 어떻게 조회 — portfolio_id별 N콜 vs daemon 집계. (slots↔portfolio 1:1이라 N콜이 자연스러우나 종목 多면 비용.) + - 상태: Open / 처리: design, 팀장 / 종료: 조회 단위 확정 시 Answered +- OQ-5: portfolio-hub 미가용 시 degrade. + - 상태: **Answered (2026-06-26, 사장님)** — **(b) 실현손익 섹션 에러 명시**(당일 net 폴백 없음). 나머지 섹션(보유/체결/대기주문) 정상 출력. + +### 오픈 질문 처리 기준 +- `Open`/`Answered`/`Deferred`/`Dropped` 표준 처리. + +# 7. 초기 성공 기준 +- S-1: 전일 이전 매수분을 당일 매도한 시나리오에서 리포트 실현손익이 **WAC 기준 정확값**(당일 net 왜곡 아님)으로 표시된다. +- S-2: 매수만 한 기간 → 실현손익 0 + 수수료 비용 표시. +- S-3: portfolio-hub `data_completeness_warning`이 리포트에 표면화된다. +- S-4: portfolio-hub 미가용 시 리포트가 degrade(정의된 방식)하고 데몬 본업 무영향. +- S-5: 일중 매수+매도(같은 날) 케이스에서 WAC 실현손익 = 기존 당일 net (단기 의미 보존, NFR-2). + +## 측정 방식 및 데이터 출처 +- S-1/S-2: 멀티데이 픽스처(전일 매수+당일 매도, 매수만) 통합 테스트(portfolio-hub 모킹) / 신규 테스트. +- S-3/S-4: 경고 주입·미가용 모킹 테스트. +- S-5: 기존 operator_report 테스트 회귀. + +# 8. REQ로 넘기기 전 확인 체크 +- [x] 적용 범위 확정 — OQ-1 = 전 인스턴스 WAC 통일 +- [x] degrade 정책 — OQ-5 = 실현손익 섹션 에러 명시 +- [ ] 기간/수수료/조회단위 design 인계 — OQ-2/3/4 (draft-req/design 단계) +- [x] 성공지표 측정 이견 없음 +- [x] Freeze 담당자(CONFIRMED_BY) 지정 — 팀장 + +# 9. 파일 처리 결과 +- 처리 결과: 생성 +- 생성 경로: docs/discovery/dcy-035_20260626_multiday-realized-pnl-report-wac.md +- 참조 문서: + - docs/discovery/dcy-029(운영 리포트 REQ-088), dcy-032(cost basis 오염) + - portfolio-hub dcy-007/REQ-040/041(WAC 실현 PnL), app/api/v1_portfolios.py:712, app/domain/pnl.py + - trex_core/application/operator_report.py(현행 당일 net), runtime/cli.py(report) + +# 10. 사용자 결정 필요 항목 요약 +## DECIDE +- (OQ-1) WAC 실현손익 적용 범위: 전 인스턴스 통일 vs 중장기 모드만. +- (OQ-5) portfolio-hub 미가용 시 degrade 방식. +## CONFIRM +- 본 건을 M3 중장기 운영 리포트(멀티데이 WAC 실현손익) 단위로 채택 — portfolio-hub 무변경, trex-core 연동. +## DATA +- (OQ-3 보조) 매수일 수수료 출처(portfolio-hub 거래내역 REQ-039 vs trex-core ledger) — 팀장 design서 확인. + +# 11. Discovery Freeze +## 섹션 참조 +- 범위: [# 5 > 범위 경계](#5-요구사항-목록) / 리스크·OQ: [# 6](#6-리스크가정-목록) / 성공: [# 7](#7-초기-성공-기준) / 인계 전 확인: [# 8](#8-req로-넘기기-전-확인-체크) / 결정: [# 10](#10-사용자-결정-필요-항목-요약) +## Freeze 플래그 +- Handoff Decision: 진행 (인계) — OQ-1/OQ-5 사장님 결정 완료, OQ-2/3/4는 draft-req/design 단계 처리 +- Handoff Rationale: portfolio-hub WAC API는 이미 존재(무변경), trex-core 연동만. 적용범위=전 인스턴스 WAC 통일, degrade=섹션 에러 명시 확정. 기간/수수료/조회단위는 REQ·design에서 구체화. +- Ready for REQ Drafting: true +- Confirmed By: 팀장 (Claude) +- Confirmed At (KST): 2026-06-26 + +# 12. 작성 가이드 +- trex-core dcy 템플릿 기반. Open Questions는 가정 대체 금지. confirm-discovery 시 OQ 처리 + Freeze 갱신. diff --git a/docs/srs/Exception/req-111_operator-report-realized-pnl-degrade.md b/docs/srs/Exception/req-111_operator-report-realized-pnl-degrade.md new file mode 100644 index 0000000..9839cb1 --- /dev/null +++ b/docs/srs/Exception/req-111_operator-report-realized-pnl-degrade.md @@ -0,0 +1,77 @@ +# req-111: 운영 리포트 — portfolio-hub 미가용 시 실현손익 섹션 에러 degrade + +- Status: Approved +- Type: Exception +- Priority: High +- Owner: trex-core-dev +- Approved By: 팀장 (Claude), 2026-06-26 (confirm-req) + +## Intent +- portfolio-hub가 미가용(네트워크 오류, 5xx, timeout)이면 실현손익 섹션에 에러를 명시하고 나머지 섹션(보유/체결/대기주문)은 정상 출력한다. 폴백(당일 net 대체)은 없다. (dcy-035 FR-5, OQ-5=에러 명시, NFR-3=데몬 본업 격리) + +## Requirement +- `_cmd_report`에서 portfolio-hub WAC 조회(`get_realized_pnl`) 시 `PortfolioHubUnavailableError`가 발생하면: + - 실현손익 섹션에 에러 메시지를 명시한다(`portfolio-hub unavailable — realized PnL unavailable` 또는 동등한 식별 가능한 문구). + - 당일 net 값으로 대체(폴백) 하지 않는다. + - 나머지 리포트 섹션(보유/체결/대기주문)은 정상적으로 조회·출력한다. +- `--json` 모드에서도 동일하게 실현손익 필드를 `null` 또는 `{"error": "..."}` 형태로 표기하고 나머지 섹션은 정상 포함한다. +- portfolio-hub 호출 실패가 `trex-core-daemon`의 슬롯/집행 루프, AMQP 연결, safety-sell 등 데몬 본업에 영향을 주지 않는다(NFR-3: report는 CLI 단독 실행, 데몬 루프 경계 외부). +- portfolio-hub `base_url` 미설정 시도 동일하게 degrade 처리한다(설정 부재 = 미가용과 동일 경로). + +## Acceptance Criteria +1. Given portfolio-hub가 `PortfolioHubUnavailableError`를 raise 할 때, When `report` 실행, Then 텍스트 출력의 실현손익 섹션에 에러 문구가 표시되고 보유/체결/대기주문 섹션은 정상 출력된다. +2. Given portfolio-hub 미가용 상황에서 `--json` 플래그, When `report` 실행, Then JSON 출력의 실현손익 관련 필드가 에러 식별 가능한 값(`null` 또는 `"error"` 키 포함)이고 `holdings`, `pending`, `fills_count` 필드는 정상 포함된다. +3. Given portfolio-hub 미가용, When `report` 실행, Then 당일 net 값으로 대체(폴백)하지 않는다(당일 net 실현손익 값이 출력에 포함되지 않는다). +4. Given portfolio-hub `base_url` 미설정(`cfg.portfolio_hub is None` 또는 `base_url` 빈값), When `report` 실행, Then AC-1/AC-2와 동일한 degrade 경로를 밟는다. +5. Given `report` CLI가 portfolio-hub 호출 실패로 degrade하는 상황, When 데몬 본업(슬롯 RPC, execution 루프, safety-sell 트리거) 확인, Then report CLI의 예외가 데몬 루프로 전파되지 않는다(구조적 분리 — report는 CLI 독립 실행). + +## Impacted Area +- Docs: 없음(운영 설명은 REQ-110의 docs/operations.md 갱신에 포함) +- Modules: + - `trex_core/runtime/cli.py` — `_cmd_report` 내 `PortfolioHubUnavailableError` 및 설정 부재 catch, degrade 분기 +- Tests: + - `tests/runtime/test_cli.py` 또는 `tests/application/test_operator_report.py` — portfolio-hub 미가용 모킹 테스트(AC-1~4), 폴백 미발생 단언(AC-3) + +## Notes +- OQ-5 결정(사장님, 2026-06-26): 폴백 없이 실현손익 섹션 에러 명시. 나머지 섹션 정상. +- AC-5(데몬 본업 격리)는 구조적으로 `_cmd_report`가 `main()`의 `if args.cmd == "report"` 분기에서 독립 실행됨으로써 보장된다. 데몬 루프(`daemon.py`)는 report CLI를 호출하지 않는다. +- NFR-1(wire 계약 불변)은 이 REQ와 무관(report는 read-only, AMQP 미관여). + +## Change Log + +### CHG-20260626-01 +- Date: 2026-06-26 +- Author: trex-core-dev + +#### Change Summary +- dcy-035 FR-5, OQ-5(에러 명시, 폴백 없음), NFR-3(데몬 본업 격리)로부터 최초 생성(Proposed). + +#### Intent +- portfolio-hub 미가용 시 운영 리포트가 안전하게 degrade하고 데몬 본업에 영향이 없음을 요구사항으로 명시. + +#### Acceptance Criteria Delta +- Added: + - AC-1~5 신규 +- Updated: + - none +- Removed: + - none + +#### Impacted Area Delta +- Docs: 없음 +- Modules: + - cli.py _cmd_report degrade 분기 +- Tests: + - 미가용 모킹 단위 테스트 신규 + +#### 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 승인 게이트 후 suggest-batch-reqs diff --git a/docs/srs/Integration/req-109_portfolio-hub-realized-pnl-read-adapter.md b/docs/srs/Integration/req-109_portfolio-hub-realized-pnl-read-adapter.md new file mode 100644 index 0000000..686f2d7 --- /dev/null +++ b/docs/srs/Integration/req-109_portfolio-hub-realized-pnl-read-adapter.md @@ -0,0 +1,81 @@ +# req-109: portfolio-hub /pnl/realized read 포트 메서드 + HTTP 어댑터 + +- Status: Approved +- Type: Integration +- Priority: High +- Owner: trex-core-dev +- Approved By: 팀장 (Claude), 2026-06-26 (confirm-req) + +## Intent +- 현재 `PortfolioHubReadAdapter`(`hub_read.py`)에는 portfolio-hub WAC 실현 PnL 조회(`GET /v1/portfolios/{id}/pnl/realized`) 메서드가 없다. 운영 리포트가 WAC 기반 실현손익을 정확하게 표시하려면 이 엔드포인트를 소비하는 포트 메서드와 HTTP 어댑터가 필요하다. (dcy-035 FR-1) + +## Requirement +- `PortfolioHubReadPort`(프로토콜, `trex_core/application/portfolio.py`)에 `get_realized_pnl(portfolio_id, from_dt, to_dt, *, symbol)` 메서드를 추가한다. 반환 타입은 `RealizedPnlResult` 데이터클래스로 정의한다. +- `RealizedPnlResult`는 다음 필드를 포함한다: `items: list[RealizedPnlItem]`, `cost_basis_from: str | None`, `data_completeness_warning: bool`, `total_realized_pnl: float`. `RealizedPnlItem`은 최소 `symbol`, `realized_pnl`, `sell_qty`를 포함한다. +- `PortfolioHubReadAdapter`(`hub_read.py`)에 위 메서드를 구현한다: `GET /v1/portfolios/{portfolio_id}/pnl/realized` 를 `from_dt`(ISO8601 date), `to_dt`(ISO8601 date), `symbol`(optional)을 query param으로 전달해 호출하고, 응답을 `RealizedPnlResult`로 파싱·반환한다. 네트워크/HTTP 오류 시 `PortfolioHubUnavailableError`를 raise 한다. +- `StubPortfolioRead`(`stub_read.py`)에도 동일 메서드를 결정론 stub로 추가한다(테스트/CI용). +- 기존 `whoami`, `get_portfolios`, `get_cost_basis` 메서드와 동작은 변경하지 않는다. + +## Acceptance Criteria +1. Given portfolio-hub가 portfolio_id/from_dt/to_dt에 대해 `{"items": [...], "cost_basis_from": "...", "data_completeness_warning": false}` 형태의 200 응답을 반환할 때, When `get_realized_pnl` 호출, Then `RealizedPnlResult`가 올바른 items/cost_basis_from/data_completeness_warning/total_realized_pnl으로 반환된다. +2. Given `symbol` 파라미터가 제공될 때, When `get_realized_pnl` 호출, Then `?symbol=<값>`이 쿼리 파라미터로 포함된다. +3. Given portfolio-hub가 네트워크 오류 또는 비-2xx 응답을 반환할 때, When `get_realized_pnl` 호출, Then `PortfolioHubUnavailableError`가 raise 된다. +4. Given `stub_read.py` stub, When `get_realized_pnl` 호출, Then 네트워크 없이 미리 설정한 고정 `RealizedPnlResult`를 반환한다. +5. Given import sweep, When `trex_core/domain`을 검사, Then 신규 포트/어댑터 관련 IO/네트워크 의존이 domain에 없다(NFR: 도메인 순수성 불변). +6. Given `data_completeness_warning: true` 응답, When 파싱, Then `RealizedPnlResult.data_completeness_warning == True`로 반영된다. + +## Impacted Area +- Docs: 없음(리포트 표면 갱신은 REQ-110/111) +- Modules: + - `trex_core/application/portfolio.py` — `PortfolioHubReadPort`에 `get_realized_pnl` 메서드 추가, `RealizedPnlResult`/`RealizedPnlItem` 데이터클래스 신규 정의 + - `trex_core/adapters/portfolio/hub_read.py` — `get_realized_pnl` HTTP 구현 추가 + - `trex_core/adapters/portfolio/stub_read.py` — stub 메서드 추가 +- Tests: + - `tests/adapters/portfolio/test_hub_read.py` — 신규 AC-1~3, AC-6 케이스 추가 + - `tests/adapters/portfolio/test_stub_read.py` — AC-4 stub 케이스 추가 + - `tests/domain/test_domain_import_sweep.py` — AC-5 회귀(기존 sweep 재실행) + +## Notes +- portfolio-hub `GET /v1/portfolios/{id}/pnl/realized` 계약(REQ-040/041): from_dt/to_dt는 **매도 기준** date-filter. cost basis는 기간 밖 매수도 WAC에 포함(멀티데이 정의). 응답 envelope는 portfolio-hub `app/api/v1_portfolios.py:712`·`app/domain/pnl.py`·schemas 기준 — 구현 시 실 계약을 `hub_read.py`의 기존 `get_cost_basis` 패턴(graceful degrade 방식)에 맞춰 구현한다. +- OQ-2(기간 파라미터 기본값 정의), OQ-3(수수료 출처), OQ-4(N콜 vs 집계) 미결 → 이 REQ는 어댑터 계층만 정의. 기간 인자는 caller(REQ-110의 CLI/report 레이어)가 결정해 전달. 어댑터는 전달된 파라미터를 그대로 쿼리에 쓴다. +- `total_realized_pnl`은 `items`의 `realized_pnl` 합산으로 어댑터 내에서 계산(portfolio-hub 응답에 합산 필드가 있으면 재사용, 없으면 클라이언트 합산). +- 기존 `get_cost_basis`(REQ-105 FR-7)는 sync CLI용 포트로 독립 유지 — 이 REQ와 중복 아님. + +## Change Log + +### CHG-20260626-01 +- Date: 2026-06-26 +- Author: trex-core-dev + +#### Change Summary +- dcy-035 FR-1로부터 최초 생성(Proposed). portfolio-hub WAC 실현 PnL read 포트 + HTTP 어댑터 + stub. + +#### Intent +- 운영 리포트(REQ-110)가 WAC 실현손익을 표시하기 위한 하위 의존 어댑터 정의. + +#### Acceptance Criteria Delta +- Added: + - AC-1~6 신규 +- Updated: + - none +- Removed: + - none + +#### Impacted Area Delta +- Docs: 없음 +- Modules: + - portfolio.py 포트 확장, hub_read.py/stub_read.py 어댑터 신규 메서드 +- Tests: + - test_hub_read.py, test_stub_read.py, test_domain_import_sweep.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 승인 게이트 후 suggest-batch-reqs diff --git a/docs/srs/Interface/req-110_operator-report-wac-realized-pnl.md b/docs/srs/Interface/req-110_operator-report-wac-realized-pnl.md new file mode 100644 index 0000000..4e39a28 --- /dev/null +++ b/docs/srs/Interface/req-110_operator-report-wac-realized-pnl.md @@ -0,0 +1,87 @@ +# req-110: 운영 리포트 WAC 실현손익 통합 — 전 인스턴스 공통 + +- Status: Approved +- Type: Interface +- Priority: High +- Owner: trex-core-dev +- Approved By: 팀장 (Claude), 2026-06-26 (confirm-req) + +## Intent +- 현재 `operator_report.py`(REQ-088)의 `realized_pnl(trades)`은 당일 net 현금흐름(`Σ매도대금 − Σ매수대금 − 수수료 − 세금`)을 사용한다. 중장기 인스턴스에서 전일 매수→당일 매도 시 cost basis가 반영되지 않아 실현손익이 왜곡된다. portfolio-hub WAC API(`/pnl/realized`)를 소비해 전 인스턴스 공통으로 정확한 실현손익을 산출하고 리포트에 표시한다. (dcy-035 FR-2, FR-3, FR-4, OQ-1=전 인스턴스 WAC 통일, NFR-2) + +## Requirement +- `operator_report.py`의 `OperatorReport`에 WAC 실현손익 관련 필드를 추가한다: `realized_pnl_wac: float`, `realized_pnl_wac_by_asset: dict[str, float]`, `buy_fee_only_assets: dict[str, float]`(매수만 한 기간의 수수료 비용 별도), `data_completeness_warning: bool`. +- 운영 리포트(`trex-core report` CLI)는 당일 net 대신 portfolio-hub WAC 실현손익(`/pnl/realized`)을 **전 인스턴스 공통**으로 사용해 실현손익을 표시한다. +- 매도가 있는 자산: WAC 기준 실현손익(`(매도가 − WAC) × qty − fee − tax`)을 자산별로 분해해 출력한다. +- 매수만 한 기간(해당 자산의 매도 0): 실현손익 0으로 표시하고, **수수료 비용(`buy_fee_only_assets`)을 별도 항목으로 명시**한다. 수수료 출처는 `ledger_outbox` 당일 거래(기존 경로 재사용; OQ-3 deferred). +- portfolio-hub 응답의 `data_completeness_warning: true` 시 리포트 출력에 경고를 표면화한다(텍스트/JSON 모두 포함). +- `render_text` / `render_json`을 WAC 모드에 맞게 갱신한다. +- CLI `report` 서브커맨드에서 portfolio-hub 설정(`cfg.portfolio_hub.base_url`)이 있을 때 WAC 경로를 사용하고, 미설정이면 REQ-111 degrade 경로로 위임한다. + +## Acceptance Criteria +1. Given 전일 매수 + 당일 매도 시나리오(portfolio-hub `/pnl/realized` 모킹 응답), When `report` 실행, Then 실현손익이 WAC 기준 정확값(`(매도가 − WAC) × qty − fee − tax`)으로 표시되고 당일 net 왜곡값과 다르다. +2. Given 동일 날 매수 + 매도(일중) 시나리오(WAC = 당일 매수가), When `report` 실행, Then WAC 실현손익 == 기존 당일 net과 수치 일치한다(NFR-2 회귀 보장). +3. Given 매수만 한 기간(매도 0), When `report` 실행, Then 실현손익 0 + `buy_fee_only_assets` 수수료 비용이 별도 항목으로 표시된다. +4. Given portfolio-hub 응답에 `data_completeness_warning: true`, When `report` 실행, Then 텍스트 출력과 `--json` 출력 모두에 completeness 경고가 표시된다. +5. Given 자산별 실현손익 분해, When `report` 실행, Then `realized_pnl_wac_by_asset`에 자산(symbol)별 WAC 실현손익이 분해된다. +6. Given `--json` 플래그, When `report` 실행, Then `render_json` 출력에 `realized_pnl_wac`, `realized_pnl_wac_by_asset`, `buy_fee_only_assets`, `data_completeness_warning` 필드가 포함된다. +7. Given portfolio-hub `base_url` 미설정(cfg.portfolio_hub None 또는 base_url 미정의), When `report` 실행, Then REQ-111 degrade 경로로 처리된다(에러 표시). + +## Impacted Area +- Docs: + - `docs/operations.md` — 리포트 실현손익 정의 갱신(당일 net → WAC 기준, 매수일 수수료 표시 명시) + - `docs/runtime-flows.md` — operator report 흐름에 portfolio-hub WAC 호출 경로 추가 +- Modules: + - `trex_core/application/operator_report.py` — `OperatorReport` 필드 확장, 기존 `realized_pnl(trades)` 함수는 legacy/테스트 목적 유지(NFR-2 회귀 비교용), WAC 모드 신규 함수/빌더, `render_text`/`render_json` 갱신 + - `trex_core/runtime/cli.py` — `_cmd_report` 내 portfolio-hub WAC 호출 분기, `from_dt`/`to_dt` 파라미터 전달 (OQ-2 deferred: 기본값=오늘 KST 경계로 구현) +- Tests: + - 신규: 멀티데이 픽스처(전일 매수+당일 매도) 통합 테스트, 매수만 기간 테스트, data_completeness_warning 테스트, NFR-2 일중 회귀 단위 테스트 + - 기존 `test_operator_report.py` 회귀 (당일 net 함수 동작 보존) + +## Notes +- OQ-2(기간 모델): from_dt/to_dt는 오늘(KST 경계) 기본값으로 구현. 기간 범위 확장(지정 기간)은 design/팀장 결정 대기 → 기본 구현은 "당일 KST 매도 기준" 유지, `--from`/`--to` CLI 옵션 추가는 OQ-2 확정 후 별도 change-req 또는 design 인계. +- OQ-3(수수료 출처): 매수만 한 날의 수수료는 `ledger_outbox` 당일 거래에서 집계(기존 경로 재사용). portfolio-hub 거래내역(REQ-039) 사용 여부는 design 인계. +- OQ-4(N콜 vs 집계): 슬롯↔portfolio 1:1이므로 portfolio_id별 N콜이 기본 구현. 집계 최적화는 design 인계. +- 기존 `realized_pnl(trades)` 함수는 삭제하지 않고 유지한다(NFR-2 회귀 비교, 기존 테스트 보존). +- NFR-1(wire 계약 불변): report는 read-only 운영 표면 — slots RPC/AMQP 계약 무관. REQ 명시 불필요. +- NFR-3(데몬 본업 격리): `_cmd_report`는 CLI 직접 실행 경로이며 데몬 루프와 무관 — 격리는 구조적으로 보장됨. + +## Change Log + +### CHG-20260626-01 +- Date: 2026-06-26 +- Author: trex-core-dev + +#### Change Summary +- dcy-035 FR-2/FR-3/FR-4, OQ-1(전 인스턴스 WAC 통일), NFR-2(일중 회귀)로부터 최초 생성(Proposed). + +#### Intent +- 중장기 인스턴스 실현손익 왜곡 해소 + 단기 회귀 보장 + 매수일 수수료 표시 + completeness 경고 표면화. + +#### Acceptance Criteria Delta +- Added: + - AC-1~7 신규 +- Updated: + - none +- Removed: + - none + +#### Impacted Area Delta +- Docs: + - docs/operations.md, docs/runtime-flows.md +- Modules: + - operator_report.py 확장, cli.py _cmd_report 분기 +- Tests: + - 신규 멀티데이/매수전용/completeness/NFR-2 테스트, 기존 회귀 + +#### 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 승인 게이트 후 suggest-batch-reqs diff --git a/docs/srs/index.md b/docs/srs/index.md index 8c5459f..19d775c 100644 --- a/docs/srs/index.md +++ b/docs/srs/index.md @@ -74,7 +74,7 @@ ## Next Requirement ID -- Current: `REQ-109` +- Current: `REQ-112` - Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. ## Requirement Register @@ -189,9 +189,13 @@ | REQ-106 | trex-core verify 명령 — C 불일치 슬롯 재대조 + warning 해제 (C 전용 OQ-C) | Interface | Implemented | High | trex-core-dev | [req-106](./Interface/req-106_verify-cli-command.md) | | REQ-107 | 차단/해제 전이 식별 로깅 + 고영향 불일치 알람 | Exception | Implemented | High | trex-core-dev | [req-107](./Exception/req-107_blocking-reconcile-logging-and-alarm.md) | | REQ-108 | 차단형 reconcile 비기능 요건 — 내구성·회귀 방지·자동 정정 금지·wire 계약 최소 침습 | Non-Functional | Implemented | High | trex-core-dev | [req-108](./Non-Functional/req-108_reconcile-blocking-regression-and-nfr.md) | +| REQ-109 | portfolio-hub /pnl/realized read 포트 메서드 + HTTP 어댑터 | Integration | Approved | High | trex-core-dev | [req-109](./Integration/req-109_portfolio-hub-realized-pnl-read-adapter.md) | +| REQ-110 | 운영 리포트 WAC 실현손익 통합 — 전 인스턴스 공통 | Interface | Approved | High | trex-core-dev | [req-110](./Interface/req-110_operator-report-wac-realized-pnl.md) | +| REQ-111 | 운영 리포트 — portfolio-hub 미가용 시 실현손익 섹션 에러 degrade | Exception | Approved | High | trex-core-dev | [req-111](./Exception/req-111_operator-report-realized-pnl-degrade.md) | ## Recent Change Log Summary +- 2026-06-26 :: REQ-109~111 **신규 생성(Proposed, draft-req dcy-035)**: 멀티데이 실현 PnL — WAC 기반 운영 리포트. REQ-109(portfolio-hub /pnl/realized read 포트+어댑터+stub), REQ-110(운영 리포트 WAC 실현손익 통합: 전 인스턴스 공통, 매수일 수수료 별도, completeness 경고), REQ-111(portfolio-hub 미가용 시 실현손익 섹션 에러 degrade, 폴백 없음). NFR-1(wire 불변)/NFR-3(데몬 격리)은 REQ-111 AC/Notes 흡수. NFR-2(일중 WAC==net 회귀)는 REQ-110 AC-2 흡수. OQ-2(기간)/OQ-3(수수료 출처)/OQ-4(N콜) design 인계. - 2026-06-26 :: REQ-102~108 **Approved → Implemented** (confirm-req-implemented, dcy-034): BAT-050(core-A, PR #31 ced7eda) + BAT-051(core-B, PR #32 07b393d) origin/main 머지 완료. QC(opus) PASS + 팀장 confirm-batch-verification. 최종 683 passed/47 skipped/0 fail. 차단형 reconcile(REQ-102·103), 전략 차단 게이트(REQ-104), sync/verify CLI(REQ-105·106), 차단/해제 로깅+알람(REQ-107), NFR(REQ-108) 전 AC 검증 완료. M3 배포 대기. - 2026-06-25 :: REQ-102~108 **Proposed → Approved** (confirm-req, 팀장): dcy-034 OQ-A/B/C 결정 + FR-7 참조 구체화 반영. **OQ-A**: REQ-105에 INACTIVE→HOLDING 전이 엣지 추가 승인(actor=sync/reconcile 제한, wire 무영향 — enum 5개 불변·엣지만 추가), AC-10 신규, FR-7 → portfolio-hub dcy-008/REQ-043(`GET /v1/portfolios/{id}/holdings/cost-basis`) 구체 참조 갱신. **OQ-B**: REQ-104에 차단 예외 명시 — 내부 안전망(safety-sell/watchdog/kill-switch)은 reconcile-warning 차단 우회, AC-2 갱신. **OQ-C**: REQ-106에 verify=C 전용 확정, B 케이스는 sync(REQ-105) 책임 명시. REQ-102·103·107·108은 초안 그대로 승인. - 2026-06-25 :: REQ-102~108 **신규 생성(Proposed, draft-req dcy-034)**: 슬롯↔실계좌 차단형 reconcile + sync/verify 명령 — M3 cold-start. REQ-102(durable warning store), REQ-103(B/C 차단형 reconcile), REQ-104(pause∨warning OR 게이트), REQ-105(sync CLI), REQ-106(verify CLI), REQ-107(차단/해제 로깅+알람), REQ-108(NFR: 내구성·회귀·자동정정금지·wire계약최소침습). FR-7(portfolio-hub WAC read)은 portfolio-hub-dev 별도 REQ로 위임, trex-core REQ에는 의존 참조만 포함.