From 6aff142eb2605aa823c92e33a7853b0834a4b221 Mon Sep 17 00:00:00 2001 From: xxm Date: Sun, 16 Aug 2026 14:52:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20B=E7=BB=84=E6=8C=96=E6=8E=98=E5=8A=A0?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E9=AA=8C=E8=AF=81=E9=97=A8=E6=A7=9B=E2=80=94?= =?UTF-8?q?=E2=80=94=E5=A5=BD=E6=9E=9C=E7=8E=87=E6=8F=90=E5=8D=87=E9=A1=BB?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E6=A8=A1=E6=8B=9F=E4=BA=A4=E6=98=93(?= =?UTF-8?q?=E8=83=9C=E7=8E=87=E2=89=A550%/=E6=94=B6=E7=9B=8A>0)=E6=89=8D?= =?UTF-8?q?=E7=AE=97=E5=80=99=E9=80=89,=E9=98=B2=E6=AD=A2=E6=89=AB?= =?UTF-8?q?=E6=8F=8F=E5=99=AA=E5=A3=B0=E5=BD=93=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- evolution/b_group_miner.py | 74 +++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/evolution/b_group_miner.py b/evolution/b_group_miner.py index 6325cfe9..d4f951af 100644 --- a/evolution/b_group_miner.py +++ b/evolution/b_group_miner.py @@ -84,6 +84,45 @@ def scan3(market, regime, panel, min_n=500): return results[:5] +def _simulate_verify(market, regime, panel, cond, tp=10, sl=5, maxh=20): + """模拟验证:候选条件在目标温区的模拟交易胜率/收益 + 返回 (trades, win_rate, avg_pnl) 或 None + """ + rm = load_regime_map(market) + sub = panel.copy() + sub["_regime"] = sub["date"].map(rm) + sub = sub[(sub["_regime"] == regime) & cond].copy() + if len(sub) < 200: + return None + sub = sub.sort_values(["code", "date"]) + trades = [] + for code, g in sub.groupby("code"): + g = g.sort_values("date") + idxs = list(g.index) + for k, i in enumerate(idxs): + fut = g.iloc[k+1:k+maxh+1] + if len(fut) < 2: + continue + ep = g.loc[i, "close"] + if ep <= 0: + continue + res = None + for _, fb in fut.iterrows(): + if fb["close"] <= ep * (1 - sl / 100): + res = -sl + break + if fb["close"] >= ep * (1 + tp / 100): + res = tp + break + if res is None: + res = (fut.iloc[-1]["close"] / ep - 1) * 100 + trades.append(res) + if not trades: + return None + wins = [x for x in trades if x > 0] + return len(trades), len(wins) / len(trades) * 100, sum(trades) / len(trades) + + def to_entry(cond_dict): entry = {} for feat, (op, val) in cond_dict.items(): @@ -99,14 +138,33 @@ def mine(market="a", regimes=None): for rg in regimes: combos = scan3(market, rg, panel) for cond, n, rate, avg, extra in combos[:3]: - cand = { - "regime": rg, "market": market, "group": "B", "status": "candidate", - "entry": to_entry(cond), "trades_est": n, "good_rate": rate, - "avg60": avg, "excess_pp": extra, - "hypothesis": f"[{rg}] 由果及因三因子: {list(cond.keys())} → 60日前20%占比{rate}%(超额+{extra}pp)", - } - out["candidates"].append(cand) - print(f" [{rg}] {list(cond.keys())} n={n} 好果率{rate}% 超额+{extra}pp") + # ── 模拟验证门槛(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: + c = None + break + 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) return out