QuantLib · Options pricing

QuantLib 期权定价:Black-Scholes、Greeks 与四种引擎交叉验证

同一个平值看涨期权(S=K=100、r=5%、vol=20%、一年期),本页用 QuantLib 1.43 在四类定价引擎上分别算出价格与 Greeks,并和手写 Black-Scholes 公式、有限差分 Greeks 逐项对照:解析解 10.4681483852,树引擎误差 200 ppm 上下,有限差分 38 ppm,蒙特卡洛 89 ppm。价格能算出来只是开始,误差有多大、Greeks 从哪来、什么期权没有唯一解才是这一页要回答的问题。

依据:本机 QuantLib 1.43 实测 + 官方文档对照引擎:解析 / 树 / 有限差分 / 蒙特卡洛误差量级:0 ~ 202 ppm
期权类型欧式 / 美式 / 亚式 / 障碍
定价引擎解析 · 树 · 有限差分 · 蒙特卡洛
GreeksDelta / Gamma / Vega / Theta / Rho
校验手写公式 · 差分 · 引擎互校

期权定价的四层结构:先选合约(类型与行权方式),再选引擎,引擎给出价格与 Greeks,最后用另一种方法复核。示意图为结构说明,非实盘界面。

测试版本QuantLib 1.43(PyPI wheel,本机实测)Python 版本3.11.9(Windows,CPython 官方版)最后核验2026-10-09(数值为本机一次运行输出)合约参数S=K=100,r=5%,q=0,vol=20%,2024-01-15 → 2025-01-15估值日2024-01-15(`ql.Settings.instance().evaluationDate`)计息约定Actual/365 (Fixed),T = 1.00273973 年可直接复现是(价格、Greeks、误差与脚本都在本页)本站实测范围仅定价与希腊字母对照;不含行情数据、不接实盘、不构成投资建议
实验环境QuantLib 1.43 / Python 3.11.9 / Windows
对照引擎解析 · 树 · 有限差分 · 蒙特卡洛
核心结论解析解与手写公式差 1.78e-15
证据等级本站实测(价格、Greeks、误差均在本页)
Option types

QuantLib 支持哪些期权、分别配什么引擎?

先看合约层:不同期权类型能用的引擎并不一样,行权方式选错会直接报错。下表把类型、可用引擎与本站是否实测写在一起,避免「看起来能用」的误判。

合约类型与引擎的对应关系(「未实测」一栏如实标注,本站不编造数字)
期权类型QuantLib 类 / 构造本页/本站实测过的引擎本站实测的 NPV适用场景与注意点
欧式看涨(基线)`ql.VanillaOption` + `ql.PlainVanillaPayoff` + `ql.EuropeanExercise`AnalyticEuropeanEngine / BinomialVanillaEngine / FdBlackScholesVanillaEngine / MCEuropeanEngine10.4681483852(解析)有解析解,适合当基准;注意 vega 单位是「每 100% 波动率」
美式看涨`ql.AmericanExercise(today, expiry)`BinomialVanillaEngine(crr/jr, 801) / FdBlackScholesVanillaEngine(200,400) / BaroneAdesiWhaley / BjerksundStensland10.47026038(CRR 801)只能用树、有限差分或近似式;配 AnalyticEuropeanEngine 会报 `RuntimeError: not an European option`
算术平均亚式`ql.DiscreteAveragingAsianOption(ql.Average.Arithmetic, 0.0, 0, fixingDates, …)`MCDiscreteArithmeticAPEngine / FdBlackScholesAsianEngine6.1646871(MC 20 万路径 seed=42)路径依赖,没有通用解析解;要额外给定平均价观察日
几何平均亚式同上,改用 `ql.Average.Geometric`AnalyticDiscreteGeometricAveragePriceAsianEngine5.945642有解析解,可用来给算术版做量级校验(几何平均 ≤ 算术平均)
敲出 / 敲入障碍`ql.BarrierOption(ql.Barrier.UpOut, level, rebate, payoff, exercise)`AnalyticBarrierEngineup-out@130 = 3.32633974;down-out@80 = 10.36788009本机实测敲入 + 敲出价格之和 = 对应欧式价格(见下文校验)
二元 / 篮子等其它结构官方 Instruments 模块中的对应类(本页未实测)官方文档列出的引擎(未实测)未实测本站没有运行结果,只给官方文档入口;不要按本页其它数字类推
一个真实报错:把美式期权交给 ql.AnalyticEuropeanEngine,本机直接抛 RuntimeError: not an European option。引擎不做近似降级,也不给警告——这类错误属于「合约与引擎不匹配」,改引擎即可,不需要换参数。
Baseline

第一步:把解析解和手写公式对上,差 1.78e-15

定价链路的起点是一个能对得上的基准。本页用同一组参数在 QuantLib 里建 Black-Scholes 过程、算出欧式看涨理论价,同时用 Python 标准库手写 Black-Scholes 公式,两条路应该得到同一个数。

baseline.py(QuantLib 1.43 / Python 3.11.9 实测通过)import math import QuantLib as ql today = ql.Date(15, 1, 2024) ql.Settings.instance().evaluationDate = today day_counter = ql.Actual365Fixed() # T = 1.00273973 expiry = ql.Date(15, 1, 2025) spot = ql.QuoteHandle(ql.SimpleQuote(100.0)) r_ts = ql.YieldTermStructureHandle(ql.FlatForward(today, 0.05, day_counter)) q_ts = ql.YieldTermStructureHandle(ql.FlatForward(today, 0.00, day_counter)) v_ts = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(today, ql.NullCalendar(), 0.20, day_counter)) process = ql.BlackScholesMertonProcess(spot, q_ts, r_ts, v_ts) # 注意参数顺序:分红在前、利率在后 option = ql.VanillaOption(ql.PlainVanillaPayoff(ql.Option.Call, 100.0), ql.EuropeanExercise(expiry)) option.setPricingEngine(ql.AnalyticEuropeanEngine(process)) print("QuantLib:", option.NPV()) # 10.4681483852 # 手写 Black-Scholes 公式做交叉验证 T = day_counter.yearFraction(today, expiry) d1 = (math.log(100.0 / 100.0) + (0.05 + 0.5 * 0.2 ** 2) * T) / (0.2 * math.sqrt(T)) d2 = d1 - 0.2 * math.sqrt(T) N = lambda x: 0.5 * (1 + math.erf(x / math.sqrt(2))) print("手写公式:", 100.0 * N(d1) - 100.0 * math.exp(-0.05 * T) * N(d2))
基准与自洽性检查(实测)
检查项本机实测值说明
AnalyticEuropeanEngine NPV10.4681483852黑-舒尔斯解析解,作为本页基准
手写 Black-Scholes 公式10.4681483852用 Python `math` 标准库实现,不依赖 QuantLib
两者绝对差1.7763568394e-15浮点尾差量级,可认为完全一致
T(年)1.00273973Actual/365 (Fixed) 口径;不是整数 1,会直接影响价格
隐含波动率反解(K=90/100/110)24.99998122% / 24.99999542% / 25.0%输入 25% 的波动率,用 `ql.blackFormulaImpliedStdDev` 反解回来,误差 ≤ 2e-5
为什么要反解隐含波动率:它同时验证了两件事——前向价格与折现因子的口径一致、以及定价公式本身没写错。反解需要正确的远期价(本机实测 S·e^{rT} = 105.1415116);远期给错就会抛 RuntimeError: negative Put price … No solution exists,这属于「输入口径不一致」,不是公式错了。
Greeks

Greeks 实测值与单位:vega 单位最容易搞错

Greeks 是价格对某个因子的偏导。QuantLib 直接给解析 Greeks,但单位口径需要自己确认:vega 与 rho 是「每 100% 变化」,不是每 1%。下表是本机实测值。

同一平值看涨期权(S=K=100, r=5%, vol=20%, T=1.00273973)的解析 Greeks,QuantLib 1.43 实测
Greeks本机实测值(原始单位)换算后的常用口径经济含义什么时候看
delta0.63701042279308470.6370(每 1 元标的价格)标的价格涨 1 元,期权价约涨 0.64 元对冲比例、方向敞口
gamma0.0187332247901540220.0187(每 1 元价格的 delta 变化)标的价格涨 1 元,delta 约涨 0.0187调仓频率、凸性风险
vega37.569097387377390.3756909738737739(每 1% 波动率)波动率涨 1 个百分点,期权价约涨 0.376 元波动率风险、波动率交易
theta-6.408289652737268-0.017556957952704844(每天,= theta/365)每过一天,期权价约降 0.0176 元持有成本、到期临近
rho53.3787374390447060.5337873743904471(每 1% 利率)利率涨 1 个百分点,看涨期权价约涨 0.534 元利率敏感型组合
单位口径提醒:QuantLib 返回的 vega() 是波动率变化 1.0(即 100 个百分点)对应的价格变化,所以「每 1% 的 vega」= 37.56909738737739 ÷ 100 = 0.3756909738737739。报风险数字前先把这个换算写清,否则会放大 100 倍。theta() 同理是「每年」,除以 365 才是每天。
Verify Greeks

Greeks 怎么自己验:用有限差分对一遍解析值

解析 Greeks 没法直接验,但你可以在 QuantLib 里对参数加一个小扰动、重新定价,用差分结果反推 Greeks。本页对 delta、gamma、vega、rho、theta 各做了一次差分(h=0.01 / 1bp / 1 天),与解析值对比如下。

解析 Greeks vs 有限差分 Greeks(同一期权,本机实测)
Greeks解析值差分值绝对差差分设置与说明
delta0.63701042279308470.63701041420696218.59e-09中心差分:标的价格 ±0.01
gamma0.0187332247901540220.0187332245005222832.90e-10二阶中心差分:(P(S+h) − 2P(S) + P(S−h)) / h²,h=0.01
vega(每 1%)0.37569097387377390.375690970817377233.06e-09波动率 ±1e-4 的中心差分后除以 100
rho(每 1%)0.53378737439044710.53378736662867967.76e-09利率 ±1e-4 的中心差分后除以 100
theta(每天)-0.017556957952704844-0.0175648129935765957.86e-06把估值日 +1 天后重新定价,取价格差;与 theta/365 的差最大
读法:delta / gamma / vega / rho 的差分误差都在 1e-9 ~ 1e-8 量级,说明解析实现与差分一致。theta 的差是 7.86e-06,比其他高三个数量级——原因是「过一天」不只是时间少一天,估值日改变还会同时移动曲线的参考点,属于差分实验的固有偏差,不是 QuantLib 算错了。写风险报告时,如果两种方法口径不同,要写明用的是哪一种。
Cross-check

同一个期权,四个引擎的价格差多少(含误差与耗时)

把同一个欧式看涨交给解析、树、有限差分、蒙特卡洛四类引擎,各自报价并计时。基准取解析解 10.46814839,下表把绝对差、相对误差(ppm)与耗时都列出来,方便你判断「差多少才算正常」。

四类引擎交叉验证(本机实测,基准 = AnalyticEuropeanEngine 10.46814839)
引擎与配置NPV与解析解绝对差相对误差(ppm)耗时(秒)读法与注意点
AnalyticEuropeanEngine10.468148390(基准)00.0002有解析解就用它;本机最快且精确
BinomialVanillaEngine(crr, 801)10.470260380.00211199201.750.0008CRR 树上偏;奇数步与偶数步的收敛路径不同
BinomialVanillaEngine(jr, 801)10.470168430.00202004192.970.0011JR 与 CRR 误差量级接近
BinomialVanillaEngine(lr, 801)10.468147835.6e-070.050.0006LR 用平滑收敛技巧,本机精度比 CRR 高约 3600 倍
FdBlackScholesVanillaEngine(200, 400)10.46854520.0003968137.910.0025有限差分精度取决于时间/空间网格,适合可加结构
MCEuropeanEngine(1e6 路径, seed=42)10.469079320.0009309388.930.2214蒙特卡洛不提供 delta/vega;误差约 1/√N
MCEuropeanEngine(低差异序列 65536 点)10.467006120.00114227109.120.0155低差异序列路径少但更快,误差量级相近
可操作阈值(本机实测口径):树引擎 801 步与解析解的差在 200 ppm 上下,有限差分 200×400 在 40 ppm 上下,蒙特卡洛 100 万路径在 90 ppm 上下。若你看到两个引擎差出 1% 以上,那不是引擎噪声,而是要查输入(波动率、日历、计息约定或行权方式)。收敛速度与步数/路径数的完整对照见定价引擎页。
Exotics

亚式与障碍期权实测:平均价怎么取、敲入敲出能不能对上

路径依赖期权没有通用的解析解,构造方式不同结果就会差。本页用「每月一个观察日、共 12 个观察日」构造算术平均亚式,并同时给出几何平均(有解析解)作量级校验;障碍期权则用敲入 + 敲出的互补关系做自检。

asian_barrier.py(观察日构造与定价,实测通过)dates = [ql.TARGET().advance(today, ql.Period(i, ql.Months)) for i in range(1, 13)] # 第 2 个参数是 runningAccumulator、第 3 个是 pastFixings,都不是「平均价」 opt = ql.DiscreteAveragingAsianOption(ql.Average.Arithmetic, 0.0, 0, dates, ql.PlainVanillaPayoff(ql.Option.Call, 100.0), ql.EuropeanExercise(expiry)) opt.setPricingEngine(ql.MCDiscreteArithmeticAPEngine(process, "PseudoRandom", requiredSamples=200000, seed=42)) print(opt.NPV()) # 6.1646871 barrier = ql.BarrierOption(ql.Barrier.DownOut, 80.0, 0.0, ql.PlainVanillaPayoff(ql.Option.Call, 100.0), ql.EuropeanExercise(expiry)) barrier.setPricingEngine(ql.AnalyticBarrierEngine(process)) print(barrier.NPV()) # 10.36788009
亚式与障碍期权实测(本机,2024-01-15 估值,12 个观察日为每月 15 日顺延后的营业日)
结构引擎本机实测 NPV对照与读法
欧式看涨(对照基准)AnalyticEuropeanEngine10.46814839所有奇异结构的锚点
算术平均亚式(12 个观察日)MCDiscreteArithmeticAPEngine(20 万路径, seed=42)6.1646871平均价压低波动,价格低于同参数的欧式看涨(10.4681)
算术平均亚式(同结构)FdBlackScholesAsianEngine(100, 100, 50)6.18993503与蒙特卡洛差 0.025(约 0.4%),说明两条数值路径彼此接近
几何平均亚式(同观察日)AnalyticDiscreteGeometricAveragePriceAsianEngine5.945642几何平均 ≤ 算术平均,方向正确;可作为算术版的数量级校验
向下敲出(障碍 80)AnalyticBarrierEngine10.36788009只在价格未触及 80 时有效,故低于欧式
向下敲入(障碍 80)AnalyticBarrierEngine0.10026829与同障碍的敲出价之和 = 10.46814838,等于欧式 10.46814839(差 1e-8)
向上敲出(障碍 130)AnalyticBarrierEngine3.32633974涨幅受限,权利金显著变便宜
向上敲入(障碍 130)AnalyticBarrierEngine7.14180864与向上敲出价之和同样等于欧式价格
两个可复用的自检:①敲入 + 敲出 = 欧式:本机实测 10.36788009 + 0.10026829 = 10.46814838,与欧式解析价 10.46814839 只差 1e-8,这是检查障碍设置(障碍类型、水平、观察方式)是否写对的快捷方法。②几何 ≤ 算术:同观察日下几何平均亚式 5.945642 低于算术平均 6.1646871,方向符合数学关系。另外注意接口本身的坑:DiscreteAveragingAsianOption 构造函数的第 2、3 个参数是 runningAccumulator 与 pastFixings,不是「平均价」;本机曾把 12 传进这两个位置,结果几何版直接抛「positive running product required」、算术版静默返回 0.0。
Early exercise

提前行权溢价实测:只有分红才会让美式看涨更值钱

美式期权多了「随时行权」的权利,但这份权利值多少钱取决于分红。本页把分红率从 0 调到 6%,比较美式(CRR 801)与欧式(解析)的价格差。

提前行权溢价实测(同一合约,仅改分红率与引擎)
分红率 q欧式解析价美式价(CRR 801)提前行权溢价读法与注意点
0%10.4681483910.470260380.002112无分红时提前行权不是好选择,溢价几乎只是树的截断误差
3%8.664811868.667371810.00255996分红开始压低看涨价值,但行权权仍不值钱
6%7.08349327.267730570.18423737分红足够高时提前行权开始划算,溢价跳到 0.18 量级
近似式引擎(q=0)10.4681483910.46814839(BAW / Bjerksund-Stensland)≈ 0无分红时近似式退化为欧式解,本机与解析解逐位一致
引擎选择后果———算美式必须用树 / 有限差分 / 近似式;用解析欧式引擎会直接报错
结论与边界:在无分红的 Black-Scholes 世界里,美式看涨 = 欧式看涨,所以本机测到的「溢价」实际是树引擎的离散误差(0.002112);把分红拉到 6% 才出现真正有价值的提前行权权(0.18423737)。这只是单一合约的受控实验,真实标的的分红是离散的现金流,应当用带分红日历的输入,具体口径以你的合约条款为准。
Triage

价格对不上先查什么:6 步定位顺序

把自己算的价和市场报价、或两个引擎的价对上,是日常最花时间的活。下面 6 步按「先排除低级错误、再怀疑数值方法」排序,每步都给出本页实测过的判据。

  • 1. 先确认合约与引擎是否匹配

    美式配欧式引擎会直接抛 RuntimeError: not an European option;树引擎与蒙特卡洛引擎**不提供 vega**(RuntimeError: vega not provided),要希腊字母就换解析或有限差分引擎。
  • 2. 再核对输入口径(最常错)

    估值日、到期日、计息约定三项必须先确认。本页基线 T = 1.00273973 而非 1,就是 Actual/365 (Fixed) 的结果;换成别的计息约定,同一个合约会算出不同的价格。
  • 3. 检查远期与折现是否自洽

    反解隐含波动率是最快的自检:输入 25% 应能反解回 25%(本机实测 24.999981% ~ 25.0%)。如果反解报错,几乎一定是远期价(S·e^{rT})或折现因子给错了。
  • 4. 看差异量级是否在引擎噪声内

    本机实测:树 801 步约 200 ppm、有限差分 200×400 约 40 ppm、蒙特卡洛 100 万路径约 90 ppm。差异在几十到两百 ppm 属正常;超过 1% 就继续往下查。
  • 5. 用第二种方法复算

    解析解对手写公式(差 1.78e-15)、解析 Greeks 对有限差分 Greeks(差 1e-9 ~ 1e-8)、障碍期权用「敲入 + 敲出 = 欧式」自检(实测差 1e-8)。三种复算都比「再跑一遍同一个引擎」有价值。
  • 6. 最后才怀疑模型假设

    如果前 5 步都对不上,问题通常在波动率曲面、分红或利率曲线的构造上——那属于输入数据与期限结构的范畴,先看期限结构页的曲线自洽性检查。
  • FAQ

    常见问题

    为什么我用公式手算的结果和 QuantLib 差一点点?

    先看差多少。本站实测:解析引擎对手写 Black-Scholes 公式的差是 1.7763568394e-15,属于浮点尾差;如果你看到的是 0.01 量级的差,通常是计息约定或时间口径不同(本页基线 T = 1.00273973,不是整数 1)。把估值日、到期日、计息约定三项对齐后再比较,具体以实现与官方文档为准。

    Greeks 的 vega 单位到底是什么?

    QuantLib 的 vega() 返回「波动率变化 1.0(100 个百分点)」对应的价格变化,本机实测 37.56909738737739;业内常报的「每 1%」要除以 100,即 0.3756909738737739。rho() 同理(53.378737439044706 → 每 1% 为 0.5337873743904471),theta() 是每年(-6.408289652737268 → 每天约 -0.017557)。报数字前先写清单位,否则会放大 100 倍。以官方文档为准。

    亚式期权为什么没有唯一答案?

    因为「平均价」的取法本身要约定:观察日清单、算术还是几何平均、固定还是浮动行权价。本站实测同一组观察日下,算术平均(MC 20 万路径)6.1646871、有限差分 6.18993503、几何平均解析 5.945642。也就是说,不写清构造方式的「亚式期权价格」是不完整的。以合约条款与官方文档为准。

    蒙特卡洛和树引擎的结果差多少算正常?

    本机实测(同一平值看涨):解析基准 10.46814839,CRR 801 步 10.47026038(201.75 ppm),蒙特卡洛 100 万路径 10.46907932(88.93 ppm),两者相差约 0.0001。在几十到两百 ppm 之内属数值方法噪声;差到 1% 以上就要查输入而不是调步数。以官方文档与你的容差要求为准。

    为什么 QuantLib 不给我 vega?

    引擎能力不同:本机实测 BinomialVanillaEngine 取 vega() 直接抛 RuntimeError: vega not provided,蒙特卡洛引擎连 delta 都不给。需要希腊字母时用解析引擎(欧式)或有限差分引擎,或者自己对波动率加扰动、重新定价做差分(方法见本页第 4 个板块)。以官方文档中各引擎的能力说明为准。

    美式看涨一定比欧式贵吗?

    不一定。本站实测:无分红时美式(CRR 801)10.47026038 对欧式 10.46814839,差 0.002112 基本是树的离散误差;分红 6% 时差才扩大到 0.18423737。在无分红的假设下,提前行权不划算,两者理论上相等。真实标的有分红日历,应按合约条款与市场惯例处理。

    用 QuantLib 算期权需要在 EasyClaw 里配置什么吗?

    不需要,也没有可配置项:QuantLib 是运行在本机的 Python / C++ 库,与 EasyClaw 无已证实集成。另一条路线是本机量化研究类技能(如 quant-analyst 侧重回测与风险指标方法论、akshare-finance 侧重取行情数据),两者各自独立、任务不同;那边给出的结果同样仅供研究,不构成投资建议。

    本页的价格能直接拿去做交易决策吗?

    不能。本页全部是受控实验:固定波动率 20%、固定利率 5%、虚构标的,目的是让引擎差异可比较、可复现。真实定价需要市场隐含波动率曲面、真实利率与分红、以及你所在市场的合约条款;结果仅供研究与学习参考,不构成投资建议。

    本页结论同一平值看涨期权:解析解 10.4681483852(与手写公式差 1.78e-15),树引擎 801 步约 200 ppm、有限差分约 40 ppm、蒙特卡洛 100 万路径约 90 ppm;Greeks 解析值与差分校验差 1e-9 ~ 1e-8 量级。价格能算出来不等于算对了,误差量级与 Greeks 口径才是可验证的部分。
    适用边界全部数值来自单组合约的受控实验(S=K=100、r=5%、q=0、vol=20%、Actual/365 Fixed、估值日 2024-01-15,环境 QuantLib 1.43 / Python 3.11.9);换参数、换版本或换市场,绝对值会不同。二元期权等结构本站未实测,已如实标注。
    风险提示期权定价依赖波动率、利率、分红等假设,结果不构成投资建议;蒙特卡洛价格每次运行会因随机数不同而变化,复现时请固定 seed 并记录路径数与引擎配置。

    看完期权,接下来该看哪一页?

    想知道「为什么换引擎结果会不一样、步数该取多少」,去定价引擎页看收敛与方差削减的实测数据;如果价格对不上是出在日期与计息口径上,直接去金融约定审计。