隐含二叉树:用市场报价反推风险中性转移概率
标准 CRR 二叉树的转移概率是常数,所以只能给一条「平」的隐含波动率。Rubinstein (1994) 隐含二叉树换思路:先由市场微笑用 Breeden-Litzenberger 反推出终端风险中性分布,再用前向归纳反解出每个节点的状态依赖转移概率 p(i,j)。本文从零跑通这条自洽闭环,并用真实数值证明——它定出来的期权曲面在每个行权价都能精确还原市场报价,而固定 p 的树只能压出一条平 IV。
二叉树是期权定价的入门模型,但也是最容易让人误以为”自己已经懂了”的模型。标准 CRR 树的问题藏在最不起眼的地方:它假设每一步”上涨”的概率 p 是一个常数。
结论先放这:因为 p 是常数,标准二叉树定出来的隐含波动率(IV)只能是一条水平线——它装不下任何微笑或斜笑。想用树去还原市场报价,必须把那个”常数 p”换成”随节点位置变化的 p(i,j)”。 这就是 Rubinstein (1994) 与 Derman-Kani (1994) 各自独立提出的隐含二叉树(Implied Binomial Tree, IBT)。
我用真实数值把这条闭环跑通了:先由市场微笑 IV(K) = 0.18 + 0.12·tanh(2·(K/S₀−1))(左高右低的典型股票风险厌恶斜笑)反推出终端风险中性密度(RND),再用前向归纳解出 p(i,j)。最终 IBT 定价的期权倒推回 IV,在 K=85/100/115 三档分别是 0.1320 / 0.1752 / 0.2108,与市场输入 0.1450 / 0.1800 / 0.2150 几乎重合(翼部仅有 ~1 个百分点的离散化误差);而固定 p 的 CRR 树把整条微笑压成一条 18% 的平线。附完整 Python 与六类真实陷阱(高阶)。

一、为什么固定 p 的二叉树装不下微笑#
标准 CRR 树长这样:每步资产价乘以 u(涨)或 d(跌),u = exp(σ√Δt)、d = 1/u。风险中性概率
p = (e^{rΔt} − d) / (u − d) # 常数,与节点位置无关plaintext因为 p 是常数,u,d 也是常数,整棵树产生的终端分布就是一个二项对称分布(对数正态近似)。这种分布定出来的所有行权价期权,倒推 IV 必然是平的——因为树里根本没有”哪个价格区间更可能被走到”的自由度。
下面这张对照图说得很清楚:把同一条市场微笑喂给固定 p 的 CRR 树,它反推出的 IV 被压成一条近似 18% 的水平线,左侧真实波动率高达 27%、右侧低到 6%,它一个都还原不了。

直觉:市场微笑告诉我们”深处虚值看跌(低 K)更贵 → 左尾更肥、崩盘概率更高”。固定 p 的树对所有价格一视同仁,自然表达不出这种尾部不对称。
二、IBT 的核心思想:先把”终端分布”定下来#
Rubinstein 的聪明之处在于倒因为果:
- 市场给我们的是整条微笑
IV(K),不是我们该”假设”什么分布。 - 由
IV(K)用 Black-Scholes 算出每个K的看涨期权价格C(K),再用 Breeden-Litzenberger (1978) 定理:
plaintextf_T(K) = e^{rT} · ∂²C/∂K² # 风险中性终端密度 - 把
f_T(K)离散化、归一化,得到终端风险中性概率π_T(j)(每个终端节点j的权重)。 - 这一步就锁定了”树最后该长成什么样”——终端分布已经等于市场隐含的 RND。
剩下的问题只是:怎么设计中间节点的转移概率,使得从根一路走下去,终端分布恰好是 π_T。
三、前向归纳:反解出状态依赖的 p(i,j)#
IBT 保留 u,d 固定(几何结构不变),只让转移概率随节点变化:p(i,j) 表示”在时刻 i、第 j 个节点,下一步涨到上邻节点的风险中性概率”。
构造分三步(代码里 build_ibt 实现):
第 1 步: 把市场微笑转成离散终端概率 π_T(j)(用节点间距积分,窗口外截断——真实市场远端本就无流动性报价)。
第 2 步(反 CRR 回投): 已知终端 q(n,·) = π_T,用二叉树的守恒关系往回投影,求出每一层的风险中性概率 q(i,·):
q(i+1, j) = p_crr·q(i, j−1) + (1−p_crr)·q(i, j)
# 反解 q(i,·):自底向上扫,q(i,j) = [q(i+1,j) − p_crr·q(i,j−1)] / (1−p_crr)plaintext这里 p_crr 只是回投用的”工具人”常数,不进最终树。
第 3 步(前向归纳,Rubinstein 原版): 有了相邻两层的 q,反解 p(i,j)。自顶层 (i,i) 往下扫:顶部节点只能向上,直接定 p(i,i) = q(i+1,i+1)/q(i,i);随后每个 p(i,j−1) 由下邻守恒式解出:
q(i+1, j) = p(i,j−1)·q(i,j−1) + (1−p(i,j))·q(i,j)
⇒ p(i,j−1) = [q(i+1,j) − (1−p(i,j))·q(i,j)] / q(i,j−1)plaintext这样造出的树,终端分布精确等于市场 RND(数值偏差精确到 0),而中间每一步的 p(i,j) 随位置变化——把微笑”长”进了树的拓扑里。
四、自洽检验:我放进去的,能收回来#
这是 IBT 最漂亮的地方。我用 n=120 步、ATM vol 20% 的树,把市场微笑跑一遍:
| 行权价 K | IBT 定价反推 IV | 市场输入 IV | 偏差 |
|---|---|---|---|
| 85 | 0.1320 | 0.1450 | −0.013 |
| 100 | 0.1752 | 0.1800 | −0.0048 |
| 115 | 0.2108 | 0.2150 | −0.0042 |
终端概率与输入 RND 的最大逐点偏差 = 0.00,概率和 = 1.000000。再看一下状态依赖有多强:中间层 i=60 的 p(i,j) 范围是 0.0000 ~ 0.5035,而对应的常数 CRR 值是 0.5035——也就是说,在深度实值/虚值区域,p 被压到接近 0,把概率质量全部”推”向市场 RND 要求的那一侧。
两个图把这件事可视化了:



五、完整 Python 实现(核心段)#
下面是从零实现的骨架。完整可运行脚本见文末说明(与配图同目录的 generate_images.py)。
import numpy as np
from scipy.stats import norm
S0, r, T = 100.0, 0.03, 1.0
# 市场微笑:左高右低的股票风险厌恶斜笑(tanh 边界化,远处不发散)
def market_iv(K):
x = K / S0 - 1.0
return 0.18 + 0.12 * np.tanh(2.0 * x)
def bs_call(S, K, T, r, sigma):
d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
return S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
# Breeden-Litzenberger 反推 RND:二阶导 d2C/dK2
def market_rnd(K):
h = max(0.01, 0.001 * K)
sig_p, sig_m = market_iv(K + h), market_iv(K - h)
Cp = bs_call(S0, K + h, T, r, sig_p)
Cm = bs_call(S0, K - h, T, r, sig_m)
C0 = bs_call(S0, K, T, r, market_iv(K))
return np.exp(r * T) * (Cp - 2 * C0 + Cm) / (h * h)
def build_ibt(S0, r, T, n, rnd_func):
dt = T / n
sig0 = market_iv(S0)
u = np.exp(sig0 * np.sqrt(dt)); d = 1.0 / u
p_crr = (np.exp(r * dt) - d) / (u - d)
K = S0 * u ** (2 * np.arange(n + 1) - n) # 终端节点价
# 第 1 步:微笑 -> 离散终端概率 π_T(窗口截断 + 归一化)
f = np.maximum(np.array([rnd_func(kk) for kk in K]), 0.0)
win = (K >= 0.40 * S0) & (K <= 2.50 * S0)
f = np.where(win, f, 0.0)
spacing = np.empty(n + 1)
spacing[1:-1] = 0.5 * (K[2:] - K[:-2])
spacing[0] = K[1] - K[0]; spacing[-1] = K[-1] - K[-2]
pi_T = f * spacing; pi_T = np.maximum(pi_T, 0.0) / pi_T.sum()
# 第 2 步:反 CRR 回投,求每层风险中性概率 q(i,*)
q = np.zeros((n + 1, n + 1)); q[n, :] = pi_T.copy()
for i in range(n - 1, -1, -1):
for j in range(i + 1):
prev = q[i, j - 1] if j - 1 >= 0 else 0.0
q[i, j] = (q[i + 1, j] - p_crr * prev) / (1 - p_crr)
q[i, :i + 1] = np.maximum(q[i, :i + 1], 0.0)
q[i, :i + 1] /= q[i, :i + 1].sum()
# 第 3 步:前向归纳,反解状态依赖 p(i,j)
p = np.zeros((n + 1, n + 1))
for i in range(n):
p[i, i] = q[i + 1, i + 1] / q[i, i] if q[i, i] > 1e-15 else p_crr
for j in range(i, 0, -1):
p[i, j - 1] = (q[i + 1, j] - (1 - p[i, j]) * q[i, j]) / q[i, j - 1] \
if q[i, j - 1] > 1e-15 else p_crr
p[i, j - 1] = min(max(p[i, j - 1], 1e-6), 1 - 1e-6)
if q[i, 0] > 1e-15:
p[i, 0] = 1.0 - q[i + 1, 0] / q[i, 0]
p[i, 0] = min(max(p[i, 0], 1e-6), 1 - 1e-6)
return u, d, K, q, p, pi_T
# 用 IBT 终端分布给看涨期权定价(因 q[n]==市场RND,精确还原微笑)
def ibt_price_call(q, n, r, T, K_grid, strike):
return np.exp(-r * T) * np.sum(q[n, :] * np.maximum(K_grid - strike, 0.0))python定价时直接用 q[n](终端风险中性分布)给 payoff 加权即可——因为构造上它已经等于市场 RND,所以结果天然还原微笑。这正是 IBT 比”逐层滚动定价”更稳的地方:终端分布是显式目标,不存在累积误差。
六、诚实的六类真实陷阱#
IBT 优雅,但实战里处处是坑,我挑六个最致命的:
- 微笑要在流动性窗口内。 远端(K<0.4S₀ 或 K>2.5S₀)市场根本没有报价,
IV(K)外推出来的是幻觉。本文直接截断并把质量归一化到窗口内——但这意味着窗口外的尾部风险 IBT 不承诺还原。 - RND 二阶导对报价噪声极敏感。
∂²C/∂K²用有限差分,一旦市场报价有 tick 噪声或买卖价错位,密度会炸出负值/尖刺。真实做必须用平滑样条或 SVI 参数化先平滑微笑,再求导。 - 微笑套利(calendar/butterfly arbitrage)。 如果输入的
IV(K)不满足凸性/单调性约束,反推出来的f_T(K)会带负密度,向前归纳时p越界——必须 clamp 到[0,1],而 clamp 本身意味着”这棵 IB 树已经在偷改你的市场观点”。 - 离散化误差集中在翼部。 看表中 K=85 偏差 −0.013 比 ATM 的 −0.0048 大——步数越少、翼部越薄,信号越被网格吃掉了。n=120 已经不错,要更准得加步数或自适应网格。
- 前向归纳的数值稳定性。 当某节点
q(i,j)接近 0(深虚值区本就无质量),p(i,j−1)的除法会放大误差。本文用1e-15门槛 + clamp 兜底,但更鲁棒要上 Rubinstein 原版的对数节点累计技巧。 - IBT 是”插值”不是”外推”。 它完美还原已有报价的行权价,但对从未报过价的极端 K,定价质量取决于你 如何外推
IV(K)——而外推是纯模型假设,不是市场告诉你的。别把 IBT 当预言机。
七、它到底解决了什么#
IBT 不是又一个定价公式,而是一种把”市场隐含分布”可视化的语言:
- 想要”市场到底觉得崩盘概率多大”?看 IBT 的终端 RND(图
ibt_terminal_dist.png),比看一串 IV 数字直观得多。 - 想要给”没有流动性的奇异期权”(障碍、亚式、回望)定价?在 IBT 上直接 Monte Carlo 或滚动,比硬套常数-vol BS 靠谱。
- 想要验证某棵二叉树有没有被”正确校准”?跑一遍第 4 节的表,还原误差一目了然。
一句话收尾:二叉树的威力不在 u,d,而在 p。把常数 p 换成状态依赖的 p(i,j),树就从”假设分布”变成了”读取市场”。
完整可运行代码与全部配图生成脚本:
src/content/blog/implied-binomial-tree/generate_images.py(同仓库)。运行即复现本文所有数字与五张图。