feat: resolve and persist actual order venue

This commit is contained in:
jordan
2026-07-29 17:10:32 +09:00
parent 6398ff250c
commit a656350a35
18 changed files with 304 additions and 47 deletions
+88 -5
View File
@@ -26,13 +26,12 @@ from __future__ import annotations
from datetime import datetime, timedelta, timezone
from decimal import Decimal
import httpx
import pytest
from trex_core.adapters.broker import kis as kis_mod
from trex_core.adapters.broker.kis import KISBroker
from trex_core.adapters.broker.kis import KISBroker, resolve_order_venue
from trex_core.adapters.broker.paper import PaperBroker
from trex_core.application.broker_port import BrokerPort, OrderRequest
from trex_core.application.broker_port import OrderRequest
_MOCK_URL = "https://openapivts.koreainvestment.com:29443"
_LIVE_URL = "https://openapi.koreainvestment.com:9443"
@@ -42,8 +41,14 @@ _LIVE_URL = "https://openapi.koreainvestment.com:9443"
# Helpers
# ---------------------------------------------------------------------------
def _mock_broker() -> KISBroker:
b = KISBroker("12345678-01", app_key="k", app_secret="s", base_url=_MOCK_URL)
def _mock_broker(*, clock=None) -> KISBroker:
b = KISBroker(
"12345678-01",
app_key="k",
app_secret="s",
base_url=_MOCK_URL,
clock=clock,
)
b._token = "tok"
b._token_exp = datetime.now(tz=timezone.utc) + timedelta(hours=1)
return b
@@ -85,6 +90,84 @@ def test_order_request_venue_nxt():
assert req.venue == "NXT"
@pytest.mark.parametrize(
("at", "expected"),
[
(datetime(2026, 7, 29, 8, 0, tzinfo=timezone(timedelta(hours=9))), "NXT"),
(datetime(2026, 7, 29, 8, 49, 59, tzinfo=timezone(timedelta(hours=9))), "NXT"),
(datetime(2026, 7, 29, 9, 0, 30, tzinfo=timezone(timedelta(hours=9))), "KRX"),
(datetime(2026, 7, 29, 15, 29, 59, tzinfo=timezone(timedelta(hours=9))), "KRX"),
(datetime(2026, 7, 29, 15, 30, tzinfo=timezone(timedelta(hours=9))), "NXT"),
(datetime(2026, 7, 29, 20, 0, tzinfo=timezone(timedelta(hours=9))), "NXT"),
],
)
def test_auto_venue_resolves_by_kst_session(at, expected):
assert resolve_order_venue("AUTO", at) == expected
@pytest.mark.parametrize(
"at",
[
datetime(2026, 7, 29, 7, 59, 59, tzinfo=timezone(timedelta(hours=9))),
datetime(2026, 7, 29, 8, 50, tzinfo=timezone(timedelta(hours=9))),
datetime(2026, 7, 29, 9, 0, 29, tzinfo=timezone(timedelta(hours=9))),
datetime(2026, 7, 29, 20, 0, 1, tzinfo=timezone(timedelta(hours=9))),
],
)
def test_auto_venue_fails_closed_outside_supported_sessions(at):
with pytest.raises(ValueError, match="auto_venue_session_closed"):
resolve_order_venue("AUTO", at)
def test_submit_auto_uses_nxt_and_reports_actual_venue(monkeypatch):
captured = {}
at = datetime(2026, 7, 29, 8, 1, tzinfo=timezone(timedelta(hours=9)))
def fake_post(url, headers=None, json=None, timeout=None):
captured["body"] = json
return _FakeResp(
{"rt_cd": "0", "output": {"ODNO": "0002", "KRX_FWDG_ORD_ORGNO": "06010"}}
)
broker = _mock_broker(clock=lambda: at)
monkeypatch.setattr(kis_mod.httpx, "post", fake_post)
ack = broker.submit_order(
OrderRequest(
slot_id="s-auto",
side="buy",
symbol="005930",
qty=1,
venue="AUTO",
)
)
assert ack.accepted is True
assert ack.venue == "NXT"
assert captured["body"]["EXCG_ID_DVSN_CD"] == "NXT"
def test_submit_auto_outside_session_rejects_without_http(monkeypatch):
at = datetime(2026, 7, 29, 8, 55, tzinfo=timezone(timedelta(hours=9)))
broker = _mock_broker(clock=lambda: at)
def unexpected_post(*args, **kwargs):
raise AssertionError("AUTO session rejection must happen before HTTP")
monkeypatch.setattr(kis_mod.httpx, "post", unexpected_post)
ack = broker.submit_order(
OrderRequest(
slot_id="s-auto-closed",
side="buy",
symbol="005930",
qty=1,
venue="AUTO",
)
)
assert ack.accepted is False
assert ack.reason == "auto_venue_session_closed"
# ---------------------------------------------------------------------------
# REQ-117 AC-4: 기존 위치 인수 순서 불변
# ---------------------------------------------------------------------------
@@ -20,7 +20,7 @@ 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),
executed_at=datetime(2026, 6, 4, tzinfo=timezone.utc), venue="NXT",
)
@@ -32,6 +32,7 @@ def test_enqueue_persists_and_is_idempotent(pg_clean):
pending = outbox.list_pending()
assert len(pending) == 1
assert pending[0].status is WorkItemStatus.PENDING
assert pending[0].trade.venue == "NXT"
def test_writer_completes_persisted_item(pg_clean):