235 lines
13 KiB
Python
235 lines
13 KiB
Python
#!/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()
|