#!/usr/bin/env python3 # -*- coding: utf-8 -*- """MoFin 策略定义库落地脚本(2026-08-18) 1. 建 strategy_defs 表 2. holding_strategies 加列 strategy_provenance/strategy_state/strategy_attributed 3. 种子卡:8 个策略(accumulation/b_td1_v3/v_mr/hk_pe_mom/hk_pe_oversold/p_oversold/s2_panic/leader) 执行:/home/hmo/MoFin/venv/bin/python setup_strategy_defs.py """ import sqlite3, json, hashlib, sys DB = "/home/hmo/MoFin/data/mofin.db" def get_conn(): conn = sqlite3.connect(DB, timeout=30) conn.execute("PRAGMA busy_timeout=30000") return conn def create_tables(conn): # 策略定义表 conn.execute(""" CREATE TABLE IF NOT EXISTS strategy_defs ( strategy_name TEXT PRIMARY KEY, display_name TEXT NOT NULL, aliases TEXT, market TEXT NOT NULL DEFAULT 'A', regime TEXT, family TEXT, summary TEXT NOT NULL, entry_logic TEXT NOT NULL, exit_logic TEXT NOT NULL, key_params TEXT, review_focus TEXT NOT NULL, holding_style TEXT, status TEXT NOT NULL DEFAULT 'active', retired_reason TEXT, superseded_by TEXT, params_hash TEXT, needs_review INTEGER DEFAULT 0, doc_ref TEXT, scanner_file TEXT, version TEXT, updated_at TEXT, updated_by TEXT )""") # holding_strategies 加列(幂等) cols = [c[1] for c in conn.execute("PRAGMA table_info(holding_strategies)").fetchall()] for col, ddl in [ ("strategy_provenance", "TEXT"), ("strategy_state", "TEXT DEFAULT 'active'"), ("strategy_attributed", "TEXT"), ]: if col not in cols: conn.execute(f"ALTER TABLE holding_strategies ADD COLUMN {col} {ddl}") print(f" + holding_strategies.{col} 已加") else: print(f" = holding_strategies.{col} 已存在") # 策略种子卡(从 scanner 头部 + docs + 实盘参数提炼,2026-08-18) SEEDS = [ dict( strategy_name="accumulation", display_name="主力建仓期捕捉", aliases=json.dumps(["accumulation", "acc"]), market="A", regime="all", family="accumulation", summary="捕捉主力资金建仓期的股票:价格20日区间中下段、量能放大、连续增量,基本面安全(PB<2或PE合理)。", entry_logic="价格处于20日中下段;成交量放大>50%且连续多日增量;小涨或平盘吸筹形态;基本面PB<2或PE合理(防暴雷)。", exit_logic="建仓结束信号(放量滞涨/主力流出)或价格突破主升浪后分批止盈;跌破建仓成本区下沿止损。", key_params=json.dumps({"vol_ratio": 1.5, "pos": "20d_mid_low", "pb_max": 2.0}), review_focus="①主力是否仍在吸筹(量能/资金流)②是否已进入拉升阶段(建仓逻辑可能已结束)③放量滞涨风险④止损位是否仍合理", holding_style="波段/中期(建仓期到拉升期)", status="active", version="v1", scanner_file="accumulation_scanner.py", doc_ref="docs/strategy-current-state-20260811.md", ), dict( strategy_name="b_td1_v3", display_name="B组超跌·原池优选", aliases=json.dumps(["b_td1_v3", "b_td1"]), market="A", regime="trend_down,choppy", family="oversold", summary="弱市/震荡市买入小市值+低估值+深超跌股(mcap_q<0.3、pe_q<0.3、bias60<-20、rsi<40、距20日低点>5%),等反弹至+15%。", entry_logic="mcap_q<0.3 且 pe_q<0.3(小市值低估值);bias60<-20(比MA60低20%+);rsi<40(超卖);dist_lo20>5(距20日低点>5%);评分=超跌深度+超卖程度+5日急跌。", exit_logic="止盈 +15%(target=close×1.15);止损 -8%(stop=close×0.92);温区 trend_up 时不再开新仓。", key_params=json.dumps({"mcap_q_max": 0.3, "pe_q_max": 0.3, "bias60_max": -20, "rsi_max": 40, "dist_lo20_min": 5, "tp_pct": 0.15, "sl_pct": 0.08, "regime": ["trend_down","choppy"]}), review_focus="①超卖是否修复(RSI/bias60 回到区间外=反弹逻辑兑现)②反弹动能是否还在(量价)③距+15%止盈的空间④原入场条件是否仍成立(若已反弹出超跌区→策略失效)", holding_style="波段(超跌反弹,快进快出)", status="active", version="v4", scanner_file="b_td1_v3_scanner.py", doc_ref="", ), dict( strategy_name="v_mr", display_name="弱市深超跌均值回复", aliases=json.dumps(["v_mr", "v_weak"]), market="A", regime="trend_down,choppy", family="mean_reversion", summary="弱市(大盘MA20下)+ADX甜区时买入深超跌(bias60∈[-35,-20))、极度超卖(RSI≤25)、5日急跌、距20日低点<5%的票,等均值回复。", entry_logic="bias60∈[-35,-20)(深超跌不过深);RSI≤25(极度超卖);r5f≤-3%(5日急跌);dist_lo20<5%(下方有承接);信号日收阳+缩量。", exit_logic="止盈 +30%;止损 -12%;最长持有 40 交易日(RR 2.5:1)。", key_params=json.dumps({"bias60_range": [-35,-20], "rsi_max": 25, "r5f_max": -3, "dist_lo20_max": 5, "tp_pct": 0.30, "sl_pct": 0.12, "max_hold_days": 40}), review_focus="①超卖是否修复②反弹动能③距止损/止盈空间④原入场条件是否仍成立(反弹出区间→失效)⑤是否超最长持有期", holding_style="波段(弱市均值回复,最长40交易日)", status="active", version="v_weak", scanner_file="mr_scanner.py", doc_ref="docs/v_mr_strategy.md", ), dict( strategy_name="hk_pe_mom", display_name="港股低PE+小市值+行业动量", aliases=json.dumps(["hk_pe_mom"]), market="HK", regime="trend_up", family="momentum", summary="港股通 trend_up 温区:买入低PE+小市值+行业动量强势的港股,追涨不追高。", entry_logic="低PE(估值便宜);小市值;行业动量强(sec_ret20>0);价格处于上升趋势。", exit_logic="动量衰减/跌破趋势线止损;止盈按行业压力位分批。", key_params=json.dumps({"regime": "trend_up"}), review_focus="①动量是否延续(行业/个股趋势)②温区是否从 trend_up 切换(动量策略主战场变了)③估值是否仍低④止损位合理性", holding_style="趋势跟踪(动量延续期)", status="active", version="v2", scanner_file="hk_scanner.py", doc_ref="", ), dict( strategy_name="hk_pe_oversold", display_name="港股低PE超卖反弹", aliases=json.dumps(["hk_pe_oversold", "hk_mr1"]), market="HK", regime="choppy", family="oversold", summary="港股通 choppy 温区:买入低PE+超卖(RSI低)的港股,等超卖修复反弹。", entry_logic="低PE;超卖(RSI低/深回调);choppy 温区(震荡市超卖反弹逻辑)。", exit_logic="超卖修复/反弹至压力位分批止盈;跌破支撑止损。", key_params=json.dumps({"regime": "choppy"}), review_focus="①超卖是否修复②温区是否仍 choppy③反弹空间④止损合理性", holding_style="波段(超卖修复)", status="active", version="v2", scanner_file="hk_scanner.py", doc_ref="", ), dict( strategy_name="p_oversold", display_name="预测超跌反弹", aliases=json.dumps(["p_oversold"]), market="A", regime="trend_down,choppy", family="oversold", summary="弱市/恐慌期买入深度超跌+低估值+小市值+有新闻的票(mcap_q<0.2、pe_q<0.2、bias60<-20、行业20日动量弱),等反弹。", entry_logic="mcap_q<0.2(小市值);pe_q<0.2(低估值);bias60<-20(深超跌);sec_ret20<0(行业弱);news3≥1(有新闻催化)。", exit_logic="支撑位下方5%止损;压力位分批止盈;40交易日强平。", key_params=json.dumps({"mcap_q_max": 0.2, "pe_q_max": 0.2, "bias60_max": -20, "sec_ret20_max": 0, "news_min": 1, "max_hold_days": 40}), review_focus="①超跌是否修复②消息催化是否兑现③行业20日动量是否转强④40日强平窗口⑤止损/止盈空间", holding_style="波段(超跌+催化反弹)", status="active", version="v5", scanner_file="predictive_oversold_scanner.py", doc_ref="docs/predictive_oversold_strategy.md", ), dict( strategy_name="s2_panic", display_name="恐慌日买强势", aliases=json.dumps(["s2_panic", "s2_panic_v2"]), market="A", regime="trend_down", family="panic", summary="恐慌日(大盘RSI超卖)买入小市值+高RSI+行业抗跌的强势股(mcap_q<0.4、rsi≥35、sec_ret20≥-10),恐慌错杀中的强势alpha。", entry_logic="大盘 RSI<25(恐慌日门控);mcap_q<0.4(小市值);rsi≥35(个股强势);sec_ret20≥-10(行业抗跌)。", exit_logic="止盈 +30%;止损 -12%;最长持有 60 交易日。", key_params=json.dumps({"mkt_rsi_max": 25, "mcap_q_max": 0.4, "rsi_min": 35, "sec_ret20_min": -10, "tp_pct": 0.30, "sl_pct": 0.12, "max_hold_days": 60}), review_focus="①恐慌情绪是否缓解(大盘RSI修复→策略逻辑失效)②个股强势是否延续③行业抗跌性④止损/止盈空间⑤是否超60日持有期", holding_style="波段(恐慌错杀修复)", status="active", version="v2", scanner_file="s2_panic_v2_scanner.py", doc_ref="", ), dict( strategy_name="leader", display_name="龙头回调买点", aliases=json.dumps(["leader", "bull_trend"]), market="A", regime="trend_up", family="momentum", summary="牛市(trend_up)买入龙头股回调到 MA20 附近的健康回调买点(close>MA20、dist_ma20∈[-3%,+2%]、行业强势、市值>0.3分位),不追高。", entry_logic="close>MA20(趋势向上);dist_ma20∈[-3%,+2%](回调到MA20附近);sector_above_ma20=1 或 sector_ret20>0(行业强势);mcap_q>0.3(龙头非小盘)。", exit_logic="突破前高后趋势延续持有;跌破MA20/趋势线止损;行业转弱退出。", key_params=json.dumps({"regime": "trend_up", "dist_ma20_range": [-3,2], "mcap_q_min": 0.3}), review_focus="①牛市温区是否延续(trend_up→其他=龙头策略失效)②回调买点是否仍成立③行业强度④止损位(跌破MA20)合理性", holding_style="趋势持有(龙头主升)", status="active", version="v1", scanner_file="leader_scanner.py", doc_ref="", ), ] def seed(conn): for s in SEEDS: hp = hashlib.md5((s.get("key_params") or "").encode()).hexdigest()[:12] old = conn.execute("SELECT params_hash FROM strategy_defs WHERE strategy_name=?", (s["strategy_name"],)).fetchone() if old and old[0] == hp: print(f" = {s['strategy_name']} 定义未变,跳过") continue s2 = dict(s) s2["params_hash"] = hp s2["updated_at"] = "2026-08-18 17:30:00" s2["updated_by"] = "setup_strategy_defs.py" cols = ",".join(s2.keys()) ph = ",".join("?" * len(s2)) conn.execute( f"INSERT INTO strategy_defs ({cols}) VALUES ({ph}) " f"ON CONFLICT(strategy_name) DO UPDATE SET " f"display_name=excluded.display_name, aliases=excluded.aliases, market=excluded.market, " f"regime=excluded.regime, family=excluded.family, summary=excluded.summary, " f"entry_logic=excluded.entry_logic, exit_logic=excluded.exit_logic, " f"key_params=excluded.key_params, review_focus=excluded.review_focus, " f"holding_style=excluded.holding_style, status=excluded.status, " f"retired_reason=excluded.retired_reason, superseded_by=excluded.superseded_by, " f"params_hash=excluded.params_hash, needs_review=excluded.needs_review, " f"doc_ref=excluded.doc_ref, scanner_file=excluded.scanner_file, " f"version=excluded.version, updated_at=excluded.updated_at, updated_by=excluded.updated_by", [s2[k] for k in s2]) print(f" + {s['strategy_name']} 种子卡已写入") def main(): conn = get_conn() print("[1/3] 建表...") create_tables(conn) print("[2/3] 种子卡...") seed(conn) conn.commit() print("[3/3] 校验...") n = conn.execute("SELECT COUNT(*) FROM strategy_defs").fetchone()[0] print(f" strategy_defs 共 {n} 条") for r in conn.execute("SELECT strategy_name, display_name, status FROM strategy_defs ORDER BY strategy_name").fetchall(): print(f" - {r[0]} | {r[1]} | {r[2]}") conn.close() print("DONE") if __name__ == "__main__": main()