From 86c94cff6280be039fd9eba7e0606b848adc8317 Mon Sep 17 00:00:00 2001 From: agentson Date: Sat, 14 Feb 2026 23:20:24 +0900 Subject: [PATCH] feat: cross-market date fix and strategic context selector (issue #88) KR planner now reads US scorecard from previous day (timezone-aware), and generate_playbook uses STRATEGIC context selection. Co-Authored-By: Claude Opus 4.6 --- src/strategy/pre_market_planner.py | 5 ++-- tests/test_pre_market_planner.py | 48 +++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/strategy/pre_market_planner.py b/src/strategy/pre_market_planner.py index cb03589..4420dae 100644 --- a/src/strategy/pre_market_planner.py +++ b/src/strategy/pre_market_planner.py @@ -8,7 +8,7 @@ from __future__ import annotations import json import logging -from datetime import date +from datetime import date, timedelta from typing import Any from src.analysis.smart_scanner import ScanCandidate @@ -145,7 +145,8 @@ class PreMarketPlanner: other_market = "US" if target_market == "KR" else "KR" if today is None: today = date.today() - timeframe = today.isoformat() + timeframe_date = today - timedelta(days=1) if target_market == "KR" else today + timeframe = timeframe_date.isoformat() scorecard_key = f"scorecard_{other_market}" scorecard_data = self._context_store.get_context( diff --git a/tests/test_pre_market_planner.py b/tests/test_pre_market_planner.py index aa3a662..6e6db55 100644 --- a/tests/test_pre_market_planner.py +++ b/tests/test_pre_market_planner.py @@ -9,6 +9,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest from src.analysis.smart_scanner import ScanCandidate +from src.brain.context_selector import DecisionType from src.brain.gemini_client import TradeDecision from src.config import Settings from src.context.store import ContextLayer @@ -16,12 +17,10 @@ from src.strategy.models import ( CrossMarketContext, DayPlaybook, MarketOutlook, - PlaybookStatus, ScenarioAction, ) from src.strategy.pre_market_planner import PreMarketPlanner - # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @@ -111,7 +110,9 @@ def _make_planner( # Mock ContextSelector selector = MagicMock() - selector.select_layers = MagicMock(return_value=[ContextLayer.L7_REALTIME, ContextLayer.L6_DAILY]) + selector.select_layers = MagicMock( + return_value=[ContextLayer.L7_REALTIME, ContextLayer.L6_DAILY] + ) selector.get_context_data = MagicMock(return_value=context_data or {}) settings = Settings( @@ -220,11 +221,25 @@ class TestGeneratePlaybook: stocks = [ { "stock_code": "005930", - "scenarios": [{"condition": {"rsi_below": 30}, "action": "BUY", "confidence": 85, "rationale": "ok"}], + "scenarios": [ + { + "condition": {"rsi_below": 30}, + "action": "BUY", + "confidence": 85, + "rationale": "ok", + } + ], }, { "stock_code": "UNKNOWN", - "scenarios": [{"condition": {"rsi_below": 20}, "action": "BUY", "confidence": 90, "rationale": "bad"}], + "scenarios": [ + { + "condition": {"rsi_below": 20}, + "action": "BUY", + "confidence": 90, + "rationale": "bad", + } + ], }, ] planner = _make_planner(gemini_response=_gemini_response_json(stocks=stocks)) @@ -254,6 +269,19 @@ class TestGeneratePlaybook: assert pb.token_count == 450 + @pytest.mark.asyncio + async def test_generate_playbook_uses_strategic_context_selector(self) -> None: + planner = _make_planner() + candidates = [_candidate()] + + await planner.generate_playbook("KR", candidates, today=date(2026, 2, 8)) + + planner._context_selector.select_layers.assert_called_once_with( + decision_type=DecisionType.STRATEGIC, + include_realtime=True, + ) + planner._context_selector.get_context_data.assert_called_once() + # --------------------------------------------------------------------------- # _parse_response @@ -402,7 +430,12 @@ class TestParseResponse: class TestBuildCrossMarketContext: def test_kr_reads_us_scorecard(self) -> None: - scorecard = {"total_pnl": 2.5, "win_rate": 65, "index_change_pct": 0.8, "lessons": ["Stay patient"]} + scorecard = { + "total_pnl": 2.5, + "win_rate": 65, + "index_change_pct": 0.8, + "lessons": ["Stay patient"], + } planner = _make_planner(scorecard_data=scorecard) ctx = planner.build_cross_market_context("KR", today=date(2026, 2, 8)) @@ -415,8 +448,9 @@ class TestBuildCrossMarketContext: # Verify it queried scorecard_US planner._context_store.get_context.assert_called_once_with( - ContextLayer.L6_DAILY, "2026-02-08", "scorecard_US" + ContextLayer.L6_DAILY, "2026-02-07", "scorecard_US" ) + assert ctx.date == "2026-02-07" def test_us_reads_kr_scorecard(self) -> None: scorecard = {"total_pnl": -1.0, "win_rate": 40, "index_change_pct": -0.5}