#!/usr/bin/env python3
"""
mmlab.py -- Does market making work on Kraken? The microstructure decomposition.

A market maker's economics are NOT "capture the spread". They are:

    effective half-spread  =  realized half-spread  +  adverse selection
    (what you quote)          (what you KEEP)          (how far the mid runs
                                                        against you after you fill)

You fill at your quote, then the market moves. If it systematically moves against
you -- because the person who traded with you knew something, or was simply the
leading edge of a move -- you gave up the spread and more. That is adverse
selection, and it is what kills retail market making. It is invisible in OHLCV
data and measurable in tick data.

The test: realized half-spread vs the maker fee.
    realized > maker fee  -> market making is viable, then it's an execution problem
    realized < maker fee  -> arithmetically dead before skill enters the picture

Data: Kraken BTC/USD spot trades with aggressor side.
"""
import numpy as np
import pandas as pd

# Kraken fee schedule, lowest (retail) volume tier
SPOT_MAKER = 0.0025      # 25 bps
FUT_MAKER = 0.0002       # 2 bps
HORIZONS = (1, 5, 10, 30, 60, 300)     # seconds after the fill


def load():
    d = pd.read_feather("user_data/data/kraken/BTC_USD-trades.feather")
    d = d.sort_values("timestamp").reset_index(drop=True)
    d["t"] = d["timestamp"] / 1000.0
    d["q"] = np.where(d["side"] == "buy", 1.0, -1.0)   # +1 taker bought (lifted ask)
    return d


def build_mid(d, stale=5.0):
    """Proxy the touch: most recent buy-initiated price = ask, sell-initiated = bid.
    Mid is valid only when both sides are fresher than `stale` seconds."""
    ask = np.full(len(d), np.nan); bid = np.full(len(d), np.nan)
    ta = tb = -1e18; pa = pb = np.nan
    p, q, t = d["price"].values, d["q"].values, d["t"].values
    for i in range(len(d)):
        ask[i], bid[i] = pa, pb                       # state BEFORE this trade
        if q[i] > 0: pa, ta = p[i], t[i]
        else:        pb, tb = p[i], t[i]
    mid = (ask + bid) / 2.0
    return mid, ask, bid


if __name__ == "__main__":
    d = load()
    span = (d["t"].iloc[-1] - d["t"].iloc[0]) / 3600
    print(f"Kraken BTC/USD spot trades: {len(d):,} over {span:.1f}h "
          f"({d['cost'].sum()/1e6:.1f}M USD notional)")
    print(f"  taker buys {(d['q']>0).mean()*100:.1f}%  |  "
          f"median trade ${d['cost'].median():,.0f}\n")

    mid, ask, bid = build_mid(d)
    p, q, t = d["price"].values, d["q"].values, d["t"].values
    ok = np.isfinite(mid) & (mid > 0)

    # quoted spread, from the touch proxy
    sp = (ask - bid) / mid
    sp_ok = ok & np.isfinite(sp) & (sp > 0) & (sp < 0.01)
    print("=" * 78)
    print("QUOTED SPREAD  (touch proxy from signed ticks)")
    print("=" * 78)
    print(f"  median {np.median(sp[sp_ok])*1e4:6.2f} bps    "
          f"mean {np.mean(sp[sp_ok])*1e4:6.2f} bps    "
          f"p90 {np.percentile(sp[sp_ok],90)*1e4:6.2f} bps")
    print(f"  round-trip maker cost -- Kraken FUTURES: {FUT_MAKER*2*1e4:.0f} bps"
          f"   |  Kraken SPOT: {SPOT_MAKER*2*1e4:.0f} bps")

    # effective half-spread the maker earns at the moment of fill
    eff = q * (p - mid) / mid
    eff_ok = ok & np.isfinite(eff) & (np.abs(eff) < 0.01)
    print(f"\n  effective half-spread (maker earns at fill): "
          f"{np.mean(eff[eff_ok])*1e4:.2f} bps")

    # ---- the decomposition ----
    print("\n" + "=" * 78)
    print("WHERE THE SPREAD GOES: realized vs adverse selection, by horizon")
    print("=" * 78)
    print(f"{'horizon':>8s} | {'effective':>10s} {'adverse sel':>12s} {'REALIZED':>10s} |"
          f" {'vs fut fee':>11s} {'vs spot fee':>12s}")
    print("-" * 78)

    idx = np.arange(len(d))
    for H in HORIZONS:
        j = np.searchsorted(t, t + H, side="left")
        j = np.clip(j, 0, len(d) - 1)
        mid_f = mid[j]
        good = ok & np.isfinite(mid_f) & (mid_f > 0) & (j > idx)
        e = q[good] * (p[good] - mid[good]) / mid[good]
        # maker sold at p (taker bought); mid then moves to mid_f -> maker keeps p - mid_f
        r = q[good] * (p[good] - mid_f[good]) / mid[good]
        m = np.abs(e) < 0.01
        e, r = e[m], r[m]
        a = e - r
        print(f"{H:7d}s | {np.mean(e)*1e4:9.2f}b {np.mean(a)*1e4:11.2f}b"
              f" {np.mean(r)*1e4:9.2f}b | {(np.mean(r)-FUT_MAKER)*1e4:+10.2f}b"
              f" {(np.mean(r)-SPOT_MAKER)*1e4:+11.2f}b")

    print("\n  'REALIZED' = what a market maker actually keeps per fill, in bps.")
    print(f"  It must exceed the maker fee ({FUT_MAKER*1e4:.0f} bps futures /"
          f" {SPOT_MAKER*1e4:.0f} bps spot) for the business to work.")
