"""Step 38:point-in-time 重建——回测的中枢,在当时那一刻真的存在吗。 incremental.py 开头写明「最后一笔 is_sure 允许收回」。而中枢生效时刻 available_ts 取的正是笔的 sure_time,那是全量重算得出的。 此前所有审计(后移入场、剔尾、多空、时间样本外、跨品种)都共用同一份 全量结构,无法发现这类偏差。 本步对每个信号做严格的时点重建:只喂到信号那一根为止的数据, 重跑 TF_DF -> 中枢 -> fast_bsp3,看该信号是否真的在那一刻出现。 召回 全量口径的信号,在时点口径下同一根也出现 偏移 出现了但不在同一根(早/晚几根) 消失 时点口径下完全没有 另外在随机非信号点上做同样重建,量化时点口径的假阳性—— 那是实盘会真的下单、而回测里根本不存在的交易。 """ from __future__ import annotations import argparse import os import sys import warnings from concurrent.futures import ProcessPoolExecutor, as_completed from pathlib import Path import numpy as np import pandas as pd warnings.filterwarnings("ignore") for v in ("OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS"): os.environ.setdefault(v, "1") HERE = Path(__file__).resolve().parent sys.path.insert(0, str(HERE)) sys.path.insert(0, str(HERE.parent)) pd.set_option("display.width", 320) BEST = {"15m": "1h", "30m": "2h"} WINDOW = 4000 # 时点重建时回看多少根,实盘也不会带六年历史 TOLERANCE = 3 # 判定「同一时刻」允许的根数偏移 def pit_signals(df_slice: pd.DataFrame, ltf: str) -> set[int]: """只用给定切片重建,返回该切片内的信号在切片中的下标集合。""" from chanlun import TF_DF from lib.fast_bsp3 import find_fast_bsp3 from lib.nested_level import build_htf_zones chan = TF_DF(df_slice, 1, ltf) cdf = chan.dataframe zones = build_htf_zones(cdf, ltf, chan=chan) if zones.empty: return set() sig = find_fast_bsp3(cdf, zones.reset_index(drop=True)) if sig.empty: return set() return set(sig["entry_idx"].astype(int).tolist()) def run_one(task: tuple) -> dict | None: import warnings as _w _w.filterwarnings("ignore") sys.path.insert(0, str(HERE)) sys.path.insert(0, str(HERE.parent)) from chanlun import TF_DF from lib.data import fetch_ohlcv from lib.fast_bsp3 import find_fast_bsp3 from lib.fx_signal import extract_fx_signals, signals_to_frame from lib.nested_bsp import attach_htf_context, htf_fx_timeline from lib.nested_level import build_htf_zones sym, ltf, n_probe = task pair = f"{sym}/USDT:USDT" try: df_l = fetch_ohlcv(pair, ltf, 10**9) if df_l is None or len(df_l) < 3000: return None chan_l = TF_DF(df_l, 1, ltf) cdf = chan_l.dataframe zones = build_htf_zones(cdf, ltf, chan=chan_l).reset_index(drop=True) if zones.empty: return None z = zones.copy() pg, pdn = z["zg"].shift(), z["zd"].shift() z["z_above"], z["z_below"] = z["zd"] > pg, z["zg"] < pdn z["zone_i"] = np.arange(len(z)) df_h = fetch_ohlcv(pair, BEST[ltf], 10**9) chan_h = TF_DF(df_h, 1, BEST[ltf]) s = signals_to_frame(extract_fx_signals(chan_h, chan_h.dataframe)) tl = htf_fx_timeline(s, chan_h.dataframe) # 全量口径(回测用的那一份) full = find_fast_bsp3(cdf, zones) full = full.merge(z[["zone_i", "z_above", "z_below"]], on="zone_i", how="left") full = attach_htf_context(full, cdf, tl, "h1") full["push"] = np.where(full["direction"] == 1, full["z_above"], full["z_below"]) fin = full[(full["h1_agree"] == 1) & full["push"].fillna(False).astype(bool)] idx_all = fin["entry_idx"].astype(int).to_numpy() idx_all = idx_all[idx_all >= WINDOW] if len(idx_all) < 20: return None rng = np.random.default_rng(7) probe = (rng.choice(idx_all, size=min(n_probe, len(idx_all)), replace=False) if len(idx_all) > n_probe else idx_all) rows = [] for i in sorted(probe): sl = cdf.iloc[i - WINDOW + 1: i + 1].reset_index(drop=True) pit = pit_signals(sl, ltf) last = len(sl) - 1 # 信号那一根在切片中的位置 hit_exact = last in pit near = [p - last for p in pit if abs(p - last) <= TOLERANCE] rows.append({"idx": int(i), "exact": hit_exact, "near": bool(near), "shift": min(near, key=abs) if near else np.nan}) # 假阳性:随机非信号点,看时点口径是否在当根给出信号 pool = np.setdiff1d(np.arange(WINDOW, len(cdf) - 1), idx_all) fp_probe = rng.choice(pool, size=min(len(probe), len(pool)), replace=False) fp = 0 for i in sorted(fp_probe): sl = cdf.iloc[i - WINDOW + 1: i + 1].reset_index(drop=True) if (len(sl) - 1) in pit_signals(sl, ltf): fp += 1 return {"task": f"{sym} {ltf}", "sym": sym, "ltf": ltf, "probe": pd.DataFrame(rows), "n_full": len(idx_all), "fp": fp, "n_fp": len(fp_probe)} except Exception as e: return {"task": f"{sym} {ltf}", "error": repr(e)[:250]} def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--symbols", default="BTC,ETH,SOL,BNB,ADA,LINK") ap.add_argument("--probe", type=int, default=120) ap.add_argument("--workers", type=int, default=6) args = ap.parse_args() syms = [s.strip() for s in args.symbols.split(",")] tasks = [(s, l, args.probe) for l in BEST for s in syms] print(f"[时点重建] {len(tasks)} 个任务 × 每个抽 {args.probe} 个信号" f"(窗口 {WINDOW} 根)\n", flush=True) res = [] with ProcessPoolExecutor(max_workers=args.workers) as ex: futs = {ex.submit(run_one, t): t for t in tasks} for i, f in enumerate(as_completed(futs), 1): r = f.result() if r is None or "error" in (r or {}): print(f" [{i}] 跳过 {(r or {}).get('error', '')}", flush=True) continue res.append(r) p = r["probe"] print(f" [{i}/{len(tasks)}] {r['task']} 命中 " f"{p['exact'].mean() * 100:.0f}% / 容差内 {p['near'].mean() * 100:.0f}%", flush=True) if not res: return print("\n" + "=" * 104) print("########## 1. 全量口径的信号,在时点口径下还在吗 ##########") rows = [] for r in res: p = r["probe"] rows.append({"品种": r["sym"], "级别": r["ltf"], "全量信号数": r["n_full"], "抽检": len(p), "同根命中": f"{p['exact'].mean() * 100:.1f}%", f"±{TOLERANCE}根内": f"{p['near'].mean() * 100:.1f}%", "完全消失": f"{(~p['near']).mean() * 100:.1f}%", "中位偏移": (f"{np.nanmedian(p['shift']):+.1f}" if p["near"].any() else "—")}) d = pd.DataFrame(rows).sort_values(["级别", "品种"]) print(d.to_string(index=False)) print("\n########## 2. 分级别汇总 ##########") for ltf in BEST: sub = [r for r in res if r["ltf"] == ltf] if not sub: continue p = pd.concat([r["probe"] for r in sub], ignore_index=True) print(f" {ltf}: 抽检 {len(p)} 同根命中 {p['exact'].mean() * 100:.1f}% " f"±{TOLERANCE}根内 {p['near'].mean() * 100:.1f}% " f"完全消失 {(~p['near']).mean() * 100:.1f}%") print("\n########## 3. 假阳性:实盘会下、回测里没有的单 ##########") rows = [] for r in res: rows.append({"品种": r["sym"], "级别": r["ltf"], "抽检非信号点": r["n_fp"], "当根却给信号": r["fp"], "假阳性率": f"{r['fp'] / max(r['n_fp'], 1) * 100:.1f}%"}) print(pd.DataFrame(rows).sort_values(["级别", "品种"]).to_string(index=False)) p_all = pd.concat([r["probe"] for r in res], ignore_index=True) fp_tot = sum(r["fp"] for r in res) fp_n = sum(r["n_fp"] for r in res) print("\n########## 结论 ##########") print(f" 同根命中 {p_all['exact'].mean() * 100:.1f}%," f"±{TOLERANCE}根内 {p_all['near'].mean() * 100:.1f}%," f"完全消失 {(~p_all['near']).mean() * 100:.1f}%," f"假阳性 {fp_tot / max(fp_n, 1) * 100:.1f}%") print(" 同根命中率若接近 100%,说明结构在当时就已确定,回测口径可信;") print(" 若大量消失或偏移,则回测收益里有一部分实盘永远拿不到。") if __name__ == "__main__": main()