第1章:实盘数据获取——用Python抓取期权链与计算隐含波动率

做期权交易,最怕什么?

我个人觉得,最怕的是「闭着眼睛做」。你连当前市场的隐含波动率是多少都不知道,那跟赌博有什么区别?

这一章,咱们就来解决这个最基础、也最关键的问题——怎么用Python拿到实盘期权数据,并算出隐含波动率

1.1 数据源的选择:别在第一步就踩坑

先说说数据源。我刚开始做量化的时候,也踩过不少坑。有的数据源免费但延迟高,有的收费但接口不稳定。

目前国内比较靠谱的期权数据源,我列一下:

数据源 特点 费用 我个人的建议
Wind 数据全,接口稳定 贵(机构用) 机构首选,个人不推荐
聚宽/JQData 有免费额度,适合学习 部分免费 初学者可以先用这个
东方财富Choice 数据质量不错 中等 性价比还行
新浪/腾讯财经API 免费,但需要自己解析 免费 适合练手,别用于实盘
小提示: 我个人习惯先用聚宽做策略回测,实盘时再切换到Wind。别在数据源上省钱,数据不准,策略再好也是白搭。

1.2 抓取期权链数据:代码来了

好,咱们直接上代码。这里我用的是聚宽的数据接口,因为免费且容易上手。

import pandas as pd
import numpy as np
from jqdatasdk import *
import datetime

# 登录聚宽(需要注册账号)
auth('你的账号', '你的密码')

# 获取50ETF期权链
def get_option_chain(date, etf_code='510050.XSHG'):
    """
    获取指定日期的期权链数据
    """
    # 获取所有期权合约
    all_options = get_all_securities(['option'], date=date)
    
    # 筛选出50ETF期权
    etf_options = all_options[all_options['display_name'].str.contains('50ETF')]
    
    # 获取合约信息
    option_data = []
    for code in etf_options.index:
        # 获取合约基本信息
        info = get_security_info(code)
        
        # 获取当日行情
        day_data = get_price(code, start_date=date, end_date=date, 
                            fields=['open', 'close', 'high', 'low', 'volume', 'money'])
        
        if not day_data.empty:
            option_data.append({
                'code': code,
                'name': info.display_name,
                'type': 'call' if '购' in info.display_name else 'put',
                'strike': info.strike_price,
                'expire_date': info.end_date,
                'close': day_data['close'].values[0],
                'volume': day_data['volume'].values[0]
            })
    
    return pd.DataFrame(option_data)

# 获取今天的数据
today = datetime.date.today()
option_chain = get_option_chain(today)
print(f"共获取到 {len(option_chain)} 个期权合约")
print(option_chain.head())
重点: 这段代码的核心是get_all_securities和get_price两个函数。前者拿到所有期权合约列表,后者获取具体行情。我在项目中遇到过一个问题——有些合约当天没有交易,成交量是0,这种合约的隐含波动率算出来也没意义,记得过滤掉。

1.3 计算隐含波动率:这才是硬核部分

拿到期权价格了,怎么算隐含波动率?

说白了,隐含波动率就是「把市场价格代入BS公式,反推出来的波动率」。但BS公式没法直接求逆,得用数值方法。

我常用的方法是牛顿迭代法。为什么?因为它收敛快,一般迭代5-10次就能得到精确结果。

from scipy.stats import norm
from scipy.optimize import brentq

def bs_price(S, K, T, r, sigma, option_type='call'):
    """
    Black-Scholes定价公式
    S: 标的价格
    K: 行权价
    T: 剩余时间(年)
    r: 无风险利率
    sigma: 波动率
    """
    d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
    d2 = d1 - sigma*np.sqrt(T)
    
    if option_type == 'call':
        price = S*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2)
    else:
        price = K*np.exp(-r*T)*norm.cdf(-d2) - S*norm.cdf(-d1)
    
    return price

def implied_volatility(market_price, S, K, T, r, option_type='call'):
    """
    使用牛顿迭代法计算隐含波动率
    """
    # 初始猜测
    sigma = 0.3
    
    # 牛顿迭代
    for i in range(100):
        price = bs_price(S, K, T, r, sigma, option_type)
        vega = S * norm.pdf(d1(S, K, T, r, sigma)) * np.sqrt(T)
        
        diff = price - market_price
        
        if abs(diff) < 1e-6:
            break
        
        sigma = sigma - diff / vega
        
        # 防止波动率变成负数
        if sigma < 0.01:
            sigma = 0.01
    
    return sigma

def d1(S, K, T, r, sigma):
    return (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))

# 计算整个期权链的隐含波动率
def calculate_chain_iv(option_chain, S, r=0.03):
    """
    计算期权链上所有合约的隐含波动率
    """
    results = []
    
    for _, row in option_chain.iterrows():
        # 计算剩余时间(年)
        T = (row['expire_date'] - today).days / 365.0
        
        if T <= 0 or row['close'] <= 0:
            continue
        
        try:
            iv = implied_volatility(
                row['close'], S, row['strike'], T, r, 
                'call' if '购' in row['name'] else 'put'
            )
            
            results.append({
                'strike': row['strike'],
                'type': row['type'],
                'iv': iv,
                'volume': row['volume']
            })
        except:
            # 有些合约可能算不出来,跳过
            continue
    
    return pd.DataFrame(results)

# 假设50ETF当前价格是2.800
S = 2.800
iv_data = calculate_chain_iv(option_chain, S)
print("隐含波动率计算结果:")
print(iv_data.head())
注意: 我曾经在实盘中遇到过一个问题——临近到期的期权,时间价值很小,隐含波动率计算容易发散。我的经验是:剩余时间少于7天的合约,直接过滤掉,算出来的值也不可靠。

1.4 可视化:看一眼波动率微笑

数据算出来了,光看数字不够直观。咱们画个图,看看波动率微笑长什么样。

import matplotlib.pyplot as plt

def plot_volatility_smile(iv_data):
    """
    绘制波动率微笑曲线
    """
    plt.figure(figsize=(12, 6))
    
    # 分别画看涨和看跌
    for opt_type in ['call', 'put']:
        subset = iv_data[iv_data['type'] == opt_type]
        plt.plot(subset['strike'], subset['iv'], 
                'o-', label=f'{opt_type}期权', linewidth=2)
    
    plt.xlabel('行权价')
    plt.ylabel('隐含波动率')
    plt.title('50ETF期权波动率微笑')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.show()

plot_volatility_smile(iv_data)

你想想看,如果画出来的曲线是平的,说明市场认为未来波动不大。如果左边翘起来(虚值看跌期权IV高),说明市场在担心下跌——这就是偏斜。

1.5 知识体系总览

这一章的内容,我画了个图帮你理清思路:

实盘数据获取与隐含波动率计算 数据源选择 API接口调用 数据清洗过滤 BS公式定价 牛顿迭代法 隐含波动率 波动率微笑 波动率偏斜

1.6 避坑指南:我踩过的那些坑

  • 数据延迟问题: 我曾经用免费API做日内交易,结果数据延迟了3秒,策略直接亏了2%。后来我学乖了——做日内交易必须用Level-2数据
  • 合约过滤: 有些期权合约流动性极差,一天就成交几手。这种合约的隐含波动率算出来也没参考价值。我一般会过滤掉成交量低于100手的合约。
  • 除权除息: 50ETF分红的时候,期权合约会调整。如果不做复权处理,算出来的隐含波动率会突变。嗯,这个坑我踩过两次才记住。
  • 无风险利率: 别用固定的3%!我建议用SHIBOR 1个月利率,或者国债收益率。利率差0.5%,隐含波动率能差出1-2个点。
我的习惯: 每天开盘前,我会先跑一遍这个脚本,看看当前市场的波动率结构。如果发现某个行权价的IV异常高,我会特别关注——往往意味着有大资金在押注某个方向。

好了,这一章的内容就到这里。数据拿到了,隐含波动率也算出来了,下一章咱们就可以开始分析波动率微笑的形态了。

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