From 2998bfb06f7e2d53eb4c54ec450a0cbd8f1f9011 Mon Sep 17 00:00:00 2001 From: xxm Date: Mon, 17 Aug 2026 04:01:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8B=A9=E4=BC=98=E6=BF=80=E6=B4=BB?= =?UTF-8?q?=E8=A7=84=E5=88=99(strategy=5Factivation=5Fselector)=E2=80=94?= =?UTF-8?q?=E2=80=94=E8=B4=A8=E9=87=8F=E5=88=86(=E7=BB=BC=E5=90=88?= =?UTF-8?q?=E5=88=86=C3=97=E6=99=AE=E9=80=82=E6=9C=89=E6=95=88=E5=B9=B4?= =?UTF-8?q?=E5=8D=A0=E6=AF=94)=E6=8E=92=E5=BA=8F+=E5=AE=B6=E6=97=8F?= =?UTF-8?q?=E5=8E=BB=E9=87=8D+=E5=87=BA=E6=89=8B=E4=B8=8A=E9=99=90,?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E5=90=84=E6=B8=A9=E5=8C=BA=E5=BA=94=E6=BF=80?= =?UTF-8?q?=E6=B4=BB=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../strategy_activation_selector.py | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 deploy/profile-scripts/strategy_activation_selector.py diff --git a/deploy/profile-scripts/strategy_activation_selector.py b/deploy/profile-scripts/strategy_activation_selector.py new file mode 100644 index 00000000..826b3b62 --- /dev/null +++ b/deploy/profile-scripts/strategy_activation_selector.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +"""择优激活规则 v2:修正上限逻辑——按质量排序选最优N个,家族去重,软上限 +输出:每个温区应激活的策略(写入 strategy_weights.json 的 active)""" +import sys, json, sqlite3 +sys.path.insert(0, "/home/hmo/MoFin") +sys.path.insert(0, "/home/hmo/MoFin/deploy/profile-scripts") +import strategy_qualify as sq + +MIN_YEARLY = 15 # 年化成交下限(低于此=机会不足) +MAX_YEARLY = 300 # 年化成交上限(软上限,质量优先) + +FAMILIES = { + "s2_panic": "s2", "s2_panic_v2": "s2", "s2_panic_v3": "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", + "hk_pe_mom": "hkpe", "hk_pe_oversold": "hkpe", "hk_mr1": "hkmr", "hk_mr2": "hkmr", +} + + +def efficiency_factor(sig, pos): + ratio = sig / pos if pos else 99 + if ratio <= 2: return 1.0 + if ratio <= 5: return 0.9 + if ratio <= 10: return 0.75 + return 0.5 + + +def get_strategy_info(version, market, regime): + conn = sqlite3.connect("/home/hmo/MoFin/data/mofin.db", timeout=10) + r = conn.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'", + (version, market, regime)).fetchone() + dep = conn.execute("SELECT MAX(deprecated) FROM strategy_research WHERE version=? AND deprecated IS NOT NULL AND deprecated!=''", (version,)).fetchone()[0] + conn.close() + if not r: + return None + sig, pos, wr, sh, pf, ret, dd, univ, uyears, uvalid = 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 + n = sig or 0 + conf = min(1, n / 40) + eff = efficiency_factor(sig, pos) + comp = round((ret_c + wr_c + sh_c + pf_c + dd_c) * conf * eff) + return {"sig": sig, "pos": pos, "comp": comp, "univ": univ or 0, + "uyears": uyears or 0, "uvalid": uvalid or 0, "dep": dep} + + +def select_best(regime, market='a'): + conn = sqlite3.connect("/home/hmo/MoFin/data/mofin.db", timeout=10) + rows = conn.execute("SELECT DISTINCT strategy FROM strategy_regime_perf_by_period WHERE market=? AND regime=?", (market, regime)).fetchall() + conn.close() + avail = sq.load_availability() + candidates = [] + for (version,) in rows: + info = get_strategy_info(version, market, regime) + if not info or info["dep"]: + continue + if not avail.get(version, {}).get("available", False): + continue + try: + q = sq.evaluate_all_regimes(version, market=market) + qr = q.get(regime, {}) + if not (qr.get("long_ok") and qr.get("mid_ok") and qr.get("short_ok")): + continue + except Exception: + continue + # 质量分 = 综合分 × 普适(有效年占比越高越好) + univ_ratio = (info["uvalid"] / info["uyears"]) if info["uyears"] else 0 + quality = info["comp"] * (0.5 + 0.5 * univ_ratio) + candidates.append({"version": version, "info": info, "quality": round(quality, 1), + "family": FAMILIES.get(version, version)}) + candidates.sort(key=lambda x: -x["quality"]) + + # 择优:家族去重 + 软上限 + selected = [] + used_fam = set() + total_yearly = 0 + for c in candidates: + if c["family"] in used_fam: + continue + # 出手次数太少的不选(年化 < MIN_YEARLY/2) + yearly = c["info"]["pos"] / 2 # 2y→年化 + if yearly < 5: + continue + if total_yearly + yearly > MAX_YEARLY and selected: + break # 超上限停止 + used_fam.add(c["family"]) + selected.append(c) + total_yearly += yearly + return selected, total_yearly + + +if __name__ == "__main__": + print("# 择优激活建议(质量分=综合分×普适有效年占比)") + all_sel = {} + for market in ["a", "hk"]: + for regime in ["trend_down", "choppy", "trend_up"]: + sel, total = select_best(regime, market) + if sel: + print(f"\n## {market} {regime}: 年化总出手≈{total:.0f}") + for c in sel: + i = c["info"] + print(f" {c['version']:18} 质量{c['quality']:5.1f} 综合{i['comp']:4} 信号{i['sig']:5} 成交{i['pos']:4} 普适{i['univ']:4.0f}({i['uvalid']}/{i['uyears']}年)") + all_sel[(market, regime)] = [c["version"] for c in sel] + # 保存建议 + with open("/tmp/activation_suggestion.json", "w") as f: + json.dump({f"{m}:{r}": v for (m, r), v in all_sel.items()}, f, ensure_ascii=False, indent=1) + print("\n# 建议已存 /tmp/activation_suggestion.json")