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.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression per-step discount 1 | 1.150352 | 1.503068 | Failed |
| regression per-step discount 2 | 6.783554 | 7.040754 | Failed |
| partial repair probe 1 | 11.907064 | 12.027684 | Failed |
| partial repair probe 2 | 11.458104 | 11.691272 | Failed |
| normal control 1 | 9.614399 | 9.614399 | Passed |
| normal control 2 | 11.525829 | 11.525829 | Passed |
| normal control 3 | 22.961525 | 22.961525 | Passed |
| normal control 4 | 8.87394 | 8.87394 | Passed |
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression per-step discount 1 | 1.503107 | 1.503068 | Failed |
| regression per-step discount 2 | 7.041297 | 7.040754 | Failed |
| partial repair probe 1 | 12.02781 | 12.027684 | Failed |
| partial repair probe 2 | 11.691761 | 11.691272 | Failed |
| normal control 1 | 9.614399 | 9.614399 | Passed |
| normal control 2 | 11.525829 | 11.525829 | Passed |
| normal control 3 | 22.961525 | 22.961525 | Passed |
| normal control 4 | 8.87394 | 8.87394 | Passed |
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression per-step discount 1 | 1.503068 | 1.503068 | Passed |
| regression per-step discount 2 | 7.040754 | 7.040754 | Passed |
| partial repair probe 1 | 12.027684 | 12.027684 | Passed |
| partial repair probe 2 | 11.691272 | 11.691272 | Passed |
| normal control 1 | 9.614399 | 9.614399 | Passed |
| normal control 2 | 11.525829 | 11.525829 | Passed |
| normal control 3 | 22.961525 | 22.961525 | Passed |
| normal control 4 | 8.87394 | 8.87394 | Passed |
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