"""What would the DECAYING-ARM policy have earned on a past day?

Compares three policies on the same logged quotes and the same real trade tape:

  A  FIXED arm, no concede      -- what actually ran for most of 2026-07-29
  B  FIXED arm + concede at T-60
  C  DECAYING arm + concede     -- the current policy

C is what we want to price. B is the control that isolates how much of C's gain
comes from conceding rather than from the decay itself; without it, the decay
would get credit for the concede's work.

Every policy is scored with the SAME queue-aware fill model used live: a fill
requires cumulative volume at or above our price, since we last (re)posted, to
exceed the depth resting there when we posted. Above the touch that depth is 0
(nobody else quotes there), so stepping down through high rungs costs no queue
position; joining the touch does.

Book outcomes follow the live stack: 3 legs -> natural; 2 -> cross the last into
the bid; 1 -> buy it back flat; 0 -> no position.

CAVEAT the numbers cannot escape: our own orders are not in the tape. If we had
actually rested at these prices, some of the volume we count would have traded
against US and not printed at all, and other makers would have reacted. This is
a replay, not a counterfactual market.

Usage:  python research\\decay_backtest.py [YYYY-MM-DD]
"""
import csv
import datetime as dt
import math
import sys
from collections import defaultdict

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

SIZE = 10
TAKER = 0.07


def simulate(legs_by_book, trades, policy):
    """policy: 'fixed', 'fixed_concede', 'decay_concede'."""
    nat = comp = flat = none = 0
    total = 0.0
    for ev, legs in legs_by_book.items():
        if len(legs) != 3:
            continue
        filled, last = {}, {}
        for side, (tk, rs) in legs.items():
            ko = S._pt(rs[0]['kickoff'])
            tr = trades.get(tk, [])
            arm0 = round(float(rs[0]['our_ask']) * 100 - float(rs[0]['k_ask']) * 100)
            cur_px = None
            queue = 0.0
            cum = 0.0
            hit_px = None
            ti = 0
            for i, r in enumerate(rs):
                t = S._pt(r['ts'])
                if t is None or t >= ko:
                    break
                hrs = (ko - t).total_seconds() / 3600.0
                mkt = float(r['k_ask'])
                try:
                    fair = float(r['pin_fair'])
                except (TypeError, ValueError):
                    continue
                # what we would be showing right now
                if policy == 'decay_concede':
                    arm = Q.arm_for(ev, side, hrs)
                else:
                    arm = arm0
                if policy in ('fixed_concede', 'decay_concede') and hrs <= 1.0:
                    want = mkt
                else:
                    want = min(fair + Q.margin_for(hrs) / 100.0, mkt + arm / 100.0)
                want = math.ceil(round(want * 100, 6)) / 100.0
                # only ever reprice DOWN (matches live rule)
                if cur_px is None or want < cur_px - 1e-9:
                    if cur_px is not None:
                        cum = 0.0          # cancel-replace: back of the queue
                    cur_px = want
                    asz = r.get('ask_size')
                    queue = (float(asz) if (asz or '').strip() and
                             want <= mkt + 1e-9 else 0.0)
                # consume trades up to the next quote timestamp
                nxt = S._pt(rs[i + 1]['ts']) if i + 1 < len(rs) else ko
                if nxt is None or nxt > ko:
                    nxt = ko
                while ti < len(tr) and tr[ti][0] < nxt:
                    tt, tp, tc = tr[ti]
                    ti += 1
                    if tt >= ko:
                        break
                    if tp >= cur_px - 1e-9:
                        cum += tc
                        if cum > queue:
                            hit_px = cur_px
                            break
                if hit_px is not None:
                    break
            if hit_px is not None:
                filled[side] = hit_px
            last[side] = (float(rs[-1]['k_bid']), float(rs[-1]['k_ask']))

        if len(filled) == 3:
            nat += 1
            total += (sum(filled.values()) - 1.0) * SIZE
        elif len(filled) == 2:
            miss = [s for s in legs if s not in filled][0]
            bid = last[miss][0]
            coll = sum(filled.values()) + bid
            comp += 1
            total += ((coll - 1.0) - TAKER * bid * (1 - bid)) * SIZE
        elif len(filled) == 1:
            s0 = list(filled)[0]
            ask = last[s0][1]
            flat += 1
            total += -((ask - filled[s0]) + TAKER * ask * (1 - ask)) * SIZE
        else:
            none += 1
    return nat, comp, flat, none, total


def main():
    day = sys.argv[1] if len(sys.argv) > 1 else '2026-07-29'
    rows = defaultdict(list)
    with open(r'E:\soccer\research\mm_paper_quotes.csv', encoding='utf-8') as fh:
        for r in csv.DictReader(fh):
            if (r['ts'][:10] == day and r.get('our_ask') and r.get('k_ask')
                    and r.get('pin_fair')):
                rows[r['leg_ticker']].append(r)
    by = defaultdict(dict)
    for tk, rs in rows.items():
        rs.sort(key=lambda x: x['ts'])
        by[rs[0]['event']][rs[0]['side']] = (tk, rs)
    print(f'{day}: {len(rows)} legs across {len(by)} books\n')

    trades = {}
    for tk, rs in rows.items():
        ko = S._pt(rs[0]['kickoff'])
        trades[tk] = [(t, p, c) for t, p, c
                      in trades_sized(tk, S._pt(rs[0]['ts'])) if t < ko]

    print(f"  {'policy':26s} {'nat':>4s} {'2/3':>4s} {'1/3':>4s} {'none':>5s} {'P&L':>9s}")
    for pol, lbl in (('fixed', 'A fixed arm, no concede'),
                     ('fixed_concede', 'B fixed arm + concede'),
                     ('decay_concede', 'C DECAY + concede')):
        n, c, f, z, t = simulate(by, trades, pol)
        print(f'  {lbl:26s} {n:4d} {c:4d} {f:4d} {z:5d} {t:+8.2f}$')


if __name__ == '__main__':
    main()
