【回测口径】你的回测夏普,可能虚高了一倍以上——稀疏日序列与统一交易日历
【回测口径】你的回测夏普,可能虚高了一倍以上讲一个我自己刚踩的坑,因为这个坑太普遍了,而且不查不知道、一查吓一跳。
先说结果:我算出一个组合的夏普是 1.01,很漂亮。后来发现这个数字是错的,正确值是 0.43。
不是数据错了,是口径错了。差了一倍多,全部来自一个不起眼的细节。
一、坑在哪:稀疏日序列,按日频年化
这个策略是事件驱动的——它只在平仓那天产生一笔盈亏。36年历史,总共只有几百个平仓日。
而我在算夏普时,用的是所有人都熟悉的那条公式:
夏普 = 日收益均值 ÷ 日收益标准差 × √252
问题就在这个 √252。它假设你有一年252个交易日的连续收益序列。但我手上只有几百个点,分散在36年里。我用"每天"的公式去套"有事才记一笔"的序列,等于把这几百天当成了36年的日数据。
结果就是:夏普从 0.43 虚高到 1.01,虚高 2.35 倍。
正确的做法很朴素:把盈亏流 reindex 到完整交易日历,没有交易的日子填 0,然后才用 √252。
为什么填0是对的?因为那些没交易的日子,钱也在账户里,只是没动。它们是收益序列的一部分,只是收益为零。把它们丢掉,你的"日均收益"就会被系统性高估。
二、另外两个同样常见的口径错误
错误二:滑点用「价格点」而不是「元/手」
这是国内品种上最容易犯错的地方。同样的"一个点",不同品种的实际成本差 5 到 100 倍,因为合约乘数完全不同。
正确的算法是:最小变动价位 × 合约乘数 = 每手一个tick值多少钱(元),滑点和手续费都按元来算。
我早期用"价格点"算,得出的结论是"成本可以忽略";改成元计之后,结论直接反转——有些品种组合的收益基本被成本吃光了。
错误三:回测用的函数和实盘用的函数不是同一个
这个是隐蔽的杀手。回测脚本里 import 了一个指标函数,实盘代码里又内联写了一个同名函数,两者公式不同。
我遇到过:回测的 ADX 和实盘的 ADX 算法不一致,导致历史回测的门控结论全部作废,必须重测。这个坑不会报错,只会让你在实盘里慢慢发现"怎么和回测不一样"。
三、我后来给自己定的三条纪律
1. 所有盈亏流,先 reindex 到统一交易日历(缺日填0),再算夏普、再画曲线。不做这一步,夏普不算数。
2. 成本一律按元/手计,按品种算 tick 值,做 0 / 1 / 2 tick 三档敏感性测试。
3. 回测引擎的指标函数,从实盘代码里逐字拷贝,不 import 同名函数、不重写。
四、为什么值得较这个真
因为夏普0.43和夏普1.01,是两个完全不同的决策。
前者你会谨慎地小仓位试、会盯着回撤;后者你可能直接上大资金、甚至加上杠杆——那才是真正的灾难起点。
而且这个误差不会让你少赚,它会让你在错误的信心下加仓。虚高的数字比亏损更危险。
自检一下:你手上的回测,盈亏流的日频密度是多少?如果一年252个交易日里只有几十天有记录,而你在用 √252 年化,那你的夏普大概率虚高了。
本文数据来自真实回测,不构成投资建议。有不同看法欢迎讨论。
补充一个口径更新(v2),比首帖里那三条更关键的一条:**随机入场对照**。
首帖讲了两个坑:稀疏日序列不能直接年化、组合权重必须用滚动 σ + shift(1)。这两个解决的是"算得对不对"。但还有一个更根本的问题:**这个策略真的含有时序信息吗?**
做法:把策略的实际仓位序列拆成"每笔持仓长度",然后保持**交易次数与持仓长度分布不变**,把入场时点随机化,重复 150~200 次,得到一条夏普分布。看真实策略落在第几分位。
- 分位 ≥90% → 说明入场时点确实带信息;
- 分位 40~60% → 等于随机;
- 分位 <10% → 比随机还差(这种情况在形态类指标里非常常见)。
我最近把 11 个指标规则族在 8 个标的上跑了一遍这套对照,结果是:多数族零通过,其中供需区、顶底折返、RSI 这类族的随机对照分位是 **0%** —— 也就是说"随机的等长持仓"几乎总比它们好。这比"胜率不够高"严重得多,因为它意味着方向性的错误,不是参数问题。
顺带补一个配套判据:参数扫全网格时,统计"定参期与验证期同时为正超额"的比例。我这边最好的族也只有 50%,多数在 12%~38%。如果某个参数是网格里唯一的亮点,基本可以判定是拟合出来的。
所以建议的复核顺序是:① 口径(盯市/年化)→ ② 随机对照 → ③ 样本外 → ④ 成本压测 → ⑤ 换标的还成不成立。前两步不做,后面三步都是白算。
页:
[1]