"""Counterfactual: at 1-of-3 near kickoff, is it better to FLATTEN or to CROSS OUT?

Today the Poly-US quoter buys back the single sold leg (FLATTEN_1OF3) and goes
flat. That bucket is the maker's largest loss: 79 books, -$23.01, roughly -29c
a book. The alternative is to finish the book instead -- cross BOTH unsold legs
into their bids at whatever price is there, ending short all three outcomes for
a locked, outcome-independent P&L.

This prices that alternative at the EXACT moment each flatten actually fired,
using the bids logged for the other two legs in the same snapshot window, so
the two rules are compared on identical market state rather than on a model.

  flatten P&L    = -( (ask - sold)*100 + taker_fee(ask)*100 )   [+ rebate earned]
  cross-out P&L  = (sold + bid_b + bid_c - 1)*100 + rebate
                   - taker_fee(bid_b)*100 - taker_fee(bid_c)*100

Both are realised before kickoff, so neither carries match risk and they are
directly comparable. No settlement data is needed.

Usage:  python research\\pus_flatten_vs_complete.py
"""
import csv
import os
import re
import shutil
import statistics
import tempfile
from collections import defaultdict
from pathlib import Path

QUOTES = Path(r'E:\soccer\research\mm_polyus_quotes.csv')
TAKER_THETA = 0.06          # mm_quoter_polyus.TAKER_THETA
RE_COST = re.compile(r'=>\s*cost\s*([+-]?[\d.]+)c/contract')
RE_SOLD = re.compile(r'sold @([\d.]+)')


def taker_fee(p):
    return TAKER_THETA * p * (1 - p)


def _f(x, d=None):
    try:
        return float(x)
    except (TypeError, ValueError):
        return d


def load():
    tmp = Path(tempfile.gettempdir()) / 'mm_polyus_quotes.snap.csv'
    shutil.copyfile(QUOTES, tmp)
    books = defaultdict(lambda: defaultdict(list))
    with tmp.open(newline='', encoding='utf-8', errors='replace') as fh:
        for r in csv.DictReader(fh):
            ev, slug = r.get('event_slug'), r.get('market_slug')
            if ev and slug:
                books[ev][slug].append(r)
    for legs in books.values():
        for v in legs.values():
            v.sort(key=lambda r: r.get('ts') or '')
    return books


def bid_at(rows, ts):
    """Best bid for a leg at the snapshot nearest to (and not after) `ts`,
    falling back to the closest later row if the leg had not started quoting."""
    best, prior = None, None
    for r in rows:
        b = _f(r.get('p_bid'))
        if b is None or not (0 < b < 1):
            continue
        if (r.get('ts') or '') <= ts:
            prior = b
        elif best is None:
            best = b
    return prior if prior is not None else best


def main():
    books = load()
    rows = []
    for ev, legs in books.items():
        flat_row = flat_slug = None
        for slug, rs in legs.items():
            for r in rs:
                if 'FLATTEN_1OF3' in (r.get('reason') or ''):
                    flat_row, flat_slug = r, slug
                    break
            if flat_row:
                break
        if not flat_row:
            continue

        ts = flat_row.get('ts') or ''
        m_cost, m_sold = RE_COST.search(flat_row['reason']), RE_SOLD.search(flat_row['reason'])
        if not (m_cost and m_sold):
            continue
        sold = float(m_sold.group(1))
        flat_pnl = -float(m_cost.group(1))          # cost -> P&L

        others = [s for s in legs if s != flat_slug]
        if len(others) != 2:
            continue
        bids = [bid_at(legs[s], ts) for s in others]
        if any(b is None for b in bids):
            continue

        reb = 0.0
        for r in legs[flat_slug]:
            v = _f(r.get('rebate_c'))
            if v:
                reb = v
                break
        cross = ((sold + bids[0] + bids[1] - 1.0) * 100.0 + reb
                 - taker_fee(bids[0]) * 100 - taker_fee(bids[1]) * 100)
        rows.append({'ev': ev, 'div': flat_row.get('div'),
                     'match': f"{flat_row.get('home')} vs {flat_row.get('away')}",
                     'sold': sold, 'b1': bids[0], 'b2': bids[1],
                     'flat': flat_pnl, 'cross': cross, 'delta': cross - flat_pnl})

    if not rows:
        print('no flattened books with both counterpart bids logged')
        return

    n = len(rows)
    f_tot = sum(r['flat'] for r in rows)
    c_tot = sum(r['cross'] for r in rows)
    better = sum(1 for r in rows if r['delta'] > 0)
    d = [r['delta'] for r in rows]
    print(f'FLATTEN vs CROSS-OUT at 1-of-3   ({n} flattened books with both bids)\n')
    print(f'  {"rule":12} {"total c/contract":>18} {"$ at size 10":>14} {"per book":>10}')
    print(f'  {"flatten":12} {f_tot:>18,.1f} {f_tot/10:>14,.2f} {f_tot/n:>10,.2f}')
    print(f'  {"cross out":12} {c_tot:>18,.1f} {c_tot/10:>14,.2f} {c_tot/n:>10,.2f}')
    print(f'\n  difference   : {(c_tot - f_tot)/10:+,.2f} dollars '
          f'({(c_tot - f_tot)/n:+.2f} c/contract per book)')
    print(f'  cross-out better on {better}/{n} books ({100.0*better/n:.0f}%)')
    print(f'  delta mean {statistics.mean(d):+.2f}c  median '
          f'{statistics.median(d):+.2f}c  sd {statistics.stdev(d):.2f}c'
          if n > 1 else '')
    se = (statistics.stdev(d) / (n ** 0.5)) if n > 1 else 0
    if se:
        print(f'  t = {statistics.mean(d)/se:+.2f}  '
              f'{"SIGNIFICANT" if abs(statistics.mean(d)/se) > 1.96 else "not significant"}')

    # CONDITIONAL rule. Both prices are visible at the moment of the decision --
    # the ask you would pay to flatten and the two bids you would cross into --
    # so picking the better one per book is implementable in real time, not
    # hindsight. This is the ceiling for "choose at T-1h" rather than a blanket
    # rule either way.
    best_tot = sum(max(r['flat'], r['cross']) for r in rows)
    print(f'\n  {"conditional":12} {best_tot:>18,.1f} {best_tot/10:>14,.2f} '
          f'{best_tot/n:>10,.2f}   <- pick the better of the two per book')
    print(f'  gain over always-flatten: {(best_tot - f_tot)/10:+,.2f} dollars '
          f'({(best_tot - f_tot)/n:+.2f} c/contract per book)')
    xs = sum(1 for r in rows if r['cross'] > r['flat'])
    print(f'  would cross out on {xs}/{n} books, flatten on {n-xs}')

    worst = sorted(rows, key=lambda r: r['delta'])[:5]
    best = sorted(rows, key=lambda r: -r['delta'])[:5]
    print('\n  where crossing out helps most:')
    for r in best:
        print(f'    {r["div"]:5} {r["match"][:34]:34} sold {r["sold"]:.2f} '
              f'bids {r["b1"]:.2f}/{r["b2"]:.2f}  {r["delta"]:+7.1f}c')
    print('\n  where it hurts most:')
    for r in worst:
        print(f'    {r["div"]:5} {r["match"][:34]:34} sold {r["sold"]:.2f} '
              f'bids {r["b1"]:.2f}/{r["b2"]:.2f}  {r["delta"]:+7.1f}c')


if __name__ == '__main__':
    main()
