"""
Analyse the market-maker feasibility data collected by mm_feasibility.py.
(read-only, trades nothing)

THE HEADLINE NUMBER IS NOT THE NOTIONAL-WEIGHTED ROI. On the first 40 events the
notional-weighted figure was +7.88% while only 19/40 events were profitable and
the MEDIAN event was -1.25%. A few large matches carried the whole result. So
this reports per-event distribution first and the aggregate second.

MAPPING (verified against our own fills, which are all buys carrying
book_side='bid', is_taker=true):
    taker_book_side == 'bid'  -> taker BOUGHT  -> MAKER SOLD
    taker_book_side == 'ask'  -> taker SOLD    -> MAKER BOUGHT
An earlier version had this inverted and confidently reported market making as a
-5% loser. If takers do not come out as net BUYERS (~4:1), the mapping is wrong.

WHAT IT STILL CANNOT TELL YOU: queue priority. These are the fills makers DID
get in aggregate; you would win some fraction of them, and plausibly the worse
fraction. Treat every number here as a CEILING.

Usage:  python research\\mm_analyse.py
"""
import csv
import math
import statistics
import datetime as dt
from collections import defaultdict

TRADES = r'E:\soccer\research\mm_trades.csv'
BACKFILL = r'E:\soccer\research\kalshi_backfill.csv'


def kickoffs():
    out = {}
    try:
        with open(BACKFILL, newline='', encoding='utf-8') as fh:
            for r in csv.DictReader(fh):
                try:
                    out[r['event']] = dt.datetime.fromisoformat(
                        r['kickoff'].replace('Z', '+00:00'))
                except (ValueError, KeyError):
                    continue
    except FileNotFoundError:
        pass
    return out


def event_of(ticker):
    return '-'.join(ticker.split('-')[:2])


def main():
    ko = kickoffs()
    rows = list(csv.DictReader(open(TRADES, newline='', encoding='utf-8')))
    print(f'{len(rows):,} trades across {len({event_of(r["ticker"]) for r in rows})} events\n')

    def pre(r):
        e = event_of(r['ticker'])
        if e not in ko or not r['ts']:
            return False
        try:
            return dt.datetime.fromisoformat(r['ts'].replace('Z', '+00:00')) < ko[e]
        except ValueError:
            return False

    def report(sel, label):
        by = defaultdict(lambda: [0.0, 0.0])     # event -> [pnl, notional]
        short = defaultdict(float)
        sold = bought = 0.0
        for r in rows:
            if not sel(r):
                continue
            p, c, res = float(r['price']), float(r['count']), int(r['result_yes'])
            e = event_of(r['ticker'])
            if r['taker_book_side'] == 'bid':          # maker SOLD
                by[e][0] += p * c - res * c
                by[e][1] += p * c
                short[e] += c
                sold += c
            elif r['taker_book_side'] == 'ask':        # maker BOUGHT
                by[e][0] += res * c - p * c
                by[e][1] += p * c
                short[e] -= c
                bought += c
        ev = [(v[0] / v[1] * 100, v[1]) for v in by.values() if v[1] > 1000]
        if len(ev) < 10:
            print(f'{label}: only {len(ev)} events -- too few')
            return
        roi = [x[0] for x in ev]
        tot = sum(v[0] for v in by.values())
        noti = sum(v[1] for v in by.values())
        se = statistics.pstdev(roi) / math.sqrt(len(roi))
        pos = sum(1 for x in roi if x > 0)
        print(f'{label}')
        print(f'   events {len(ev):4d}   profitable {pos:4d} ({pos/len(ev)*100:.0f}%)   '
              f'median {statistics.median(roi):+7.2f}%   mean {statistics.mean(roi):+7.2f}%   '
              f't={statistics.mean(roi)/se:+5.2f}')
        print(f'   notional-weighted {tot/noti*100:+.2f}%   on ${noti:,.0f}   '
              f'taker buy:sell {sold/max(bought,1):.1f}:1')
        ns = [short[e] for e in by]
        print(f'   maker ends net SHORT on {sum(1 for x in ns if x>0)}/{len(ns)} events, '
              f'median {statistics.median(ns):,.0f} contracts')
        print()

    report(lambda r: True, 'ALL trades')
    report(lambda r: pre(r) and 0.05 <= float(r['price']) <= 0.95,
           'PRE-KICKOFF, 5c-95c')
    report(lambda r: pre(r) and 0.15 <= float(r['price']) <= 0.85,
           'PRE-KICKOFF, 15c-85c   <-- the market you would quote')

    print('READ: the per-event MEDIAN and t are the honest numbers. A high')
    print('notional-weighted ROI with a negative median means a few big matches')
    print('carried it, which is not a business.')


if __name__ == '__main__':
    main()
