"""Reconstruct the historical cost of flattening a book at its FIRST FILL.

THE QUESTION THIS ANSWERS
-------------------------
Once one leg of a three-leg book sells, the maker either keeps quoting the two
survivors (and risks a strand at about -$0.76) or buys the filled leg back and
walks away. Every policy comparison so far has had to assume that buy-back
price -- reported as "@2c / @5c / @10c" columns -- because nothing logged it.

This measures it from what WAS logged:

  * mm_fill_dataset.csv gives, per leg posting episode, ts_first,
    seconds_to_fill and price_cents -- so fill_ts and the price WE SOLD AT.
  * mm_live_legs.csv snapshots every quoted leg each cycle with the market's
    ask_at_post / bid_at_post at that instant, and keeps logging a leg after it
    fills (verified: 36 of 40 sampled first-filled legs have a post-fill snap).

Buying back a leg we are short means CROSSING UP -- we pay the ask. So

    flatten_cost_per_contract = ask_at(fill_ts) - price_sold

WHY THE SIGN MATTERS. A book strands precisely because the market moved away
from the survivors, which usually means it moved TOWARD the leg that filled.
The naive assumption "flatten cost is about the spread" is therefore optimistic
in exactly the cases where flattening would be worth doing. That optimism is
what made an earlier repair analysis look like +$11.45 before it evaporated, so
this script deliberately reports the cost separately for books that went on to
strand and books that completed.

TOLERANCE. The nearest snapshot at or after fill_ts within MAX_LAG is used. A
leg is skipped rather than guessed at if nothing lands inside the window.
"""
import numpy as np
import pandas as pd

MAX_LAG = pd.Timedelta(minutes=10)
FILL_CSV = 'research/mm_fill_dataset.csv'
LEGS_CSV = 'research/mm_live_legs.csv'
BOOK_CSV = 'research/mm_book_dataset.csv'
OUT_CSV = 'research/mm_flatten_cost.csv'


def first_fills():
    fd = pd.read_csv(FILL_CSV, usecols=['ts_first', 'event', 'ticker', 'side',
                                        'price_cents', 'filled', 'seconds_to_fill'])
    fd = fd[fd['filled'].astype(str).isin(['1', 'True', 'true'])].copy()
    fd['ts_first'] = pd.to_datetime(fd['ts_first'], errors='coerce', utc=True)
    secs = pd.to_numeric(fd['seconds_to_fill'], errors='coerce')
    fd['fill_ts'] = fd['ts_first'] + pd.to_timedelta(secs, unit='s')
    fd['sold_px'] = pd.to_numeric(fd['price_cents'], errors='coerce') / 100.0
    fd = fd.dropna(subset=['fill_ts', 'sold_px'])
    return fd.sort_values('fill_ts').groupby('event', as_index=False).first()


def ask_at(legs, ticker, when):
    """Market ask on `ticker` at the first snapshot at/after `when`."""
    sub = legs.get(ticker)
    if sub is None:
        return None, None
    i = sub['ts'].searchsorted(when, side='left')
    if i >= len(sub):
        return None, None
    row = sub.iloc[i]
    lag = row['ts'] - when
    if lag > MAX_LAG:
        return None, None
    return row['ask_at_post'], lag.total_seconds()


def main():
    ff = first_fills()
    legs = pd.read_csv(LEGS_CSV, usecols=['ts', 'ticker', 'ask_at_post', 'bid_at_post'])
    legs['ts'] = pd.to_datetime(legs['ts'], errors='coerce', utc=True)
    legs['ask_at_post'] = pd.to_numeric(legs['ask_at_post'], errors='coerce')
    legs = legs.dropna(subset=['ts', 'ask_at_post']).sort_values('ts')
    idx = {t: g.reset_index(drop=True) for t, g in legs.groupby('ticker')}

    rows = []
    for r in ff.itertuples():
        a, lag = ask_at(idx, r.ticker, r.fill_ts)
        if a is None:
            continue
        rows.append({'event': r.event, 'ticker': r.ticker, 'fill_ts': r.fill_ts,
                     'sold_px': r.sold_px, 'ask_at_fill': a,
                     'flatten_cost': a - r.sold_px, 'lag_s': lag})
    out = pd.DataFrame(rows)

    book = pd.read_csv(BOOK_CSV, usecols=['event', 'shape', 'pnl', 'venue',
                                          'has_taker', 'legs_quoted', 'size'])
    book = book[(book.venue == 'kalshi') & (book.has_taker == 0) & (book.legs_quoted == 3)]
    out = out.merge(book, on='event', how='inner')
    out.to_csv(OUT_CSV, index=False)

    print('reconstructed first-fill flatten cost for %d books  ->  %s' % (len(out), OUT_CSV))
    print('  median snapshot lag: %.0fs' % out['lag_s'].median())
    print()
    print('FLATTEN COST PER CONTRACT (ask at fill  minus  price we sold at)')
    print('  %-14s %5s %9s %9s %9s %9s' % ('outcome', 'n', 'median', 'mean', 'p25', 'p75'))
    for sh, g in out.groupby('shape'):
        c = g['flatten_cost']
        print('  %-14s %5d %+9.4f %+9.4f %+9.4f %+9.4f'
              % (sh, len(g), c.median(), c.mean(), c.quantile(.25), c.quantile(.75)))
    c = out['flatten_cost']
    print('  %-14s %5d %+9.4f %+9.4f %+9.4f %+9.4f'
          % ('ALL', len(out), c.median(), c.mean(), c.quantile(.25), c.quantile(.75)))

    print()
    print('THE ASYMMETRY THAT DECIDES EVERY POLICY:')
    st = out[out.shape_ == 'stranded'] if 'shape_' in out else out[out['shape'] == 'stranded']
    cl = out[out['shape'] == 'clean']
    if len(st) and len(cl):
        print('  books that went on to STRAND: median flatten cost %+0.4f (n=%d)'
              % (st['flatten_cost'].median(), len(st)))
        print('  books that COMPLETED        : median flatten cost %+0.4f (n=%d)'
              % (cl['flatten_cost'].median(), len(cl)))
        print('  -> flattening is %s on the books where you would want it'
              % ('MORE expensive' if st['flatten_cost'].median() > cl['flatten_cost'].median()
                 else 'no more expensive'))
    return out


if __name__ == '__main__':
    main()
