#!/usr/bin/env python3 # -*- coding: utf-8 -*- """strategy_router.py v5 — MoFin 策略动态路由(动态策略库,2026-08-13) 核心(老莫原则): - 策略全温区发信号(不留温区门控在策略内) - 策略-温区表现常态化记录(regime_perf.py / strategy_regime_perf),适用温区动态化 - 组合按当前温区自动激活/休眠策略:当前温区实测胜率高的策略优先 - 不漏历史策略(v_mr_sel trend_down 93%、v7.3 trend_up 94% 等被埋没的强者) 数据流: market_regime(三态) + regime_tracker(平滑K=5) → 当前温区 temp_band(rsi) → 温度/仓位乘数 regime_perf(实测) → 各策略温区表现 → strategy_weights.json(各策略权重/激活状态) """ import json import sys from pathlib import Path from datetime import datetime # ── 消息通道统一路由(broadcast/xmpp by delivery) ── try: from messenger import install_stdio_hook as _msh _msh() except Exception: pass _SCRIPT_DIR = Path(__file__).resolve().parent sys.path.insert(0, str(_SCRIPT_DIR)) sys.path.insert(0, "/home/hmo/MoFin") OUT = Path("/home/hmo/MoFin/data/strategy_weights.json") # 2026-08-17 老莫:择优激活——每温区最多激活的策略数 MAX_ACTIVE = 3 # 家族映射(同家族只保留质量最优的一个,避免重复策略占位) FAMILY_MAP_ACT = { "s2_panic": "s2", "s2_panic_v2": "s2", "v_lurk_v1": "vlurk", "v_lurk_v2": "vlurk", "v_lurk_v3": "vlurk", "v_mr": "vmr", "v_mr2": "vmr", "v_mr3": "vmr", "v_mr4": "vmr", "v_oversold": "vover", "v_weak": "vweak", "b_td1": "b_td", "b_td1_v2": "b_td", "b_td1_v3": "b_td", "v1.0": "v1", "v2.0": "v2", "v3.0": "v3", "v_next": "vnext", } DEFAULT_FAMILY = "mr" FAMILY_MAP = { "v_weak": "mr", "v_oversold": "mr", "v_mr": "mr", "v_mr2": "mr", "v_mr3": "mr", "v_mr4": "mr", "v_mr_sel": "mr", "v_lurk_v1": "mr", "v_lurk_v2": "mr", "v_lurk_v3": "mr", "s2_panic": "mr", "v_osc": "mr", "v_next": "trend", "v_next3": "trend", "v_next4": "trend", "v_next5": "trend", "v8.0": "trend", "v8.1": "trend", "v8.2": "trend", "v8.3": "trend", "v7.0": "trend", "v7.1": "trend", "v7.1b": "trend", "v7.2": "trend", "v7.3": "trend", "v6.0": "trend", "v6.1": "trend", "v6.2": "trend", "v9.0": "trend", "v9.1": "trend", "v9.2": "trend", "v_combo": "trend", "v1.0": "trend", "v2.0": "trend", "v3.0": "trend", "v4.0": "trend", "v4.0a": "trend", "v4.0b": "trend", "v4.0c": "trend", "v4.0d": "trend", "v4.0e": "trend", "v4.0f": "trend", "v4.0g": "trend", "v4.0h": "trend", "v4.1": "trend", "v5.0": "trend", "v11.0": "trend", "v11.1": "trend", "hk_mr1": "mr", # 港股深度超卖反弹(2026-08-14 港股归因研发,trend_down 主战场) } def load_regime(market='a'): """读取当前温区。market='a'(默认,A股 smoothed 顶层,行为不变) / 'hk'(港股 markets.hk)""" if market == 'hk': try: p = Path("/home/hmo/MoFin/data/market_regime_smoothed.json") if p.exists(): d = json.loads(p.read_text(encoding="utf-8")) mk = (d.get("markets") or {}).get("hk") or {} if mk.get("current_regime"): return {"regime": mk["current_regime"], "date": mk.get("current_date", "")} except Exception: pass try: from market_regime import load_market_regime return load_market_regime(market='hk') except Exception: return {"regime": "unknown"} try: p = Path("/home/hmo/MoFin/data/market_regime_smoothed.json") if p.exists(): d = json.loads(p.read_text(encoding="utf-8")) return {"regime": d.get("current_regime", "unknown"), "date": d.get("current_date", "")} except Exception: pass try: from market_regime import load_market_regime return load_market_regime() except Exception: return {"regime": "unknown"} def load_temp(market='a'): """读取当前温度。market='a'(默认,A股) / 'hk'(港股)""" try: from temp_band import get_market_temp return get_market_temp(market=market) except Exception: return {"band": "unknown", "rsi": None} def load_regime_perf(market='a'): """读取策略-温区表现(2026-08-17 改用 by_period 2y——含 b_td1_v3/s2_panic_v2 等新策略)。 旧表 strategy_regime_perf 不含新策略,导致择优激活漏选;by_period 全量覆盖。""" conn = None try: import sqlite3 conn = sqlite3.connect("/home/hmo/MoFin/data/mofin.db", timeout=30) conn.execute("PRAGMA busy_timeout=30000") # 新表 by_period(2y) 优先,旧表补齐 rows = conn.execute( "SELECT strategy, regime, trades, win_rate, avg_pnl FROM strategy_regime_perf_by_period " "WHERE COALESCE(market,'a')=? AND period_tag='2y'", (market,) ).fetchall() result = {} for strat, reg, n, wr, pnl in rows: result.setdefault(strat, {})[reg] = {"trades": n, "win_rate": wr, "avg_pnl": pnl} # 旧表补缺(by_period 没覆盖的策略) rows2 = conn.execute( "SELECT strategy, regime, trades, win_rate, avg_pnl FROM strategy_regime_perf " "WHERE COALESCE(market,'a')=?", (market,) ).fetchall() for strat, reg, n, wr, pnl in rows2: result.setdefault(strat, {}).setdefault(reg, {"trades": n, "win_rate": wr, "avg_pnl": pnl}) return result except Exception: return {} finally: if conn: conn.close() def route(regime, temp, perf): current_regime = regime.get("regime", "unknown") band = temp.get("band", "unknown") weights = {} # 已证伪策略(从 strategy_research.deprecated 读,数据驱动——证伪绝不激活) dep = set() try: import sqlite3 as _sq _c = _sq.connect("/home/hmo/MoFin/data/mofin.db", timeout=5) dep = {r[0] for r in _c.execute( "SELECT DISTINCT version FROM strategy_research WHERE deprecated IS NOT NULL AND deprecated != ''")} _c.close() except Exception: pass for name, regs in perf.items(): if not regs: continue if name in dep: # 已证伪策略绝不激活(数据驱动,2026-08-15) continue cur = regs.get(current_regime) best = max(regs.items(), key=lambda x: x[1].get("win_rate", 0)) best_wr = best[1].get("win_rate", 0) cur_wr = cur.get("win_rate") if cur else None # 2026-08-16 资格标准A(老莫):长期10y/近期2y/当下1y 适应温区年化>大盘 三项达标 + 手动可用 # 2026-08-18 重构(老莫):matched 改为【任一温区三项达标】即可(不只当前温区)。 # 核心:资格够就必须能激活;当前温区是实盘闸门,但choppy/trend_up等温区达标策略也要备好激活。 # 不合格(如 v_lurk_v3 @ choppy ✅❌❌)绝不激活该温区;合格(v_lurk_v2 @ choppy)就激活。 try: from strategy_qualify import evaluate_all_regimes, is_available, get_benchmarks _qbench = get_benchmarks('a') _qev_all = evaluate_all_regimes(name, 'a', bench=_qbench) or {} # 达标温区列表(任一温区三项全达) _qual_regimes = [] for _rg in ("trend_down", "choppy", "trend_up"): _q = (_qev_all or {}).get(_rg) or {} if _q.get("long_ok") and _q.get("mid_ok") and _q.get("short_ok"): _qual_regimes.append(_rg) _manual_ok = is_available(name) matched = bool(_qual_regimes) and _manual_ok _qual_cur_ok = False if _qual_regimes: _qev_cur = (_qev_all or {}).get(current_regime) or {} _qual_cur_ok = bool(_qev_cur.get("long_ok") and _qev_cur.get("mid_ok") and _qev_cur.get("short_ok")) except Exception: _qual_regimes = [current_regime] if ((best_wr >= 50) and (cur_wr is not None and cur_wr >= 50)) else [] _qual_cur_ok = current_regime in _qual_regimes matched = bool(_qual_regimes) # ✓ 记录达标温区(供按温区激活矩阵) qualifies = _qual_regimes if 'qualifies' in dir() else [] family = FAMILY_MAP.get(name, DEFAULT_FAMILY) base = 1.0 if matched else 0.3 try: from temp_band import temp_multiplier mult = temp_multiplier(band, family) except Exception: mult = 0.8 weights[name] = { "weight": round(base * mult, 2), "regime": current_regime, "temp_band": band, "family": family, "matched": matched, "qualifies": qualifies, # 达标温区列表(2026-08-18) "qual_cur_ok": _qual_cur_ok, # 当前温区是否达标(2026-08-18) "best_regime": best[0], "best_win_rate": best[1].get("win_rate", 0), "cur_win_rate": cur.get("win_rate") if cur else None, "cur_avg_pnl": cur.get("avg_pnl") if cur else None, "trades": cur.get("trades") if cur else 0, "active_regimes": qualifies, # 别名(前端用) } return dict(sorted(weights.items(), key=lambda x: -x[1]["weight"])) def _quality_score(name, market, regime): """质量分 = 综合分(含效率惩罚) × 普适有效年占比(与 strategy_activation_selector 一致)""" try: import sqlite3 as _sq _c = _sq.connect("/home/hmo/MoFin/data/mofin.db", timeout=5) r = _c.execute( "SELECT trades, positions_taken, win_rate, sharpe_ratio, profit_factor, total_return_pct, " "portfolio_max_dd_pct, universality_score, universality_years, universality_valid_years " "FROM strategy_regime_perf_by_period WHERE strategy=? AND market=? AND regime=? AND period_tag='2y'", (name, market, regime)).fetchone() _c.close() if not r: return 0 sig, pos, wr, sh, pf, ret, dd, univ, uy, uv = r pos = pos or 0 ret_c = min(ret or 0, 100) / 100 * 30 wr_c = (wr or 0) / 100 * 20 sh_c = min(max(sh or 0, 0), 20) / 20 * 20 pf_c = min(pf or 0, 5) / 5 * 15 dd_c = (1 - min(dd or 0, 50) / 50) * 15 conf = min(1, (sig or 0) / 40) # 效率惩罚(信号/成交比) ratio = sig / pos if pos else 99 eff = 1.0 if ratio <= 2 else 0.9 if ratio <= 5 else 0.75 if ratio <= 10 else 0.5 comp = (ret_c + wr_c + sh_c + pf_c + dd_c) * conf * eff univ_ratio = (uv / uy) if uy else 0 return comp * (0.5 + 0.5 * univ_ratio) except Exception: return 0 def _select_active(weights, regime, market): """择优激活:matched 策略按质量分排序,家族去重,取前 MAX_ACTIVE""" matched = [(k, v) for k, v in weights.items() if v.get("matched")] scored = [] for name, v in matched: q = _quality_score(name, market, regime) fam = FAMILY_MAP_ACT.get(name, name) scored.append({"name": name, "quality": q, "family": fam}) scored.sort(key=lambda x: -x["quality"]) used_fam = set() sel = [] for s in scored: if s["family"] in used_fam: continue if len(sel) >= MAX_ACTIVE: break used_fam.add(s["family"]) sel.append(s["name"]) return sel def main(): # A股路由(完全不变) regime = load_regime() # A股温区 temp = load_temp() # A股温度 perf = load_regime_perf('a') # A股策略表现 weights = route(regime, temp, perf) # 2026-08-18:按温区生成激活矩阵(老莫:资格够就激活,不合格绝不激活) # - 每个温区,从"在该温区三项达标(qualifies含该温区)"里按质量分择优取前 MAX_ACTIVE # - 当前温区 active = regime_active[当前温区](实盘闸门) _all_regimes = ("trend_down", "choppy", "trend_up") _cur_rg = regime.get("regime", "unknown") regime_active = {} for _rg in _all_regimes: # 该温区达标策略(qualifies 含 rg) _rg_matched = [(k, v) for k, v in weights.items() if v.get("matched") and _rg in v.get("qualifies", [])] # 按质量分择优 _rg_scored = [] for name, v in _rg_matched: q = _quality_score(name, 'a', _rg) fam = FAMILY_MAP_ACT.get(name, name) _rg_scored.append({"name": name, "quality": q, "family": fam}) _rg_scored.sort(key=lambda x: -x["quality"]) _used = set(); _sel = [] for s in _rg_scored: if s["family"] in _used: continue if len(_sel) >= MAX_ACTIVE: break _used.add(s["family"]); _sel.append(s["name"]) regime_active[_rg] = _sel out = { "state": _cur_rg, "state_date": regime.get("date", ""), "temp_band": temp.get("band", "unknown"), "temp_rsi": temp.get("rsi"), "weights": weights, "active": regime_active.get(_cur_rg, []), "regime_active": regime_active, "updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "note": "择优激活(2026-08-18): 按温区——任一温区三项达标即可激活该温区;当前温区实盘闸门;不合格绝不激活;合格就激活(择优排序)", } # 港股路由(2026-08-14 新增,不影响A股)——港股组合按温区调度(hk_strategies 定义) try: hk_regime = load_regime('hk') hk_temp = load_temp('hk') from hk_strategies import strategies_for_regime hk_cur_rg = hk_regime.get("regime", "unknown") hk_active = strategies_for_regime(hk_cur_rg) # 2026-08-18 港股按温区激活矩阵(老莫:非当前温区资格够也激活/备好) hk_regime_active = {} for _r in ("trend_down", "choppy", "trend_up"): hk_regime_active[_r] = strategies_for_regime(_r) out["markets"] = {"hk": { "state": hk_cur_rg, "state_date": hk_regime.get("date", ""), "temp_band": hk_temp.get("band", "unknown"), "active": hk_active, "regime_active": hk_regime_active, }} except Exception as e: print(f"港股路由失败(不影响A股): {e}", flush=True) OUT.write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8") print(f"strategy_weights.json: 状态={out['state']} 温度={out['temp_band']}(rsi={out['temp_rsi']})") print(f"激活策略(A股): {out['active']}") if "markets" in out: hk = out["markets"]["hk"] print(f"港股温区={hk['state']} 激活策略(港股): {hk['active']}") print("权重排名(前15):") for name, w in list(weights.items())[:15]: mark = "✓" if w["matched"] else "观察" print(f" {name:<12} w={w['weight']:<5} {w['best_regime']:<11} 最佳{w['best_win_rate']:>3.0f}% {mark}") if __name__ == "__main__": main()