"""
Market-making feasibility on Kalshi soccer — measured, not modelled. (read-only)

WHY THIS FIRST. project_xlow_spread_verdict established that the money in these
markets is the spread, and that MAKERS PAY NO FEE (confirmed on a real fill:
7.8 contracts @ 0.36, fee_cost 0.0000) while takers pay 0.07*P*(1-P) ~= 4.4% of
stake. So making is the structurally correct side. The open question is whether
the spread you collect survives ADVERSE SELECTION — you get filled precisely when
someone wanted to trade, which is disproportionately when they are right.

That question is answerable from history with zero capital at risk, which is why
this runs before anything is built. [[project_no_subask_limits]] already measured
a version of it (resting BELOW ask on a directional bet: 37% fill rate, P&L
+$213 -> +$19). This is the cleaner test: pure passive quoting, both sides.

THE MEASUREMENT. Kalshi's /markets/trades exposes `taker_book_side` on every
trade, so we know which side was PASSIVE. **The field describes the TAKER'S OWN
order side** -- verified against our own fills, which are all buys and all carry
`book_side='bid'`, `is_taker=true`:
  - taker_book_side == 'bid'  -> the TAKER BOUGHT (lifted a resting ask)
                                 -> a MAKER SOLD at that price
  - taker_book_side == 'ask'  -> the TAKER SOLD (hit a resting bid)
                                 -> a MAKER BOUGHT at that price
The first version of this script had that inverted and produced a confident
-5% "market making loses" result. Sanity check that saves you: takers here are
net BUYERS about 4:1, which is exactly what a sports market looks like (retail
backs teams, it rarely lays them). If your mapping shows takers as net sellers,
it is backwards.
Every settled market has a known outcome (1.00 or 0.00), so each passive fill has
an exact realised P&L. No simulation of queue position is needed for the headline
number: this is what makers actually got.

READ IT AS: if passive BUYS settle below what makers paid, and passive SELLS
settle above what makers received, adverse selection is winning and market making
here loses despite the zero fee. Both sides positive = the spread survives.

CAVEAT THIS CANNOT ANSWER: queue priority. These are the fills makers DID get;
whether *you* would have been at the front of the queue is a latency question no
historical data answers.

Usage:  python research\\mm_feasibility.py [n_markets]
"""
import sys
import csv
import time
import json
from collections import defaultdict

sys.path.insert(0, r'E:\soccer\main')
import kalshi_trader as kt

BACKFILL = r'E:\soccer\research\kalshi_backfill.csv'
OUT = r'E:\soccer\research\mm_trades.csv'
N_MARKETS = int(sys.argv[1]) if len(sys.argv) > 1 else 60
FIELDS = ['ticker', 'div', 'result_yes', 'ts', 'price', 'count', 'taker_book_side']


def settled_targets(n):
    """Settled soccer markets whose YES outcome we already know, from the backfill."""
    out = []
    try:
        with open(BACKFILL, newline='', encoding='utf-8') as fh:
            for r in csv.DictReader(fh):
                ev = r['event']
                # the backfill stores the event; rebuild the three leg tickers by
                # asking the API once per event (cheap) -- but we only need legs
                # whose settlement we can infer from ftr.
                out.append(r)
    except FileNotFoundError:
        return []
    return out[-n:]


def legs_for_event(event_ticker):
    try:
        r = kt._get('/markets', {'event_ticker': event_ticker, 'limit': 10})
        return r.get('markets', [])
    except Exception:
        return []


def trades_for(ticker, cap=2000):
    out, cursor = [], None
    while len(out) < cap:
        p = {'ticker': ticker, 'limit': 200}
        if cursor:
            p['cursor'] = cursor
        try:
            d = kt._get('/markets/trades', p)
        except Exception:
            break
        tr = d.get('trades', [])
        out += tr
        cursor = d.get('cursor')
        if not cursor or not tr:
            break
        time.sleep(0.08)
    return out


def main():
    import os
    targets = settled_targets(N_MARKETS)
    if not targets:
        print('no backfill rows -- run research/kalshi_backfill.py first')
        return

    # RESUMABLE: skip events already written, so an interrupted long pull is not lost.
    done = set()
    existing = 0
    if os.path.exists(OUT):
        with open(OUT, newline='', encoding='utf-8') as fh:
            for r in csv.DictReader(fh):
                done.add('-'.join(r['ticker'].split('-')[:2]))
                existing += 1
        print(f'resuming: {len(done)} events / {existing} trades already on disk')

    todo = [t for t in targets if t['event'] not in done]
    print(f'pulling trades for {len(todo)} new events ({len(targets)} requested) ...')

    fh = open(OUT, 'a', newline='', encoding='utf-8')
    w = csv.DictWriter(fh, fieldnames=FIELDS)
    if not done:
        w.writeheader()

    total = 0
    for i, t in enumerate(todo, 1):
        for m in legs_for_event(t['event']):
            res = m.get('result')
            if res not in ('yes', 'no'):
                continue
            for x in trades_for(m['ticker']):
                try:
                    w.writerow(dict(
                        ticker=m['ticker'], div=t['div'],
                        result_yes=1 if res == 'yes' else 0,
                        ts=x.get('created_time'),
                        price=float(x['yes_price_dollars']),
                        count=float(x['count_fp']),
                        taker_book_side=x.get('taker_book_side')))
                    total += 1
                except (KeyError, TypeError, ValueError):
                    continue
            time.sleep(0.05)
        if i % 20 == 0:
            fh.flush()
            print(f'  {i}/{len(todo)} events, {total} new trades')
    fh.close()
    print(f'wrote {total} new trades -> {OUT}')


def analyse(rows):
    """Realised P&L of the PASSIVE side of every trade."""
    buy_n = buy_cost = buy_val = 0.0
    sell_n = sell_recv = sell_val = 0.0
    for r in rows:
        p, c, res = r['price'], r['count'], r['result_yes']
        if r['taker_book_side'] == 'bid':      # taker BOUGHT -> maker SOLD at p
            sell_n += c
            sell_recv += p * c
            sell_val += res * c
        elif r['taker_book_side'] == 'ask':    # taker SOLD -> maker BOUGHT at p
            buy_n += c
            buy_cost += p * c
            buy_val += res * c

    print('PASSIVE (MAKER) SIDE, realised to settlement, zero fees:')
    if buy_n:
        pnl = buy_val - buy_cost
        print(f'  maker BOUGHT : {buy_n:12,.0f} contracts  avg px {buy_cost/buy_n:.4f}  '
              f'settled {buy_val/buy_n:.4f}  P&L ${pnl:+,.0f}  '
              f'({pnl/buy_cost*100:+.2f}% of cost)')
    if sell_n:
        pnl = sell_recv - sell_val
        print(f'  maker SOLD   : {sell_n:12,.0f} contracts  avg px {sell_recv/sell_n:.4f}  '
              f'settled {sell_val/sell_n:.4f}  P&L ${pnl:+,.0f}  '
              f'({pnl/sell_recv*100:+.2f}% of received)')
    if buy_n and sell_n:
        tot = (buy_val - buy_cost) + (sell_recv - sell_val)
        notional = buy_cost + sell_recv
        print(f'  COMBINED     : P&L ${tot:+,.0f} on ${notional:,.0f} notional '
              f'= {tot/notional*100:+.3f}%')
        print()
        print('  Positive combined = the spread survives adverse selection.')
        print('  Negative = takers are informed and making loses despite the zero fee.')
        print('  NOTE: assumes you get every passive fill. Queue priority is the one')
        print('  thing history cannot tell you -- treat this as the CEILING.')


if __name__ == '__main__':
    main()
