"""What do the books that DON'T complete actually cost?

The ratchet backtest scores completed books only (+10.7c queue-aware over ~2.5
weeks) and treats the other ~96% as free. They are not free: every filled leg of
an incomplete book is an unhedged short carried to settlement, worth
(price collected - 1) if that outcome wins and (price collected) if it does not.

And there is a reason to expect them to be biased. Favourites fill at 49.6% and
draws at 17.7%, so the leg we are left holding is systematically the one buyers
wanted -- which is the classic shape of adverse selection.

This adds settlement P&L on every partial to the completion P&L, for the real
total.
"""
import csv, collections, json, os, sys

sys.path.insert(0, r'E:\soccer\main')
sys.path.insert(0, r'E:\soccer\research')
import mm_settle as S
import mm_maker_live as ML
from mm_volatility_clean import load_legs, CACHE

LIVE_DIR = r'E:\soccer\live_data'


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


def load_meta():
    """{leg_ticker: (touch, side, gd, home, away)} at the first quote."""
    out = {}
    with open(r'E:\soccer\research\mm_paper_quotes.csv',
              encoding='utf-8', errors='replace') as fh:
        for r in csv.DictReader(fh):
            t = r.get('leg_ticker')
            if not t or t in out or not (r.get('ask_size') or '').strip():
                continue
            a = _f(r.get('k_ask'))
            if not (a and 0 < a < 1):
                continue
            out[t] = (a, (r.get('side') or '').lower(),
                      (r.get('kickoff') or '')[:10], r.get('home'), r.get('away'))
    return out


def load_results():
    """{(gd, home, away): 'H'|'D'|'A'} from live_data's backfilled ftr."""
    res = {}
    for fn in sorted(f for f in os.listdir(LIVE_DIR) if f.endswith('.csv')):
        with open(os.path.join(LIVE_DIR, fn), encoding='utf-8',
                  errors='replace') as fh:
            for r in csv.DictReader(fh):
                v = (r.get('ftr') or '').upper().strip()
                if v in ('H', 'D', 'A'):
                    res[(r.get('game_date'), r.get('home'), r.get('away'))] = v
    return res


WINS = {'H': 'home', 'D': 'draw', 'A': 'away'}


def main():
    legs, meta = load_legs(), load_meta()
    res = load_results()
    trades = json.load(open(CACHE, encoding='utf-8'))

    books = collections.defaultdict(dict)
    for t, v in legs.items():
        if t in meta:
            books[t.rsplit('-', 1)[0]][t] = v

    comp_n = comp_pnl = 0.0
    part_n = part_pnl = 0.0
    part_legs = 0
    no_result = 0
    by_side = collections.defaultdict(lambda: [0, 0.0])

    for ev, ls in books.items():
        if len(ls) != 3:
            continue
        asks = {t: meta[t][0] for t in ls}
        if ML.book_budget(asks, {})['decision'] == 'gate':
            continue
        filled = {}
        for t, v in ls.items():
            key = f"{t}|{v['ts'].isoformat()}"
            if key not in trades:
                filled = None
                break
            cum, hit = 0.0, False
            for ts_s, p, c in trades[key]:
                ts = S._pt(ts_s)
                if not ts:
                    break
                ko = v['ko']
                if (ts.tzinfo is None) != (ko.tzinfo is None):
                    ts = ts.astimezone() if ts.tzinfo else ts.replace(tzinfo=ko.tzinfo)
                if ts >= ko:
                    break
                if p >= asks[t] - 1e-9:
                    cum += c
                    if cum > v['queue']:
                        hit = True
                        break
            filled[t] = hit
        if filled is None:
            continue

        if all(filled.values()):
            coll = sum(asks.values())
            fees = sum(ML.maker_fee(a) for a in asks.values())
            comp_n += 1
            comp_pnl += coll - fees - 1.0
            continue

        hits = [t for t, h in filled.items() if h]
        if not hits:
            continue
        _, _, gd, home, away = meta[hits[0]]
        r = res.get((gd, home, away))
        if r is None:
            no_result += 1
            continue
        part_n += 1
        for t in hits:
            px, side = meta[t][0], meta[t][1]
            pnl = px - ML.maker_fee(px) - (1.0 if WINS[r] == side else 0.0)
            part_pnl += pnl
            part_legs += 1
            b = by_side[side]
            b[0] += 1
            b[1] += pnl

    print(f'completed books : {comp_n:>4.0f}   P&L {comp_pnl*100:>+8.1f}c')
    print(f'partial books   : {part_n:>4.0f}   P&L {part_pnl*100:>+8.1f}c   '
          f'({part_legs} stranded legs)')
    print(f'{"":16}          ------------')
    print(f'{"TOTAL":>16}          {(comp_pnl+part_pnl)*100:>+8.1f}c')
    if part_n:
        print(f'\nper partial book: {part_pnl/part_n*100:+.2f}c')
    if no_result:
        print(f'({no_result:.0f} partial books dropped -- no settled result)')
    print('\nstranded legs by side:')
    for s in ('home', 'draw', 'away'):
        n, p = by_side[s]
        if n:
            print(f'  {s:>5} {n:>4} legs  {p*100:>+8.1f}c  '
                  f'({p/n*100:+.2f}c each)')


if __name__ == '__main__':
    main()
