v0.1 骨架、v0.2 Opus 生成,v0.3 该上真金白银数据了。
这一版跑的是:
- Binance 公共 API 拉 BTCUSDT 4h K 线
- 2024-01-01 → 2026-04-19,共 5,033 根 K 线,约 2.3 年
- 同一个策略 × 4 种 sizing:fixed $100 / fixed $200 / Kelly / Volatility Target
- 对照 基准 · 买入持有 +79.2%
结论先行:4 种 sizing 全部跑输买入持有。而且不止是"跑输"——其中一种被组合 drawdown 硬停机了。
这就是 v0.3 要教的第一件事——回测是用来拒绝坏策略的,不是用来展示好策略的。
运行结果
pnpm tsx scripts/demo-strategy-backtest.ts
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
变体 总收益 最大回撤 夏普 交易 胜率 盈亏比 终值
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
A · fixed_notional \$100 -4.3% -8.0% -0.15 140 35.0% 1.76 \$957
B · fixed_notional \$200 -8.6% -15.5% -0.10 140 35.0% 1.76 \$914
C · Kelly (55% 胜率 1.5:1) -3.7% -6.5% -0.21 140 35.0% 1.74 \$963
D · Volatility Target 20% -25.9% -30.6% -2.19 24 16.7% 2.41 \$741 🚨
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
基准 · 买入持有 +79.2%
(🚨 = 触发组合 drawdown 硬停机,强平剩余仓位)
解读 · 五个发现
发现 1 · 趋势策略在趋势行情里 ≠ 赢
这是最反直觉的。
BTC 2024-2026 是典型牛市:$42,330 → $75,851(+79%)。按理说趋势跟随策略应该吃肉。
结果:所有变体都亏。
为什么?
- 胜率 35% · 说明 EMA20>EMA50 的信号里只有 1/3 真走趋势
- 盈亏比 1.76 · 胜率 35% × 1.76 ≈ 期望 +0.62 · 亏损 65% × 1 = -0.65 · 净期望 −0.03
- 140 笔交易 × 摩擦(滑点/手续费 + 止损被洗) · 把那一点正期望吃光
结论:这条策略的信号质量不够——即使是牛市也不够。
发现 2 · Kelly 最保守 · 因为它"自我怀疑"
Kelly 变体的参数是 win_rate=0.55, payoff_ratio=1.5, kelly_scale=0.3。
- Kelly f* = 0.55 − 0.45/1.5 = 0.25 (25% 本金)
- 实际仓位 = 25% × 0.3 = 7.5% 本金
所以 Kelly 每单约 $75(本金 $1,000 的 7.5%)——比 fixed $100 还保守。
结果:亏得最少(-3.7%)、回撤最小(-6.5%)、存活下来。
这是 Kelly 的正确用法——不是追求最大收益,是"在信号不确定时,仓位主动缩小"。
发现 3 · Volatility Target 20% 被硬停机 · 为什么
这是最重要的教训。
Volatility Target 的逻辑:把组合的年化波动率稳定在目标值。BTC 当前年化波动 ≈ 60%,目标 20%——所以仓位 = 20/60 = 33% 本金 × 3x 杠杆 = 单仓 100% 有效杠杆。
问题:当 BTC 波动瞬间冲到 80%(如暴跌),算出来的"合理仓位"会被动态缩小——但已经开的仓位不会自动缩。高波动时入场 + 低波动假设 = 被强仓。
- 24 笔交易(因为波动率经常让仓位超出 max_notional_per_trade_usd)
- 胜率跌到 16.7%
- 夏普 -2.19
- -30.6% 触发了组合 drawdown 硬停机,立即强平所有仓位
Guardrail 按预期工作。如果没有组合级 drawdown 硬停机,这条变体可能亏到 -50% 甚至更多。
这是 Guardrail 层存在的意义。
发现 4 · 同信号 × sizing 差 × 终值从 -25.9% 到 -3.7%
同一个策略 · 同一个入场/出场 · 只是 sizing 不同:
| 变体 | 总收益 |
|---|---|
| Kelly | -3.7% |
| fixed $100 | -4.3% |
| fixed $200 | -8.6% |
| Vol Target 20% | -25.9% 🚨 |
22 个百分点的差距——全部来自 sizing 选择。
入场信号只是"选对方向",sizing 决定了"你能亏多少"。
发现 5 · "跑输买入持有"不是失败
所有变体都跑输 +79.2%。但这不是策略失败——是 trend-following 的常态。
- 买入持有吃整个行情
- 趋势跟随吃行情的后半段(等信号确认)+ 丢回撤时段
- 数学期望:趋势策略 < 买入持有,当且仅当趋势没被打断
BTC 2024-2026 是顺势牛市 → 买入持有赢。 如果是 2022 熊市?买入持有 -65%,Kelly 变体大概会守平或小亏。
策略好坏要看完整周期,不是某个窗口。v0.4 会加入 2022 熊市回测做对照。
架构上印证的三件事
① Executor 是纯函数 · 回测即"真实世界快照"
for (const bar of klines) {
const actions = runStrategy(spec, tick, state); // 纯函数
applyActions(actions, state, bar);
}
跑 5,033 根 K 线零副作用,同 spec + 同 klines = 同结果。
这意味着我们可以用 CI 跑回归测试:每次改 spec 或 executor 都自动跑一遍 2 年历史,对比 KPI 变化。VergeX 做不到这个——LLM 的随机性让他们无法做"回归测试"这件事。
② Guardrail 在 Vol-target 变体上救了场
-30.6% drawdown 触发硬停机。如果让它继续跑,可能 -50% / -70%。
组合级 drawdown 硬停机是产品对用户最后一道保护。即使 spec 有 bug、sizing 算错、信号坏了——账户亏到一半必须强制停下。
③ Validator + Backtest 联合工作 · 不放过任何坏策略
- Validator 拦结构性违规(杠杆超限、avoid 清单里的标的等)
- Backtest 拦经济性失败(夏普 < 1、DD > 20%、交易数 < 20)
两层都过不了 = 不给实盘权限。
v0.3 的 BacktestResult 直接匹配 spec.guardrail.paper_cutoff 字段,实际实盘流程会是:
生成 spec → validator 通过 → 72h Paper Trading → cutoff 判断 → 通过 → 实盘审批 → 实盘
v0.3 填上了"Paper Trading → cutoff"这一段。
仓库新增
lib/strategy/
├── binance-klines.ts # 公共 API 拉 K 线 + 缓存 + ATR/年化波动率
└── backtest.ts # 回测引擎:指标计算 + 驱动 executor + KPI
scripts/
└── demo-strategy-backtest.ts # 2 年 BTC × 4 种 sizing 对比
约 600 行新代码,零外部依赖(只用 Node 内置 fs/path + fetch)。
第一次跑会从 api.binance.com 拉数据(约 30 秒 · 分页);之后缓存在 /tmp/dingdingtao-klines/,秒出。
v0.4 预告
v0.3 证明了"我们能回测、能拦坏策略"。v0.4 要做:
- 接 Binance MCP(testnet) · 把通过 Paper cutoff 的 spec 真正下到模拟盘
- OKX MCP(testnet) · 同上
- 实时监控循环 · 每分钟查 equity,触发 drawdown 立即停止并平仓
- Webhook 通知 · 下单 / 止损 / 停机 都推送到 Slack / 企业微信
- 真正的熊市回测 · 跑 2022 和 2018 看策略能不能不死
v0.4 之前的代码都是"理论上能跑"。v0.4 之后,策略第一次真的会下单——哪怕只是 testnet。
这是从"纸上"到"水面"的过渡。我会谨慎。
最后一个思考
数据跑完,我的交易哲学被这次回测强化了一条:
策略的"护城河"不在信号,在 sizing。
同样的入场信号,Kelly 让你活到下一个机会,Vol-target 让你被硬停机。两者差22 个百分点。
这和 bwjoke、brucelinda、老恶魔、Bit浪浪四个人说过的话完全一致——80% 仓位管理 + 20% 信号。
AI 改变不了这个数学。AI 只能帮你稳定地执行 sizing,让你在情绪波动时不自己把仓位搞坏。
这就是**"AI 是纪律执行者"的字面意思**。
声明:回测用的是 Binance 公共 K 线,没用任何账户数据。所有代码在仓库里,任何人都能 clone 下来复现同样的结果。本文不构成投资建议。