diff --git a/src/main.py b/src/main.py index 9ccdd28..965261a 100644 --- a/src/main.py +++ b/src/main.py @@ -22,6 +22,7 @@ from src.broker.overseas import OverseasBroker from src.config import Settings from src.context.aggregator import ContextAggregator from src.context.layer import ContextLayer +from src.context.scheduler import ContextScheduler from src.context.store import ContextStore from src.core.criticality import CriticalityAssessor from src.core.priority_queue import PriorityTaskQueue @@ -772,6 +773,35 @@ async def _handle_market_close( ) +def _run_context_scheduler( + scheduler: ContextScheduler, now: datetime | None = None, +) -> None: + """Run periodic context scheduler tasks and log when anything executes.""" + result = scheduler.run_if_due(now=now) + if any( + [ + result.weekly, + result.monthly, + result.quarterly, + result.annual, + result.legacy, + result.cleanup, + ] + ): + logger.info( + ( + "Context scheduler ran (weekly=%s, monthly=%s, quarterly=%s, " + "annual=%s, legacy=%s, cleanup=%s)" + ), + result.weekly, + result.monthly, + result.quarterly, + result.annual, + result.legacy, + result.cleanup, + ) + + async def run(settings: Settings) -> None: """Main async loop — iterate over open markets on a timer.""" broker = KISBroker(settings) @@ -782,6 +812,10 @@ async def run(settings: Settings) -> None: decision_logger = DecisionLogger(db_conn) context_store = ContextStore(db_conn) context_aggregator = ContextAggregator(db_conn) + context_scheduler = ContextScheduler( + aggregator=context_aggregator, + store=context_store, + ) # V2 proactive strategy components context_selector = ContextSelector(context_store) @@ -1015,6 +1049,7 @@ async def run(settings: Settings) -> None: while not shutdown.is_set(): # Wait for trading to be unpaused await pause_trading.wait() + _run_context_scheduler(context_scheduler, now=datetime.now(UTC)) try: await run_daily_session( @@ -1053,6 +1088,7 @@ async def run(settings: Settings) -> None: while not shutdown.is_set(): # Wait for trading to be unpaused await pause_trading.wait() + _run_context_scheduler(context_scheduler, now=datetime.now(UTC)) # Get currently open markets open_markets = get_open_markets(settings.enabled_market_list) diff --git a/tests/test_main.py b/tests/test_main.py index 2b2a363..93318ee 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,16 +1,17 @@ """Tests for main trading loop integration.""" -from datetime import UTC, date +from datetime import UTC, date, datetime from unittest.mock import ANY, AsyncMock, MagicMock, patch import pytest from src.context.layer import ContextLayer +from src.context.scheduler import ScheduleResult from src.core.risk_manager import CircuitBreakerTripped, FatFingerRejected from src.db import init_db, log_trade from src.evolution.scorecard import DailyScorecard from src.logging.decision_logger import DecisionLogger -from src.main import _handle_market_close, safe_float, trading_cycle +from src.main import _handle_market_close, _run_context_scheduler, safe_float, trading_cycle from src.strategy.models import ( DayPlaybook, ScenarioAction, @@ -1295,3 +1296,13 @@ async def test_handle_market_close_without_lessons_stores_once() -> None: ) assert reviewer.store_scorecard_in_context.call_count == 1 + + +def test_run_context_scheduler_invokes_scheduler() -> None: + """Scheduler helper should call run_if_due with provided datetime.""" + scheduler = MagicMock() + scheduler.run_if_due = MagicMock(return_value=ScheduleResult(cleanup=True)) + + _run_context_scheduler(scheduler, now=datetime(2026, 2, 14, tzinfo=UTC)) + + scheduler.run_if_due.assert_called_once()