FAILURE MAP
← Case archive

FA-61591 / Options payoff and settlement / Open access

Listed option expiration date calendar: the first Friday offset can be negative · case 01

In months starting on a Saturday or Sunday the expiry lands a week early.

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

ROOT CAUSE

The offset to the first Friday omits the modulo 7.

VERIFIED REPAIR

Use (4 - weekday) mod 7 as the offset to the first Friday.

Unsuccessful approach: Forcing a full week when the month starts on Friday skips the first Friday.

Case contract

Inputs year, month, kind and holidays. Monthly expiry is the third Friday of the month; quarterly expiry is the last calendar day of the quarter-end month (Mar/Jun/Sep/Dec) containing the month. Either date rolls back to the preceding business day (weekday not in holidays) when it is not a business day. Return [y,m,d].

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 datetime
N = 1
observations = []
def solve(y, m, kind, holidays):
    H = {datetime.date(*h) for h in holidays}
    def biz(x):
        return x.weekday() < 5 and x not in H
    def mlen(yy, mm):
        if mm == 2:
            return 29 if (yy % 4 == 0 and yy % 100 != 0) or yy % 400 == 0 else 28
        return 30 if mm in (4, 6, 9, 11) else 31
    if kind == 'quarterly':
        qm = ((m - 1) // 3 + 1) * 3
        x = datetime.date(y, qm, mlen(y, qm))
    else:
        first = datetime.date(y, m, 1)
        offset = 4 - first.weekday()
        x = first + datetime.timedelta(days=offset + 14)
    while not biz(x):
        x -= datetime.timedelta(days=1)
    return [x.year, x.month, x.day]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression first friday offset 1', [2032, 2, 'monthly', []], [2032, 2, 20]], ['regression first friday offset 2', [2030, 9, 'monthly', [[2030, 9, 20]]], [2030, 9, 19]], ['partial repair probe 1', [2023, 12, 'monthly', []], [2023, 12, 15]], ['partial repair probe 2', [2029, 6, 'monthly', []], [2029, 6, 15]], ['boundary control 1', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['boundary control 2', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['normal control 1', [2028, 8, 'monthly', []], [2028, 8, 18]], ['normal control 2', [2022, 3, 'quarterly', [[2022, 3, 28]]], [2022, 3, 31]]], [['regression first friday offset 1', [2023, 7, 'monthly', []], [2023, 7, 21]], ['regression first friday offset 2', [2031, 6, 'monthly', [[2031, 6, 20], [2031, 6, 19]]], [2031, 6, 18]], ['partial repair probe 1', [2025, 8, 'monthly', []], [2025, 8, 15]], ['partial repair probe 2', [2019, 2, 'monthly', [[2019, 2, 15], [2019, 2, 14]]], [2019, 2, 13]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2021, 3, 'quarterly', []], [2021, 3, 31]], ['normal control 2', [2030, 11, 'quarterly', [[2030, 11, 15]]], [2030, 12, 31]]], [['regression first friday offset 1', [2023, 7, 'monthly', []], [2023, 7, 21]], ['regression first friday offset 2', [2020, 2, 'monthly', [[2020, 2, 21], [2020, 2, 20]]], [2020, 2, 19]], ['partial repair probe 1', [2029, 6, 'monthly', []], [2029, 6, 15]], ['partial repair probe 2', [2031, 8, 'monthly', []], [2031, 8, 15]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2027, 3, 'monthly', [[2027, 3, 19], [2027, 3, 18]]], [2027, 3, 17]], ['normal control 2', [2022, 5, 'quarterly', [[2022, 6, 29]]], [2022, 6, 30]]], [['regression first friday offset 1', [2020, 2, 'monthly', []], [2020, 2, 21]], ['regression first friday offset 2', [2026, 3, 'monthly', [[2026, 3, 20]]], [2026, 3, 19]], ['partial repair probe 1', [2026, 5, 'monthly', []], [2026, 5, 15]], ['partial repair probe 2', [2019, 11, 'monthly', [[2019, 11, 15], [2019, 11, 14]]], [2019, 11, 13]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2031, 1, 'quarterly', [[2031, 1, 17], [2031, 3, 28]]], [2031, 3, 31]], ['normal control 2', [2021, 4, 'quarterly', [[2021, 4, 16], [2021, 4, 15]]], [2021, 6, 30]]], [['regression first friday offset 1', [2025, 3, 'monthly', [[2025, 3, 21]]], [2025, 3, 20]], ['regression first friday offset 2', [2021, 8, 'monthly', []], [2021, 8, 20]], ['partial repair probe 1', [2022, 7, 'monthly', [[2022, 7, 15]]], [2022, 7, 14]], ['partial repair probe 2', [2025, 8, 'monthly', []], [2025, 8, 15]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2029, 2, 'monthly', []], [2029, 2, 16]], ['normal control 2', [2032, 7, 'quarterly', []], [2032, 9, 30]]]]
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 first friday offset 1[2032, 2, 13][2032, 2, 20]Failed
regression first friday offset 2[2030, 9, 13][2030, 9, 19]Failed
partial repair probe 1[2023, 12, 15][2023, 12, 15]Passed
partial repair probe 2[2029, 6, 15][2029, 6, 15]Passed
boundary control 1[2024, 3, 29][2024, 3, 29]Passed
boundary control 2[2024, 12, 31][2024, 12, 31]Passed
normal control 1[2028, 8, 18][2028, 8, 18]Passed
normal control 2[2022, 3, 31][2022, 3, 31]Passed

SHA-256 / 307d045a6bba931d7e97721251d14531bfb89d182bec9e61e0c8a2f1e0c21b31

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import datetime
N = 1
observations = []
def solve(y, m, kind, holidays):
    H = {datetime.date(*h) for h in holidays}
    def biz(x):
        return x.weekday() < 5 and x not in H
    def mlen(yy, mm):
        if mm == 2:
            return 29 if (yy % 4 == 0 and yy % 100 != 0) or yy % 400 == 0 else 28
        return 30 if mm in (4, 6, 9, 11) else 31
    if kind == 'quarterly':
        qm = ((m - 1) // 3 + 1) * 3
        x = datetime.date(y, qm, mlen(y, qm))
    else:
        first = datetime.date(y, m, 1)
        offset = (4 - first.weekday()) % 7 or 7
        x = first + datetime.timedelta(days=offset + 14)
    while not biz(x):
        x -= datetime.timedelta(days=1)
    return [x.year, x.month, x.day]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression first friday offset 1', [2032, 2, 'monthly', []], [2032, 2, 20]], ['regression first friday offset 2', [2030, 9, 'monthly', [[2030, 9, 20]]], [2030, 9, 19]], ['partial repair probe 1', [2023, 12, 'monthly', []], [2023, 12, 15]], ['partial repair probe 2', [2029, 6, 'monthly', []], [2029, 6, 15]], ['boundary control 1', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['boundary control 2', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['normal control 1', [2028, 8, 'monthly', []], [2028, 8, 18]], ['normal control 2', [2022, 3, 'quarterly', [[2022, 3, 28]]], [2022, 3, 31]]], [['regression first friday offset 1', [2023, 7, 'monthly', []], [2023, 7, 21]], ['regression first friday offset 2', [2031, 6, 'monthly', [[2031, 6, 20], [2031, 6, 19]]], [2031, 6, 18]], ['partial repair probe 1', [2025, 8, 'monthly', []], [2025, 8, 15]], ['partial repair probe 2', [2019, 2, 'monthly', [[2019, 2, 15], [2019, 2, 14]]], [2019, 2, 13]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2021, 3, 'quarterly', []], [2021, 3, 31]], ['normal control 2', [2030, 11, 'quarterly', [[2030, 11, 15]]], [2030, 12, 31]]], [['regression first friday offset 1', [2023, 7, 'monthly', []], [2023, 7, 21]], ['regression first friday offset 2', [2020, 2, 'monthly', [[2020, 2, 21], [2020, 2, 20]]], [2020, 2, 19]], ['partial repair probe 1', [2029, 6, 'monthly', []], [2029, 6, 15]], ['partial repair probe 2', [2031, 8, 'monthly', []], [2031, 8, 15]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2027, 3, 'monthly', [[2027, 3, 19], [2027, 3, 18]]], [2027, 3, 17]], ['normal control 2', [2022, 5, 'quarterly', [[2022, 6, 29]]], [2022, 6, 30]]], [['regression first friday offset 1', [2020, 2, 'monthly', []], [2020, 2, 21]], ['regression first friday offset 2', [2026, 3, 'monthly', [[2026, 3, 20]]], [2026, 3, 19]], ['partial repair probe 1', [2026, 5, 'monthly', []], [2026, 5, 15]], ['partial repair probe 2', [2019, 11, 'monthly', [[2019, 11, 15], [2019, 11, 14]]], [2019, 11, 13]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2031, 1, 'quarterly', [[2031, 1, 17], [2031, 3, 28]]], [2031, 3, 31]], ['normal control 2', [2021, 4, 'quarterly', [[2021, 4, 16], [2021, 4, 15]]], [2021, 6, 30]]], [['regression first friday offset 1', [2025, 3, 'monthly', [[2025, 3, 21]]], [2025, 3, 20]], ['regression first friday offset 2', [2021, 8, 'monthly', []], [2021, 8, 20]], ['partial repair probe 1', [2022, 7, 'monthly', [[2022, 7, 15]]], [2022, 7, 14]], ['partial repair probe 2', [2025, 8, 'monthly', []], [2025, 8, 15]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2029, 2, 'monthly', []], [2029, 2, 16]], ['normal control 2', [2032, 7, 'quarterly', []], [2032, 9, 30]]]]
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 first friday offset 1[2032, 2, 20][2032, 2, 20]Passed
regression first friday offset 2[2030, 9, 19][2030, 9, 19]Passed
partial repair probe 1[2023, 12, 22][2023, 12, 15]Failed
partial repair probe 2[2029, 6, 22][2029, 6, 15]Failed
boundary control 1[2024, 3, 29][2024, 3, 29]Passed
boundary control 2[2024, 12, 31][2024, 12, 31]Passed
normal control 1[2028, 8, 18][2028, 8, 18]Passed
normal control 2[2022, 3, 31][2022, 3, 31]Passed

SHA-256 / 4be18ce517374c078594ccf5b6624b4bdda8c1817387e005856f69011d165e7c

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import datetime
N = 1
observations = []
def solve(y, m, kind, holidays):
    H = {datetime.date(*h) for h in holidays}
    def biz(x):
        return x.weekday() < 5 and x not in H
    def mlen(yy, mm):
        if mm == 2:
            return 29 if (yy % 4 == 0 and yy % 100 != 0) or yy % 400 == 0 else 28
        return 30 if mm in (4, 6, 9, 11) else 31
    if kind == 'quarterly':
        qm = ((m - 1) // 3 + 1) * 3
        x = datetime.date(y, qm, mlen(y, qm))
    else:
        first = datetime.date(y, m, 1)
        offset = (4 - first.weekday()) % 7
        x = first + datetime.timedelta(days=offset + 14)
    while not biz(x):
        x -= datetime.timedelta(days=1)
    return [x.year, x.month, x.day]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression first friday offset 1', [2032, 2, 'monthly', []], [2032, 2, 20]], ['regression first friday offset 2', [2030, 9, 'monthly', [[2030, 9, 20]]], [2030, 9, 19]], ['partial repair probe 1', [2023, 12, 'monthly', []], [2023, 12, 15]], ['partial repair probe 2', [2029, 6, 'monthly', []], [2029, 6, 15]], ['boundary control 1', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['boundary control 2', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['normal control 1', [2028, 8, 'monthly', []], [2028, 8, 18]], ['normal control 2', [2022, 3, 'quarterly', [[2022, 3, 28]]], [2022, 3, 31]]], [['regression first friday offset 1', [2023, 7, 'monthly', []], [2023, 7, 21]], ['regression first friday offset 2', [2031, 6, 'monthly', [[2031, 6, 20], [2031, 6, 19]]], [2031, 6, 18]], ['partial repair probe 1', [2025, 8, 'monthly', []], [2025, 8, 15]], ['partial repair probe 2', [2019, 2, 'monthly', [[2019, 2, 15], [2019, 2, 14]]], [2019, 2, 13]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2021, 3, 'quarterly', []], [2021, 3, 31]], ['normal control 2', [2030, 11, 'quarterly', [[2030, 11, 15]]], [2030, 12, 31]]], [['regression first friday offset 1', [2023, 7, 'monthly', []], [2023, 7, 21]], ['regression first friday offset 2', [2020, 2, 'monthly', [[2020, 2, 21], [2020, 2, 20]]], [2020, 2, 19]], ['partial repair probe 1', [2029, 6, 'monthly', []], [2029, 6, 15]], ['partial repair probe 2', [2031, 8, 'monthly', []], [2031, 8, 15]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2027, 3, 'monthly', [[2027, 3, 19], [2027, 3, 18]]], [2027, 3, 17]], ['normal control 2', [2022, 5, 'quarterly', [[2022, 6, 29]]], [2022, 6, 30]]], [['regression first friday offset 1', [2020, 2, 'monthly', []], [2020, 2, 21]], ['regression first friday offset 2', [2026, 3, 'monthly', [[2026, 3, 20]]], [2026, 3, 19]], ['partial repair probe 1', [2026, 5, 'monthly', []], [2026, 5, 15]], ['partial repair probe 2', [2019, 11, 'monthly', [[2019, 11, 15], [2019, 11, 14]]], [2019, 11, 13]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2031, 1, 'quarterly', [[2031, 1, 17], [2031, 3, 28]]], [2031, 3, 31]], ['normal control 2', [2021, 4, 'quarterly', [[2021, 4, 16], [2021, 4, 15]]], [2021, 6, 30]]], [['regression first friday offset 1', [2025, 3, 'monthly', [[2025, 3, 21]]], [2025, 3, 20]], ['regression first friday offset 2', [2021, 8, 'monthly', []], [2021, 8, 20]], ['partial repair probe 1', [2022, 7, 'monthly', [[2022, 7, 15]]], [2022, 7, 14]], ['partial repair probe 2', [2025, 8, 'monthly', []], [2025, 8, 15]], ['boundary control 1', [2024, 12, 'quarterly', []], [2024, 12, 31]], ['boundary control 2', [2024, 3, 'quarterly', []], [2024, 3, 29]], ['normal control 1', [2029, 2, 'monthly', []], [2029, 2, 16]], ['normal control 2', [2032, 7, 'quarterly', []], [2032, 9, 30]]]]
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 first friday offset 1[2032, 2, 20][2032, 2, 20]Passed
regression first friday offset 2[2030, 9, 19][2030, 9, 19]Passed
partial repair probe 1[2023, 12, 15][2023, 12, 15]Passed
partial repair probe 2[2029, 6, 15][2029, 6, 15]Passed
boundary control 1[2024, 3, 29][2024, 3, 29]Passed
boundary control 2[2024, 12, 31][2024, 12, 31]Passed
normal control 1[2028, 8, 18][2028, 8, 18]Passed
normal control 2[2022, 3, 31][2022, 3, 31]Passed

SHA-256 / 9e8ff4f68a3398554bd2c78784e1ce88aa55f9635e8328b486cefd91f14466bd

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

Case digest / 409e62400f938ce7e56a5d7e29e76a6cd11c4f1ab9a0b6cf1bd1a534c8e1800b