fix(ledger): retain SOR fills with unknown venue
This commit is contained in:
@@ -6,10 +6,12 @@ critical path while the ledger write is eventually-consistent.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from trex_core.adapters.broker.kis import KISBroker
|
||||
from trex_core.adapters.broker.paper import PaperBroker
|
||||
from trex_core.adapters.ledger.in_memory import InMemoryLedger
|
||||
from trex_core.adapters.persistence.in_memory_intent_store import InMemoryIntentStore
|
||||
from trex_core.application.execution import ExecutionService
|
||||
from trex_core.application.broker_port import FillStatus
|
||||
from trex_core.application.intents import BuyIntent, IntentService, SellIntent
|
||||
from trex_core.application.ledger_outbox import (
|
||||
InMemoryLedgerOutbox,
|
||||
@@ -44,6 +46,36 @@ def _buy(intents, *, iid="b1", portfolio_id="PF-1"):
|
||||
))
|
||||
|
||||
|
||||
class KisSorUnknownVenueBroker(PaperBroker):
|
||||
"""KIS daily-ccld fixture with SOR routing but no actual exchange evidence."""
|
||||
|
||||
def poll_fill(self, broker_order_id, *, side, symbol, **kwargs):
|
||||
return KISBroker._fill_status_from_row(
|
||||
{
|
||||
"ord_qty": "10",
|
||||
"tot_ccld_qty": "10",
|
||||
"avg_prvs": "1000",
|
||||
"ord_dt": "20260824",
|
||||
"ord_gno_brno": "12345678",
|
||||
"odno": broker_order_id,
|
||||
"excg_id_dvsn_cd": "SOR",
|
||||
},
|
||||
side=side,
|
||||
today="20260824",
|
||||
fallback_odno=broker_order_id,
|
||||
)
|
||||
|
||||
|
||||
class MissingExecutionIdBroker(PaperBroker):
|
||||
def poll_fill(self, broker_order_id, *, side, symbol, **kwargs):
|
||||
return FillStatus(
|
||||
state="filled",
|
||||
filled_qty=10,
|
||||
avg_price=1000,
|
||||
execution_venue="KRX",
|
||||
)
|
||||
|
||||
|
||||
def test_buy_fill_enqueues_one_ledger_item_and_writer_records_it():
|
||||
ledger = InMemoryLedger()
|
||||
repo, svc, intents, execution, outbox, writer = _wire(PaperBroker(fill_after_polls=0),
|
||||
@@ -63,6 +95,36 @@ def test_buy_fill_enqueues_one_ledger_item_and_writer_records_it():
|
||||
assert t.venue == "KRX" # paper's deterministic SOR execution exchange
|
||||
|
||||
|
||||
def test_kis_sor_fill_without_actual_venue_enqueues_and_writes_once() -> None:
|
||||
"""Issues #65/#66: SOR provenance must not suppress an unknown actual venue fill."""
|
||||
ledger = InMemoryLedger()
|
||||
repo, _, intents, execution, outbox, writer = _wire(KisSorUnknownVenueBroker(), ledger=ledger)
|
||||
|
||||
sid = _buy(intents, iid="sor-unknown").slot_id
|
||||
result = execution.run_once()
|
||||
|
||||
assert result.filled == 1
|
||||
assert result.ledger_enqueued == 1
|
||||
assert result.ledger_skipped == 0
|
||||
assert repo.fetch_slot(sid).status is SlotState.HOLDING
|
||||
[item] = outbox.all_items()
|
||||
assert item.trade.venue is None
|
||||
assert item.trade.order_venue == "SOR"
|
||||
|
||||
assert writer.run_once().completed == 1
|
||||
assert ledger.count() == 1
|
||||
[trade] = ledger.trades()
|
||||
assert trade.execution_id == item.trade.execution_id
|
||||
assert trade.venue is None
|
||||
assert trade.order_venue == "SOR"
|
||||
|
||||
# Replay remains idempotent at the durable outbox and portfolio ledger boundaries.
|
||||
outbox.enqueue(item.trade)
|
||||
assert len(outbox.all_items()) == 1
|
||||
assert writer.run_once().completed == 0
|
||||
assert ledger.count() == 1
|
||||
|
||||
|
||||
def test_sell_fill_enqueues_sell_ledger_item():
|
||||
ledger = InMemoryLedger()
|
||||
repo, svc, intents, execution, outbox, writer = _wire(PaperBroker(fill_after_polls=0),
|
||||
@@ -100,6 +162,20 @@ def test_portfolio_id_absent_skips_ledger_but_transitions_slot():
|
||||
assert ledger.count() == 0
|
||||
|
||||
|
||||
def test_missing_execution_id_still_skips_malformed_ledger_work_item() -> None:
|
||||
repo, _, intents, execution, outbox, _ = _wire(MissingExecutionIdBroker())
|
||||
|
||||
sid = _buy(intents, iid="missing-execution-id").slot_id
|
||||
assert sid is not None
|
||||
result = execution.run_once()
|
||||
|
||||
assert result.filled == 1
|
||||
assert result.ledger_enqueued == 0
|
||||
assert result.ledger_skipped == 1
|
||||
assert repo.fetch_slot(sid).status is SlotState.HOLDING
|
||||
assert outbox.all_items() == []
|
||||
|
||||
|
||||
def test_ledger_skip_emits_warning(caplog):
|
||||
"""dcy-011 / REQ-018 FR-4 (S-3): ledger skip is logged, not silent."""
|
||||
import logging
|
||||
|
||||
Reference in New Issue
Block a user