本文会阶段性持续性更新。
1. QuantLib-python 安装
pip install QuantLib-Python
2. python 版教程+例子
外国人写的,网址:QuantLib Python Tutorials With Examples - G B
quantLib属于黑匣子,大家有问题除了看教程,还可以问gpt,我主要是根据我自己的需求写笔记。问Ai的时候,要多跟它确认一下他的回答是否正确。
2.1 QuoteHandle有哪些
在 QuantLib 中,QuoteHandle
是用于管理和引用实时市场数据(如价格、利率等)的抽象接口。QuoteHandle
通常包装一个继承自 Quote
的对象。除了 SimpleQuote
,以下是一些常见的子类:
-
SimpleQuote
-
提供一个简单的可变值(如价格、利率、波动率)。
-
-
ForwardValueQuote
-
引用一个远期定价引擎并提供远期值。
-
-
ImpliedVolQuote
-
用于计算和引用隐含波动率,基于市场价格反推出隐含的波动率。
-
-
DerivedQuote
-
允许通过其他
Quote
值动态计算一个派生值。
-
-
CompositeQuote
-
基于多个
Quote
,通过自定义函数计算一个组合值。
-
-
FittedBondDiscountCurveQuote
-
引用贴现曲线,从而基于债券数据计算值。
-
这些类可以根据需求实现动态、实时的市场数据处理和引用。
2.2 YieldTermStructureHandle有哪些
在 QuantLib 中,YieldTermStructureHandle
是对 收益率曲线(Yield Term Structure) 的封装,用于方便管理和引用曲线数据。除了基本的 FlatForward
和 ZeroCurve
等,以下是常见的子类:
-
FlatForward
-
假设单一的远期利率,提供平坦的收益率曲线。
-
-
ZeroCurve
-
基于零息收益率构建的收益率曲线。
-
-
ForwardCurve
-
基于远期利率构建的收益率曲线。
-
-
PiecewiseYieldCurve
-
使用插值算法(线性、样条等)通过一系列市场数据点(如零息收益率或远期点)构建的分段收益率曲线。
-
-
DiscountCurve
-
直接使用贴现因子构建的收益率曲线。
-
-
FittedBondDiscountCurve
-
基于债券价格数据拟合得到的收益率曲线。
-
-
InterpolatedCurve
-
泛型类,允许通过指定插值方法(如线性、多项式或样条)构建曲线。
-
-
SwapCurve
-
基于市场掉期利率(如固定浮动利率掉期)构建的收益率曲线。
-
-
QuantExt::OvernightIndexedSwapCurve (扩展库)
-
用于基于隔夜指数掉期(OIS)市场数据构建收益率曲线。
-
这些曲线类被封装在 YieldTermStructureHandle
中,允许高效地共享和更新曲线数据,并支持依赖它的对象(如定价引擎)自动感知变化。
2.2.1 day_count 365改成到252天
这个包里默认一年365天,但是一般交易日都是252天,所以需要自己重构一下:
import QuantLib as ql
# 创建自定义的 DayCounter 类,表示每年252个交易日
class Actual252(ql.Actual365Fixed):
def __init__(self):
# 使用父类的构造函数
super().__init__()
# 重写 yearFraction 方法,返回年化分数,使用 252 天作为分母
def yearFraction(self, d1, d2, ref_period_start=None, ref_period_end=None):
return (d2 - d1) / 252.0 # 分母为 252 天
# 测试用例
day_count = Actual252()
# 定义日期
d1 = ql.Date(1, 1, 2024)
d2 = ql.Date(31, 12, 2024)
# 实际天数与年分数
days = day_count.dayCount(d1, d2)
year_fraction = day_count.yearFraction(d1, d2)
print("Day count:", days)
print("Year fraction (252):", year_fraction)
输出结果:
Day count: 365
Year fraction (252): 1.4484126984126984
2.2.2 没有股息或者分红应该如何传参?(以FlatForward为例)
正确的做法是使用 FlatForward(calculation_date, 0.0, day_count)
来表示没有股息收益率。不要传递 None。
import QuantLib as ql
# 定义现货价格、无风险利率、波动率
spot_price = 100
volatility = 0.2
risk_free_rate = 0.05
calculation_date = ql.Date(25, 11, 2024)
day_count = ql.Actual365Fixed()
# 创建现货价格、无风险利率和波动率曲线
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
flat_ts = ql.YieldTermStructureHandle(
ql.FlatForward(calculation_date, risk_free_rate, day_count)
)
flat_vol_ts = ql.BlackVolTermStructureHandle(
ql.BlackConstantVol(calculation_date, ql.NullCalendar(), volatility, day_count)
)
# 创建一个零股息收益率曲线 (表示没有股息)
dividend_yield = ql.YieldTermStructureHandle(
ql.FlatForward(calculation_date, 0.0, day_count) # 0.0 表示没有股息
)
# 创建 Black-Scholes-Merton 过程
bsm_process = ql.BlackScholesMertonProcess(spot_handle,
dividend_yield,
flat_ts,
flat_vol_ts)
2.3 BlackVolTermStructureHandle有哪些
在 QuantLib 中,BlackVolTermStructureHandle
是一个用于表示 波动率期限结构(Volatility Term Structure) 的类。它用于处理波动率随时间变化的情况,特别是当你需要处理 不同到期日的隐含波动率 时。
常用的 BlackVolTermStructure
的实现类:
1. BlackConstantVol
(常数波动率)
- 描述:表示恒定的波动率。
- 用途:用于假设市场波动率是恒定的模型,如经典的 Black-Scholes 模型。
- 特点:波动率在整个到期日期和所有行权价格下都相同。
vol_structure = ql.BlackConstantVol(calculation_date, calendar, ql.QuoteHandle(ql.SimpleQuote(0.2)), day_count)
2. BlackForwardVol
(前向波动率)
- 描述:表示 前向波动率(forward volatility),即一个从当前时间开始,到某个未来时间点的波动率。
- 用途:适用于当你希望波动率基于某个特定未来时间段的变化时,可以用于市场中期权的波动率数据。
- 特点:前向波动率根据时间段的不同而变化,可以通过历史或市场数据来获取。
vol_structure = ql.BlackForwardVol(calculation_date, calendar, ql.QuoteHandle(ql.SimpleQuote(0.2)), day_count)
3. BlackVarianceSurface
(波动率面)
- 描述:表示 波动率面(Volatility Surface),即波动率不仅随着到期时间变化,也随着不同的行权价格变化。
- 用途:当波动率不仅受到时间的影响,还受行权价格的影响时,通常会用到波动率面。它通常是根据市场期权数据(比如不同到期日和行权价的期权价格)来构建的。
- 特点:对于不同的到期日和不同的行权价,波动率是不同的,适用于更加复杂的市场条件。
# 需要准备一个波动率表面数据集,可以通过市场数据来构建
vol_surface = ql.BlackVarianceSurface(
calculation_date, calendar, strikes, maturities, vol_data, day_count
)
vol_structure_handle = ql.BlackVolTermStructureHandle(vol_surface)
4. BlackInterpolatedVol
(插值波动率)
- 描述:表示一种 插值波动率(Interpolated Volatility),即通过插值来得到波动率数据,通常用于从离散的市场数据构建连续的波动率曲线或面。
- 用途:当你有离散的市场数据(如多个不同到期日和行权价格的期权数据),并希望通过插值生成平滑的波动率曲线时,可以使用插值波动率。
- 特点:可以根据现有的波动率数据(例如多期权合约的隐含波动率)插值得到任意点的波动率。
# 假设你有一些市场隐含波动率数据
strikes = [100, 110, 120] # 行权价格
maturities = [ql.Date(15, 12, 2024), ql.Date(15, 12, 2025)] # 到期时间
vol_data = [[0.20, 0.22, 0.24], [0.21, 0.23, 0.25]] # 对应的波动率数据
vol_surface = ql.BlackVarianceSurface(
calculation_date, calendar, strikes, maturities, vol_data, day_count
)
vol_handle = ql.BlackVolTermStructureHandle(vol_surface)
5. PiecewiseConstantVol
(分段常数波动率)
- 描述:表示 分段常数波动率,即波动率在不同的时间段内是常数,但在不同时间段之间可以有不同的值。这种方法通常用于模型中,波动率可能会分为几个不同的阶段,例如,短期波动率、中期波动率和长期波动率。
- 用途:适用于建模一些分阶段变化的波动率,例如在金融危机时期的短期波动率较高,而在稳定时期波动率较低。
- 特点:可以通过指定不同区间的常数波动率来描述波动率的变化。
# 假设我们有不同阶段的常数波动率
times = [0.0, 1.0, 2.0] # 期权到期的时间,单位是年!
volatilities = [0.2, 0.3, 0.4] # 每个阶段的波动率
vol_structure = ql.PiecewiseConstantVol(calculation_date, calendar, times, volatilities, day_count)
2.3.1 区别:PiecewiseConstantVol
(分段常数波动率)和BlackForwardVol
(前向波动率)
总的来说,分段波动率是0到t1,t2……的波动率,前向是t1到t2,t2到t3……。在代码的用法上是一样的
2. PiecewiseConstantVol的代码用法
import QuantLib as ql
calculation_date = ql.Date(15, 11, 2024)
ql.Settings.instance().evaluationDate = calculation_date
# 定义时间分段点和对应波动率(现货波动率)
times = [1.0, 2.0, 3.0] # 1年、2年、3年的切换点
spot_vols = [0.2, 0.25, 0.3] # 每段的现货波动率
# 构造 PiecewiseConstantVol
piecewise_vol = ql.PiecewiseConstantVol(calculation_date, ql.NullCalendar(), times, spot_vols, ql.Actual365Fixed())
# 查询某个时间的波动率
print("波动率(1.5年):", piecewise_vol.blackVol(1.5, 0))
print("波动率(2.5年):", piecewise_vol.blackVol(2.5, 0))
BlackForwardVol的代码用法
import QuantLib as ql
calculation_date = ql.Date(15, 11, 2024)
ql.Settings.instance().evaluationDate = calculation_date
# 定义时间分段点和对应的前向波动率
times = [1.0, 2.0, 3.0] # 时间切换点(以年为单位)
forward_vols = [0.2, 0.25, 0.3] # 前向波动率
# 构造 BlackForwardVol
forward_vol = ql.BlackForwardVol(calculation_date, ql.NullCalendar(), times, forward_vols, ql.Actual365Fixed())
# 查询某个时间的波动率
print("波动率(1.5年):", forward_vol.blackVol(1.5, 0))
print("波动率(2.5年):", forward_vol.blackVol(2.5, 0))
使用场景的总结,仅为参考。
2.3.2 波动率参数(1.5,0),(1.5,1)的区别
上文例子中blackVol(1.5, 0)(或者其他)里面1.5代表从当前到未来1.5的时间段。0代表了要查现货波动率,就是基于现在的波动率。如果0变成了1,就代表了期权到期为1年,波动率就是到期一年的时间点到未来1.5年的波动率。
例子:
假设当前时间是 2024年11月15日,(1.5,0)代表从2024/11/15到未来1.5年,也就是2024/11/15到2026年5月15日的波动率。(1.5,1)代表了期权到期时间(1年)到1.5年后的前向波动率,即从2025年11月15日到2026年5月15日这段时间的波动率。
2.4 Heston模型-QuantLib
2.4.1 模型概述
Heston 模型的基本思想是波动率(或方差)不是一个常数,而是一个随机变量,可以随着时间的推移而波动。具体来说,Heston 模型通过以下两个随机过程来描述:
-
标的资产价格的动态(类似于 Black-Scholes 模型中的动态):
以上公式大概看看就行,总的来说,Heston默认波动率是一个随时间变化而波动的随机过程。
2.4.2 Heston 模型的特点:
- 它是一个 带有随机波动率 的模型,而不像 Black-Scholes 模型那样假设波动率是常数。
- 可以捕捉到市场上 隐含波动率曲线的微笑现象,这意味着不同到期日或不同执行价格的期权隐含波动率往往是不同的。
- 通过引入一个 波动率方程,Heston 模型可以描述波动率的随机变化,这使得它在实践中非常有用,尤其是在价格跳跃、波动率波动较大的市场环境中。
2.4.3 Heston 模型的参数估计:
在 Heston 模型中,我们需要估计以下参数:
- μ\muμ - 资产的预期回报率。
- κ\kappaκ - 波动率的回归速率(均值回归的速度)。
- θ\thetaθ - 波动率的长期均值(方差的目标值)。
- σv\sigma_vσv - 波动率的波动率(即方差的波动性)。
- ρ\rhoρ - 资产价格过程和波动率过程之间的相关性。
- v0v_0v0 - 初始方差(初始波动率的平方)。
2.4.3.1 如何获得资产预期回报率μ?
不是瞎写一个数字!heston模型里的回报率是年回报率
1)历史数据估算法
可以通过历史数据来估算 u
,即根据过去一段时间内资产价格的表现来计算它的平均回报率。拉到每日价格后,进行对数处理
这个就可以当作heston的u。
还有其他方法,就是找个瞎估估的数字称之为风险溢价+无风险利率这样。觉得没啥用,不记了。
2.4.3.2 如何获得资产预期回报率kappax及剩余参数?
kappa 通常是通过 Heston模型的参数估计 来获取的,但是不管什么方法都需要进行一个预先猜测。
2.4.3.2.1 预先猜测的原理
如何调整初始值?
-
基于标的资产特性:
- 如果标的资产是低波动性资产(如债券),可以降低
theta
和v0
。 - 如果标的资产是高波动性资产(如加密货币),可以提高
sigma
和v0
。
- 如果标的资产是低波动性资产(如债券),可以降低
-
基于隐含波动率曲面:
- 观察市场隐含波动率曲面,判断短期波动率是否较高(调整
v0
和sigma
)或长期波动率是否稳定(调整theta
和kappa
)。
- 观察市场隐含波动率曲面,判断短期波动率是否较高(调整
-
优化效率:
- 如果优化过程收敛缓慢,可以尝试调整
initial_guess
,将其设为更接近预期市场条件的值。
- 如果优化过程收敛缓慢,可以尝试调整
2.4.3.2.2 参数估计具体方法
1)最小二乘法
import numpy as np
from scipy.optimize import minimize
import QuantLib as ql
# 生成期权数据和市场价格(假设你已经有了市场数据)
market_prices = np.array([10, 15, 20]) # 市场的期权价格
strikes = np.array([100, 105, 110]) # 期权的执行价
maturity = 1 # 期权到期时间(假设为1年)
spot_price = 100 # 标的资产的现货价格
interest_rate = 0.05 # 无风险利率
dividend_rate = 0.02 # 股息率
volatility = 0.2 # 初始波动率
# 创建QuantLib相关对象
calendar = ql.NullCalendar()
day_count = ql.Actual365Fixed()
risk_free_curve = ql.FlatForward(0, ql.NullCalendar(), ql.QuoteHandle(ql.SimpleQuote(interest_rate)), day_count)
dividend_curve = ql.FlatForward(0, ql.NullCalendar(), ql.QuoteHandle(ql.SimpleQuote(dividend_rate)), day_count)
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
# Heston模型需要的初始参数(可以设置一个初始估计)
def heston_model_params(params):
kappa, theta, sigma, rho, v0 = params
# 构造Heston过程
heston_process = ql.HestonProcess(risk_free_curve, dividend_curve, spot_handle,
ql.QuoteHandle(ql.SimpleQuote(v0)),
kappa, theta, sigma, rho)
# 定义期权定价引擎
engine = ql.AnalyticHestonEngine(heston_process)
# 使用期权定价计算模型价格(假设我们有一个欧式期权)
european_option = ql.EuropeanOption(ql.PlainVanillaPayoff(ql.Option.Call, strike), ql.EuropeanExercise(maturity))
european_option.setPricingEngine(engine)
model_prices = np.array([european_option.NPV() for strike in strikes])
# 最小化市场价格与模型价格之间的误差(最小二乘法)
error = np.sum((market_prices - model_prices)**2)
return error
# 初始参数猜测
initial_guess = [1.0, 0.02, 0.5, -0.5, 0.04]
# 执行优化过程
result = minimize(heston_model_params, initial_guess, bounds=[(0.01, 2.0), (0.01, 0.5), (0.1, 2.0), (-1.0, 1.0), (0.01, 0.5)])
# 输出结果
print(f"Estimated parameters: kappa={result.x[0]}, theta={result.x[1]}, sigma={result.x[2]}, rho={result.x[3]}, v0={result.x[4]}")
2)使用期权的 隐含波动率曲面 和 期权价格 来反向推算Heston模型的参数(个人感觉比最小二乘法靠谱)
步骤(这一大堆文字随便看看吧,主要看后面的代码样例)
-
收集市场数据:
- 期权的市场价格:你需要从市场上获取不同到期日、行权价格的期权价格。
- 隐含波动率曲面:通过期权的市场价格和 Black-Scholes模型,你可以反推出市场的隐含波动率曲面。隐含波动率是期权价格与模型价格之间反向求解波动率的结果。
-
利用隐含波动率曲面计算隐含波动率:
- 给定期权的 市场价格、行权价格、到期日 和 标的资产价格,通过 Black-Scholes模型,你可以反向计算出期权的隐含波动率。
- 隐含波动率曲面指的是在不同的行权价格和到期日上,隐含波动率的变化情况。
-
使用隐含波动率曲面进行 Heston 模型校准:
- 隐含波动率曲面可以用来作为 Heston 模型参数的 参考数据。通过最小化期权的 市场价格 和 模型价格 之间的误差,我们可以 反推 Heston 模型中的参数。
具体步骤:
-
定义一个误差函数:误差函数是根据期权的市场价格与 Heston 模型生成的期权价格之间的差异来计算的。具体来说,对于每个市场期权,你计算其 模型价格,然后计算 模型价格 与 市场价格 之间的误差。
-
设置初始参数和边界条件:由于 Heston 模型的参数没有明确的理论推导,因此我们需要为模型参数设置初始猜测值(
kappa
、theta
、sigma
、rho
、v0
)以及合理的边界条件。 -
优化过程:通过优化算法(如最小化算法),不断调整 Heston 模型的参数,以最小化误差函数的值,直到模型价格和市场价格之间的误差足够小,从而得到最合适的 Heston 模型参数。
import numpy as np
from scipy.optimize import minimize
import QuantLib as ql
# 1. 市场数据
market_strikes = [100, 110, 120] # 行权价格
market_maturities = [0.5, 1.0, 2.0] # 到期时间(以年为单位)
market_prices = [10.5, 8.2, 12.3] # 市场期权价格
spot_price = 100 # 标的资产价格
risk_free_rate = 0.05 # 无风险利率
dividend_rate = 0.02 # 股息率
# 2. 定义QuantLib相关对象
calendar = ql.NullCalendar()
day_count = ql.Actual365Fixed()
evaluation_date = ql.Date(15, 11, 2024)
ql.Settings.instance().evaluationDate = evaluation_date
# 无风险利率和股息率
risk_free_curve = ql.FlatForward(evaluation_date, ql.QuoteHandle(ql.SimpleQuote(risk_free_rate)), day_count)
dividend_curve = ql.FlatForward(evaluation_date, ql.QuoteHandle(ql.SimpleQuote(dividend_rate)), day_count)
# 3. 定义Heston模型的误差函数
def heston_calibration_error(params):
kappa, theta, sigma, rho, v0 = params
# 构建Heston过程
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
heston_process = ql.HestonProcess(
ql.YieldTermStructureHandle(risk_free_curve),
ql.YieldTermStructureHandle(dividend_curve),
spot_handle,
v0, kappa, theta, sigma, rho
)
heston_model = ql.HestonModel(heston_process)
engine = ql.AnalyticHestonEngine(heston_model)
# 计算模型价格与市场价格的差异
error = 0.0
for strike, maturity, market_price in zip(market_strikes, market_maturities, market_prices):
payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike)
exercise = ql.EuropeanExercise(evaluation_date + int(maturity * 365))
option = ql.VanillaOption(payoff, exercise)
option.setPricingEngine(engine)
# 计算模型期权价格
model_price = option.NPV()
# 误差度量(市场价格与模型价格的差异)
error += (model_price - market_price)**2
return error
# 4. 初始参数猜测
initial_guess = [2.0, 0.02, 0.5, -0.5, 0.04]
bounds = [(0.01, 5.0), (0.01, 1.0), (0.01, 1.0), (-1.0, 1.0), (0.01, 1.0)]
# 5. 执行优化算法,最小化误差
result = minimize(heston_calibration_error, initial_guess, bounds=bounds, method='L-BFGS-B')
# 输出最终校准结果
kappa, theta, sigma, rho, v0 = result.x
print(f"Calibrated parameters: kappa={kappa}, theta={theta}, sigma={sigma}, rho={rho}, v0={v0}")
解释:
- 市场数据:
market_strikes
是期权的行权价格。market_maturities
是期权的到期时间。market_prices
是期权的市场价格(你从市场获取的数据)。
- 误差函数:
heston_calibration_error
函数计算 Heston 模型的期权价格与市场实际期权价格之间的误差。误差度量通常采用 平方误差 形式。
- 最小化误差:
- 使用
scipy.optimize.minimize
函数最小化误差,进而反向推算出 Heston 模型的最佳参数(kappa
、theta
、sigma
、rho
、v0
)。
- 使用
上文的bounds是啥?
在Heston模型的参数校准过程中,bounds
用来限定每个参数的合法取值范围。因为Heston模型有多个参数(kappa
、theta
、sigma
、rho
、v0
),这些参数必须满足一定的物理和市场逻辑约束,所以我们会为每个参数设定一个合理的上下限,避免优化过程中的不合理结果。
为什么需要 bounds
?
-
防止不合理的参数取值:
- 比如,波动率(
sigma
和v0
)不能为负值。 kappa
、theta
、rho
的取值也有市场逻辑上的限制。
- 比如,波动率(
-
优化过程中的约束:
- 在优化过程中,
bounds
确保每个参数在合理的范围内进行调整,避免过大的数值变化或不切实际的解。 - 如果不设置
bounds
,优化算法可能会返回一些极端值,导致无法实际使用的结果。
- 在优化过程中,
上文的'L-BFGS-B'是啥?
'L-BFGS-B'
是一种 约束优化算法,它是 BFGS(Broyden–Fletcher–Goldfarb–Shanno)算法 的一种变体,常用于求解 无约束优化问题。而 L-BFGS-B
是一种 有限内存(Limited-memory) 的方法,能够高效处理大量变量的优化问题,同时也支持 边界约束(Bound constraints)。这个算法在优化过程中用于调整参数,使得模型误差最小化。
L-BFGS-B算法解释
- BFGS(Broyden-Fletcher-Goldfarb-Shanno)算法:
- 是一种常见的拟牛顿法,它通过估计和更新目标函数的 海森矩阵(Hessian matrix) 来进行优化。BFGS算法不需要显式地计算海森矩阵,而是通过迭代过程逐步更新它的近似值。
- L-BFGS-B(Limited-memory BFGS with Bound constraints):
- L-BFGS:这个算法是 BFGS 的变种,主要通过 有限内存 的方式来优化计算。它不存储整个海森矩阵,而是通过较少的内存来估计它的近似,从而减少了计算资源的消耗。
- B:表示优化问题中允许有 边界约束(bound constraints)。也就是说,参数的取值范围是有限制的。比如你在 Heston 模型中设置了每个参数的范围(如
kappa
从0.01
到5.0
),L-BFGS-B 可以处理这些约束条件。
L-BFGS-B的优势
- 高效性:与标准的 BFGS 相比,L-BFGS-B 使用的内存更少,适合处理更大规模的优化问题,尤其是在变量非常多时。
- 支持约束:可以在优化过程中为每个参数设置上下界,确保算法在合法的区域内搜索解。
- 适用性广:L-BFGS-B 适用于许多实际问题,包括具有非线性、非凸性的优化问题。
其他优化算法对比
除了 L-BFGS-B
,常用的优化算法还包括:
- Nelder-Mead:一种直接搜索法,适用于不需要梯度信息的优化问题,但在处理大规模问题时效率较低。
- Powell:一种无约束的优化方法,不需要计算梯度,适用于某些情况下的优化。
- CG(Conjugate Gradient):适用于大规模问题,尤其是在目标函数光滑的情况下。
但在Heston模型参数校准中,由于问题是高维的、且带有约束,因此L-BFGS-B
是一个常见且高效的选择。
2.4.4 Heston 模型的校准(Calibration):
在实际应用中,我们需要通过市场数据来估计 Heston 模型中的这些参数,通常通过 最小化误差 的方法来进行校准。校准的目标是使得模型的预测值(例如期权价格)尽可能匹配市场上实际交易的期权价格。具体步骤如下:
-
获取市场数据:包括期权的市场价格、不同到期日、执行价格等。
-
选择定价公式:利用 Heston 模型的期权定价公式来预测期权价格。
- Heston 模型的期权定价公式通常没有解析解,因此需要使用数值方法(如蒙特卡洛模拟或快速傅里叶变换,FFT)来进行定价。
-
最小化误差:使用一个优化算法(如最小二乘法、最优化算法)来调整模型参数,最小化 模型价格与市场价格 之间的误差。
-
参数估计:经过校准后,得出的参数可以用来进行期权定价和风险管理。
2.4.4.1 校准实例代码(最小二乘法)
个人感觉不太靠谱,可以看2.4.3.2.2里如何使用隐含波动率进行估计
import QuantLib as ql
# 市场数据
spot_price = 100
strike_price = 100
interest_rate = 0.05
dividend_yield = 0.02
volatility = 0.2
maturity = 1.0
option_type = ql.Option.Call
# 设置日期
calculation_date = ql.Date(25, 11, 2024)
ql.Settings.instance().evaluationDate = calculation_date
calendar = ql.NullCalendar()
day_count = ql.Actual365Fixed()
# 设置定价模型参数
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
rate_handle = ql.YieldTermStructureHandle(ql.FlatForward(calculation_date, interest_rate, day_count))
dividend_handle = ql.YieldTermStructureHandle(ql.FlatForward(calculation_date, dividend_yield, day_count))
vol_handle = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(calculation_date, calendar, volatility, day_count))
# Heston 模型参数
kappa = 2.0 # 波动率回归速率
theta = 0.04 # 长期均值
sigma_v = 0.1 # 波动率的波动率
rho = -0.5 # 相关性
v0 = 0.04 # 初始波动率
# 定义 Heston 过程
heston_process = ql.HestonProcess(rate_handle, dividend_handle, spot_handle, v0, kappa, theta, sigma_v, rho)
# 创建 Heston 模型
heston_model = ql.HestonModel(heston_process)
# 选择优化算法
minimizer = ql.Simplex()
end_criteria = ql.EndCriteria(500, 100, 1e-6, 1e-6, 1e-6)
# 校准模型
heston_model.calibrate([ql.QuoteHandle(ql.SimpleQuote(volatility))], minimizer, end_criteria)
print(f"Calibrated parameters: kappa={kappa}, theta={theta}, sigma_v={sigma_v}, rho={rho}, v0={v0}")
2.4.5 Heston 模型的期权定价:
Heston 模型的期权定价公式通常通过数值方法来计算,比较常用的两种方法是:
- 蒙特卡洛模拟(Monte Carlo Simulation):通过模拟多条价格路径来估计期权的理论价格。
- 快速傅里叶变换(FFT):这是一种高效的数值方法,可以计算Heston模型的闭式解,并且计算速度很快,适合处理大规模的定价问题。
2.4.6 如何确定初始波动率
1) 历史波动率
历史波动率是最简单的一种估计方法,它基于标的资产的过去价格变化来计算波动率。
步骤:
-
收集历史价格数据:收集标的资产的历史日收盘价。
-
计算每日回报率:通过计算每日的对数回报率(log returns),公式如下:
rt=ln(St/St−1)r_t = \ln(S_t / S_{t-1})rt=ln(St/St−1)其中,rtr_trt 是第 ttt 天的对数回报率,StS_tSt 是第 ttt 天的标的资产价格,St−1S_{t-1}St−1 是前一交易日的价格。
-
计算历史标准差:使用计算出的回报率,计算其标准差,得到 年化波动率。如果你使用的是日回报率,年化波动率的计算公式为:
σhist=252×std(rt)\sigma_{hist} = \sqrt{252} \times \text{std}(r_t)σhist=252×std(rt)这里,252 是每年交易日的平均数量(如果你使用的是日数据),
std(r_t)
是回报率的标准差。 -
估计 v0v_0v0:通过历史数据估算的波动率 σhist2\sigma_{hist}^2σhist2 即可作为初始方差 v0v_0v0 的估计。
优点:
- 简单易懂,容易计算。
缺点:
- 历史波动率 可能并不能准确反映未来波动率的变化,因为市场波动是动态的,而历史数据是静态的。
- 无法捕捉 隐含波动率曲线 的信息(即市场对未来波动性的预期)。
2)隐含波动率
可使用ATM(平值期权,或者近似平直期权)利用black scholes模型计算隐含波动率,当作Heston模型的初始波动率。
import QuantLib as ql
# 定义期权参数
spot_price = 100.0 # 当前标的资产价格
strike_price = 100.0 # 执行价格(ATM)
interest_rate = 0.05 # 无风险利率
volatility = 0.2 # 初始猜测的隐含波动率(用于计算期权价格)
expiry_date = ql.Date(15, 12, 2024) # 到期日
calculation_date = ql.Date(15, 11, 2024) # 当前日期
ql.Settings.instance().evaluationDate = calculation_date # 设置当前日期
# 创建 QuantLib 对象
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
rate_handle = ql.YieldTermStructureHandle(ql.FlatForward(calculation_date, interest_rate, ql.Actual365Fixed()))
vol_handle = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(calculation_date, ql.NullCalendar(), ql.QuoteHandle(ql.SimpleQuote(volatility)), ql.Actual365Fixed()))
# 创建 Black-Scholes 过程
bsm_process = ql.BlackScholesProcess(spot_handle, rate_handle, vol_handle)
# 创建欧式看涨期权
option_type = ql.Option.Call # 看涨期权
option = ql.EuropeanOption(ql.PlainVanillaExercise(expiry_date), ql.PlainVanillaPayoff(option_type, strike_price))
# 定义一个定价器
option.setPricingEngine(ql.AnalyticEuropeanEngine(bsm_process))
# 给定一个期权市场价格,进行反推隐含波动率
market_price = 5.0 # 假设期权的市场价格是 5.0
# 定义一个函数来计算隐含波动率
def implied_volatility(market_price):
# 用期权的市场价格进行反向计算
return option.impliedVolatility(market_price, bsm_process)
# 计算隐含波动率
implied_vol = implied_volatility(market_price)
print(f"隐含波动率: {implied_vol}")
解释:
-
定义期权相关参数:
spot_price
:当前标的资产价格。strike_price
:期权的执行价格(这里是 ATM 期权,执行价格和标的资产价格相同)。interest_rate
:无风险利率。volatility
:初始猜测的波动率(用于计算期权价格)。expiry_date
:期权的到期日期。calculation_date
:当前日期。
-
创建 QuantLib 对象:
spot_handle
:表示标的资产价格的对象。rate_handle
:表示无风险利率的对象。vol_handle
:表示波动率的对象。
-
Black-Scholes 过程:使用
BlackScholesProcess
来定义期权的标的资产价格过程。 -
期权定价:我们使用
ql.AnalyticEuropeanEngine
创建一个定价引擎,并将其应用到option
上。这个定价引擎根据 Black-Scholes 模型计算期权价格。 -
隐含波动率反向计算:
impliedVolatility
:QuantLib 中的impliedVolatility
方法可以给定市场价格,反向计算隐含波动率。我们将期权的市场价格作为输入,并使用 Black-Scholes 模型来计算隐含波动率。
2.5 Heston模型计算隐含波动率和希腊值
2.5.1. 校准 Heston 模型的参数
- 目标:找到最优的 Heston 模型参数(
kappa
、theta
、sigma
、rho
、v0
)。 - 方法:
- 通过市场数据(期权价格、行权价、到期时间等)来校准 Heston 模型,使得模型价格与市场实际价格尽可能一致。
- 使用 期权价格和市场数据 作为输入,通过优化算法(如
L-BFGS-B
)最小化模型价格与市场价格的误差。
# 为什么要校准?
Heston 模型参数的物理意义(如波动率的回归速率 kappa
和长期均值 theta
等)不能直接从市场数据中获得。这些参数需要通过拟合市场价格间接推算,确保模型的生成价格能够准确反映市场真实情况。(校准看前文2.4.3.2.2 -隐含波动率估计法)
这篇样例和上文一样的,只是更规整点。
# 这篇样例按照结算日期校准,就是不输入固定日期,按照今日日期,注意flatForward第一参数是0,,就代表是以今天为起始点。
# 校准使用的期权
market_prices
和 strikes
是列表,因为通常我们会使用多个期权的数据来校准 Heston 模型的参数。每个期权提供一个市场价格、行权价和到期时间的信息,所有这些期权的数据一起用于优化目标函数。
##为什么需要多个期权数据?
-
增强模型的适配能力:
- Heston 模型包含 5 个参数(
kappa
,theta
,sigma
,rho
,v0
),仅通过一个期权的数据很难确定这些参数的值。 - 使用多个期权的数据可以提高模型的稳定性和拟合的准确性,让模型更好地捕捉市场的特性。
- Heston 模型包含 5 个参数(
-
捕捉波动率曲面特性:
- 波动率曲面描述了不同到期日和不同行权价的隐含波动率。
- 使用多个期权的数据,可以帮助 Heston 模型更准确地反映市场波动率曲面的结构(例如,波动率微笑或偏斜)。
-
减少过拟合:
- 如果只用一个期权数据,Heston 模型可能会过拟合到该期权,而无法很好地泛化到其他期权。
- 使用多个期权数据可以让优化过程更稳健,避免过拟合。
##一般选几个期权?
-
推荐数量:
- 至少需要 5~10 个期权,以便为 Heston 模型的 5 个参数(
kappa
,theta
,sigma
,rho
,v0
)提供足够的信息。 - 如果可能,使用 15~20 个期权数据点 是一个不错的选择,尤其是在需要更精确拟合波动率曲面时。
- 至少需要 5~10 个期权,以便为 Heston 模型的 5 个参数(
-
多样性:
- 数据点应该覆盖不同的到期时间(
maturities
)和行权价(strikes
),以便更全面地校准 Heston 模型。
- 数据点应该覆盖不同的到期时间(
-
平衡数据规模:
- 过少的数据会导致模型的参数估计不准确。
- 过多的数据可能会增加计算复杂度,尤其是在参数校准时。
##行权价和到期日的选择讲究
1. 行权价(strikes
)的选择
- 覆盖深度虚值、平值和深度实值期权: 包括虚值(OTM)、平值(ATM)和实值(ITM)期权,确保模型能够捕捉波动率微笑或偏斜的特性。
- 推荐分布:
- 选择覆盖标的资产价格(
spot_price
)上下的多个行权价(例如,标的价格为 100,可以选择 80、90、100、110、120)。
- 选择覆盖标的资产价格(
- 为何需要分布?
- 行权价的分布有助于捕捉波动率微笑(行权价和波动率的关系)和市场动态特性。
- 单一行权价会导致模型难以区分参数之间的影响。
2. 到期日(maturities
)的选择
- 覆盖短期、中期和长期到期: 选择几个不同到期时间的期权,比如 1 个月、6 个月、1 年、2 年等。
- 为何需要分布?
- 不同到期时间的期权帮助捕捉波动率的时间动态特性。
- Heston 模型中,
kappa
和theta
等参数反映了波动率的均值回归特性,不同到期时间的数据能更好地校准这些参数。
- 注意到期时间分布: 短期和长期期权的比例应合理平衡,过多的短期数据可能导致对短期波动率的过拟合。
##实际操作建议
1. 平值期权优先:
- 平值期权(行权价接近标的价格)对模型参数的估计最敏感,通常是优先选择的。
- 例如:标的价格为 100,选择行权价 95、100 和 105 的期权。
2. 行权价分布:
- 如果标的价格为 100,选择的行权价可能是:[80, 90, 100, 110, 120]
3. 到期日分布
选择 3~5 个具有不同到期时间的期权,例如:[0.08(1 个月), 0.25 (3 个月), 0.5 (6 个月), 1, 2 ]
4. 优先市场活跃的期权:
数据来源很重要,优先选择市场成交量大的期权,因为它们的价格更可靠,反映了真实的市场情况
5. 注意市场异常:
如果期权价格受到了流动性不足或市场异常的影响(例如大幅偏离理论价格),需要谨慎处理或剔除。
##数据使用
1. 使用还未过期的期权数据
优点:
-
反映当前市场动态:
- 使用当前市场上未过期的期权数据,可以让模型捕捉到最新的市场特性,比如波动率微笑、偏斜和波动率曲面。
- 适用于实时定价、风险管理和交易策略分析。
-
校准结果更具实时性:
- 如果目标是校准 Heston 模型用于当前市场定价(例如对其他期权定价或预测隐含波动率曲面),那么未过期的期权数据是首选。
注意事项:
- 流动性问题:
- 优先选择成交活跃的期权,因为流动性不足的期权价格可能偏离市场真实水平。
- 数据范围:
- 确保选取的数据覆盖不同行权价(深度虚值到深度实值)和到期时间(短期到长期)。
2. 使用已过期的历史期权数据
优点:
-
研究和建模:
-
如果目标是研究历史市场行为(比如波动率变化、参数稳定性)或者构建长期的统计模型,历史期权数据非常有用。
-
-
丰富数据来源:
-
历史数据可以提供更多样化的市场情景,尤其是波动率曲面在不同时间点的特性。
-
使用场景:
-
市场行为分析:
-
分析过去的市场事件(例如金融危机期间的波动率变化)。
-
-
参数稳定性研究:
-
使用历史数据测试模型参数是否对市场变化敏感。
-
-
生成波动率曲面:
-
从历史期权数据中提取隐含波动率,生成历史波动率曲面。
-
注意事项:
-
数据准确性:
-
历史期权价格可能受到过时估值方法、异常交易或低流动性的影响,需要清洗数据。
-
-
基准日期问题:
-
Heston 模型的校准需要知道数据的“基准日期”(即评估日期),因此历史数据的评估日期需要明确。
-
总结
- 实时分析和交易: 使用未过期的实时期权数据。
- 市场研究和长期模型: 使用已过期的历史期权数据。
- 数据处理: 无论选择哪种数据,都要确保数据质量,并覆盖不同的行权价和到期时间。
3. 为什么可以混合使用看涨和看跌期权?(重要)
严格来说,你不需要分别用看涨期权和看跌期权单独校准 Heston 模型的参数,因为 Heston 模型的参数(如 kappa
, theta
, sigma
, rho
, v0
)描述的是标的资产的波动率动态,而不是具体某种期权的定价。因此,理论上你可以用看涨期权和看跌期权的数据混合在一起进行校准。
-
Heston 模型的核心:
-
Heston 模型描述的是标的资产价格的波动率行为,这与期权类型无关。
-
无论是看涨期权还是看跌期权,其价格都来源于标的资产的动态变化,因此可以通过相同的 Heston 模型参数进行拟合。
-
-
看涨看跌平价:
-
根据看涨看跌平价(Put-Call Parity),看涨期权和看跌期权的价格是相关的: C−P=S−Ke−rTC - P = S - K e^{-rT}C−P=S−Ke−rT
-
如果你知道一组看涨期权的价格,通过平价公式可以推算出看跌期权的价格,反之亦然。因此,校准模型时,看涨和看跌期权的数据是可以互补使用的。
-
-
混合数据提高稳定性:
-
使用看涨期权和看跌期权的混合数据,可以提供更多的信息,有助于模型的稳健校准。
-
如果只使用一类期权(例如只有看涨期权),可能会对模型的某些参数造成偏差。
-
def heston_calibration_error(params,
market_prices,
strikes,
spot_price,
risk_free_rate,
dividend_rate,
evaluation_date,
exercise_dates,
option_types):
kappa, theta, sigma, rho, v0 = params
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
risk_free_curve = ql.FlatForward(evaluation_date,
ql.QuoteHandle(ql.SimpleQuote(risk_free_rate)),
ql.Actual365Fixed(),
ql.Compounded,
dividend_curve = ql.FlatForward(evaluation_date, # 明确指定评估日期
dividend_rate,
ql.Actual365Fixed(),
ql.Compounded,
ql.Annual)
heston_process = ql.HestonProcess(
ql.YieldTermStructureHandle(risk_free_curve),
ql.YieldTermStructureHandle(dividend_curve),
spot_handle,
v0, kappa, theta, sigma, rho
)
heston_model = ql.HestonModel(heston_process)
engine = ql.AnalyticHestonEngine(heston_model)
error = 0.0
for i, (strike, maturity, market_price, option_type,exercise_date) in enumerate(zip(strikes, maturities, market_prices, option_types,exercise_dates)):
payoff = ql.PlainVanillaPayoff(option_type, strike) # 使用不同的期权类型
option = ql.VanillaOption(payoff, exercise_date)
option.setPricingEngine(engine)
model_price = option.NPV()
error += (model_price - market_price)**2
return error
调用校准:
option_types = [ql.Option.Call, ql.Option.Put, ql.Option.Call, ql.Option.Put, ql.Option.Call, ql.Option.Put]
exercise_dates = ['XXXXXXXX', 'XXXXXXXX', 'XXXXXXXX', 'XXXXXXXX', 'XXXXXXXX', 'XXXXXXXX']
evaluation_date = 'xxxxxxxx'
try:
datetime.datetime.strptime(evaluation_date, '%Y-%m-%d')
except Exception as e:
try:
datetime.datetime.strptime(evaluation_date,'%Y%m%d')
except Exception as e:
print('evaluation_date日期格式错误')
return None
else:
# 处理日期格式
evaluation_date = datetime.datetime.strptime(evaluation_date,'%Y%m%d').strftime('%Y-%m-%d')
tradeDateObj = datetime.datetime.strptime(evaluation_date, '%Y-%m-%d')
ql_evaluation = ql.Date(tradeDateObj.day,
tradeDateObj.month,
tradeDateObj.year)
ql_exercise_date_List = []
for content in exercise_dates:
try:
datetime.datetime.strptime(content, '%Y-%m-%d')
except Exception as e:
try:
datetime.datetime.strptime(content,'%Y%m%d')
except Exception as e:
print('exercise_date日期格式错误')
badTypeBool = True
break
else:
# 处理日期格式
content = datetime.datetime.strptime(content,'%Y%m%d').strftime('%Y-%m-%d')
exercise_tradeDateObj = datetime.datetime.strptime(content, '%Y-%m-%d')
ql_exercise_date = ql.Date(exercise_tradeDateObj.day,
exercise_tradeDateObj.month,
exercise_tradeDateObj.year)
ql_exercise_date_List.append(ql_exercise_date)
initial_guess = [2.0, 0.02, 0.5, -0.5, 0.04]
bounds = [(0.01, 5.0), (0.01, 1.0), (0.01, 1.0), (-1.0, 1.0), (0.01, 1.0)]
market_prices = [xxx, xxx, xxx,xxx,xxx,xxx]
strikes = [xxx, xxx, xxx,xxx,xxx,xxx]
risk_free_rate = 0.05
dividend_rate = 0.02
result = minimize(
heston_calibration_error,
initial_guess,
args=(market_prices,
strikes,
spot_price,
risk_free_rate,
dividend_rate,
ql_evaluation ,
ql_exercise_date_List,
option_types),
bounds=bounds,
method='L-BFGS-B'
)
# ql.FlatForward注意参数输入顺序
ql.flatForward()这玩意儿,输入参数顺序不同,代表的含义也不同。
# 不指定日期(没试过这段代码,慎重)
from scipy.optimize import minimize
import QuantLib as ql
# 定义目标函数:误差度量
def heston_calibration_error(params, market_prices, strikes, maturities, spot_price, risk_free_rate, dividend_rate):
kappa, theta, sigma, rho, v0 = params
# 定义 Heston 模型
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
risk_free_curve = ql.FlatForward(0, ql.NullCalendar(), ql.SimpleQuote(risk_free_rate), ql.Actual365Fixed())
dividend_curve = ql.FlatForward(0, ql.NullCalendar(), ql.SimpleQuote(dividend_rate), ql.Actual365Fixed())
heston_process = ql.HestonProcess(
ql.YieldTermStructureHandle(risk_free_curve),
ql.YieldTermStructureHandle(dividend_curve),
spot_handle,
v0, kappa, theta, sigma, rho
)
heston_model = ql.HestonModel(heston_process)
engine = ql.AnalyticHestonEngine(heston_model)
# 计算每个期权的模型价格
error = 0.0
for i, (strike, maturity, market_price) in enumerate(zip(strikes, maturities, market_prices)):
payoff = ql.PlainVanillaPayoff(ql.Option.Call, strike)
exercise = ql.EuropeanExercise(ql.Date().todaysDate() + int(maturity * 365))
option = ql.VanillaOption(payoff, exercise)
option.setPricingEngine(engine)
model_price = option.NPV()
error += (model_price - market_price)**2
return error
# 优化校准
initial_guess = [2.0, 0.02, 0.5, -0.5, 0.04] # 初始参数
bounds = [(0.01, 5), (0.01, 1), (0.01, 1), (-1, 1), (0.01, 1)]
market_prices = [10.5, 8.2, 12.3] # 市场期权价格
strikes = [100, 110, 120] # 行权价
maturities = [1.0, 2.0, 3.0] # 到期时间
spot_price = 100
risk_free_rate = 0.05
dividend_rate = 0.02
result = minimize(heston_calibration_error, initial_guess, args=(market_prices, strikes, maturities, spot_price, risk_free_rate, dividend_rate), bounds=bounds, method='L-BFGS-B')
kappa, theta, sigma, rho, v0 = result.x
20241203更新
heston_calibration_error
函数实际上是专门为 minimize
函数设计的,它本身并不直接用于其他场景。它的目的是计算当前参数下的误差(即模型价格与市场价格之间的差异),并将这个误差返回给 minimize
函数进行最小化。它的第一个参数params在使用的时候其实就是initial_guess,在minimize运行的时候会自动传参。
enumerate的作用:
其中,enumerate的作用:
# 以指定日期进行校准(我用的这个)
import QuantLib as ql
# 这个给股指期权用,所以都是欧式的
# 不能单独使用,只能被heston_getParam调用
def heston_calibration_error(params,
market_prices,
strikes,
spot_price,
risk_free_rate,
evaluation_date,
exercise_dates,
optionTypeList):
kappa, theta, sigma, rho, v0 = params
ql.Settings.instance().evaluationDate = evaluation_date
# 价格handle
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
# 无风险利率,这里用中国十年期债券年收益率
risk_free_curve = ql.FlatForward(evaluation_date,
ql.QuoteHandle(ql.SimpleQuote(risk_free_rate)),
ql.Actual365Fixed(),
ql.Compounded,
ql.Annual)
# 没有股息
dividend_curve = ql.FlatForward(evaluation_date, # 明确指定评估日期
0,
ql.Actual365Fixed(),
ql.Compounded,
ql.Annual)
# 过程
# ql.HestonProcess(
# yieldCurve, # 第1个参数:无风险收益率曲线 (YieldTermStructureHandle)
# dividendCurve, # 第2个参数:股息收益率曲线 (YieldTermStructureHandle)
# s0, # 第3个参数:标的资产价格的初始值 (QuoteHandle)
# v0, # 第4个参数:初始方差
# kappa, # 第5个参数:均值回归速率
# theta, # 第6个参数:长期方差
# sigma, # 第7个参数:方差的波动率
# rho # 第8个参数:资产收益率与方差的相关系数
# )
heston_process = ql.HestonProcess(ql.YieldTermStructureHandle(risk_free_curve),
ql.YieldTermStructureHandle(dividend_curve),
spot_handle,
v0, kappa, theta, sigma, rho)
heston_model = ql.HestonModel(heston_process)
engine = ql.AnalyticHestonEngine(heston_model)
error = 0.0
for i, (strike,exercise_date,market_price,option_type) in enumerate(zip(strikes,exercise_dates,market_prices,optionTypeList)):
payoff = ql.PlainVanillaPayoff(option_type, strike)
exercise = ql.EuropeanExercise(exercise_date)
option = ql.VanillaOption(payoff,exercise)
option.setPricingEngine(engine)
# 模型价格
model_price = option.NPV()
error += (model_price - market_price) ** 2
return error
# heston Process注意输入参数的顺序
ql.HestonProcess()
的输入参数是有严格的顺序要求的。QuantLib 中的许多类(包括 ql.HestonProcess
)是用 C++ 编写的,在 Python 接口中,它们的参数顺序直接映射自 C++ 的构造函数。因此,必须按照规定的顺序传递参数,否则会抛出错误。
# ql.HestonProcess()
的参数顺序
ql.HestonProcess
的构造函数定义如下:
ql.HestonProcess(
yieldCurve, # 第1个参数:无风险收益率曲线 (YieldTermStructureHandle)
dividendCurve, # 第2个参数:股息收益率曲线 (YieldTermStructureHandle)
s0, # 第3个参数:标的资产价格的初始值 (QuoteHandle)
v0, # 第4个参数:初始方差
kappa, # 第5个参数:均值回归速率
theta, # 第6个参数:长期方差
sigma, # 第7个参数:方差的波动率
rho # 第8个参数:资产收益率与方差的相关系数
)
# 期权执行价模型
1. ql.EuropeanExercise
-
用途: 表示欧式期权。
-
特点: 欧式期权只能在到期日当天行权。
-
构造函数:
exercise = ql.EuropeanExercise(ql.Date(31, 12, 2024))
2. ql.AmericanExercise
-
用途: 表示美式期权。
-
特点: 美式期权可以在到期日之前的任意时间点行权。
-
构造函数:
# 美式期权,可以从 2024-01-01 到 2024-12-31 行权
exercise = ql.AmericanExercise(ql.Date(1, 1, 2024), ql.Date(31, 12, 2024), payoff_at_expiry=True)
-
earliest_date
: 期权最早可以行权的日期。 -
latest_date
: 到期日期(行权的最后一天)。 -
payoff_at_expiry
: (可选)如果为True
,收益在到期日支付;如果为False
,收益即时支付。
ql.BermudanExercise
-
用途: 表示百慕大式期权。
-
特点: 百慕大期权只能在一组预先定义的日期中行权。
-
构造函数:
# 百慕大式期权,允许在指定日期行权
dates = [ql.Date(1, 6, 2024), ql.Date(1, 9, 2024), ql.Date(31, 12, 2024)]
exercise = ql.BermudanExercise(dates, payoff_at_expiry=True)
-
dates
: 一个ql.Date
列表,表示允许行权的日期集合。 -
payoff_at_expiry
: (可选)如果为True
,收益在到期日支付;如果为False
,收益即时支付。
第四种自定义我用不来,也用不到,大家有需要只能自己摸索了
2.5.2 使用 Heston 模型计算隐含波动率
heston没有直接算波动率的方法,需要使用二分法。我先复制一个初步版本的,大家自己镶嵌到项目里面吧。我写好了,先复制一个初步版本的,大家自己镶嵌到项目里面吧。需要注意的是,极度实质和虚值的期权是算不出隐含波动率的。但是不影响计算希腊值。初始猜测值,可以用常数,也可以直接先用一个bs模型先计算一个隐含波动率,然后作为heston模型的初始猜测值。
# 布莱克舒尔茨模型
def black_scholes_implied_vol(market_price,
spot_price,
strike,
evaluation_date,
exercise_date,
risk_free_rate,
call_put_type):
# 创建期权
if call_put_type == 'C':
optionType = ql.Option.Call
elif call_put_type == 'P':
optionType = ql.Option.Put
else:
print('call_put_type没有输入准确,请核对后再输入')
return None
try:
datetime.datetime.strptime(evaluation_date, '%Y-%m-%d')
except Exception as e:
try:
datetime.datetime.strptime(evaluation_date,'%Y%m%d')
except Exception as e:
print('startDate日期格式错误')
return None
else:
# 处理日期格式
evaluation_date = datetime.datetime.strptime(evaluation_date,'%Y%m%d').strftime('%Y-%m-%d')
tradeDateObj = datetime.datetime.strptime(evaluation_date, '%Y-%m-%d')
ql_evaluation = ql.Date(tradeDateObj.day,
tradeDateObj.month,
tradeDateObj.year)
try:
datetime.datetime.strptime(exercise_date, '%Y-%m-%d')
except Exception as e:
try:
datetime.datetime.strptime(exercise_date,'%Y%m%d')
except Exception as e:
print('exercise_date日期格式错误')
return None
else:
# 处理日期格式
exercise_date = datetime.datetime.strptime(exercise_date,'%Y%m%d').strftime('%Y-%m-%d')
exercise_tradeDateObj = datetime.datetime.strptime(exercise_date, '%Y-%m-%d')
ql_exercise_date = ql.Date(exercise_tradeDateObj.day,
exercise_tradeDateObj.month,
exercise_tradeDateObj.year)
payoff = ql.PlainVanillaPayoff(optionType, strike)
exercise = ql.EuropeanExercise(ql_exercise_date)
option = ql.VanillaOption(payoff, exercise)
# 无风险利率,这里用中国十年期债券年收益率
risk_free_curve = ql.FlatForward(ql_evaluation,
ql.QuoteHandle(ql.SimpleQuote(risk_free_rate)),
ql.Actual365Fixed(),
ql.Compounded,
ql.Annual)
# 没有股息
dividend_curve = ql.FlatForward(ql_evaluation, # 明确指定评估日期
0,
ql.Actual365Fixed(),
ql.Compounded,
ql.Annual)
# 价格handle
# 标的资产价格
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
# 初始猜测
volatility = ql.BlackConstantVol(ql_evaluation,
self.option_calendar,
ql.QuoteHandle(ql.SimpleQuote(0.2)),
ql.Actual365Fixed())
bs_process = ql.BlackScholesMertonProcess(spot_handle,
ql.YieldTermStructureHandle(dividend_curve),
ql.YieldTermStructureHandle(risk_free_curve),
ql.BlackVolTermStructureHandle(volatility))
try:
implied_vol = option.impliedVolatility(market_price,
bs_process,
1e-2,
5000,
1e-7,
50)
except RuntimeError as e:
implied_vol = 9999
print('极度实值或者虚值,隐含波动率计算失败')
return implied_vol
# 算隐含波动率
def heston_impliedVolatility(
option_marketPrice,
spot_price,
risk_free_rate,
strike,
evaluation_date,
exercise_date,
hestonParamDict:dict,
call_put_type,
bs_initialVol
):
# 创建期权
if call_put_type == 'C':
optionType = ql.Option.Call
elif call_put_type == 'P':
optionType = ql.Option.Put
else:
print('call_put_type没有输入准确,请核对后再输入')
return None
try:
datetime.datetime.strptime(evaluation_date, '%Y-%m-%d')
except Exception as e:
try:
datetime.datetime.strptime(evaluation_date,'%Y%m%d')
except Exception as e:
print('evaluation_date日期格式错误')
return None
else:
# 处理日期格式
evaluation_date = datetime.datetime.strptime(evaluation_date,'%Y%m%d').strftime('%Y-%m-%d')
tradeDateObj = datetime.datetime.strptime(evaluation_date, '%Y-%m-%d')
ql_evaluation = ql.Date(tradeDateObj.day,
tradeDateObj.month,
tradeDateObj.year)
try:
datetime.datetime.strptime(exercise_date, '%Y-%m-%d')
except Exception as e:
try:
datetime.datetime.strptime(exercise_date,'%Y%m%d')
except Exception as e:
print('exercise_date日期格式错误')
return None
else:
# 处理日期格式
exercise_date = datetime.datetime.strptime(exercise_date,'%Y%m%d').strftime('%Y-%m-%d')
exercise_tradeDateObj = datetime.datetime.strptime(exercise_date, '%Y-%m-%d')
ql_exercise_date = ql.Date(exercise_tradeDateObj.day,
exercise_tradeDateObj.month,
exercise_tradeDateObj.year)
ql.Settings.instance().evaluationDate = ql_evaluation
# 无风险利率,这里用中国十年期债券年收益率
risk_free_curve = ql.FlatForward(ql_evaluation,
ql.QuoteHandle(ql.SimpleQuote(risk_free_rate)),
ql.Actual365Fixed(),
ql.Compounded,
ql.Annual)
# 没有股息
dividend_curve = ql.FlatForward(ql_evaluation, # 明确指定评估日期
0,
ql.Actual365Fixed(),
ql.Compounded,
ql.Annual)
# 价格handle
# 标的资产价格
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
v0 = hestonParamDict['v0']
kappa = hestonParamDict['kappa']
theta = hestonParamDict['theta']
sigma = hestonParamDict['sigma']
rho = hestonParamDict['rho']
heston_process = ql.HestonProcess(ql.YieldTermStructureHandle(risk_free_curve),
ql.YieldTermStructureHandle(dividend_curve),
spot_handle,
v0,
kappa, theta, sigma, rho)
heston_model = ql.HestonModel(heston_process)
heston_engine = ql.AnalyticHestonEngine(heston_model)
payoff = ql.PlainVanillaPayoff(optionType,strike)
exercise = ql.EuropeanExercise(ql_exercise_date)
option_index = ql.VanillaOption(payoff,exercise)
# 设置引擎
option_index.setPricingEngine(heston_engine)
# heston_price = option_index.NPV()
# # 使用隐含波动率函数反推隐含波动率
# implied_vol_heston = ql.Impliedvo
# # implied_vol = option_index.impliedVolatility(option_marketPrice,heston_process)
implied_vol = self.implied_vol_heston_bisection(market_price=option_marketPrice,
option=option_index,
heston_process=heston_process,
heston_model=heston_model,
bs_initialVol=bs_initialVol)
# print(f"反推的隐含波动率是: {implied_vol}")
return implied_vol
# 二分法
def implied_vol_heston_bisection(
market_price,
option,
heston_process,
heston_model,
bs_initialVol,
lower_bound=1e-08,
upper_bound=2.0,
tol=1e-2):
"""
使用二分法计算隐含波动率,基于 Heston 模型。
:param market_price: 市场期权价格
:param option: 定义好的期权对象(VanillaOption)
:param heston_engine: 使用 Heston 模型的定价引擎
:param lower_bound: 波动率下界
:param upper_bound: 波动率上界
:param tol: 收敛阈值
:param max_iter: 最大迭代次数
:return: 隐含波动率
"""
mid_vol = copy.deepcopy(bs_initialVol)
diff = 9999
i = 0
while abs(diff) > tol:
sleep(0.2)
# 从 HestonProcess 和 HestonModel 提取静态参数
risk_free_rate = heston_process.riskFreeRate()
dividend_curve = heston_process.dividendYield()
spot_handle = heston_process.s0()
# kappa = 2
# theta = 0.02
# sigma = 0.5
# rho = -0.5
kappa = heston_model.kappa()
theta = heston_model.theta()
sigma = heston_model.sigma()
rho = heston_model.rho()
# 重新创建 HestonProcess,更新初始波动率 v0
updated_heston_process = ql.HestonProcess(
risk_free_rate, dividend_curve, spot_handle,
bs_initialVol, kappa, theta, sigma, rho
)
# 重新创建 HestonModel 和引擎
updated_heston_model = ql.HestonModel(updated_heston_process)
updated_heston_engine = ql.AnalyticHestonEngine(updated_heston_model)
# 重新设置定价引擎
option.setPricingEngine(updated_heston_engine)
model_price = option.NPV()
# 误差检查
diff = model_price - market_price
# 更新上下界
if diff > 0:
upper_bound = mid_vol # 模型价格高,代表mid 偏高,减小波动率
else:
lower_bound = mid_vol # 模型价格低,代表mid 偏低,增加波动率
if abs(diff) <= tol:
break
else:
mid_vol = (lower_bound + upper_bound) / 2
i += 1
print(str(i) + ' ' + 'diff: ' + str(diff))
print('stop')
return mid_vol
2.5.3 使用 Heston 模型计算希腊值
QuantLib 允许直接计算期权的希腊值,例如 Delta、Gamma、Vega 等。你可以使用 Heston 模型导出的期权实例调用这些属性:
# 计算希腊值
delta = option.delta()
gamma = option.gamma()
vega = option.vega()
theta = option.theta()
rho = option.rho()
print(f"Delta: {delta}, Gamma: {gamma}, Vega: {vega}, Theta: {theta}, Rho: {rho}")
2.5.4 波动率曲面和heston计算希腊值
上面都是单一的波动率数据,但是一般业内比较专业的都是用波动率曲面。示例代码如下:
import QuantLib as ql
import numpy as np
# 1. 市场数据
spot_price = 100
risk_free_rate = 0.05
dividend_rate = 0.02
market_strikes = [90, 100, 110] # 行权价格
market_maturities = [0.5, 1.0, 2.0] # 到期时间(单位:年)
market_vols = np.array([[0.25, 0.20, 0.18], [0.22, 0.18, 0.17], [0.20, 0.19, 0.18]]) # 波动率曲面
# 2. 创建波动率曲面对象
calendar = ql.NullCalendar()
day_count = ql.Actual365Fixed()
evaluation_date = ql.Date(15, 11, 2024)
ql.Settings.instance().evaluationDate = evaluation_date
# 设置无风险利率和股息率曲线
risk_free_curve = ql.FlatForward(evaluation_date, risk_free_rate, day_count)
dividend_curve = ql.FlatForward(evaluation_date, dividend_rate, day_count)
# 3. 为Heston模型构建过程
v0 = 0.04
kappa = 2.0
theta = 0.04
sigma = 0.5
rho = -0.5
# 定义波动率曲面
vol_surface = ql.Matrix(len(market_maturities), len(market_strikes))
# 填充波动率曲面数据
for i, maturity in enumerate(market_maturities):
for j, strike in enumerate(market_strikes):
vol_surface[i][j] = market_vols[i, j]
# 构建Heston过程
spot_handle = ql.QuoteHandle(ql.SimpleQuote(spot_price))
risk_free_handle = ql.YieldTermStructureHandle(risk_free_curve)
dividend_handle = ql.YieldTermStructureHandle(dividend_curve)
vol_handle = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(evaluation_date, calendar, ql.QuoteHandle(ql.SimpleQuote(0.2)), day_count))
# Heston模型
heston_process = ql.HestonProcess(risk_free_handle, dividend_handle, spot_handle, v0, kappa, theta, sigma, rho)
heston_model = ql.HestonModel(heston_process)
engine = ql.AnalyticHestonEngine(heston_model)
# 4. 计算期权价格和Delta
for i, maturity in enumerate(market_maturities):
for j, strike in enumerate(market_strikes):
# 计算期权定价
payoff = ql.PlainVanillaPayoff(ql.Option.Put, strike)
exercise = ql.EuropeanExercise(evaluation_date + int(maturity * 365)) # 到期日
option = ql.VanillaOption(payoff, exercise)
option.setPricingEngine(engine)
# 计算期权Delta
delta = option.delta()
print(f"Maturity: {maturity}, Strike: {strike}, Delta: {delta}")
2.5.5 模型到期日
这里模型到期日值得是距离今天还剩到期多少时间,单位是年。分母是365或者250,自己调整。
2.5.6 python 波动率曲面绘图
1) 步骤概览:
-
准备数据:你需要有期权的行权价格(strikes)、到期日(maturities)和隐含波动率(implied volatilities)。这些数据可以从期权市场获取,或者通过期权定价模型(如Black-Scholes)计算得到。
-
创建网格:为了绘制波动率曲面,你需要将行权价格和到期日的数据组织成网格状。
-
绘制图形:使用
matplotlib
中的plot_surface
函数绘制波动率曲面。
示例代码:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 假设数据
strikes = np.linspace(80, 120, 10) # 行权价格从80到120,共10个值
maturities = np.linspace(0.1, 2.0, 5) # 到期时间从0.1年到2年,共5个值
# 假设隐含波动率数据(通常通过市场数据或者定价模型来获得)
# 这里使用简单的示例数据
X, Y = np.meshgrid(strikes, maturities) # 创建行权价和到期日的网格
Z = np.exp(-0.02 * Y) * (0.2 + 0.03 * np.log(X)) # 假设的隐含波动率公式
# 创建3D图形
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# 绘制波动率曲面
ax.plot_surface(X, Y, Z, cmap='viridis')
# 设置标签
ax.set_xlabel('Strike Price')
ax.set_ylabel('Maturity (Years)')
ax.set_zlabel('Implied Volatility')
ax.set_title('Volatility Surface')
# 显示图形
plt.show()
2) 解释:
-
准备数据:
strikes
:期权的行权价格数组。这里假设行权价格从80到120,以10个值为例。maturities
:期权的到期时间数组,单位是年。这里假设到期时间从0.1年到2年,共有5个值。Z
:隐含波动率数据,这里使用了一个假设的公式,通常你会根据实际的市场数据或通过期权定价模型来获取隐含波动率。
-
创建网格:
- 使用
np.meshgrid
将行权价格strikes
和到期时间maturities
组合成一个二维网格,得到X
(行权价格网格)和Y
(到期时间网格)。
- 使用
-
绘制波动率曲面:
- 使用
matplotlib
中的Axes3D
模块来绘制三维图形。 ax.plot_surface
用来绘制三维曲面,cmap='viridis'
是设置图形的颜色映射。
- 使用
-
显示图形:
- 使用
plt.show()
来显示波动率曲面图。
- 使用
结果:
这段代码将生成一个三维波动率曲面图,显示隐含波动率(Z
轴)与行权价格(X
轴)和到期日(Y
轴)之间的关系。
2.6 波动率曲面有啥用?
波动率曲面(Volatility Surface) 是描述不同时期、不同行权价的期权隐含波动率(Implied Volatility,简称 IV)变化的三维图表。它是金融衍生品市场中不可或缺的工具,用于捕捉市场对于未来波动性的预期,并为期权定价、风险管理和交易策略提供重要的参考。
2.6.1 波动率曲面的构成
波动率曲面通常有以下三个维度:
-
行权价(Strike Price):期权的执行价格,通常横轴(X轴)表示。行权价对隐含波动率的影响通常表现在不同到期日的期权上。
-
到期时间(Maturity):期权的到期日,通常作为一个独立的维度,垂直于行权价和隐含波动率。它展示了不同到期日的期权如何根据市场预期变化。
-
隐含波动率(Implied Volatility,IV):隐含波动率是通过期权市场价格反推出的波动率,通常作为曲面的第三维度(纵轴)。它告诉我们市场对未来波动性的预期。
波动率曲面通过显示不同到期时间、行权价格与隐含波动率之间的关系,帮助我们理解和分析市场对波动性的预期。
2.6.2 波动率曲面在实际中的作用
-
期权定价:
- 定价模型:波动率曲面为期权定价提供了不可或缺的信息。在实际定价中,无法使用一个固定的波动率来定价所有期权,尤其是不同到期日和不同执行价格的期权。波动率曲面通过提供多维的隐含波动率,帮助我们更准确地对期权进行定价。
- Heston模型等:许多定价模型(如Heston模型)需要以波动率曲面作为输入,用来反映市场对于不同执行价和不同到期日的隐含波动率预期。
-
风险管理与对冲:
- 风险评估:通过分析波动率曲面,交易员可以识别不同到期日、不同执行价格的期权之间的波动率差异,从而为投资组合的风险管理提供信息。
- 对冲策略:波动率曲面有助于制定对冲策略,尤其是对于那些包含多个期权合约的复杂期权组合。
-
市场情绪与趋势分析:
- Volatility Smile/Skew:波动率曲面可以帮助识别**波动率微笑(Volatility Smile)或波动率倾斜(Volatility Skew)**的现象。这些现象反映了市场的情绪。例如,股指期权往往呈现出波动率微笑,表明市场对尾部风险的预期较高;而股票期权可能呈现波动率倾斜,表明市场对下跌的风险比上涨更为关注。
- 隐含波动率变化:通过波动率曲面,交易员可以分析隐含波动率的变化趋势,并预测未来市场波动的潜在变化。例如,如果某一特定到期日的隐含波动率突然上升,可能表示市场预期即将有重要事件发生(如公司财报、央行决策等)。
-
套利机会与定价偏差:
- 套利策略:波动率曲面可帮助交易员识别套利机会。例如,如果市场上的期权隐含波动率曲面与理论模型(如Black-Scholes或Heston模型)的曲面不一致,交易员可以通过买入低估的期权并卖出高估的期权来实现套利。
- 定价偏差:通过波动率曲面,交易员可以识别期权定价中的不合理现象(如某个行权价的期权波动率异常),并从中找到潜在的交易机会。
-
对冲和波动率交易:
- 波动率交易:波动率曲面还被用来进行波动率交易。波动率本身也是一种资产类别,投资者可以通过买卖期权、期货等金融工具来对冲波动风险或进行波动率套利。
- 波动率对冲:例如,某些投资者可能对股票或指数的波动性风险感兴趣,而不关心其具体方向。在这种情况下,波动率曲面为他们提供了如何管理这些风险的策略。
2.6.3 波动率曲面如何变化?
波动率曲面并非静态,它会随着市场环境的变化而变化,受多种因素影响,如:
- 市场风险偏好变化:当市场不确定性增大时,期权的隐含波动率通常会上升,特别是对于远离当前标的资产价格的期权(例如远离实值的期权)。这会导致波动率曲面向上抬升。
- 市场事件:比如公司财报发布、政策变动、宏观经济变化等,都可能使市场隐含波动率发生剧烈变化。
- 市场流动性:市场流动性也会影响波动率曲面的形态。在流动性较低的情况下,某些期权的隐含波动率可能会出现剧烈波动。
2.6.4 如何使用波动率曲面进行建模?
在 QuantLib 或其他定价软件中,波动率曲面通常通过插值或拟合方法来构建。例如,你可以使用 PiecewiseConstantVol 或 BlackForwardVol 等类来定义不同到期日、行权价的隐含波动率。这些方法能够处理市场数据,形成完整的波动率曲面,然后输入到定价模型中进行期权的定价和风险分析。
波动率曲面构建的常见方法
-
插值法:
- 使用已知的隐含波动率点,利用插值方法(如立方插值、样条插值等)生成完整的曲面。
-
多项式拟合:
- 使用多项式回归方法拟合市场数据,得到光滑的波动率曲面。
-
模型拟合:
- 使用一些模型(如 Heston、SABR 等)来拟合市场数据,得到参数化的波动率曲面。
附录
1. scipy手搓bs:
网址:https://www.zhihu.com/question/51713524/answer/2506998553
2. scipy手搓和使用quantLib的优缺点
2.1使用 SciPy 手动实现的优缺点
优点:
- 灵活性高:可以完全自定义损失函数、迭代过程和插值方法,适合复杂需求或特殊期权定价模型。
- 轻量化:依赖简单(
numpy
,scipy
),无需额外安装大型金融库。 - 透明性:实现过程清晰,便于学习和调试。
缺点:
- 实现工作量较大:需要自己编写Black-Scholes公式、目标函数,并配置数值求解器。
- 优化性能有限:SciPy的求解器(如
scipy.optimize
)虽易用,但对金融场景的优化能力可能不如专用库。 - 缺乏附加功能:无法直接处理期限结构、波动率曲面等复杂场景。
适用场景:
- 教学或研究目的,想了解隐含波动率计算的过程。
- 轻量级项目,仅需简单计算。
2.2使用 QuantLib 的优缺点
优点:
- 功能强大:QuantLib 提供完整的金融工具链,包括期权定价、隐含波动率计算、波动率曲面建模等。
- 高效可靠:专为金融计算设计,性能优化良好,数值稳定性高。
- 支持复杂场景:支持期限结构、波动率曲面插值和不同资产类型的期权。
缺点:
- 学习曲线较陡:QuantLib 的接口相对复杂,入门可能需要时间。
- 依赖较重:需要安装和了解一个大型库。
- 灵活性稍低:需要适配库提供的工具,某些特殊需求可能难以实现。
适用场景:
- 实际交易或金融工程项目,需处理复杂的波动率计算场景。
- 需要频繁计算隐含波动率、处理大量期权数据。
选择建议
-
如果你需要快速上手、计算简单的隐含波动率:
使用 SciPy 手动实现。
示例:import numpy as np from scipy.stats import norm from scipy.optimize import brentq def black_scholes(S, K, T, r, sigma, option_type="call"): 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": return S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2) elif option_type == "put": return K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1) def implied_volatility(S, K, T, r, market_price, option_type="call"): def objective_function(sigma): return black_scholes(S, K, T, r, sigma, option_type) - market_price return brentq(objective_function, 1e-6, 5.0) # 搜索隐含波动率 # 示例 S, K, T, r, market_price = 100, 110, 1, 0.05, 5 iv = implied_volatility(S, K, T, r, market_price) print(f"Implied Volatility: {iv:.4f}")
-
如果你需要处理多个期权,或者涉及波动率曲面:
使用 QuantLib。
示例:import QuantLib as ql # 参数 spot_price = 100 strike_price = 110 maturity = 1.0 # 到期时间(年) market_price = 5 # 市场期权价格 risk_free_rate = 0.05 # QuantLib 定义 spot = ql.SimpleQuote(spot_price) rf_curve = ql.FlatForward(0, ql.NullCalendar(), ql.QuoteHandle(ql.SimpleQuote(risk_free_rate)), ql.Actual365Fixed()) vol_handle = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(0, ql.NullCalendar(), ql.QuoteHandle(ql.SimpleQuote(0.2)), ql.Actual365Fixed())) process = ql.BlackScholesProcess(ql.QuoteHandle(spot), ql.YieldTermStructureHandle(rf_curve), vol_handle) # 隐含波动率计算 option = ql.EuropeanOption(ql.PlainVanillaPayoff(ql.Option.Call, strike_price), ql.EuropeanExercise(ql.Date(365 * maturity))) iv = option.impliedVolatility(market_price, process) print(f"Implied Volatility: {iv:.4f}")
2.3 总结
- 简单需求:SciPy(轻量、高灵活性)。
- 专业金融应用:QuantLib(强大、稳定)。
3. QuantLib常见交易日规则
除了ql.NullCalendar()之外还有: