"""In-play swings: how big, and what would they do to a resting ask?

Clarification from the user: "volatility" meant the GAME swinging -- a goal, an
equaliser, a winner in stoppage time -- not the pre-kickoff price drift I had
been measuring. That is a different regime entirely, and one the maker has never
touched: both quoters require `0 < hrs`, so everything stops at kickoff.

Two things decide whether it is worth going there.

  SIZE     -- how much bigger are in-play moves than pre-match ones? If a goal
              reprices a leg by 20c, a resting ask is certain to be reached.

  POISON   -- and that is exactly the problem. A leg fills BECAUSE the goal made
              it more likely, so we sell at the old price into the new truth.
              Pre-match that adverse selection is worth fractions of a cent; a
              goal is worth tens. Measured here as: after a leg trades up
              through a level, where does it settle?

The three-leg structure is the only reason this could work at all -- a completed
book does not care who scores. But it takes one leg filling on a goal, with the
other two left behind, to lose more than a hundred completed books earn.
"""
import csv, collections, os, statistics, sys

sys.path.insert(0, r'E:\soccer\main')
sys.path.insert(0, r'E:\soccer\research')
import mm_settle as S

LIVE_DIR = r'E:\soccer\live_data'
SIDES = {'home': 'kalshi_home_mid', 'draw': 'kalshi_draw_mid',
         'away': 'kalshi_away_mid'}
DAYS = 25


def _f(x):
    try:
        v = float(x)
        return v if 0 < v < 1 else None
    except (TypeError, ValueError):
        return None


def load():
    files = sorted(f for f in os.listdir(LIVE_DIR) if f.endswith('.csv'))[-DAYS:]
    legs = collections.defaultdict(lambda: {'pre': [], 'live': []})
    for fn in files:
        with open(os.path.join(LIVE_DIR, fn), encoding='utf-8',
                  errors='replace') as fh:
            for r in csv.DictReader(fh):
                ts = S._pt(r.get('timestamp'))
                el = r.get('elapsed')
                if ts is None or el in (None, ''):
                    continue
                try:
                    el = float(el)
                except ValueError:
                    continue
                base = (r.get('game_date'), r.get('home'), r.get('away'))
                for side, col in SIDES.items():
                    m = _f(r.get(col))
                    if m is None:
                        continue
                    d = legs[base + (side,)]
                    (d['live'] if el > 0 else d['pre']).append((ts, m))
    return legs


def moves(series):
    """Absolute snapshot-to-snapshot moves, in cents."""
    s = sorted(series)
    return [abs(b - a) * 100 for (_, a), (_, b) in zip(s, s[1:])]


def main():
    legs = load()
    pre_m, live_m = [], []
    jumps = []            # (size, settle_after) for big in-play moves
    for key, d in legs.items():
        if len(d['pre']) > 5:
            pre_m += moves(d['pre'])
        s = sorted(d['live'])
        if len(s) > 5:
            live_m += moves(s)
            # a big up-move: where is the price 5 snapshots later?
            for i in range(len(s) - 6):
                jump = (s[i + 1][1] - s[i][1]) * 100
                if jump >= 5:
                    after = (s[i + 6][1] - s[i + 1][1]) * 100
                    jumps.append((jump, after))

    print(f'{len(pre_m):,} pre-match moves, {len(live_m):,} in-play moves\n')
    for label, m in (('PRE-MATCH', pre_m), ('IN-PLAY', live_m)):
        if not m:
            continue
        m2 = sorted(m)
        n = len(m2)
        big = sum(1 for x in m2 if x >= 5)
        print(f'{label:>10}: median {m2[n//2]:.2f}c  p90 {m2[int(n*0.9)]:.2f}c  '
              f'p99 {m2[int(n*0.99)]:.2f}c  max {m2[-1]:.0f}c')
        print(f'{"":>10}  moves >= 5c: {big:,} of {n:,} ({100.0*big/n:.2f}%)')
    if pre_m and live_m:
        print(f'\n  in-play p99 is '
              f'{sorted(live_m)[int(len(live_m)*0.99)] / max(0.01, sorted(pre_m)[int(len(pre_m)*0.99)]):.1f}x '
              f'the pre-match p99')

    if jumps:
        print(f'\n=== the poison: {len(jumps):,} in-play up-moves of 5c or more ===')
        after = sorted(a for _, a in jumps)
        n = len(after)
        print(f'  where the price went in the NEXT ~5 snapshots, after jumping:')
        print(f'    median {after[n//2]:+.2f}c   p25 {after[n//4]:+.2f}c   '
              f'p75 {after[3*n//4]:+.2f}c')
        held = sum(1 for a in after if a > -1)
        print(f'    held or kept rising: {held:,}/{n:,} ({100.0*held/n:.0f}%)')
        print('\n  A resting ask is lifted BY that jump, at the pre-jump price.')
        print('  If the price then holds, the seller is short a leg that is now')
        print('  worth much more than they got. That is the cost to beat.')
        avg_jump = statistics.mean(j for j, _ in jumps)
        print(f'\n  mean jump size {avg_jump:.1f}c -- against a completed book '
              f'that nets ~2c.')


if __name__ == '__main__':
    main()
