第十二章:曲面可视化:3D曲面图绘制、热力图绘制、动态曲面动画
曲面可视化,说白了就是把那些冷冰冰的数字变成你能一眼看懂的图形。我刚开始做期权交易那会儿,面对一堆隐含波动率数据,脑子都是懵的。直到有一天,我把它们画成了3D曲面图——嗯,那种感觉就像近视眼第一次戴上眼镜,整个世界都清晰了。
这一章,我们就来聊聊怎么把波动率曲面「画」出来。我会分享三种最常用的可视化方式:3D曲面图、热力图,还有动态曲面动画。每种方法都有自己的适用场景,咱们一个一个说。
12.1 3D曲面图:一眼看透曲面形态
3D曲面图是最直观的方式。它把行权价放在X轴,剩余期限放在Y轴,隐含波动率放在Z轴。你旋转一下视角,就能看到整个曲面的起伏变化。
我个人习惯用Matplotlib的plot_surface函数。先准备好数据:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 假设我们已经有了曲面数据
# strikes: 行权价数组, maturities: 剩余期限数组, iv_surface: 隐含波动率矩阵
strikes = np.linspace(0.8, 1.2, 10) # 标准化行权价
maturities = np.linspace(0.1, 2.0, 8) # 剩余期限(年)
X, Y = np.meshgrid(strikes, maturities)
# 模拟一个典型的波动率微笑曲面
Z = 0.2 + 0.1 * (X - 1.0)**2 + 0.05 * np.sqrt(Y)
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap='viridis', alpha=0.9)
ax.set_xlabel('行权价 (K/S)')
ax.set_ylabel('剩余期限 (年)')
ax.set_zlabel('隐含波动率')
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.title('波动率曲面 - 3D视图')
plt.show()
alpha=0.9 而不是默认的1.0,这样曲面会稍微透明一点,背后的网格线能透出来,看起来更有层次感。我在做策略回测时,经常需要对比不同日期的曲面,透明度调低一点,叠加起来看效果特别好。
这里要注意一个坑:meshgrid生成的矩阵维度。X是行权价沿着列方向扩展,Y是期限沿着行方向扩展。如果你搞反了,画出来的曲面会扭曲得不成样子。我曾经因为这个bug排查了整整一个下午,最后发现是维度顺序搞反了——嗯,从那以后我每次都会先打印一下X.shape和Y.shape。
12.2 热力图:从另一个角度看曲面
3D图虽然直观,但有时候你更关心的是「哪里波动率高,哪里波动率低」——这时候热力图就派上用场了。它把波动率数值映射成颜色,一眼就能看出曲面的「热点」和「冷区」。
import seaborn as sns
plt.figure(figsize=(10, 6))
sns.heatmap(Z, xticklabels=np.round(strikes, 2),
yticklabels=np.round(maturities, 2),
cmap='RdYlBu_r', annot=False, fmt='.3f')
plt.xlabel('行权价')
plt.ylabel('剩余期限')
plt.title('波动率曲面热力图')
plt.show()
热力图有个好处:你可以精确地看到每个格点上的数值。比如你想知道「30天到期的平值期权波动率是多少」,直接定位到对应的格子就行。3D图虽然好看,但精确读数还是得靠热力图。
12.3 动态曲面动画:捕捉时间流逝的痕迹
静态图只能看到某个时间点的曲面。但波动率曲面是动态变化的——随着时间推移,曲面会扭曲、旋转、甚至出现局部凸起。这时候,动画就派上用场了。
我用的是Matplotlib的FuncAnimation。核心思路是:把每一天的曲面数据存成一个帧,然后连续播放。
from matplotlib.animation import FuncAnimation
# 假设我们有一组历史曲面数据
# historical_surfaces: shape = (n_days, n_maturities, n_strikes)
n_days = 30
historical_surfaces = np.random.randn(n_days, 8, 10) * 0.02 + 0.25
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
def update(frame):
ax.clear()
Z = historical_surfaces[frame]
surf = ax.plot_surface(X, Y, Z, cmap='viridis', alpha=0.9)
ax.set_title(f'第 {frame+1} 天')
ax.set_zlim(0.15, 0.35)
return surf,
ani = FuncAnimation(fig, update, frames=n_days, interval=200, blit=False)
plt.show()
做动画时,有个细节容易被忽略:坐标轴范围要固定。如果不固定Z轴的范围,曲面上下浮动时,坐标轴会自动缩放,看起来就像曲面在「呼吸」——虽然视觉效果挺酷,但会误导你对波动率变化的判断。我建议用set_zlim固定一个合理的范围。
12.4 三种可视化方式的对比
| 可视化方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 3D曲面图 | 直观展示曲面整体形态 | 精确读数困难 | 趋势判断、形态识别 |
| 热力图 | 精确数值、颜色对比明显 | 缺乏立体感 | 异常检测、数值分析 |
| 动态动画 | 展示时间演化过程 | 文件大、不易分享 | 历史回放、策略复盘 |
你想想看,这三种方式其实对应了三种不同的思维模式。3D图是「空间思维」,热力图是「数值思维」,动画是「时间思维」。一个成熟的量化交易员,这三种思维都得具备。
12.5 我的工作流:从数据到决策
最后,分享一下我个人的工作流程。每天收盘后,我会做三件事:
- 先看热力图:快速扫描有没有异常凸起或凹陷。比如某个行权价的波动率突然比周围高了5个点,那肯定有问题。
- 再看3D图:旋转视角,观察曲面的整体倾斜度和曲率。是不是太陡了?是不是出现了「微笑」变「皱眉」?
- 最后看动画:对比最近30天的曲面变化。如果某个区域的波动率连续三天上升,那可能是一个交易信号。
我曾经用这个方法抓到过一次机会。当时热力图上显示,深度虚值看涨期权的波动率突然飙升,3D图上那个位置鼓起了一个「包」。我立刻意识到——有人在大量买入,可能是内部消息。后来果然出了利好公告。嗯,虽然那次我没敢重仓,但至少验证了这个方法的有效性。
好了,曲面可视化的三种方法就讲到这里。记住:工具只是手段,关键是你能不能从图形中读出市场在说什么。下一章我们会聊聊如何从这些图形中提取交易信号——那才是真正赚钱的部分。
公众号:蓝海资料掘金营,微信deep3321