"""Charging more, tested CAUSALLY off the trade tape.

WHY THIS REPLACES mm_charge_more_history.py: that version priced our offer from
kalshi_backfill's ask, which is snapped at KICKOFF, then counted trades from the
hours BEFORE it. Any leg whose price drifted down therefore scored a guaranteed
fill against a price it only reached later. It reported 90% fills at +5c and
E[net] rising monotonically to +14.7c/book -- which is self-evidently broken,
because the three legs sum to ~100c and cannot all trade 5c above their asks.

The fix is ordering: our posting price must be fixed from information available
BEFORE the trades that fill it.

  T_post = kickoff - POST_H
  level  = each leg's LAST trade price at or before T_post   (known at T_post)
  our ask = level + k
  fill    = a maker-sell trade (taker_book_side='bid') in (T_post, kickoff)
            printing at or above our ask

Everything used to decide is in the past; everything used to fill is in the
future. Sample is the whole 2.28M-row tape, not just the backfill snapshots.

Remaining caveats, stated rather than buried:
  - last-trade price is a noisy stand-in for the ask (trades print on both
    sides), so `level` is biased slightly low vs a true ask. That inflates
    fills at every k equally; it does not manufacture the k-slope, which is
    what this test is about.
  - queue priority is still ignored, but posting ABOVE the touch makes us the
    only offer at that price, so k>0 is the least distorted region.
"""
import csv
import datetime as dt
import statistics
from collections import defaultdict

BACKFILL = r'E:\soccer\research\kalshi_backfill.csv'
TRADES = r'E:\soccer\research\mm_trades.csv'
POST_H = 6.0
MAX_K = 5
FEE = lambda p: 0.07 * p * (1 - p) * 100


def _pt(s):
    try:
        d = dt.datetime.fromisoformat(s.replace('Z', '+00:00'))
    except (ValueError, AttributeError):
        return None
    return d if d.tzinfo else d.replace(tzinfo=dt.timezone.utc)


def kickoffs():
    out = {}
    with open(BACKFILL, encoding='utf-8') as fh:
        for r in csv.DictReader(fh):
            ko = _pt(r['kickoff'])
            if ko:
                out[r['event']] = (ko, r['div'])
    return out


def tape(kos):
    """event -> leg suffix -> [(ts, price, maker_sold)] sorted."""
    legs = defaultdict(lambda: defaultdict(list))
    with open(TRADES, encoding='utf-8') as fh:
        for r in csv.DictReader(fh):
            tk = r['ticker']
            i = tk.rfind('-')
            if i < 0:
                continue
            ev = tk[:i]
            if ev not in kos:
                continue
            t = _pt(r['ts'])
            if t is None or t >= kos[ev][0]:
                continue
            try:
                p = float(r['price'])
            except ValueError:
                continue
            legs[ev][tk[i + 1:]].append((t, p, r['taker_book_side'] == 'bid'))
    for ev in legs:
        for s in legs[ev]:
            legs[ev][s].sort(key=lambda x: x[0])
    return legs


def main():
    kos = kickoffs()
    legs = tape(kos)
    books = []
    for ev, sides in legs.items():
        if len(sides) != 3:
            continue
        ko = kos[ev][0]
        tp = ko - dt.timedelta(hours=POST_H)
        lv, fut = {}, {}
        ok = True
        for s, rows in sides.items():
            past = [p for (t, p, _) in rows if t <= tp]
            after = [(p, m) for (t, p, m) in rows if t > tp]
            if not past or not after:
                ok = False
                break
            lv[s] = past[-1]
            fut[s] = after
        if not ok:
            continue
        tot = sum(lv.values()) * 100
        if not (90 < tot < 112):          # incoherent / in-play snapshot
            continue
        books.append({'ev': ev, 'div': kos[ev][1], 'lv': lv, 'fut': fut,
                      'gross0': tot - 100})
    print(f'{len(books)} books with a clean {POST_H:g}h-pre-KO level on all 3 legs')
    print(f'median gross at k=0: {statistics.median([b["gross0"] for b in books]):+.2f}c\n')

    print(f"  {'k':>3s} {'leg fill %':>11s} {'all-3 %':>9s} {'2-of-3 %':>9s} "
          f"{'mean gross':>11s} {'E[net]/book':>12s}")
    for k in range(0, MAX_K + 1):
        lf, per = [], []
        for b in books:
            nf = 0
            for s, level in b['lv'].items():
                our = level + k / 100.0
                hit = any(m and p >= our - 1e-9 for (p, m) in b['fut'][s])
                lf.append(hit)
                nf += hit
            gross = b['gross0'] + 3 * k
            if nf == 3:
                net = gross
            elif nf == 2:
                net = gross - k - 1.0 - FEE(min(b['lv'].values()))
            else:
                net = 0.0
            per.append({'nf': nf, 'net': net, 'gross': gross})
        n = len(per)
        print(f'  {k:3d} {sum(lf)/len(lf)*100:10.1f}% '
              f'{sum(1 for x in per if x["nf"]==3)/n*100:8.1f}% '
              f'{sum(1 for x in per if x["nf"]==2)/n*100:8.1f}% '
              f'{statistics.mean([x["gross"] for x in per]):10.2f}c '
              f'{statistics.mean([x["net"] for x in per]):11.2f}c')

    print('\nSANITY: all three legs cannot rise together (they sum to ~100c), so')
    print('all-3 fill % MUST fall steeply in k. If it does not, the test is broken.')


if __name__ == '__main__':
    main()
