FAILURE MAP
← Case archive

FA-61521 / Options payoff and settlement / Open access

Cox-Ross-Rubinstein American option tree: the growth factor uses simple interest · case 01

Tree prices drift from the stated continuous-compounding model.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

p uses 1 + r*dt instead of exp(r*dt).

VERIFIED REPAIR

Use p = (exp(r*dt) - d)/(u - d).

Unsuccessful approach: Using the full maturity T in the growth factor inflates p.

Case contract

Inputs kind, spot S, strike K, rate r, volatility sigma, maturity T in years and steps. dt=T/steps, u=exp(sigma*sqrt(dt)), d=1/u, p=(exp(r*dt)-d)/(u-d), disc=exp(-r*dt). Roll back from terminal payoffs; at each node take max(continuation, immediate exercise at that node's price). Return the root rounded to 6.

Why this case matters

Option expiry, exercise and settlement engines move cash and shares; a wrong branch misstates obligations.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(kind, S, K, r, sigma, T, steps):
    dt = T / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (1 + r * dt - d) / (u - d)
    disc = math.exp(-r * dt)
    def pay(s):
        return max(s - K, 0.0) if kind == 'C' else max(K - s, 0.0)
    vals = [pay(S * u ** j * d ** (steps - j)) for j in range(steps + 1)]
    for i in range(steps - 1, -1, -1):
        vals = [max(disc * (p * vals[j + 1] + (1 - p) * vals[j]), pay(S * u ** j * d ** (i - j))) for j in range(i + 1)]
    return round(vals[0], 6)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression risk-neutral probability 1', ['P', 95.0, 100.0, 0.01, 0.15, 0.5, 3], 6.658835], ['regression risk-neutral probability 2', ['C', 110.0, 90.0, 0.08, 0.25, 0.5, 12], 24.159122], ['partial repair probe 1', ['P', 110.0, 90.0, 0.05, 0.15, 0.25, 12], 0.003613], ['normal control 1', ['P', 110.0, 100.0, 0.0, 0.4, 0.25, 3], 4.073107], ['normal control 2', ['P', 100.0, 90.0, 0.0, 0.25, 0.25, 5], 1.438421], ['normal control 3', ['P', 95.0, 105.0, 0.08, 0.15, 0.5, 3], 10.0], ['normal control 4', ['P', 95.0, 100.0, 0.0, 0.15, 1.0, 12], 8.771401], ['normal control 5', ['C', 110.0, 105.0, 0.0, 0.25, 0.5, 25], 10.332348]], [['regression risk-neutral probability 1', ['P', 80.0, 90.0, 0.08, 0.25, 2.0, 3], 11.993101], ['regression risk-neutral probability 2', ['C', 80.0, 105.0, 0.05, 0.4, 1.0, 25], 6.416395], ['partial repair probe 1', ['C', 80.0, 100.0, 0.01, 0.15, 0.25, 25], 0.002014], ['normal control 1', ['P', 110.0, 105.0, 0.0, 0.15, 0.5, 25], 2.513171], ['normal control 2', ['P', 80.0, 100.0, 0.0, 0.15, 0.25, 5], 20.0], ['normal control 3', ['P', 95.0, 90.0, 0.0, 0.25, 2.0, 5], 11.126274], ['normal control 4', ['P', 110.0, 90.0, 0.0, 0.15, 0.25, 12], 0.006049], ['normal control 5', ['P', 80.0, 105.0, 0.01, 0.15, 0.5, 12], 25.0]], [['regression risk-neutral probability 1', ['C', 110.0, 90.0, 0.08, 0.25, 0.5, 12], 24.159122], ['regression risk-neutral probability 2', ['P', 110.0, 90.0, 0.01, 0.15, 2.0, 3], 1.727866], ['partial repair probe 1', ['C', 80.0, 105.0, 0.01, 0.15, 0.5, 12], 0.01299], ['partial repair probe 2', ['P', 110.0, 90.0, 0.05, 0.15, 0.25, 25], 0.0038], ['normal control 1', ['C', 100.0, 100.0, 0.0, 0.25, 0.5, 12], 6.898148], ['normal control 2', ['P', 80.0, 105.0, 0.01, 0.15, 1.0, 25], 25.0], ['normal control 3', ['P', 80.0, 100.0, 0.0, 0.4, 1.0, 3], 25.451176], ['normal control 4', ['C', 100.0, 100.0, 0.0, 0.25, 2.0, 12], 13.742666]], [['regression risk-neutral probability 1', ['P', 110.0, 100.0, 0.05, 0.15, 1.0, 25], 1.469382], ['regression risk-neutral probability 2', ['P', 100.0, 100.0, 0.01, 0.4, 0.5, 5], 11.583913], ['partial repair probe 1', ['P', 110.0, 90.0, 0.01, 0.15, 0.25, 25], 0.005888], ['partial repair probe 2', ['C', 80.0, 100.0, 0.05, 0.15, 0.25, 12], 0.001397], ['normal control 1', ['P', 100.0, 105.0, 0.0, 0.15, 1.0, 25], 8.986901], ['normal control 2', ['C', 100.0, 105.0, 0.0, 0.4, 2.0, 25], 20.54431], ['normal control 3', ['P', 95.0, 100.0, 0.0, 0.25, 0.25, 25], 7.804053], ['normal control 4', ['P', 80.0, 100.0, 0.0, 0.4, 1.0, 12], 26.184178]], [['regression risk-neutral probability 1', ['P', 100.0, 100.0, 0.08, 0.15, 2.0, 12], 4.019753], ['regression risk-neutral probability 2', ['P', 95.0, 90.0, 0.01, 0.15, 0.5, 5], 1.671132], ['partial repair probe 1', ['C', 80.0, 100.0, 0.05, 0.15, 0.25, 12], 0.001397], ['partial repair probe 2', ['P', 110.0, 90.0, 0.05, 0.15, 0.25, 25], 0.0038], ['normal control 1', ['P', 95.0, 105.0, 0.08, 0.15, 1.0, 5], 10.0], ['normal control 2', ['P', 80.0, 100.0, 0.0, 0.25, 0.25, 12], 20.134901], ['normal control 3', ['P', 95.0, 105.0, 0.0, 0.4, 0.25, 5], 13.698971], ['normal control 4', ['C', 100.0, 100.0, 0.0, 0.15, 1.0, 25], 6.038372]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression risk-neutral probability 16.6589866.658835Failed
regression risk-neutral probability 224.15230824.159122Failed
partial repair probe 10.0036130.003613Passed
normal control 14.0731074.073107Passed
normal control 21.4384211.438421Passed
normal control 310.010.0Passed
normal control 48.7714018.771401Passed
normal control 510.33234810.332348Passed

SHA-256 / 06ed991952ff4bb05e3e10521b0c9fc4a4259d8800baa89550efd6ea34f5af84

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(kind, S, K, r, sigma, T, steps):
    dt = T / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(r * T) - d) / (u - d)
    disc = math.exp(-r * dt)
    def pay(s):
        return max(s - K, 0.0) if kind == 'C' else max(K - s, 0.0)
    vals = [pay(S * u ** j * d ** (steps - j)) for j in range(steps + 1)]
    for i in range(steps - 1, -1, -1):
        vals = [max(disc * (p * vals[j + 1] + (1 - p) * vals[j]), pay(S * u ** j * d ** (i - j))) for j in range(i + 1)]
    return round(vals[0], 6)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression risk-neutral probability 1', ['P', 95.0, 100.0, 0.01, 0.15, 0.5, 3], 6.658835], ['regression risk-neutral probability 2', ['C', 110.0, 90.0, 0.08, 0.25, 0.5, 12], 24.159122], ['partial repair probe 1', ['P', 110.0, 90.0, 0.05, 0.15, 0.25, 12], 0.003613], ['normal control 1', ['P', 110.0, 100.0, 0.0, 0.4, 0.25, 3], 4.073107], ['normal control 2', ['P', 100.0, 90.0, 0.0, 0.25, 0.25, 5], 1.438421], ['normal control 3', ['P', 95.0, 105.0, 0.08, 0.15, 0.5, 3], 10.0], ['normal control 4', ['P', 95.0, 100.0, 0.0, 0.15, 1.0, 12], 8.771401], ['normal control 5', ['C', 110.0, 105.0, 0.0, 0.25, 0.5, 25], 10.332348]], [['regression risk-neutral probability 1', ['P', 80.0, 90.0, 0.08, 0.25, 2.0, 3], 11.993101], ['regression risk-neutral probability 2', ['C', 80.0, 105.0, 0.05, 0.4, 1.0, 25], 6.416395], ['partial repair probe 1', ['C', 80.0, 100.0, 0.01, 0.15, 0.25, 25], 0.002014], ['normal control 1', ['P', 110.0, 105.0, 0.0, 0.15, 0.5, 25], 2.513171], ['normal control 2', ['P', 80.0, 100.0, 0.0, 0.15, 0.25, 5], 20.0], ['normal control 3', ['P', 95.0, 90.0, 0.0, 0.25, 2.0, 5], 11.126274], ['normal control 4', ['P', 110.0, 90.0, 0.0, 0.15, 0.25, 12], 0.006049], ['normal control 5', ['P', 80.0, 105.0, 0.01, 0.15, 0.5, 12], 25.0]], [['regression risk-neutral probability 1', ['C', 110.0, 90.0, 0.08, 0.25, 0.5, 12], 24.159122], ['regression risk-neutral probability 2', ['P', 110.0, 90.0, 0.01, 0.15, 2.0, 3], 1.727866], ['partial repair probe 1', ['C', 80.0, 105.0, 0.01, 0.15, 0.5, 12], 0.01299], ['partial repair probe 2', ['P', 110.0, 90.0, 0.05, 0.15, 0.25, 25], 0.0038], ['normal control 1', ['C', 100.0, 100.0, 0.0, 0.25, 0.5, 12], 6.898148], ['normal control 2', ['P', 80.0, 105.0, 0.01, 0.15, 1.0, 25], 25.0], ['normal control 3', ['P', 80.0, 100.0, 0.0, 0.4, 1.0, 3], 25.451176], ['normal control 4', ['C', 100.0, 100.0, 0.0, 0.25, 2.0, 12], 13.742666]], [['regression risk-neutral probability 1', ['P', 110.0, 100.0, 0.05, 0.15, 1.0, 25], 1.469382], ['regression risk-neutral probability 2', ['P', 100.0, 100.0, 0.01, 0.4, 0.5, 5], 11.583913], ['partial repair probe 1', ['P', 110.0, 90.0, 0.01, 0.15, 0.25, 25], 0.005888], ['partial repair probe 2', ['C', 80.0, 100.0, 0.05, 0.15, 0.25, 12], 0.001397], ['normal control 1', ['P', 100.0, 105.0, 0.0, 0.15, 1.0, 25], 8.986901], ['normal control 2', ['C', 100.0, 105.0, 0.0, 0.4, 2.0, 25], 20.54431], ['normal control 3', ['P', 95.0, 100.0, 0.0, 0.25, 0.25, 25], 7.804053], ['normal control 4', ['P', 80.0, 100.0, 0.0, 0.4, 1.0, 12], 26.184178]], [['regression risk-neutral probability 1', ['P', 100.0, 100.0, 0.08, 0.15, 2.0, 12], 4.019753], ['regression risk-neutral probability 2', ['P', 95.0, 90.0, 0.01, 0.15, 0.5, 5], 1.671132], ['partial repair probe 1', ['C', 80.0, 100.0, 0.05, 0.15, 0.25, 12], 0.001397], ['partial repair probe 2', ['P', 110.0, 90.0, 0.05, 0.15, 0.25, 25], 0.0038], ['normal control 1', ['P', 95.0, 105.0, 0.08, 0.15, 1.0, 5], 10.0], ['normal control 2', ['P', 80.0, 100.0, 0.0, 0.25, 0.25, 12], 20.134901], ['normal control 3', ['P', 95.0, 105.0, 0.0, 0.4, 0.25, 5], 13.698971], ['normal control 4', ['C', 100.0, 100.0, 0.0, 0.15, 1.0, 25], 6.038372]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression risk-neutral probability 16.2933416.658835Failed
regression risk-neutral probability 284.32675124.159122Failed
partial repair probe 11e-060.003613Failed
normal control 14.0731074.073107Passed
normal control 21.4384211.438421Passed
normal control 310.010.0Passed
normal control 48.7714018.771401Passed
normal control 510.33234810.332348Passed

SHA-256 / bd64c29a4cb4adbae45feb4baf0f6322d72dfb4f83baec6dcd150e1430354b12

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(kind, S, K, r, sigma, T, steps):
    dt = T / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(r * dt) - d) / (u - d)
    disc = math.exp(-r * dt)
    def pay(s):
        return max(s - K, 0.0) if kind == 'C' else max(K - s, 0.0)
    vals = [pay(S * u ** j * d ** (steps - j)) for j in range(steps + 1)]
    for i in range(steps - 1, -1, -1):
        vals = [max(disc * (p * vals[j + 1] + (1 - p) * vals[j]), pay(S * u ** j * d ** (i - j))) for j in range(i + 1)]
    return round(vals[0], 6)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression risk-neutral probability 1', ['P', 95.0, 100.0, 0.01, 0.15, 0.5, 3], 6.658835], ['regression risk-neutral probability 2', ['C', 110.0, 90.0, 0.08, 0.25, 0.5, 12], 24.159122], ['partial repair probe 1', ['P', 110.0, 90.0, 0.05, 0.15, 0.25, 12], 0.003613], ['normal control 1', ['P', 110.0, 100.0, 0.0, 0.4, 0.25, 3], 4.073107], ['normal control 2', ['P', 100.0, 90.0, 0.0, 0.25, 0.25, 5], 1.438421], ['normal control 3', ['P', 95.0, 105.0, 0.08, 0.15, 0.5, 3], 10.0], ['normal control 4', ['P', 95.0, 100.0, 0.0, 0.15, 1.0, 12], 8.771401], ['normal control 5', ['C', 110.0, 105.0, 0.0, 0.25, 0.5, 25], 10.332348]], [['regression risk-neutral probability 1', ['P', 80.0, 90.0, 0.08, 0.25, 2.0, 3], 11.993101], ['regression risk-neutral probability 2', ['C', 80.0, 105.0, 0.05, 0.4, 1.0, 25], 6.416395], ['partial repair probe 1', ['C', 80.0, 100.0, 0.01, 0.15, 0.25, 25], 0.002014], ['normal control 1', ['P', 110.0, 105.0, 0.0, 0.15, 0.5, 25], 2.513171], ['normal control 2', ['P', 80.0, 100.0, 0.0, 0.15, 0.25, 5], 20.0], ['normal control 3', ['P', 95.0, 90.0, 0.0, 0.25, 2.0, 5], 11.126274], ['normal control 4', ['P', 110.0, 90.0, 0.0, 0.15, 0.25, 12], 0.006049], ['normal control 5', ['P', 80.0, 105.0, 0.01, 0.15, 0.5, 12], 25.0]], [['regression risk-neutral probability 1', ['C', 110.0, 90.0, 0.08, 0.25, 0.5, 12], 24.159122], ['regression risk-neutral probability 2', ['P', 110.0, 90.0, 0.01, 0.15, 2.0, 3], 1.727866], ['partial repair probe 1', ['C', 80.0, 105.0, 0.01, 0.15, 0.5, 12], 0.01299], ['partial repair probe 2', ['P', 110.0, 90.0, 0.05, 0.15, 0.25, 25], 0.0038], ['normal control 1', ['C', 100.0, 100.0, 0.0, 0.25, 0.5, 12], 6.898148], ['normal control 2', ['P', 80.0, 105.0, 0.01, 0.15, 1.0, 25], 25.0], ['normal control 3', ['P', 80.0, 100.0, 0.0, 0.4, 1.0, 3], 25.451176], ['normal control 4', ['C', 100.0, 100.0, 0.0, 0.25, 2.0, 12], 13.742666]], [['regression risk-neutral probability 1', ['P', 110.0, 100.0, 0.05, 0.15, 1.0, 25], 1.469382], ['regression risk-neutral probability 2', ['P', 100.0, 100.0, 0.01, 0.4, 0.5, 5], 11.583913], ['partial repair probe 1', ['P', 110.0, 90.0, 0.01, 0.15, 0.25, 25], 0.005888], ['partial repair probe 2', ['C', 80.0, 100.0, 0.05, 0.15, 0.25, 12], 0.001397], ['normal control 1', ['P', 100.0, 105.0, 0.0, 0.15, 1.0, 25], 8.986901], ['normal control 2', ['C', 100.0, 105.0, 0.0, 0.4, 2.0, 25], 20.54431], ['normal control 3', ['P', 95.0, 100.0, 0.0, 0.25, 0.25, 25], 7.804053], ['normal control 4', ['P', 80.0, 100.0, 0.0, 0.4, 1.0, 12], 26.184178]], [['regression risk-neutral probability 1', ['P', 100.0, 100.0, 0.08, 0.15, 2.0, 12], 4.019753], ['regression risk-neutral probability 2', ['P', 95.0, 90.0, 0.01, 0.15, 0.5, 5], 1.671132], ['partial repair probe 1', ['C', 80.0, 100.0, 0.05, 0.15, 0.25, 12], 0.001397], ['partial repair probe 2', ['P', 110.0, 90.0, 0.05, 0.15, 0.25, 25], 0.0038], ['normal control 1', ['P', 95.0, 105.0, 0.08, 0.15, 1.0, 5], 10.0], ['normal control 2', ['P', 80.0, 100.0, 0.0, 0.25, 0.25, 12], 20.134901], ['normal control 3', ['P', 95.0, 105.0, 0.0, 0.4, 0.25, 5], 13.698971], ['normal control 4', ['C', 100.0, 100.0, 0.0, 0.15, 1.0, 25], 6.038372]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression risk-neutral probability 16.6588356.658835Passed
regression risk-neutral probability 224.15912224.159122Passed
partial repair probe 10.0036130.003613Passed
normal control 14.0731074.073107Passed
normal control 21.4384211.438421Passed
normal control 310.010.0Passed
normal control 48.7714018.771401Passed
normal control 510.33234810.332348Passed

SHA-256 / d355e370e818bf5140b998f4a70edeeb1447e53124f983a7d80f95a910209cd6

Verification & scope

A deterministic toy contract stated explicitly in the contract field; no claim of conformance to any exchange or clearing rulebook. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:46:56.077533+00:00.

Case digest / 4f04865517e9b7af6baf3e4cb422c4dfdb91a5e40ddb70a618146cfeb8dd2194