二叉树期权定价
Cox-Ross-Rubinstein 美式期权定价
根据标的资产价格、行权价、无风险利率、到期时间、时间步长与波动率,使用 Cox-Ross-Rubinstein 二项式模型计算美式看涨与看跌期权的价格。支持可选的股息率、股息金额与除息日。
计算结果
点击「计算」查看输出
code
计算逻辑代码
以下为「ISDA国际通用」二叉树期权定价的 Python 代码示例(基于 QuantLib 的 Cox-Ross-Rubinstein 模型)。
# 依赖版本:
# python==3.9
# QuantLib==1.43
import math
import traceback
import QuantLib as ql
def qjquant_calu_binprice(
price: float,
strike: float,
rate: float,
time: float,
increment: float,
volatility: float,
flag: int,
dividend_rate: float = 0.0,
dividend: float = 0.0,
ex_div: float = 0.0,
):
try:
# ---------- 参数校验 ----------
if price <= 0 or strike <= 0:
return {"ok": False, "msg": "标的价格与行权价必须大于 0"}
if rate < 0:
return {"ok": False, "msg": "无风险利率不能为负"}
if time <= 0:
return {"ok": False, "msg": "到期时间必须大于 0"}
if increment <= 0 or increment > time:
return {"ok": False, "msg": "时间增量必须大于 0 且不超过到期时间"}
if volatility <= 0:
return {"ok": False, "msg": "波动率必须大于 0"}
if flag not in (0, 1):
return {"ok": False, "msg": "Flag 必须为 1(看涨)或 0(看跌)"}
# ---------- 步数与步长 ----------
steps = int(round(time / increment))
if steps < 1:
return {"ok": False, "msg": "时间增量过大,步数不足"}
dt = time / steps
# ---------- CRR 参数 ----------
u = math.exp(volatility * math.sqrt(dt))
d = 1.0 / u
p = (math.exp((rate - dividend_rate) * dt) - d) / (u - d)
disc = math.exp(-rate * dt)
# ---------- 资产价格矩阵(上三角) ----------
# asset[i][j]:第 i 步、向上走 j 次的节点资产价;j <= i
asset = [[None] * (steps + 1) for _ in range(steps + 1)]
for i in range(steps + 1):
for j in range(i + 1):
s = price * (u ** j) * (d ** (i - j))
# 离散股息:除息日在 (0, time] 内且第 i 步 >= 除息日才扣减
if dividend > 0 and 0 < ex_div <= time and i * dt >= ex_div:
s -= dividend * math.exp(-rate * (i * dt - ex_div))
asset[i][j] = round(s, 6)
# ---------- 期权价值矩阵(上三角,反向归纳) ----------
option = [[None] * (steps + 1) for _ in range(steps + 1)]
# 到期日 payoff
for j in range(steps + 1):
s = asset[steps][j]
if flag == 1:
option[steps][j] = round(max(s - strike, 0.0), 6)
else:
option[steps][j] = round(max(strike - s, 0.0), 6)
# 反向归纳(美式:每个节点取 max(继续持有, 立即行权))
for i in range(steps - 1, -1, -1):
for j in range(i + 1):
hold = disc * (p * option[i + 1][j + 1] +
(1 - p) * option[i + 1][j])
s = asset[i][j]
if flag == 1:
exercise = max(s - strike, 0.0)
else:
exercise = max(strike - s, 0.0)
option[i][j] = round(max(hold, exercise), 6)
manual_crr_value = option[0][0]
# ---------- 转置为 MATLAB 布局(列=步数,行=节点从下往上) ----------
asset_mat = [[0.0] * (steps + 1) for _ in range(steps + 1)]
option_mat = [[0.0] * (steps + 1) for _ in range(steps + 1)]
for i in range(steps + 1):
for j in range(i + 1):
row = i - j
asset_mat[row][i] = asset[i][j]
option_mat[row][i] = option[i][j]
# ---------- QuantLib CRR 交叉校验 ----------
ql_npv = None
try:
today = ql.Date.todaysDate()
ql.Settings.instance().evaluationDate = today
dc = ql.Actual365Fixed()
maturity = today + ql.Period(int(round(time * 365)), ql.Days)
spot_handle = ql.QuoteHandle(ql.SimpleQuote(price))
flat_ts = ql.YieldTermStructureHandle(
ql.FlatForward(today, rate, dc)
)
div_ts = ql.YieldTermStructureHandle(
ql.FlatForward(today, dividend_rate, dc)
)
flat_vol = ql.BlackVolTermStructureHandle(
ql.BlackConstantVol(today, ql.NullCalendar(), volatility, dc)
)
process = ql.BlackScholesMertonProcess(spot_handle, div_ts, flat_ts, flat_vol)
payoff = ql.PlainVanillaPayoff(
ql.Option.Call if flag == 1 else ql.Option.Put, strike
)
exercise = ql.AmericanExercise(today, maturity)
ql_option = ql.VanillaOption(payoff, exercise)
ql_option.setPricingEngine(ql.BinomialVanillaEngine(process, "crr", steps))
ql_npv = ql_option.NPV()
print(f"[binprice] 手动CRR={manual_crr_value:.6f}, "
f"QuantLib CRR={ql_npv:.6f}, steps={steps}, dt={dt:.8f}, "
f"u={u:.6f}, d={d:.6f}, p={p:.6f}, disc={disc:.6f}")
except Exception as ql_e:
print(f"[binprice] QuantLib 校验失败: {ql_e}")
# ---------- 返回 ----------
return {
"ok": True,
"asset_price": asset_mat,
"option_value": option_mat,
"manual_crr": round(manual_crr_value, 6),
"quantlib_crr": round(ql_npv, 6) if ql_npv is not None else None,
}
except Exception as e:
traceback.print_exc()
return {"ok": False, "msg": f"未知错误: {e}"}
# ============================================================
# 示例:复制本文件后直接运行
# ============================================================
if __name__ == "__main__":
print("=" * 60)
print("示例 1:美式看跌期权(平值附近,半年期限)")
print("=" * 60)
r1 = qjquant_calu_binprice(
price=52.0,
strike=50.0,
rate=0.10,
time=5 / 12,
increment=1 / 12,
volatility=0.40,
flag=0,
)
print("manual_crr =", r1["manual_crr"])
print("quantlib_crr =", r1["quantlib_crr"])
print("asset_price 维度 =", len(r1["asset_price"]), "x", len(r1["asset_price"][0]))
print()
print("=" * 60)
print("示例 2:美式看涨期权(含连续股息率)")
print("=" * 60)
r2 = qjquant_calu_binprice(
price=100.0,
strike=95.0,
rate=0.05,
time=1.0,
increment=0.01,
volatility=0.25,
flag=1,
dividend_rate=0.02,
)
print("manual_crr =", r2["manual_crr"])
print("quantlib_crr =", r2["quantlib_crr"])
print()
print("=" * 60)
print("示例 3:美式看跌期权(短到期,高波动)")
print("=" * 60)
r3 = qjquant_calu_binprice(
price=100.0,
strike=110.0,
rate=0.03,
time=0.25,
increment=0.01,
volatility=0.5,
flag=0,
)
print("manual_crr =", r3["manual_crr"])
print("quantlib_crr =", r3["quantlib_crr"])
print()
print("=" * 60)
print("示例 4:美式看跌期权(含离散股息)")
print("=" * 60)
r4 = qjquant_calu_binprice(
price=100.0,
strike=100.0,
rate=0.05,
time=1.0,
increment=0.01,
volatility=0.3,
flag=0,
dividend=3.0,
ex_div=0.5,
)
print("manual_crr =", r4["manual_crr"])
print("quantlib_crr =", r4["quantlib_crr"])