第二十五课:曲面数据可视化——3D曲面图、热力图、动态仪表盘、异常标记
做期权做市这么多年,我越来越觉得:曲面可视化不是花架子,是保命符。
你想想看,每天盯着几百个期权合约的报价,脑子里要同时处理期限结构、偏斜程度、波动率水平……光靠看数字,迟早要出问题。我见过太多交易员,明明曲面已经扭曲得不成样子了,还在那儿傻傻地报双边价。
所以这一章,咱们就聊聊怎么把曲面数据“画”出来。不是那种花里胡哨的PPT图表,而是真正能帮你做决策的实战工具。
一、3D曲面图:一眼看穿结构
3D曲面图,说白了就是把波动率曲面“立”起来看。X轴是剩余期限,Y轴是行权价(或者Delta),Z轴是隐含波动率。
我个人习惯用 plotly 来做,交互性好,能旋转、缩放。你看下面这个例子:
import plotly.graph_objects as go
import numpy as np
# 模拟曲面数据
tenors = np.array([0.1, 0.2, 0.5, 1.0, 2.0]) # 剩余期限
strikes = np.linspace(0.8, 1.2, 10) # 行权价(归一化)
T, K = np.meshgrid(tenors, strikes)
# 模拟波动率曲面(带偏斜)
vol_surface = 0.2 + 0.1 * (K - 1.0) + 0.05 * np.sqrt(T)
fig = go.Figure(data=[go.Surface(z=vol_surface, x=T, y=K)])
fig.update_layout(
title='期权隐含波动率曲面',
scene=dict(
xaxis_title='剩余期限 (年)',
yaxis_title='行权价 (归一化)',
zaxis_title='隐含波动率'
)
)
fig.show()
嗯,这里要注意:别用matplotlib的3D图。我在项目中遇到过,matplotlib的3D渲染在数据点多的时候会卡成PPT,而且没法交互。plotly的WebGL渲染才是做市场景下的正确选择。
二、热力图:快速扫描异常区域
3D图虽然直观,但有个问题:看久了眼睛累。而且如果你要同时监控多个品种,3D图占屏幕空间太大。
这时候热力图就派上用场了。说白了就是把曲面压扁,用颜色深浅表示波动率高低。
import seaborn as sns
import matplotlib.pyplot as plt
# 用上面的数据
plt.figure(figsize=(10, 6))
sns.heatmap(vol_surface,
xticklabels=np.round(tenors, 2),
yticklabels=np.round(strikes, 2),
cmap='RdYlGn_r', # 红绿配色,红色高波动
annot=True, fmt='.3f')
plt.title('波动率曲面热力图')
plt.xlabel('剩余期限')
plt.ylabel('行权价')
plt.show()
你看,热力图有个天然优势:异常点会像黑夜里的萤火虫一样显眼。比如某个格子突然变成深红色,而周围都是绿色,那这个点十有八九有问题。
RdYlGn_r 或者 coolwarm 这类发散型色板,中间色对应正常值,两端对应极端值。千万别用彩虹色,那玩意儿看着好看,但人眼对颜色的感知是非线性的,容易误判。
三、动态仪表盘:实时监控的利器
做市交易,说白了就是跟时间赛跑。静态图表只能看历史,而我们需要的是实时监控。
我个人习惯用 dash 或者 streamlit 搭一个动态仪表盘。核心逻辑是这样的:
- 数据流接入:从交易所或者做市系统实时拉取期权报价
- 曲面重建:每500ms重新计算一次波动率曲面
- 可视化更新:自动刷新3D图和热力图
- 异常检测:当曲面出现统计上的异常时,自动标记并报警
这里给一个streamlit的简单示例:
import streamlit as st
import plotly.graph_objects as go
import numpy as np
import time
st.set_page_config(layout='wide')
st.title('波动率曲面实时监控')
# 占位符
chart_placeholder = st.empty()
while True:
# 模拟实时数据更新
tenors = np.array([0.1, 0.2, 0.5, 1.0, 2.0])
strikes = np.linspace(0.8, 1.2, 10)
T, K = np.meshgrid(tenors, strikes)
# 加入随机扰动模拟实时变化
noise = np.random.normal(0, 0.005, T.shape)
vol_surface = 0.2 + 0.1 * (K - 1.0) + 0.05 * np.sqrt(T) + noise
fig = go.Figure(data=[go.Surface(z=vol_surface, x=T, y=K)])
fig.update_layout(
title=f'实时波动率曲面 - {time.strftime("%H:%M:%S")}',
scene=dict(
xaxis_title='剩余期限',
yaxis_title='行权价',
zaxis_title='隐含波动率'
)
)
chart_placeholder.plotly_chart(fig, use_container_width=True)
time.sleep(0.5)
四、异常标记:让问题自己跳出来
做市最怕什么?怕曲面出现结构性扭曲而你却没发现。
比如,某个深度虚值看涨期权的波动率突然飙升,而周围合约都很正常。这可能是有人在赌某个极端事件,也可能是数据错误。不管是哪种,你都得第一时间知道。
我常用的异常检测方法有几种:
- 局部离群因子(LOF):检测曲面上某个点是否明显偏离周围邻居
- Z-score阈值法:计算每个点的波动率与曲面均值的偏差,超过3个标准差就标记
- 时间序列对比:对比当前曲面与过去N分钟的曲面,变化超过阈值就报警
在可视化上,我习惯用散点图叠加到曲面上:
# 假设我们已经检测到了异常点
anomaly_points = [(0.5, 1.1, 0.35), (1.0, 0.85, 0.28)] # (期限, 行权价, 波动率)
fig.add_trace(go.Scatter3d(
x=[p[0] for p in anomaly_points],
y=[p[1] for p in anomaly_points],
z=[p[2] for p in anomaly_points],
mode='markers',
marker=dict(size=10, color='red', symbol='x'),
name='异常点'
))
你看,红色叉号在曲面上特别扎眼。我一般还会配合声音报警——一旦检测到异常,系统会“叮”一声,然后自动把异常区域的截图发到我的手机上。
五、知识体系总览
说了这么多,咱们来梳理一下。曲面可视化其实就干三件事:看结构、找异常、盯动态。下面这张图能帮你快速理解:
嗯,这张图其实就概括了咱们这一章的核心。3D图看宏观结构,热力图做快速扫描,动态仪表盘保证实时性,异常标记帮你抓住关键问题。四者缺一不可。
最后说一句:工具再好,也得靠人用。我见过有人把仪表盘做得跟NASA控制中心似的,结果自己从来不看。可视化不是用来炫耀的,是用来帮你赚钱的。记住这一点,你的曲面可视化才能真正发挥作用。