衍生品与波动率

核心问题: 波动率不仅是风险度量,也是可交易资产类别,如何利用期权数据增强股票预测?


学习目标

  • 理解 Black-Scholes 定价模型的推导与假设
  • 掌握 Greeks 的含义与对冲应用
  • 学习隐含波动率与波动率微笑现象
  • 了解波动率曲面的构建与插值方法
  • 理解波动率作为因子的预测力
  • 核心: 期权数据包含市场预期,是股票预测的重要信号

一、Black-Scholes 定价模型

1.1 模型假设

Black-Scholes (BS) 模型是期权定价的基石,基于以下假设:

假设现实情况影响
股价服从几何布朗运动有跳跃、肥尾低估极端事件风险
波动率恒定波动率随时间、价格变化需要波动率曲面
无交易成本存在买卖价差、冲击对冲成本被忽视
连续交易市场有闭市时间隔夜风险
无风险利率恒定利率变动对长期期权影响大

1.2 BS 公式

看涨期权价格:

看跌期权价格:

其中:

变量说明:

  • : 标的资产当前价格
  • : 行权价
  • : 无风险利率
  • : 到期时间(年)
  • : 波动率
  • : 标准正态分布的累积分布函数

1.3 Python 实现

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import norm
from scipy.optimize import brentq
 
def black_scholes_call(S, K, r, T, sigma):
    """
    Black-Scholes 看涨期权定价
 
    参数:
        S: 标的资产价格
        K: 行权价
        r: 无风险利率
        T: 到期时间(年)
        sigma: 波动率
 
    返回:
        看涨期权价格
    """
    if T <= 0:
        return max(S - K, 0)
 
    d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
 
    call_price = S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
 
    return call_price
 
def black_scholes_put(S, K, r, T, sigma):
    """
    Black-Scholes 看跌期权定价
    """
    if T <= 0:
        return max(K - S, 0)
 
    d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
 
    put_price = K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
 
    return put_price
 
# 示例计算
print("=" * 60)
print("Black-Scholes 期权定价示例")
print("=" * 60)
 
# 参数设置
S = 100      # 标的资产价格
K = 100      # 行权价(平值期权)
r = 0.05     # 无风险利率 5%
T = 0.25     # 3 个月到期
sigma = 0.2  # 20% 年化波动率
 
call_price = black_scholes_call(S, K, r, T, sigma)
put_price = black_scholes_put(S, K, r, T, sigma)
 
print(f"\n输入参数:")
print(f"  标的资产价格 (S): {S}")
print(f"  行权价 (K): {K}")
print(f"  无风险利率 (r): {r:.2%}")
print(f"  到期时间 (T): {T:.2f} 年 ({T*12:.1f} 个月)")
print(f"  波动率 (σ): {sigma:.2%}")
 
print(f"\n期权价格:")
print(f"  看涨期权: ${call_price:.4f}")
print(f"  看跌期权: ${put_price:.4f}")
 
# 验证看跌-看涨平价关系: C - P = S - K*e^(-rT)
parity_lhs = call_price - put_price
parity_rhs = S - K * np.exp(-r * T)
 
print(f"\n看跌-看涨平价验证:")
print(f"  C - P = ${parity_lhs:.4f}")
print(f"  S - K*e^(-rT) = ${parity_rhs:.4f}")
print(f"  差异: ${abs(parity_lhs - parity_rhs):.6f}")
print("=" * 60)

1.4 期权价格与各变量关系

# 可视化期权价格与各变量的关系
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
 
# 1. 与标的价格的关系
ax1 = axes[0, 0]
S_range = np.linspace(80, 120, 100)
call_prices_S = [black_scholes_call(s, K, r, T, sigma) for s in S_range]
put_prices_S = [black_scholes_put(s, K, r, T, sigma) for s in S_range]
ax1.plot(S_range, call_prices_S, label='看涨期权', linewidth=2)
ax1.plot(S_range, put_prices_S, label='看跌期权', linewidth=2)
ax1.axvline(S, color='red', linestyle='--', alpha=0.5, label='当前标的价格')
ax1.axhline(max(S-K, 0), color='green', linestyle=':', alpha=0.5, label='看涨期权内在价值')
ax1.set_xlabel('标的价格')
ax1.set_ylabel('期权价格')
ax1.set_title('期权价格 vs 标的价格')
ax1.legend()
ax1.grid(True, alpha=0.3)
 
# 2. 与波动率的关系
ax2 = axes[0, 1]
sigma_range = np.linspace(0.1, 0.5, 100)
call_prices_sigma = [black_scholes_call(S, K, r, T, s) for s in sigma_range]
put_prices_sigma = [black_scholes_put(S, K, r, T, s) for s in sigma_range]
ax2.plot(sigma_range, call_prices_sigma, label='看涨期权', linewidth=2)
ax2.plot(sigma_range, put_prices_sigma, label='看跌期权', linewidth=2)
ax2.axvline(sigma, color='red', linestyle='--', alpha=0.5, label='当前波动率')
ax2.set_xlabel('波动率')
ax2.set_ylabel('期权价格')
ax2.set_title('期权价格 vs 波动率 (Vega)')
ax2.legend()
ax2.grid(True, alpha=0.3)
 
# 3. 与到期时间的关系
ax3 = axes[0, 2]
T_range = np.linspace(0.01, 1, 100)
call_prices_T = [black_scholes_call(S, K, r, t, sigma) for t in T_range]
put_prices_T = [black_scholes_put(S, K, r, t, sigma) for t in T_range]
ax3.plot(T_range, call_prices_T, label='看涨期权', linewidth=2)
ax3.plot(T_range, put_prices_T, label='看跌期权', linewidth=2)
ax3.axvline(T, color='red', linestyle='--', alpha=0.5, label='当前到期时间')
ax3.set_xlabel('到期时间(年)')
ax3.set_ylabel('期权价格')
ax3.set_title('期权价格 vs 到期时间 (Theta)')
ax3.legend()
ax3.grid(True, alpha=0.3)
 
# 4. 与行权价的关系
ax4 = axes[1, 0]
K_range = np.linspace(80, 120, 100)
call_prices_K = [black_scholes_call(S, k, r, T, sigma) for k in K_range]
put_prices_K = [black_scholes_put(S, k, r, T, sigma) for k in K_range]
ax4.plot(K_range, call_prices_K, label='看涨期权', linewidth=2)
ax4.plot(K_range, put_prices_K, label='看跌期权', linewidth=2)
ax4.axvline(K, color='red', linestyle='--', alpha=0.5, label='当前行权价')
ax4.set_xlabel('行权价')
ax4.set_ylabel('期权价格')
ax4.set_title('期权价格 vs 行权价')
ax4.legend()
ax4.grid(True, alpha=0.3)
 
# 5. 与利率的关系
ax5 = axes[1, 1]
r_range = np.linspace(0, 0.15, 100)
call_prices_r = [black_scholes_call(S, K, rate, T, sigma) for rate in r_range]
put_prices_r = [black_scholes_put(S, K, rate, T, sigma) for rate in r_range]
ax5.plot(r_range, call_prices_r, label='看涨期权', linewidth=2)
ax5.plot(r_range, put_prices_r, label='看跌期权', linewidth=2)
ax5.axvline(r, color='red', linestyle='--', alpha=0.5, label='当前利率')
ax5.set_xlabel('无风险利率')
ax5.set_ylabel('期权价格')
ax5.set_title('期权价格 vs 无风险利率 (Rho)')
ax5.legend()
ax5.grid(True, alpha=0.3)
 
# 6. 期权内在价值与时间价值
ax6 = axes[1, 2]
# 看涨期权
S_range = np.linspace(80, 120, 100)
call_intrinsic = [max(s - K, 0) for s in S_range]
call_time_value = [black_scholes_call(s, K, r, T, sigma) - max(s - K, 0) for s in S_range]
ax6.plot(S_range, call_intrinsic, label='内在价值', linewidth=2, linestyle='--')
ax6.plot(S_range, call_time_value, label='时间价值', linewidth=2, alpha=0.7)
ax6.plot(S_range, call_prices_S, label='总价值', linewidth=2, color='black')
ax6.axvline(K, color='red', linestyle=':', alpha=0.5, label='行权价')
ax6.fill_between(S_range, 0, call_time_value, alpha=0.2, color='orange', label='时间价值区域')
ax6.set_xlabel('标的价格')
ax6.set_ylabel('价值')
ax6.set_title('看涨期权: 内在价值 vs 时间价值')
ax6.legend()
ax6.grid(True, alpha=0.3)
 
plt.tight_layout()
plt.show()

二、Greeks 风险指标

2.1 定义与含义

Greeks 衡量期权价格对不同变量的敏感度:

Greek符号定义含义对冲意义
DeltaΔ∂C/∂S价格变动敏感度对冲标的资产价格风险
GammaΓ∂²C/∂S²Delta 变化率对冲凸性风险
Vegaν∂C/∂σ波动率敏感度对冲波动率风险
ThetaΘ∂C/∂T时间衰减衡量时间成本
Rhoρ∂C/∂r利率敏感度对冲利率风险

2.2 Greeks 计算公式

Delta (看涨期权):

Delta (看跌期权):

Gamma (看涨/看跌相同):

Vega (看涨/看跌相同):

Theta (看涨期权):

Rho (看涨期权):

2.3 Python 实现

def calculate_d1_d2(S, K, r, T, sigma):
    """计算 d1 和 d2"""
    if T <= 0:
        return 0, 0
 
    d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
 
    return d1, d2
 
def delta_call(S, K, r, T, sigma):
    """看涨期权 Delta"""
    d1, _ = calculate_d1_d2(S, K, r, T, sigma)
    return norm.cdf(d1)
 
def delta_put(S, K, r, T, sigma):
    """看跌期权 Delta"""
    return delta_call(S, K, r, T, sigma) - 1
 
def gamma(S, K, r, T, sigma):
    """Gamma (看涨和看跌相同)"""
    d1, _ = calculate_d1_d2(S, K, r, T, sigma)
    return norm.pdf(d1) / (S * sigma * np.sqrt(T))
 
def vega(S, K, r, T, sigma):
    """Vega (看涨和看跌相同)"""
    d1, _ = calculate_d1_d2(S, K, r, T, sigma)
    return S * norm.pdf(d1) * np.sqrt(T) / 100  # 通常以 1% 波动率变化为单位
 
def theta_call(S, K, r, T, sigma):
    """看涨期权 Theta"""
    d1, d2 = calculate_d1_d2(S, K, r, T, sigma)
    theta = (
        -S * norm.pdf(d1) * sigma / (2 * np.sqrt(T)) -
        r * K * np.exp(-r * T) * norm.cdf(d2)
    )
    return theta / 365  # 以天为单位
 
def theta_put(S, K, r, T, sigma):
    """看跌期权 Theta"""
    d1, d2 = calculate_d1_d2(S, K, r, T, sigma)
    theta = (
        -S * norm.pdf(d1) * sigma / (2 * np.sqrt(T)) +
        r * K * np.exp(-r * T) * norm.cdf(-d2)
    )
    return theta / 365
 
def rho_call(S, K, r, T, sigma):
    """看涨期权 Rho"""
    _, d2 = calculate_d1_d2(S, K, r, T, sigma)
    return K * T * np.exp(-r * T) * norm.cdf(d2) / 100  # 以 1% 利率变化为单位
 
# 计算 Greeks
print("\n【Greeks 计算】")
print("=" * 60)
print(f"{'Greek':<15} {'看涨期权':<15} {'看跌期权':<15}")
print("-" * 60)
 
greeks_call = {
    'Delta': delta_call(S, K, r, T, sigma),
    'Gamma': gamma(S, K, r, T, sigma),
    'Vega': vega(S, K, r, T, sigma),
    'Theta': theta_call(S, K, r, T, sigma),
    'Rho': rho_call(S, K, r, T, sigma)
}
 
greeks_put = {
    'Delta': delta_put(S, K, r, T, sigma),
    'Gamma': gamma(S, K, r, T, sigma),
    'Vega': vega(S, K, r, T, sigma),
    'Theta': theta_put(S, K, r, T, sigma),
}
 
for greek in ['Delta', 'Gamma', 'Vega', 'Theta', 'Rho']:
    call_val = greeks_call[greek]
    put_val = greeks_put.get(greek, greeks_call[greek])
    print(f"{greek:<15} {call_val:>13.4f}   {put_val:>13.4f}")
 
print("=" * 60)

2.4 Greeks 可视化

# 可视化 Greeks 与标的价格的关系
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
 
S_range = np.linspace(80, 120, 100)
 
# Delta
ax1 = axes[0, 0]
call_delta = [delta_call(s, K, r, T, sigma) for s in S_range]
put_delta = [delta_put(s, K, r, T, sigma) for s in S_range]
ax1.plot(S_range, call_delta, label='看涨 Delta', linewidth=2)
ax1.plot(S_range, put_delta, label='看跌 Delta', linewidth=2)
ax1.axhline(0, color='black', linestyle=':', alpha=0.5)
ax1.axhline(0.5, color='red', linestyle='--', alpha=0.5, label='平值 Delta ≈ 0.5')
ax1.axvline(S, color='green', linestyle='--', alpha=0.5)
ax1.set_xlabel('标的价格')
ax1.set_ylabel('Delta')
ax1.set_title('Delta vs 标的价格')
ax1.legend()
ax1.grid(True, alpha=0.3)
 
# Gamma
ax2 = axes[0, 1]
gammas = [gamma(s, K, r, T, sigma) for s in S_range]
ax2.plot(S_range, gammas, linewidth=2, color='purple')
ax2.axvline(K, color='red', linestyle='--', alpha=0.5, label='行权价(Gamma 峰值)')
ax2.fill_between(S_range, 0, gammas, alpha=0.2, color='purple')
ax2.set_xlabel('标的价格')
ax2.set_ylabel('Gamma')
ax2.set_title('Gamma vs 标的价格')
ax2.legend()
ax2.grid(True, alpha=0.3)
 
# Vega
ax3 = axes[0, 2]
vegas = [vega(s, K, r, T, sigma) for s in S_range]
ax3.plot(S_range, vegas, linewidth=2, color='green')
ax3.axvline(K, color='red', linestyle='--', alpha=0.5, label='行权价(Vega 峰值)')
ax3.set_xlabel('标的价格')
ax3.set_ylabel('Vega')
ax3.set_title('Vega vs 标的价格')
ax3.legend()
ax3.grid(True, alpha=0.3)
 
# Theta
ax4 = axes[1, 0]
call_thetas = [theta_call(s, K, r, T, sigma) for s in S_range]
put_thetas = [theta_put(s, K, r, T, sigma) for s in S_range]
ax4.plot(S_range, call_thetas, label='看涨 Theta', linewidth=2)
ax4.plot(S_range, put_thetas, label='看跌 Theta', linewidth=2)
ax4.axhline(0, color='black', linestyle=':', alpha=0.5)
ax4.set_xlabel('标的价格')
ax4.set_ylabel('Theta (每天)')
ax4.set_title('Theta vs 标的价格')
ax4.legend()
ax4.grid(True, alpha=0.3)
 
# Delta vs 时间
ax5 = axes[1, 1]
T_range = np.linspace(0.01, 1, 100)
call_delta_T = [delta_call(S, K, r, t, sigma) for t in T_range]
put_delta_T = [delta_put(S, K, r, t, sigma) for t in T_range]
ax5.plot(T_range, call_delta_T, label='看涨 Delta', linewidth=2)
ax5.plot(T_range, put_delta_T, label='看跌 Delta', linewidth=2)
ax5.axhline(0, color='black', linestyle=':', alpha=0.5)
ax5.axhline(0.5, color='red', linestyle='--', alpha=0.5, label='深度实值/虚值极限')
ax5.set_xlabel('到期时间(年)')
ax5.set_ylabel('Delta')
ax5.set_title('Delta vs 到期时间')
ax5.legend()
ax5.grid(True, alpha=0.3)
 
# Gamma vs 时间
ax6 = axes[1, 2]
gammas_T = [gamma(S, K, r, t, sigma) for t in T_range]
ax6.plot(T_range, gammas_T, linewidth=2, color='purple')
ax6.fill_between(T_range, 0, gammas_T, alpha=0.2, color='purple')
ax6.set_xlabel('到期时间(年)')
ax6.set_ylabel('Gamma')
ax6.set_title('Gamma vs 到期时间(临近到期 Gamma 峰值)')
ax6.grid(True, alpha=0.3)
 
plt.tight_layout()
plt.show()

2.5 Delta 对冲

def delta_hedging_simulation(S0, K, r, T, sigma, n_steps=100, hedge_frequency='daily'):
    """
    Delta 对冲模拟
 
    模拟卖出看涨期权后通过 Delta 对冲管理风险
    """
    dt = T / n_steps
    time_steps = np.linspace(0, T, n_steps + 1)
 
    # 生成股价路径
    np.random.seed(42)
    price_path = np.zeros(n_steps + 1)
    price_path[0] = S0
 
    for i in range(1, n_steps + 1):
        dW = np.random.normal(0, np.sqrt(dt))
        price_path[i] = price_path[i-1] * np.exp((r - 0.5 * sigma**2) * dt + sigma * dW)
 
    # 初始卖出看涨期权,收取权利金
    option_premium = black_scholes_call(S0, K, r, T, sigma)
 
    # 对冲记录
    hedge_position = []  # 持有的股票数量
    hedge_pnl = []       # 对冲盈亏
    option_pnl = []      # 期权端盈亏
    total_pnl = []       # 总盈亏
 
    stock_position = 0  # 初始股票持仓
    cash_account = option_premium  # 初始现金(收到的权利金)
 
    for i in range(n_steps + 1):
        current_time = T - time_steps[i]
        current_price = price_path[i]
 
        # 计算当前应该持有的 Delta
        target_delta = delta_call(current_price, K, r, max(current_time, 0.001), sigma)
 
        # 计算需要调整的股票数量
        shares_to_trade = target_delta - stock_position
 
        # 执行交易(假设以中间价成交)
        stock_position = target_delta
        cash_account -= shares_to_trade * current_price
 
        # 记录
        hedge_position.append(stock_position)
 
        # 计算各端盈亏
        if i == n_steps:
            # 到期日
            option_payoff = max(current_price - K, 0)
            current_option_value = -option_payoff  # 卖出期权,需支付
        else:
            current_option_value = -black_scholes_call(
                current_price, K, r, max(current_time, 0.001), sigma
            )
 
        stock_value = stock_position * current_price
        total_value = cash_account + stock_value + current_option_value
 
        option_pnl.append(current_option_value)
        total_pnl.append(total_value - option_premium)  # 相对于初始权利金的盈亏
 
    return {
        'price_path': price_path,
        'hedge_position': np.array(hedge_position),
        'total_pnl': np.array(total_pnl),
        'initial_premium': option_premium
    }
 
# 运行 Delta 对冲模拟
hedge_result = delta_hedging_simulation(S0=100, K=100, r=0.05, T=0.25, sigma=0.2, n_steps=100)
 
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
 
# 股价路径
ax1 = axes[0, 0]
ax1.plot(hedge_result['price_path'], linewidth=2)
ax1.axhline(K, color='red', linestyle='--', alpha=0.5, label='行权价')
ax1.set_xlabel('时间步')
ax1.set_ylabel('股价')
ax1.set_title('标的价格路径')
ax1.legend()
ax1.grid(True, alpha=0.3)
 
# Delta 对冲头寸
ax2 = axes[0, 1]
ax2.plot(hedge_result['hedge_position'], linewidth=2, color='blue')
ax2.axhline(0, color='black', linestyle=':', alpha=0.5)
ax2.set_xlabel('时间步')
ax2.set_ylabel('持有股数')
ax2.set_title('Delta 对冲持仓')
ax2.grid(True, alpha=0.3)
 
# 对冲总盈亏
ax3 = axes[1, 0]
ax3.plot(hedge_result['total_pnl'], linewidth=2, color='green')
ax3.axhline(0, color='black', linestyle=':', alpha=0.5)
ax3.fill_between(
    range(len(hedge_result['total_pnl'])),
    0, hedge_result['total_pnl'],
    where=hedge_result['total_pnl'] > 0,
    alpha=0.3, color='green', label='盈利'
)
ax3.fill_between(
    range(len(hedge_result['total_pnl'])),
    0, hedge_result['total_pnl'],
    where=hedge_result['total_pnl'] < 0,
    alpha=0.3, color='red', label='亏损'
)
ax3.set_xlabel('时间步')
ax3.set_ylabel('累计盈亏')
ax3.set_title('Delta 对冲总盈亏')
ax3.legend()
ax3.grid(True, alpha=0.3)
 
# 最终盈亏分布(多次模拟)
ax4 = axes[1, 1]
n_simulations = 100
final_pnls = []
 
for _ in range(n_simulations):
    result = delta_hedging_simulation(S0=100, K=100, r=0.05, T=0.25, sigma=0.2, n_steps=100)
    final_pnls.append(result['total_pnl'][-1])
 
ax4.hist(final_pnls, bins=30, edgecolor='black', alpha=0.7)
ax4.axvline(np.mean(final_pnls), color='red', linestyle='--', linewidth=2,
            label=f'平均值: {np.mean(final_pnls):.4f}')
ax4.axvline(np.median(final_pnls), color='blue', linestyle='--', linewidth=2,
            label=f'中位数: {np.median(final_pnls):.4f}')
ax4.set_xlabel('最终盈亏')
ax4.set_ylabel('频数')
ax4.set_title(f'Delta 对冲盈亏分布({n_simulations} 次模拟)')
ax4.legend()
ax4.grid(True, alpha=0.3)
 
plt.tight_layout()
plt.show()
 
print("\n【Delta 对冲分析】")
print("=" * 60)
print(f"{'统计量':<20} {'值':<20}")
print("-" * 60)
print(f"{'平均盈亏':<20} {np.mean(final_pnls):>15.4f}")
print(f"{'标准差':<20} {np.std(final_pnls):>15.4f}")
print(f"{'最大盈利':<20} {np.max(final_pnls):>15.4f}")
print(f"{'最大亏损':<20} {np.min(final_pnls):>15.4f}")
print(f"{'胜率':<20} {(np.array(final_pnls) > 0).mean():>15.2%}")
print("=" * 60)

三、隐含波动率

3.1 定义

隐含波动率 (Implied Volatility, IV) 是使 BS 模型价格等于市场价格的波动率参数。

3.2 反解 BS 方程

def implied_volatility_call(market_price, S, K, r, T, initial_guess=0.2):
    """
    反解看涨期权的隐含波动率
 
    使用 Brent 方法求解 BS(market_price) = market_price
 
    参数:
        market_price: 市场期权价格
        S: 标的资产价格
        K: 行权价
        r: 无风险利率
        T: 到期时间
        initial_guess: 初始猜测值
 
    返回:
        隐含波动率
    """
    if T <= 0:
        return None
 
    def objective_function(sigma):
        return black_scholes_call(S, K, r, T, sigma) - market_price
 
    try:
        iv = brentq(
            objective_function,
            0.001,  # 下界
            5.0,    # 上界
            maxiter=100
        )
        return iv
    except ValueError:
        return None
 
def implied_volatility_put(market_price, S, K, r, T, initial_guess=0.2):
    """反解看跌期权的隐含波动率"""
    if T <= 0:
        return None
 
    def objective_function(sigma):
        return black_scholes_put(S, K, r, T, sigma) - market_price
 
    try:
        iv = brentq(objective_function, 0.001, 5.0, maxiter=100)
        return iv
    except ValueError:
        return None
 
# 计算示例
market_call_price = 5.5  # 假设市场价格
iv_call = implied_volatility_call(market_call_price, S, K, r, T)
 
print("\n【隐含波动率计算】")
print("=" * 60)
print(f"市场看涨期权价格: ${market_call_price}")
print(f"隐含波动率: {iv_call:.2%}" if iv_call else "无法求解")
print("=" * 60)

3.3 波动率微笑 (Volatility Smile)

现象: 相同到期日、不同行权价的期权,隐含波动率呈现”微笑”形状。

原因:

  1. 肥尾分布: 真实收益有更多极端值(比正态分布)
  2. ** Crashophobia**: 市场对暴跌的恐惧(虚值看跌期权 IV 更高)
  3. 供需关系: 对冲需求影响期权价格
def generate_volatility_smile(S, r, T, strikes, market_prices):
    """
    生成波动率微笑曲线
 
    参数:
        S: 标的资产价格
        r: 无风险利率
        T: 到期时间
        strikes: 行权价列表
        market_prices: 对应的期权市场价格
 
    返回:
        隐含波动率列表
    """
    ivs = []
 
    for K, market_price in zip(strikes, market_prices):
        iv = implied_volatility_call(market_price, S, K, r, T)
        ivs.append(iv)
 
    return ivs
 
# 模拟波动率微笑
np.random.seed(42)
S_base = 100
strikes = np.linspace(80, 120, 9)
 
# 模拟市场价格(带有微笑效应)
market_prices = []
for K in strikes:
    # 基础 BS 价格
    bs_price = black_scholes_call(S_base, K, r, T, sigma)
 
    # 添加微笑效应:偏离平值越远,IV 越高
    moneyness = abs(np.log(K / S_base))
    smile_adjustment = 0.03 * moneyness  # 每 10% moneyness 增加 3% IV
 
    # 用调整后的 IV 重新计算价格
    smile_iv = sigma + smile_adjustment
    smile_price = black_scholes_call(S_base, K, r, T, smile_iv)
 
    market_prices.append(smile_price)
 
# 计算隐含波动率
ivs = generate_volatility_smile(S_base, r, T, strikes, market_prices)
 
# 可视化波动率微笑
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
 
# 波动率微笑曲线
ax1 = axes[0]
ax1.plot(strikes, ivs, marker='o', linewidth=2, markersize=8)
ax1.axhline(sigma, color='red', linestyle='--', alpha=0.5, label=f'ATM IV ({sigma:.2%})')
ax1.axvline(S_base, color='green', linestyle=':', alpha=0.5, label='标的价格')
ax1.set_xlabel('行权价')
ax1.set_ylabel('隐含波动率')
ax1.set_title('波动率微笑 (Volatility Smile)')
ax1.legend()
ax1.grid(True, alpha=0.3)
 
# 与正态分布假设对比
ax2 = axes[1]
# 绘制假设的收益分布
x_range = np.linspace(-0.2, 0.2, 200)
normal_pdf = norm.pdf(x_range, 0, sigma / np.sqrt(T))
 
# 肥尾分布(用 t 分布模拟)
from scipy.stats import t as t_dist
fat_tail_pdf = t_dist.pdf(x_range / (sigma / np.sqrt(T)), df=5) / (sigma / np.sqrt(T))
 
ax2.plot(x_range, normal_pdf, linewidth=2, label='正态分布 (BS 假设)', linestyle='--')
ax2.plot(x_range, fat_tail_pdf, linewidth=2, label='肥尾分布 (实际)', alpha=0.7)
ax2.fill_between(x_range, 0, normal_pdf, alpha=0.1)
ax2.fill_between(x_range, 0, fat_tail_pdf, where=fat_tail_pdf > normal_pdf,
                 alpha=0.3, color='red', label='肥尾部分')
ax2.set_xlabel('收益率')
ax2.set_ylabel('概率密度')
ax2.set_title('肥尾效应导致波动率微笑')
ax2.legend()
ax2.grid(True, alpha=0.3)
 
plt.tight_layout()
plt.show()
 
print("\n【波动率微笑数据】")
print("=" * 60)
print(f"{'行权价':<10} {'隐含IV':<12} {'ATM IV':<12} {'偏差':<10}")
print("-" * 60)
for K, iv in zip(strikes, ivs):
    deviation = iv - sigma
    print(f"{K:<10} {iv:<12.2%} {sigma:<12.2%} {deviation*100:>6.2f}%")
print("=" * 60)

四、波动率曲面

4.1 定义

波动率曲面 是不同到期时间和行权价下的隐含波动率三维图:

IV(T, K) → 随 T 和 K 变化

常见特征:
├── 短期期权: 微笑更明显
├── 长期期权: 微乐较平坦
└── 股票指数: 典型的波动率微笑

4.2 波动率曲面构建

def build_volatility_surface(S, r, maturities, strikes, market_prices_grid):
    """
    构建波动率曲面
 
    参数:
        S: 标的资产价格
        r: 无风险利率
        maturities: 到期时间列表(年)
        strikes: 行权价列表
        market_prices_grid: 市场价格网格 [T][K]
 
    返回:
        隐含波动率网格
    """
    iv_surface = []
 
    for i, T in enumerate(maturities):
        iv_row = []
        for j, K in enumerate(strikes):
            market_price = market_prices_grid[i][j]
            iv = implied_volatility_call(market_price, S, K, r, T)
            iv_row.append(iv)
        iv_surface.append(iv_row)
 
    return np.array(iv_surface)
 
# 模拟波动率曲面数据
maturities = [0.1, 0.25, 0.5, 1.0]  # 1月、3月、6月、1年
strikes_surface = np.linspace(85, 115, 7)
 
# 生成模拟市场价格(带微笑效应)
market_prices_grid = []
for T in maturities:
    row_prices = []
    for K in strikes_surface:
        # 基础 IV 随到期时间增加
        base_iv = sigma * (1 + 0.1 * np.sqrt(T))
 
        # 微笑效应随到期时间减弱
        moneyness = abs(np.log(K / S))
        smile = 0.05 * moneyness / np.sqrt(T + 0.1)
 
        adjusted_iv = base_iv + smile
        price = black_scholes_call(S, K, r, T, adjusted_iv)
        row_prices.append(price)
    market_prices_grid.append(row_prices)
 
# 构建波动率曲面
iv_surface = build_volatility_surface(S, r, maturities, strikes_surface, market_prices_grid)
 
# 可视化波动率曲面
from mpl_toolkits.mplot3d import Axes3D
 
fig = plt.figure(figsize=(14, 6))
 
# 3D 曲面图
ax1 = fig.add_subplot(121, projection='3d')
 
T_mesh, K_mesh = np.meshgrid(maturities, strikes_surface)
 
surf = ax1.plot_surface(
    T_mesh, K_mesh, iv_surface.T,
    cmap='viridis', edgecolor='none', alpha=0.8
)
 
ax1.set_xlabel('到期时间(年)')
ax1.set_ylabel('行权价')
ax1.set_zlabel('隐含波动率')
ax1.set_title('波动率曲面 (3D)')
fig.colorbar(surf, ax=ax1, shrink=0.5)
 
# 热力图
ax2 = fig.add_subplot(122)
 
im = ax2.imshow(
    iv_surface,
    cmap='viridis',
    aspect='auto',
    extent=[min(maturities), max(maturities), min(strikes_surface), max(strikes_surface)],
    origin='lower'
)
 
ax2.set_xlabel('到期时间(年)')
ax2.set_ylabel('行权价')
ax2.set_title('波动率曲面 (热力图)')
 
# 添加数值标注
for i, T in enumerate(maturities):
    for j, K in enumerate(strikes_surface):
        text = ax2.text(T, K, f'{iv_surface[i, j]:.2f}',
                        ha="center", va="center", color="white", fontsize=8)
 
fig.colorbar(im, ax=ax2)
 
plt.tight_layout()
plt.show()

4.3 曲面插值

from scipy.interpolate import griddata, RBFInterpolator
 
def interpolate_volatility_surface(iv_grid, maturities, strikes, query_points, method='rbf'):
    """
    插值波动率曲面
 
    参数:
        iv_grid: 已知的 IV 网格
        maturities: 已知到期时间
        strikes: 已知行权价
        query_points: 待查询点 [(T, K), ...]
        method: 插值方法 ('linear', 'rbf', 'cubic')
 
    返回:
        插值得到的 IV 值
    """
    # 构造已知点
    known_points = []
    known_values = []
 
    for i, T in enumerate(maturities):
        for j, K in enumerate(strikes):
            known_points.append([T, K])
            known_values.append(iv_grid[i, j])
 
    known_points = np.array(known_points)
    known_values = np.array(known_values)
    query_points = np.array(query_points)
 
    if method == 'rbf':
        interpolator = RBFInterpolator(known_points, known_values)
        interpolated_values = interpolator(query_points)
    else:
        interpolated_values = griddata(
            known_points, known_values, query_points,
            method=method
        )
 
    return interpolated_values

五、波动率作为因子

5.1 波动率因子的预测力

def volatility_signal_analysis(returns, realized_vol, implied_vol, window=20):
    """
    分析波动率信号的预测力
 
    参数:
        returns: 收益率序列
        realized_vol: 实现波动率序列
        implied_vol: 隐含波动率序列
        window: 回望窗口
 
    返回:
        预测统计分析
    """
    # 波动率风险溢价 (VRP) = IV - RV
    vrp = implied_vol - realized_vol
 
    # 未来收益
    future_returns = returns.shift(-window)
 
    # 构建数据
    analysis_df = pd.DataFrame({
        'VRP': vrp,
        'IV': implied_vol,
        'RV': realized_vol,
        'Future_Return': future_returns
    }).dropna()
 
    # 计算相关性
    correlations = analysis_df.corr()
 
    # 分组分析
    analysis_df['VRP Quintile'] = pd.qcut(
        analysis_df['VRP'], 5,
        labels=['Q1 (Low)', 'Q2', 'Q3', 'Q4', 'Q5 (High)']
    )
 
    quintile_returns = analysis_df.groupby('VRP Quintile')['Future_Return'].mean()
 
    return {
        'correlations': correlations,
        'quintile_returns': quintile_returns,
        'full_data': analysis_df
    }
 
# 模拟波动率数据
np.random.seed(42)
n_days = 500
 
# 生成收益率
daily_returns = np.random.normal(0.0005, 0.015, n_days)
 
# 生成实现波动率(滚动标准差)
realized_vol = pd.Series(daily_returns).rolling(20).std() * np.sqrt(252)
 
# 生成隐含波动率(IV 通常高于 RV,加上时变溢价)
implied_vol = realized_vol + np.random.normal(0.02, 0.01, n_days) * np.sqrt(252)
 
# 分析
result = volatility_signal_analysis(
    pd.Series(daily_returns),
    realized_vol,
    implied_vol
)
 
print("\n【波动率因子预测分析】")
print("=" * 60)
print("\n相关性矩阵:")
print(result['correlations'].round(4))
 
print("\nVRP 分组未来收益:")
print(result['quintile_returns'])
 
print("\n" + "=" * 60)

5.2 波动率风险溢价策略

def vrp_trading_strategy(returns, implied_vol, realized_vol, threshold=0.01):
    """
    基于波动率风险溢价的交易策略
 
    逻辑:
    - 当 IV >> RV 时,市场过度担忧,实际波动往往低于预期
    - 可以做空波动率(卖出期权)
    """
    vrp = implied_vol - realized_vol
 
    # 信号
    signal = np.where(vrp > threshold, 1, 0)  # VRP 高时做多股票(做空波动率)
 
    # 计算策略收益
    strategy_returns = pd.Series(signal).shift(1) * returns
 
    return strategy_returns, signal
 
# 运行策略
vrp_returns, vrp_signals = vrp_trading_strategy(daily_returns, implied_vol, realized_vol)
 
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
 
# VRP 时间序列
ax1 = axes[0, 0]
vrp_series = implied_vol - realized_vol
ax1.plot(vrp_series.values, linewidth=2, label='VRP')
ax1.axhline(0, color='black', linestyle=':', alpha=0.5)
ax1.axhline(vrp_series.mean(), color='red', linestyle='--',
            label=f'平均值 ({vrp_series.mean():.2%})')
ax1.fill_between(range(len(vrp_series)), 0, vrp_series.values,
                 where=vrp_series.values > 0, alpha=0.3, color='green')
ax1.fill_between(range(len(vrp_series)), 0, vrp_series.values,
                 where=vrp_series.values < 0, alpha=0.3, color='red')
ax1.set_xlabel('交易日')
ax1.set_ylabel('VRP')
ax1.set_title('波动率风险溢价时间序列')
ax1.legend()
ax1.grid(True, alpha=0.3)
 
# IV vs RV 散点图
ax2 = axes[0, 1]
ax2.scatter(realized_vol[20:], implied_vol[20:], alpha=0.5, s=20)
min_val = min(realized_vol.min(), implied_vol.min())
max_val = max(realized_vol.max(), implied_vol.max())
ax2.plot([min_val, max_val], [min_val, max_val], 'r--', linewidth=2,
         label='IV = RV (无溢价)')
ax2.set_xlabel('实现波动率 (RV)')
ax2.set_ylabel('隐含波动率 (IV)')
ax2.set_title('IV vs RV')
ax2.legend()
ax2.grid(True, alpha=0.3)
 
# 策略累计收益
ax3 = axes[1, 0]
buy_hold_cumulative = (1 + pd.Series(daily_returns)).cumprod()
vrp_cumulative = (1 + vrp_returns.fillna(0)).cumprod()
ax3.plot(buy_hold_cumulative.values, label='买入持有', linewidth=2, alpha=0.7)
ax3.plot(vrp_cumulative.values, label='VRP 策略', linewidth=2, alpha=0.7)
ax3.set_xlabel('交易日')
ax3.set_ylabel('累计收益')
ax3.set_title('策略累计收益对比')
ax3.legend()
ax3.grid(True, alpha=0.3)
 
# 策略信号分布
ax4 = axes[1, 1]
signal_counts = pd.Series(vrp_signals).value_counts()
colors = ['green' if s == 1 else 'gray' for s in signal_counts.index]
ax4.bar(signal_counts.index, signal_counts.values, color=colors, edgecolor='black')
ax4.set_xlabel('信号 (1=做多, 0=空仓)')
ax4.set_ylabel('天数')
ax4.set_title('策略信号分布')
ax4.grid(True, alpha=0.3, axis='y')
 
plt.tight_layout()
plt.show()

六、Gamma Scalping

6.1 原理

Gamma Scalping 是一种波动率交易策略:

策略逻辑:
├── 持有期权 + Delta 对冲
├── Gamma > 0: 价格波动越大,对冲收益越高
├── 高波动环境: 频繁再平衡获利
└── 目标: 从实际波动中获利,补偿 Theta 损失

6.2 盈亏分解

Gamma Scalping 的每日盈亏:

其中:

  • : Gamma 盈利(来自波动)
  • : Theta 损失(时间衰减)
def gamma_scalping_pnl(S, K, r, T, sigma, actual_sigma, n_steps=100):
    """
    模拟 Gamma Scalping 盈亏
 
    参数:
        S: 初始标的价格
        K: 行权价
        r: 无风险利率
        T: 到期时间
        sigma: 期权定价波动率(IV)
        actual_sigma: 实际波动率(RV)
        n_steps: 模拟步数
 
    返回:
        盈亏分解
    """
    dt = T / n_steps
 
    # 生成股价路径(使用实际波动率)
    np.random.seed(42)
    price_path = np.zeros(n_steps + 1)
    price_path[0] = S
 
    for i in range(1, n_steps + 1):
        dW = np.random.normal(0, np.sqrt(dt))
        price_path[i] = price_path[i-1] * np.exp(
            (r - 0.5 * actual_sigma**2) * dt + actual_sigma * dW
        )
 
    # Gamma Scalping 模拟
    gamma_pnl = []
    theta_pnl = []
    total_pnl = []
 
    # 初始买入跨式组合(Straddle)
    straddle_cost = black_scholes_call(S, K, r, T, sigma) + black_scholes_put(S, K, r, T, sigma)
 
    for i in range(n_steps):
        current_T = T - i * dt
        current_S = price_path[i]
 
        if current_T <= 0:
            break
 
        # 计算 Greeks
        curr_gamma = gamma(current_S, K, r, current_T, sigma)
        curr_theta_call = theta_call(current_S, K, r, current_T, sigma)
        curr_theta_put = theta_put(current_S, K, r, current_T, sigma)
        curr_theta = curr_theta_call + curr_theta_put
 
        # 价格变化
        delta_S = price_path[i+1] - current_S if i < n_steps else 0
 
        # Gamma 盈亏 (近似)
        gamma_profit = 0.5 * curr_gamma * delta_S**2
 
        # Theta 损失
        theta_loss = curr_theta * dt  # theta 为负值
 
        gamma_pnl.append(gamma_profit)
        theta_pnl.append(theta_loss)
        total_pnl.append(gamma_profit + theta_loss)
 
    return {
        'gamma_pnl': np.array(gamma_pnl),
        'theta_pnl': np.array(theta_pnl),
        'total_pnl': np.array(total_pnl),
        'price_path': price_path,
        'straddle_cost': straddle_cost
    }
 
# 运行模拟
# 情况 1: 实际波动率 > IV(盈利)
result_high_vol = gamma_scalping_pnl(S=100, K=100, r=0.05, T=0.25, sigma=0.2, actual_sigma=0.3)
 
# 情况 2: 实际波动率 < IV(亏损)
result_low_vol = gamma_scalping_pnl(S=100, K=100, r=0.05, T=0.25, sigma=0.2, actual_sigma=0.15)
 
print("\n【Gamma Scalping 分析】")
print("=" * 60)
print(f"{'情况':<30} {'高波动 (RV=30%)':<20} {'低波动 (RV=15%)':<20}")
print("-" * 60)
print(f"{'跨式组合成本':<30} ${result_high_vol['straddle_cost']:<18.2f} ${result_low_vol['straddle_cost']:<18.2f}")
print(f"{'累计 Gamma 盈利':<30} ${result_high_vol['gamma_pnl'].sum():<18.2f} ${result_low_vol['gamma_pnl'].sum():<18.2f}")
print(f"{'累计 Theta 损失':<30} ${result_high_vol['theta_pnl'].sum():<18.2f} ${result_low_vol['theta_pnl'].sum():<18.2f}")
print(f"{'总盈亏':<30} ${result_high_vol['total_pnl'].sum():<18.2f} ${result_low_vol['total_pnl'].sum():<18.2f}")
print("=" * 60)
 
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
 
# 高波动情况:股价路径
ax1 = axes[0, 0]
ax1.plot(result_high_vol['price_path'], linewidth=2)
ax1.axhline(K, color='red', linestyle='--', alpha=0.5, label='行权价')
ax1.set_title('高波动情况: 股价路径 (RV=30%)')
ax1.set_ylabel('股价')
ax1.legend()
ax1.grid(True, alpha=0.3)
 
# 高波动情况:盈亏分解
ax2 = axes[0, 1]
ax2.plot(result_high_vol['gamma_pnl'], label='Gamma 盈利', linewidth=2)
ax2.plot(result_high_vol['theta_pnl'], label='Theta 损失', linewidth=2, alpha=0.7)
ax2.plot(result_high_vol['total_pnl'], label='总盈亏', linewidth=2, color='black')
ax2.axhline(0, color='black', linestyle=':', alpha=0.5)
ax2.set_title('高波动情况: 盈亏分解')
ax2.set_ylabel('盈亏')
ax2.legend()
ax2.grid(True, alpha=0.3)
 
# 低波动情况:股价路径
ax3 = axes[1, 0]
ax3.plot(result_low_vol['price_path'], linewidth=2)
ax3.axhline(K, color='red', linestyle='--', alpha=0.5, label='行权价')
ax3.set_title('低波动情况: 股价路径 (RV=15%)')
ax3.set_xlabel('时间步')
ax3.set_ylabel('股价')
ax3.legend()
ax3.grid(True, alpha=0.3)
 
# 低波动情况:盈亏分解
ax4 = axes[1, 1]
ax4.plot(result_low_vol['gamma_pnl'], label='Gamma 盈利', linewidth=2)
ax4.plot(result_low_vol['theta_pnl'], label='Theta 损失', linewidth=2, alpha=0.7)
ax4.plot(result_low_vol['total_pnl'], label='总盈亏', linewidth=2, color='black')
ax4.axhline(0, color='black', linestyle=':', alpha=0.5)
ax4.set_title('低波动情况: 盈亏分解')
ax4.set_xlabel('时间步')
ax4.set_ylabel('盈亏')
ax4.legend()
ax4.grid(True, alpha=0.3)
 
plt.tight_layout()
plt.show()

七、量化研究员为什么需要理解衍生品?

7.1 波动率是因子

期权数据包含的独特信息:
├── 隐含波动率 → 市场预期、风险溢价
├── 波动率偏斜 → 极端事件概率
├── Gamma 风险 → 做市商对冲压力
└── 期权成交量 → 机构活动信号

7.2 对冲是成本

理解衍生品有助于:
├── 评估真实对冲成本(不仅仅是交易费)
├── 优化对冲频率(Delta-Gamma 对冲 vs 纯 Delta 对冲)
├── 理解期权风险对股票的溢出效应
└── 构造更复杂的策略(期权增强、波动率加权)

7.3 实际应用

def option_enhanced_signals(returns, iv_changes, put_call_ratio):
    """
    利用期权数据增强股票信号
 
    参数:
        returns: 股票收益率
        iv_changes: 隐含波动率变化
        put_call_ratio: 看跌/看涨成交量比
 
    返回:
        增强信号
    """
    # 基础信号:动量
    momentum_signal = returns.rolling(20).mean()
 
    # 期权增强信号
    # IV 下降 + 看涨增加 → 看多
    option_signal = (
        -iv_changes.rolling(5).mean() +  # IV 下降为正
        (1 - put_call_ratio.rolling(5).mean())  # PCR 下降为正
    )
 
    # 综合信号
    combined_signal = momentum_signal + option_signal
 
    return combined_signal

八、核心知识点总结

概念核心内容Python 实现
BS 模型期权定价基础公式black_scholes_call/put
Greeks敏感度度量、对冲依据delta/gamma/vega/theta 函数
隐含波动率反解 BS、市场预期implied_volatility
波动率微笑行权价与 IV 的 U 型关系市场数据反解可视化
波动率曲面T × K 的 IV 三维图build_volatility_surface
VRP波动率风险溢价 = IV - RV波动率因子构造
Gamma Scalping波动率交易策略盈亏分解模拟

关键公式:

BS 看涨: C = S·N(d₁) - K·e^(-rT)·N(d₂)
Delta: Δ = N(d₁)
Gamma: Γ = N'(d₁) / (Sσ√T)
Vega: ν = S·N'(d₁)√T
VRP: IV - RV
Gamma PnL: ½·Γ·(ΔS)² - Θ·dt

实践要点:

  1. BS 假设严格,实际需要调整(波动率曲面)
  2. IV 包含风险溢价,不是对未来波动的无偏估计
  3. Greeks 是动态的,需要持续监控和对冲
  4. 期权数据是股票预测的重要另类信号

模块总结

完成金融基础模块后,你已掌握:

模块核心收获
资产定价风险与收益的关系、因子投资的逻辑
微观结构交易成本的量化、执行算法的设计
衍生品期权定价、波动率作为信号源

下一步: 将这些金融知识应用到机器学习模型中,在特征工程模块学习如何构造有效的预测因子。