docs(dcy-035): 멀티데이 WAC 실현PnL 운영리포트 — discovery confirmed + REQ-109/110/111 approved

- dcy-035 confirm-discovery: OQ-1=전 인스턴스 WAC 통일, OQ-5=섹션 에러 degrade
- REQ-109 portfolio-hub /pnl/realized read 어댑터(포트+HTTP+stub)
- REQ-110 운영리포트 WAC 실현손익 통합(전 인스턴스, 매수일 수수료, completeness, 일중 net 회귀)
- REQ-111 portfolio-hub 미가용 시 실현손익 섹션 에러 degrade(폴백 없음)
- srs/index register 갱신

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
2026-06-26 18:18:39 +09:00
co-authored by Claude Opus 4.8
parent 5b2e256c8d
commit e32eb78656
5 changed files with 404 additions and 1 deletions
@@ -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
@@ -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
@@ -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
+5 -1
View File
@@ -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에는 의존 참조만 포함.