FAILURE MAP
← Case archive

FA-61516 / Options payoff and settlement / Open access

Cox-Ross-Rubinstein American option tree: the early-exercise test prices puts as calls · case 01

American puts lose their early exercise premium.

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

ROOT CAUSE

The immediate exercise value always uses the call payoff.

VERIFIED REPAIR

Use the option's own payoff at the node price.

Unsuccessful approach: Evaluating the payoff at the terminal node price for that index uses the wrong price level.

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 * 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]), max(S * u ** j * d ** (i - j) - K, 0.0)) 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 early exercise value 1', ['P', 80.0, 105.0, 0.05, 0.25, 2.0, 5], 25.210167], ['regression early exercise value 2', ['P', 100.0, 100.0, 0.0, 0.4, 2.0, 5], 23.343597], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.25, 0.5, 3], 25.390902], ['normal control 1', ['C', 110.0, 100.0, 0.08, 0.4, 1.0, 3], 27.248875], ['normal control 2', ['C', 110.0, 90.0, 0.05, 0.25, 0.25, 12], 21.337289], ['normal control 3', ['C', 110.0, 105.0, 0.08, 0.25, 0.25, 5], 9.501783], ['normal control 4', ['C', 95.0, 105.0, 0.0, 0.25, 0.25, 3], 1.45145], ['normal control 5', ['C', 100.0, 90.0, 0.01, 0.15, 0.25, 25], 10.448278]], [['regression early exercise value 1', ['P', 100.0, 105.0, 0.08, 0.4, 1.0, 12], 15.430377], ['regression early exercise value 2', ['P', 80.0, 90.0, 0.08, 0.25, 0.5, 5], 10.841628], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.15, 2.0, 3], 26.088253], ['partial repair probe 2', ['P', 80.0, 100.0, 0.0, 0.15, 2.0, 3], 21.605448], ['normal control 1', ['C', 95.0, 105.0, 0.0, 0.15, 1.0, 5], 2.309033], ['normal control 2', ['C', 95.0, 105.0, 0.01, 0.25, 2.0, 3], 10.862909], ['normal control 3', ['C', 80.0, 100.0, 0.01, 0.4, 1.0, 25], 6.577995], ['normal control 4', ['C', 80.0, 90.0, 0.0, 0.25, 2.0, 25], 7.653711]], [['regression early exercise value 1', ['P', 110.0, 105.0, 0.0, 0.25, 0.25, 12], 3.309833], ['regression early exercise value 2', ['P', 95.0, 100.0, 0.01, 0.25, 1.0, 25], 11.824535], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.25, 0.5, 3], 25.390902], ['partial repair probe 2', ['P', 80.0, 100.0, 0.0, 0.25, 0.5, 3], 20.925093], ['normal control 1', ['C', 80.0, 90.0, 0.0, 0.25, 0.5, 3], 1.993475], ['normal control 2', ['C', 95.0, 100.0, 0.01, 0.15, 0.25, 5], 1.165015], ['normal control 3', ['C', 80.0, 100.0, 0.08, 0.15, 1.0, 3], 0.879347], ['normal control 4', ['C', 110.0, 105.0, 0.05, 0.15, 0.5, 5], 9.10462]], [['regression early exercise value 1', ['P', 95.0, 105.0, 0.05, 0.4, 0.25, 25], 13.262218], ['regression early exercise value 2', ['P', 95.0, 100.0, 0.08, 0.15, 0.25, 5], 5.303147], ['partial repair probe 1', ['P', 80.0, 100.0, 0.0, 0.15, 0.5, 5], 20.039153], ['normal control 1', ['C', 110.0, 90.0, 0.0, 0.4, 0.25, 3], 21.805173], ['normal control 2', ['C', 100.0, 90.0, 0.0, 0.15, 2.0, 3], 13.704549], ['normal control 3', ['C', 100.0, 100.0, 0.08, 0.4, 1.0, 25], 19.526386], ['normal control 4', ['C', 80.0, 90.0, 0.0, 0.15, 0.5, 25], 0.605969], ['normal control 5', ['C', 80.0, 105.0, 0.01, 0.4, 0.25, 12], 0.754375]], [['regression early exercise value 1', ['P', 110.0, 100.0, 0.08, 0.25, 0.5, 3], 2.270558], ['regression early exercise value 2', ['P', 110.0, 90.0, 0.08, 0.4, 2.0, 5], 8.982113], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.15, 2.0, 3], 26.088253], ['partial repair probe 2', ['P', 80.0, 105.0, 0.0, 0.15, 0.5, 3], 25.0], ['normal control 1', ['C', 80.0, 105.0, 0.05, 0.4, 0.25, 12], 0.832557], ['normal control 2', ['C', 100.0, 100.0, 0.0, 0.4, 2.0, 12], 21.811807], ['normal control 3', ['C', 95.0, 100.0, 0.08, 0.15, 0.5, 3], 3.266989], ['normal control 4', ['C', 80.0, 90.0, 0.05, 0.25, 0.5, 25], 2.803238]]]
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 early exercise value 124.56769725.210167Failed
regression early exercise value 242.11712123.343597Failed
partial repair probe 125.39090225.390902Passed
normal control 127.24887527.248875Passed
normal control 221.33728921.337289Passed
normal control 39.5017839.501783Passed
normal control 41.451451.45145Passed
normal control 510.44827810.448278Passed

SHA-256 / cc02ac68543d3167a2f477f30f584e1f1eff78eb7af00542514b575883d8355d

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 = 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 ** (steps - 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 early exercise value 1', ['P', 80.0, 105.0, 0.05, 0.25, 2.0, 5], 25.210167], ['regression early exercise value 2', ['P', 100.0, 100.0, 0.0, 0.4, 2.0, 5], 23.343597], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.25, 0.5, 3], 25.390902], ['normal control 1', ['C', 110.0, 100.0, 0.08, 0.4, 1.0, 3], 27.248875], ['normal control 2', ['C', 110.0, 90.0, 0.05, 0.25, 0.25, 12], 21.337289], ['normal control 3', ['C', 110.0, 105.0, 0.08, 0.25, 0.25, 5], 9.501783], ['normal control 4', ['C', 95.0, 105.0, 0.0, 0.25, 0.25, 3], 1.45145], ['normal control 5', ['C', 100.0, 90.0, 0.01, 0.15, 0.25, 25], 10.448278]], [['regression early exercise value 1', ['P', 100.0, 105.0, 0.08, 0.4, 1.0, 12], 15.430377], ['regression early exercise value 2', ['P', 80.0, 90.0, 0.08, 0.25, 0.5, 5], 10.841628], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.15, 2.0, 3], 26.088253], ['partial repair probe 2', ['P', 80.0, 100.0, 0.0, 0.15, 2.0, 3], 21.605448], ['normal control 1', ['C', 95.0, 105.0, 0.0, 0.15, 1.0, 5], 2.309033], ['normal control 2', ['C', 95.0, 105.0, 0.01, 0.25, 2.0, 3], 10.862909], ['normal control 3', ['C', 80.0, 100.0, 0.01, 0.4, 1.0, 25], 6.577995], ['normal control 4', ['C', 80.0, 90.0, 0.0, 0.25, 2.0, 25], 7.653711]], [['regression early exercise value 1', ['P', 110.0, 105.0, 0.0, 0.25, 0.25, 12], 3.309833], ['regression early exercise value 2', ['P', 95.0, 100.0, 0.01, 0.25, 1.0, 25], 11.824535], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.25, 0.5, 3], 25.390902], ['partial repair probe 2', ['P', 80.0, 100.0, 0.0, 0.25, 0.5, 3], 20.925093], ['normal control 1', ['C', 80.0, 90.0, 0.0, 0.25, 0.5, 3], 1.993475], ['normal control 2', ['C', 95.0, 100.0, 0.01, 0.15, 0.25, 5], 1.165015], ['normal control 3', ['C', 80.0, 100.0, 0.08, 0.15, 1.0, 3], 0.879347], ['normal control 4', ['C', 110.0, 105.0, 0.05, 0.15, 0.5, 5], 9.10462]], [['regression early exercise value 1', ['P', 95.0, 105.0, 0.05, 0.4, 0.25, 25], 13.262218], ['regression early exercise value 2', ['P', 95.0, 100.0, 0.08, 0.15, 0.25, 5], 5.303147], ['partial repair probe 1', ['P', 80.0, 100.0, 0.0, 0.15, 0.5, 5], 20.039153], ['normal control 1', ['C', 110.0, 90.0, 0.0, 0.4, 0.25, 3], 21.805173], ['normal control 2', ['C', 100.0, 90.0, 0.0, 0.15, 2.0, 3], 13.704549], ['normal control 3', ['C', 100.0, 100.0, 0.08, 0.4, 1.0, 25], 19.526386], ['normal control 4', ['C', 80.0, 90.0, 0.0, 0.15, 0.5, 25], 0.605969], ['normal control 5', ['C', 80.0, 105.0, 0.01, 0.4, 0.25, 12], 0.754375]], [['regression early exercise value 1', ['P', 110.0, 100.0, 0.08, 0.25, 0.5, 3], 2.270558], ['regression early exercise value 2', ['P', 110.0, 90.0, 0.08, 0.4, 2.0, 5], 8.982113], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.15, 2.0, 3], 26.088253], ['partial repair probe 2', ['P', 80.0, 105.0, 0.0, 0.15, 0.5, 3], 25.0], ['normal control 1', ['C', 80.0, 105.0, 0.05, 0.4, 0.25, 12], 0.832557], ['normal control 2', ['C', 100.0, 100.0, 0.0, 0.4, 2.0, 12], 21.811807], ['normal control 3', ['C', 95.0, 100.0, 0.08, 0.15, 0.5, 3], 3.266989], ['normal control 4', ['C', 80.0, 90.0, 0.05, 0.25, 0.5, 25], 2.803238]]]
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 early exercise value 168.71308525.210167Failed
regression early exercise value 271.7735623.343597Failed
partial repair probe 146.1000425.390902Failed
normal control 127.24887527.248875Passed
normal control 221.33728921.337289Passed
normal control 39.5017839.501783Passed
normal control 41.451451.45145Passed
normal control 510.44827810.448278Passed

SHA-256 / 03321b612329d2787280bffebc29fe4dae2780a6d4603fad59a5623c57f5df30

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 early exercise value 1', ['P', 80.0, 105.0, 0.05, 0.25, 2.0, 5], 25.210167], ['regression early exercise value 2', ['P', 100.0, 100.0, 0.0, 0.4, 2.0, 5], 23.343597], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.25, 0.5, 3], 25.390902], ['normal control 1', ['C', 110.0, 100.0, 0.08, 0.4, 1.0, 3], 27.248875], ['normal control 2', ['C', 110.0, 90.0, 0.05, 0.25, 0.25, 12], 21.337289], ['normal control 3', ['C', 110.0, 105.0, 0.08, 0.25, 0.25, 5], 9.501783], ['normal control 4', ['C', 95.0, 105.0, 0.0, 0.25, 0.25, 3], 1.45145], ['normal control 5', ['C', 100.0, 90.0, 0.01, 0.15, 0.25, 25], 10.448278]], [['regression early exercise value 1', ['P', 100.0, 105.0, 0.08, 0.4, 1.0, 12], 15.430377], ['regression early exercise value 2', ['P', 80.0, 90.0, 0.08, 0.25, 0.5, 5], 10.841628], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.15, 2.0, 3], 26.088253], ['partial repair probe 2', ['P', 80.0, 100.0, 0.0, 0.15, 2.0, 3], 21.605448], ['normal control 1', ['C', 95.0, 105.0, 0.0, 0.15, 1.0, 5], 2.309033], ['normal control 2', ['C', 95.0, 105.0, 0.01, 0.25, 2.0, 3], 10.862909], ['normal control 3', ['C', 80.0, 100.0, 0.01, 0.4, 1.0, 25], 6.577995], ['normal control 4', ['C', 80.0, 90.0, 0.0, 0.25, 2.0, 25], 7.653711]], [['regression early exercise value 1', ['P', 110.0, 105.0, 0.0, 0.25, 0.25, 12], 3.309833], ['regression early exercise value 2', ['P', 95.0, 100.0, 0.01, 0.25, 1.0, 25], 11.824535], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.25, 0.5, 3], 25.390902], ['partial repair probe 2', ['P', 80.0, 100.0, 0.0, 0.25, 0.5, 3], 20.925093], ['normal control 1', ['C', 80.0, 90.0, 0.0, 0.25, 0.5, 3], 1.993475], ['normal control 2', ['C', 95.0, 100.0, 0.01, 0.15, 0.25, 5], 1.165015], ['normal control 3', ['C', 80.0, 100.0, 0.08, 0.15, 1.0, 3], 0.879347], ['normal control 4', ['C', 110.0, 105.0, 0.05, 0.15, 0.5, 5], 9.10462]], [['regression early exercise value 1', ['P', 95.0, 105.0, 0.05, 0.4, 0.25, 25], 13.262218], ['regression early exercise value 2', ['P', 95.0, 100.0, 0.08, 0.15, 0.25, 5], 5.303147], ['partial repair probe 1', ['P', 80.0, 100.0, 0.0, 0.15, 0.5, 5], 20.039153], ['normal control 1', ['C', 110.0, 90.0, 0.0, 0.4, 0.25, 3], 21.805173], ['normal control 2', ['C', 100.0, 90.0, 0.0, 0.15, 2.0, 3], 13.704549], ['normal control 3', ['C', 100.0, 100.0, 0.08, 0.4, 1.0, 25], 19.526386], ['normal control 4', ['C', 80.0, 90.0, 0.0, 0.15, 0.5, 25], 0.605969], ['normal control 5', ['C', 80.0, 105.0, 0.01, 0.4, 0.25, 12], 0.754375]], [['regression early exercise value 1', ['P', 110.0, 100.0, 0.08, 0.25, 0.5, 3], 2.270558], ['regression early exercise value 2', ['P', 110.0, 90.0, 0.08, 0.4, 2.0, 5], 8.982113], ['partial repair probe 1', ['P', 80.0, 105.0, 0.0, 0.15, 2.0, 3], 26.088253], ['partial repair probe 2', ['P', 80.0, 105.0, 0.0, 0.15, 0.5, 3], 25.0], ['normal control 1', ['C', 80.0, 105.0, 0.05, 0.4, 0.25, 12], 0.832557], ['normal control 2', ['C', 100.0, 100.0, 0.0, 0.4, 2.0, 12], 21.811807], ['normal control 3', ['C', 95.0, 100.0, 0.08, 0.15, 0.5, 3], 3.266989], ['normal control 4', ['C', 80.0, 90.0, 0.05, 0.25, 0.5, 25], 2.803238]]]
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 early exercise value 125.21016725.210167Passed
regression early exercise value 223.34359723.343597Passed
partial repair probe 125.39090225.390902Passed
normal control 127.24887527.248875Passed
normal control 221.33728921.337289Passed
normal control 39.5017839.501783Passed
normal control 41.451451.45145Passed
normal control 510.44827810.448278Passed

SHA-256 / 642c67746189590d088903b9c6fb99f21e5c47cc01269f9e16b27384977d7ab3

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

Case digest / aa05efacce6ce895b8caa7e5dbf51c09449fcb6f36931e3ca7ef99d100537ab6