QuantLib · Pricing engines

QuantLib 定价引擎怎么选?四类引擎交叉验证实测

同一个期权、同一组参数,解析引擎给 10.46814839、CRR 二叉树 801 步给 10.47026038、Leisen-Reimer 801 步给 10.46814783、有限差分给 10.4685452、蒙特卡洛 100 万路径给 10.46907932。本页把五条路径摆在一起,给出绝对误差、相对误差(ppm)与耗时,并回答一个更实用的问题:两个引擎差多少才算正常。

交叉验证:5 种引擎最小实测误差:0.05 ppm(LR 801 步)依据:本机实测 + 官方文档
解析解AnalyticEuropeanEngine
树 / 网格Binomial · FiniteDifference
路径模拟MonteCarlo · LowDiscrepancy
对照判据误差 ppm + 标准误

四类引擎各有一条独立的计算路径:解析解按闭式公式、树与网格按离散化、蒙特卡洛按路径抽样。交叉验证就是用不同路径互相印证同一个价格。示意图非实盘界面。

测试版本QuantLib 1.43(PyPI wheel `quantlib-1.43-cp39-abi3-win_amd64`)Python 版本3.11.9(Windows,Python 官方 CPython)最后核验2026-10-09(全部数值为本机一次运行输出)基准标的欧式看涨 S=K=100、r=5%、q=0、vol=20%期限与计息2024-01-15 → 2025-01-15,T=1.00273973(Actual/365 Fixed)解析基准AnalyticEuropeanEngine NPV = 10.46814839计时口径time.perf_counter() 单次调用,非多轮平均,仅用于量级比较本站实测范围单标的欧式期权;路径依赖期权的收敛性本站未做结论
交叉验证解析 / 树 / 有限差分 / 蒙特卡洛 / 低差异
最小实测误差0.05 ppm(LR 801 步)
最大实测误差201.75 ppm(CRR 801 步)
证据等级本站实测(代码与输出均在本页)
Engine types

四类定价引擎分别是什么、各自付出什么代价

QuantLib 的引擎不是「快慢之别」,而是「能算什么」之别:解析解只覆盖有闭式解的品种,树与网格适合可提前行权的品种,蒙特卡洛适合路径依赖品种但给不出可靠的 Greeks。下表按这个逻辑排列。

本机 QuantLib 1.43 实测可用的引擎族;具体可用类以官方文档为准
引擎族代表类(本机 1.43 可实例化)擅长什么主要代价适用场景注意点
解析解AnalyticEuropeanEngine闭式公式,误差为浮点尾差(实测 1.8e-15 量级)只覆盖少数有闭式解的品种,模型假设固定欧式期权、需要 Greeks 与快速重估的场合美式或非欧式品种配它会直接报错
树 / 网格(二叉树)BinomialVanillaEngine(process, "crr", steps)可处理提前行权;实现直观、可解释误差随步数下降慢;800 步实测仍有约 200 ppm美式期权、需要观察提前行权价值的场合不同树型(crr/jr/lr)收敛速度差别很大
有限差分FdBlackScholesVanillaEngine(process, 200, 400)网格法,适合有边界条件与提前行权的问题网格密度要自己调;对多维问题成本上升美式期权、障碍类问题的稳定备选实测 200x400 网格误差约 38 ppm,可作校验基准
蒙特卡洛MCEuropeanEngine(process, "PseudoRandom", requiredSamples=…, seed=…)通用性更强,能处理路径依赖与高维问题单次结果带随机误差;耗时随路径数线性增长亚式、篮子、路径依赖类结构不提供 delta / vega,敏感度要另想办法
低差异序列MCEuropeanEngine(process, "LowDiscrepancy", requiredSamples=65536)同样路径数下抽样更均匀,短时间可跑出可用精度序列生成有额外开销;高维效果会变差快速估值或需要可重复的快速校验本机实测 65536 点即达约 109 ppm
近似解析式BaroneAdesiWhaleyApproximationEngine / BjerksundStenslandApproximationEngine美式期权的近似闭式解,速度接近解析解是近似,不是精确值;对分红与边界假设敏感美式期权需要批量快速估值时本组参数下它们与欧式解析解给出同一数值(无分红溢价)
选引擎的正确顺序:先问「这个品种有没有闭式解」→ 有就用 QuantLib 的解析引擎;没有且可提前行权 → 用树或有限差分;路径依赖或高维 → 用蒙特卡洛。本页的 5 条路径都是在这只欧式看涨上跑的,因此可以互相校验;换成路径依赖品种,解析解这条路径直接不存在,交叉验证就只能靠网格与模拟互相印证。
Selection

什么场景该用哪类引擎:场景 → 选项 → 理由

下面这张表是本页的选型清单,每行的「理由」都对应上表或下文实测中可核对的量级。

选型清单:每行的理由都对应本页实测数据,可直接复核
你的场景建议优先选备选理由(可核对)
欧式期权,要价格 + 全部 Greeks解析引擎有限差分引擎解析引擎给出 delta/gamma/vega/theta/rho 全部解析值;树与 MC 引擎不提供 vega
欧式期权,只要价格、要快解析引擎LR 801 步树实测解析 0.0002s、LR 801 步 0.0006s,都比 MC(100 万路径 0.22s)快两个量级
美式期权(可提前行权)树或有限差分BAW / BS 近似式解析引擎不支持美式,配它会抛 not an European option;近似式只适合对精度要求不苛刻的批量估值
亚式 / 篮子 / 路径依赖蒙特卡洛有限差分(仅部分结构)解析解不存在;实测 12 个观察日的算术亚式用 MC 20 万路径得 6.1646871、FD 得 6.18993503
障碍期权(连续监测)解析障碍引擎蒙特卡洛(离散监测)实测 up-out 130 得 3.32633974、down-in 80 得 0.10026829;离散监测要改用 MC 并说明监测频率
需要一份可重复的校验基准解析解 + 有限差分LR 801 步树实测有限差分 38 ppm、LR 0.05 ppm;CRR 801 步 202 ppm,作为基准偏粗
要给曲线的敏感度做压力测试解析引擎(对每条曲线分别重估)树(步数要固定)重估次数多、单次要快;树与 MC 的离散化误差会混进敏感度里
模型本身没有闭式解(如 Heston)模型对应的解析引擎蒙特卡洛实测 Heston 用 AnalyticHestonEngine 可定价,但它不提供 delta(报 delta not provided)
Cross-validation

五条路径交叉验证:同一个期权,价格差多少

同一只欧式看涨(S=K=100、r=5%、q=0、vol=20%、2024-01-15 → 2025-01-15、Actual/365 Fixed),用五种引擎分别定价,全部与解析解对照。误差同时给出绝对值与 ppm(百万分之一),后者方便跨价位比较。

实测:解析解作为基准(本组参数下它与手写 Black-Scholes 公式的差为 1.78e-15)
引擎(本机参数)NPVdelta与解析解绝对差相对误差 (ppm)耗时 (s)
解析 AnalyticEuropeanEngine10.468148390.637010420.0(基准)0.000.0002
CRR 二叉树 801 步10.470260380.636932240.00211199201.750.0008
JR 二叉树 801 步10.470168430.636984330.00202004192.970.0011
LR 二叉树 801 步10.468147830.636951820.000000560.050.0006
有限差分 200×40010.46854520.637037860.0003968137.910.0025
蒙特卡洛 100 万路径(seed=42)10.46907932不提供0.0009309388.930.2214
蒙特卡洛低差异 65536 点(seed=42)10.46700612不提供0.00114227109.120.0155
把 ppm 换算成「每 100 元价格差多少钱」,判断可接受度更直观
读法数值量级含义
解析解与手写公式1.8e-15浮点尾差,可认为两者完全一致
LR 二叉树的误差0.05 ppm1 ppm 相当于每 100 元价格差 0.0001 元;0.05 ppm 已接近数值精度
有限差分的误差37.91 ppm每 100 元约差 0.0004 元,作校验基准够用
CRR / JR 二叉树的误差约 193~202 ppm每 100 元约差 0.002 元,作为基准偏粗
蒙特卡洛(100 万路径)88.93 ppm单次结果,误差带随机性(见下文标准误)
delta 的离散化差异0.63693 ~ 0.63704与解析 0.63701042 的差在第 4~5 位小数,属离散化误差
Convergence

收敛性实测:为什么多加步数、多加路径不一定立刻变准

同一只期权,把二叉树的步数从 10 加到 2000、把蒙特卡洛的路径数从 1 万加到 100 万,观察误差怎么走。基准仍是解析解 10.46814839。

实测:二叉树步数 10 → 2000、蒙特卡洛路径 1 万 → 100 万(时间步长取 1,欧式期权只需到期分布)
方法参数NPV绝对误差相对误差 (ppm)耗时 (s)
CRR 二叉树10 步10.264456060.2036923219458.300.0001
CRR 二叉树50 步10.426944080.041204313936.160.0001
CRR 二叉树100 步10.447519790.02062861970.610.0001
CRR 二叉树200 步10.457827590.01032079985.920.0001
CRR 二叉树500 步10.464018520.00412986394.520.0003
CRR 二叉树801 步10.470260380.002112201.750.0005
CRR 二叉树1000 步10.46608320.00206519197.280.0007
CRR 二叉树2000 步10.467115730.0010326698.650.0021
蒙特卡洛1 万路径(seed=42)10.364425170.103723219909.520.0023
蒙特卡洛10 万路径(seed=42)10.437618530.030529852916.440.0233
蒙特卡洛100 万路径(seed=42)10.469079320.0009309488.930.2291
三个必须看懂的细节(均来自本机 QuantLib 1.43 实测):①二叉树误差不是单调下降:801 步 201.75 ppm 反而比 1000 步 197.28 ppm 略差,因为树在到期附近存在奇偶振荡,奇数步与偶数步走在两条不同的收敛路径上,想更准应当成倍加步数(500 → 1000 → 2000)而不是微调。②蒙特卡洛两段的下降倍数差别很大(1 万→10 万降 3.4 倍,10 万→100 万降 32.8 倍),这不是「收敛变快了」,而是单次抽样结果本身带随机性:按 20 万路径的标准误估计 0.03289931 推算,100 万路径的标准误约 0.0147,而这次实测误差 0.00093094 明显小于一个标准误——属于抽到的运气。③判断蒙特卡洛精度要看引擎给出的标准误估计,不能拿一次差值就下结论。
Reproducibility

复现性与方差削减:固定种子、换种子、加对偶变量

蒙特卡洛的随机性可以管理:固定种子能复现,换种子会偏移,对偶变量能压低标准误。下表是 20 万路径下的实测对照,标准误来自引擎的 errorEstimate()。

实测:20 万路径、timeSteps=1;种子与 antitheticVariate 由引擎构造参数控制
配置NPV(20 万路径)标准误估计耗时 (s)结论
seed=42(第 1 次)10.460205340.032899310.047基准跑
seed=42(第 2 次)10.460205340.032899310.045同种子完全复现,逐位相同
seed=7(第 1 次)10.489159940.033021560.045换种子后结果偏移约 0.029
seed=7(第 2 次)10.489159940.033021560.046同种子同样逐位复现
seed=42 + 对偶变量10.452086060.016429580.080路径数不变,标准误约减半,耗时约翻倍
低差异序列 65536 点10.46700612不提供0.015点数少得多,误差反而更小(约 109 ppm)
怎么用这张表:①要出报告或对账,必须固定 seed,否则同一个脚本两次运行会给出不同价格(实测 seed=42 与 seed=7 相差约 0.029,与标准误 0.0329 同量级);②不同 seed 之间的差异大小,本身就是一个免费的误差检验——差异远大于标准误,说明路径数还不够;③对偶变量让标准误从 0.0329 降到 0.0164(约一半),代价是耗时约翻倍,在时间预算宽松时是比「直接加路径」更划算的选择;④低差异序列在本例用 65536 点就达到约 109 ppm,比 100 万路径的伪随机还省 15 倍时间,但它不给标准误,无法自我评估误差。
Capability boundary

引擎能力边界:哪些量它给不出来(附报错原文)

引擎除了价格,能给出的敏感度并不一致。这一节全部是本机实测的「能 / 不能」以及抛出的原始报错——写生产脚本前值得先看一遍,避免在循环里才发现取不到 vega。

实测:本机 QuantLib 1.43,逐引擎调用对应方法
引擎价格deltagammavega取不到时的真实报错
AnalyticEuropeanEngine给给给给—(本组参数下 vega=37.56909738737739,即每 100% 波动率)
BinomialVanillaEngine给给不给不给RuntimeError: vega not provided
FdBlackScholesVanillaEngine给给不给不给与树引擎同属 Vanilla 引擎族,敏感度支持有限
MCEuropeanEngine(伪随机)给不给不给不给模拟引擎按路径估值,逐路径敏感度无定义
MCEuropeanEngine(低差异)给不给不给不给同上;且不提供 errorEstimate
AnalyticHestonEngine给不给不给不给RuntimeError: delta not provided
替代方案:引擎不给的量,用受控重估做差分;关键是每次只动一个变量
需要的量本页实测可行的替代做法代价与注意点
树 / 网格引擎的 vega用波动率 ±1bp 分别重估,做中心差分:(P(σ+h) − P(σ−h)) / (2h)每次重估都要跑一遍树;实测解析引擎的 vega 与差分值一致到 3.1e-09
美式期权的 vega同上,用有限差分网格或树做两次重估网格密度必须固定,否则差分里会混进离散化差异
蒙特卡洛的 delta改用一条能提供 delta 的路径(解析/网格),或做共同随机数的差分直接对 MC 做差分需要固定相同的随机数,否则噪声会盖住敏感度
Heston 的 Greeks对参数逐个做中心差分(v0 / kappa / theta / sigma / rho)本页实测参数敏感性可稳定得到(如 v0 +0.01 使平值价格从 4.94091535 升到 5.41508783)
跨引擎的一致性判据以解析解为基准:LR 0.05 ppm、FD 37.91 ppm、CRR 201.75 ppm差异明显超过这些量级,说明参数或约定(而非引擎)出错了
Tolerance

两个引擎差多少才算正常:一张可操作的判据表

判断「结果不一致是否正常」需要量级参考。下表把本页实测的相对误差换算成阈值,并写明超过阈值时该先查什么——顺序是:先查约定,再查参数,最后才怀疑引擎。

阈值来自本页实测的相对误差,按量级归组
实测相对偏差对应本页的引擎组合正常 / 异常超过时应先查
0 ~ 0.1 ppm解析 vs 手写公式(1.8e-15 绝对差)正常,属浮点精度不用查;两者视为同一结果
0.05 ~ 40 ppm解析 vs LR 801 步(0.05) / 有限差分(37.91)正常,属离散化误差确认网格 / 步数设置是否与本页一致
约 90 ~ 110 ppm解析 vs MC 100 万路径(88.93) / 低差异 65536 点(109.12)正常范围内,但要看标准误用 errorEstimate 确认误差是否在标准误之内
约 190 ~ 210 ppm解析 vs CRR 801 / JR 801 步正常,属粗网格误差若要更准,成倍加步数(1000 / 2000 步)或换 LR 树型
1 万 ~ 2 万 ppm(1% ~ 2%)显式差分步长 h 过大、或树步数小于 50 步偏大,通常是设置问题先查步数 / 网格密度;50 步实测误差 3936 ppm 可作下限参考
超过 1%(10000 ppm)且换了引擎仍如此不在本页实测范围内的差异异常按「约定 → 参数 → 曲线 → 日历」顺序查(见金融约定审计页),不要先怀疑引擎实现
一句话判据:同一只欧式期权,QuantLib 的解析解、有限差分与 LR 树三者之间的差异应当在 0.1 ~ 40 ppm 量级;一旦看到 1% 以上的差异,问题几乎不在引擎选择上。
Reproduce

自己复现本页全部数字:一段可运行脚本 + 六个可替换开关

下面脚本就是本站生成本页数值的写法(去掉打印格式,本机 QuantLib 1.43 实跑通过)。把它存成 pricing_engines_check.py、在装好 QuantLib 的环境里运行,应当复现同一批量级;把「替换开关」逐项改掉,就能看出每个参数的影响。

pricing_engines_check.py(QuantLib 1.43 / Python 3.11.9 本机实测通过)import time import QuantLib as ql today, expiry = ql.Date(15, 1, 2024), ql.Date(15, 1, 2025) ql.Settings.instance().evaluationDate = today dc = ql.Actual365Fixed() process = ql.BlackScholesMertonProcess( ql.QuoteHandle(ql.SimpleQuote(100.0)), ql.YieldTermStructureHandle(ql.FlatForward(today, 0.0, dc)), # 分红率 q ql.YieldTermStructureHandle(ql.FlatForward(today, 0.05, dc)), # 无风险利率 r ql.BlackVolTermStructureHandle(ql.BlackConstantVol(today, ql.NullCalendar(), 0.20, dc))) def price(engine): option = ql.VanillaOption(ql.PlainVanillaPayoff(ql.Option.Call, 100.0), ql.EuropeanExercise(expiry)) option.setPricingEngine(engine) t0 = time.perf_counter() npv = option.NPV() return round(npv, 8), round(time.perf_counter() - t0, 4) for label, engine in [ ("analytic", ql.AnalyticEuropeanEngine(process)), ("crr-801", ql.BinomialVanillaEngine(process, "crr", 801)), ("lr-801", ql.BinomialVanillaEngine(process, "lr", 801)), ("fd-200x400", ql.FdBlackScholesVanillaEngine(process, 200, 400)), ("mc-1e6-seed42", ql.MCEuropeanEngine(process, "PseudoRandom", timeSteps=1, requiredSamples=1000000, seed=42))]: npv, secs = price(engine) print("%-14s %.8f %.4fs" % (label, npv, secs)) # 蒙特卡洛的标准误必须单独取:它才是误差判据 mc = ql.MCEuropeanEngine(process, "PseudoRandom", timeSteps=1, requiredSamples=200000, seed=42, antitheticVariate=True) opt = ql.VanillaOption(ql.PlainVanillaPayoff(ql.Option.Call, 100.0), ql.EuropeanExercise(expiry)) opt.setPricingEngine(mc) print("mc-200k-antithetic %.8f standard error %.8f" % (opt.NPV(), opt.errorEstimate()))
复现时的六个开关(本页表格全部来自这些开关的不同组合)
替换开关改哪里本页实测的后果
树型把 "crr" 换成 "jr" / "lr"801 步下误差从 201.75 ppm 变为 192.97 / 0.05 ppm,树型选择比加步数更有效
树的步数把 801 改成 10 / 100 / 1000 / 200010 步误差 19458 ppm,2000 步 98.65 ppm;且误差随奇偶步数振荡,建议成倍加
网格密度把 (200, 400) 改成更粗或更细的网格实测 200×400 为 37.91 ppm,可作基准;网格过粗会掩盖真实差异
路径数与种子改 requiredSamples 与 seed20 万路径下换种子结果偏移约 0.029,与标准误 0.0329 同量级
方差削减加 antitheticVariate=True标准误从 0.0329 降到 0.0164,耗时约翻倍
抽样序列把 "PseudoRandom" 换成 "LowDiscrepancy"65536 点即达约 109 ppm,但不提供标准误估计
FAQ

常见问题

为什么 801 步和 1000 步的误差没有变好?

因为二叉树的离散化误差带奇偶振荡:801 步与 1000 步走在两条不同的收敛路径上。本机实测 801 步 201.75 ppm、1000 步 197.28 ppm(几乎持平),而 2000 步降到 98.65 ppm。所以「加步数」的正确做法是成倍加(500 → 1000 → 2000),不是每次加几十步;想一步到位可以换 Leisen-Reimer 树型(本组 801 步仅 0.05 ppm)。以官方文档对树型实现的说明为准。

蒙特卡洛每次结果都不一样,怎么办?

固定 seed,并且不要用「两次运行的差值」判断精度,要看引擎的 errorEstimate()。本机实测:seed=42 两次运行逐位相同(10.46020534);换 seed=7 得 10.48915994,偏移约 0.029,与 20 万路径的标准误估计 0.0329 同量级。如果要压标准误,加对偶变量比加路径更省(实测标准误减半,耗时约翻倍)。具体接口以官方文档为准。

能不能只用解析引擎,其他都不用?

只对「有闭式解的品种」成立。解析引擎覆盖不了美式与大多数路径依赖结构:把美式期权配给 AnalyticEuropeanEngine 会直接抛 RuntimeError: not an European option(本机实测原文)。更实用的做法是:解析解做基准,树 / 有限差分做结构性校验,蒙特卡洛覆盖路径依赖——三者互为验证。以官方文档为准。

两个引擎结果差 0.002,算正常吗?

按本页实测换算:0.002 相对于 10.468 的价格约 190 ppm,正好落在 CRR 801 步(201.75 ppm)与 JR 801 步(192.97 ppm)的量级上,属于粗网格误差范围,正常。判断时建议统一换算成 ppm 或「每 100 元价格差多少钱」:0.002 / 10.468 ≈ 0.019%,即每 100 元差约 0.019 元。超过 1% 的差异就不该归因于引擎,先查约定与参数。以官方实现为准。

耗时数据能直接用来做性能预算吗?

不能。本页耗时是单次调用的 time.perf_counter() 结果,没有做多轮平均、也没控制机器负载(本机为共享环境),只适合比较量级:解析 0.0002s、LR 801 步 0.0006s、MC 100 万路径 0.2214s,相差三个量级这个结论是稳的,但具体毫秒数不要照搬。做性能预算请在目标机器上、用你自己标的与参数重测,以官方文档与实测为准。

为什么这个例子只能用欧式期权做交叉验证?

因为只有欧式期权同时具备「解析解 + 树 + 网格 + 模拟」四条独立路径,四条路径的结果可以直接互相对照。亚式与障碍类结构没有统一的闭式解,本页实测 12 个观察日的算术亚式 MC 20 万路径给 6.1646871、有限差分给 6.18993503,两者差异更多反映平均价定义与监测方式的不同,而不是单纯的数值误差。路径依赖品种的交叉验证要先把条款(观察日、平均方式、监测频率)对齐,再谈精度。以官方文档为准。

这些数字在别的 QuantLib 版本上还成立吗?

结论(哪类引擎适合什么、敏感度哪些给不出来、误差量级关系)通常稳定,但具体数值可能随版本与平台变化:本页数字来自 QuantLib 1.43 / Python 3.11.9 / Windows。跨版本复现时请以你自己环境跑出的结果为准,并注意不同版本对引擎默认参数的调整,以官方 release notes 与文档为准。

引擎选对了,价格还是和对手方不一样,接下来查什么?

先查约定而不是引擎:估值日、结算天数、日历、计息约定、折现曲线的口径。本页实测中,换应计计息约定就能让债券净价移动 0.24 元,比任何引擎差异都大。约定逐项对照的清单见金融约定审计一页;本页只负责证明「引擎之间的差异通常在 ppm 量级」。结果仅供研究与对账方法参考,不构成投资建议。

本页结论四类引擎的差异是可量化的:解析解与 LR 801 步差 0.05 ppm、有限差分 37.91 ppm、CRR 801 步 201.75 ppm、蒙特卡洛 100 万路径 88.93 ppm;因此同一只欧式期权出现 1% 以上的差异,问题基本不在引擎选择上,应先查约定与参数。
适用边界全部数值来自单只欧式看涨(S=K=100、r=5%、q=0、vol=20%、2024-01-15 → 2025-01-15),环境为 QuantLib 1.43 / Python 3.11.9 / Windows;耗时是单次调用且未控制机器负载,只可作量级参考。路径依赖结构的收敛性本页未下结论。
风险提示引擎之间的差异属数值误差讨论,不代表任何标的的估值结论;结果仅供研究,不构成投资建议。跨版本、跨平台的数字请以你自己环境实测为准。

引擎选好之后,下一步看什么?

要把引擎接到具体的期权、债券与曲线上去,继续看期权定价与期限结构;如果结果是 0、报错或不收敛,直接进错误与排查。