pdeSolver 金融衍生品定价教程
本教程介绍如何使用 DolphinDB pdeSolver 系列函数求解一维和二维线性抛物型偏微分方程(Partial Differential
Equation,PDE),并将其应用于金融衍生品定价。内容覆盖模型与接口映射、典型定价案例,适合量化研究、衍生品定价研发等人员阅读。本教程以
pdeSolver 代码示例和部分性能展示为主,不构成完整数值验收报告,也不代表所有案例已通过生产环境回归测试。
1. 功能概览与快速开始
本章介绍 pdeSolver
在金融衍生品定价中的用途、支持范围和基本建模约定,并通过一个欧式看涨期权示例展示从模型设置到价格求解的基本流程。
1.1 为什么用 PDE 做定价
金融衍生品的价格取决于标的价格、时间、波动率等状态变量。PDE 求解器会先把这些变量划分成网格,再从到期日的收益开始,逐步向当前时刻倒推,计算出不同状态下的理论价格。
因此,PDE 求解器的作用不只是给出一个价格,还可以一次得到一整张价格面,并在同一框架下处理美式期权的提前行权、障碍期权的边界条件、随时间变化的参数以及较复杂的收益结构。
PDE 方法适合状态变量较少的问题。状态维度增加后,网格规模会迅速扩大:二维问题通常需要 ADI 等专门方法,三维及以上问题则要结合计算成本考虑其他数值方法。
1.2 能力与不适用范围
pdeSolver 主要用于求解一维和二维线性抛物型
PDE,采用有限差分法(FDM),支持结构化均匀网格和非均匀网格,以及常用的 Dirichlet、Neumann 和 Robin
边界条件。此外,它还支持一维障碍问题和二维交替方向隐式(ADI)方法。
需要注意的是,pdeSolver 并非通用的微分方程求解器。目前,它暂不支持非结构化网格、三维问题、有限元法(FEM)、有限体积法(FVM)、强非线性问题和二维障碍定价。详见 pdeSolver 官方文档。
1.3 重要约定
-
时间用剩余期限 ,从到期日的 向估值日推进。
-
贴现利率和股息/便利收益率采用连续复利,波动率按年化标准差计。
-
一维坐标多取 ,二维可能取 或 ;坐标必须在系数、初值和边界中一致。
-
文中的定价价值、现金流和边界均基于明确的示例约定;交易条款必须由客户实际 payoff、观察方式和结算规则决定。
-
严格区分模型误差、截断误差、空间误差、时间误差、迭代误差与 Monte Carlo 统计误差。
1.4 快速开始
如果你第一次接触 pdeSolver,先运行下面这个最小例子。它只做一件事:用一维 PDE 计算一个 European Call
在 S=100 处的价格。模型推导、边界含义和结果对照说明见 4.1 European Call 与 European Put。
spot = 100.0 //股票当前价格
strike = 100.0 //执行价格
rate = 0.03 //无风险利率
dividend = 0.01 //股息率
volatility = 0.20 //波动率
finalTime = 1.0 //期权的剩余期限
spotMax = 400.0 //股票价格上边界
// DolphinDB 的匿名函数在顶层不捕获外部变量;把系数和边界放入函数作用域。
def makeQuickBSCase(vol, rate, dividend, strike, spotMax) {
coeff = pdeCoeff1D(
diff={S,tau -> 0.5 * vol * vol * S * S},
conv={S,tau -> (rate-dividend) * S},
react=-rate
)
payoff = {S -> max(S-strike, 0.0)}
boundary = [
{tau -> 0.0},
{tau -> spotMax*exp(-dividend*tau) - strike*exp(-rate*tau)}
]
return coeff, payoff, boundary
}
coeff, payoff, boundary = makeQuickBSCase(volatility, rate, dividend, strike, spotMax)
spotGrid = pdeGrid1D(0.0, spotMax, 401, "sinh", strike, 3.0)
tGrid = (0..400) * finalTime / 400.0
setting = dict(STRING, ANY)
setting["interpAt"] = spot
result = pdeSolver(
coeff, payoff, pdeDomain1D(spotGrid), tGrid,
boundary, dict(STRING, ANY), setting
)
result["interpValue"]
在本教程的固定参数下,输出应接近期权解析解 8.8269。
先认识几个词
-
payoff:到期收益函数,例如 Call 的 。
-
obstacle:美式期权的提前行权约束,要求数值解不低于立即行权收益。
-
PSOR:处理美式期权离散互补问题的一种逐点迭代方法。
-
ADI:把二维问题拆成多个方向子步的交替方向隐式方法。
-
callback:由用户定义、在求解过程中提供系数或边界值的函数。
常用术语速查见附录 B。
2. 金融 PDE 的数学基础与建模规范
本章只保留理解接口和排查常见错误所需的数学内容。若目标是先把案例跑起来,可以先读第 3、4 章,再回来查对应公式;不需要一开始完整推导所有模型。
2.1 风险中性定价与 Itô 引理
在连续股息率为 q 的 Black–Scholes 模型下,风险中性测度中的标的过程为
对足够光滑的价格函数 V(t,S),Itô 引理给出
由复制组合消除随机项或由 Feynman–Kac 定理,可得定价 PDE
欧式看涨与看跌共用同一个 PDE,差别由到期收益决定:
2.2 从到期终值到正向剩余期限
令 ,因为 ,故
把这一式写成 pdeSolver 的一维标准形式:
| 字段 | Black–Scholes 映射 | 校验要点 |
|---|---|---|
|
mass |
1(默认) |
必须为正 |
|
diff |
|
当 S=0 时退化 |
|
conv |
(r-q)S |
有分红时不能漏掉 q |
|
react |
-r |
折现符号为负 |
|
source |
0(默认) |
有额外现金流时单独建模 |
2.3 二维交叉导数的完整系数
若两个 Brownian 运动满足 ,生成元中的交叉项是
这里没有额外的 1/2:它来自 Itô 展开中两项相同混合二阶导数的合并。pdeCoeff2D(xy=...)
输入的就是这一完整系数,不是协方差项的一半。
二维标准形式为
2.4 边界与初值的不同职责
| 对象 | 金融解释 | 不应混淆的事项 |
|---|---|---|
|
initial |
到期收益 |
欧式期权的到期条件,不是当日价格 |
|
boundary |
远端渐近式、吸收边界或精确边界 |
必须与 payoff 在 兼容 |
|
method["obstacle"] |
每个时间层的提前行权下界 |
不能用仅在左右端的 Dirichlet 边界代替 |
|
tGrid |
剩余期限方向的正向推进网格 |
要严格递增,从 0 至 T |
欧式 Call 的典型远端边界为 ;欧式 Put 的左端边界为 。美式 Put 在常见非负利率、无股息模型下左端边界则为 K:两者不能混用。
2.5 一维与二维的结果精度来源
总误差不应被一个“相对误差”数字掩盖。将同一组参数下的误差概念性地拆成
若使用 MC 基准,还应报告统计误差(标准误)。其中 属于产品假设与市场模型本身,并不能通过加密网格消除。
3. 核心函数概览
本章只说明函数的职责、输入输出关系和调用顺序;参数取值范围、类型检查、默认值、完整返回结构及版本差异,请直接参考下表的 DolphinDB 官方函数文档。
3.1 接口速查
| 函数 | 功能概述 | 参考调用 | 官方文档 |
|---|---|---|---|
|
|
创建一维均匀、sinh/tanh/log 非均匀空间网格 |
|
|
|
|
构造一维空间计算域 |
|
|
|
|
由 x/y 网格构造二维结构化计算域 |
|
|
|
|
封装一维扩散、对流、反应、源项和质量项系数 |
|
|
|
|
封装二维扩散、交叉导数、漂移与反应项 |
|
|
|
|
结合系数、初值、计算域、时间网格和边界求解 PDE |
|
|
|
|
查看网格、系数和其他对象的诊断概要 |
|
3.2 建模与求解流程
method 用于选择时间推进、对流离散、ADI/obstacle 及资源配置;setting
用于返回模式、时间层、插值、Greeks 和诊断。不要把这两种字典的字段混用。interpAt
应位于定义域内;resultMode="detail" 可在验证阶段配合
diagnostics=true 使用。
4. 一维金融定价案例
本章每个案例都按“产品与假设 → 系数和边界 → 最小可运行代码 → 结果核对提示”展开。第一次阅读时先看产品说明、代码和运行结果;需要自己推导时,再回头看数学映射和边界细节。
建议按这个顺序学习:先用 European Call 确认基本接口,再用 American Put 理解 obstacle,接着看 Local Volatility 和 Barrier 等一维扩展。Digital、Asian、Quanto 适合在已经熟悉一维调用后阅读。
4.1 European Call 与 European Put
产品与假设。仅在到期行权;同一风险中性模型下,两种 payoff 不同,PDE 相同。此模板是验证定价器符号与边界的首选入口。
推导与映射。 , 。Call 左端为 0;Put 左端必须是 ,不应沿用美式 Put 的 K。右端 Call 使用渐近线性边界,Put 使用 0。
样例参数。 ;网格聚焦在 K。
DolphinDB 完整示例
def makeBS1DCase(volatility, rate, dividend, strike, spotMax, productType) {
coeff = pdeCoeff1D(
diff={S,tau -> 0.5 * volatility * volatility * S * S},
conv={S,tau -> (rate-dividend) * S},
react=-rate
)
if (productType == "call") {
payoff = {S -> max(S-strike, 0.0)}
boundary = [
{tau -> 0.0},
{tau -> spotMax*exp(-dividend*tau) - strike*exp(-rate*tau)}
]
} else {
payoff = {S -> max(strike-S, 0.0)}
boundary = [{tau -> strike*exp(-rate*tau)}, 0.0]
}
return coeff, payoff, boundary
}
spot = 100.0
strike = 100.0
rate = 0.03
dividend = 0.01
volatility = 0.20
finalTime = 1.0
spotMax = 400.0
spotGrid = pdeGrid1D(0.0, spotMax, 401, "sinh", strike, 3.0)
tGrid = (0..400) * finalTime / 400.0
coeff, payoff, boundary = makeBS1DCase(volatility, rate, dividend, strike, spotMax, "call")
method = dict(STRING, ANY)
method["rannacherSteps"] = 2
setting = dict(STRING, ANY)
setting["interpAt"] = spot
result = pdeSolver(
coeff, payoff, pdeDomain1D(spotGrid), tGrid,
boundary, method, setting
)
pdePrice = result["interpValue"]
d1 = (log(spot/strike) + (rate-dividend+0.5*volatility*volatility)*finalTime) / (volatility*sqrt(finalTime))
d2 = d1 - volatility*sqrt(finalTime)
reference = spot*exp(-dividend*finalTime)*cdfNormal(0.0,1.0,d1) - strike*exp(-rate*finalTime)*cdfNormal(0.0,1.0,d2)
relativeError = abs(pdePrice-reference) / max(abs(reference), 1e-12)
summary = dict(STRING, ANY)
summary["pdePrice"] = pdePrice
summary["closedFormPrice"] = reference
summary["relativeError"] = relativeError
summary
解析结果: Call = 8.82732123;Put = 6.86689121。解析 Put-Call Parity:
。
可选结果核对: 报告每种产品的 pdePrice、reference、绝对误差和相对误差;网格 分别加密,并用 检查截断误差。验证 Put 左端 。
运行结果:
N_x=401, N_t=400 时,Call
pdePrice=8.8268960987,解析值 8.8273212254,相对误差 4.8160e-05;Put 绝对误差
4.248859e-04,Put–Call parity residual 2.406850e-07。
欧式 Put 与 Put–Call Parity
上方工厂已经提供 Put 分支。如需核对 Put 结果,可以同时报告解析误差和 Put–Call parity residual;Put
左端必须使用随剩余期限折现的 K*exp(-r*tau),不能沿用美式 Put 的常数边界。
4.2 American Put:PSOR 与 Penalty
产品与假设。美式看跌允许在到期之前的任意时刻行权。对于 r>0,q=0 的标准例,提前行权可能有价值。
数学关系。 ,其中 。令 ,剩余期限方向满足互补条件 。这不是一个只靠左右边界能实现的约束。
边界。在 S=0 处即时行权使价格为 K(本例无分红且 r>0),远端近似为
0。method["obstacle"] 表达内点不等式;PSOR 与 penalty 为两条可选路径。
DolphinDB 完整示例
spot = 100.0
strike = 100.0
rate = 0.05
volatility = 0.20
finalTime = 1.0
spotMax = 300.0
spotGrid = pdeGrid1D(0.0, spotMax, 401, "sinh", strike, 3.0)
tGrid = (0..400) / 400.0
def makeAmericanPutCase(volatility, rate, strike, spotMax) {
coeff = pdeCoeff1D(
diff={S,tau -> 0.5 * volatility * volatility * S * S},
conv={S,tau -> rate * S},
react=-rate
)
payoff = {S -> max(strike-S, 0.0)}
boundary = [strike, 0.0]
return coeff, payoff, boundary
}
coeff, payoff, boundary = makeAmericanPutCase(volatility, rate, strike, spotMax)
method = dict(STRING, ANY)
method["rannacherSteps"] = 2
method["obstacle"] = payoff
method["obstacleSolver"] = "PSOR"
method["psorOmega"] = 1.2
method["psorTolerance"] = 1e-10
method["psorMaxIterations"] = 20000
setting = dict(STRING, ANY)
setting["resultMode"] = "detail"
setting["diagnostics"] = true
setting["interpAt"] = spot
result = pdeSolver(
coeff, payoff, pdeDomain1D(spotGrid), tGrid,
boundary, method, setting
)
payoffVector = max(strike - spotGrid, 0.0)
obstacleViolation = max(0.0, max(payoffVector - result["value"]))
summary = dict(STRING, ANY)
summary["price"] = result["interpValue"]
summary["obstacleViolation"] = obstacleViolation
summary["iterations"] = result["diagnostics"]["iterations"]["total"]
summary
Penalty 配置要点
Penalty 与上面的 PSOR 示例共用系数、payoff、网格和边界,但必须新建参数字典,不能把 PSOR 字段留在同一个 method 中:
penaltyMethod = dict(STRING, ANY)
penaltyMethod["scheme"] = "implicit"
penaltyMethod["obstacle"] = payoff
penaltyMethod["obstacleSolver"] = "penalty"
penaltyMethod["penaltyFactor"] = 1e8
penaltyMethod["penaltyTolerance"] = 1e-8
penaltyMethod["penaltyMaxIterations"] = 100
penaltyTGrid = (0..200) / 200.0
penaltyResult = pdeSolver(
coeff, payoff, pdeDomain1D(spotGrid), penaltyTGrid,
boundary, penaltyMethod, setting
)
summary = dict(STRING, ANY)
summary["price"] = penaltyResult["interpValue"]
summary["linearSolver"] = penaltyResult["meta"]["linearSolver"]
summary["finalResidual"] = penaltyResult["diagnostics"]["iterations"]["finalResidual"]
summary
如需比较 PSOR 与 Penalty,可同时记录价格、最终残差和网格变化;本文示例不构成完整 obstacle 验收。
独立基准: 如需进一步核对,可使用 2000、4000、8000 步 CRR 二叉树或外部美式定价器。本文不提供完整独立基准结论。
执行验收: (1) ;(2) 所有网格点 ;(3) PSOR 容差及步数变化结果稳定;(4) Penalty 因子加倍不产生不合理震荡;(5) 核对每个时间层的迭代残差及非收敛告警。
运行结果: PSOR 返回
price=6.0901245592、obstacleViolation=0、总迭代次数
17341;Penalty 返回
price=6.0833080913、finalResidual=2.5560e-12。
4.3 Local Volatility:时空系数与缓存
产品与假设。欧式看涨,局部波动率依赖 S 和 。PDE 的结构保持一维,但二阶系数成为 。
接口映射。函数型 diff
实现模型面,conv=(r-q)S,react=-r;时间片与全量预计算只改变计算策略,不改变数学方程。
DolphinDB 完整示例
spot = 100.0
strike = 100.0
rate = 0.03
dividend = 0.01
volatility = 0.20
spotMax = 400.0
spots = 80.0 100.0 120.0
def makeLocalVolCase(volatility, rate, dividend, strike, spotMax) {
coeff = pdeCoeff1D(
diff={S,tau -> 0.5 * (volatility * (1.0 + 0.20*tanh((S-strike)/strike)) * (1.0 + 0.10*tau)) * (volatility * (1.0 + 0.20*tanh((S-strike)/strike)) * (1.0 + 0.10*tau)) * S * S},
conv={S,tau -> (rate-dividend)*S},
react=-rate
)
payoff = {S -> max(S-strike, 0.0)}
boundary = [
{tau -> 0.0},
{tau -> spotMax*exp(-dividend*tau) - strike*exp(-rate*tau)}
]
return coeff, payoff, boundary
}
coeff, payoff, boundary = makeLocalVolCase(volatility, rate, dividend, strike, spotMax)
coarseGrid = pdeGrid1D(0.0, spotMax, 201, "sinh", strike, 3.0)
mediumGrid = pdeGrid1D(0.0, spotMax, 301, "sinh", strike, 3.0)
coarseMethod = dict(STRING, ANY)
coarseMethod["precompute"] = "all"
mediumAllMethod = dict(STRING, ANY)
mediumAllMethod["precompute"] = "all"
mediumSliceMethod = dict(STRING, ANY)
mediumSliceMethod["precompute"] = "timeSlice"
coarseSetting = dict(STRING, ANY)
coarseSetting["resultMode"] = "detail"
coarseSetting["interpAt"] = spots
mediumAllSetting = dict(STRING, ANY)
mediumAllSetting["resultMode"] = "detail"
mediumAllSetting["interpAt"] = spots
mediumSliceSetting = dict(STRING, ANY)
mediumSliceSetting["resultMode"] = "detail"
mediumSliceSetting["interpAt"] = spots
coarse = pdeSolver(coeff, payoff, pdeDomain1D(coarseGrid), (0..100) / 100.0, boundary, coarseMethod, coarseSetting)
mediumAll = pdeSolver(coeff, payoff, pdeDomain1D(mediumGrid), (0..150) / 150.0, boundary, mediumAllMethod, mediumAllSetting)
mediumSlice = pdeSolver(coeff, payoff, pdeDomain1D(mediumGrid), (0..150) / 150.0, boundary, mediumSliceMethod, mediumSliceSetting)
precomputeDifference = max(abs(mediumAll["interpValue"] - mediumSlice["interpValue"]))
summary = dict(STRING, ANY)
summary["coarsePrice"] = coarse["interpValue"]
summary["mediumPrice"] = mediumAll["interpValue"]
summary["precomputeDifference"] = precomputeDifference
summary["precomputeModeAll"] = mediumAll["meta"]["precomputeMode"]
summary["precomputeModeTimeSlice"] = mediumSlice["meta"]["precomputeMode"]
summary
独立基准: 同一模型的更细空间/时间网格解;若有独立 local-vol 实现,则以相同局部波动率函数对照。
可选结果核对: 可以比较 precompute=all 与 timeSlice
的结果,并观察空间、时间步长和 Smax 的影响。
运行结果: 粗网格三个插值点的价格为 (1.53592973, 9.22061441, 23.88065593),中网格为 (1.53651947, 9.22152872, 23.88069652)。
4.4 Digital:不连续收益与 Rannacher
产品与假设。现金或无看涨数字期权,payoff 为 ,在 K 处不连续。
模型与处理。Black–Scholes PDE 不变,initial 在阈值处跳跃;优先用 Rannacher
smoothing,并在 strike 附近聚焦网格。此举控制短期高频振荡,但不等于精度自动达标。
DolphinDB 最小调用片段
Digital 与 Vanilla 的 PDE 相同,主要替换不连续 payoff、边界和初始 Rannacher 设置。下面的函数可独立运行:
def runDigitalCase(spot, strike, rate, dividend, vol, maturity, spotMax) {
payout = 1.0
coeff = pdeCoeff1D(
diff={S,tau -> 0.5 * vol * vol * S * S},
conv={S,tau -> (rate-dividend) * S},
react=-rate
)
payoff = {S -> iif(S > strike, payout, 0.0)}
boundary = [{tau -> 0.0}, {tau -> payout*exp(-rate*tau)}]
spotGrid = pdeGrid1D(0.0, spotMax, 401, "sinh", strike, 3.0)
tGrid = (0..200) / 200.0
method = dict(STRING, ANY)
method["theta"] = 0.5
method["rannacherSteps"] = 2
setting = dict(STRING, ANY)
setting["resultMode"] = "detail"
setting["interpAt"] = spot
result = pdeSolver(coeff, payoff, pdeDomain1D(spotGrid), tGrid, boundary, method, setting)
d1 = (log(spot/strike) + (rate-dividend+0.5*vol*vol)*maturity) / (vol*sqrt(maturity))
d2 = d1 - vol*sqrt(maturity)
reference = payout * exp(-rate*maturity) * cdfNormal(0.0, 1.0, d2)
summary = dict(STRING, ANY)
summary["price"] = result["interpValue"]
summary["closedFormPrice"] = reference
summary["absoluteError"] = abs(result["interpValue"] - reference)
return summary
}
runDigitalCase(100.0, 100.0, 0.03, 0.01, 0.20, 1.0, 400.0)
完整可运行脚本和解析参考计算见案例包;重点是检查 strike 附近振荡和 Greeks 对网格的敏感性。
解析基准(单位现金支付): 。取 时,参考值 0.48522277。脚本参数若与该组不同,必须重新计算解析值。
可选结果核对: 可以比较 rannacherSteps=0/1/2,并观察 strike
附近振荡及网格敏感性。
运行结果:
N_x=401, N_t=200 时,价格为 0.4823201116,解析值为 0.4852227668,绝对误差
0.0029026552;数值范围为 [0.0, 0.9704455396]。
4.5 Barrier:单障碍与双障碍
产品与假设。连续观察且无返还的 Knock-Out 期权。触碰障碍后价值变成 0,所以障碍位置对应吸收边界。单障碍情形把障碍本身作为定义域端点;双障碍把整个求解域限制在两个障碍之间。
边界模型。下敲出 ,上敲出 。对于未触及障碍的终值 ,在障碍上需要令 ;若 payoff 在边界并非零,应检查初始兼容性并按合同正确解释到期触碰条款。
DolphinDB 最小示例:下敲出
spot = 100.0
strike = 100.0
barrier = 80.0
rate = 0.03
dividend = 0.01
volatility = 0.20
finalTime = 1.0
spotMax = 400.0
def makeDownOutCallCase(volatility, rate, dividend, strike, barrier, spotMax) {
coeff = pdeCoeff1D(
diff={S,tau -> 0.5 * volatility * volatility * S * S},
conv={S,tau -> (rate-dividend) * S},
react=-rate
)
payoff = {S -> max(S-strike, 0.0)}
boundary = [
0.0,
{tau -> spotMax*exp(-dividend*tau) - strike*exp(-rate*tau)}
]
return coeff, payoff, boundary
}
coeff, payoff, boundary = makeDownOutCallCase(volatility, rate, dividend, strike, barrier, spotMax)
spotGrid = pdeGrid1D(barrier, spotMax, 401, "sinh", strike, 3.0)
tGrid = (0..400) / 400.0
method = dict(STRING, ANY)
method["rannacherSteps"] = 2
setting = dict(STRING, ANY)
setting["resultMode"] = "detail"
setting["interpAt"] = spot
result = pdeSolver(coeff, payoff, pdeDomain1D(spotGrid), tGrid, boundary, method, setting)
summary = dict(STRING, ANY)
summary["price"] = result["interpValue"]
summary["barrierValue"] = result["value"][0]
summary["minValue"] = min(result["value"])
summary
其他障碍类型的映射
单敲出与双敲出的代码结构相同,差异只在定义域和两端边界:
| 产品 | 定义域 | 障碍边界 |
|---|---|---|
|
Down-and-Out |
[B_down,Smax] |
下端设为 0,远端使用 Vanilla 渐近边界 |
|
Up-and-Out |
[0,B_up] |
上端设为 0,下端按产品边界处理 |
|
Double Knock-Out |
[B_down,B_up] |
两端均设为 0 |
因此,先用上面的 Down-and-Out 最小示例跑通,再按表格替换 domain、payoff 和 boundary;完整三种脚本保留在案例包中。
独立基准: 单障碍可用满足一致连续观察条款的解析屏障定价公式;双障碍可用高精度独立 PDE 或谱展开参考。
可选结果核对: 可以检查障碍边界、价格非负性以及网格和截断域敏感性。
运行结果: 下敲出看涨价格 8.7309347961,上敲出看涨价格 3.0969185841,双敲出看涨价格 3.0964585436;各示例障碍端点值均为 0.0。
4.6 One Touch:何时支付决定边界
产品与假设。标的首次触及上障碍时触发给付。必须明确合同支付时点:触碰即付(Pay at Hit)或触碰确认后到期才付(Pay at Maturity)。两者可使用相同生存域,但碰触边界不同。
边界。触碰即付: ;到期支付: 。若 且未触及,初值在域内为 0。
DolphinDB 最小调用片段
One Touch 的核心不是新 PDE,而是触碰支付时点对应的障碍边界。下面以 Pay at Maturity 为例,并保留
payAtHit 切换:
def runOneTouchCase(spot, barrier, rate, dividend, vol, payout, maturity, payAtHit) {
coeff = pdeCoeff1D(
diff={S,tau -> 0.5 * vol * vol * S * S},
conv={S,tau -> (rate-dividend) * S},
react=-rate
)
payoff = {S -> iif(S >= barrier, payout, 0.0)}
if (payAtHit) {
boundary = [0.0, payout]
} else {
boundary = [0.0, {tau -> payout * exp(-rate*tau)}]
}
spotGrid = pdeGrid1D(0.0, barrier, 401, "sinh", spot, 3.0)
tGrid = (0..200) / 200.0
method = dict(STRING, ANY)
method["rannacherSteps"] = 2
setting = dict(STRING, ANY)
setting["resultMode"] = "detail"
setting["interpAt"] = spot
result = pdeSolver(coeff, payoff, pdeDomain1D(spotGrid), tGrid, boundary, method, setting)
summary = dict(STRING, ANY)
summary["price"] = result["interpValue"]
summary["barrierValue"] = result["value"][size(result["value"]) - 1]
return summary
}
runOneTouchCase(100.0, 130.0, 0.03, 0.01, 0.20, 1.0, 1.0, false)
将对应的 boundary 传给同一 pdeSolver 调用即可。完整函数和 Pay at
Maturity 运行结果见案例包。
独立基准: 采用 GBM 首达时间的解析概率/折现首达时间期望,或带 Brownian bridge 监测修正的独立 MC。
可选结果核对:可以检查非负性、价格范围、Pay at Hit 与 Pay at Maturity 的差异,以及是否已触碰障碍。
运行结果: 当前代码是 Pay at Maturity,价格 0.1839887388,障碍端值 0.9704455335。
4.7 Asian:连续算术平均的状态降维
产品与假设。连续算术平均固定执行价看涨: , 是累计积分,单位为价格×时间,不是已实现平均值。
原始二维 PDE。对 有 。
令 ,可推得
本案例采用 q=0;当 时,解在右侧区域近似线性,其斜率满足 ,因此右端可以采用 Neumann 边界近似。合约起点定价时 ,故示例 对应 。
DolphinDB 完整示例
spot = 100.0
strike = 100.0
rate = 0.03
volatility = 0.20
finalTime = 1.0
def makeAsianCase(volatility, rate, xMin, xMax) {
coeff = pdeCoeff1D(
diff={x,tau -> 0.5*volatility*volatility*x*x},
conv={x,tau -> 1.0-rate*x}
)
payoff = {x -> max(x,0.0)}
rightGradient = dict(STRING, ANY)
rightGradient["type"] = "neumann"
rightGradient["value"] = {tau -> exp(-rate*tau)}
boundary = [0.0, rightGradient]
return coeff, payoff, boundary
}
accumulatedIntegral = 0.0 // A_0 = integral_0^0 S_u du = 0
coeff, payoff, boundary = makeAsianCase(volatility, rate, -6.0, 8.0)
xGrid = pdeGrid1D(-6.0, 8.0, 401, "sinh", 0.0, 3.0)
tGrid = (0..400) * finalTime / 400.0
method = dict(STRING, ANY)
setting = dict(STRING, ANY)
setting["resultMode"] = "value"
x0 = (accumulatedIntegral-strike*finalTime) / spot
setting["interpAt"] = x0
result = pdeSolver(coeff, payoff, pdeDomain1D(xGrid), tGrid, boundary, method, setting)
price = spot/finalTime * result["interpValue"]
summary = dict(STRING, ANY)
summary["price"] = price
summary["transformedCoordinate"] = x0
summary["gridSize"] = size(xGrid)
summary
独立基准: 连续路径 MC,采用与 PDE 相同的积分归一化 ;离散求和需报告监测步长的离散化偏差。
如需进一步验证,可使用 Monte Carlo 对照,并报告路径数、时间离散方式和标准误。本文示例不提供完整 MC 验收结论。
运行结果:
x_0=-1.0、网格大小为 401 时,价格为 5.2742743038。
4.8 Quanto:外币股票固定汇率结算期权
4.8.1 产品定义与外汇报价约定
考虑外国股票 S(单位:外币/股),其连续股息率为 ,外币无风险利率为 。X 定义为每 1 单位外币兑换多少本币(本币/外币),两者瞬时收益的相关系数为 。本币利率为 。合约用固定换算因子 H(本币/外币)支付到期收益:
H 为合同确定的固定结算汇率,并非到期市场汇率 。若实际合同结算规则不同(浮动汇率结算、汇率反向报价、汇率决定执行价等),必须重新建立模型,而非复用本节公式。
4.8.2 测度转换、PDE 与系数映射
外国股票在外国风险中性测度 下满足 。以本币计价并转到本币测度 后,Quanto 协方差修正为
这里的相关性、符号和 均基于上文的 本币/外币 报价 X;反向外汇变量不可直接套用。因此
令剩余期限 ,以本币计价的期权价值满足
pdeCoeff1D 字段 |
系数 | 单位 / 备注 |
|---|---|---|
|
diff |
|
二阶项 |
|
conv |
|
本币测度修正后的漂移 |
|
react |
|
本币折现 |
|
mass |
1 |
默认值 |
左边界 。右边界可用 ;这是远端渐近边界,必须检查 的扩张误差。
4.8.3 完整 DolphinDB 示例
// X = 本币/外币,固定合同换算因子 H = 1 本币/外币。
spot = 100.0
strike = 100.0
domesticRate = 0.03
foreignRate = 0.01
foreignDividend = 0.0
volatility = 0.20
fxVolatility = 0.10
correlation = 0.25
fixedConversionRate = 1.0
finalTime = 1.0
spotMax = 400.0
def makeQuantoCase(sigmaS, sigmaX, rho, rd, rf, qS, K, Smax, H) {
qEffective = rd - rf + qS + rho*sigmaS*sigmaX
coeff = pdeCoeff1D(
diff={S,tau -> 0.5*sigmaS*sigmaS*S*S},
conv={S,tau -> (rd-qEffective)*S},
react=-rd
)
payoff = {S -> H*max(S-K, 0.0)}
boundary = [
{tau -> 0.0},
{tau -> H*(Smax*exp(-qEffective*tau) - K*exp(-rd*tau))}
]
return coeff, payoff, boundary
}
coeff, payoff, boundary = makeQuantoCase(
volatility, fxVolatility, correlation,
domesticRate, foreignRate, foreignDividend,
strike, spotMax, fixedConversionRate
)
spotGrid = pdeGrid1D(0.0, spotMax, 401, "sinh", strike, 3.0)
tGrid = (0..200) * (finalTime / 200.0)
method = dict(STRING, ANY)
method["rannacherSteps"] = 2
setting = dict(STRING, ANY)
setting["resultMode"] = "detail"
setting["interpAt"] = spot
result = pdeSolver(coeff, payoff, pdeDomain1D(spotGrid), tGrid, boundary, method, setting)
qEffective = domesticRate-foreignRate+foreignDividend+correlation*volatility*fxVolatility
d1 = (log(spot/strike)+(domesticRate-qEffective+0.5*volatility*volatility)*finalTime) / (volatility*sqrt(finalTime))
d2 = d1-volatility*sqrt(finalTime)
// DolphinDB 对跨行括号表达式解析较严格,拆成单行等价表达式。
reference = fixedConversionRate*spot*exp(-qEffective*finalTime)*cdfNormal(0.0,1.0,d1) - fixedConversionRate*strike*exp(-domesticRate*finalTime)*cdfNormal(0.0,1.0,d2)
summary = dict(STRING, ANY)
summary["qEffective"] = qEffective
summary["domesticDrift"] = domesticRate-qEffective
summary["pdePrice"] = result["interpValue"]
summary["analyticPrice"] = reference
summary["absoluteError"] = abs(result["interpValue"]-reference)
summary
4.8.4 独立 Benchmark 与收敛性
本案例参数给出 ;独立解析价格为 7.9951555831 本币单位(H=1)。
如需进一步核对,可参考以下检查项目:
-
时 Quanto 修正项归零,漂移为 ;相关性取 -0.5,0,0.5 进行单调性与边界一致性比较;
-
使用本币测度的 GBM Monte Carlo 独立计算折现收益,报告随机数种子、路径数及标准误;
-
网格 、时间步 ,分别测试空间、时间和 的截断敏感性;
-
若改用 1/X 的外汇报价,应重新推导测度转换与相关性符号,不能只修改参数名;
-
检查固定换算因子 H 对价格的线性缩放。
运行结果:
qEffective=0.025、本币漂移 0.005;PDE 价格 7.9946995316,解析值
7.9951555831,绝对误差 0.0004560515。
5. 二维金融定价案例
二维问题须明确坐标、全混合系数、各边界顺序和矩阵布局。Heston 选择 ,篮子选择 ,Exchange 选择 ,不要互相照搬参数映射。
二维案例先看三件事:第二个状态变量是什么、交叉导数系数是否完整、每条边界对应哪个坐标方向。代码跑通后,再比较 ADI 方案和二维截断域收敛;不要先用一维案例的直觉判断二维结果。
5.1 Heston:log-price 与方差二维状态
产品与假设。Heston 方差过程 ,标的 , 。令 ,得到
边界选择。x 方向低端近零价、高端近线性价;v=0 为退化边界,简单 Neumann=0 是数值近似,不等于一般模型解析边界;应扫描 、近零网格和替代边界约定。Feller 条件 属于过程性质,不是 API 强制约束。
DolphinDB 完整示例
rate = 0.05
theta = 0.04
kappa = 2.0
sigmaV = 0.30
rho = -0.70
spot = 100.0
strike = 100.0
finalTime = 1.0
spotMax = 2000.0
vMax = 1.0
def makeHestonCase(rate, theta, kappa, sigmaV, rho, strike, xMax) {
coeff = pdeCoeff2D(
xx={x,v,tau -> 0.5*v},
yy={x,v,tau -> 0.5*sigmaV*sigmaV*v},
xy={x,v,tau -> rho*sigmaV*v},
x={x,v,tau -> rate-0.5*v},
y={x,v,tau -> kappa*(theta-v)},
u=-rate
)
payoff = {x,v -> max(exp(x)-strike, 0.0)}
varianceBoundary = dict(STRING, ANY)
varianceBoundary["type"] = "neumann"
varianceBoundary["value"] = 0.0
boundary = [
0.0,
{v,tau -> exp(xMax) - strike*exp(-rate*tau)},
varianceBoundary,
varianceBoundary
]
return coeff, payoff, boundary
}
logGrid = (0..120) * ((log(spotMax) - log(1.0)) / 120.0) + log(1.0)
varianceUnit = (0..60) / 60.0
varianceGrid = varianceUnit * varianceUnit
coeff, payoff, boundary = makeHestonCase(rate, theta, kappa, sigmaV, rho, strike, log(spotMax))
tGrid = (0..120) * (finalTime / 120.0)
method = dict(STRING, ANY)
method["adiScheme"] = "MCS"
method["parallel"] = false
method["rannacherSteps"] = 2
method["precompute"] = "timeSlice"
setting = dict(STRING, ANY)
setting["resultMode"] = "detail"
setting["diagnostics"] = true
setting["interpAt"] = [log(spot), 0.04]
result = pdeSolver(coeff, payoff, pdeDomain2D(logGrid, varianceGrid), tGrid, boundary, method, setting)
summary = dict(STRING, ANY)
summary["price"] = result["interpValue"]
summary["adiScheme"] = result["meta"]["adiScheme"]
summary["success"] = result["diagnostics"]["success"]
summary
独立基准: 优先使用 Heston 半解析特征函数积分定价、经验证的外部 pricer 或高精度 Fourier 定价实现。测得 QuantLib
AnalyticHestonEngine 的半解析结果为 10.3942185652。
可选结果核对:可以核对混合导数系数,并观察空间、方差和时间网格以及截断域变化的影响。
运行结果: 121×61 空间网格、120 个时间区间、MCS 下,价格为
10.4614959124,diagnostics.success=true。
5.2 Basket:二维相关性与退化边界
产品与假设。两资产加权篮子
。在零股息情形下,二维 xy=rho*sigma1*sigma2*S1*S2。
关键边界纠错。在 时, 仍服从扩散过程,该边界上的价值是 ,不能直接用 代替。在 时对称处理。两个高价格端使用远端 Delta 的 Neumann 渐近条件,避免给出相互冲突的角点 Dirichlet 值。
DolphinDB 最小可运行示例
Basket 的关键不是重复二维求解器调用,而是交叉项、退化边界和高端 Neumann 边界。下面保留一维辅助边界和最小二维求解:
def oneAssetCall(S, K, r, sigma, tau) {
if (S <= 0.0) return 0.0
if (tau <= 0.0) return max(S-K, 0.0)
d1 = (log(S/K) + (r+0.5*sigma*sigma)*tau) / (sigma*sqrt(tau))
d2 = d1 - sigma*sqrt(tau)
return S*cdfNormal(0.0,1.0,d1) - K*exp(-r*tau)*cdfNormal(0.0,1.0,d2)
}
def runBasketCase() {
spot1=100.0; spot2=100.0; strike=100.0; rate=0.03
sigma1=0.20; sigma2=0.30; rho=0.25; w1=0.5; w2=0.5
coeff = pdeCoeff2D(
xx={S1,S2,tau -> 0.5*sigma1*sigma1*S1*S1},
yy={S1,S2,tau -> 0.5*sigma2*sigma2*S2*S2},
xy={S1,S2,tau -> rho*sigma1*sigma2*S1*S2},
x={S1,S2,tau -> rate*S1}, y={S1,S2,tau -> rate*S2}, u=-rate
)
payoff = {S1,S2 -> max(w1*S1+w2*S2-strike,0.0)}
xHigh=dict(STRING,ANY); xHigh["type"]="neumann"; xHigh["value"]={S2,tau -> w1}
yHigh=dict(STRING,ANY); yHigh["type"]="neumann"; yHigh["value"]={S1,tau -> w2}
boundary = [
{S2,tau -> w2*oneAssetCall(S2,strike/w2,rate,sigma2,tau)}, xHigh,
{S1,tau -> w1*oneAssetCall(S1,strike/w1,rate,sigma1,tau)}, yHigh
]
xGrid=(0..80)*5.0; yGrid=xGrid; tGrid=(0..120)/120.0
method=dict(STRING,ANY); method["adiScheme"]="MCS"; method["parallel"]=false
setting=dict(STRING,ANY); setting["resultMode"]="detail"; setting["diagnostics"]=true; setting["interpAt"]=[spot1,spot2]
result=pdeSolver(coeff,payoff,pdeDomain2D(xGrid,yGrid),tGrid,boundary,method,setting)
summary=dict(STRING,ANY); summary["price"]=result["interpValue"]; summary["success"]=result["diagnostics"]["success"]
return summary
}
runBasketCase()
边界顺序仍为 [xMin,xMax,yMin,yMax];两个低端边界使用一维 BS 看涨价格,高端使用 Neumann 条件。
独立基准: 独立多资产 Monte Carlo,相关正态增量 ,给出路径数、时间离散(若需要)和标准误。
可选结果核对: 可以检查退化边界、价格非负性、相关性变化和两个空间截断边界的影响。
运行结果: 81×81 空间网格、120 个时间区间、MCS 下,价格为
9.3986919796,diagnostics.success=true。
5.3 Exchange:Margrabe 解析边界与二维 ADI
考虑零成本交换权,收益为 。两标的在同一风险中性测度下分别服从相关 GBM,连续股息率为 ,波动率为 ,瞬时相关系数为 。令 ,两维 PDE 的混合导数完整系数为 。
Margrabe 解析解:
当 ,直接返回 payoff,以避免除以零。假设正的标的价格、正的合成波动率。二维空间截断取 ;四条边界按当前边上的两个标的值使用同一 Margrabe 公式,同时满足角点一致性。
DolphinDB 最小可运行示例
Exchange 使用 log-price 坐标,并把同一个 Margrabe 公式用于四条边界:
def margrabeBoundary(s1, s2, tau, vol1, vol2, corr, q1, q2) {
if (tau <= 0.0) return max(s1-s2, 0.0)
volM=sqrt(vol1*vol1+vol2*vol2-2.0*corr*vol1*vol2)
d1=(log(s1/s2)+(q2-q1+0.5*volM*volM)*tau)/(volM*sqrt(tau))
d2=d1-volM*sqrt(tau)
return s1*exp(-q1*tau)*cdfNormal(0.0,1.0,d1)-s2*exp(-q2*tau)*cdfNormal(0.0,1.0,d2)
}
def runExchangeCase() {
asset1=100.0; asset2=100.0; rate=0.03; sigma1=0.20; sigma2=0.30; rho=0.25
q1=0.0; q2=0.0; sMin=1.0; sMax=2000.0; maturity=1.0
coeff=pdeCoeff2D(xx=0.5*sigma1*sigma1, yy=0.5*sigma2*sigma2, xy=rho*sigma1*sigma2,
x=rate-q1-0.5*sigma1*sigma1, y=rate-q2-0.5*sigma2*sigma2, u=-rate)
payoff={x,y -> max(exp(x)-exp(y),0.0)}
xGrid=(0..600)*(log(sMax/sMin)/600.0)+log(sMin); yGrid=xGrid; tGrid=(0..120)/120.0
boundary=[
{y,tau -> margrabeBoundary(exp(xGrid[0]),exp(y),tau,sigma1,sigma2,rho,q1,q2)},
{y,tau -> margrabeBoundary(exp(xGrid[size(xGrid)-1]),exp(y),tau,sigma1,sigma2,rho,q1,q2)},
{x,tau -> margrabeBoundary(exp(x),exp(yGrid[0]),tau,sigma1,sigma2,rho,q1,q2)},
{x,tau -> margrabeBoundary(exp(x),exp(yGrid[size(yGrid)-1]),tau,sigma1,sigma2,rho,q1,q2)}
]
method=dict(STRING,ANY); method["adiScheme"]="MCS"; method["rannacherSteps"]=2
setting=dict(STRING,ANY); setting["resultMode"]="detail"; setting["diagnostics"]=true; setting["interpAt"]=[log(asset1),log(asset2)]
result=pdeSolver(coeff,payoff,pdeDomain2D(xGrid,yGrid),tGrid,boundary,method,setting)
reference=margrabeBoundary(asset1,asset2,maturity,sigma1,sigma2,rho,q1,q2)
summary=dict(STRING,ANY); summary["pdePrice"]=result["interpValue"]; summary["margrabeReference"]=reference; summary["absoluteError"]=abs(result["interpValue"]-reference)
return summary
}
runExchangeCase()
四个边界函数都调用同一个 margrabeBoundary,并使用当前边上的两个标的值。
运行结果为 12.5667783189,Margrabe 解析值为 12.5632938837,绝对误差为 0.0034844352。若需要更严格的价格误差,应继续细化二维空间、时间网格并扩大截断域。
6. 数值方法选择和收敛性
本章结合前述案例,介绍一维时间离散、初值平滑、美式行权约束和二维 ADI 的选择要点,并说明如何通过调整网格与截断域检查结果的可靠性。
6.1 一维 Theta:显式、隐式、Crank–Nicolson
对半离散系统 ,Theta 格式满足:
-
:显式,稳定性限制严,在非均匀网格最小间距处尤需小心。
-
:隐式 Euler,一阶时间精度,单调性一般更稳定。
-
:Crank–Nicolson,在足够光滑解下通常二阶时间精度,但不连续 payoff 附近可能产生短暂振荡。
pdeSolver 的具体 scheme/theta 可用组合和合法参数请见第 3
章官方文档。
6.2 Rannacher smoothing
对 Digital 或 Call/Put 的不光滑 payoff,初期若干时间区间使用隐式半步,有助于抑制 Crank–Nicolson
在初值附近的振荡。rannacherSteps 指的是公开时间区间数,不是需要额外增加到 tGrid
的独立时间点。比较 0/1/2 必须固定其他参数。
6.3 American obstacle、PSOR 与 penalty
美式期权是离散互补问题。选 PSOR 时记录
omega/tolerance/maxIterations、最终约束违反量和迭代统计;选 penalty
时记录惩罚因子、停止容差及偏离 obstacle 的量。两组求解器的专有参数不得混用。对于非收敛不应以“最后一次迭代价格”代替已验证价格。
6.4 二维 ADI 与交叉项
Douglas、CS、MCS、HV 各有稳定性与截断误差特征。具有混合导数的 Heston/Basket/Exchange,优先从官方默认 MCS 入手,再扫描
adiTheta 与空间、时间网格;没有统一对全部参数都最优的方案。二维完整隐式与二维 obstacle
当前不在接口范围内。
6.5 非均匀网格与截断域
对于执行价附近弯折,使用 sinh 聚焦;障碍产品同时关注障碍边界附近;Heston
方差维度在低方差区加密。聚焦网格是重分配节点,不保证任意指标自动提升。单独扩大
、
或 log 坐标范围,是严肃定价验证的必需步骤。
7. Greeks 与数值输出
本章介绍输出结构、一维与二维 Greeks、对数坐标下的导数转换以及 Theta 的时间方向约定,最后给出使用价格扰动核对 Greeks 的方法。
7.1 输出结构
-
默认
resultMode="value"且没有插值/Greeks 请求:直接返回数值数组。 -
有
interpAt或calcGreeks=true时:即便resultMode="value",也可能返回包含interpValue、greeks的字典。 -
resultMode="detail"返回字典;diagnostics=true仅与该模式配合。默认输出是最终时间层,可用output="all"/"selected"修改。
7.2 一维与二维 Greeks
对 S 坐标, 、 ;对二维价格坐标,获取 和混合 Gamma。不要将全部二维 Greek 直接称为 Vega:如果第二坐标是方差 对应 variance sensitivity,转换成对波动率 的 Vega 还需要 因子(对于 )。
7.3 log-price 坐标的链式法则
若 ,对 S 的价格 Delta/Gamma 为
,
pdeSolver 输出的 Greek 是相对于其计算坐标的导数;在 Heston、Exchange 的 log
坐标模型中,金融 Greek 必须经坐标转换才有业务意义。
7.4 Theta 的正负方向
时间网格沿剩余期限 正向计算;标准日历 Theta 定义为在其他变量不变时 。在文档和测试中必须区分“期限方向导数”和“日历日时间流逝的敏感度”。
7.5 Greek 结果核对建议
采用 Spot 上下扰动 h 的中心差分,
至少扫描三个 h,注意过小的 h 会放大数值噪声。对 Digital、Barrier、American 早行权边界附近 Greeks,可出现不光滑或局部不稳定,应单独解释而不是用一个全球容差掩盖。
8. 核心优势
对业务人员来说,pdeSolver
的价值主要体现在四个方面:定价模型更容易编写,支持的产品类型较多,多个任务可以批量处理,并且在部分测试场景下具有较好的计算效率。
8.1 代码简洁
pdeSolver 已经封装了网格、PDE
系数、边界条件、时间推进和求解过程。使用时,用户主要需要说明三个问题:产品的收益如何计算、模型参数是什么、价格在边界上如何变化。有限差分离散、矩阵组装和线性方程求解等通用工作由
pdeSolver 完成。
典型流程比较固定:
构建网格 → 定义区域 → 设置 PDE 系数 → 设置 payoff 和边界
→ 选择求解方法 → 调用 pdeSolver → 读取价格和诊断
同一套流程可以用于欧式期权、American 期权、Barrier、Digital、Asian、Quanto、Local Volatility 以及部分二维模型。更换产品时,通常只需调整 payoff、系数或边界条件,不需要重新实现整个求解器。
这里的“简洁”不只是代码行数少,更重要的是业务逻辑和数值算法分开了。对于需要维护多个定价模型的团队,这可以减少重复代码,也方便后续修改、排查和复现。
8.2 产品丰富
pdeSolver
将模型描述和数值求解分开。网格、定义域、系数、边界、障碍条件和输出设置分别配置,产品逻辑不会和底层求解过程混在一起。
目前教程中的案例覆盖了几类常见场景:
-
标准期权:European Call、European Put、Digital;
-
带边界或路径特征的产品:Barrier、One Touch、Asian、Quanto;
-
更复杂的模型:Local Volatility、Heston、Basket、Exchange。
这些产品的金融含义不同,但可以使用相近的网格、系数、边界和求解流程。以 European Put 和 American Put 为例,两者可以共享大部分 PDE 设置,American Put 只需要增加早行权约束,并选择 PSOR 或 Penalty 方法。
对业务人员来说,这意味着新增产品时不必从零开始开发。已有案例可以作为模板,重点修改 payoff、参数和边界条件。
8.3 方法灵活
pdeSolver 提供多种时间推进和二维求解方法,包括 Explicit、Implicit
Euler、Crank–Nicolson、Theta、Douglas、CS、MCS 和 HV 等方法,同时支持 PSOR、Penalty、非均匀网格和
Rannacher smoothing。
不同产品遇到的计算问题并不一样,因此可以按产品特点选择方法:
| 遇到的情况 | 可以采用的处理方式 |
|---|---|
|
payoff 在执行价处不连续 |
使用 Rannacher smoothing 或局部网格加密 |
|
产品允许提前行权 |
使用 PSOR 或 Penalty |
|
模型包含两个风险因子的混合项 |
选择合适的 ADI 方法 |
|
波动率或其他系数随时间、空间变化 |
使用回调函数或预计算机制 |
因此,复杂产品通常可以通过调整系数、边界和求解设置来实现,而不是为每个产品单独开发一套数值程序。最终方法仍需要结合网格收敛和参考结果进行确认。
8.4 结果完整
pdeSolver 不只返回一个价格,还可以根据配置输出插值价格、详细结果、Delta、Gamma、Mixed
Gamma、诊断信息以及部分执行路径信息。
一次计算可以同时回答三个问题:
-
价格是多少:获取指定标的和参数下的定价结果;
-
风险如何变化:获取 Delta、Gamma 等 Greeks;
-
这次计算是否正常:查看收敛、迭代和其他诊断信息。
对于 Digital、Barrier 和 American 等数值上不够光滑的产品,诊断信息有助于发现网格、边界、迭代或收敛方面的问题。这样,业务结果和技术状态可以一起记录,而不是只保存一个价格。
需要注意,diagnostics.success=true 只能说明求解过程成功返回,不能代替独立的精度验证。
8.5 批量高效
多个定价案例可以在 DolphinDB 服务端集中执行。这样可以减少客户端与服务端之间的重复请求,也便于统一记录模型参数、网格设置、求解方法和诊断结果。
单个产品定价时,客户端发起一次请求即可;当需要同时计算多个产品、多个期限或多个参数组合时,可以把任务集中提交给服务端。本文示例中,16 个案例通过一次服务端请求完成,而逐案例调用需要发起 16 次请求。
批量执行的主要价值是减少客户端编排和通信管理,同时集中保存每个案例的参数、价格和诊断信息。实际耗时仍取决于网络、并发量、任务规模和部署方式。
8.6 计算高效
pdeSolver
可以结合向量化计算、precompute="all"、precompute="timeSlice"、JIT
和低延迟执行路径进行优化。简单来说,就是尽量减少重复计算,并让适合向量化的计算一次处理一批数据。
不同路径适用于不同类型的系数和工作流:
-
系数不随时间变化时,可以考虑预计算;
-
系数随时间变化时,可以使用
timeSlice; -
批量计算时,可以将多个案例放在服务端统一执行;
-
对适合的表达式,可以尝试 JIT 或向量化路径。
| 场景 | 空间节点 × 时间区间 | 纯 C++ (ms) | QuantLib (ms) | Python (ms) | DDB 非 JIT (ms) | DDB JIT (ms) | 对比 QuantLib | 对比 Python |
|---|---|---|---|---|---|---|---|---|
| BS 欧式看涨 | 401 × 400 | 1.473 | 3.534 | 12.590 | 4.584 | 4.366 | 1.24 倍耗时 | 加速比 2.88 倍 |
| Heston 随机波动率 | 121 × 61 × 120 | 48.812 | 80.162 | 509.309 | 98.908 | 96.271 | 1.20 倍耗时 | 加速比 5.29 倍 |
| BS 美式看跌 (PSOR) | 401 × 200 | 9.990 | 2.848 | 1092.076 | 25.533 | 15.449 | (算法不同) | 加速比 70.69 倍 |
| 现金或无价值数字看涨 | 401 × 200 | 0.804 | 3.150 | 7.474 | 2.370 | 2.284\* | 加速比 1.38 倍 | 加速比 3.27 倍 |
| 资产或无价值数字看涨 | 801 × 400 | 3.115 | 10.821 | 18.906 | 9.794 | 9.781\* | 加速比 1.11 倍 | 加速比 1.93 倍 |
| 下敲出单障碍看涨 | 601 × 400 | 2.323 | 8.172 | 16.800 | 7.267 | 7.107 | 加速比 1.15 倍 | 加速比 2.36 倍 |
| 上敲出单障碍看涨 | 601 × 400 | 2.367 | 8.181 | 16.434 | 7.314 | 7.161\* | 加速比 1.14 倍 | 加速比 2.30 倍 |
| 时空局部波动率 (Local Vol) | 301 × 150 | 1.491 | 7.835 | 8.401 | 4.181 | 4.946 | 加速比 1.87 倍 | 加速比 2.01 倍 |
| Quanto 固定汇率看涨 | 401 × 200 | 0.807 | 3.040 | 7.536 | 2.386 | 2.150 | 加速比 1.41 倍 | 加速比 3.51 倍 |
-
耗时均为同机单核 CPU6、Release 版本预热后的中位数,单位为毫秒。
-
对比列按每行加粗的较快 DDB 路径计算。耗时倍数为 DDB 耗时除以参照耗时,加速比为参照耗时除以 DDB 耗时。
-
DDB 非 JIT 列采用捕获闭包写法:符合条件的时空系数使用向量引擎,其余使用低延迟路径。JIT 列带
*表示部分初值回退到低延迟执行。JIT 的主要收益是将用户函数编译为机器码,降低每一次回调的执行成本;当重复回调已经被大量消除,剩余耗时主要集中在原本就由 C++ 实现的数值求解上,所以本次测试 JIT 对总耗时的进一步提升比较有限。 -
美式期权 QuantLib 采用逐步投影法,DDB 采用 PSOR,该行分别展示各自算法的耗时。
-
Heston 使用先导粗网格,价格精度仍需进一步做网格加密验证。
以上性能基于 DolphinDB V3.00.6.4 Linux
版本测试。不同实现的算法、精度、网格和执行路径可能不同,表中数据仅供参考。不同实现可能使用不同算法、网格和精度设置,因此不能据此得出
pdeSolver 在所有场景下都更快的结论。American Put、Heston
等案例仍应结合算法一致性和网格收敛结果一起判断。
本次测试 pdeSolver 相对于 QuantLib 性能提升不明显,原因是 QuantLib 本身是也是 C++
开发的。pdeSolver 的主要优势是模型表达和复用:从欧式期权扩展到数字期权、障碍期权、局部波动率或
Quanto 时,通常可以沿用求解流程,重点修改 payoff、PDE 系数或边界条件。QuantLib
也可以扩展,但使用现成引擎之外的模型往往需要进一步组合或开发其底层组件,灵活性不够。
8.7 易于扩展
由于模型参数、网格、边界和数值方法都可以显式记录,pdeSolver 案例比较适合做以下工作:
-
保存定价配置;
-
对比不同网格和求解方法;
-
扩展新的 payoff;
-
批量运行多个案例;
-
记录价格、Greeks 和诊断结果;
-
在不同 DolphinDB 版本和部署环境中重新测试。
实际使用时,可以先从一个已有案例开始,替换产品参数和 payoff,再逐步增加新的边界条件或风险因子。这样比从空白代码开始实现更容易控制修改范围。
这些优势主要体现在代码复用和工程组织上。具体数值精度和运行性能仍需要结合产品定义、参数、网格、版本和硬件环境单独确认。
9. 生产建议与故障诊断
生产排障时,顺序很重要:先查业务定义,再查 PDE 系数和边界,然后看数值收敛,最后才看输出和性能。JIT、并行和缓存只能优化已经通过正确性验收的模型,不能替代模型复核。
9.1 最小可审计运行记录
每次生产定价至少保存:产品与报价标识、模型版本、定价日、输入参数、domain/coeff/method/setting、DolphinDB
构建版本、价格与 Greeks、诊断告警、基准测试结果或上一次有效的回归版本。
9.2 常见故障定位顺序
| 现象 | 第一检查项 | 常见原因 | 处理方向 |
|---|---|---|---|
|
价格为负或明显越界 |
payoff、边界、反应项 |
折现符号或边界写错 |
回到 4.x 模型定义,检查初值和边界一致性 |
|
加密网格后价格变化很大 |
定义域和边界 |
|
分别扩大空间域和时间步,不要只加节点 |
|
PSOR 不收敛 |
|
时间步过大或障碍条件不一致 |
记录残差,调整求解器参数并重新验收 |
|
Greeks 异常 |
坐标和扰动步长 |
log 坐标导数未转换或 |
使用第 7 章链式法则和 bump 检查 |
|
JIT 没有加速 |
|
Server 不支持或函数不符合条件 |
检查 ABI Server、命名函数和 fallback |
|
批量结果不一致 |
参数表与插值点 |
客户端和服务端使用了不同参数 |
固定参数表,逐项比较价格和诊断 |
若无法判断属于哪一类,再按下列顺序执行:
-
先核对 payoff、现金流、货币与期限,再查 PDE 系数符号。
-
检查
initial与 Dirichlet 边界在 是否兼容。 -
检查坐标单位、log 变换、二阶和混合导数系数。
-
判断网格/时间步/截断域三类数值误差是否互相掩盖。
-
检查 obstacle 非收敛、ADI 诊断、输出字典结构。
-
最后才调整 JIT、precompute、内存和并行。
附录 A:案例包索引
下表只列出代表性案例入口,其余详见 ZIP 包根目录 README.md。
| 案例编号 | 主题 | ZIP 目录 |
|---|---|---|
|
FIN-009/010/011 |
BS Call、Put 与 parity |
|
|
FIN-012 |
Cash Digital |
|
|
FIN-020/033 |
American obstacle、Penalty |
|
|
FIN-034/046 |
Heston 与 ADI 方案 |
|
|
FIN-064/065 |
Quanto 与 Exchange |
|
附录 B:术语与结果速查
B.1 产品与定价
| 术语 | 一句话理解 | 在 pdeSolver 中对应 |
|---|---|---|
|
|
到期收益函数,是 PDE 倒推的初值。 |
|
|
剩余期限 |
从到期日 |
|
|
Put–Call parity |
欧式 Call 与 Put 在同一模型下的无套利关系,用于核对符号和边界。 |
4.1 节的 Call/Put 结果核对 |
|
吸收边界 |
触碰障碍后价值变为固定值,常见敲出产品中为 |
|
|
Pay at Hit / Pay at Maturity |
One Touch 触碰后立即支付或到期支付;支付时点会改变障碍边界。 |
4.6 节 |
B.2 PDE 与接口对象
| 术语 | 一句话理解 | 在 pdeSolver 中对应 |
|---|---|---|
|
initial |
到期时刻的终值条件,不是估值日价格。 |
|
|
boundary |
空间边界上的价格或导数条件,需要与 payoff 在 |
一维为左右端;二维顺序为 |
|
Dirichlet / Neumann |
Dirichlet 给定边界值,Neumann 给定边界导数。 |
常数/函数边界,或 |
|
mass/diff/conv/react/source |
一维 PDE 中质量、扩散、对流、反应和源项系数。 |
|
|
xx/yy/xy/x/y/u |
二维 PDE 中二阶、混合、一阶和反应项系数; |
|
|
callback |
用户定义、在求解过程中返回系数或边界值的函数。 |
函数型
|
B.3 数值方法
| 术语 | 一句话理解 | 在 pdeSolver 中对应 |
|---|---|---|
|
FDM |
有限差分方法,把连续 PDE 离散到空间和时间网格上。 |
|
|
Theta 格式 |
统一表示显式、隐式 Euler 和 Crank–Nicolson 的时间推进。 |
|
|
Rannacher |
payoff 不光滑时的初始平滑,用隐式半步抑制振荡。 |
|
|
obstacle |
美式期权提前行权下界,要求数值解不低于立即行权收益。 |
|
|
PSOR / penalty |
处理美式 obstacle 的两类数值路径:迭代投影或惩罚项近似。 |
|
|
ADI |
二维问题按方向拆分求解,适用于 Heston、Basket、Exchange 等。 |
|
B.4 输出、Greeks 与诊断
| 术语 | 一句话理解 | 在 pdeSolver 中对应 |
|---|---|---|
|
resultMode |
控制返回最终数值数组还是包含元信息、诊断和插值的详细字典。 |
|
|
interpAt/interpValue |
在指定状态点插值读取价格,避免手工找网格点。 |
|
|
Delta / Gamma / Mixed Gamma |
价格对状态变量的一阶、二阶和混合二阶敏感度。 |
|
|
log 坐标链式法则 |
log-price 坐标下的导数不是直接的价格坐标 Greek,需要转换。 |
Heston、Exchange 等使用 |
|
diagnostics.success |
表示求解流程成功返回,但不能代替价格精度验证。 |
|
B.5 误差与验收
| 术语 | 一句话理解 | 在 pdeSolver 中对应 |
|---|---|---|
|
模型误差 |
来自产品条款、市场模型和参数假设本身,不能靠加密网格消除。 |
先核对产品定义、payoff、测度和币种 |
|
截断误差 |
把无限或很大状态空间截成有限计算域带来的误差。 |
扫描 |
|
空间 / 时间误差 |
空间节点和时间步长不够细产生的离散误差。 |
分别加密 |
|
迭代误差 |
PSOR、penalty 等迭代求解器停止容差或未收敛带来的误差。 |
检查 residual、iterations、tolerance 和 warnings |
|
解析 / 数值参考值 |
有闭式解用解析公式;无闭式解用细网格、独立 PDE 或 MC 参考。 |
Benchmark、收敛表和回归脚本 |
附录 C:测试案例
本附件包含示例代码及可选的回归脚本。附件中的脚本用于复现教程案例;除页面明确说明的运行结果外,本文不声明所有案例均已通过目标部署环境下的服务端回归测试。
