Files
MoFin/deploy/profile-scripts/setup_strategy_defs.py
T

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