feat(signal_v2-phase4-task3): integrate signal_generator into poll_loop
poll_loop now accepts dedup + settings kwargs (backwards-compatible defaults). After each in-window cycle (stock pull + minute momentum + optional post-close), generate_signals is called to populate state.signals for downstream Phase 5 pickup. main.py lifespan wires _ctx.dedup + settings into the poll_loop task. 1 integration test added (anomaly-free stop_loss path via direct generate_signals call, exercises the same code path that poll_loop runs). 56 tests pass.
This commit is contained in:
@@ -82,6 +82,8 @@ async def lifespan(app: FastAPI):
|
|||||||
_ctx.client, state_mod.state, _ctx.shutdown,
|
_ctx.client, state_mod.state, _ctx.shutdown,
|
||||||
kis_client=_ctx.kis_client,
|
kis_client=_ctx.kis_client,
|
||||||
chronos=_ctx.chronos,
|
chronos=_ctx.chronos,
|
||||||
|
dedup=_ctx.dedup,
|
||||||
|
settings=settings,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ async def poll_loop(
|
|||||||
client: StockClient, state: PollState, shutdown: asyncio.Event,
|
client: StockClient, state: PollState, shutdown: asyncio.Event,
|
||||||
kis_client: KISClient | None = None,
|
kis_client: KISClient | None = None,
|
||||||
chronos=None,
|
chronos=None,
|
||||||
|
dedup=None,
|
||||||
|
settings=None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""FastAPI lifespan 에서 asyncio.create_task 로 시작."""
|
"""FastAPI lifespan 에서 asyncio.create_task 로 시작."""
|
||||||
logger.info("poll_loop started")
|
logger.info("poll_loop started")
|
||||||
@@ -40,6 +42,13 @@ async def poll_loop(
|
|||||||
await _run_post_close_cycle(kis_client, chronos, state)
|
await _run_post_close_cycle(kis_client, chronos, state)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("post-close cycle failed")
|
logger.exception("post-close cycle failed")
|
||||||
|
# Phase 4: generate signals
|
||||||
|
if dedup is not None and settings is not None:
|
||||||
|
try:
|
||||||
|
from signal_v2.signal_generator import generate_signals
|
||||||
|
generate_signals(state, dedup, settings)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("generate_signals failed")
|
||||||
interval = _next_interval(now)
|
interval = _next_interval(now)
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(shutdown.wait(), timeout=interval)
|
await asyncio.wait_for(shutdown.wait(), timeout=interval)
|
||||||
|
|||||||
@@ -95,3 +95,37 @@ async def test_post_close_cycle_updates_chronos_predictions():
|
|||||||
assert state.chronos_predictions["005930"]["conf"] == 0.85
|
assert state.chronos_predictions["005930"]["conf"] == 0.85
|
||||||
assert "005930" in state.daily_ohlcv
|
assert "005930" in state.daily_ohlcv
|
||||||
assert "chronos/005930" in state.last_updated
|
assert "chronos/005930" in state.last_updated
|
||||||
|
|
||||||
|
|
||||||
|
def test_poll_loop_calls_generate_signals_after_cycle(monkeypatch):
|
||||||
|
"""Phase 4: generate_signals 가 cycle 후 state.signals 를 갱신한다."""
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
from signal_v2.state import PollState
|
||||||
|
from signal_v2.signal_generator import generate_signals
|
||||||
|
|
||||||
|
state = PollState()
|
||||||
|
state.portfolio = {"holdings": [{
|
||||||
|
"ticker": "005930", "name": "삼성전자",
|
||||||
|
"avg_price": 75000, "current_price": 69000,
|
||||||
|
"pnl_pct": -0.08, "profit_rate": -8.0,
|
||||||
|
"quantity": 100, "broker": "키움",
|
||||||
|
}]}
|
||||||
|
state.screener_preview = {"items": []}
|
||||||
|
|
||||||
|
dedup = MagicMock()
|
||||||
|
dedup.is_recent.return_value = False
|
||||||
|
|
||||||
|
settings = MagicMock()
|
||||||
|
settings.stop_loss_pct = -0.07
|
||||||
|
settings.take_profit_pct = 0.15
|
||||||
|
settings.chronos_spread_threshold = 0.6
|
||||||
|
settings.asking_bid_ratio_threshold = 0.6
|
||||||
|
settings.confidence_threshold = 0.7
|
||||||
|
settings.min_momentum_for_buy = "strong_up"
|
||||||
|
|
||||||
|
generate_signals(state, dedup, settings)
|
||||||
|
|
||||||
|
assert "005930" in state.signals
|
||||||
|
assert state.signals["005930"]["action"] == "sell"
|
||||||
|
assert state.signals["005930"]["confidence_webai"] == 1.0
|
||||||
|
dedup.record.assert_called_with("005930", "sell", confidence=1.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user