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.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression risk-neutral probability 1 | 6.658986 | 6.658835 | Failed |
| regression risk-neutral probability 2 | 24.152308 | 24.159122 | Failed |
| partial repair probe 1 | 0.003613 | 0.003613 | Passed |
| normal control 1 | 4.073107 | 4.073107 | Passed |
| normal control 2 | 1.438421 | 1.438421 | Passed |
| normal control 3 | 10.0 | 10.0 | Passed |
| normal control 4 | 8.771401 | 8.771401 | Passed |
| normal control 5 | 10.332348 | 10.332348 | Passed |
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression risk-neutral probability 1 | 6.293341 | 6.658835 | Failed |
| regression risk-neutral probability 2 | 84.326751 | 24.159122 | Failed |
| partial repair probe 1 | 1e-06 | 0.003613 | Failed |
| normal control 1 | 4.073107 | 4.073107 | Passed |
| normal control 2 | 1.438421 | 1.438421 | Passed |
| normal control 3 | 10.0 | 10.0 | Passed |
| normal control 4 | 8.771401 | 8.771401 | Passed |
| normal control 5 | 10.332348 | 10.332348 | Passed |
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression risk-neutral probability 1 | 6.658835 | 6.658835 | Passed |
| regression risk-neutral probability 2 | 24.159122 | 24.159122 | Passed |
| partial repair probe 1 | 0.003613 | 0.003613 | Passed |
| normal control 1 | 4.073107 | 4.073107 | Passed |
| normal control 2 | 1.438421 | 1.438421 | Passed |
| normal control 3 | 10.0 | 10.0 | Passed |
| normal control 4 | 8.771401 | 8.771401 | Passed |
| normal control 5 | 10.332348 | 10.332348 | Passed |
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