OOS-Gauntlet 数据驱动审计方法
一套用于"决定要不要改策略"的证伪流程。核心一句话:任何"改动会变好"的假说, 先用它自己的数据尽力推翻它,推翻不掉才部署。 2026-06-05 整理,源自黑名单审计 / WLD 调查 / range_break_probe 关闭等一系列实战。
1. 背景:为什么需要这套方法
量化策略的每一次改动——加黑名单、加 gate 、关闭某个 mode 、按某指标缩 sizing—— 背后都是一个假说:"这样改,整体会变好"。这些假说往往来自对历史数据的观察: 某个币一直亏、某个信号胜率低、某个 confidence 区间表现差。
陷阱在于:历史数据里看到的"规律",大部分是假的。 它可能是:
- 单日/单时段的运气:某币在某一天爆亏 -24,看起来很差,但那是当天的市场事件,不可外推。
- 单个 symbol 的巧合:某"类型"的币赚钱,拆开发现整类只有 1 个币在赚,其余在亏。
- 聚合数字的假象:"全市场关闭 X 改善 +98%",拆到 symbol 级发现主流币其实在赚,+98% 是被少数大亏 symbol 拉出来的。
- 不携带 edge 的维度:confidence 看起来高就该赚,但 held-out 测试发现 confidence 根本不 rank edge(甚至反向)。
这个项目反复栽在这些坑里(都有记录):confidence floor 假说被双轮 OOS 证伪、 funding-squeeze 信号三重收敛却死于 90 天全市场 OOS 、多个"利润集中"结论其实是 STG/LAB 两个币的尾部运气。每一次"我从数据里发现了规律"的兴奋,都需要先被 怀疑。 这套方法就是把怀疑系统化。
2. 核心方法:OOS-Gauntlet(样本外多重关卡)
OOS = Out-Of-Sample(样本外)。Gauntlet = 一连串必须逐个通过的关卡。一个假说要 活下来,必须连续通过下面每一关——任何一关翻车,假说就被否决。
假说(从 in-sample 数据挖出)
│
├─ 关卡 1: in-sample 时段分布 —— 亏损/盈利是集中单日(regime),还是跨多日(系统性)?
│
├─ 关卡 2: OOS 多窗口 —— 换 2-3 个独立时段(fetch --end-date)重测,方向一致吗?
│
├─ 关卡 3: 多 symbol —— 同一结论在 ≥3 个不同 symbol 上都成立,还是单 symbol 假象?
│
├─ 关卡 4: 对照基准 —— 同期 crypto 对照(SOL/AVAX)是什么表现?排除大盘 regime 。
│
├─ 关卡 5: 拆穿聚合 —— 整体数字拆到 symbol/时段级,赢家是不是少数尾部撑的?
│
└─ 关卡 6: 端到端 live 验证 —— 部署后真在 live 生效吗?(不只单元测/backtest)
关键原则:每加一关,样本就更"样本外"一点。 in-sample 是你挖假说的那段数据 (必然支持假说,因为假说是从它挖的);OOS 多窗口排除时段运气;多 symbol 排除单币 巧合;对照排除大盘;拆聚合排除尾部;端到端排除"代码根本没生效"。
3. 用了哪些模块的数据,为什么这样用
| 数据源 | 是什么 | 在方法里的角色 | 为什么用它 |
|---|---|---|---|
reports/runtime/income_ledger/ |
交易所返回的真实已实现 PnL(REALIZED_PNL 事件) | 实盘真值:某币/某 mode 真实赚亏 | 唯一不掺假的真金白银;但必须聚合回 trade(事件级会被分批止盈骗) |
reports/runtime/shadow_trades/ |
影子交易:被 gate block / HOLD 的信号的 counterfactual(假如开了会怎样)的模拟 PnL | 黑名单/关闭候选的"假如交易"依据 | 黑名单币不实盘→信号被 block→shadow 记录"假如不黑会怎样",这是判断"该不该解黑"的直接证据 |
reports/runtime/decision_journal/ |
每个决策的完整 metadata(regime/strategy_mode/confidence/各质量指标/git_sha/blocked_by) | 归因 + live 验证:某改动 live 是否生效、决策按 git_sha 分布 | 含 git_sha,能区分"部署前 vs 部署后"的决策,做 live A/B;含丰富质量指标供 rank-edge 分析 |
reports/backtests/*/trades.csv |
回测逐笔成交(symbol/side/pnl/confidence/regime/strategy_mode/entry_time) | OOS 多窗口 + 多 symbol 的主力:可控时段(--end-date)、可控 flag(env) | 一体记录"入场条件 + 出场 PnL",无手动平仓污染,可批量跑多 symbol × 多时段 |
Binance exchangeInfo(underlyingType / underlyingSubType) |
交易所对每个 symbol 的官方分类(EQUITY/COIN/COMMODITY / DeFi/AI/Layer-1/Infrastructure...) | 跨 symbol 归类:把单 symbol 结论提升到"类型"维度验证 | 当内部 symbol_group 全是 default 无区分力时,交易所的官方分类提供了正交的归类维度(但要防单 symbol 假象) |
为什么 shadow 和 income 要分开看: income 是实盘开了仓的真值,shadow 是被拦下来 的 counterfactual 。黑名单币不实盘(income 没数据),只能靠 shadow 判断"解黑后会怎样"; 而"该不该加黑"要看实盘 income + backtest 。两者互补,缺一不可。
为什么必须聚合回 trade: income_ledger 是 REALIZED_PNL 事件流,一笔仓分批止盈 会产生多个事件。如果按事件算胜率/payoff,会被"小额分批止盈"骗出虚高胜率。必须 按 symbol+时间窗聚合回 trade 级再统计。
4. 两个具体事例
事例 A:DRIFT 该不该加黑名单 —— in-sample 假阳性被 OOS 推翻
假说(从 in-sample 挖出): DRIFT 是黑名单候选。shadow 看它 -16.5 、实盘 income -8.5,合计昨日 -24.6,是当天最差的 symbol,看起来铁定该黑。
逐关验证:
- 关卡 1(时段分布): DRIFT 的 -24.6 全部集中在 06-04 单日,其余日期几乎无活动 → 强烈的单日 regime 信号,不像系统性。
-
关卡 2(OOS 多窗口): 用
fetch --end-date拉两个独立时段回测 DRIFT:- OOS 窗口 1(04-30~05-10):+13.15
- OOS 窗口 2(04-05~04-15):+90.66
- 两个样本外时段 DRIFT 都大赚!
- 结论: in-sample 的 -24.6 是 06-04 单日市场事件,不可外推。加黑名单会错失 OOS 那 +90 的盈利。DRIFT 不加黑。
这就是 in-sample 假阳性的典型:单日爆亏被误读成"这币系统性差"。如果只看挖假说 的那一天就拍板,会做出完全错误的决定。
事例 B:range_break_probe 该不该关闭 —— subType 单 symbol 假象被多 symbol OOS 拆穿
假说演进(层层深入): range_break_probe(RANGING regime + 均值回归失败后转去追
区间突破)整体负 EV,该关。但"整体关闭"在某些 symbol 上反而亏(主流币的 probe 在
赚),于是怀疑 EV 是 symbol 依赖的。进一步用 Binance underlyingSubType 拆,发现:
| subType | in-sample(少 symbol) | 看起来 |
|---|---|---|
| Infrastructure | +29(wr75%) | 赚!该保留 |
| Layer-1 | +20(wr57%) | 赚!该保留 |
看起来可以做"subType-aware gate":只关亏的 subType,保留赚的。但加一关:
-
关卡 3(多 symbol): Infrastructure 之前的 +29 全部来自 LINK 一个 symbol。
换成多 symbol(BAT/LINK/MASK)跑 OOS:
- Infrastructure: +29 → -32(wr 0%) ← 完全反转!
- Layer-1: +20 → +0.4(breakeven) ← edge 消失
- 所有 subType 多 symbol OOS 全部亏或打平。
- 结论: "subType 区分"是单 symbol 假象——Infrastructure=LINK 、AI=WLD 、 DeFi=DRIFT,每类只 1 个 symbol 时,"subType 信号"其实是"那个 symbol 的信号" 伪装的。range_break_probe 普遍负 EV(概念性:RANGING 追假突破),全局关闭。
这是事例 A 的同一个陷阱换了维度:单日 → 单 symbol 。用户的洞察(用交易所 subType 数据细分)方向对,但必须配多 symbol OOS,否则会被单 symbol 假象骗去做一个 错误的 subType gate 。
5. "OOS 一致"的作用:照妖镜
整套方法的判据可以浓缩成一句话:
跨窗口、跨 symbol 一致 = 真信号;一翻转/只靠单点 = 方差假象。
- DRIFT:in-sample -24.6 但 OOS 两窗口 +13/+90 → 不一致(翻转) → 假阳性,不加黑。
- BZ/NATGAS:in-sample shadow 负 + OOS-1 负 + OOS-2 负,三窗口一致 + 多 symbol + 对照 SOL 同期正 → 真系统性亏 → 加黑。
- Infrastructure:LINK 单 symbol +29,换多 symbol → -32 → 不一致 → 单 symbol 假象。
- range_break_probe:所有 subType 多 symbol OOS 一致亏 → 真普遍负 EV → 关闭。
"一致"之所以是照妖镜,是因为方差(运气)在不同时段/不同 symbol 上方向随机—— 你换个窗口、换批 symbol,运气就洗掉了,只有真实的 edge/loss 才会在所有切片上同向 保留。一个结论能在 3 个独立时段 + 3 个不同 symbol + 对照组上都同向,它是运气的概率 就极低了。
反过来,任何"只在一个时段/一个 symbol 上成立"的结论,默认当方差,不部署。 这也解释了为什么聚合数字(全市场 +98%)危险:聚合把不一致藏起来了,必须拆开看每个 切片是否一致。
6. 我是如何想到这套方法的
不是一开始就有的,是被历次失败逼出来的:
-
confidence floor 的双轮 OOS 死亡让我学到:in-sample 挖出的"好区间",必须用 pre-registered 的 held-out OOS 去打——而且要双轮(0.62 死于 OOS-1,我换 0.65 又死于 OOS-2)。一轮 OOS 不够,赢家会"刚好"过第一轮。→ 催生多窗口。
-
funding-squeeze 三重收敛却死于全市场 OOS让我学到:机制合理 + 学术支持 + 样本验证(三重收敛)都不等于 robust,横截面 demean(扣大盘)后才见真章。→ 催生 对照基准(排除大盘 regime)。
-
STG/LAB 尾部运气让我学到:"整体盈利"可能是 2 个币撑的,去掉就转负。聚合 会骗人。→ 催生拆穿聚合(拆到 symbol/时段级)。
-
momentum_tilt 的 stale-code 事故(单元测过、reviewer 过,但部署后 count 仍 0, 因为改动根本没进数据流)让我学到:observability/flag 改动,单元测和 source-guard 都可能放过"代码没真生效"。→ 催生端到端 live 验证(这次也立刻抓到了 "flag-on/off backtest 完全相同 = 云端没 pull 新代码")。
-
这次 range_break_probe 里单 symbol 假象(Infrastructure=LINK)是新增的一关: 当我想用 subType 做精细化时,意识到"每个 subType 几个 symbol?"——只 1 个就是 symbol 伪装。→ 催生多 symbol关卡。
一句话总结思路: 每次我对一个数据发现感到兴奋、想立刻部署时,就强迫自己问 "这个结论,换个时段还成立吗?换批 symbol 还成立吗?拆开看每个切片还一致吗? 部署后真生效吗?"——把每一次踩过的坑变成一道固定关卡。方法的价值不在于找到新 alpha,而在于拦住那些看起来像 alpha 、实际会亏钱的假象。
7. 落地工具速查
- 拉独立时段数据:
fetch_backtest_inputs.py --symbol X --end-date YYYY-MM-DD --exchange-kind binance - 控 flag 跑回测:
GENERAL_XXX_ENABLED=false python run_backtest.py --csv ... --symbol X - 实盘归因:
reports/runtime/{income,shadow_trades,decision_journal}_ledger,decision_journal 按git_sha切 live A/B - 交易所分类:Binance
fapi/v1/exchangeInfo的underlyingType/underlyingSubType - 黑名单/cohort 正道:
EDGE_DISABLED_COHORTS(cohort 级 holdout-validated),优于手动 symbol blacklist - 实盘改动(.env flag 翻转、mode 禁用)需当前对话明确授权 + 可回滚 + 部署后 live 监控