import os import sys from datetime import datetime, timedelta import pytest from fastapi.testclient import TestClient 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) from app.db import altcoin_db from app.web import web_server @pytest.fixture def temp_db(monkeypatch, tmp_path): db_path = tmp_path / "altcoin_monitor.db" monkeypatch.setattr(altcoin_db, "DB_PATH", str(db_path)) monkeypatch.setattr(web_server, "init_db", altcoin_db.init_db) monkeypatch.setattr(web_server, "get_review_stats", altcoin_db.get_review_stats) monkeypatch.setattr(web_server, "get_stats", altcoin_db.get_stats) altcoin_db.init_db() return db_path def test_iteration_log_roundtrip_and_summary(temp_db): altcoin_db.log_strategy_iteration( run_date="2026-04-29", trigger_source="daily_review", title="缩小滞后指标权重", summary="复盘发现 MACD/RSI 追涨假信号偏多,降低滞后指标基础权重。", findings=[ "过去 5 次失败案例里,4 次同时出现滞后指标共振但没有前瞻量价确认", "横盘样本集中在仅有 MACD/RSI 共振的票", ], problems=[ "滞后指标在启动末端给出追涨确认,导致入场过晚", ], actions=[ "category_base_weights.滞后: 0.5 → 0.3", "kill_min_samples: 5 → 4", ], changed_rules=[ {"field": "category_base_weights.滞后", "old": 0.5, "new": 0.3}, {"field": "kill_min_samples", "old": 5, "new": 4}, ], metrics={"reviews_done": 6, "fail_count": 4, "new_rules": 1}, related_symbols=["DOGE/USDT", "PEPE/USDT"], pollution_summary={ "window_days": 7, "effective_start": "2026-04-22T00:00:00", "contaminated_symbol_count": 2, "screening_hit_count": 3, "recommendation_hit_count": 1, "contaminated_symbols": ["EUR/USDT", "USD1/USDT"], "layer_counts": {"coarse": 2, "fine": 1}, }, ) altcoin_db.log_strategy_iteration( run_date="2026-04-29", trigger_source="manual", title="补充等待回踩过滤", summary="将追高型候选延后到回踩确认。", findings=["爆发前 1H 放量但偏离 5EMA 过远的票,回撤概率上升"], problems=["原有即刻买入条件对急拉币容忍度过高"], actions=["新增 wait_pullback 场景说明"], changed_rules=[], metrics={"affected_candidates": 3}, related_symbols=["FIL/USDT"], ) logs = altcoin_db.get_strategy_iteration_logs(limit=10) assert len(logs) == 2 assert logs[0]["title"] == "补充等待回踩过滤" assert logs[1]["changed_rules"][0]["field"] == "category_base_weights.滞后" assert logs[1]["metrics"]["reviews_done"] == 6 assert logs[1]["pollution_summary"]["contaminated_symbol_count"] == 2 assert logs[1]["pollution_summary"]["contaminated_symbols"] == ["EUR/USDT", "USD1/USDT"] summary = altcoin_db.get_strategy_iteration_summary(days=30) assert summary["total_logs"] == 2 assert summary["unique_run_days"] == 1 assert summary["trigger_counts"]["daily_review"] == 1 assert summary["trigger_counts"]["manual"] == 1 assert summary["change_rule_count"] == 2 assert summary["recent_titles"][0] == "补充等待回踩过滤" def test_review_api_exposes_iteration_logs(temp_db): altcoin_db.log_strategy_iteration( run_date="2026-04-29", trigger_source="daily_review", title="新增失败案例日志化", summary="把问题和动作拆开记录到网站。", findings=["用户需要直接看到每天改了什么"], problems=["之前只有复盘结果,没有策略修改过程记录"], actions=["新增 strategy_iteration_log 表与 review 页面展示"], changed_rules=[{"field": "ui.review.iteration_log", "old": False, "new": True}], metrics={"items": 1}, related_symbols=["FIL/USDT"], pollution_summary={ "window_days": 7, "effective_start": "2026-04-22T00:00:00", "contaminated_symbol_count": 1, "screening_hit_count": 1, "recommendation_hit_count": 0, "contaminated_symbols": ["EUR/USDT"], "layer_counts": {"coarse": 1}, }, ) client = TestClient(web_server.app) resp = client.get("/api/review") assert resp.status_code == 200 data = resp.json() assert data["iteration_summary"]["total_logs"] == 1 assert data["iteration_logs"][0]["title"] == "新增失败案例日志化" assert data["iteration_logs"][0]["changed_rules"][0]["field"] == "ui.review.iteration_log" assert data["iteration_logs"][0]["pollution_summary"]["contaminated_symbol_count"] == 1 assert data["iteration_logs"][0]["pollution_summary"]["contaminated_symbols"] == ["EUR/USDT"]