记录一次被自己代码打脸的 alpha(2026-07-26)
记录一次被自己代码打脸的 alpha(2026-07-26)
「+$1,666 的 alpha 是 3 倍杠杆差造出来的幻觉,而 -$8,740 才是等敞口后的真相。」
事情要从 7 月 26 日三组全跑完说起。Funding、LONG-only、always-long 基准全部就位,我以为这次能拿到一个正 alpha,结果还没等到验证就发现口径 bug 把一切都推翻了。
一、三个策略跑完,+$1,666 的 alpha 让我兴奋了半小时
三组趋势跟踪对比,5.8 年 / 64 段 / 含 funding:
| 组 | 笔数 | Net PnL | 每笔 | Funding | Fee |
|---|---|---|---|---|---|
| A: Donchian 多空 | 185 | +$4,480 | +$24.21 | -$350 | $217 |
| B: Donchian LONG-only | 97 | +$5,316 | +$54.80 | -$377 | $133 |
| C: Always-long 基准 | 1920天 | +$3,650 | — | 含 | 含 |
B - C = +$1,666,我当时脑子里的第一反应是「LONG-only 跑赢 always-long $1,666」,折算下来 $287/年,在 $10,000 本金上是 2.9% 年化 alpha。
但会话里助手一直在核对真实代码,尤其盯着一件事:alpha = B - C 完全依赖 C 和 B 同口径。它能确认 LONG-only 是 a priori 禁空整段重跑、funding 三组同口径、段级 bootstrap 只对段均值重采样。这些都没问题。
二、÷3 的敞口 bug:+$1,666 是 3 倍杠杆的滤镜
代码读完全部后,助手挖到的问题比前几轮加起来都致命:run_trend_backtest(B/A)的仓位是 capital * target_vol / annual_vol,而 run_always_long_benchmark(C)的仓位是 .../ len(candles_by_sym)。
如果传入多个币种,基准 C 的总敞口只有 B 的 1/3。 SYMBOLS 里传了 3 个币,所以 C 的敞口被压到了 B 的 1/3。之前的 +$1,666 是杠杆差混出来的伪影。
这里有个细节:B 年度求和 1935−1254+4240+1616−269−951=5317≈5316,B−C=5316−3650=1666 算术全对,2023 一年占 4240/5316=80%。算术没有错,错在口径。
三、等敞口修正:alpha 翻负 -$8,740
修掉 ÷3 后 318-319 行变成 size_usd = min(capital * target_vol / annual_vol, capital * max_lev),C 与 B 现在是逐币同口径 sizing。
结果:
| 组 | PnL | 之前(÷3 bug) | 修正后 |
|---|---|---|---|
| A: Donchian 多空 | +$4,480 | 不变 | 不变 |
| B: Donchian LONG-only | +$5,316 | 不变 | 不变 |
| C: Always-long 基准 | +$14,056 | +$3,650 | +$14,056 |
| B - C (alpha) | -$8,740 | +$1,666 | -$8,740 |
Donchian LONG-only 跑输 buy-and-hold $8,740,是基准的 2.6 倍差距。 根因很清楚:always-long 每天满仓 vol-target 持有 3 币,5.8 年 crypto 牛市漂移全部吃到。Donchian 的突破确认延迟成本远大于避险收益。在牛市偏置的资产上,择时是负和。
四、做市器:三道安全闸和一个死熔断
方向性策略被证伪后,当天又回到 strategy_engine/hl_market_maker.py 做市器。Phase 1 已经就位,参数是 SPREAD 0.04%、每边 $200、TTL 10s、库存上限 $1,000、日亏 $100 熔断。
助手通读代码后发现一个安全关键级问题:熔断是死开关。_daily_pnl 和 _total_pnl 初始化后从没被更新。旧进程 14 笔成交、-$24 SHORT inventory,pnl 却恒 $0——这正是死熔断的现场证据。
修复分三道:
- 死熔断:
_get_equity()读crossMarginSummary.accountValue,每轮刷新_daily_pnl = equity - _daily_start_equity。启动打印初始净值: $880.62。 - API 异常→假成交:
_get_open_orders()异常返回None,_check_fills和_cancel_all都 guardNone。 - 死循环间隔:
main()加了time.sleep(LOOP_INTERVAL_SEC),实际循环 ~15s。
五、90 分钟 testnet 实况:-24 SHORT 到 +123 LONG
修复后跑了 90 分钟观察:17 笔成交、PnL -$1.46、Inventory +$123 LONG、Mid $64,406。
PnL -$1.46 说明真实净值跟踪生效了,不再是恒 0。但我得承认,助手后来指出我对 inventory 漂移的归因是错的。我写「skew 因子确实在工作」,但读 _calc_quotes(303 行)后看到:
skew = (inventory / 1000) * 0.0002
-$24 SHORT 时 skew 只有 -0.0005%,+$123 LONG 时 skew +0.0025%,而基差报价是 ±0.02%。skew 在 ±$24/$123 这俩量级上完全被基差淹没。 inventory 翻多不是 skew 的作用,是 testnet 模拟撮合的单向吃 bid 驱动。
六、下一步:转向 ETH/BTC 统计套利
三个方向性信号全被证伪:combo 是 ≈0 不可区分(4 段 × 30 天、224 笔、-$8.94、每笔 -$0.04、CI 含 0),Funding MR 显著负(4 段全负、-$189.39、-$0.92/笔、CI [-1.39, -0.35] 不含 0),Donchian 等敞口后跑输基准 -$8,740。
当天最后决定转向 ETH/BTC 统计套利——方向无关,多空对冲后 funding 大致互抵,用历史回测就能验证。新建 backtest_pairs.py,复用 v4 框架的 fetch / bootstrap / continuity 函数。
遗留
backtest_pairs.py 刚搭起来,还没跑出第一批回测结果。ETH/BTC 比价在多年窗口上不是平稳的,2022 年相对大跌、2023+ 又回升,朴素 z-score 配对在趋势期会被打脸。回测很可能揭示需要短窗口 + 紧止损,这正是要测的东西。