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