第二章:曲面构建数据源——期权链数据获取、清洗与预处理、到期日与行权价标准化
做波动率曲面,第一步不是建模,不是插值。
第一步是——拿到干净的数据。
我见过太多团队,模型跑得飞起,结果曲面一出来全是毛刺。为什么?数据源就有问题。期权链数据看着简单,其实坑特别多。今天我就把这块的实战经验掰开揉碎讲给你听。
2.1 期权链数据获取:别只盯着一个交易所
期权链数据,说白了就是某个标的物所有期权合约的实时快照。包括行权价、到期日、买卖价、成交量、持仓量、隐含波动率等等。
我个人习惯,数据源至少要覆盖三个维度:
- 交易所直连:比如上交所的SSE期权、中金所的股指期权。延迟最低,但成本高。
- 数据商API:像Wind、Bloomberg、Quandl。方便,但注意数据可能有延迟或缺失。
- 自建爬虫:某些公开数据源,比如期权计算器网站。嗯,这个只适合回测,别用于实盘。
获取数据时,我建议用WebSocket实时流,别用REST轮询。为什么?因为期权链数据变化极快,尤其是临近到期日,一秒可能跳几十次。轮询根本跟不上。
# 伪代码示例:WebSocket订阅期权链
import websocket
import json
def on_message(ws, message):
data = json.loads(message)
# 解析期权链数据
for contract in data['contracts']:
process_contract(contract)
ws = websocket.WebSocketApp("wss://option-chain.example.com/stream",
on_message=on_message)
ws.run_forever()
2.2 数据清洗与预处理:脏数据是曲面的天敌
拿到原始数据后,别急着用。先做三件事:
- 去重:同一个合约可能被推送多次,保留最新时间戳的那条。
- 过滤异常值:比如买卖价差超过阈值、隐含波动率为负、成交量异常大但持仓量为0。这些基本都是数据错误。
- 对齐时间戳:不同合约的报价时间可能差几毫秒,需要统一到同一个时间基准。
我曾经遇到过一个经典案例:某只股票期权在开盘后第一分钟,所有合约的隐含波动率突然飙升到200%。我一开始以为是市场异动,后来发现是数据商把昨收盘价当成了当前价。嗯,这种坑,踩过一次就记住了。
清洗时,我常用的几个过滤器:
- 买卖价差 > 中间价的5% → 丢弃
- 隐含波动率 < 0 或 > 200% → 丢弃
- 成交量 < 10 且 持仓量 < 100 → 标记为低流动性合约,不参与曲面构建
2.3 到期日标准化:把时间变成统一的尺子
不同合约的到期日不一样。有的剩3天,有的剩30天,有的剩180天。直接拿原始到期日去构建曲面,你会发现曲面在时间轴上分布极不均匀。
我个人的做法是:把到期日转换成剩余期限(Time to Expiry),单位用年。
# 计算剩余期限(年)
from datetime import datetime, date
def time_to_expiry(expiry_date, current_date=None):
if current_date is None:
current_date = date.today()
delta = expiry_date - current_date
return delta.days / 365.0
但这里有个细节:交易日历。你想想看,周末和节假日市场不开盘,但时间还在走。严格来说,应该用交易日天数除以252,而不是自然日除以365。
标准化到期日时,我建议把剩余期限分成几个标准桶:
| 桶名称 | 剩余期限范围 | 代表合约 |
|---|---|---|
| 短期 | 0-7天 | 当周合约 |
| 中短期 | 7-30天 | 次周、当月合约 |
| 中期 | 30-90天 | 次月、季度合约 |
| 长期 | 90-365天 | 半年、一年合约 |
2.4 行权价标准化:把价格变成相对值
行权价的问题更隐蔽。不同标的物的价格不一样,比如茅台期权行权价是2000,中证500期权行权价是6000。直接拿绝对行权价去构建曲面,不同标的之间没法比较。
解决方案:用货币化程度(Moneyness)代替绝对行权价。
常用的货币化程度定义有两种:
- 简单货币化:行权价 / 标的物价格。比如标的物价格5000,行权价5200,货币化程度就是1.04。
- 对数货币化:ln(行权价 / 标的物价格)。这个在学术论文里更常见。
我个人习惯用简单货币化,因为直观。但要注意:股息和利率会影响货币化程度的计算。如果标的物有股息,或者利率很高,需要用远期价格代替现货价格。
# 货币化程度计算
def moneyness(strike, spot, dividend_yield=0, risk_free_rate=0, time_to_expiry=1):
# 考虑股息和利率的远期价格
forward = spot * exp((risk_free_rate - dividend_yield) * time_to_expiry)
return strike / forward
标准化行权价时,我建议把货币化程度分成几个标准区间:
- 深度实值:0.80 - 0.95
- 实值:0.95 - 0.99
- 平值:0.99 - 1.01
- 虚值:1.01 - 1.05
- 深度虚值:1.05 - 1.20
2.5 整体流程:一张图说清楚
说了这么多,我把整个数据处理的流程画成了一张图。你一看就明白。
2.6 实战中的几个坑
最后,分享几个我踩过的坑,你遇到了可以少走弯路:
- 坑一:不同交易所的合约代码规则不一样。上交所是"510050C2209M02800",中金所是"IO2209-C-2800"。解析时一定要写通用解析器。
- 坑二:临近到期日的合约,流动性极差。我建议剩余期限小于1天的合约直接剔除,不然曲面尾部会剧烈抖动。
- 坑三:行权价标准化时,如果标的物价格发生跳空(比如分红、拆股),货币化程度会突变。需要提前检测并调整。
好了,数据源这块就讲到这里。数据干净了,曲面构建才有意义。下一章我们聊聊插值方法——怎么把离散的期权数据变成连续的曲面。