feat(signal_v2-phase3a): pull_worker KIS minute cycle + WS callback
_run_kis_minute_cycle: portfolio + screener union 종목 분봉 fetch + screener-only 종목 호가 REST fetch. WebSocket callback factory (make_asking_price_callback). poll_loop / _run_polling_cycle 에 kis_client optional param 추가 (Phase 5 까지 None 일 때도 정상 동작). 2 new tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
55
signal_v2/tests/test_pull_worker.py
Normal file
55
signal_v2/tests/test_pull_worker.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""Tests for pull_worker (Phase 3a additions)."""
|
||||
from collections import deque
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from signal_v2.state import PollState
|
||||
|
||||
|
||||
async def test_minute_polling_cycle_updates_state_minute_bars():
|
||||
"""KIS REST mock 의 분봉 데이터가 state.minute_bars[ticker] deque 에 들어간다."""
|
||||
from signal_v2.pull_worker import _run_kis_minute_cycle
|
||||
|
||||
state = PollState()
|
||||
state.portfolio = {"holdings": [{"ticker": "005930"}, {"ticker": "000660"}]}
|
||||
state.screener_preview = {
|
||||
"items": [{"ticker": "005930"}, {"ticker": "035720"}]
|
||||
}
|
||||
|
||||
kis_client_mock = MagicMock()
|
||||
kis_client_mock.get_minute_ohlcv = AsyncMock(side_effect=[
|
||||
[{"datetime": "2026-05-18T09:00:00+09:00", "open": 78000,
|
||||
"high": 78500, "low": 77900, "close": 78300, "volume": 12345}],
|
||||
[{"datetime": "2026-05-18T09:00:00+09:00", "open": 180000,
|
||||
"high": 181000, "low": 179800, "close": 180500, "volume": 5000}],
|
||||
[{"datetime": "2026-05-18T09:00:00+09:00", "open": 51000,
|
||||
"high": 51200, "low": 50800, "close": 51100, "volume": 8000}],
|
||||
])
|
||||
kis_client_mock.get_asking_price = AsyncMock(return_value={
|
||||
"bid_total": 600, "ask_total": 400, "bid_ratio": 0.6,
|
||||
"current_price": 51100, "as_of": "2026-05-18T09:00:30+09:00",
|
||||
})
|
||||
|
||||
await _run_kis_minute_cycle(kis_client_mock, state)
|
||||
|
||||
# 3 unique tickers (005930, 000660, 035720)
|
||||
assert "005930" in state.minute_bars
|
||||
assert "000660" in state.minute_bars
|
||||
assert "035720" in state.minute_bars
|
||||
assert len(state.minute_bars["005930"]) >= 1
|
||||
# asking_price 만 screener-only ticker (035720) 에 들어가야 함
|
||||
# (portfolio = 005930, 000660 는 WebSocket 으로 들어옴)
|
||||
assert "035720" in state.asking_price
|
||||
|
||||
|
||||
def test_websocket_message_updates_state_asking_price():
|
||||
"""WebSocket callback factory → state.asking_price 갱신."""
|
||||
from signal_v2.pull_worker import make_asking_price_callback
|
||||
|
||||
state = PollState()
|
||||
cb = make_asking_price_callback(state)
|
||||
cb("005930", {"bid_total": 1000, "ask_total": 800, "bid_ratio": 0.555,
|
||||
"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
|
||||
Reference in New Issue
Block a user