fix: converge broker failures without retry
This commit is contained in:
@@ -91,38 +91,28 @@ 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("venue", ["KRX", "NXT", "SOR"])
|
||||
@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, 8, 0, tzinfo=timezone(timedelta(hours=9))),
|
||||
datetime(2026, 7, 29, 8, 55, 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"):
|
||||
def test_explicit_venue_is_preserved_independent_of_time(venue, at):
|
||||
assert resolve_order_venue(venue, at) == venue
|
||||
|
||||
|
||||
def test_auto_venue_is_rejected():
|
||||
at = datetime(2026, 7, 29, 8, 1, tzinfo=timezone(timedelta(hours=9)))
|
||||
with pytest.raises(ValueError, match="unsupported_order_venue"):
|
||||
resolve_order_venue("AUTO", at)
|
||||
|
||||
|
||||
def test_submit_auto_uses_nxt_and_reports_actual_venue(monkeypatch):
|
||||
def test_submit_sor_preserves_sor(monkeypatch):
|
||||
captured = {}
|
||||
at = datetime(2026, 7, 29, 8, 1, tzinfo=timezone(timedelta(hours=9)))
|
||||
at = datetime(2026, 7, 29, 8, 55, tzinfo=timezone(timedelta(hours=9)))
|
||||
|
||||
def fake_post(url, headers=None, json=None, timeout=None):
|
||||
captured["body"] = json
|
||||
@@ -134,39 +124,17 @@ def test_submit_auto_uses_nxt_and_reports_actual_venue(monkeypatch):
|
||||
monkeypatch.setattr(kis_mod.httpx, "post", fake_post)
|
||||
ack = broker.submit_order(
|
||||
OrderRequest(
|
||||
slot_id="s-auto",
|
||||
slot_id="s-sor",
|
||||
side="buy",
|
||||
symbol="005930",
|
||||
qty=1,
|
||||
venue="AUTO",
|
||||
venue="SOR",
|
||||
)
|
||||
)
|
||||
|
||||
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"
|
||||
assert ack.venue == "SOR"
|
||||
assert captured["body"]["EXCG_ID_DVSN_CD"] == "SOR"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -7,10 +7,11 @@ iteration: EXPECTED_SLOT_FIELDS (code contract) vs daily_slot actual columns
|
||||
from __future__ import annotations
|
||||
|
||||
import psycopg
|
||||
import pytest
|
||||
|
||||
from trex_core.adapters.persistence.migrate import apply_migrations, migration_files
|
||||
from trex_core.adapters.persistence.postgresql import PostgreSQLSlotRepository
|
||||
from trex_core.domain.slot import EXPECTED_SLOT_FIELDS, SlotService, SlotState
|
||||
from trex_core.domain.slot import EXPECTED_SLOT_FIELDS
|
||||
|
||||
_EXPECTED_TABLES = {
|
||||
"daily_slot",
|
||||
@@ -64,20 +65,29 @@ def test_apply_migrations_is_idempotent(pg_dsn):
|
||||
apply_migrations(pg_dsn)
|
||||
|
||||
|
||||
def test_migration_replay_accepts_existing_auto_venue_row(pg_clean):
|
||||
def test_migration_replay_converts_existing_auto_venue_row_to_sor(pg_clean):
|
||||
repo = PostgreSQLSlotRepository(pg_clean)
|
||||
service = SlotService(repo, instance_key="migration-auto")
|
||||
repo.reconcile_universe("migration-auto", {"pf-auto": "005930"})
|
||||
slot = repo.find_slot_by_portfolio("migration-auto", "pf-auto")
|
||||
service.transit_slot(
|
||||
slot.slot_id,
|
||||
SlotState.INACTIVE,
|
||||
SlotState.PENDING_BUY,
|
||||
reason="migration_replay",
|
||||
actor="test",
|
||||
venue="AUTO",
|
||||
)
|
||||
assert slot is not None
|
||||
|
||||
# Simulate a row written before migration 023 tightened the constraint.
|
||||
with psycopg.connect(pg_clean) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("ALTER TABLE daily_slot DROP CONSTRAINT daily_slot_venue_check")
|
||||
cur.execute(
|
||||
"UPDATE daily_slot SET venue = 'AUTO' WHERE slot_id = %s",
|
||||
(slot.slot_id,),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
apply_migrations(pg_clean)
|
||||
|
||||
assert repo.fetch_slot(slot.slot_id).venue == "AUTO"
|
||||
assert repo.fetch_slot(slot.slot_id).venue == "SOR"
|
||||
with psycopg.connect(pg_clean) as conn:
|
||||
with conn.cursor() as cur:
|
||||
with pytest.raises(psycopg.errors.CheckViolation):
|
||||
cur.execute(
|
||||
"UPDATE daily_slot SET venue = 'AUTO' WHERE slot_id = %s",
|
||||
(slot.slot_id,),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user