Files
MoFin/evolution/health_monitor.py
T

144 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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()