衍生品与波动率
核心问题: 波动率不仅是风险度量,也是可交易资产类别,如何利用期权数据增强股票预测?
学习目标
- 理解 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)
现象: 相同到期日、不同行权价的期权,隐含波动率呈现”微笑”形状。
原因:
- 肥尾分布: 真实收益有更多极端值(比正态分布)
- ** Crashophobia**: 市场对暴跌的恐惧(虚值看跌期权 IV 更高)
- 供需关系: 对冲需求影响期权价格
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
实践要点:
- BS 假设严格,实际需要调整(波动率曲面)
- IV 包含风险溢价,不是对未来波动的无偏估计
- Greeks 是动态的,需要持续监控和对冲
- 期权数据是股票预测的重要另类信号
模块总结
完成金融基础模块后,你已掌握:
| 模块 | 核心收获 |
|---|---|
| 资产定价 | 风险与收益的关系、因子投资的逻辑 |
| 微观结构 | 交易成本的量化、执行算法的设计 |
| 衍生品 | 期权定价、波动率作为信号源 |
下一步: 将这些金融知识应用到机器学习模型中,在特征工程模块学习如何构造有效的预测因子。