From c95102a0bd0d0554edad57b8dc027d72ae05094e Mon Sep 17 00:00:00 2001 From: agentson Date: Tue, 10 Feb 2026 04:25:37 +0900 Subject: [PATCH] feat: DailyScorecard model for per-market performance review (issue #90) - Add DailyScorecard dataclass with market-scoped fields - Fields: date, market, decisions, pnl, win_rate, scenario_match_rate, lessons, cross_market_note - Export from src/evolution/__init__.py Co-Authored-By: Claude Opus 4.6 --- src/evolution/__init__.py | 2 + src/evolution/scorecard.py | 25 ++++++++++++ tests/test_scorecard.py | 81 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 src/evolution/scorecard.py create mode 100644 tests/test_scorecard.py diff --git a/src/evolution/__init__.py b/src/evolution/__init__.py index 0267d8c..3346a61 100644 --- a/src/evolution/__init__.py +++ b/src/evolution/__init__.py @@ -7,6 +7,7 @@ from src.evolution.performance_tracker import ( PerformanceTracker, StrategyMetrics, ) +from src.evolution.scorecard import DailyScorecard __all__ = [ "EvolutionOptimizer", @@ -16,4 +17,5 @@ __all__ = [ "PerformanceTracker", "PerformanceDashboard", "StrategyMetrics", + "DailyScorecard", ] diff --git a/src/evolution/scorecard.py b/src/evolution/scorecard.py new file mode 100644 index 0000000..06d0b2b --- /dev/null +++ b/src/evolution/scorecard.py @@ -0,0 +1,25 @@ +"""Daily scorecard model for end-of-day performance review.""" + +from __future__ import annotations + +from dataclasses import dataclass, field + + +@dataclass +class DailyScorecard: + """Structured daily performance snapshot for a single market.""" + + date: str + market: str + total_decisions: int + buys: int + sells: int + holds: int + total_pnl: float + win_rate: float + avg_confidence: float + scenario_match_rate: float + top_winners: list[str] = field(default_factory=list) + top_losers: list[str] = field(default_factory=list) + lessons: list[str] = field(default_factory=list) + cross_market_note: str = "" diff --git a/tests/test_scorecard.py b/tests/test_scorecard.py new file mode 100644 index 0000000..ff2563f --- /dev/null +++ b/tests/test_scorecard.py @@ -0,0 +1,81 @@ +"""Tests for DailyScorecard model.""" + +from __future__ import annotations + +from src.evolution.scorecard import DailyScorecard + + +def test_scorecard_initialization() -> None: + scorecard = DailyScorecard( + date="2026-02-08", + market="KR", + total_decisions=10, + buys=3, + sells=2, + holds=5, + total_pnl=1234.5, + win_rate=60.0, + avg_confidence=78.5, + scenario_match_rate=70.0, + top_winners=["005930", "000660"], + top_losers=["035420"], + lessons=["Avoid chasing breakouts"], + cross_market_note="US volatility spillover", + ) + + assert scorecard.market == "KR" + assert scorecard.total_decisions == 10 + assert scorecard.total_pnl == 1234.5 + assert scorecard.top_winners == ["005930", "000660"] + assert scorecard.lessons == ["Avoid chasing breakouts"] + assert scorecard.cross_market_note == "US volatility spillover" + + +def test_scorecard_defaults() -> None: + scorecard = DailyScorecard( + date="2026-02-08", + market="US", + total_decisions=0, + buys=0, + sells=0, + holds=0, + total_pnl=0.0, + win_rate=0.0, + avg_confidence=0.0, + scenario_match_rate=0.0, + ) + + assert scorecard.top_winners == [] + assert scorecard.top_losers == [] + assert scorecard.lessons == [] + assert scorecard.cross_market_note == "" + + +def test_scorecard_list_isolation() -> None: + a = DailyScorecard( + date="2026-02-08", + market="KR", + total_decisions=1, + buys=1, + sells=0, + holds=0, + total_pnl=10.0, + win_rate=100.0, + avg_confidence=90.0, + scenario_match_rate=100.0, + ) + b = DailyScorecard( + date="2026-02-08", + market="US", + total_decisions=1, + buys=0, + sells=1, + holds=0, + total_pnl=-5.0, + win_rate=0.0, + avg_confidence=60.0, + scenario_match_rate=50.0, + ) + + a.top_winners.append("005930") + assert b.top_winners == []