4. BS模型的Python实现:从公式到代码
说实话,BS公式看着挺唬人,一堆希腊字母和积分符号。但真把它写成Python代码,其实就那么几行。我当年第一次实现时,花了半小时查公式,十分钟写代码——嗯,调试倒是花了一下午(笑)。
这一章,咱们就把BS公式、隐含波动率、希腊字母这三个核心模块,一个一个用Python敲出来。你跟着我走一遍,以后面试或做项目,直接复制粘贴改改就能用。
4.1 BS公式的代码实现
先回顾一下BS公式的核心:
看涨期权价格 C = S₀·N(d₁) - K·e^(-rT)·N(d₂)
看跌期权价格 P = K·e^(-rT)·N(-d₂) - S₀·N(-d₁)
其中:
d₁ = [ln(S₀/K) + (r + σ²/2)T] / (σ√T)
d₂ = d₁ - σ√T
说白了,就是算两个正态分布累积概率,再做个加减乘除。我习惯把N(d)用scipy.stats.norm.cdf来实现,这样最稳。
import numpy as np
from scipy.stats import norm
def bs_price(S, K, T, r, sigma, option_type='call'):
"""
BS模型定价
S: 标的价格
K: 行权价
T: 剩余期限(年)
r: 无风险利率
sigma: 波动率
option_type: 'call' 或 'put'
"""
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
# 举个栗子
S, K, T, r, sigma = 100, 100, 1, 0.05, 0.2
call_price = bs_price(S, K, T, r, sigma, 'call')
put_price = bs_price(S, K, T, r, sigma, 'put')
print(f"看涨期权价格: {call_price:.4f}")
print(f"看跌期权价格: {put_price:.4f}")
小提示: 我建议你每次算完价格后,检查一下看涨看跌是否满足put-call parity。如果C - P ≠ S - K·e^(-rT),那多半是代码哪里写错了。我在项目中吃过这个亏,后来养成了习惯。
4.2 隐含波动率计算
隐含波动率是什么?说白了,就是把市场上的期权价格代入BS公式,反解出来的波动率。它反映了市场对未来波动的一致预期。
为什么需要算这个?因为BS公式里,除了sigma,其他参数都能直接观察到。只有sigma是未知的。隐含波动率就是市场给这个未知数定的价。
计算隐含波动率,本质上是个求根问题。我常用牛顿法,收敛快,几行代码搞定。
def implied_volatility(market_price, S, K, T, r, option_type='call',
initial_guess=0.2, tol=1e-6, max_iter=100):
"""
用牛顿法计算隐含波动率
"""
sigma = initial_guess
for i in range(max_iter):
# 计算当前sigma下的价格和vega
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)
vega = S * norm.pdf(d1) * np.sqrt(T)
else:
price = K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
vega = S * norm.pdf(d1) * np.sqrt(T)
diff = price - market_price
if abs(diff) < tol:
return sigma
# 牛顿迭代
sigma = sigma - diff / vega
# 防止sigma变成负数
if sigma <= 0:
sigma = 0.01
raise ValueError("牛顿法未收敛,请检查输入参数")
# 测试一下
market_call = bs_price(100, 100, 1, 0.05, 0.25, 'call')
iv = implied_volatility(market_call, 100, 100, 1, 0.05, 'call')
print(f"隐含波动率: {iv:.4f} (真实值: 0.25)")
注意: 牛顿法对初始值敏感。如果市场价对应的波动率特别大或特别小,初始值设0.2可能不收敛。我建议先用二分法粗算一下,再用牛顿法精算。另外,vega接近0时(深度实值或深度虚值),牛顿法容易崩,记得加异常处理。
4.3 希腊字母计算
希腊字母是期权风险管理的核心。说白了,它们就是期权价格对各个参数的偏导数。我当年做期权做市商时,每天盯着希腊字母看,比看价格本身还多。
咱们把最常用的五个希腊字母实现一下:Delta、Gamma、Theta、Vega、Rho。
def bs_greeks(S, K, T, r, sigma, option_type='call'):
"""
计算BS模型的希腊字母
"""
d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
# 正态分布PDF和CDF
phi = norm.pdf
Phi = norm.cdf
if option_type == 'call':
delta = Phi(d1)
gamma = phi(d1) / (S * sigma * np.sqrt(T))
theta = (-S * phi(d1) * sigma / (2 * np.sqrt(T))
- r * K * np.exp(-r * T) * Phi(d2))
vega = S * phi(d1) * np.sqrt(T)
rho = K * T * np.exp(-r * T) * Phi(d2)
else:
delta = Phi(d1) - 1
gamma = phi(d1) / (S * sigma * np.sqrt(T))
theta = (-S * phi(d1) * sigma / (2 * np.sqrt(T))
+ r * K * np.exp(-r * T) * Phi(-d2))
vega = S * phi(d1) * np.sqrt(T)
rho = -K * T * np.exp(-r * T) * Phi(-d2)
return {
'delta': delta,
'gamma': gamma,
'theta': theta / 365, # 转换为每日theta
'vega': vega / 100, # 转换为1%波动率变化
'rho': rho / 100 # 转换为1%利率变化
}
# 测试
greeks = bs_greeks(100, 100, 1, 0.05, 0.2, 'call')
for greek, value in greeks.items():
print(f"{greek}: {value:.6f}")
关键点: 注意theta和vega的单位转换。theta通常用每日(除以365),vega和rho用每1%变化(除以100)。如果不转换,数值会很大,看着不直观。我见过不少新手直接拿原始值去对冲,结果对不上账。
4.4 知识体系总览
下面这张图,把BS模型实现的三个核心模块串起来了。你写代码时,按这个流程走,不会乱。
4.5 实战中的避坑指南
代码写完了,但实际用起来还有几个坑。我一个个说:
- 时间单位统一: T一定要用年。如果期权还有30天到期,T=30/365。我见过有人直接传30进去,算出来的价格离谱到天际。
- 股息处理: 如果标的资产有股息,BS公式要调整。最简单的做法是把S换成S·e^(-qT),其中q是股息率。我在做股指期权时,这个坑踩过好几次。
- 边界情况: 当T接近0或sigma接近0时,d1和d2会变成无穷大。代码里要加判断,比如T<1e-10时直接用内在价值。
- 数值稳定性: 计算d1时,如果S/K很大或很小,np.log(S/K)可能溢出。我习惯先取对数再判断,或者用np.log1p做优化。
我的习惯: 每次写完BS代码,我都会用几个已知案例做验证。比如平价期权(S=K)的看涨看跌价格应该相等,深度实值期权的delta应该接近1。这些小测试能帮你快速发现bug。
好了,BS模型的Python实现就讲到这里。代码你直接拿去用,但建议你手敲一遍——只有自己敲过,才能真正理解每个参数是怎么影响价格的。下次遇到期权定价问题,你就能自信地说:「这个我写过,没问题。」
公众号:蓝海资料掘金营,微信deep3321