QuantLib · Pricing engines
QuantLib 定价引擎怎么选?四类引擎交叉验证实测
同一个期权、同一组参数,解析引擎给 10.46814839、CRR 二叉树 801 步给 10.47026038、Leisen-Reimer 801 步给 10.46814783、有限差分给 10.4685452、蒙特卡洛 100 万路径给 10.46907932。本页把五条路径摆在一起,给出绝对误差、相对误差(ppm)与耗时,并回答一个更实用的问题:两个引擎差多少才算正常。
四类引擎各有一条独立的计算路径:解析解按闭式公式、树与网格按离散化、蒙特卡洛按路径抽样。交叉验证就是用不同路径互相印证同一个价格。示意图非实盘界面。
四类定价引擎分别是什么、各自付出什么代价
QuantLib 的引擎不是「快慢之别」,而是「能算什么」之别:解析解只覆盖有闭式解的品种,树与网格适合可提前行权的品种,蒙特卡洛适合路径依赖品种但给不出可靠的 Greeks。下表按这个逻辑排列。
| 引擎族 | 代表类(本机 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 | 美式期权的近似闭式解,速度接近解析解 | 是近似,不是精确值;对分红与边界假设敏感 | 美式期权需要批量快速估值时 | 本组参数下它们与欧式解析解给出同一数值(无分红溢价) |
什么场景该用哪类引擎:场景 → 选项 → 理由
下面这张表是本页的选型清单,每行的「理由」都对应上表或下文实测中可核对的量级。
| 你的场景 | 建议优先选 | 备选 | 理由(可核对) |
|---|---|---|---|
| 欧式期权,要价格 + 全部 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) |
五条路径交叉验证:同一个期权,价格差多少
同一只欧式看涨(S=K=100、r=5%、q=0、vol=20%、2024-01-15 → 2025-01-15、Actual/365 Fixed),用五种引擎分别定价,全部与解析解对照。误差同时给出绝对值与 ppm(百万分之一),后者方便跨价位比较。
| 引擎(本机参数) | NPV | delta | 与解析解绝对差 | 相对误差 (ppm) | 耗时 (s) |
|---|---|---|---|---|---|
| 解析 AnalyticEuropeanEngine | 10.46814839 | 0.63701042 | 0.0(基准) | 0.00 | 0.0002 |
| CRR 二叉树 801 步 | 10.47026038 | 0.63693224 | 0.00211199 | 201.75 | 0.0008 |
| JR 二叉树 801 步 | 10.47016843 | 0.63698433 | 0.00202004 | 192.97 | 0.0011 |
| LR 二叉树 801 步 | 10.46814783 | 0.63695182 | 0.00000056 | 0.05 | 0.0006 |
| 有限差分 200×400 | 10.4685452 | 0.63703786 | 0.00039681 | 37.91 | 0.0025 |
| 蒙特卡洛 100 万路径(seed=42) | 10.46907932 | 不提供 | 0.00093093 | 88.93 | 0.2214 |
| 蒙特卡洛低差异 65536 点(seed=42) | 10.46700612 | 不提供 | 0.00114227 | 109.12 | 0.0155 |
| 读法 | 数值量级 | 含义 |
|---|---|---|
| 解析解与手写公式 | 1.8e-15 | 浮点尾差,可认为两者完全一致 |
| LR 二叉树的误差 | 0.05 ppm | 1 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 位小数,属离散化误差 |
收敛性实测:为什么多加步数、多加路径不一定立刻变准
同一只期权,把二叉树的步数从 10 加到 2000、把蒙特卡洛的路径数从 1 万加到 100 万,观察误差怎么走。基准仍是解析解 10.46814839。
| 方法 | 参数 | NPV | 绝对误差 | 相对误差 (ppm) | 耗时 (s) |
|---|---|---|---|---|---|
| CRR 二叉树 | 10 步 | 10.26445606 | 0.20369232 | 19458.30 | 0.0001 |
| CRR 二叉树 | 50 步 | 10.42694408 | 0.04120431 | 3936.16 | 0.0001 |
| CRR 二叉树 | 100 步 | 10.44751979 | 0.0206286 | 1970.61 | 0.0001 |
| CRR 二叉树 | 200 步 | 10.45782759 | 0.01032079 | 985.92 | 0.0001 |
| CRR 二叉树 | 500 步 | 10.46401852 | 0.00412986 | 394.52 | 0.0003 |
| CRR 二叉树 | 801 步 | 10.47026038 | 0.002112 | 201.75 | 0.0005 |
| CRR 二叉树 | 1000 步 | 10.4660832 | 0.00206519 | 197.28 | 0.0007 |
| CRR 二叉树 | 2000 步 | 10.46711573 | 0.00103266 | 98.65 | 0.0021 |
| 蒙特卡洛 | 1 万路径(seed=42) | 10.36442517 | 0.10372321 | 9909.52 | 0.0023 |
| 蒙特卡洛 | 10 万路径(seed=42) | 10.43761853 | 0.03052985 | 2916.44 | 0.0233 |
| 蒙特卡洛 | 100 万路径(seed=42) | 10.46907932 | 0.00093094 | 88.93 | 0.2291 |
复现性与方差削减:固定种子、换种子、加对偶变量
蒙特卡洛的随机性可以管理:固定种子能复现,换种子会偏移,对偶变量能压低标准误。下表是 20 万路径下的实测对照,标准误来自引擎的 errorEstimate()。
| 配置 | NPV(20 万路径) | 标准误估计 | 耗时 (s) | 结论 |
|---|---|---|---|---|
| seed=42(第 1 次) | 10.46020534 | 0.03289931 | 0.047 | 基准跑 |
| seed=42(第 2 次) | 10.46020534 | 0.03289931 | 0.045 | 同种子完全复现,逐位相同 |
| seed=7(第 1 次) | 10.48915994 | 0.03302156 | 0.045 | 换种子后结果偏移约 0.029 |
| seed=7(第 2 次) | 10.48915994 | 0.03302156 | 0.046 | 同种子同样逐位复现 |
| seed=42 + 对偶变量 | 10.45208606 | 0.01642958 | 0.080 | 路径数不变,标准误约减半,耗时约翻倍 |
| 低差异序列 65536 点 | 10.46700612 | 不提供 | 0.015 | 点数少得多,误差反而更小(约 109 ppm) |
引擎能力边界:哪些量它给不出来(附报错原文)
引擎除了价格,能给出的敏感度并不一致。这一节全部是本机实测的「能 / 不能」以及抛出的原始报错——写生产脚本前值得先看一遍,避免在循环里才发现取不到 vega。
| 引擎 | 价格 | delta | gamma | vega | 取不到时的真实报错 |
|---|---|---|---|---|---|
| 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 | 差异明显超过这些量级,说明参数或约定(而非引擎)出错了 |
两个引擎差多少才算正常:一张可操作的判据表
判断「结果不一致是否正常」需要量级参考。下表把本页实测的相对误差换算成阈值,并写明超过阈值时该先查什么——顺序是:先查约定,再查参数,最后才怀疑引擎。
| 实测相对偏差 | 对应本页的引擎组合 | 正常 / 异常 | 超过时应先查 |
|---|---|---|---|
| 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 1.43 实跑通过)。把它存成 pricing_engines_check.py、在装好 QuantLib 的环境里运行,应当复现同一批量级;把「替换开关」逐项改掉,就能看出每个参数的影响。
| 替换开关 | 改哪里 | 本页实测的后果 |
|---|---|---|
| 树型 | 把 "crr" 换成 "jr" / "lr" | 801 步下误差从 201.75 ppm 变为 192.97 / 0.05 ppm,树型选择比加步数更有效 |
| 树的步数 | 把 801 改成 10 / 100 / 1000 / 2000 | 10 步误差 19458 ppm,2000 步 98.65 ppm;且误差随奇偶步数振荡,建议成倍加 |
| 网格密度 | 把 (200, 400) 改成更粗或更细的网格 | 实测 200×400 为 37.91 ppm,可作基准;网格过粗会掩盖真实差异 |
| 路径数与种子 | 改 requiredSamples 与 seed | 20 万路径下换种子结果偏移约 0.029,与标准误 0.0329 同量级 |
| 方差削减 | 加 antitheticVariate=True | 标准误从 0.0329 降到 0.0164,耗时约翻倍 |
| 抽样序列 | 把 "PseudoRandom" 换成 "LowDiscrepancy" | 65536 点即达约 109 ppm,但不提供标准误估计 |
常见问题
为什么 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 量级」。结果仅供研究与对账方法参考,不构成投资建议。