import os import sys PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) if PROJECT_DIR not in sys.path: sys.path.insert(0, PROJECT_DIR) import altcoin_db def test_review_stats_contains_strategy_version_summary_and_changelog(monkeypatch): monkeypatch.setattr(altcoin_db, "get_strategy_iteration_logs", lambda limit=30: [ { "id": 1, "title": "第2轮复盘迭代", "strategy_version": "v1.2", "version_change_summary": "v1.2:加入静K蓄力旁路 + 板块联动降级保留", "changed_rules": [{"type": "learned_rule", "description": "静K旁路"}], "config_diff": {"changed": [{"path": "screener.vp_fly"}], "added": [], "removed": []}, "effect_summary": {"hit_rate_pct": 50.0, "avg_pnl": 0.79}, } ]) monkeypatch.setattr(altcoin_db, "get_strategy_iteration_summary", lambda days=30: { "total_logs": 1, "version_stats": [ {"strategy_version": "v1.2", "recommendation_count": 9, "success_count": 5, "failed_count": 1, "pending_count": 3, "success_rate_pct": 55.6, "avg_pnl_pct": 3.2} ], "version_changelog": [ {"strategy_version": "v1.2", "title": "第2轮复盘迭代", "summary": "加入旁路", "version_change_summary": "v1.2:加入静K蓄力旁路 + 板块联动降级保留"} ], }) class DummyConn: def execute(self, sql, params=()): class Rows(list): def fetchall(self_inner): return [] return Rows() def close(self): pass monkeypatch.setattr(altcoin_db, "get_conn", lambda: DummyConn()) data = altcoin_db.get_review_stats() assert data["iteration_summary"]["version_stats"][0]["strategy_version"] == "v1.2" assert "静K蓄力旁路" in data["iteration_summary"]["version_changelog"][0]["version_change_summary"] assert data["iteration_logs"][0]["strategy_version"] == "v1.2" def test_strategy_iteration_summary_aggregates_version_stats(monkeypatch): class DummyConn: def execute(self, sql, params=()): sql_norm = " ".join(sql.split()) if "FROM strategy_iteration_log" in sql_norm: class Rows(list): def fetchall(self_inner): return [ { "id": 1, "created_at": "2026-04-30T00:32:05", "run_date": "2026-04-30", "title": "第2轮复盘迭代", "summary": "继续积累样本", "changed_rules_json": "[]", "metrics_json": "{}", "related_symbols_json": "[]", "findings_json": "[]", "problems_json": "[]", "actions_json": "[]", "config_diff_json": '{"changed": [{"path": "meta.last_review"}], "added": [], "removed": []}', "effect_summary_json": '{"hit_rate_pct": 50.0, "avg_pnl": 0.79}', "strategy_version": "v1.2", "version_change_summary": "v1.2:加入静K蓄力旁路 + 板块联动降级保留", } ] return Rows() if "FROM recommendation" in sql_norm: class Rows(list): def fetchall(self_inner): return [ {"strategy_version": "v1.2", "status": "hit_tp1", "pnl_pct": 8.0, "max_pnl_pct": 12.0, "max_drawdown_pct": -1.0}, {"strategy_version": "v1.2", "status": "active", "pnl_pct": 2.0, "max_pnl_pct": 6.0, "max_drawdown_pct": -2.0}, {"strategy_version": "v1.2", "status": "stopped_out", "pnl_pct": -4.0, "max_pnl_pct": 1.0, "max_drawdown_pct": -6.0}, ] return Rows() raise AssertionError(sql) def close(self): pass monkeypatch.setattr(altcoin_db, "get_conn", lambda: DummyConn()) summary = altcoin_db.get_strategy_iteration_summary(days=30) assert summary["version_stats"][0]["strategy_version"] == "v1.2" assert summary["version_stats"][0]["recommendation_count"] == 3 assert summary["version_stats"][0]["success_count"] == 2 assert summary["version_stats"][0]["failed_count"] == 1 assert summary["version_changelog"][0]["strategy_version"] == "v1.2"