← 返回目录
p4.8动手⏱ 约 14 分钟

结果可视化:让报告自己说出问题

198 个月里只有 34.3% 是赚的,而最好的 1 个月贡献了全部收益的 48.7%

🔎 最后验证 2026-07📚 来源:六联报告图与收益集中度于 2026-07-31 在 ECS 实测,图与输出见 code/outputs/🧰 matplotlib 3.11.1、qlab.engine(本课自研)、Python 3.12.13
为什么学这个

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) 被拒时间轴

数字回答"是多少",图回答"长什么样"。两者缺一不可。

只给数字,读者无法判断路径;只给图,读者无法复核。

❓ 测验
一份回测报告显示年化 12%、夏普 0.9。你要求作者补一张图,最该先要哪一张?
✏️ 填空
日收益分布图必须用 ___ 纵轴,否则尾部那些柱子高度接近 0,厚尾根本看不见。

📌 免责:本课为技术教学,标的为沪深300 成分股按代码序取样(非精选),所有回测为历史模拟,不构成投资建议。

✅ 小结

三件事:

  1. 六联报告图——每一联对应 p4.7 的一个指标;第四联用对数纵轴让厚尾从"超额峰度 15.48"变成看得见的形状
  2. 收益集中度:198 个月只有 34.3% 为正,最好的 1 个月贡献 48.7%,最好的 3 个月贡献 136.6%(超过 100% = 其余月份合计亏损)。错过那几个月 = 策略失效
  3. 第六联(被拒委托)是向量化回测画不出来的——它区分"约束没绑定"与"约束绑定了很多次",而向量化里这两种情况长得一模一样

外加血缘水印:比 p1.8 多了引擎名成本假设——因为 p4.2 已经证明,成本假设值 74 个百分点。

判断标准:不是好不好看,是每个数字有没有一张对应的图。数字回答"是多少",图回答"长什么样"。

下一节引入外部参照:单元测试和双路对账都是"自己验自己",这一节把 qlab 和社区最常用的 backtrader 跑同一条策略对拍——交易次数差了数倍,期末权益却只差约 1%,而每一条差异都要能解释得出来。

下一节 → 引擎交叉验证:与 backtrader 对拍
🔎 来源与核验· 2 条,点开核对
本节每个关键论断都对应一个可追溯的来源 —— 这是本课程"靠谱、不过时"的底线。
「六联报告图实测(sh.600000,3976 个交易日):策略期末 1,585,944 vs 买入持有 1,971,219;最大回撤 -36.4%,最长水下 1207 个交易日(约 5.0 年);198 个月中正收益 68 个月(34.3%);超额峰度 15.48;买入 136 次、卖出 132 次;被拒 0 笔」
📚 本节 code/report_plot.py,2026-07-31 于 ECS 实测,图见 code/outputs/report.png✓ 已核验 2026-07
「收益集中度:最好的 1/3/6/12 个月分别贡献全部收益的 48.7%/136.6%/222.6%/363.9%;超过 100% 系因其余月份合计为负」
📚 同上实测(按对数收益求和计算贡献占比)✓ 已核验 2026-07
收益集中度以对数收益求和计算;占比超过 100% 表示其余月份合计亏损,非计算错误。
智图软件的赞赏码
都看到这了,打个赏呗!
接下来 · p4.9
引擎交叉验证:与 backtrader 对拍
继续读下一节 →