项目实战(一):从零搭建一个完整的曲面套利策略
说实话,做期权交易这么多年,我最怕的就是学员问我:「老师,曲面套利到底能不能赚钱?」
我的回答永远是:能,但前提是你得有一套完整的流程。不是拍脑袋看到曲面有个凸起就冲进去,那叫赌博,不叫套利。
今天这一章,我们就从零开始,把整个曲面套利策略的流水线搭起来。数据怎么拿、信号怎么算、回测怎么做、分析怎么搞,一步到位。
一、整体框架:先看地图再上路
我个人习惯,做任何项目之前先画一张图。脑子里的想法再漂亮,落不了地就是白搭。
曲面套利策略的完整流程:
数据获取 → 曲面构建 → 信号生成 → 交易执行 → 回测验证 → 绩效分析
这六个环节,缺一个都不行。
下面这张SVG图,是我自己项目里一直用的框架。你把它存下来,以后做任何套利策略都能往上套。
二、数据获取:地基打不牢,房子会塌
你想想看,曲面套利的核心是什么?是找到曲面上的「异常点」。但如果你拿到的数据本身就是错的,那找出来的异常点就是笑话。
我在项目中遇到过最坑的一次:某数据商提供的期权行情,行权价居然有两位小数点的误差。我跑了一周的信号,全是假的。从那以后,我养成了一个习惯——数据清洗比策略本身更重要。
2.1 需要哪些数据?
| 数据类型 | 具体字段 | 频率要求 |
|---|---|---|
| 期权行情 | 合约代码、行权价、到期日、买卖价、成交量、持仓量 | 分钟级/日频 |
| 标的资产 | 最新价、前收盘、股息率、无风险利率 | 实时/日频 |
| 合约信息 | 合约乘数、交易时间、行权方式 | 静态 |
我的小技巧:数据源至少准备两个。一个做主数据,一个做校验。我常用Wind做主数据,用Tushare做交叉验证。两个对不上?那肯定有一个有问题。
2.2 数据清洗的坑
嗯,这里要注意几个点:
- 剔除异常报价:买卖价差超过正常范围3倍以上的,直接扔掉。我曾经遇到过某合约买一价0.001,卖一价999,这种数据留着干嘛?
- 处理缺失值:深度虚值合约经常没有成交。我的做法是:如果连续3个时间点都没有报价,就用前向填充。超过5个点,直接剔除该合约。
- 时间对齐:期权数据和标的数据的时间戳必须对齐。差一秒钟,算出来的隐含波动率可能差好几个点。
三、曲面构建:把散点变成连续面
数据拿到手了,接下来就是构建波动率曲面。说白了,就是把不同行权价、不同到期日的隐含波动率,拟合成一个光滑的曲面。
我个人习惯用SVI(Stochastic Volatility Inspired)模型来做。为什么?因为它参数少、拟合快、而且对微笑曲线的刻画很到位。
3.1 SVI模型的核心公式
# SVI模型参数化
# w(k) = a + b * (rho * (k - m) + sqrt((k - m)^2 + sigma^2))
# 其中:
# k = log(K / F) # 对数行权价
# a: 水平参数
# b: 倾斜参数
# rho: 偏斜参数
# m: 偏移参数
# sigma: 平滑参数
def svi_vol(k, a, b, rho, m, sigma):
"""计算SVI模型下的方差"""
return a + b * (rho * (k - m) + np.sqrt((k - m)**2 + sigma**2))
关键点:SVI拟合的是方差,不是波动率。拟合完之后要开方才能得到波动率。这个坑我踩过,当时死活对不上市场数据,后来才发现是单位问题。
3.2 拟合步骤
- 计算隐含波动率:用BS公式反推每个合约的IV
- 筛选有效合约:剔除剩余期限小于7天的合约(临近到期波动太大)
- 参数初始化:用最小二乘法给SVI参数赋初值
- 非线性优化:用Levenberg-Marquardt算法迭代求解
- 插值扩展:对拟合好的曲面做二维插值,得到任意行权价和期限的波动率
四、信号生成:找到曲面的「褶皱」
曲面建好了,接下来就是找套利机会。说白了,就是看市场报价和曲面理论值之间的偏差。
我常用的方法是Z-score统计套利:
# 计算偏差信号
def generate_signal(market_iv, surface_iv, window=20):
"""
market_iv: 市场实际隐含波动率
surface_iv: 曲面理论隐含波动率
"""
# 计算偏差
deviation = market_iv - surface_iv
# 滚动Z-score
mean_dev = deviation.rolling(window).mean()
std_dev = deviation.rolling(window).std()
z_score = (deviation - mean_dev) / std_dev
# 生成信号
# z_score > 2: 市场IV偏高,做空波动率
# z_score < -2: 市场IV偏低,做多波动率
signal = np.where(z_score > 2, -1, np.where(z_score < -2, 1, 0))
return signal, z_score
警告:Z-score的窗口期不能随便选。我测试过,20个交易日对于日频数据效果最好。太短了噪音多,太长了反应迟钝。你可以根据自己的交易频率调整。
五、回测验证:别让历史数据骗了你
回测是检验策略的唯一标准。但我要说一句:回测做得好,不代表实盘能赚钱。为什么?因为回测有太多坑。
我曾经做过一个策略,回测年化收益30%,夏普比率2.5,漂亮得不得了。结果实盘跑了三个月,亏了8%。后来一查,问题出在滑点上——回测时我假设能以中间价成交,但实盘里深度虚值合约的流动性差得要命。
5.1 回测的核心要素
| 要素 | 说明 | 我的建议 |
|---|---|---|
| 滑点模型 | 买卖价差+市场冲击 | 至少按买卖价差的50%计算滑点 |
| 交易成本 | 手续费+印花税+保证金占用 | 按实际费率上浮20% |
| 流动性限制 | 持仓量不足的合约无法成交 | 日均持仓量低于1000手的合约不交易 |
| 对冲频率 | Delta对冲的时间间隔 | 日频对冲即可,太频繁成本太高 |
5.2 回测代码框架
class SurfaceArbitrageBacktest:
def __init__(self, data, initial_capital=1000000):
self.data = data
self.capital = initial_capital
self.positions = {}
self.trades = []
def run(self):
"""执行回测"""
for date in self.data.index:
# 1. 获取当日信号
signals = self.generate_signals(date)
# 2. 执行交易
self.execute_trades(date, signals)
# 3. 日终对冲
self.delta_hedge(date)
# 4. 记录持仓
self.record_positions(date)
return self.calculate_performance()
六、绩效分析:别只看收益率
很多人做回测,一看年化收益30%就兴奋得不行。我劝你冷静。收益率只是冰山一角,水面下的东西才要命。
我一般看这几个指标:
- 夏普比率:至少要大于1.5,低于1的策略不值得实盘
- 最大回撤:不能超过15%,否则你拿不住
- 胜率:曲面套利的胜率一般在55%-65%之间,太高或太低都不正常
- 盈亏比:至少1.5:1,不然赚的钱不够亏的
- 资金曲线平滑度:这个很多人忽略。曲线越平滑,说明策略越稳健
我的经验:绩效分析不是一次性的事。每跑完一轮回测,都要回头去调整参数。曲面套利策略的参数敏感性很高,行权价筛选范围、Z-score阈值、对冲频率,每一个参数都值得反复测试。
七、写在最后
好了,从数据到回测的完整流程,我已经给你捋了一遍。你可能会觉得内容有点多,但做量化交易就是这样——80%的时间花在数据清洗和回测验证上,只有20%的时间在真正做策略。
下一章我们会用真实数据,手把手带你跑一遍这个流程。到时候你会发现,纸上谈兵和真刀真枪的差距有多大。
记住一句话:曲面套利不是印钞机,它是一把需要精心打磨的手术刀。刀磨好了,才能精准地切下市场的「脂肪」。
公众号:蓝海资料掘金营,微信deep3321