feat(ai_trade): poll_loop가 매 cycle 끝에 expired signal purge (F5 part 4)
Phase 5 consumer(agent-office /signal)가 안 붙은 상태에서도 state.signals가 무한 누적되지 않도록 매 cycle 끝에 state.purge_expired_signals(now) 호출. expires_at < now인 signal 자동 제거. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -178,3 +178,64 @@ async def test_post_close_fires_at_1601_when_not_yet_today(monkeypatch):
|
||||
)
|
||||
|
||||
assert post_close.await_count >= 1, "post-close가 16:01에 호출되지 않음 (F3 회귀)"
|
||||
|
||||
|
||||
async def test_poll_loop_purges_expired_signals(monkeypatch):
|
||||
"""F5 — 매 cycle 끝에 expired signal이 제거됨."""
|
||||
from datetime import datetime as _dt
|
||||
from zoneinfo import ZoneInfo as _ZI
|
||||
import asyncio as _asyncio
|
||||
|
||||
from ai_trade import pull_worker
|
||||
from ai_trade.state import PollState
|
||||
|
||||
_kst = _ZI("Asia/Seoul")
|
||||
now = _dt(2026, 5, 18, 10, 0, tzinfo=_kst)
|
||||
|
||||
class FrozenDT:
|
||||
@staticmethod
|
||||
def now(tz=None):
|
||||
return now
|
||||
|
||||
state = PollState()
|
||||
state.signals = {
|
||||
"OLD": {
|
||||
"ticker": "OLD",
|
||||
"expires_at": _dt(2026, 5, 18, 9, 0, tzinfo=_kst).isoformat(),
|
||||
"cycle_id": 1,
|
||||
},
|
||||
"FRESH": {
|
||||
"ticker": "FRESH",
|
||||
"expires_at": _dt(2026, 5, 18, 10, 30, tzinfo=_kst).isoformat(),
|
||||
"cycle_id": 1,
|
||||
},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(pull_worker, "datetime", FrozenDT)
|
||||
monkeypatch.setattr(pull_worker, "_is_market_day", lambda n: True)
|
||||
monkeypatch.setattr(pull_worker, "_is_polling_window", lambda n: True)
|
||||
monkeypatch.setattr(pull_worker, "_next_interval", lambda n: 0.01)
|
||||
monkeypatch.setattr(pull_worker, "_run_polling_cycle", AsyncMock())
|
||||
monkeypatch.setattr(pull_worker, "update_minute_momentum_for_all", lambda s: None)
|
||||
monkeypatch.setattr(pull_worker, "_is_post_close_trigger", lambda *a, **k: False)
|
||||
|
||||
shutdown = _asyncio.Event()
|
||||
|
||||
async def stop_soon():
|
||||
await _asyncio.sleep(0.05)
|
||||
shutdown.set()
|
||||
|
||||
_asyncio.create_task(stop_soon())
|
||||
|
||||
await pull_worker.poll_loop(
|
||||
client=MagicMock(),
|
||||
state=state,
|
||||
shutdown=shutdown,
|
||||
kis_client=MagicMock(),
|
||||
chronos=MagicMock(),
|
||||
dedup=None,
|
||||
settings=None,
|
||||
)
|
||||
|
||||
assert "OLD" not in state.signals
|
||||
assert "FRESH" in state.signals
|
||||
|
||||
Reference in New Issue
Block a user