二叉树期权定价

二叉树期权定价 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"])