"""
Reconstruction of the "early-volume edge" finding, built fresh to verify it
survives the same scrutiny that broke the paper quoter today.

WHAT WENT WRONG TODAY (in mm_quoter.py / mm_settle.py, NOT this data): a
taker/maker mapping that was briefly inverted, a raw-string timestamp
comparison that silently filtered out every trade, and a fill test that
counted IN-PLAY trades as if they were pre-kickoff resting fills. All three
inflated results. This script re-derives the ORIGINAL historical finding from
mm_trades.csv with none of those mechanisms present, and prints explicit sanity
checks rather than asserting the number is right.

METHOD: for every trade on a settled market, compute what SHARE of that
market's total pre-kickoff volume had already traded before this trade (0.0 =
first trade, 1.0 = last). Bucket into deciles. For each decile, compute the
REALISED MAKER P&L using the verified taker_book_side mapping:
    taker_book_side == 'bid' -> taker BOUGHT -> MAKER SOLD at `price`
    taker_book_side == 'ask' -> taker SOLD   -> MAKER BOUGHT at `price`
This uses REAL trades that REALLY happened between REAL counterparties -- it is
not a simulation of our own resting order, so it cannot inherit the fill-timing
bugs that hit the paper quoter. It answers "what did the maker side of this
market earn", not "would WE have gotten that fill" (queue priority is still
unknowable from history -- unchanged caveat).

SANITY CHECKS (printed, not assumed):
  1. takers must be net BUYERS (~3-4:1) -- if not, the mapping is backwards.
  2. total notional and event count should roughly match kalshi_backfill.csv's
     641 events -- if far off, the join or filter is broken.

Usage:  python research\\mm_decile_by_volume.py
"""
import csv
import math
import statistics
import datetime as dt
from collections import defaultdict

TRADES = r'E:\soccer\research\mm_trades.csv'
BACKFILL = r'E:\soccer\research\kalshi_backfill.csv'


def event_of(ticker):
    return '-'.join(ticker.split('-')[:2])


def pt(s):
    """Parse to an aware datetime. Never compare timestamp strings directly --
    that silently dropped every row in an earlier version of this analysis."""
    try:
        return dt.datetime.fromisoformat(str(s).replace('Z', '+00:00'))
    except (ValueError, TypeError):
        return None


def load_kickoffs():
    out = {}
    with open(BACKFILL, newline='', encoding='utf-8') as fh:
        for r in csv.DictReader(fh):
            k = pt(r.get('kickoff'))
            if k:
                out[r['event']] = k
    return out


def main():
    ko = load_kickoffs()
    rows = list(csv.DictReader(open(TRADES, newline='', encoding='utf-8')))
    events = {event_of(r['ticker']) for r in rows}
    print(f'{len(rows):,} trade rows loaded, {len(events)} distinct events')
    print(f'kickoffs loaded for {len(ko)} events')

    # ---- sanity check 1: buy/sell balance ----
    buy_ct = sum(float(r['count']) for r in rows if r['taker_book_side'] == 'bid')
    sell_ct = sum(float(r['count']) for r in rows if r['taker_book_side'] == 'ask')
    print(f'sanity: taker BOUGHT {buy_ct:,.0f} contracts, SOLD {sell_ct:,.0f} '
          f'-> buy:sell = {buy_ct/max(sell_ct,1):.1f}:1 '
          f'({"OK, retail backs teams" if buy_ct > sell_ct else "SUSPICIOUS -- check mapping"})')

    # ---- PRE-KICKOFF ONLY, and a sane trading range ----
    # Without the kickoff cutoff, in-play trades near full time (where the
    # outcome is nearly certain) get bucketed into the LATE deciles and swamp
    # them with informed one-sided flow -- this is exactly what broke the first
    # run of this script (deciles 80-100% jumped to +2.6%/+10.4%, non-monotone).
    kept = []
    dropped_no_ko = dropped_inplay = 0
    for r in rows:
        e = event_of(r['ticker'])
        k = ko.get(e)
        if k is None:
            dropped_no_ko += 1
            continue
        t = pt(r['ts'])
        if t is None or t >= k:
            dropped_inplay += 1
            continue
        if not (0.05 <= float(r['price']) <= 0.95):
            continue
        kept.append(r)
    # CRITICAL: sort by (event, real timestamp). Trades were fetched per LEG
    # (home, then draw, then away) and appended in that order by
    # mm_feasibility.py, so within an event the rows are NOT chronological
    # across legs -- they are all of home's trades, then all of draw's, then
    # all of away's. Computing "cumulative volume share" without re-sorting
    # first measures which LEG a trade belongs to, not how early it happened,
    # and produced a non-monotone, wrong result (deciles 80-100% spuriously
    # elevated). This is the mistake, not the data.
    def keyf(r):
        t = pt(r['ts'])
        return (event_of(r['ticker']), t if t else dt.datetime.min.replace(tzinfo=dt.timezone.utc))
    kept.sort(key=keyf)
    rows = kept
    print(f'dropped (no kickoff on record): {dropped_no_ko:,}')
    print(f'dropped (in-play, at/after kickoff): {dropped_inplay:,}')
    print(f'PRE-KICKOFF, 5c-95c, TIME-SORTED: {len(rows):,} rows, '
          f'{len({event_of(r["ticker"]) for r in rows})} events\n')

    # ---- cumulative volume position within each EVENT (not leg) ----
    # Sort by the order Kalshi returned trades in (already chronological per the
    # /markets/trades pagination) grouped by event across all 3 legs, since a
    # market's "volume" is the combined activity across home/draw/away.
    by_event = defaultdict(list)
    for r in rows:
        by_event[event_of(r['ticker'])].append(r)

    bucket = defaultdict(lambda: [0.0, 0.0])   # decile -> [pnl_dollars, notional]
    bucket_events = defaultdict(set)
    for ev, trs in by_event.items():
        total = sum(float(r['count']) for r in trs)
        if total <= 0:
            continue
        cum = 0.0
        for r in trs:   # rows keep the API's chronological order within an event
            c = float(r['count'])
            frac_before = cum / total
            cum += c
            p, res = float(r['price']), int(r['result_yes'])
            if r['taker_book_side'] == 'bid':
                pnl = p * c - res * c        # maker SOLD
            else:
                pnl = res * c - p * c        # maker BOUGHT
            d = min(int(frac_before * 10), 9)
            bucket[d][0] += pnl
            bucket[d][1] += p * c
            bucket_events[d].add(ev)

    print(f'{"decile":9s} {"events":>7s} {"notional":>13s} {"ROI":>8s}')
    for d in range(10):
        pnl, noti = bucket[d]
        if noti < 1000:
            continue
        n_ev = len(bucket_events[d])
        flag = '  <-- opening trades' if d == 0 else ''
        print(f'{d*10:3d}-{d*10+10:3d}%  {n_ev:7d}  ${noti:12,.0f}  {pnl/noti*100:+7.2f}%{flag}')

    print('\nsanity check 2: decile-0 event count should be close to the total')
    print(f'  (every event contributes exactly one opening trade) -> decile-0 events '
          f'= {len(bucket_events[0])} vs {len(events)} total events loaded')


if __name__ == '__main__':
    main()
