13. 曲面平滑度检测:如何检测曲面是否存在异常凸起或凹陷
做波动率曲面套利,说白了就是找「不正常」的地方。
曲面上的异常凸起或凹陷,就是我们的猎物。我做了这么多年,发现很多人盯着曲面看半天,却不知道哪里不对劲。今天咱们就聊聊,怎么用数学工具把这些「畸形」揪出来。
13.1 为什么平滑度这么重要?
一个正常的波动率曲面,应该是光滑的、渐变的。就像一块丝绸,摸起来顺滑。但市场有时候会抽风——某个深度虚值期权突然被大量买入,或者某个近月合约出现流动性真空,曲面上就会鼓起一个包,或者塌下去一个坑。
这些异常点,往往意味着:
- 套利机会:凸起处可能被高估,适合做空波动率
- 数据错误:交易所的报价有时会抽风,我遇到过某合约报价比合理值高了30%
- 市场情绪:某个行权价突然被大量交易,可能是大资金在布局
核心观点:平滑度检测不是学术游戏,它是发现套利机会的第一道筛子。
13.2 检测方法一:局部曲率分析
我最常用的方法,就是计算曲面上每个点的「局部曲率」。曲率大的地方,就是异常点。
具体怎么做?
- 对曲面进行网格化,每个网格点对应一个行权价和到期时间
- 计算每个点的一阶偏导和二阶偏导
- 用二阶偏导的绝对值作为「异常分数」
代码实现其实不复杂:
import numpy as np
from scipy.ndimage import gaussian_filter
def detect_anomalies(surface, sigma=1.5, threshold=2.0):
"""
surface: 二维数组,行=行权价,列=到期时间
sigma: 高斯平滑参数
threshold: 异常阈值(标准差倍数)
"""
# 先平滑,得到基准曲面
smooth_surface = gaussian_filter(surface, sigma=sigma)
# 计算残差
residual = surface - smooth_surface
# 计算局部曲率(用拉普拉斯算子近似)
from scipy.ndimage import laplace
curvature = laplace(surface)
# 异常分数 = 残差 * 曲率
anomaly_score = np.abs(residual * curvature)
# 标记异常点
mean_score = np.mean(anomaly_score)
std_score = np.std(anomaly_score)
anomalies = anomaly_score > (mean_score + threshold * std_score)
return anomalies, anomaly_score
我的经验:sigma参数很关键。太小了会把正常波动也当成异常,太大了又会漏掉真正的异常。我一般先用1.5试跑,然后根据结果调整。
13.3 检测方法二:样条插值对比法
这个方法更直观。你想想看,如果我们用光滑的样条曲线去拟合曲面,拟合误差大的地方,就是异常点。
步骤是这样的:
- 用三次样条对曲面进行插值,得到一个光滑版本
- 计算原始曲面和插值曲面的差值
- 差值超过3个标准差的地方,标记为异常
我曾经用这个方法发现过一个很有意思的现象:某个行权价的波动率连续三天都比样条插值结果高5%以上。后来一查,原来是某家做市商在故意拉高那个行权价的报价,想诱多。嗯,这种信息对交易决策很有价值。
from scipy.interpolate import SmoothBivariateSpline
def spline_detection(strikes, maturities, iv_surface, s=0.1):
"""
strikes: 行权价数组
maturities: 到期时间数组
iv_surface: 波动率曲面(二维数组)
s: 平滑参数,越大越光滑
"""
# 构建网格
X, Y = np.meshgrid(strikes, maturities)
x_flat = X.flatten()
y_flat = Y.flatten()
z_flat = iv_surface.flatten()
# 拟合样条
spline = SmoothBivariateSpline(x_flat, y_flat, z_flat, s=s)
# 在原始网格点上评估
smooth_surface = spline.ev(X.flatten(), Y.flatten()).reshape(X.shape)
# 计算残差
residual = iv_surface - smooth_surface
# 异常检测
threshold = 3 * np.std(residual)
anomalies = np.abs(residual) > threshold
return anomalies, residual
注意:样条插值的平滑参数s需要谨慎选择。s太小,样条会过拟合,把正常波动也当成异常;s太大,又会过度平滑,漏掉真正的异常。我建议先用交叉验证选一个合适的s值。
13.4 检测方法三:相邻点比较法
这个方法最简单,也最实用。不需要复杂的数学,就是比较每个点跟它周围邻居的差异。
具体来说:
- 对每个点,取它周围8个邻居(3x3窗口)
- 计算该点与邻居均值的差异
- 如果差异超过某个阈值,标记为异常
为什么这个方法好用?因为真正的异常往往是孤立的。如果一大片区域都偏高,那可能是整个期限结构在变化,不是异常。只有那种「鹤立鸡群」的点,才值得我们关注。
def neighbor_comparison(surface, window_size=3, threshold=2.5):
from scipy.ndimage import uniform_filter
# 计算局部均值
local_mean = uniform_filter(surface, size=window_size)
# 计算局部标准差
local_std = np.sqrt(uniform_filter((surface - local_mean)**2, size=window_size))
# 避免除零
local_std = np.maximum(local_std, 1e-8)
# Z-score
z_scores = (surface - local_mean) / local_std
# 标记异常
anomalies = np.abs(z_scores) > threshold
return anomalies, z_scores
避坑指南:我曾经用3x3窗口检测5分钟级别的数据,结果发现异常点太多。后来改成5x5窗口,效果就好多了。窗口大小要根据数据频率来调整,没有万能参数。
13.5 三种方法的对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 局部曲率分析 | 数学严谨,能检测微小异常 | 对噪声敏感,需要调参 | 高频数据、精细检测 |
| 样条插值对比 | 直观易懂,结果可解释 | 计算量大,边界效应明显 | 日频数据、研究分析 |
| 相邻点比较 | 简单快速,鲁棒性好 | 可能漏掉区域性异常 | 实时监控、快速筛查 |
13.6 实战中的注意事项
说了这么多方法,最后分享几个实战心得:
- 不要只看一个指标:我习惯同时用三种方法跑一遍,取交集。三个方法都标记为异常的点,基本就是真异常了。
- 注意边界点:曲面边缘的点,邻居少,检测结果不可靠。我一般会忽略最外两行两列的点。
- 结合成交量看:如果一个点被标记为异常,但成交量很小,那可能是数据错误。如果成交量很大,那才是真正的套利机会。
- 动态调整阈值:市场波动大的时候,阈值要放宽。波动小的时候,阈值要收紧。我一般用滚动窗口的动态阈值。
记住:平滑度检测只是第一步。发现异常后,还要分析原因、评估风险、制定策略。但如果没有这一步,你就像在黑暗中找东西,全靠运气。
13.7 知识体系图
下面这张图总结了曲面平滑度检测的核心逻辑:
这张图把整个检测流程串起来了。从原始曲面出发,三种方法并行计算,最后综合判断。我个人习惯把三种方法的异常点取交集,这样误报率最低。
公众号:蓝海资料掘金营,微信deep3321