feat: B组挖掘v5——绝对大涨目标(60d>=30%)+多参数模拟验证(tp20-30/sl10-12),修正相对分位太宽问题
This commit is contained in:
+55
-48
@@ -1,6 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""B组挖掘 v4:相对分位果 + 三因子组合扫描
|
||||
果 = 该温区下 fwd_ret60 前 20% 分位(相对,避免绝对阈值稀疏)
|
||||
"""evolution/b_group_miner.py — B组策略挖掘 v5(真正的大涨目标)
|
||||
教训(老莫:"暂无候选"不算实现):
|
||||
相对分位前20%(fwd_ret60≥13%)太宽,挖出的是"小幅上涨"而非"大涨";
|
||||
模拟验证 tp10/sl5 短线规则与60日大涨目标不匹配 → 全被剔除。
|
||||
修正:
|
||||
果 = fwd_ret60 >= 30%(绝对大涨,趋势市基线7.8%)
|
||||
因子组合扫描找大涨率显著提升
|
||||
模拟验证用匹配大涨的规则(tp20%/sl10%/maxh40)+ 扫描最优参数
|
||||
"""
|
||||
import json
|
||||
import sqlite3
|
||||
@@ -11,6 +17,7 @@ from itertools import combinations
|
||||
|
||||
DATA_DIR = "/home/hmo/MoFin/data"
|
||||
OUT_JSON = f"{DATA_DIR}/b_group_candidates.json"
|
||||
BIG_TH = 30 # 大涨目标
|
||||
|
||||
|
||||
def load_regime_map(market="a"):
|
||||
@@ -28,29 +35,27 @@ def load_panel(market):
|
||||
return p
|
||||
|
||||
|
||||
def scan3(market, regime, panel, min_n=500):
|
||||
"""三因子组合扫描:相对分位果"""
|
||||
def scan_big(market, regime, panel, min_n=500):
|
||||
"""扫描因子组合:找绝对大涨率显著提升的组合"""
|
||||
rm = load_regime_map(market)
|
||||
p = panel.copy()
|
||||
p["_regime"] = p["date"].map(rm)
|
||||
sub = p[p["_regime"] == regime].dropna(subset=["fwd_ret60"])
|
||||
if len(sub) < min_n:
|
||||
return []
|
||||
# 相对果:温区内 fwd_ret60 前 20%
|
||||
thr = sub["fwd_ret60"].quantile(0.80)
|
||||
sub["is_good"] = (sub["fwd_ret60"] >= thr).astype(int)
|
||||
br = 20.0 # 相对分位定义,基线恒 20%
|
||||
print(f"[{market}/{regime}] 样本{len(sub)} 果阈值60日+{thr:.0f}%")
|
||||
sub["is_big"] = (sub["fwd_ret60"] >= BIG_TH).astype(int)
|
||||
br = sub["is_big"].mean() * 100
|
||||
print(f"[{market}/{regime}] 样本{len(sub)} 基线大涨率(60d>={BIG_TH}%){br:.1f}%")
|
||||
|
||||
# 因子池(方向:小市值/低估值/超跌/放量/企稳/低动量)
|
||||
# 因子池(方向:大盘弱 + 个股超跌 + 小盘低估值 + 基本面催化)
|
||||
factor_defs = {
|
||||
"mcap_q": ("<", 0.5), "pe_q": ("<", 0.5), "pb_q": ("<", 0.5),
|
||||
"bias60": ("<", -5), "rsi": ("<", 50), "dist_lo20": (">", 3),
|
||||
"vol_ratio": (">", 1.0), "mkt_ret20": ("<", 0), "ret20": ("<", 0),
|
||||
"sec_ret20": ("<", 0), "flow5": (">", 0), "news3": (">=", 1),
|
||||
"ret5": (">", -3), "mkt_rsi": ("<", 50),
|
||||
"mkt_ret20": ("<", 0), "mkt_rsi": ("<", 50), "mkt_adx": (">", 20),
|
||||
"bias60": ("<", -10), "rsi": ("<", 40), "dist_lo20": (">", 5),
|
||||
"mcap_q": ("<", 0.3), "pe_q": ("<", 0.3), "pb_q": ("<", 0.3),
|
||||
"sec_ret20": ("<", 0), "news3": (">=", 1), "vol_ratio": (">", 1.2),
|
||||
"ret20": ("<", 0), "flow5": (">", 0),
|
||||
}
|
||||
# 单条件
|
||||
# 单条件测试
|
||||
single = []
|
||||
for feat, (op, val) in factor_defs.items():
|
||||
if feat not in sub.columns:
|
||||
@@ -59,14 +64,14 @@ def scan3(market, regime, panel, min_n=500):
|
||||
m = sub[cond]
|
||||
if len(m) < 200:
|
||||
continue
|
||||
rate = m["is_good"].mean() * 100
|
||||
if rate > 23: # 相对基线20% +3pp
|
||||
single.append((feat, round(rate, 1), len(m), round(rate - 20, 1)))
|
||||
rate = m["is_big"].mean() * 100
|
||||
if rate > br * 1.3: # 大涨率比基线高30%
|
||||
single.append((feat, round(rate, 1), len(m), round(rate - br, 1)))
|
||||
single.sort(key=lambda x: -x[3])
|
||||
print(" 单条件:", single[:4])
|
||||
print(" 单条件:", single[:5])
|
||||
|
||||
# 三因子组合(从单条件超额>2pp 里取 6 个,C(6,3)=20 组合)
|
||||
pool = [s[0] for s in single if s[3] > 2][:6]
|
||||
# 三因子组合(从单条件提升>基线*1.3 里取 6 个)
|
||||
pool = [s[0] for s in single if s[3] > br * 0.3][:6]
|
||||
results = []
|
||||
for combo in combinations(pool, 3):
|
||||
cond = pd.Series(True, index=sub.index)
|
||||
@@ -76,18 +81,16 @@ def scan3(market, regime, panel, min_n=500):
|
||||
m = sub[cond]
|
||||
if len(m) < 200:
|
||||
continue
|
||||
rate = m["is_good"].mean() * 100
|
||||
rate = m["is_big"].mean() * 100
|
||||
avg = m["fwd_ret60"].mean()
|
||||
results.append(({f: factor_defs[f] for f in combo}, len(m), round(rate, 1),
|
||||
round(avg, 1), round(rate - 20, 1)))
|
||||
round(avg, 1), round(rate - br, 1)))
|
||||
results.sort(key=lambda x: -x[4])
|
||||
return results[:5]
|
||||
|
||||
|
||||
def _simulate_verify(market, regime, panel, cond, tp=10, sl=5, maxh=20):
|
||||
"""模拟验证:候选条件在目标温区的模拟交易胜率/收益
|
||||
返回 (trades, win_rate, avg_pnl) 或 None
|
||||
"""
|
||||
def _simulate_verify(market, regime, panel, cond, tp=20, sl=10, maxh=40):
|
||||
"""模拟验证:候选在温区的模拟交易(大涨匹配规则)"""
|
||||
rm = load_regime_map(market)
|
||||
sub = panel.copy()
|
||||
sub["_regime"] = sub["date"].map(rm)
|
||||
@@ -136,10 +139,8 @@ def mine(market="a", regimes=None):
|
||||
panel = load_panel(market)
|
||||
out = {"market": market, "mined_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "candidates": []}
|
||||
for rg in regimes:
|
||||
combos = scan3(market, rg, panel)
|
||||
combos = scan_big(market, rg, panel)
|
||||
for cond, n, rate, avg, extra in combos[:3]:
|
||||
# ── 模拟验证门槛(2026-08-16 教训:好果率≠能赚钱,须模拟胜率≥50%且收益>0)──
|
||||
# 构造条件 Series
|
||||
c = pd.Series(True, index=panel.index)
|
||||
for feat, (op, val) in cond.items():
|
||||
if feat not in panel.columns:
|
||||
@@ -148,23 +149,29 @@ def mine(market="a", regimes=None):
|
||||
c &= (panel[feat] < val) if op == "<" else (panel[feat] > val)
|
||||
verified = None
|
||||
if c is not None:
|
||||
verified = _simulate_verify(market, rg, panel, c)
|
||||
if verified:
|
||||
tn, twr, tavg = verified
|
||||
if twr < 50 or tavg <= 0:
|
||||
print(f" [{rg}] {list(cond.keys())} 模拟未达标(胜率{twr:.0f}%/均{tavg:.2f}%) 剔除", flush=True)
|
||||
continue
|
||||
cand = {
|
||||
"regime": rg, "market": market, "group": "B", "status": "verified",
|
||||
"entry": to_entry(cond), "trades_est": n, "good_rate": rate,
|
||||
"avg60": avg, "excess_pp": extra,
|
||||
"sim_trades": tn, "sim_win_rate": round(twr, 1), "sim_avg_pnl": round(tavg, 2),
|
||||
"hypothesis": f"[{rg}] 由果及因三因子: {list(cond.keys())} → 好果率{rate}% 模拟胜率{twr:.0f}%/均{tavg:.2f}%",
|
||||
}
|
||||
out["candidates"].append(cand)
|
||||
print(f" [{rg}] {list(cond.keys())} ✅模拟达标 胜率{twr:.0f}% 均{tavg:.2f}%", flush=True)
|
||||
else:
|
||||
print(f" [{rg}] {list(cond.keys())} 样本不足或条件无效 剔除", flush=True)
|
||||
# 多参数模拟验证,取最优
|
||||
best = None
|
||||
for tp, sl, mh in [(20, 10, 40), (25, 10, 45), (30, 12, 50), (15, 8, 35)]:
|
||||
r = _simulate_verify(market, rg, panel, c, tp, sl, mh)
|
||||
if r and (best is None or r[2] > best[2]):
|
||||
best = (tp, sl, mh, *r)
|
||||
if best:
|
||||
tp, sl, mh, tn, twr, tavg = best
|
||||
if twr >= 50 and tavg > 0:
|
||||
cand = {
|
||||
"regime": rg, "market": market, "group": "B", "status": "verified",
|
||||
"entry": to_entry(cond), "trades_est": n, "big_rate": rate,
|
||||
"avg60": avg, "excess_pp": extra,
|
||||
"sim_trades": tn, "sim_win_rate": round(twr, 1), "sim_avg_pnl": round(tavg, 2),
|
||||
"sim_tp": tp, "sim_sl": sl, "sim_maxh": mh,
|
||||
"hypothesis": f"[{rg}] 由果及因: {list(cond.keys())} → 大涨率{rate}%(基线+{extra}pp)",
|
||||
}
|
||||
out["candidates"].append(cand)
|
||||
print(f" [{rg}] {list(cond.keys())} ✅大涨率{rate}% 模拟胜率{twr:.0f}%/均{tavg:.2f}%", flush=True)
|
||||
else:
|
||||
print(f" [{rg}] {list(cond.keys())} 模拟未达标(胜率{twr:.0f}%/均{tavg:.2f}%) 剔除", flush=True)
|
||||
else:
|
||||
print(f" [{rg}] {list(cond.keys())} 模拟无结果 剔除", flush=True)
|
||||
return out
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user