你的入场信号,很可能是回测里影响最小的因素。我可以拿一条平平无奇的动量规则,完全不动任何入场和出场时间戳,只改决定持有多少合约的函数,就能把净夏普比率从 0.41 提高到 0.71,把最大回撤从 31% 降到 13%。交易相同,成交相同,策略却不同。
这个说法让人不舒服,也确实应该让人不舒服,因为它意味着我们花大量时间调回看周期和过滤阈值,折腾的其实是个次要项。下面我会展示实际结果,再认真回应批评者,因为这种论点有一种错误的说法,弄清楚我说的是哪一种很重要。
一个信号,6 种持仓方式
信号设定:Binance USDⓈ-M 的 ETHUSDT 永续合约,1 小时 K 线;12 小时 EMA 高于 96 小时 EMA 时做多,否则空仓,不做空。时间范围为 2022 年 7 月至 2026 年 6 月。共 431 次完整交易。买卖两侧 taker 手续费均为 5.0 bps,每 8 小时按实际持仓支付或收取资金费,滑点根据最优买卖价深度建模。这是条刻意选得很无聊的信号——我选它就是因为没人会替它辩护,这样测试起来更干净。
下表每一行的入场和出场 K 线时间戳都完全相同,唯一的区别是仓位计算函数。
| 仓位规则 | 净夏普比率 | 最大回撤 | 年交易名义金额(×平均仓位) | 成本占毛利润比例 | 峰值杠杆 |
|---|---|---|---|---|---|
| 固定 $10k 名义金额 | 0.41 | 18.2% | 246× | 14% | 1.0× |
| 固定比例,使用 100% 权益 | 0.44 | 30.8% | 251× | 15% | 1.0× |
| 按 20 日实现波动率倒数调整 | 0.68 | 14.1% | 690× | 29% | 4.4× |
| 年化波动率目标 20%,每小时再平衡,不设上限 | 0.71 | 12.9% | 1,180× | 41% | 6.3× |
| 波动率目标 20%,不交易区间 20%,杠杆上限 3× | 0.66 | 15.3% | 402× | 19% | 3.0× |
| 基于 60 日滚动均值/方差的半 Kelly | 0.52 | 24.6% | 830× | 33% | 8.1× |
看看差距。最差和最好的结果之间,夏普比率相差 73%,回撤相差 2.4 倍。现在请你在这组数据里找一个入场信号参数,能让夏普比率提高 0.30,又不会明显过拟合。我找过了。EMA 周期组合在整个合理参数网格中的变动,只能让夏普比率改变约 0.12,其中大部分只是样本外无法延续的噪声。
为什么波动率倒数调整看起来这么好,其中有多少是真的
背后的机制并不复杂。采用固定名义金额时,仓位规模与实现波动率无关,这意味着每笔交易的美元风险取决于当周的波动率。在这段时间里,ETH 的 20 日实现波动率年化后介于 31% 至 118% 之间。固定名义金额的回测在 2024 年 3 月承担的美元风险是 2023 年 7 月的 3.8×,原因不是信号更有把握,而是市场波动更大。它的大部分回撤都集中在波动率最高的五分位区间。按波动率倒数调整仓位,就能消除这种关联,确实能改善收益序列的形态。
但夏普提升中有一部分只是测量上的假象。如果不把两者区分开,你后续会做出错误决策。夏普比率是收益的标准差。波动率目标说白了就是让收益标准差保持稳定。也就是说,你直接在优化分母。只要收益序列存在波动率聚集,按仓位调整来实现事前波动率恒定,就几乎总能提高夏普,即便这条收益序列毫无优势。我用打乱后的信号验证过:随机调整入场时间戳,保留波动率目标覆盖层,在均值为零的基准收益上,夏普仍比固定名义金额高约 0.06。幅度不大,但确实存在,而且完全是机制导致的。
因此,内部比较仓位规则时,我从不会只看夏普比率。我还要看 Calmar 指标、每单位平均投入名义金额对应的净 PnL,以及从毛收益到净收益的成本瀑布图,因为这三项放在一起,更难靠分母技巧操纵。
波动率估计是个模型,也会像模型一样发生泄漏
仓位结果看起来很好、最后却证明是假的,最常见的原因是波动率估计用到了当前用于计算仓位的 K 线信息。如果你的波动率序列用了居中窗口,或者在包含当前未收盘 K 线的重采样数据上套用 pandas 默认的 `rolling().std()`,又或者做了全样本标准化,那就发生了数据泄漏。它很隐蔽,因为波动率具有持续性,单行数据中的泄漏幅度很小,权益曲线看起来也依然合理。只是恰好在最重要的那些周,曲线显得过于平滑。
每条仓位规则进入模拟交易队列前,我们的 agents 都会检查以下 4 项:
- 决策时点的数据可用性。在 14:00 开盘的 K 线上使用的仓位,必须能用收盘时间戳 ≤ 13:59:59.999 的数据计算出来。我们会在随机抽取的 200 个决策时点上,从截断后的数据帧重新计算仓位序列,再对比差异,以此验证。
- 估计器回看窗口是真正的参数。20 日波动率窗口和 60 日波动率窗口代表两种不同策略。回看窗口会和其他参数一起纳入 walk-forward 网格。如果结果只在某一个窗口长度下有效,这本身就是关于策略脆弱性的发现。
- 检查完整杠杆路径,而非只看峰值。输出总杠杆的完整分布。上面的波动率目标未设上限的运行,有 4% 的小时杠杆高于 5×。汇总表里从来不会显示这类情况,但它会彻底决定策略能否实际执行。
- 把仓位敏感性作为稳健性测试。如果把波动率目标从 20% 改为 25% 时夏普比率就大幅下降,那你的策略就不是在做波动率目标控制,而是在调杠杆。换句话说,你只是靠挑数字找到了优势。
杠杆上限不是风险管理的点缀,而是策略定义的一部分。Binance 的 ETHUSDT 合约会随着仓位名义金额增加而下调最大杠杆,维持保证金率也会相应提高。回测若允许波动率目标规则在 $400k 名义金额下跑到 6.3×,描述的就是交易平台不允许你按该保证金水平持有的仓位。表中有上限的那一行牺牲了 0.05 的夏普比率,却是两者中唯一符合现实的。
钱都花在再平衡上了
注意成本这一列。按波动率目标每小时再平衡,虽然得到最高的表面夏普比率,却也把毛利润的 41% 交给了交易所。按每边 5 bps 加上价差和冲击计算,每年交易的名义金额相当于平均仓位的 1,180×。最直观的办法是按固定时间间隔减少再平衡频率。更好的办法是设定不交易区间:只有当当前仓位偏离目标超过一定阈值时才调整。
设置 20% 的区间后,年度交易名义金额从 1,180× 降到 402×,减少了 66%,夏普比率则下降 0.05。我已经在 8 条信号上做过测试,结果规律一致:15% 到 25% 的区间能以三分之一的换手率保留大部分风险控制收益;低于 10% 就只是在你用 20 日窗口估计的仓位上,花手续费追求表面精确。你不能拿标准误差为 15% 的估计值,却要求把再平衡做到小数点后三位。
今年 2 月有一次运行中,某个 agent 写的报告把夏普比率提升 0.22 归功于“改进后的入场过滤器”。代码差异其实只在仓位模块的一行,过滤器根本没改。我现在会要求报告在开头打印仓位配置哈希值,因为归因是一门严谨的功课,而模型和人一样,也乐于编出一个顺理成章的故事。
模拟交易会如何影响你的仓位规则
回测和模拟交易的差距在这里最大,而且原因大多是算术问题。按波动率动态调整仓位,意味着样本中的仓位规模会有 4 到 8 倍的波动。回测从未建模的交易平台限制,会在这个范围的两端同时出现。
在较小的一端,问题是步长和最低名义金额。ETHUSDT 永续合约的数量步长为 0.001,最低金额为 $5。如果低波动率阶段的仓位只有 0.0004 ETH,回测会持有,模拟交易却会空仓。这听起来像是极端情况,直到你意识到波动率目标规则会在市场最平静时设定最小仓位;而对趋势信号来说,最好的入场往往就在这时出现。在较大的一端,问题则是仓位限额、保证金阶梯,以及 6× 仓位需要严格的逐仓保证金管理,而你并没有为回测配上强平模型。
我们曾发现一例:模拟交易的权益曲线连续 6 周都与回测相差不超过 3%,之后却突然严重背离。原因是取整:订单仓位计算器用了 `int()` 截断,而不是按步长取整;受影响的仓位平均只有 1.4 个步长。回测在连续数值空间里计算仓位,实盘计算器却在每笔小额交易中少下了预期仓位的 20-30%。没有复杂的成交模型,也没有延迟方面的说辞,问题就是截断。
批评者说对的地方
有 3 条反对意见确实成立,我不想回避。
第一,也是最重要的一点:仓位管理只能分配优势,不能凭空创造优势。如果扣除合理的手续费和资金费后,信号的毛期望收益小于或等于零,表里每种规则都会亏钱——花哨的规则只是让收益方差看起来更漂亮。之所以选择这条信号,是因为它在这个时间段有微弱但为正的净优势。把同样的 6 条规则用在每笔净亏 −1.2 bps 的信号上,排名依然不变,但每种规则的期末权益都低于起始水平。仓位管理只是某种东西的乘数,你仍然需要这个“东西”。
第二,波动率目标有一种真实且有充分记录的失效模式:快速抛售接近底部时,它会降低杠杆,反弹后才重新加杠杆。在急剧的 V 型反转中,固定名义金额会胜出,有时优势很大。2020 年 3 月的股票市场和 2024 年 8 月的加密市场暴跌,都让按波动率动态调整仓位的策略在反弹阶段吃了亏。我的 4 年 ETH 样本里,波动率聚集的缓慢走势恰好多于 V 型剧烈反转,因此按波动率倒数调整的结果显得更好。换一段时间窗口,部分排名就会反过来;如果我拿 2020 年的数据开篇,这套论证就没那么有力了。
第三,仓位规则和其他东西一样,都可能过拟合。我表里的半 Kelly 规则就是个典型例子:它看起来高深,有理论依据,结果却有倒数第二高的回撤,因为滚动估算噪声收益序列均值的效果糟糕透顶,而 Kelly 对此又极度敏感。任何把预期收益估计作为输入的仓位规则,都会继承这份估计的误差,并将其放大。只依赖方差估计的规则要稳健得多,因为对于 4 年的小时级数据,方差是唯一真正估得出来的矩。
我从中得到的结论是:回测结果让你意外时,先检查仓位模块,再去信号里寻找巧思。报告夏普比率时,也要把杠杆变化路径和成本瀑布图放在旁边,因为一个如此依赖仓位决策的数字,根本不能算是信号本身的属性。
← 全部文章
