From 8e02b1ea4f72c8ad246db1f2f792059418cd0b2e Mon Sep 17 00:00:00 2001 From: agentson Date: Mon, 2 Mar 2026 03:09:33 +0900 Subject: [PATCH 1/2] blackout: persist session_id across queued intent lifecycle (#375) --- src/core/blackout_manager.py | 1 + src/main.py | 11 +++++++++-- tests/test_blackout_manager.py | 5 +++++ tests/test_main.py | 10 +++++++++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/core/blackout_manager.py b/src/core/blackout_manager.py index c4f46ed..bd26a20 100644 --- a/src/core/blackout_manager.py +++ b/src/core/blackout_manager.py @@ -23,6 +23,7 @@ class BlackoutWindow: class QueuedOrderIntent: market_code: str exchange_code: str + session_id: str stock_code: str order_type: str quantity: int diff --git a/src/main.py b/src/main.py index 2f119a4..6bf885b 100644 --- a/src/main.py +++ b/src/main.py @@ -1004,6 +1004,7 @@ async def build_overseas_symbol_universe( def _build_queued_order_intent( *, market: MarketInfo, + session_id: str, stock_code: str, order_type: str, quantity: int, @@ -1013,6 +1014,7 @@ def _build_queued_order_intent( return QueuedOrderIntent( market_code=market.code, exchange_code=market.exchange_code, + session_id=session_id, stock_code=stock_code, order_type=order_type, quantity=quantity, @@ -1025,6 +1027,7 @@ def _build_queued_order_intent( def _maybe_queue_order_intent( *, market: MarketInfo, + session_id: str, stock_code: str, order_type: str, quantity: int, @@ -1038,6 +1041,7 @@ def _maybe_queue_order_intent( queued = BLACKOUT_ORDER_MANAGER.enqueue( _build_queued_order_intent( market=market, + session_id=session_id, stock_code=stock_code, order_type=order_type, quantity=quantity, @@ -1208,7 +1212,6 @@ async def process_blackout_recovery_orders( accepted = result.get("rt_cd", "0") == "0" if accepted: - runtime_session_id = get_session_info(market).session_id log_trade( conn=db_conn, stock_code=intent.stock_code, @@ -1220,7 +1223,7 @@ async def process_blackout_recovery_orders( pnl=0.0, market=market.code, exchange_code=market.exchange_code, - session_id=runtime_session_id, + session_id=getattr(intent, "session_id", get_session_info(market).session_id), ) logger.info( "Recovered queued order executed: %s %s (%s) qty=%d price=%.4f source=%s", @@ -2057,6 +2060,7 @@ async def trading_cycle( return if _maybe_queue_order_intent( market=market, + session_id=runtime_session_id, stock_code=stock_code, order_type=decision.action, quantity=quantity, @@ -2104,6 +2108,7 @@ async def trading_cycle( return if _maybe_queue_order_intent( market=market, + session_id=runtime_session_id, stock_code=stock_code, order_type=decision.action, quantity=quantity, @@ -3264,6 +3269,7 @@ async def run_daily_session( continue if _maybe_queue_order_intent( market=market, + session_id=runtime_session_id, stock_code=stock_code, order_type=decision.action, quantity=quantity, @@ -3301,6 +3307,7 @@ async def run_daily_session( continue if _maybe_queue_order_intent( market=market, + session_id=runtime_session_id, stock_code=stock_code, order_type=decision.action, quantity=quantity, diff --git a/tests/test_blackout_manager.py b/tests/test_blackout_manager.py index b63c478..71651ff 100644 --- a/tests/test_blackout_manager.py +++ b/tests/test_blackout_manager.py @@ -35,6 +35,7 @@ def test_recovery_batch_only_after_blackout_exit() -> None: intent = QueuedOrderIntent( market_code="KR", exchange_code="KRX", + session_id="KRX_REG", stock_code="005930", order_type="BUY", quantity=1, @@ -64,6 +65,7 @@ def test_requeued_intent_is_processed_next_non_blackout_cycle() -> None: intent = QueuedOrderIntent( market_code="KR", exchange_code="KRX", + session_id="KRX_REG", stock_code="005930", order_type="BUY", quantity=1, @@ -90,6 +92,7 @@ def test_queue_overflow_drops_oldest_and_keeps_latest() -> None: first = QueuedOrderIntent( market_code="KR", exchange_code="KRX", + session_id="KRX_REG", stock_code="000001", order_type="BUY", quantity=1, @@ -100,6 +103,7 @@ def test_queue_overflow_drops_oldest_and_keeps_latest() -> None: second = QueuedOrderIntent( market_code="KR", exchange_code="KRX", + session_id="KRX_REG", stock_code="000002", order_type="BUY", quantity=1, @@ -110,6 +114,7 @@ def test_queue_overflow_drops_oldest_and_keeps_latest() -> None: third = QueuedOrderIntent( market_code="KR", exchange_code="KRX", + session_id="KRX_REG", stock_code="000003", order_type="SELL", quantity=2, diff --git a/tests/test_main.py b/tests/test_main.py index 5c83d9e..9072d20 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -6543,6 +6543,7 @@ def test_blackout_queue_overflow_keeps_latest_intent() -> None: with patch("src.main.BLACKOUT_ORDER_MANAGER", manager): assert _maybe_queue_order_intent( market=market, + session_id="KRX_REG", stock_code="005930", order_type="BUY", quantity=1, @@ -6551,6 +6552,7 @@ def test_blackout_queue_overflow_keeps_latest_intent() -> None: ) assert _maybe_queue_order_intent( market=market, + session_id="KRX_REG", stock_code="000660", order_type="BUY", quantity=2, @@ -6564,6 +6566,7 @@ def test_blackout_queue_overflow_keeps_latest_intent() -> None: batch = manager.pop_recovery_batch() assert len(batch) == 1 assert batch[0].stock_code == "000660" + assert batch[0].session_id == "KRX_REG" @pytest.mark.asyncio @@ -6587,6 +6590,7 @@ async def test_process_blackout_recovery_executes_valid_intents() -> None: intent.quantity = 1 intent.price = 100.0 intent.source = "test" + intent.session_id = "NXT_AFTER" intent.attempts = 0 blackout_manager = MagicMock() @@ -6617,7 +6621,7 @@ async def test_process_blackout_recovery_executes_valid_intents() -> None: assert row is not None assert row[0] == "BUY" assert row[1] == 1 - assert row[2] == "KRX_REG" + assert row[2] == "NXT_AFTER" assert row[3].startswith("[blackout-recovery]") @@ -6642,6 +6646,7 @@ async def test_process_blackout_recovery_drops_policy_rejected_intent() -> None: intent.quantity = 1 intent.price = 100.0 intent.source = "test" + intent.session_id = "KRX_REG" intent.attempts = 0 blackout_manager = MagicMock() @@ -6691,6 +6696,7 @@ async def test_process_blackout_recovery_drops_intent_on_excessive_price_drift() intent.quantity = 1 intent.price = 100.0 intent.source = "test" + intent.session_id = "US_PRE" intent.attempts = 0 blackout_manager = MagicMock() @@ -6741,6 +6747,7 @@ async def test_process_blackout_recovery_drops_overseas_intent_on_excessive_pric intent.quantity = 1 intent.price = 100.0 intent.source = "test" + intent.session_id = "KRX_REG" intent.attempts = 0 blackout_manager = MagicMock() @@ -6790,6 +6797,7 @@ async def test_process_blackout_recovery_requeues_intent_when_price_lookup_fails intent.quantity = 1 intent.price = 100.0 intent.source = "test" + intent.session_id = "KRX_REG" intent.attempts = 0 blackout_manager = MagicMock() -- 2.49.1 From 4f21117eca00e58601e7d2251204c37019a7957c Mon Sep 17 00:00:00 2001 From: agentson Date: Mon, 2 Mar 2026 03:16:55 +0900 Subject: [PATCH 2/2] blackout: simplify recovery session_id binding to queued value --- src/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index 6bf885b..8e34654 100644 --- a/src/main.py +++ b/src/main.py @@ -1223,7 +1223,7 @@ async def process_blackout_recovery_orders( pnl=0.0, market=market.code, exchange_code=market.exchange_code, - session_id=getattr(intent, "session_id", get_session_info(market).session_id), + session_id=intent.session_id, ) logger.info( "Recovered queued order executed: %s %s (%s) qty=%d price=%.4f source=%s", -- 2.49.1