结果可视化:让报告自己说出问题
198 个月里只有 34.3% 是赚的,而最好的 1 个月贡献了全部收益的 48.7%
p4.7 给了你一堆数字。这一节把它们变成图——但重点不是好看。
先看一个只有图才说得清的事实。同一条策略,198 个月:
- 正收益的月份只有 68 个(34.3%)
- 最好的 1 个月,贡献了全部收益的 48.7%
- 最好的 3 个月贡献了 136.6%——超过 100%,意味着其余月份合计是亏的
年化把这 198 个月摊平成一个数。而真实体验是:熬了 5 年水下,靠一个月翻身。
这一节做一张六联报告图,每一联回答 p4.7 的一个指标;其中第六联,是向量化回测画不出来的。
病历上写"平均血压 120/80"。
而实际情况是:大部分时间 110/70,每个月有两天飙到 180/110。
平均值没撒谎,但它把最要命的信息抹平了。
年化收益之于净值曲线,就是那个平均血压。
一、六联报告图
每一联对应 p4.7 的一个问题:
| 面板 | 画什么 | 回答什么 |
|---|---|---|
| (1) | 净值 + 基准,对数坐标 | 跑赢了没有(对数,否则早期被压扁——p1.8) |
| (2) | 水下图 | 我要熬多久——最长水下天数一眼可见 |
| (3) | 月度收益热力 | 收益是稳定的,还是靠几个月 |
| (4) | 日收益分布 + 正态对照 | 厚尾有多厚,夏普可不可信(p4.7/p3.1) |
| (5) | 成交点标注 | 策略到底在什么位置进出 |
| (6) | 被拒委托时间轴 | 向量化回测画不出这一联(p4.1) |
本次实测(sh.600000,3976 个交易日):
(1) 策略期末 1,585,944 vs 买入持有 1,971,219
(2) 最大回撤 -36.4%,最长水下 1207 个交易日(约 5.0 年)
(3) 198 个月,正收益 68 个月(34.3%)
(4) 超额峰度 15.48
(5) 买入 136 次,卖出 132 次
(6) 被拒委托 0 笔
第四联最值得单独看
日收益分布画成对数纵轴,再叠加一条正态拟合曲线。
厚尾在这张图上是可见的:两侧尾部有一串孤立的柱子,远高于正态曲线。p4.7 那个"超额峰度 15.48"和"3σ 事件是正态预期的 8.9 倍",在这里从数字变成了形状。
如果用线性纵轴,尾部那些柱子高度接近 0,你什么都看不见。这是 p1.8 那条纪律的又一次应用。
🔧 动手做:一行代码,让"厚尾风险"从看得见变看不见(5 分钟)
跑 python report_plot.py 生成 report.png,打开第(4)联"日收益分布"——两侧尾部有一串孤立的高柱,远高于正态曲线,那就是厚尾(p4.7 的超额峰度 15.48 在这里变成了形状)。
现在把 report_plot.py 第 101 行的 ax.set_yscale("log") 注释掉(换回线性纵轴),重新生成。
你会看到:那些尾部的柱子高度接近 0、几乎看不见了——图上一片"正常"。
想明白:同一份数据、同一张图,只是把纵轴从对数换成线性,"厚尾风险"就从刺眼变成隐形。图会骗人——不是数据假,是坐标轴的选择决定了你看得见什么。所以看任何分布图先问纵轴是什么;画自己的图,厚尾一律用对数轴,别把最要命的信息藏进坐标里。
二、收益集中度:那个只有拆开才看得见的事实
| 最好的 N 个月 | 贡献全部收益的 | 占全部 198 个月 |
|---|---|---|
| 1 个月 | 48.7% | 0.5% |
| 3 个月 | 136.6% | 1.5% |
| 6 个月 | 222.6% | 3.0% |
| 12 个月 | 363.9% | 6.1% |
"贡献超过 100%"不是算错了。
它意味着:其余月份合计是亏的。
本样本 198 个月里,只有 68 个月为正(34.3%)。也就是说:
- 三分之二的月份在亏钱
- 全部收益由极少数几个月撑起
- 最好的 1 个月就贡献了 48.7%
这个结构有一个直接后果:错过那几个月 = 策略失效。
而"错过"在现实里非常容易发生——停牌、没钱、心态崩了、正好那阵子在改代码。
这与 p4.5 的"分红贡献超过 100%"是同一类现象:当基数里有负项时,占比会超过 100%,而那恰恰是最该注意的信号。
三、第六联:向量化回测画不出来的那一张
被拒委托时间轴。本次运行是 0 笔,所以图上写的是:
No rejected orders in this run (constraints did not bind — see p4.1)
画一张"什么都没有"的图,有意义吗?有。
因为它区分了两种情况:
| 情况 | 向量化回测 | 本引擎 |
|---|---|---|
| 约束没绑定 | 看不出区别 | 明确写出"没绑定" |
| 约束绑定了很多次 | 看不出区别 | 柱状图会立刻暴露 |
向量化回测里,这两种情况长得一模一样——因为它根本没有"被拒"这个概念。
p4.2 那个"5000 元本金收益最高"的陷阱,就是靠这一联识破的:收益列看起来最好,而被拒列有 338.7 笔。
四、血缘水印:一行代码
meta = (f"{sym} | qlab.engine | {start}~{end} | {len(g)} bars | "
f"cash {CASH:,} | comm 2.5bp + slip 5bp | fetched 2026-07-31")
fig.text(0.005, 0.002, meta, fontsize=7, color="#666")
承接 p1.8:标的、引擎、区间、行数、初始资金、成本假设、拉取时间,全部写在图的左下角。
图会被截屏、转发、三个月后翻出来。没有水印的图,三天后就是一张不可追溯的图片。
注意这里的水印比 p1.8 多了两项:引擎名与成本假设。因为对回测报告而言,"用什么引擎、什么成本跑的"和"用什么数据"同等重要——p4.2 的成本网格已经证明了这一点(74 个百分点)。
五、报告图的判断标准
不是"好不好看",是"每个 p4.7 的指标有没有一张对应的图"。
| 数字(p4.7) | 图(本节) |
|---|---|
| 年化、累计 | (1) 净值 + 基准 |
| 最大回撤、最长水下 | (2) 水下图 |
| 收益分布是否均匀 | (3) 月度热力 |
| 偏度、峰度 | (4) 分布 + 正态对照 |
| 交易次数 | (5) 成交点 |
| 被拒笔数 | (6) 被拒时间轴 |
数字回答"是多少",图回答"长什么样"。两者缺一不可。
只给数字,读者无法判断路径;只给图,读者无法复核。
📌 免责:本课为技术教学,标的为沪深300 成分股按代码序取样(非精选),所有回测为历史模拟,不构成投资建议。
三件事:
- 六联报告图——每一联对应 p4.7 的一个指标;第四联用对数纵轴让厚尾从"超额峰度 15.48"变成看得见的形状
- 收益集中度:198 个月只有 34.3% 为正,最好的 1 个月贡献 48.7%,最好的 3 个月贡献 136.6%(超过 100% = 其余月份合计亏损)。错过那几个月 = 策略失效
- 第六联(被拒委托)是向量化回测画不出来的——它区分"约束没绑定"与"约束绑定了很多次",而向量化里这两种情况长得一模一样
外加血缘水印:比 p1.8 多了引擎名与成本假设——因为 p4.2 已经证明,成本假设值 74 个百分点。
判断标准:不是好不好看,是每个数字有没有一张对应的图。数字回答"是多少",图回答"长什么样"。
下一节引入外部参照:单元测试和双路对账都是"自己验自己",这一节把 qlab 和社区最常用的 backtrader 跑同一条策略对拍——交易次数差了数倍,期末权益却只差约 1%,而每一条差异都要能解释得出来。
🔎 来源与核验· 2 条,点开核对
