fix: preserve SOR execution venue separately
This commit is contained in:
@@ -137,6 +137,44 @@ def test_submit_sor_preserves_sor(monkeypatch):
|
||||
assert captured["body"]["EXCG_ID_DVSN_CD"] == "SOR"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("row", "expected"),
|
||||
[
|
||||
({"ord_exg_gb": "1"}, "KRX"),
|
||||
({"ord_exg_gb": "2"}, "NXT"),
|
||||
({"ord_exg_gb": "3"}, "KRX"),
|
||||
({"ord_exg_gb": "4"}, "NXT"),
|
||||
({"excg_id_dvsn_cd": "KRX"}, "KRX"),
|
||||
({"excg_id_dvsn_cd": "NXT"}, "NXT"),
|
||||
({"excg_id_dvsn_cd": "SOR-KRX"}, "KRX"),
|
||||
({"excg_id_dvsn_Cd": "SOR-NXT"}, "NXT"),
|
||||
({"excg_id_dvsn_cd": "SOR"}, None),
|
||||
({"excg_dvsn_cd": "02"}, None),
|
||||
({}, None),
|
||||
],
|
||||
)
|
||||
def test_kis_execution_venue_parser_is_allowlisted(row, expected):
|
||||
assert KISBroker._execution_venue_from_row(row) == expected
|
||||
|
||||
|
||||
def test_kis_fill_status_carries_sor_actual_execution_venue():
|
||||
status = KISBroker._fill_status_from_row(
|
||||
{
|
||||
"ord_qty": "10",
|
||||
"tot_ccld_qty": "10",
|
||||
"avg_prvs": "70000",
|
||||
"ord_dt": "20260819",
|
||||
"ord_gno_brno": "12345",
|
||||
"odno": "0000000001",
|
||||
"excg_id_dvsn_cd": "NXT",
|
||||
},
|
||||
side="buy",
|
||||
today="20260819",
|
||||
fallback_odno="0000000001",
|
||||
)
|
||||
assert status.execution_venue == "NXT"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# REQ-117 AC-4: 기존 위치 인수 순서 불변
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -20,7 +20,8 @@ def _trade(execution_id="paper:1:10") -> LedgerTrade:
|
||||
return LedgerTrade(
|
||||
asset_id="PF-1", order_id="ORD-1", execution_id=execution_id,
|
||||
side="buy", qty=10, price=1000.0, fee=1.5, tax=0.0,
|
||||
executed_at=datetime(2026, 6, 4, tzinfo=timezone.utc), venue="NXT",
|
||||
executed_at=datetime(2026, 6, 4, tzinfo=timezone.utc),
|
||||
venue="NXT", order_venue="SOR",
|
||||
)
|
||||
|
||||
|
||||
@@ -33,6 +34,7 @@ def test_enqueue_persists_and_is_idempotent(pg_clean):
|
||||
assert len(pending) == 1
|
||||
assert pending[0].status is WorkItemStatus.PENDING
|
||||
assert pending[0].trade.venue == "NXT"
|
||||
assert pending[0].trade.order_venue == "SOR"
|
||||
|
||||
|
||||
def test_writer_completes_persisted_item(pg_clean):
|
||||
|
||||
@@ -116,7 +116,8 @@ def test_partial_fill_then_cancel_records_and_keeps_remainder():
|
||||
broker.balance = {"005930": 6}
|
||||
broker.responder = lambda side: FillStatus(
|
||||
state="partially_filled", filled_qty=4, avg_price=1100.0,
|
||||
execution_id="exec-partial", fee=0.5, tax=0.0, cancelled=True,
|
||||
execution_id="exec-partial", fee=0.5, tax=0.0,
|
||||
execution_venue="KRX", cancelled=True,
|
||||
)
|
||||
r = execution.run_once()
|
||||
slot = repo.fetch_slot(sid)
|
||||
|
||||
@@ -148,6 +148,7 @@ def _make_filled_status(
|
||||
execution_id=execution_id,
|
||||
fee=fee,
|
||||
tax=tax,
|
||||
execution_venue="KRX",
|
||||
)
|
||||
|
||||
|
||||
@@ -167,6 +168,7 @@ def _make_partial_cancel_status(
|
||||
execution_id=execution_id,
|
||||
fee=fee,
|
||||
tax=tax,
|
||||
execution_venue="KRX",
|
||||
cancelled=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ def test_buy_fill_enqueues_one_ledger_item_and_writer_records_it():
|
||||
assert (t.asset_id, t.side, t.qty, t.price) == ("PF-1", "buy", 10, 1000.0)
|
||||
assert t.execution_id.startswith("paper:PAPER-")
|
||||
assert t.fee > 0.0 and t.tax == 0.0
|
||||
assert t.venue == "SOR"
|
||||
assert t.order_venue == "SOR"
|
||||
assert t.venue == "KRX" # paper's deterministic SOR execution exchange
|
||||
|
||||
|
||||
def test_sell_fill_enqueues_sell_ledger_item():
|
||||
|
||||
@@ -33,7 +33,7 @@ class RecordingBroker:
|
||||
return OrderAck(
|
||||
accepted=True,
|
||||
broker_order_id=f"order-{len(self.orders)}",
|
||||
venue=self.actual_venue or req.venue,
|
||||
venue=req.venue,
|
||||
)
|
||||
|
||||
def poll_fill(self, broker_order_id: str, *, side: str, symbol: str) -> FillStatus:
|
||||
@@ -121,7 +121,7 @@ def test_invalid_venue_is_rejected_before_slot_transition(invalid_venue: str) ->
|
||||
assert repo.list_slots("default")[0].status is SlotState.INACTIVE
|
||||
|
||||
|
||||
def test_omitted_wire_venue_is_sor_then_actual_venue_is_persisted() -> None:
|
||||
def test_omitted_wire_venue_remains_sor_for_cancel_and_restart() -> None:
|
||||
repo = InMemorySlotRepository()
|
||||
service = SlotService(repo)
|
||||
intents = IntentService(
|
||||
@@ -153,7 +153,9 @@ def test_omitted_wire_venue_is_sor_then_actual_venue_is_persisted() -> None:
|
||||
execution.run_once()
|
||||
dispatched = repo.fetch_slot(slot_id)
|
||||
assert broker.orders[0].venue == "SOR"
|
||||
assert dispatched is not None and dispatched.venue == "NXT"
|
||||
# Ack cannot turn SOR routing provenance into an execution venue. Actual
|
||||
# KRX/NXT is established by fill polling and stored on the ledger trade.
|
||||
assert dispatched is not None and dispatched.venue == "SOR"
|
||||
|
||||
|
||||
def test_inactive_transition_resets_venue_to_krx() -> None:
|
||||
|
||||
Reference in New Issue
Block a user