Forward ledger venue provenance to Portfolio Hub (#99)
Co-authored-by: Hermes Agent <noreply@nousresearch.com>
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user