#!/usr/bin/env python3
"""Exact synthetic option-chain examples; Python standard library only.

Not market data, a trained generator, a paper replication, or a full surface
validator. European calls share one expiry and cash-settlement reference.
Spot is 100, rates/dividends are zero, and contract multiplier is one.
All price/portfolio checks use rational arithmetic. Implied volatilities are
display-only numerical inversions of Black-Scholes at a half-year maturity.
"""

from fractions import Fraction as F
from math import erf, log, sqrt


def slopes(strikes, prices):
    return tuple((b - a) / (y - x) for x, y, a, b in
                 zip(strikes, strikes[1:], prices, prices[1:]))


def slice_checks(strikes, prices, spot=F(100)):
    """Necessary finite-grid checks, not a general no-arbitrage certificate."""
    assert len(strikes) == len(prices) >= 2
    assert all(x < y for x, y in zip(strikes, strikes[1:]))
    s = slopes(strikes, prices)
    return {
        "bounds": all(max(spot - k, 0) <= c <= spot
                      for k, c in zip(strikes, prices)),
        "monotone": all(x <= 0 for x in s),
        "vertical_spread_bounds": all(-1 <= x <= 0 for x in s),
        "convex": all(x <= y for x, y in zip(s, s[1:])),
    }


def butterfly_cost(prices):
    """Unit wings, short two center calls; requires equally spaced strikes."""
    return prices[0] - 2 * prices[1] + prices[2]


def executable_cost(bids, asks, fee_per_contract=F(0)):
    # Four contracts: buy the wings at ask and sell two center calls at bid.
    return asks[0] - 2 * bids[1] + asks[2] + 4 * fee_per_contract


def payoff(spot_at_expiry, strikes):
    calls = [max(spot_at_expiry - k, 0) for k in strikes]
    return butterfly_cost(calls)


def black_scholes_call(strike, vol, maturity=0.5, spot=100.0):
    def normal_cdf(x):
        return (1 + erf(x / sqrt(2))) / 2
    width = vol * sqrt(maturity)
    d1 = log(spot / strike) / width + width / 2
    return spot * normal_cdf(d1) - strike * normal_cdf(d1 - width)


def implied_volatility(strike, call_price):
    low, high = 1e-8, 5.0
    assert black_scholes_call(strike, low) < call_price
    assert call_price < black_scholes_call(strike, high)
    for _ in range(100):
        mid = (low + high) / 2
        if black_scholes_call(strike, mid) < call_price:
            low = mid
        else:
            high = mid
    vol = (low + high) / 2
    assert abs(black_scholes_call(strike, vol) - call_price) < 1e-10
    return vol


def main():
    strikes = tuple(map(F, (90, 100, 110)))
    mid = tuple(map(F, (12, 9, 5)))
    checks = slice_checks(strikes, mid)
    assert checks == {"bounds": True, "monotone": True,
                      "vertical_spread_bounds": True, "convex": False}
    assert butterfly_cost(mid) == -1
    assert not slice_checks(strikes, tuple(map(F, (12, 13, 5))))["monotone"]

    # The payoff is piecewise affine. Non-negativity at every breakpoint,
    # together with zero outer slopes, proves it for every nonnegative spot.
    values = [payoff(s, strikes) for s in (F(0), *strikes)]
    assert values == [0, 0, 10, 0]
    assert sum((F(1), F(-2), F(1))) == 0

    wide_bids = tuple(x - F("0.8") for x in mid)
    wide_asks = tuple(x + F("0.8") for x in mid)
    assert executable_cost(wide_bids, wide_asks) == F("2.2")

    # Stronger than failure to find one trade: an explicit probability law
    # with mean 100 generates call prices strictly inside all wide spreads.
    states = tuple(map(F, (0, 90, 100, 110, 130)))
    probabilities = (F(1, 36), F(103, 180), F(1, 20), F(1, 10), F(1, 4))
    assert all(p > 0 for p in probabilities) and sum(probabilities) == 1
    expected_spot = sum(s * p for s, p in zip(states, probabilities))
    assert expected_spot == 100
    shadow = tuple(sum(p * max(s - k, 0)
                       for s, p in zip(states, probabilities)) for k in strikes)
    assert shadow == (F("12.5"), F("8.5"), F("5.0"))
    assert all(b < c < a for b, c, a in zip(wide_bids, shadow, wide_asks))
    assert all(slice_checks(strikes, shadow).values())

    narrow_bids = tuple(x - F("0.1") for x in mid)
    narrow_asks = tuple(x + F("0.1") for x in mid)
    narrow_before = executable_cost(narrow_bids, narrow_asks)
    narrow_after = executable_cost(narrow_bids, narrow_asks, F("0.05"))
    assert narrow_before == F("-0.6") and narrow_after == F("-0.4")

    # Flat strike slices with falling IV but increasing total variance.
    t1, t2, vol1, vol2 = F(1, 4), F(1), F("0.4"), F("0.25")
    w1, w2 = t1 * vol1**2, t2 * vol2**2
    assert vol2 < vol1 and w2 > w1

    print("synthetic examples; rates=dividends=0; multiplier=1")
    print("midpoint checks: " + ", ".join(f"{k}={v}" for k, v in checks.items()))
    print("midpoint slopes: " + ", ".join(str(s) for s in slopes(strikes, mid)))
    ivs = [implied_volatility(float(k), float(c)) for k, c in zip(strikes, mid)]
    print("half-year implied volatilities: " + ", ".join(f"{v:.4%}" for v in ivs))
    print(f"midpoint butterfly cost: {float(butterfly_cost(mid)):.2f}")
    print(f"wide-spread executable cost: {float(executable_cost(wide_bids, wide_asks)):.2f}")
    print("wide-spread model prices: " + ", ".join(f"{float(c):.2f}" for c in shadow))
    print(f"model probability sum: {sum(probabilities)}; expected spot: {expected_spot}")
    print(f"narrow-spread executable cost before fees: {float(narrow_before):.2f}")
    print(f"narrow-spread executable cost after fees: {float(narrow_after):.2f}")
    print(f"falling IV: {float(vol1):.0%} -> {float(vol2):.0%}; total variance: {float(w1):.4f} -> {float(w2):.4f}")
    print("PASS: exact arithmetic, payoff breakpoints, model certificate, IV inversion")


if __name__ == "__main__":
    main()
