Skip to content

10、归因结果可视化:瀑布图、热力图、堆积面积图、雷达图

归因分析做完了,数据摆在那,然后呢?

说实话,我见过太多团队把归因结果扔进Excel表格里,然后开会时对着密密麻麻的数字发呆。这太可惜了。你想想看,归因分析的本质是「找问题」,而可视化就是让问题自己「跳出来」。

我个人习惯把归因可视化分成四类:拆解贡献、暴露风险、追踪时序、综合对比。正好对应四种图:瀑布图、热力图、堆积面积图、雷达图。咱们一个一个聊。

10.1 瀑布图:拆解收益的「流水账」

瀑布图是我最常用的图,没有之一。它能把总收益一步步拆开,告诉你每一层贡献了多少、拖累了多少。

我在项目中遇到过一件事:一个策略回测年化20%,但实盘只有8%。用瀑布图一画,发现「交易成本」这一项比回测时预估的高了3倍,直接把收益吃掉了大半。嗯,从那以后,我每次归因必画瀑布图。

核心逻辑: 从总收益出发,依次加上/减去各因子的贡献,最终落到残差或特定因子。

举个例子,假设我们有一个多因子策略,归因结果如下:

归因项 贡献(bp)
市场择时 +15
行业配置 +8
选股Alpha +22
交易成本 -5
其他残差 +2
总超额收益 +42

用Python画瀑布图其实不复杂,我习惯用waterfallcharts库或者自己用matplotlib拼一个:

import matplotlib.pyplot as plt
import numpy as np

# 数据准备
labels = ['总收益', '市场择时', '行业配置', '选股Alpha', '交易成本', '残差']
values = [42, 15, 8, 22, -5, 2]  # 注意:总收益是最终结果

# 计算累积值
cumulative = np.cumsum([0] + values[:-1])

# 画图
fig, ax = plt.subplots(figsize=(10, 6))
for i in range(len(labels)):
    if values[i] >= 0:
        ax.bar(i, values[i], bottom=cumulative[i], color='#2ecc71', edgecolor='white')
    else:
        ax.bar(i, abs(values[i]), bottom=cumulative[i] + values[i], color='#e74c3c', edgecolor='white')

ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels, rotation=45)
ax.set_ylabel('累计贡献 (bp)')
ax.set_title('策略收益归因瀑布图')
plt.tight_layout()
plt.show()

我的小技巧: 瀑布图的颜色要区分正负贡献。绿色代表正贡献,红色代表负贡献。一眼就能看出「谁在赚钱,谁在亏钱」。

10.2 热力图:暴露风险的「X光片」

热力图适合展示因子暴露的分布情况。说白了,就是看你的策略在哪些风格因子上下注了,下注了多少。

我记得有一次帮朋友诊断一个量化策略,回测曲线漂亮得不像话。我画了一张热力图,发现它在「小市值」因子上的暴露高达3.5个标准差。这哪是Alpha策略?这分明是押注小盘股。后来小盘股崩了,策略也跟着崩了。

热力图的横轴通常是时间(月份/季度),纵轴是因子名称,颜色深浅代表暴露度:

import seaborn as sns
import pandas as pd
import numpy as np

# 模拟数据:12个月 × 6个因子
np.random.seed(42)
data = np.random.randn(12, 6)
df = pd.DataFrame(data,
                  columns=['动量', '价值', '质量', '成长', '低波', '规模'],
                  index=[f'2024-{m:02d}' for m in range(1, 13)])

# 画热力图
plt.figure(figsize=(12, 6))
sns.heatmap(df, cmap='RdBu_r', center=0, annot=True, fmt='.2f',
            linewidths=0.5, cbar_kws={'label': '因子暴露 (Z-score)'})
plt.title('策略因子暴露热力图(月度)')
plt.tight_layout()
plt.show()

避坑指南: 我曾经犯过一个错——直接用原始暴露值画热力图,结果不同因子的量纲不同,颜色深浅完全没意义。一定要先做标准化(Z-score),让每个因子的均值为0、标准差为1,这样颜色才有可比性。

10.3 堆积面积图:追踪收益的「时间机器」

瀑布图看的是「某个时间点」的拆解,但策略是动态的。堆积面积图能展示收益来源随时间的变化趋势。

我习惯用堆积面积图来回答一个问题:「这个策略的Alpha来源稳定吗?」如果某个因子的贡献面积忽大忽小,说明策略可能过度依赖某个不稳定的信号。

# 模拟数据:24个月的累积收益拆解
months = pd.date_range('2023-01', periods=24, freq='M')
np.random.seed(42)
factor1 = np.cumsum(np.random.randn(24) * 0.5 + 0.3)  # 动量
factor2 = np.cumsum(np.random.randn(24) * 0.3 + 0.2)  # 价值
factor3 = np.cumsum(np.random.randn(24) * 0.2 + 0.1)  # 质量
residual = np.cumsum(np.random.randn(24) * 0.1)       # 残差

plt.figure(figsize=(12, 6))
plt.stackplot(months, factor1, factor2, factor3, residual,
              labels=['动量因子', '价值因子', '质量因子', '残差'],
              colors=['#3498db', '#2ecc71', '#f39c12', '#95a5a6'],
              alpha=0.8)
plt.legend(loc='upper left')
plt.title('策略累积收益来源分解(堆积面积图)')
plt.ylabel('累积收益 (bp)')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

注意: 堆积面积图一定要用「累积值」,而不是每期的增量。否则看起来会像一堆杂乱无章的波浪线,根本看不出趋势。

10.4 雷达图:综合对比的「体检报告」

雷达图适合做横向对比。比如,比较两个策略在「收益、风险、夏普、回撤、胜率」五个维度上的表现。

我个人习惯在季度复盘时用雷达图。把当前策略和基准、以及历史最优策略放在一起,一眼就能看出「短板在哪」。

import numpy as np
import matplotlib.pyplot as plt

# 定义指标
categories = ['年化收益', '夏普比率', '最大回撤', '胜率', '信息比率']
N = len(categories)

# 两个策略的数据(注意:最大回撤是负向指标,需要取倒数或正向化)
strategy_a = [0.18, 1.5, -0.12, 0.55, 0.8]
strategy_b = [0.22, 1.8, -0.18, 0.52, 1.2]

# 正向化处理:最大回撤取绝对值后取倒数,再归一化
# 这里简化处理,直接使用原始值(假设已处理好)
values_a = strategy_a
values_b = strategy_b

# 闭合数据
values_a += values_a[:1]
values_b += values_b[:1]
angles = [n / float(N) * 2 * np.pi for n in range(N)]
angles += angles[:1]

# 画图
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
ax.plot(angles, values_a, 'o-', linewidth=2, label='策略A', color='#3498db')
ax.fill(angles, values_a, alpha=0.25, color='#3498db')
ax.plot(angles, values_b, 'o-', linewidth=2, label='策略B', color='#e74c3c')
ax.fill(angles, values_b, alpha=0.25, color='#e74c3c')

ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories)
ax.set_title('策略综合对比雷达图')
ax.legend(loc='upper right')
plt.tight_layout()
plt.show()

避坑指南: 雷达图最怕「量纲不统一」。比如年化收益是0.18,最大回撤是-0.12,放在一起画,回撤那条线几乎看不见。我的做法是:把所有指标映射到0-1之间,或者用百分位数。这样雷达图才公平。

10.5 四种图的选型指南

你可能会问:这么多图,我到底该用哪个?

嗯,我总结了一个简单的判断逻辑:

  • 想拆解「谁贡献了多少」 → 瀑布图
  • 想看「因子暴露是否集中」 → 热力图
  • 想看「收益来源是否稳定」 → 堆积面积图
  • 想对比「多个策略的综合能力」 → 雷达图

当然,实际工作中我经常把几种图组合使用。比如先画瀑布图看整体拆解,再用热力图深挖某个因子的暴露细节。你想想看,这样是不是比看一堆数字直观多了?

核心要点: 可视化不是为了好看,是为了「发现问题」。如果一张图画完,你没有任何新的发现,那这张图就是废的。