Forward ledger venue provenance to Portfolio Hub (#99)

Co-authored-by: Hermes Agent <noreply@nousresearch.com>
This commit is contained in:
2026-09-19 19:30:57 +09:00
committed by GitHub
co-authored by Hermes Agent
parent bbdacb121c
commit cd2342f2c6
3 changed files with 38 additions and 3 deletions
+19 -2
View File
@@ -4,8 +4,8 @@
- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml - Baseline Seed: .stage-pilot/bootstrap/baseline.yaml
- Status: draft - Status: draft
- Owner: project maintainers - Owner: project maintainers
- Last Updated (KST): 2026-07-15 (BAT-057 — operator broker truth hotfix baseline alignment) - 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 - 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 도메인) - Primary Runtime: daemon (HTTP API) + library (slot 도메인)
## Project Summary ## Project Summary
@@ -259,6 +259,23 @@ resume_kill_switch 요청·응답 (REQ-046):
| GET | `/v1/auth/whoami` | `{daemon_id: str, ...}` | | GET | `/v1/auth/whoami` | `{daemon_id: str, ...}` |
| GET | `/v1/portfolios?managed_by_daemon_id=<id>` | `{items: [PortfolioItem, ...]}` 또는 list root | | GET | `/v1/portfolios?managed_by_daemon_id=<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) ## Interface: `SnapshotStore` + `PauseStore` + `SnapshotScheduler` (Protocol, BAT-008)
- Type: internal-service - Type: internal-service
@@ -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( return LedgerTrade(
asset_id=asset, order_id="PAPER-000001", execution_id=execution_id, asset_id=asset, order_id="PAPER-000001", execution_id=execution_id,
side="buy", qty=10, price=1000.0, fee=1.5, tax=0.0, side="buy", qty=10, price=1000.0, fee=1.5, tax=0.0,
executed_at=datetime(2026, 6, 4, 9, 0, tzinfo=timezone.utc), 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)) 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(): def test_post_contract_includes_execution_id_and_path_uses_portfolio_id():
captured = {} captured = {}
@@ -78,6 +78,8 @@ class PortfolioHubLedger:
"executed_at": trade.executed_at.isoformat() if trade.executed_at else None, "executed_at": trade.executed_at.isoformat() if trade.executed_at else None,
"execution_id": trade.execution_id, "execution_id": trade.execution_id,
"order_id": trade.order_id, "order_id": trade.order_id,
"execution_venue": trade.venue,
"order_venue": trade.order_venue,
} }
def _post(self, url: str, json: dict) -> httpx.Response: def _post(self, url: str, json: dict) -> httpx.Response: