""" evolution/health_monitor.py — 策略健康度监控 对比实盘交易 vs 回测预期,计算健康分,偏差过大时报警 """ import sys, os, json, sqlite3 from datetime import datetime, timedelta sys.path.insert(0, '/home/hmo/MoFin') sys.path.insert(0, '/home/hmo/MoFin/deploy/profile-scripts') DB = os.environ.get('MOFIN_DB', '/home/hmo/MoFin/data/mofin.db') CURRENT_STRATEGY = 'v_next4' def get_backtest_baseline(conn, version): """从 strategy_research 取回测基线""" r = conn.execute(""" SELECT results_json FROM strategy_research WHERE version=? AND period_tag='5y' ORDER BY id DESC LIMIT 1 """, (version,)).fetchone() if not r: return None res = json.loads(r[0]) s = res.get('summary', {}) return { 'win_rate': s.get('win_rate', 0), 'avg_profit_pct': s.get('avg_profit_pct', 0), 'total_trades': s.get('total_trades', 0), } def get_live_trades(conn, days=7): """取近N天实盘交易(holding_strategies 全部,计算盈亏)""" since = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') try: rows = conn.execute(""" SELECT code, name, price as current_price, avg_price as entry_price, timing_signal as signal, updated_at, CASE WHEN avg_price > 0 THEN round((price - avg_price) / avg_price * 100, 2) ELSE 0 END as profit_pct FROM holding_strategies WHERE updated_at >= ? AND avg_price > 0 ORDER BY updated_at DESC """, (since,)).fetchall() return rows except sqlite3.OperationalError as e: print(f"查询失败: {e}", flush=True) return [] def calc_health_score(live_wr, live_ret, backtest_wr, backtest_ret): """计算健康分 (0-100) 健康分 = 100 - 偏差惩罚 偏差 = |实盘胜率-回测胜率| + |实盘收益-回测收益|/2 """ if backtest_wr == 0: return 50 # 无基线,中性分 wr_dev = abs(live_wr - backtest_wr) ret_dev = abs(live_ret - backtest_ret) / 2 deviation = wr_dev + ret_dev # 偏差越大,健康分越低 health = max(0, 100 - deviation * 2) return round(health, 1) def run_health_check(strategy_version=None): """执行健康度检查""" version = strategy_version or CURRENT_STRATEGY conn = sqlite3.connect(DB) conn.row_factory = sqlite3.Row # 回测基线 baseline = get_backtest_baseline(conn, version) if not baseline: print(f"无 {version} 回测基线", flush=True) conn.close() return None # 实盘交易(近7天) live = get_live_trades(conn, days=7) today = datetime.now().strftime('%Y-%m-%d') if not live: # 无实盘数据,记录中性健康分 health = 50 deviation = 0 live_wr = live_ret = 0 print(f"{version}: 近7天无实盘交易,健康分=50(中性)", flush=True) else: wins = sum(1 for t in live if (t.get('profit_pct') or 0) > 0) total = len(live) live_wr = round(100 * wins / total, 1) if total else 0 live_ret = round(sum(t.get('profit_pct') or 0 for t in live) / total, 2) if total else 0 health = calc_health_score(live_wr, live_ret, baseline['win_rate'], baseline['avg_profit_pct']) deviation = abs(live_wr - baseline['win_rate']) # 写入 strategy_health 表 conn.execute(""" INSERT OR REPLACE INTO strategy_health (strategy_version, date, live_trades, live_wins, live_return_pct, backtest_wr, backtest_avg_ret, deviation, health_score) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, (version, today, len(live), sum(1 for t in live if (t.get('profit_pct') or 0) > 0), live_ret, baseline['win_rate'], baseline['avg_profit_pct'], deviation, health)) conn.commit() # 报警判断(2026-08-12 修:无实盘交易时不告警——health=50 是中性"无数据",非"偏低") alert = None if live: if health < 40: alert = f"🔴 策略健康度严重下降: {health}分 (偏差{deviation}pp)" elif health < 60: alert = f"🟡 策略健康度偏低: {health}分 (偏差{deviation}pp)" result = { 'version': version, 'date': today, 'live_trades': len(live), 'live_wr': live_wr, 'live_ret': live_ret, 'backtest_wr': baseline['win_rate'], 'backtest_ret': baseline['avg_profit_pct'], 'deviation': deviation, 'health_score': health, 'alert': alert, } print(f"{version} 健康度: {health}分 (实盘{live_wr}%/{live_ret}% vs 回测{baseline['win_rate']}%/{baseline['avg_profit_pct']}%)", flush=True) if alert: print(f" {alert}", flush=True) conn.close() return result if __name__ == '__main__': import sys sys.path.insert(0, '/home/hmo/MoFin/evolution') from __init__ import init_evolution_tables init_evolution_tables() run_health_check()