From cd2342f2c69267c471b5e81658c58332e00857de Mon Sep 17 00:00:00 2001 From: Jinhwan Ko Date: Sat, 19 Sep 2026 19:30:57 +0900 Subject: [PATCH] Forward ledger venue provenance to Portfolio Hub (#99) Co-authored-by: Hermes Agent --- docs/interface-contract.md | 21 +++++++++++++++++-- .../ledger/test_portfolio_hub_ledger.py | 18 +++++++++++++++- trex_core/adapters/ledger/portfolio_hub.py | 2 ++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/docs/interface-contract.md b/docs/interface-contract.md index efa3afd..efefbb5 100644 --- a/docs/interface-contract.md +++ b/docs/interface-contract.md @@ -4,8 +4,8 @@ - Baseline Seed: .stage-pilot/bootstrap/baseline.yaml - Status: draft - Owner: project maintainers -- Last Updated (KST): 2026-07-15 (BAT-057 — operator broker truth hotfix baseline alignment) -- Source Discovery / Batch: bootstrap-baseline (pre-Discovery); HTTP API = dcy-002 / BAT-002; operator broker truth = dcy-037 / BAT-057 +- Last Updated (KST): 2026-09-19 (portfolio-hub Issue #32 venue provenance transport mapping) +- Source Discovery / Batch: bootstrap-baseline (pre-Discovery); HTTP API = dcy-002 / BAT-002; operator broker truth = dcy-037 / BAT-057; ledger venue transport = portfolio-hub ProofLine req-0009 - Primary Runtime: daemon (HTTP API) + library (slot 도메인) ## Project Summary @@ -259,6 +259,23 @@ resume_kill_switch 요청·응답 (REQ-046): | GET | `/v1/auth/whoami` | `{daemon_id: str, ...}` | | GET | `/v1/portfolios?managed_by_daemon_id=` | `{items: [PortfolioItem, ...]}` 또는 list root | +## Interface: `LedgerPort` → portfolio-hub trade write (BAT-006; portfolio-hub REQ-0009) + +- Type: external-service (HTTP, portfolio-hub) +- Consumers / Producers: `LedgerWriter`(소비) → `PortfolioHubLedger`(transport adapter) → portfolio-hub +- Purpose: durable `LedgerTrade`를 `POST /v1/portfolios/{portfolio_id}/ledger/trades` 요청으로 직렬화한다. + +### Venue Provenance Mapping + +| Core field | HTTP request field | Contract | +|---|---|---| +| `LedgerTrade.venue` | `execution_venue` | 실제 체결 venue. `KRX`, `NXT`, `null`; `SOR` 금지 | +| `LedgerTrade.order_venue` | `order_venue` | 원래 주문 routing venue. `KRX`, `NXT`, `SOR` | + +- `PortfolioHubLedger._payload()`는 두 값을 그대로 전달하며 `order_venue`에서 `execution_venue`를 추론하지 않는다. +- Core producer, `LedgerTrade` domain model, durable outbox schema와 broker-routing 동작은 변경하지 않는다. 기존 outbox가 보존한 두 필드가 HTTP 경계에서 누락되지 않도록 하는 transport mapping이다. +- Rollout 순서: nullable fields를 수용하는 portfolio-hub API/migration을 먼저 또는 동시에 배포한 뒤 이 adapter를 배포한다. 구형 Core가 두 필드를 보내지 않는 mixed-version 요청은 Hub에서 `null`로 수용한다. + ## Interface: `SnapshotStore` + `PauseStore` + `SnapshotScheduler` (Protocol, BAT-008) - Type: internal-service diff --git a/tests/adapters/ledger/test_portfolio_hub_ledger.py b/tests/adapters/ledger/test_portfolio_hub_ledger.py index f3f9ccd..2eca9de 100644 --- a/tests/adapters/ledger/test_portfolio_hub_ledger.py +++ b/tests/adapters/ledger/test_portfolio_hub_ledger.py @@ -17,11 +17,13 @@ from trex_core.application.ledger_port import ( ) -def _trade(asset="PF-1", execution_id="paper:PAPER-000001:10") -> LedgerTrade: +def _trade(asset="PF-1", execution_id="paper:PAPER-000001:10", *, + venue=None, order_venue="KRX") -> LedgerTrade: return LedgerTrade( asset_id=asset, order_id="PAPER-000001", execution_id=execution_id, side="buy", qty=10, price=1000.0, fee=1.5, tax=0.0, executed_at=datetime(2026, 6, 4, 9, 0, tzinfo=timezone.utc), + venue=venue, order_venue=order_venue, ) @@ -29,6 +31,20 @@ def _client(handler): return httpx.Client(transport=httpx.MockTransport(handler)) +def test_payload_maps_execution_and_order_venues(): + cases = [ + (None, "SOR"), + ("KRX", "NXT"), + ("NXT", "KRX"), + ] + for execution_venue, order_venue in cases: + body = PortfolioHubLedger._payload( + _trade(venue=execution_venue, order_venue=order_venue) + ) + assert body["execution_venue"] == execution_venue + assert body["order_venue"] == order_venue + + def test_post_contract_includes_execution_id_and_path_uses_portfolio_id(): captured = {} diff --git a/trex_core/adapters/ledger/portfolio_hub.py b/trex_core/adapters/ledger/portfolio_hub.py index caf79bf..5a33f53 100644 --- a/trex_core/adapters/ledger/portfolio_hub.py +++ b/trex_core/adapters/ledger/portfolio_hub.py @@ -78,6 +78,8 @@ class PortfolioHubLedger: "executed_at": trade.executed_at.isoformat() if trade.executed_at else None, "execution_id": trade.execution_id, "order_id": trade.order_id, + "execution_venue": trade.venue, + "order_venue": trade.order_venue, } def _post(self, url: str, json: dict) -> httpx.Response: