feat(signal_v2-phase3b): post-close cycle + minute momentum update
scheduler._is_post_close_trigger: 16:00 KST ±1min detection (market day). pull_worker: - _run_post_close_cycle: daily fetch (60일) + chronos batch predict → state.chronos_predictions + state.daily_ohlcv. - update_minute_momentum_for_all: 매 cycle 마다 state.minute_momentum 갱신. - poll_loop signature 확장 (chronos optional). 45 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,3 +53,45 @@ def test_websocket_message_updates_state_asking_price():
|
||||
"current_price": 78500, "as_of": "2026-05-18T10:00:00+09:00"})
|
||||
assert state.asking_price["005930"]["bid_total"] == 1000
|
||||
assert "asking_price/005930" in state.last_updated
|
||||
|
||||
|
||||
async def test_post_close_cycle_updates_chronos_predictions():
|
||||
"""mock kis + mock chronos → state.chronos_predictions + state.daily_ohlcv 갱신."""
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from signal_v2.pull_worker import _run_post_close_cycle
|
||||
from signal_v2.chronos_predictor import ChronosPrediction
|
||||
from signal_v2.state import PollState
|
||||
|
||||
state = PollState()
|
||||
state.portfolio = {"holdings": [{"ticker": "005930"}]}
|
||||
state.screener_preview = {"items": [{"ticker": "000660"}]}
|
||||
|
||||
kis_mock = MagicMock()
|
||||
daily_005930 = [{"datetime": f"2026-05-{i+1:02d}", "open": 100, "high": 105,
|
||||
"low": 95, "close": 100 + i, "volume": 1000} for i in range(60)]
|
||||
daily_000660 = [{"datetime": f"2026-05-{i+1:02d}", "open": 200, "high": 210,
|
||||
"low": 190, "close": 200 + i, "volume": 2000} for i in range(60)]
|
||||
# _run_post_close_cycle iterates tickers and calls get_daily_ohlcv per ticker.
|
||||
# Order depends on set() so use side_effect mapping if possible, otherwise list.
|
||||
async def fake_daily(ticker, days=60):
|
||||
if ticker == "005930":
|
||||
return daily_005930
|
||||
if ticker == "000660":
|
||||
return daily_000660
|
||||
return []
|
||||
kis_mock.get_daily_ohlcv = AsyncMock(side_effect=fake_daily)
|
||||
|
||||
chronos_mock = MagicMock()
|
||||
chronos_mock.predict_batch = MagicMock(return_value={
|
||||
"005930": ChronosPrediction(0.02, -0.01, 0.04, 0.85, "2026-05-18T16:00:00+09:00"),
|
||||
"000660": ChronosPrediction(0.03, -0.02, 0.06, 0.75, "2026-05-18T16:00:00+09:00"),
|
||||
})
|
||||
|
||||
await _run_post_close_cycle(kis_mock, chronos_mock, state)
|
||||
|
||||
assert "005930" in state.chronos_predictions
|
||||
assert "000660" in state.chronos_predictions
|
||||
assert state.chronos_predictions["005930"]["median"] == 0.02
|
||||
assert state.chronos_predictions["005930"]["conf"] == 0.85
|
||||
assert "005930" in state.daily_ohlcv
|
||||
assert "chronos/005930" in state.last_updated
|
||||
|
||||
Reference in New Issue
Block a user