Files
MoFin/evolution/evolution_api.py
T
xxm b5aed51924 fix: 研究Tab与组合Tab反映最新策略家族状态
- 策略Tab: v_mr/v_mr_sel/v_combo 加'已证伪'红色标记(划线+badge+悬停原因)
- 当前策略从v_next4单点扩展为策略家族(v_weak/s2_panic/v_next4)
- 组合Tab: v_next4卡片version字段bug修复(错显示v_mr)
- 组合Tab文案更新为策略家族(同时运行/票自己对号入座/零重叠)
2026-08-06 00:16:58 +08:00

217 lines
8.1 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/evolution_api.py — 进化模块 API 接口
供 dashboard 查询健康度、教训、迭代历史
"""
import sys, os, json, sqlite3
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')
def get_evolution_dashboard():
"""进化模块 Dashboard 数据"""
conn = sqlite3.connect(DB)
conn.row_factory = sqlite3.Row
# 最近健康度(近30天)
health = []
for r in conn.execute("""
SELECT strategy_version, date, live_trades, live_wins, live_return_pct,
backtest_wr, backtest_avg_ret, deviation, health_score
FROM strategy_health ORDER BY date DESC LIMIT 30
""").fetchall():
health.append(dict(r))
# 最近教训(近20条)
lessons = []
for r in conn.execute("""
SELECT strategy_version, lesson_type, lesson_text, confidence, applied, created_at
FROM strategy_lessons ORDER BY id DESC LIMIT 20
""").fetchall():
lessons.append(dict(r))
# 迭代历史
evolution = []
for r in conn.execute("""
SELECT parent_version, child_version, change_description, promoted, created_at
FROM strategy_evolution ORDER BY id DESC LIMIT 20
""").fetchall():
evolution.append(dict(r))
# 当前策略基线
baseline = {}
for v in ['v_next4', 'v_next3', 'v8.1']:
r = conn.execute("""
SELECT results_json FROM strategy_research
WHERE version=? AND period_tag='5y' ORDER BY id DESC LIMIT 1
""", (v,)).fetchone()
if r:
res = json.loads(r[0])
s = res.get('summary', {})
pf = s.get('portfolio_full', {})
baseline[v] = {
'win_rate': s.get('win_rate', 0),
'total_return': pf.get('total_return_pct', 0),
'cagr': pf.get('cagr_pct', 0),
'max_dd': pf.get('portfolio_max_dd_pct', 0),
}
conn.close()
return {
'health': health,
'lessons': lessons,
'evolution': evolution,
'baseline': baseline,
}
def get_combo_dashboard():
"""组合方案 Dashboard 数据 (2026-08-02 新增)
返回: 当前组合方案(v_next4+v_mr按regime分工) + 组合回测版本(v_combo) + 市场阶段
"""
conn = sqlite3.connect(DB)
conn.row_factory = sqlite3.Row
# 1. 当前市场阶段 (market_regime)
regime = None
r = conn.execute("SELECT * FROM market_regime ORDER BY date DESC LIMIT 1").fetchone()
if r:
regime = dict(r)
# 2. 组合回测版本 (v_combo 家族)
combos = []
rows = conn.execute(
"SELECT id, version, market, period_tag, created_at, results_json"
" FROM strategy_research WHERE version LIKE '%combo%' OR version LIKE 'v_combo%'"
" ORDER BY id DESC"
).fetchall()
for r in rows:
d = dict(r)
res = json.loads(d.pop("results_json") or "{}")
s = res.get("summary", {})
pf = s.get("portfolio_full", {})
p5 = s.get("portfolio", {})
d["summary_stats"] = {
"total_trades": s.get("total_trades"),
"win_rate": s.get("win_rate"),
"avg_profit_pct": s.get("avg_profit_pct"),
"avg_hold_days": s.get("avg_hold_days"),
"sharpe_ratio": s.get("sharpe_ratio"),
"profit_factor": s.get("profit_factor"),
"universality": s.get("universality", {}),
"portfolio": p5,
"portfolio_full": pf,
}
combos.append(d)
# 3. 组合成员策略的独立指标
# v_mr 位置优先取 v_weak(六步方法论+12维框架定稿,2026-08-03),回退 v_mr_sel/v_mr
members = {}
for v in ["v_next4", "v_mr"]:
sel_v = "v_weak" if v == "v_mr" else None
candidates = [sel_v, "v_mr_sel", v] if sel_v else [v]
r = None
used_sel = False
for cv in candidates:
r = conn.execute(
"SELECT results_json FROM strategy_research"
" WHERE version=? AND period_tag='10y' ORDER BY id DESC LIMIT 1",
(cv,),
).fetchone()
if r:
used_sel = (cv == "v_weak")
break
if r:
res = json.loads(r[0])
s = res.get("summary", {})
pf = s.get("portfolio_full", {})
p5 = s.get("portfolio", {})
members[v] = {
"role": "趋势市主战" if v == "v_next4" else "震荡/下跌市接管",
"version": ("v_weak" if used_sel else "v_mr") if v == "v_mr" else v,
"is_sel": used_sel,
"trades": s.get("total_trades"),
"win_rate": s.get("win_rate"),
"avg_profit_pct": s.get("avg_profit_pct"),
"avg_hold_days": s.get("avg_hold_days"),
"cagr_pct": p5.get("cagr_pct"),
"return_pct": p5.get("total_return_pct"),
"max_dd_pct": p5.get("portfolio_max_dd_pct"),
"slots": p5.get("slots") or 6,
"universality": s.get("universality", {}),
# 组合模拟实际执行笔数(扣费后) + 年均(手工可行性参考)
"positions_taken_5slot": p5.get("positions_taken"),
"positions_taken_full": pf.get("positions_taken"),
}
# 3.5 策略家族 S2 卡片(s2_panic2026-08-05 §39/§40 上线)
r = conn.execute(
"SELECT results_json FROM strategy_research"
" WHERE version='s2_panic' AND period_tag='10y' ORDER BY id DESC LIMIT 1"
).fetchone()
if r:
res = json.loads(r[0])
s2 = res.get("summary", {})
pf = s2.get("portfolio_full", {})
p5 = s2.get("portfolio", {})
members["s2_panic"] = {
"role": "极端恐慌买超跌(RSI<25)",
"version": "s2_panic",
"is_sel": True,
"trades": s2.get("total_trades"),
"win_rate": s2.get("win_rate"),
"avg_profit_pct": s2.get("avg_profit_pct"),
"avg_hold_days": s2.get("avg_hold_days"),
"cagr_pct": p5.get("cagr_pct"),
"return_pct": p5.get("total_return_pct"),
"max_dd_pct": p5.get("portfolio_max_dd_pct"),
"slots": p5.get("slots") or 8,
"universality": s2.get("universality", {}),
"positions_taken_5slot": p5.get("positions_taken"),
"positions_taken_full": pf.get("positions_taken"),
}
conn.close()
return {
"regime": regime,
"combos": combos,
"members": members,
"routing": [
{"regime": "trend_up", "active": "v_next4", "action": "追涨买入/加仓放行", "desc": "大盘MA20上方+ADX强, 趋势追涨主战场"},
{"regime": "choppy", "active": "v_mr", "action": "追涨降级为关注", "desc": "震荡市, 超跌反弹主战场, 趋势追涨让位"},
{"regime": "trend_down", "active": "v_mr", "action": "禁止追涨", "desc": "深超跌主战场, 只做均值回复"},
],
}
def get_health_trend(version='v_next4', days=30):
"""健康度趋势"""
conn = sqlite3.connect(DB)
conn.row_factory = sqlite3.Row
rows = conn.execute("""
SELECT date, health_score, deviation, live_trades
FROM strategy_health WHERE strategy_version=? ORDER BY date DESC LIMIT ?
""", (version, days)).fetchall()
conn.close()
return [dict(r) for r in rows]
def record_evolution(parent, child, description, backtest_result=None, promoted=0):
"""记录一次策略迭代"""
conn = sqlite3.connect(DB)
conn.execute("""
INSERT INTO strategy_evolution (parent_version, child_version, change_description, backtest_result, promoted)
VALUES (?, ?, ?, ?, ?)
""", (parent, child, description, json.dumps(backtest_result) if backtest_result else None, promoted))
conn.commit()
conn.close()
if __name__ == '__main__':
d = get_evolution_dashboard()
print(f"健康度: {len(d['health'])}条, 教训: {len(d['lessons'])}条, 迭代: {len(d['evolution'])}条")
print(f"基线: {list(d['baseline'].keys())}")