"""How often does a stranded 2-of-3 book get rescued in play?

MOQSBA sold home 35c and away 33c pre-match, stranded on the draw, then the game
stayed 0-0, the draw drifted to 52c, and our resting offer was lifted there --
120c collected, +20c locked.

The bar is lower than it looks. If home and away were sold at their touches, the
book needs the draw to fetch roughly

    1.015 - (h + a)  ~=  d_prematch - 0.005

so the draw only has to hold its own pre-match price, not appreciate. Since the
maker now works books for 3 hours after kickoff, the question is simply: how
often does the in-play draw reach its pre-match level?

Measured on live_data, which carries both pre-kickoff and in-play snapshots.
"""
import csv, collections, os, statistics, sys

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

LIVE_DIR = r'E:\soccer\live_data'
DAYS = 30


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


def main():
    files = sorted(f for f in os.listdir(LIVE_DIR) if f.endswith('.csv'))[-DAYS:]
    pre = collections.defaultdict(list)      # fixture -> [draw asks pre-KO]
    live = collections.defaultdict(list)     # fixture -> [(elapsed, draw mid)]
    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):
                el = r.get('elapsed')
                if el in (None, ''):
                    continue
                try:
                    el = float(el)
                except ValueError:
                    continue
                key = (r.get('game_date'), r.get('home'), r.get('away'))
                if el > 0:
                    m = _f(r.get('kalshi_draw_mid'))
                    if m is not None:
                        live[key].append((el, m))
                else:
                    a = _f(r.get('kalshi_draw_ask'))
                    if a is not None:
                        pre[key].append(a)

    rows = []
    for k, ps in pre.items():
        ls = live.get(k)
        if not ls or len(ps) < 3:
            continue
        d0 = statistics.median(ps)           # our latched floor, roughly
        peak = max(m for _, m in ls)
        # when did it first reach the floor?
        first = next((e for e, m in sorted(ls) if m >= d0), None)
        rows.append({'d0': d0, 'peak': peak, 'first': first,
                     'reached': peak >= d0})

    n = len(rows)
    if n < 50:
        print(f'only {n} fixtures with both pre and in-play draw prices')
        return
    hit = sum(1 for r in rows if r['reached'])
    print(f'{n} fixtures with pre-kickoff and in-play draw prices\n')
    print(f'  in-play draw reaches its pre-match price : {hit}/{n} '
          f'({100.0*hit/n:.1f}%)')
    firsts = sorted(r['first'] for r in rows if r['first'] is not None)
    if firsts:
        print(f'  when it first gets there (minutes)       : '
              f'median {firsts[len(firsts)//2]:.0f}, '
              f'p25 {firsts[len(firsts)//4]:.0f}, '
              f'p75 {firsts[3*len(firsts)//4]:.0f}')
    print()
    print('  how far above the pre-match price does it peak:')
    for lo, hi, lbl in ((0, 0.05, '+0-5c'), (0.05, 0.15, '+5-15c'),
                        (0.15, 0.30, '+15-30c'), (0.30, 9, '+30c or more')):
        c = sum(1 for r in rows if lo <= r['peak'] - r['d0'] < hi)
        if c:
            print(f'    {lbl:>14} {c:>4} ({100.0*c/n:>4.1f}%)')
    below = sum(1 for r in rows if r['peak'] < r['d0'])
    print(f'    {"never reaches":>14} {below:>4} ({100.0*below/n:>4.1f}%)')

    print('\n  The 3h in-play window covers the whole match, so a book stranded')
    print('  on the draw gets this entire distribution as its second chance --')
    print('  provided the offer is left resting, which is what the HOLD change')
    print('  does. Cancelling on abandon forfeits all of it.')


if __name__ == '__main__':
    main()
