第十二章:曲面可视化: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图虽然好看,但精确读数还是得靠热力图。

实战经验: 我在做波动率套利时,经常把热力图和3D图放在一起看。先用热力图快速定位异常区域,再用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()
注意: 动画的帧数不要太多。我一般控制在30-60帧,再多的话文件会很大,而且播放起来卡顿。如果你有几百天的数据,建议先做降采样,或者只展示关键时间节点。

做动画时,有个细节容易被忽略:坐标轴范围要固定。如果不固定Z轴的范围,曲面上下浮动时,坐标轴会自动缩放,看起来就像曲面在「呼吸」——虽然视觉效果挺酷,但会误导你对波动率变化的判断。我建议用set_zlim固定一个合理的范围。

12.4 三种可视化方式的对比

可视化方式 优点 缺点 适用场景
3D曲面图 直观展示曲面整体形态 精确读数困难 趋势判断、形态识别
热力图 精确数值、颜色对比明显 缺乏立体感 异常检测、数值分析
动态动画 展示时间演化过程 文件大、不易分享 历史回放、策略复盘

你想想看,这三种方式其实对应了三种不同的思维模式。3D图是「空间思维」,热力图是「数值思维」,动画是「时间思维」。一个成熟的量化交易员,这三种思维都得具备。

12.5 我的工作流:从数据到决策

最后,分享一下我个人的工作流程。每天收盘后,我会做三件事:

  1. 先看热力图:快速扫描有没有异常凸起或凹陷。比如某个行权价的波动率突然比周围高了5个点,那肯定有问题。
  2. 再看3D图:旋转视角,观察曲面的整体倾斜度和曲率。是不是太陡了?是不是出现了「微笑」变「皱眉」?
  3. 最后看动画:对比最近30天的曲面变化。如果某个区域的波动率连续三天上升,那可能是一个交易信号。

我曾经用这个方法抓到过一次机会。当时热力图上显示,深度虚值看涨期权的波动率突然飙升,3D图上那个位置鼓起了一个「包」。我立刻意识到——有人在大量买入,可能是内部消息。后来果然出了利好公告。嗯,虽然那次我没敢重仓,但至少验证了这个方法的有效性。

避坑指南: 做动画时,记得把每一帧的曲面数据保存下来。我曾经因为电脑死机,做了两小时的动画全丢了。现在我会边生成边保存到本地,每10帧存一次。血的教训啊。

好了,曲面可视化的三种方法就讲到这里。记住:工具只是手段,关键是你能不能从图形中读出市场在说什么。下一章我们会聊聊如何从这些图形中提取交易信号——那才是真正赚钱的部分。


公众号:蓝海资料掘金营,微信deep3321