143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
"""
|
|
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()
|
|
|
|
# 报警判断
|
|
alert = None
|
|
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()
|