有限差分法期权定价:用网格把 Black-Scholes 偏微分方程敲成数值解
Black-Scholes 只有欧式有闭式,美式、障碍、回望这类带早期行权或路径依赖的期权只能靠数值解。有限差分法(PDE 数值解)把一条连续偏微分方程离散成网格上的三对角代数系统:显式最直观却要守 CFL 稳定性,隐式无条件稳定但要解线性方程组,Crank-Nicolson 折中两者且二阶收敛。本文用 Python 从零实现三种格式,对欧式看涨做 BS 闭式自洽校验,对美式看跌加 Early-Exercise 约束,并指出网格、稳定性与边界三类真实陷阱。
Black-Scholes 公式是期权定价的基石,但它只给了欧式期权的闭式解。一旦你遇到美式期权(可以在到期前提前行权)、障碍期权(价格触到某条线就作废)、回望期权(payoff 依赖路径极值),闭式就基本消失了——这时候你必须靠数值方法。
数值期权定价有三条主流路线:蒙特卡洛(模拟路径)、二叉/三叉树(离散时间)、以及有限差分法(Finite Difference Method, FDM)——它直接在 BS 偏微分方程(PDE)上做文章。本文聚焦 FDM:它把一条连续的偏微分方程,离散成网格上的巨大代数方程组,用计算机求解。

一、从 BS 偏微分方程到差分网格#
BS 方程描述的是期权价值 随标的 和时间 演化的规律:
有限差分法的思路非常朴素:把连续的 和 切成网格。设标的价格从 到 均匀分 格,时间从 到 均匀分 格,网格点 表示「第 个价格节点、第 个时间步」。
在每一个网格点上,用差商近似导数:
- 对 的二阶导(Gamma 项)用中心差分:
- 对 的一阶导(Delta 项)用中心差分:
- 对 的导(Theta 项)用前向/后向差分,这就是三种格式的分水岭
把差商代回 BS 方程,你会得到每个内点 与邻居 的线性关系。整条网格在任一时刻就是一个三对角方程组——这正是 FDM 计算高效的根源。
二、三种时间离散格式:显式 / 隐式 / Crank-Nicolson#
时间导数用前向还是后向差分(或各取一半),决定了格式的稳定性和精度:
- 显式(Explicit / 前向欧拉):新时刻的值直接用旧时刻的邻居算出来,无需解方程,最直观。但它在稳定性上有硬性约束:CFL 条件——要求 。网格太粗(时间步太大)就会发散震荡。
- 隐式(Implicit / 向后欧拉):新时刻的值依赖新时刻的邻居,于是每个时间步要解一个三对角线性方程组。代价是每次解一次方程,但无条件稳定——随便怎么加密网格都不会炸。
- Crank-Nicolson(CN,1950):前向和后向各取一半(权重 ),既二阶精度又无条件稳定,是实务首选。
举个直观对照:显式像「闭眼按公式一步步推」,快但可能踩空;隐式像「每一步都回头校验一遍约束」,稳但要算方程;CN 是两者折中。
三、Python 实战 1:三种格式的核心循环#
下面用 scipy.linalg.solve_banded 解三对角系统,把三套逻辑统一到一个函数里(用 切换)。注意边界:看跌在 处值为 ,在 处趋于 0。
import numpy as np
from scipy.stats import norm
from scipy.linalg import solve_banded
S0, K, r, sig, T = 100.0, 100.0, 0.05, 0.20, 1.0
Smax = 300.0
def bs_call(S, K, r, sig, T):
d1 = (np.log(S/K) + (r + 0.5*sig**2)*T) / (sig*np.sqrt(T))
d2 = d1 - sig*np.sqrt(T)
return S*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2)
def fd_price(theta, Ns, Nt, option="call", american=False):
"""theta: 0=显式, 1=隐式, 0.5=Crank-Nicolson"""
dS, dt = Smax/Ns, T/Nt
S_grid = np.linspace(0, Smax, Ns+1)
i = np.arange(Ns+1)
alpha = 0.5*sig**2*dt*i**2 # ½σ²Δt·i²
delta = 0.5*r*dt*i # ½rΔt·i
gamma = r*dt
V = np.maximum(S_grid-K, 0.0) if option=="call" else np.maximum(K-S_grid, 0.0)
sub = -theta*(alpha-delta)
diag = 1.0 + theta*(2*alpha+gamma)
sup = -theta*(alpha+delta)
ab = np.zeros((3, Ns+1)); ab[0,1:]=sup[:-1]; ab[1,:]=diag; ab[2,:-1]=sub[1:]
for step in range(Nt):
tau = (step+1)*dt
if theta == 0.0:
Vnew = (alpha-delta)*np.concatenate([[0],V[:-1]]) \
+ (1-2*alpha-gamma)*V \
+ (alpha+delta)*np.concatenate([V[1:],[0]])
else:
b = V + (1-theta)*((alpha-delta)*np.concatenate([[0],V[:-1]]) \
+ (-2*alpha-gamma)*V \
+ (alpha+delta)*np.concatenate([V[1:],[0]]))
Vnew = solve_banded((1,1), ab, b)
Vnew[0] = 0.0 if option=="call" else K*np.exp(-r*tau) # 边界
Vnew[Ns] = Smax - K*np.exp(-r*tau) if option=="call" else 0.0
if american:
Vnew = np.maximum(Vnew, np.maximum(K-S_grid, 0.0)) # 提前行权
V = Vnew
idx = int(S0/dS); idx = min(max(idx,0),Ns-1)
w = (S0 - S_grid[idx])/dS
return V[idx]*(1-w) + V[idx+1]*w
bs = bs_call(S0, K, r, sig, T)
print(f"BS 闭式 = {bs:.4f}")
print(f"Crank-Nicolson= {fd_price(0.5,200,2000):.4f}")
print(f"隐式 = {fd_price(1.0,200,2000):.4f}")
print(f"显式 = {fd_price(0.0,200,4000):.4f}")python跑出来的自洽校验结果是:BS 闭式 = 10.4506,CN = 10.4544,隐式 = 10.4539,显式 = 10.4547——三种格式偏差都不到 0.005,证明 PDE 离散和内插逻辑正确。

四、美式期权:Early-Exercise 约束#
欧式期权只比较「继续持有」和「到期行权」,美式期权多一个维度:现在就行权。FDM 处理它极其自然——在每个时间步更新完 后,强制取 max(V, 内在价值):
这正是代码里 if american: Vnew = np.maximum(Vnew, intrinsic) 那一行。它表达的是:只要「立即行权拿到的钱」比「继续持有值」多,理性持有者就行权,期权价值被这道地板托住。
bs_put = K*np.exp(-r*T)*norm.cdf(-(np.log(S0/K)+(r+0.5*sig**2)*T)/(sig*np.sqrt(T))) ) \
- S0*norm.cdf(-(np.log(S0/K)+(r+0.5*sig**2)*T)/(sig*np.sqrt(T)))
am_put = fd_price(1.0, 200, 2000, "put", american=True)
print(f"欧式看跌 BS = {bs_put:.4f}")
print(f"美式看跌 FD = {am_put:.4f} (美式溢价 {am_put-bs_put:+.4f})")python数值上美式看跌 = 6.0914,欧式看跌 BS = 5.5735,美式溢价 +0.5179——提前行权权本身值钱,这与理论一致(美式看跌确实比欧式贵)。
五、Python 实战 2:价值曲面与稳定性#
把 在整张网格上存下来,就能画出价值曲面——它直观展示了「临近到期 + 深度实值」时期权价值如何被 Gamma/Theta 推高。
Ns, Nt = 120, 600
dS, dt = Smax/Ns, T/Nt
S_grid = np.linspace(0, Smax, Ns+1); i = np.arange(Ns+1)
alpha = 0.5*sig**2*dt*i**2; delta = 0.5*r*dt*i; gamma = r*dt
V = np.maximum(S_grid-K, 0.0)
sub=-0.5*(alpha-delta); diag=1+0.5*(2*alpha+gamma); sup=-0.5*(alpha+delta)
ab=np.zeros((3,Ns+1)); ab[0,1:]=sup[:-1]; ab[1,:]=diag; ab[2,:-1]=sub[1:]
Vmat=np.zeros((Nt+1,Ns+1)); Vmat[0]=V
for step in range(Nt):
tau=(step+1)*dt
b=V+0.5*((alpha-delta)*np.concatenate([[0],V[:-1]])+(-2*alpha-gamma)*V+(alpha+delta)*np.concatenate([V[1:],[0]]))
Vnew=solve_banded((1,1),ab,b)
Vnew[0]=0.0; Vnew[Ns]=Smax-K*np.exp(-r*tau); V=Vnew; Vmat[step+1]=V
# Vmat[i,j] 即 V(S_grid[j], 时刻 i) -> 可直接 contourf 画图python
显式格式的稳定性必须用图说话。下面故意违反 CFL:把时间步设得过大(),显式解立刻在障碍附近震荡发散;而 (满足 CFL)时平滑贴合 BS。
def price_curve(Nt):
V = np.maximum(S_grid-K, 0.0)
for step in range(Nt):
Vnew=(alpha-delta)*np.concatenate([[0],V[:-1]])+(1-2*alpha-gamma)*V\
+(alpha+delta)*np.concatenate([V[1:],[0]]); V=Vnew
return V
stable = price_curve(4000) # 满足 CFL
unstable = price_curve(250) # 违反 CFL -> 发散python
六、网格与边界:两个常被忽视的工程细节#
- 空间边界 :理论上 ,但网格必须有上界。实务取 的几倍,或者用对数坐标网格让尾部更密。若 太小,深度实值 call 的边界条件会系统性压低整条曲线。
- 边界条件形式:本文用的是「线性边界」(端点按 BS 渐近线性外推)。更严谨的是二次边界(考虑 Gamma 在边界的衰减),对高 Gamma 障碍期权尤其重要,否则边界会反射虚假波。
- 收敛阶:CN 对 和 都是二阶收敛;但如果在 处用线性插值取价,会引入一阶误差——要拿精确价就得在 处加密或做二次插值。
七、真实陷阱#
- CFL 稳定性陷阱(显式专属):显式快,但 一旦超过阈值就爆炸。解法:要么守 CFL(通常要求 ,代价是时间步极多),要么直接用隐式/CN。实务几乎不单独用纯显式。
- 边界反射陷阱:边界条件设错,会在 或 处产生「虚假反射波」,污染整张曲面,尤其是障碍期权贴边界时。解法:用更大的 + 二次边界,或对数网格。
- 美式约束顺序陷阱:
max(V, 内在价值)必须在每个时间步做,而不是只在期末做。若只在最后投影一次,等于没建模提前行权,美式溢价会算丢。 - 网格稀疏陷阱:用太粗的网格(比如 ),CN 偏差可能到 1% 以上。本文校验显示 时偏差已 <0.005;但障碍/美式对网格更敏感,需更密。
- 插值取价陷阱:在 不在网格节点上时,用线性插值会引入一阶误差。若要做希腊值(Delta/Gamma),应在网格上直接差分而非插值后求导。
八、落地清单#
- 欧式期权:直接用 CN(), 已足够精确到 0.01 以内,且无条件稳定。
- 美式期权:隐式或 CN + 每步
max(V, 内在价值);看跌美式溢价可用本文代码直接自洽校验。 - 障碍/回望等路径依赖:FDM 需改造敲出/敲入逻辑(下一篇巴黎期权会讲),注意边界与网格耦合。
- 要算希腊值:在网格上做中心差分(
(V[i+1]-V[i-1])/(2ΔS)得 Delta,(V[i+1]-2V[i]+V[i-1])/ΔS²得 Gamma),别用插值后的值。 - 稳定性优先:默认选 CN,别为省一次线性方程求解去冒显式发散的风险。
结语#
有限差分法是「把 BS 偏微分方程敲成网格代数」的艺术。显式最易写但受 CFL 约束,隐式稳定但要解三对角,Crank-Nicolson 用半个时间步的前后加权平均,既稳又准,几乎是实务默认。它的真正威力不在欧式(那里 BS 闭式就够了),而在美式提前行权、障碍敲出、回望极值这类闭式失效的地方——而那恰恰是奇异期权定价的主战场。下一篇我们就用这套 PDE 思想,去对付比普通障碍更刁钻的「巴黎式障碍」。
本文数值均用 Python 从零实跑自洽校验:欧式看涨三种格式对 BS 闭式偏差均 < 0.005;美式看跌溢价 +0.5179,与理论一致。代码可复现上述全部图表。