From be695a5d7cad524e22aa798323b69d5e433693ee Mon Sep 17 00:00:00 2001 From: agentson Date: Sun, 8 Feb 2026 21:57:39 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20address=20PR=20review=20=E2=80=94=20inje?= =?UTF-8?q?ct=20today=20param,=20remove=20unused=20imports,=20fix=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review findings addressed: - Finding 1 (ImportError): false positive — ContextLayer is re-exported from src.context.store, import works correctly at runtime - Finding 2 (timezone): generate_playbook() and build_cross_market_context() now accept optional today parameter for market-local date injection - Finding 3 (lint): removed unused imports (UTC, datetime, PlaybookStatus), fixed line-too-long in prompt template - Tests simplified: replaced date patching with direct today= parameter Co-Authored-By: Claude Opus 4.6 --- src/strategy/pre_market_planner.py | 27 +++++++++---- tests/test_pre_market_planner.py | 62 +++++++----------------------- 2 files changed, 32 insertions(+), 57 deletions(-) diff --git a/src/strategy/pre_market_planner.py b/src/strategy/pre_market_planner.py index 11a1eb8..cb03589 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 UTC, date, datetime +from datetime import date from typing import Any from src.analysis.smart_scanner import ScanCandidate @@ -21,7 +21,6 @@ from src.strategy.models import ( DayPlaybook, GlobalRule, MarketOutlook, - PlaybookStatus, ScenarioAction, StockCondition, StockPlaybook, @@ -74,17 +73,20 @@ class PreMarketPlanner: self, market: str, candidates: list[ScanCandidate], + today: date | None = None, ) -> DayPlaybook: """Generate a DayPlaybook for a market using Gemini. Args: market: Market code ("KR" or "US") candidates: Stock candidates from SmartVolatilityScanner + today: Override date (defaults to date.today()). Use market-local date. Returns: DayPlaybook with scenarios. Empty/defensive if no candidates or failure. """ - today = date.today() + if today is None: + today = date.today() if not candidates: logger.info("No candidates for %s — returning empty playbook", market) @@ -93,7 +95,7 @@ class PreMarketPlanner: try: # 1. Gather context context_data = self._gather_context() - cross_market = self.build_cross_market_context(market) + cross_market = self.build_cross_market_context(market, today) # 2. Build prompt prompt = self._build_prompt(market, candidates, context_data, cross_market) @@ -128,14 +130,21 @@ class PreMarketPlanner: return self._defensive_playbook(today, market, candidates) return self._empty_playbook(today, market) - def build_cross_market_context(self, target_market: str) -> CrossMarketContext | None: + def build_cross_market_context( + self, target_market: str, today: date | None = None, + ) -> CrossMarketContext | None: """Build cross-market context from the other market's L6 data. KR planner → reads US scorecard from previous night. US planner → reads KR scorecard from today. + + Args: + target_market: The market being planned ("KR" or "US") + today: Override date (defaults to date.today()). Use market-local date. """ other_market = "US" if target_market == "KR" else "KR" - today = date.today() + if today is None: + today = date.today() timeframe = today.isoformat() scorecard_key = f"scorecard_{other_market}" @@ -220,9 +229,11 @@ class PreMarketPlanner: f"## Instructions\n" f"Return a JSON object with this exact structure:\n" f'{{\n' - f' "market_outlook": "bullish|neutral_to_bullish|neutral|neutral_to_bearish|bearish",\n' + f' "market_outlook": "bullish|neutral_to_bullish|neutral' + f'|neutral_to_bearish|bearish",\n' f' "global_rules": [\n' - f' {{"condition": "portfolio_pnl_pct < -2.0", "action": "REDUCE_ALL", "rationale": "..."}}\n' + f' {{"condition": "portfolio_pnl_pct < -2.0",' + f' "action": "REDUCE_ALL", "rationale": "..."}}\n' f' ],\n' f' "stocks": [\n' f' {{\n' diff --git a/tests/test_pre_market_planner.py b/tests/test_pre_market_planner.py index 449040a..aa3a662 100644 --- a/tests/test_pre_market_planner.py +++ b/tests/test_pre_market_planner.py @@ -4,7 +4,7 @@ from __future__ import annotations import json from datetime import date -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import AsyncMock, MagicMock import pytest @@ -135,10 +135,7 @@ class TestGeneratePlaybook: planner = _make_planner() candidates = [_candidate()] - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - pb = await planner.generate_playbook("KR", candidates) + pb = await planner.generate_playbook("KR", candidates, today=date(2026, 2, 8)) assert isinstance(pb, DayPlaybook) assert pb.market == "KR" @@ -151,10 +148,7 @@ class TestGeneratePlaybook: async def test_empty_candidates_returns_empty_playbook(self) -> None: planner = _make_planner() - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - pb = await planner.generate_playbook("KR", []) + pb = await planner.generate_playbook("KR", [], today=date(2026, 2, 8)) assert pb.stock_count == 0 assert pb.scenario_count == 0 @@ -166,10 +160,7 @@ class TestGeneratePlaybook: planner._gemini.decide = AsyncMock(side_effect=RuntimeError("API timeout")) candidates = [_candidate()] - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - pb = await planner.generate_playbook("KR", candidates) + pb = await planner.generate_playbook("KR", candidates, today=date(2026, 2, 8)) assert pb.default_action == ScenarioAction.HOLD assert pb.market_outlook == MarketOutlook.NEUTRAL_TO_BEARISH @@ -184,10 +175,7 @@ class TestGeneratePlaybook: planner._gemini.decide = AsyncMock(side_effect=RuntimeError("fail")) candidates = [_candidate()] - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - pb = await planner.generate_playbook("KR", candidates) + pb = await planner.generate_playbook("KR", candidates, today=date(2026, 2, 8)) assert pb.stock_count == 0 @@ -220,10 +208,7 @@ class TestGeneratePlaybook: planner = _make_planner(gemini_response=_gemini_response_json(stocks=stocks)) candidates = [_candidate(), _candidate(code="AAPL", name="Apple")] - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - pb = await planner.generate_playbook("US", candidates) + pb = await planner.generate_playbook("US", candidates, today=date(2026, 2, 8)) assert pb.stock_count == 2 codes = [sp.stock_code for sp in pb.stock_playbooks] @@ -245,10 +230,7 @@ class TestGeneratePlaybook: planner = _make_planner(gemini_response=_gemini_response_json(stocks=stocks)) candidates = [_candidate()] # Only 005930 - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - pb = await planner.generate_playbook("KR", candidates) + pb = await planner.generate_playbook("KR", candidates, today=date(2026, 2, 8)) assert pb.stock_count == 1 assert pb.stock_playbooks[0].stock_code == "005930" @@ -258,10 +240,7 @@ class TestGeneratePlaybook: planner = _make_planner() candidates = [_candidate()] - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - pb = await planner.generate_playbook("KR", candidates) + pb = await planner.generate_playbook("KR", candidates, today=date(2026, 2, 8)) assert len(pb.global_rules) == 1 assert pb.global_rules[0].action == ScenarioAction.REDUCE_ALL @@ -271,10 +250,7 @@ class TestGeneratePlaybook: planner = _make_planner(token_count=450) candidates = [_candidate()] - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - pb = await planner.generate_playbook("KR", candidates) + pb = await planner.generate_playbook("KR", candidates, today=date(2026, 2, 8)) assert pb.token_count == 450 @@ -429,10 +405,7 @@ class TestBuildCrossMarketContext: scorecard = {"total_pnl": 2.5, "win_rate": 65, "index_change_pct": 0.8, "lessons": ["Stay patient"]} planner = _make_planner(scorecard_data=scorecard) - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - ctx = planner.build_cross_market_context("KR") + ctx = planner.build_cross_market_context("KR", today=date(2026, 2, 8)) assert ctx is not None assert ctx.market == "US" @@ -449,10 +422,7 @@ class TestBuildCrossMarketContext: scorecard = {"total_pnl": -1.0, "win_rate": 40, "index_change_pct": -0.5} planner = _make_planner(scorecard_data=scorecard) - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - ctx = planner.build_cross_market_context("US") + ctx = planner.build_cross_market_context("US", today=date(2026, 2, 8)) assert ctx is not None assert ctx.market == "KR" @@ -465,20 +435,14 @@ class TestBuildCrossMarketContext: def test_no_scorecard_returns_none(self) -> None: planner = _make_planner(scorecard_data=None) - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - ctx = planner.build_cross_market_context("KR") + ctx = planner.build_cross_market_context("KR", today=date(2026, 2, 8)) assert ctx is None def test_invalid_scorecard_returns_none(self) -> None: planner = _make_planner(scorecard_data="not a dict and not json") - with patch("src.strategy.pre_market_planner.date") as mock_date: - mock_date.today.return_value = date(2026, 2, 8) - mock_date.side_effect = lambda *a, **kw: date(*a, **kw) - ctx = planner.build_cross_market_context("KR") + ctx = planner.build_cross_market_context("KR", today=date(2026, 2, 8)) assert ctx is None