【回测口径】你的回测夏普,可能虚高了一倍以上——稀疏日序列与统一交易日历
【回测口径】你的回测夏普,可能虚高了一倍以上
讲一个我自己刚踩的坑,因为这个坑太普遍了,而且不查不知道、一查吓一跳。
先说结果:我算出一个组合的夏普是 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 年化,那你的夏普大概率虚高了。
本文数据来自真实回测,不构成投资建议。有不同看法欢迎讨论。
|
|
|
|
|
|
|
|