FA-61421 / Options payoff and settlement / Open access
Arithmetic Asian payoff with disrupted fixings: disruptions prefer the next valid fixing over the previous one · case 01
Mid-schedule disruptions borrow a future price.
ROOT CAUSE
The fallback checks later fixings before earlier ones.
VERIFIED REPAIR
Use the last preceding filled value; look forward only when nothing precedes.
Unsuccessful approach: Averaging neighbours interpolates instead of carrying the previous value.
Case contract
Inputs kind (price-call, price-put, strike-call), fixings (None = disrupted), strike and final price. A disrupted fixing takes the last preceding filled value; if none precedes, the next valid fixing. avg = mean of the filled fixings. price-call pays max(avg-K,0), price-put max(K-avg,0), strike-call max(final-avg,0). Round 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
N = 1
observations = []
def solve(kind, fixings, strike, final):
vals = list(fixings)
filled = []
for i, v in enumerate(vals):
if v is None:
prev = [x for x in filled if x is not None]
later = [x for x in vals[i + 1:] if x is not None]
v = later[0] if later else prev[-1]
filled.append(v)
avg = sum(filled) / len(filled)
if kind == 'price-call':
pay = max(avg - strike, 0.0)
elif kind == 'price-put':
pay = max(strike - avg, 0.0)
else:
pay = max(final - avg, 0.0)
return round(pay, 6)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression fallback priority 1', ['price-put', [107.56, 91.47, None, 93.63, 107.09, 85.05], 100, 107.47], 3.955], ['regression fallback priority 2', ['strike-call', [90.82, 116.42, 97.17, None, 102.9], 100, 102.74], 1.844], ['partial repair probe 1', ['strike-call', [119.87, 109.69, None], 110, 116.2], 3.116667], ['partial repair probe 2', ['price-call', [117.31, 109.18, 80.52, 110.93, None], 90, 89.18], 15.774], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-put', [115.48, 83.47, 98.82], 100, 99.98], 0.743333], ['normal control 2', ['strike-call', [111.57, None, None, None, 98.59, 116.83], 110, 84.9], 0.0], ['normal control 3', ['strike-call', [91.38, None, None], 105, 95.03], 3.65]], [['regression fallback priority 1', ['price-call', [98.02, None, 91.49, 95.03, 98.48, 97.35], 90, 119.72], 6.398333], ['regression fallback priority 2', ['price-put', [119.49, 98.97, 101.06, None, 95.58, 113.4, 91.41, 95.29], 105, 88.56], 2.9675], ['partial repair probe 1', ['strike-call', [88.95, 80.84, None], 110, 112.81], 29.266667], ['partial repair probe 2', ['price-put', [119.4, 88.69, 99.38, 103.22, 102.55, None], 110, 112.44], 7.368333], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-put', [85.84, None], 90, 114.04], 4.16], ['normal control 2', ['price-put', [None, 105.9], 105, 82.58], 0.0], ['normal control 3', ['price-put', [111.7, 101.25, 103.45, 114.56, 86.59, None, 114.39], 100, 96.18], 0.0]], [['regression fallback priority 1', ['price-call', [119.01, None, 115.28], 105, 82.02], 12.766667], ['regression fallback priority 2', ['price-call', [84.27, 118.57, None, None, None, 103.89], 100, 80.29], 10.406667], ['partial repair probe 1', ['price-put', [None, 87.07, 89.34, None], 90, 115.78], 1.795], ['partial repair probe 2', ['price-put', [110.48, 85.44, None, None, None], 110, 102.6], 19.552], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-call', [80.8, 102.44, None, 103.49, 106.76, 93.24, None], 110, 93.16], 0.0], ['normal control 2', ['price-call', [96.59, None, 81.5, 86.37, 117.86, 81.57], 100, 80.38], 0.0], ['normal control 3', ['price-put', [93.19, None, None, None, None], 110, 115.13], 16.81]], [['regression fallback priority 1', ['price-put', [85.19, None, 83.45, 104.23, 100.65, None, 102.76], 110, 109.78], 15.411429], ['regression fallback priority 2', ['strike-call', [86.53, 95.55, None, 100.69, 106.44], 90, 104.86], 7.908], ['partial repair probe 1', ['strike-call', [81.55, 101.38, 89.48, None], 100, 100.13], 9.6575], ['partial repair probe 2', ['price-put', [88.01, 87.96, None], 105, 112.32], 17.023333], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-call', [None, 112.37, None, None], 90, 98.65], 22.37], ['normal control 2', ['price-call', [None, 110.4, 80.07], 90, 95.81], 10.29], ['normal control 3', ['price-put', [110.91, None], 100, 116.16], 0.0]], [['regression fallback priority 1', ['strike-call', [100.62, None, 111.91, 86.61, None], 100, 115.98], 18.706], ['regression fallback priority 2', ['price-put', [116.55, 87.44, 91.05, None, 97.68], 110, 114.79], 13.246], ['partial repair probe 1', ['strike-call', [87.08, 93.2, None, None, None, None], 110, 94.72], 2.54], ['partial repair probe 2', ['price-call', [104.79, 89.0, None, None], 100, 101.84], 0.0], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-call', [None, 98.32, 105.85, 84.08], 100, 117.74], 0.0], ['normal control 2', ['price-call', [80.35, 102.5], 105, 119.19], 0.0], ['normal control 3', ['price-call', [97.46, 119.62, 96.51, 117.63, 87.35, 92.18, 101.58, 80.54], 90, 100.37], 9.10875]]]
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 fallback priority 1 | 3.595 | 3.955 | Failed |
| regression fallback priority 2 | 0.698 | 1.844 | Failed |
| partial repair probe 1 | 3.116667 | 3.116667 | Passed |
| partial repair probe 2 | 15.774 | 15.774 | Passed |
| boundary control 1 | 3.333333 | 3.333333 | Passed |
| normal control 1 | 0.743333 | 0.743333 | Passed |
| normal control 2 | 0.0 | 0.0 | Passed |
| normal control 3 | 3.65 | 3.65 | Passed |
SHA-256 / f316b14fe4e0191f7d80a1e821bb0840b61d7304d7aad99d0cd990d1008b09fe
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(kind, fixings, strike, final):
vals = list(fixings)
filled = []
for i, v in enumerate(vals):
if v is None:
prev = [x for x in filled if x is not None]
later = [x for x in vals[i + 1:] if x is not None]
v = (prev[-1] + later[0]) / 2 if prev and later else (prev or later)[0]
filled.append(v)
avg = sum(filled) / len(filled)
if kind == 'price-call':
pay = max(avg - strike, 0.0)
elif kind == 'price-put':
pay = max(strike - avg, 0.0)
else:
pay = max(final - avg, 0.0)
return round(pay, 6)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression fallback priority 1', ['price-put', [107.56, 91.47, None, 93.63, 107.09, 85.05], 100, 107.47], 3.955], ['regression fallback priority 2', ['strike-call', [90.82, 116.42, 97.17, None, 102.9], 100, 102.74], 1.844], ['partial repair probe 1', ['strike-call', [119.87, 109.69, None], 110, 116.2], 3.116667], ['partial repair probe 2', ['price-call', [117.31, 109.18, 80.52, 110.93, None], 90, 89.18], 15.774], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-put', [115.48, 83.47, 98.82], 100, 99.98], 0.743333], ['normal control 2', ['strike-call', [111.57, None, None, None, 98.59, 116.83], 110, 84.9], 0.0], ['normal control 3', ['strike-call', [91.38, None, None], 105, 95.03], 3.65]], [['regression fallback priority 1', ['price-call', [98.02, None, 91.49, 95.03, 98.48, 97.35], 90, 119.72], 6.398333], ['regression fallback priority 2', ['price-put', [119.49, 98.97, 101.06, None, 95.58, 113.4, 91.41, 95.29], 105, 88.56], 2.9675], ['partial repair probe 1', ['strike-call', [88.95, 80.84, None], 110, 112.81], 29.266667], ['partial repair probe 2', ['price-put', [119.4, 88.69, 99.38, 103.22, 102.55, None], 110, 112.44], 7.368333], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-put', [85.84, None], 90, 114.04], 4.16], ['normal control 2', ['price-put', [None, 105.9], 105, 82.58], 0.0], ['normal control 3', ['price-put', [111.7, 101.25, 103.45, 114.56, 86.59, None, 114.39], 100, 96.18], 0.0]], [['regression fallback priority 1', ['price-call', [119.01, None, 115.28], 105, 82.02], 12.766667], ['regression fallback priority 2', ['price-call', [84.27, 118.57, None, None, None, 103.89], 100, 80.29], 10.406667], ['partial repair probe 1', ['price-put', [None, 87.07, 89.34, None], 90, 115.78], 1.795], ['partial repair probe 2', ['price-put', [110.48, 85.44, None, None, None], 110, 102.6], 19.552], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-call', [80.8, 102.44, None, 103.49, 106.76, 93.24, None], 110, 93.16], 0.0], ['normal control 2', ['price-call', [96.59, None, 81.5, 86.37, 117.86, 81.57], 100, 80.38], 0.0], ['normal control 3', ['price-put', [93.19, None, None, None, None], 110, 115.13], 16.81]], [['regression fallback priority 1', ['price-put', [85.19, None, 83.45, 104.23, 100.65, None, 102.76], 110, 109.78], 15.411429], ['regression fallback priority 2', ['strike-call', [86.53, 95.55, None, 100.69, 106.44], 90, 104.86], 7.908], ['partial repair probe 1', ['strike-call', [81.55, 101.38, 89.48, None], 100, 100.13], 9.6575], ['partial repair probe 2', ['price-put', [88.01, 87.96, None], 105, 112.32], 17.023333], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-call', [None, 112.37, None, None], 90, 98.65], 22.37], ['normal control 2', ['price-call', [None, 110.4, 80.07], 90, 95.81], 10.29], ['normal control 3', ['price-put', [110.91, None], 100, 116.16], 0.0]], [['regression fallback priority 1', ['strike-call', [100.62, None, 111.91, 86.61, None], 100, 115.98], 18.706], ['regression fallback priority 2', ['price-put', [116.55, 87.44, 91.05, None, 97.68], 110, 114.79], 13.246], ['partial repair probe 1', ['strike-call', [87.08, 93.2, None, None, None, None], 110, 94.72], 2.54], ['partial repair probe 2', ['price-call', [104.79, 89.0, None, None], 100, 101.84], 0.0], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-call', [None, 98.32, 105.85, 84.08], 100, 117.74], 0.0], ['normal control 2', ['price-call', [80.35, 102.5], 105, 119.19], 0.0], ['normal control 3', ['price-call', [97.46, 119.62, 96.51, 117.63, 87.35, 92.18, 101.58, 80.54], 90, 100.37], 9.10875]]]
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 fallback priority 1 | 3.775 | 3.955 | Failed |
| regression fallback priority 2 | 1.271 | 1.844 | Failed |
| partial repair probe 1 | 0.0 | 3.116667 | Failed |
| partial repair probe 2 | 17.05 | 15.774 | Failed |
| boundary control 1 | 3.333333 | 3.333333 | Passed |
| normal control 1 | 0.743333 | 0.743333 | Passed |
| normal control 2 | 0.0 | 0.0 | Passed |
| normal control 3 | 3.65 | 3.65 | Passed |
SHA-256 / 35b0af2562e043643a737b5815134078478219a0d3dcf4f6dc5ffe4cc0723282
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(kind, fixings, strike, final):
vals = list(fixings)
filled = []
for i, v in enumerate(vals):
if v is None:
prev = [x for x in filled if x is not None]
later = [x for x in vals[i + 1:] if x is not None]
v = prev[-1] if prev else later[0]
filled.append(v)
avg = sum(filled) / len(filled)
if kind == 'price-call':
pay = max(avg - strike, 0.0)
elif kind == 'price-put':
pay = max(strike - avg, 0.0)
else:
pay = max(final - avg, 0.0)
return round(pay, 6)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression fallback priority 1', ['price-put', [107.56, 91.47, None, 93.63, 107.09, 85.05], 100, 107.47], 3.955], ['regression fallback priority 2', ['strike-call', [90.82, 116.42, 97.17, None, 102.9], 100, 102.74], 1.844], ['partial repair probe 1', ['strike-call', [119.87, 109.69, None], 110, 116.2], 3.116667], ['partial repair probe 2', ['price-call', [117.31, 109.18, 80.52, 110.93, None], 90, 89.18], 15.774], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-put', [115.48, 83.47, 98.82], 100, 99.98], 0.743333], ['normal control 2', ['strike-call', [111.57, None, None, None, 98.59, 116.83], 110, 84.9], 0.0], ['normal control 3', ['strike-call', [91.38, None, None], 105, 95.03], 3.65]], [['regression fallback priority 1', ['price-call', [98.02, None, 91.49, 95.03, 98.48, 97.35], 90, 119.72], 6.398333], ['regression fallback priority 2', ['price-put', [119.49, 98.97, 101.06, None, 95.58, 113.4, 91.41, 95.29], 105, 88.56], 2.9675], ['partial repair probe 1', ['strike-call', [88.95, 80.84, None], 110, 112.81], 29.266667], ['partial repair probe 2', ['price-put', [119.4, 88.69, 99.38, 103.22, 102.55, None], 110, 112.44], 7.368333], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-put', [85.84, None], 90, 114.04], 4.16], ['normal control 2', ['price-put', [None, 105.9], 105, 82.58], 0.0], ['normal control 3', ['price-put', [111.7, 101.25, 103.45, 114.56, 86.59, None, 114.39], 100, 96.18], 0.0]], [['regression fallback priority 1', ['price-call', [119.01, None, 115.28], 105, 82.02], 12.766667], ['regression fallback priority 2', ['price-call', [84.27, 118.57, None, None, None, 103.89], 100, 80.29], 10.406667], ['partial repair probe 1', ['price-put', [None, 87.07, 89.34, None], 90, 115.78], 1.795], ['partial repair probe 2', ['price-put', [110.48, 85.44, None, None, None], 110, 102.6], 19.552], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-call', [80.8, 102.44, None, 103.49, 106.76, 93.24, None], 110, 93.16], 0.0], ['normal control 2', ['price-call', [96.59, None, 81.5, 86.37, 117.86, 81.57], 100, 80.38], 0.0], ['normal control 3', ['price-put', [93.19, None, None, None, None], 110, 115.13], 16.81]], [['regression fallback priority 1', ['price-put', [85.19, None, 83.45, 104.23, 100.65, None, 102.76], 110, 109.78], 15.411429], ['regression fallback priority 2', ['strike-call', [86.53, 95.55, None, 100.69, 106.44], 90, 104.86], 7.908], ['partial repair probe 1', ['strike-call', [81.55, 101.38, 89.48, None], 100, 100.13], 9.6575], ['partial repair probe 2', ['price-put', [88.01, 87.96, None], 105, 112.32], 17.023333], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-call', [None, 112.37, None, None], 90, 98.65], 22.37], ['normal control 2', ['price-call', [None, 110.4, 80.07], 90, 95.81], 10.29], ['normal control 3', ['price-put', [110.91, None], 100, 116.16], 0.0]], [['regression fallback priority 1', ['strike-call', [100.62, None, 111.91, 86.61, None], 100, 115.98], 18.706], ['regression fallback priority 2', ['price-put', [116.55, 87.44, 91.05, None, 97.68], 110, 114.79], 13.246], ['partial repair probe 1', ['strike-call', [87.08, 93.2, None, None, None, None], 110, 94.72], 2.54], ['partial repair probe 2', ['price-call', [104.79, 89.0, None, None], 100, 101.84], 0.0], ['boundary control 1', ['price-call', [None, 100.0, 110.0], 100, 100.0], 3.333333], ['normal control 1', ['price-call', [None, 98.32, 105.85, 84.08], 100, 117.74], 0.0], ['normal control 2', ['price-call', [80.35, 102.5], 105, 119.19], 0.0], ['normal control 3', ['price-call', [97.46, 119.62, 96.51, 117.63, 87.35, 92.18, 101.58, 80.54], 90, 100.37], 9.10875]]]
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 fallback priority 1 | 3.955 | 3.955 | Passed |
| regression fallback priority 2 | 1.844 | 1.844 | Passed |
| partial repair probe 1 | 3.116667 | 3.116667 | Passed |
| partial repair probe 2 | 15.774 | 15.774 | Passed |
| boundary control 1 | 3.333333 | 3.333333 | Passed |
| normal control 1 | 0.743333 | 0.743333 | Passed |
| normal control 2 | 0.0 | 0.0 | Passed |
| normal control 3 | 3.65 | 3.65 | Passed |
SHA-256 / 2c5c22b8f85e355cda5820d337ef2f23b59f28d56e896d75357d27a982166ce1
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:55.059710+00:00.
Case digest / 392e78a2d1430a85bb192f4dc7f2c5a39167d901e9a8ef191b717425bd2cc5e9