FAILURE MAP
← Case archive

FA-61526 / Options payoff and settlement / Open access

Cox-Ross-Rubinstein American option tree: each step discounts over the full maturity · case 01

Option values collapse toward zero for many steps.

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

ROOT CAUSE

The discount factor uses T instead of dt.

VERIFIED REPAIR

Discount by exp(-r*dt) per step.

Unsuccessful approach: Simple-interest discounting per step is inconsistent with 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 = (math.exp(r * dt) - d) / (u - d)
    disc = math.exp(-r * T)
    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 per-step discount 1', ['C', 95.0, 105.0, 0.05, 0.15, 0.5, 12], 1.503068], ['regression per-step discount 2', ['P', 110.0, 100.0, 0.05, 0.4, 0.5, 3], 7.040754], ['partial repair probe 1', ['P', 80.0, 90.0, 0.05, 0.4, 0.25, 3], 12.027684], ['partial repair probe 2', ['P', 95.0, 105.0, 0.05, 0.25, 0.5, 3], 11.691272], ['normal control 1', ['C', 80.0, 105.0, 0.0, 0.4, 2.0, 5], 9.614399], ['normal control 2', ['P', 95.0, 105.0, 0.0, 0.25, 0.25, 25], 11.525829], ['normal control 3', ['P', 80.0, 100.0, 0.0, 0.4, 0.5, 3], 22.961525], ['normal control 4', ['P', 100.0, 100.0, 0.0, 0.15, 2.0, 5], 8.87394]], [['regression per-step discount 1', ['P', 100.0, 100.0, 0.05, 0.25, 1.0, 5], 8.342309], ['regression per-step discount 2', ['P', 95.0, 100.0, 0.01, 0.25, 1.0, 25], 11.824535], ['partial repair probe 1', ['C', 110.0, 105.0, 0.05, 0.15, 1.0, 3], 12.81249], ['partial repair probe 2', ['P', 95.0, 105.0, 0.05, 0.25, 0.25, 5], 10.949547], ['normal control 1', ['C', 100.0, 100.0, 0.0, 0.4, 0.5, 12], 11.014687], ['normal control 2', ['C', 100.0, 105.0, 0.0, 0.4, 0.25, 3], 6.35708], ['normal control 3', ['C', 110.0, 100.0, 0.0, 0.25, 0.25, 5], 11.787715], ['normal control 4', ['P', 110.0, 100.0, 0.0, 0.25, 0.25, 5], 1.787715]], [['regression per-step discount 1', ['C', 95.0, 90.0, 0.05, 0.4, 1.0, 25], 19.496713], ['regression per-step discount 2', ['P', 100.0, 90.0, 0.05, 0.4, 2.0, 25], 12.929888], ['partial repair probe 1', ['P', 110.0, 105.0, 0.08, 0.15, 2.0, 5], 2.721138], ['partial repair probe 2', ['P', 80.0, 105.0, 0.01, 0.25, 2.0, 12], 28.045327], ['normal control 1', ['P', 80.0, 105.0, 0.01, 0.15, 0.5, 25], 25.0], ['normal control 2', ['P', 80.0, 100.0, 0.0, 0.25, 0.25, 5], 20.157258], ['normal control 3', ['P', 100.0, 105.0, 0.0, 0.15, 2.0, 12], 11.540671], ['normal control 4', ['P', 100.0, 100.0, 0.0, 0.4, 1.0, 5], 16.637412]], [['regression per-step discount 1', ['P', 80.0, 100.0, 0.05, 0.25, 2.0, 5], 21.182349], ['regression per-step discount 2', ['C', 100.0, 105.0, 0.08, 0.15, 0.5, 12], 3.818689], ['partial repair probe 1', ['P', 95.0, 100.0, 0.05, 0.15, 2.0, 5], 7.470545], ['partial repair probe 2', ['P', 95.0, 100.0, 0.08, 0.4, 0.25, 3], 9.82806], ['normal control 1', ['C', 80.0, 90.0, 0.0, 0.4, 2.0, 12], 14.623787], ['normal control 2', ['P', 100.0, 90.0, 0.0, 0.25, 2.0, 12], 9.165983], ['normal control 3', ['P', 95.0, 105.0, 0.0, 0.25, 2.0, 25], 19.617294], ['normal control 4', ['C', 95.0, 105.0, 0.0, 0.4, 0.25, 3], 3.641054]], [['regression per-step discount 1', ['C', 95.0, 100.0, 0.01, 0.4, 0.25, 3], 6.012641], ['regression per-step discount 2', ['P', 95.0, 105.0, 0.01, 0.4, 0.25, 5], 13.586198], ['partial repair probe 1', ['P', 80.0, 100.0, 0.05, 0.4, 0.5, 5], 22.025861], ['partial repair probe 2', ['C', 100.0, 100.0, 0.05, 0.4, 0.25, 12], 8.389269], ['normal control 1', ['P', 100.0, 105.0, 0.0, 0.15, 0.25, 5], 6.210142], ['normal control 2', ['P', 95.0, 105.0, 0.0, 0.15, 0.5, 25], 10.97413], ['normal control 3', ['P', 80.0, 100.0, 0.0, 0.25, 0.5, 25], 20.787912], ['normal control 4', ['P', 100.0, 100.0, 0.0, 0.25, 0.25, 5], 5.236693]]]
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 per-step discount 11.1503521.503068Failed
regression per-step discount 26.7835547.040754Failed
partial repair probe 111.90706412.027684Failed
partial repair probe 211.45810411.691272Failed
normal control 19.6143999.614399Passed
normal control 211.52582911.525829Passed
normal control 322.96152522.961525Passed
normal control 48.873948.87394Passed

SHA-256 / d261b1a40a6d05117b912d260b63f07bed6a3ac6691c06532def6f24af928684

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 * dt) - d) / (u - d)
    disc = 1 / (1 + 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 per-step discount 1', ['C', 95.0, 105.0, 0.05, 0.15, 0.5, 12], 1.503068], ['regression per-step discount 2', ['P', 110.0, 100.0, 0.05, 0.4, 0.5, 3], 7.040754], ['partial repair probe 1', ['P', 80.0, 90.0, 0.05, 0.4, 0.25, 3], 12.027684], ['partial repair probe 2', ['P', 95.0, 105.0, 0.05, 0.25, 0.5, 3], 11.691272], ['normal control 1', ['C', 80.0, 105.0, 0.0, 0.4, 2.0, 5], 9.614399], ['normal control 2', ['P', 95.0, 105.0, 0.0, 0.25, 0.25, 25], 11.525829], ['normal control 3', ['P', 80.0, 100.0, 0.0, 0.4, 0.5, 3], 22.961525], ['normal control 4', ['P', 100.0, 100.0, 0.0, 0.15, 2.0, 5], 8.87394]], [['regression per-step discount 1', ['P', 100.0, 100.0, 0.05, 0.25, 1.0, 5], 8.342309], ['regression per-step discount 2', ['P', 95.0, 100.0, 0.01, 0.25, 1.0, 25], 11.824535], ['partial repair probe 1', ['C', 110.0, 105.0, 0.05, 0.15, 1.0, 3], 12.81249], ['partial repair probe 2', ['P', 95.0, 105.0, 0.05, 0.25, 0.25, 5], 10.949547], ['normal control 1', ['C', 100.0, 100.0, 0.0, 0.4, 0.5, 12], 11.014687], ['normal control 2', ['C', 100.0, 105.0, 0.0, 0.4, 0.25, 3], 6.35708], ['normal control 3', ['C', 110.0, 100.0, 0.0, 0.25, 0.25, 5], 11.787715], ['normal control 4', ['P', 110.0, 100.0, 0.0, 0.25, 0.25, 5], 1.787715]], [['regression per-step discount 1', ['C', 95.0, 90.0, 0.05, 0.4, 1.0, 25], 19.496713], ['regression per-step discount 2', ['P', 100.0, 90.0, 0.05, 0.4, 2.0, 25], 12.929888], ['partial repair probe 1', ['P', 110.0, 105.0, 0.08, 0.15, 2.0, 5], 2.721138], ['partial repair probe 2', ['P', 80.0, 105.0, 0.01, 0.25, 2.0, 12], 28.045327], ['normal control 1', ['P', 80.0, 105.0, 0.01, 0.15, 0.5, 25], 25.0], ['normal control 2', ['P', 80.0, 100.0, 0.0, 0.25, 0.25, 5], 20.157258], ['normal control 3', ['P', 100.0, 105.0, 0.0, 0.15, 2.0, 12], 11.540671], ['normal control 4', ['P', 100.0, 100.0, 0.0, 0.4, 1.0, 5], 16.637412]], [['regression per-step discount 1', ['P', 80.0, 100.0, 0.05, 0.25, 2.0, 5], 21.182349], ['regression per-step discount 2', ['C', 100.0, 105.0, 0.08, 0.15, 0.5, 12], 3.818689], ['partial repair probe 1', ['P', 95.0, 100.0, 0.05, 0.15, 2.0, 5], 7.470545], ['partial repair probe 2', ['P', 95.0, 100.0, 0.08, 0.4, 0.25, 3], 9.82806], ['normal control 1', ['C', 80.0, 90.0, 0.0, 0.4, 2.0, 12], 14.623787], ['normal control 2', ['P', 100.0, 90.0, 0.0, 0.25, 2.0, 12], 9.165983], ['normal control 3', ['P', 95.0, 105.0, 0.0, 0.25, 2.0, 25], 19.617294], ['normal control 4', ['C', 95.0, 105.0, 0.0, 0.4, 0.25, 3], 3.641054]], [['regression per-step discount 1', ['C', 95.0, 100.0, 0.01, 0.4, 0.25, 3], 6.012641], ['regression per-step discount 2', ['P', 95.0, 105.0, 0.01, 0.4, 0.25, 5], 13.586198], ['partial repair probe 1', ['P', 80.0, 100.0, 0.05, 0.4, 0.5, 5], 22.025861], ['partial repair probe 2', ['C', 100.0, 100.0, 0.05, 0.4, 0.25, 12], 8.389269], ['normal control 1', ['P', 100.0, 105.0, 0.0, 0.15, 0.25, 5], 6.210142], ['normal control 2', ['P', 95.0, 105.0, 0.0, 0.15, 0.5, 25], 10.97413], ['normal control 3', ['P', 80.0, 100.0, 0.0, 0.25, 0.5, 25], 20.787912], ['normal control 4', ['P', 100.0, 100.0, 0.0, 0.25, 0.25, 5], 5.236693]]]
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 per-step discount 11.5031071.503068Failed
regression per-step discount 27.0412977.040754Failed
partial repair probe 112.0278112.027684Failed
partial repair probe 211.69176111.691272Failed
normal control 19.6143999.614399Passed
normal control 211.52582911.525829Passed
normal control 322.96152522.961525Passed
normal control 48.873948.87394Passed

SHA-256 / 9b70a68f828972e70f41920ced69f3d97f956c588aae2245f8ed0c6afa2a39f1

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 per-step discount 1', ['C', 95.0, 105.0, 0.05, 0.15, 0.5, 12], 1.503068], ['regression per-step discount 2', ['P', 110.0, 100.0, 0.05, 0.4, 0.5, 3], 7.040754], ['partial repair probe 1', ['P', 80.0, 90.0, 0.05, 0.4, 0.25, 3], 12.027684], ['partial repair probe 2', ['P', 95.0, 105.0, 0.05, 0.25, 0.5, 3], 11.691272], ['normal control 1', ['C', 80.0, 105.0, 0.0, 0.4, 2.0, 5], 9.614399], ['normal control 2', ['P', 95.0, 105.0, 0.0, 0.25, 0.25, 25], 11.525829], ['normal control 3', ['P', 80.0, 100.0, 0.0, 0.4, 0.5, 3], 22.961525], ['normal control 4', ['P', 100.0, 100.0, 0.0, 0.15, 2.0, 5], 8.87394]], [['regression per-step discount 1', ['P', 100.0, 100.0, 0.05, 0.25, 1.0, 5], 8.342309], ['regression per-step discount 2', ['P', 95.0, 100.0, 0.01, 0.25, 1.0, 25], 11.824535], ['partial repair probe 1', ['C', 110.0, 105.0, 0.05, 0.15, 1.0, 3], 12.81249], ['partial repair probe 2', ['P', 95.0, 105.0, 0.05, 0.25, 0.25, 5], 10.949547], ['normal control 1', ['C', 100.0, 100.0, 0.0, 0.4, 0.5, 12], 11.014687], ['normal control 2', ['C', 100.0, 105.0, 0.0, 0.4, 0.25, 3], 6.35708], ['normal control 3', ['C', 110.0, 100.0, 0.0, 0.25, 0.25, 5], 11.787715], ['normal control 4', ['P', 110.0, 100.0, 0.0, 0.25, 0.25, 5], 1.787715]], [['regression per-step discount 1', ['C', 95.0, 90.0, 0.05, 0.4, 1.0, 25], 19.496713], ['regression per-step discount 2', ['P', 100.0, 90.0, 0.05, 0.4, 2.0, 25], 12.929888], ['partial repair probe 1', ['P', 110.0, 105.0, 0.08, 0.15, 2.0, 5], 2.721138], ['partial repair probe 2', ['P', 80.0, 105.0, 0.01, 0.25, 2.0, 12], 28.045327], ['normal control 1', ['P', 80.0, 105.0, 0.01, 0.15, 0.5, 25], 25.0], ['normal control 2', ['P', 80.0, 100.0, 0.0, 0.25, 0.25, 5], 20.157258], ['normal control 3', ['P', 100.0, 105.0, 0.0, 0.15, 2.0, 12], 11.540671], ['normal control 4', ['P', 100.0, 100.0, 0.0, 0.4, 1.0, 5], 16.637412]], [['regression per-step discount 1', ['P', 80.0, 100.0, 0.05, 0.25, 2.0, 5], 21.182349], ['regression per-step discount 2', ['C', 100.0, 105.0, 0.08, 0.15, 0.5, 12], 3.818689], ['partial repair probe 1', ['P', 95.0, 100.0, 0.05, 0.15, 2.0, 5], 7.470545], ['partial repair probe 2', ['P', 95.0, 100.0, 0.08, 0.4, 0.25, 3], 9.82806], ['normal control 1', ['C', 80.0, 90.0, 0.0, 0.4, 2.0, 12], 14.623787], ['normal control 2', ['P', 100.0, 90.0, 0.0, 0.25, 2.0, 12], 9.165983], ['normal control 3', ['P', 95.0, 105.0, 0.0, 0.25, 2.0, 25], 19.617294], ['normal control 4', ['C', 95.0, 105.0, 0.0, 0.4, 0.25, 3], 3.641054]], [['regression per-step discount 1', ['C', 95.0, 100.0, 0.01, 0.4, 0.25, 3], 6.012641], ['regression per-step discount 2', ['P', 95.0, 105.0, 0.01, 0.4, 0.25, 5], 13.586198], ['partial repair probe 1', ['P', 80.0, 100.0, 0.05, 0.4, 0.5, 5], 22.025861], ['partial repair probe 2', ['C', 100.0, 100.0, 0.05, 0.4, 0.25, 12], 8.389269], ['normal control 1', ['P', 100.0, 105.0, 0.0, 0.15, 0.25, 5], 6.210142], ['normal control 2', ['P', 95.0, 105.0, 0.0, 0.15, 0.5, 25], 10.97413], ['normal control 3', ['P', 80.0, 100.0, 0.0, 0.25, 0.5, 25], 20.787912], ['normal control 4', ['P', 100.0, 100.0, 0.0, 0.25, 0.25, 5], 5.236693]]]
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 per-step discount 11.5030681.503068Passed
regression per-step discount 27.0407547.040754Passed
partial repair probe 112.02768412.027684Passed
partial repair probe 211.69127211.691272Passed
normal control 19.6143999.614399Passed
normal control 211.52582911.525829Passed
normal control 322.96152522.961525Passed
normal control 48.873948.87394Passed

SHA-256 / 794d0dd782e050cae87b05a25275fcc8b7c69bbc9090611c7c39f990a0685955

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.118707+00:00.

Case digest / 68d4e9a773baa336fb96ce02dd58587eccbc67bb9d0c1b5b34c01ccfd4897106