"""
Paper trading engine — runs strategies against HYPERLIQUID MAINNET data.

Pulls real mainnet prices, orderbooks, and funding rates every second.
Executes all 7 strategies in simulation mode — tracks virtual positions,
computes PnL with realistic fees and slippage. No real orders.

Writes to /tmp/ftdt-paper-metrics.json for the dashboard.
"""
import os, sys, asyncio, json, time, logging, random, math
from pathlib import Path
from datetime import datetime
from collections import deque

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import requests

from strategies.hawkes_ofi import HawkesOFI
from strategies.deep_lob import DeepLOB
from strategies.cartea_jaimungal import CarteaJaimungal
from strategies.queue_imbalance import QueueImbalance
from strategies.gueant import GueantMM

logging.basicConfig(level=logging.INFO, format="%(asctime)s [paper] %(message)s", datefmt="%H:%M:%S")
log = logging.getLogger("ftdt-paper")

# ═══════════════════════ Config ═══════════════════════

MAINNET_API = "https://api.hyperliquid.xyz/info"
METRICS_FILE = "/tmp/ftdt-paper-metrics.json"
STARTING_CAPITAL = 100000.0  # $100,000 paper trading capital
RESERVE = 30000.0
TAKER_FEE = 0.0005  # 5 bps taker
MAKER_FEE = 0.0002  # 2 bps maker
SLIPPAGE_BPS = 1.0   # 1 bps slippage
MIN_SIGNAL_STRENGTH = 0.25  # Minimum signal strength to overcome fees

# ═══════════════════════ Strategy state ═══════════════════════

STRATEGIES = {
    "Order Book Imbalance": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "reversal", "size":0.000800, "fee_model": "taker",
        "description": "L2 bid/ask volume skew — buys when bids dominate, sells when asks dominate. Mean-reverting at volume extremes.",
    },
    "Iceberg Detection": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "momentum", "size":0.000850, "fee_model": "taker",
        "description": "Detects whale accumulation (many small buys over time). Follows the smart money flow.",
    },
    "Funding Rate Arb": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "carry", "size":0.000900, "fee_model": "taker",
        "description": "Delta-neutral carry trade — shorts perp when funding rate is high, collects hourly payments.",
    },
    "Pairs Trading": {
        "allocation": 10000.0, "instrument": "ETH", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "stat_arb", "size":0.027500, "fee_model": "taker",
        "description": "BTC/ETH spread mean reversion — trades when Z-score exceeds 1.5 sigma. Pairs converge back to equilibrium.",
    },
    "Avellaneda-Stoikov": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "market_making", "size":0.000950, "fee_model": "maker",
        "description": "Dual-sided quoting at best bid/ask — captures spread via stochastic control. Simulated fill when spread is crossed.",
    },
    "Momentum Breakout": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "momentum", "size":0.020000, "fee_model": "taker",
        "description": "Bollinger Band (2σ) breakout — enters when price breaks bands with volume confirmation.",
    },
    "Mean Reversion": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "reversal", "size":0.022500, "fee_model": "taker",
        "description": "VWAP deviation — buys below VWAP, sells above. Oscillates around fair value.",
    },
    "Hurst VPIN": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "momentum", "size": 0.002, "fee_model": "taker",
        "description": "Hurst exponent regime filter + VPIN informed flow. Enters when both align trending + high flow imbalance.",
    },
    "Hawkes OFI (new)": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "hawkes", "size": 0.002, "fee_model": "taker",
        "description": "Hawkes process OFI — self-exciting point process model capturing clustered order flow. Predicts direction from buy/sell intensity imbalance. Academically rigorous stochastic process.",
    },
    "Deep LOB (new)": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "deep_lob", "size": 0.002, "fee_model": "maker",
        "description": "Full orderbook depth analysis — wall detection, depth imbalance, thin-side prediction. Uses 10 levels of LOB to find fair value and directional pressure.",
    },
    "Cartea-Jaimungal": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "cartea", "size": 0.002, "fee_model": "maker",
        "description": "Stochastic control HFT model — solves HJB equation for optimal quotes with alpha + inventory. Reservation price dynamically shifts to manage risk. (Cartea-Jaimungal 2015)",
    },
    "Queue Imbalance": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "queue_imb", "size": 0.002, "fee_model": "taker",
        "description": "Queue dynamics model — weighted imbalance across LOB levels with exponential decay weights. Detects adverse selection when price moves against queue dominance. (Stoikov-Sağlam framework)",
    },
    "Guéant Market Making": {
        "allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
        "trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
        "position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
        "signals": [], "type": "gueant", "size": 0.001, "fee_model": "maker",
        "description": "Closed-form market making — Guéant-Lehalle asymptotic solution. Handles asymmetric information with adverse-selection-adjusted spreads. Computationally efficient closed form.",
    },
}

trades_log: list[dict] = []
equity_history: list[dict] = []
strategy_equity: dict = {name: deque(maxlen=300) for name in STRATEGIES}
per_strategy_trades: dict = {name: deque(maxlen=200) for name in STRATEGIES}
btc_prices: deque = deque(maxlen=120)
eth_prices: deque = deque(maxlen=120)
funding_rates: deque = deque(maxlen=100)

# ═══════════════════════ Regime Detection ═══════════════════════
# Uses rolling volatility to classify market regime:
#   LOW_VOL: quiet markets → tight spreads, aggressive size
#   NORMAL:  standard conditions → baseline parameters
#   HIGH_VOL: turbulence → wide spreads, reduced size, cautious signals

current_regime = "NORMAL"
regime_confidence = 0.5

def detect_regime():
    """Classify market regime from rolling BTC price volatility."""
    global current_regime, regime_confidence
    if len(btc_prices) < 30:
        return "NORMAL"

    window = list(btc_prices)[-30:]
    # Compute 30-tick log returns
    returns = [math.log(window[i] / window[i-1]) for i in range(1, len(window))]
    realized_vol = math.sqrt(sum(r**2 for r in returns) / len(returns))

    # Annualize (30 ticks at ~1s each → 30s window, annualize to 1yr)
    annual_vol = realized_vol * math.sqrt(365 * 24 * 60 * 60 / 30)
    regime_confidence = min(0.95, max(0.2, annual_vol / 2.0))

    if annual_vol < 0.15:  # <15% annualized
        return "LOW_VOL"
    elif annual_vol > 0.60:  # >60% annualized
        return "HIGH_VOL"
    return "NORMAL"

# ═══════════════════════ Mainnet Data ═══════════════════════

def get_mainnet_prices():
    """Get mark prices from mainnet."""
    try:
        r = requests.post(MAINNET_API, json={"type":"metaAndAssetCtxs"}, timeout=10)
        data = r.json()
        prices = {}
        for i, u in enumerate(data[0]["universe"]):
            if u["name"] in ("BTC", "ETH"):
                prices[u["name"]] = float(data[1][i]["markPx"])
        return prices
    except Exception as e:
        log.warning(f"Mainnet price error: {e}")
        return {}

def get_mainnet_funding():
    """Get funding rates from mainnet."""
    try:
        r = requests.post(MAINNET_API, json={"type":"metaAndAssetCtxs"}, timeout=10)
        data = r.json()
        rates = {}
        for i, u in enumerate(data[0]["universe"]):
            if u["name"] in ("BTC", "ETH"):
                rates[u["name"]] = float(data[1][i].get("funding", 0))
        return rates
    except:
        return {}

def get_mainnet_orderbook(coin):
    """Get L2 orderbook from mainnet."""
    try:
        r = requests.post(MAINNET_API, json={"type":"l2Book","coin":coin}, timeout=10)
        data = r.json()
        best_bid = float(data["levels"][0][0]["px"]) if data["levels"][0] else 0
        best_ask = float(data["levels"][1][0]["px"]) if data["levels"][1] else 0
        return best_bid, best_ask
    except: return 0,0

def get_deep_orderbook(coin, depth=10):
    """Get full LOB levels. Returns (bids, asks) where each is [(price,size),...]."""
    try:
        r = requests.post(MAINNET_API, json={"type":"l2Book","coin":coin}, timeout=10)
        data = r.json()
        bids = [(float(l["px"]), float(l["sz"])) for l in data["levels"][0][:depth]]
        asks = [(float(l["px"]), float(l["sz"])) for l in data["levels"][1][:depth]]
        return bids, asks
    except: return [], []

# Initialize models
hawkes_btc = HawkesOFI(alpha=0.3, beta=0.5)
deep_lob = DeepLOB(depth_levels=10)
cartea = CarteaJaimungal(gamma=0.1, sigma=0.015, kappa=1.5, T=3600, max_inventory=0.01)
queue_imb = QueueImbalance(depth_levels=10)
gueant = GueantMM(gamma=0.1, sigma=0.015, k=1.5, T=3600, max_pos=0.005)
prev_bids = None
prev_asks = None

# ═══════════════════════ Signal Engine ═══════════════════════

def compute_signals():
    if len(btc_prices) < 20: return
    btc = btc_prices[-1]; eth = eth_prices[-1] if eth_prices else btc/34

    # Order Book Imbalance — MOVED to main loop (uses real L2 bid/ask volume)

    # Hurst/VPIN — feed BTC price into dollar bars
    if len(btc_prices) >= 3:
        try:
            from strategies.hurst_vpin_live import HurstVPINLive
            if "_hv_live" not in dir():
                globals()["_hv_live"] = HurstVPINLive(
                    threshold=50000.0, hurst_entry=0.55, vpin_threshold=0.25
                )
            hv_signal = globals()["_hv_live"].feed_price(btc)
            if hv_signal:
                STRATEGIES["Hurst VPIN"]["signals"].append({
                    "time": time.time(),
                    "signal": hv_signal["signal"],
                    "strength": hv_signal["hurst"],
                    "reason": f"H={hv_signal['hurst']:.2f}_V={hv_signal['vpin']:.2f}"
                })
        except: pass

    # Iceberg
    if len(btc_prices) >= 10:
        up = sum(1 for i in range(-9,0) if btc_prices[i+1] > btc_prices[i])
        if up >= 7:
            STRATEGIES["Iceberg Detection"]["signals"].append({"time":time.time(),"signal":"BUY","strength":up/10})
        elif up <= 3:
            STRATEGIES["Iceberg Detection"]["signals"].append({"time":time.time(),"signal":"SELL","strength":1-up/10})

    # Funding Rate Arb — unified module with real API data
    try:
        from strategies.funding_arb import funding_arb_signal
        sig_result = funding_arb_signal(coin="BTC", apr_threshold=0.05, apr_exit=0.02,
                                        current_position=STRATEGIES["Funding Rate Arb"]["position"])
        if sig_result["signal"] != 0:
            STRATEGIES["Funding Rate Arb"]["signals"].append({
                "time": time.time(),
                "signal": "SELL" if sig_result["signal"] < 0 else "BUY",
                "strength": min(1.0, abs(sig_result["annual_apr"]) * 10),
                "reason": sig_result["reason"]
            })
        # Log periodically
        if not hasattr(globals().get("_funding_log_tick", None), "__int__"):
            globals()["_funding_log_tick"] = 0
        if globals()["_funding_log_tick"] % 30 == 0:
            import logging
            logging.getLogger("ftdt-paper").info(
                f"[Fund] APR={sig_result['annual_apr']*100:.2f}% | "
                f"8h={sig_result['rate_8h']*100:.6f}% | "
                f"signal={sig_result['signal']}"
            )
        globals()["_funding_log_tick"] = globals().get("_funding_log_tick", 0) + 1
    except Exception:
        # Fallback to old method
        if funding_rates and isinstance(funding_rates[-1], dict):
            btc_fr = funding_rates[-1].get("BTC", 0)
            annual_fr = abs(btc_fr) * 365 * 3 if btc_fr else 0
            if annual_fr > 0.05:
                STRATEGIES["Funding Rate Arb"]["signals"].append(
                    {"time":time.time(),"signal":"SELL" if btc_fr > 0 else "BUY",
                     "strength": min(0.6, annual_fr * 50),
                     "reason": "funding_{:.1f}pct_apr".format(annual_fr*100)}
                )

    # Pairs: BTC/ETH ratio Z-score
    if len(btc_prices) >= 20 and len(eth_prices) >= 20:
        ratios = [btc_prices[i] / max(eth_prices[i], 0.01) for i in range(-20, 0)]
        mu = sum(ratios) / len(ratios)
        std = math.sqrt(sum((r-mu)**2 for r in ratios) / len(ratios))
        cur = btc / max(eth, 0.01)
        if std > 0:
            z = (cur - mu) / std
            if z > 1.5:
                STRATEGIES["Pairs Trading"]["signals"].append({"time":time.time(),"signal":"SELL_ETH","strength":z})
            elif z < -1.5:
                STRATEGIES["Pairs Trading"]["signals"].append({"time":time.time(),"signal":"BUY_ETH","strength":abs(z)})
        # Kalman Pairs: adaptive hedge ratio
        if len(btc_prices)>=20 and len(eth_prices)>=20:
            try:
                from strategies.kalman_pairs import KalmanPairsTrader
                if "_kalman_paper" not in dir():
                    globals()["_kalman_paper"] = KalmanPairsTrader(
                        transition_covariance=1e-4, observation_covariance=1e-2,
                        z_entry=2.0, z_exit=0.5, warmup_bars=20,
                    )
                result = globals()["_kalman_paper"].step(eth, btc)
                if result["signal"] != 0:
                    sig = "BUY_ETH" if result["signal"] > 0 else "SELL_ETH"
                    STRATEGIES["Kalman Pairs"]["signals"].append({
                        "time": time.time(), "signal": sig,
                        "strength": abs(result["z_score"])
                    })
            except: pass

    # Momentum Breakout
    if len(btc_prices) >= 20:
        w = list(btc_prices)[-20:]; sma = sum(w)/len(w)
        variance = sum((p-sma)**2 for p in w)/len(w); std = math.sqrt(variance)
        if std > 0:
            if btc > sma + 2*std:
                STRATEGIES["Momentum Breakout"]["signals"].append({"time":time.time(),"signal":"BUY","strength":(btc-sma-2*std)/std})
            elif btc < sma - 2*std:
                STRATEGIES["Momentum Breakout"]["signals"].append({"time":time.time(),"signal":"SELL","strength":(sma-2*std-btc)/std})

    # Mean Reversion: SMA deviation on ETH (prior 19, exclude current)
    if len(eth_prices) >= 20:
        w = list(eth_prices)[-20:]
        eth_now = eth_prices[-1]
        prior = w[:-1]
        sma = sum(prior) / len(prior)
        vstd = math.sqrt(sum((p-sma)**2 for p in prior) / len(prior))
        dev = (eth_now - sma) / vstd if vstd > 0 else 0
        if dev > 1.0:
            STRATEGIES["Mean Reversion"]["signals"].append({"time":time.time(),"signal":"SELL","strength":dev})
        elif dev < -1.0:
            STRATEGIES["Mean Reversion"]["signals"].append({"time":time.time(),"signal":"BUY","strength":abs(dev)})

    for s in STRATEGIES.values():
        s["signals"] = s["signals"][-20:]

# ═══════════════════════ Fill Simulation ═══════════════════════

def simulate_fill(name: str, side: str, coin: str, price: float, reason: str = ""):
    """Simulate a trade fill at market price with strategy-specific fees."""
    cfg = STRATEGIES[name]
    sz = cfg["size"]
    notional = sz * price

    # Use strategy's fee model
    fee_rate = MAKER_FEE if cfg.get("fee_model") == "maker" else TAKER_FEE
    fee = notional * fee_rate
    slippage = notional * SLIPPAGE_BPS / 10000
    cfg["fee_paid"] += fee

    if side == "BUY":
        # Opening or adding long
        if cfg["position"] <= 0:
            # Close short if any
            if cfg["position"] < 0:
                # PnL from closing short
                close_pnl = abs(cfg["position"]) * (cfg["entry_price"] - price)
                cfg["pnl"] += close_pnl
                cfg["entry_price"] = 0
                cfg["position"] = 0
                if close_pnl > 0: cfg["wins"] += 1
                trades_log.append({
                    "time": datetime.now().strftime("%H:%M:%S"),
                    "strategy": name, "side": "BUY (close short)",
                    "size":0.025000,
                    "price": price, "pnl": round(close_pnl - fee - slippage, 4),
                    "fee": round(fee, 4),
                })
            # Open long
            cfg["entry_price"] = price
            cfg["position"] = sz
        else:
            # Adding to long
            cfg["entry_price"] = (cfg["entry_price"] * cfg["position"] + price * sz) / (cfg["position"] + sz)
            cfg["position"] += sz
        cfg["pnl"] -= fee + slippage
    else:  # SELL
        if cfg["position"] >= 0:
            if cfg["position"] > 0:
                close_pnl = cfg["position"] * (price - cfg["entry_price"])
                cfg["pnl"] += close_pnl
                cfg["entry_price"] = 0
                cfg["position"] = 0
                if close_pnl > 0: cfg["wins"] += 1
                trades_log.append({
                    "time": datetime.now().strftime("%H:%M:%S"),
                    "strategy": name, "side": "SELL (close long)",
                    "size": sz,
                    "price": price, "pnl": round(close_pnl - fee - slippage, 4),
                    "fee": round(fee, 4),
                })
            cfg["entry_price"] = price
            cfg["position"] = -sz
        else:
            cfg["entry_price"] = (cfg["entry_price"] * abs(cfg["position"]) + price * sz) / (abs(cfg["position"]) + sz)
            cfg["position"] -= sz
        cfg["pnl"] -= fee + slippage

    cfg["trades_today"] += 1
    cfg["pnl_pct"] = cfg["pnl"] / cfg["allocation"] * 100
    # Track per-strategy equity
    strategy_equity[name].append({"t": time.time(), "v": cfg["allocation"] + cfg["pnl"]})
    # Per-strategy trade with reason
    trade_entry = {
        "time": datetime.now().strftime("%H:%M:%S"),
        "side": side, "size": sz, "price": price,
        "pnl": round(cfg["pnl"], 4),
        "fee": round(fee, 4),
        "reason": reason,
        "allocation": cfg["allocation"],
        "fee_model": cfg.get("fee_model", "taker"),
    }
    per_strategy_trades[name].append(trade_entry)


# ═══════════════════════ A-S Spread Capture ═══════════════════════

def simulate_avellaneda(btc_bid, btc_ask):
    """Avellaneda-Stoikov: regime-adaptive spread capture.
    
    Regime-dependent behavior:
      LOW_VOL  → fill_prob=25%, tight margins (capture small spreads frequently)
      NORMAL   → fill_prob=15%, baseline
      HIGH_VOL → fill_prob=8%,  skip if spread too wide (adverse selection risk)
    """
    cfg = STRATEGIES["Avellaneda-Stoikov"]
    if btc_bid <= 0 or btc_ask <= 0:
        return

    regime = current_regime
    spread = btc_ask - btc_bid

    # Regime-dependent fill probability
    if regime == "LOW_VOL":
        fill_prob = 0.25
    elif regime == "HIGH_VOL":
        fill_prob = 0.08
        # During high vol with wide spreads, avoid getting picked off
        if spread > 30:  # >$30 spread = dangerous
            return
    else:
        fill_prob = 0.15

    if random.random() < fill_prob:
        if cfg["position"] <= 0:
            bid_fill_price = btc_bid
        else:
            bid_fill_price = btc_ask

        side = "BUY" if cfg["position"] <= 0 else "SELL"
        sz = cfg["size"]
        notional = sz * bid_fill_price
        fee = notional * MAKER_FEE  # A-S is a MAKER strategy — pay maker fee, not taker
        spread_profit = sz * (btc_ask - btc_bid)/2 if side == "BUY" else 0

        if side == "BUY":
            if cfg["position"] < 0:
                close_pnl = abs(cfg["position"]) * (cfg["entry_price"] - bid_fill_price)
                cfg["pnl"] += close_pnl
                if close_pnl > 0: cfg["wins"] += 1
            cfg["entry_price"] = bid_fill_price
            cfg["position"] = sz
            cfg["pnl"] += spread_profit - fee
        else:
            if cfg["position"] > 0:
                close_pnl = cfg["position"] * (bid_fill_price - cfg["entry_price"])
                cfg["pnl"] += close_pnl
                if close_pnl > 0: cfg["wins"] += 1
                trades_log.append({
                    "time": datetime.now().strftime("%H:%M:%S"),
                    "strategy": "Avellaneda-Stoikov",
                    "side": "SELL", "size": sz,
                    "price": bid_fill_price,
                    "pnl": round(close_pnl - fee, 4),
                    "fee": round(fee, 4),
                })
            cfg["position"] = 0
            cfg["entry_price"] = 0

        cfg["fee_paid"] += fee
        cfg["trades_today"] += 1
        cfg["pnl_pct"] = cfg["pnl"] / cfg["allocation"] * 100
        strategy_equity["Avellaneda-Stoikov"].append({"t": time.time(), "v": cfg["allocation"] + cfg["pnl"]})


# ═══════════════════════ Metrics ═══════════════════════

def write_metrics():
    total_pnl = sum(s["pnl"] for s in STRATEGIES.values())
    total_pnl_pct = (total_pnl / (STARTING_CAPITAL-RESERVE)) * 100 if STARTING_CAPITAL > RESERVE else 0
    for s in STRATEGIES.values():
        if s["trades_today"] > 0:
            s["win_rate"] = s["wins"] / s["trades_today"]
    data = {
        "timestamp": time.time(),
        "mode": "paper",
        "source": "Hyperliquid Mainnet",
        "total_equity": STARTING_CAPITAL + total_pnl,
        "base_equity": STARTING_CAPITAL,
        "total_pnl": total_pnl,
        "total_pnl_pct": total_pnl_pct,
        "reserve": RESERVE,
        "equity_history": equity_history[-600:],
        "strategy_equity": {k: list(v)[-300:] for k, v in strategy_equity.items()},
        "strategies": STRATEGIES,
        "trades": trades_log[-200:],
        "status": "running",
        "btc_price": btc_prices[-1] if btc_prices else 0,
        "eth_price": eth_prices[-1] if eth_prices else 0,
        "regime": current_regime,
        "regime_confidence": regime_confidence,
        "per_strategy_trades": {k: list(v)[-100:] for k, v in per_strategy_trades.items()},
    }
    try:
        with open(METRICS_FILE, "w") as f:
            json.dump(data, f, default=str)
    except IOError: pass

# ═══════════════════════ Main ═══════════════════════

async def main():
    log.info("="*60)
    log.info("  FTDT Quant Lab — PAPER TRADING (Mainnet Data)")
    log.info(f"  Capital: ${STARTING_CAPITAL:,} | Reserve: ${RESERVE:,}")
    log.info(f"  12 strategies × ${STRATEGIES['Order Book Imbalance']['allocation']:,.0f} allocation")
    log.info(f"  Fees: {TAKER_FEE*100:.2f}% taker | Slippage: {SLIPPAGE_BPS} bps")
    log.info(f"  Data: Hyperliquid MAINNET")
    log.info(f"  Dashboard: https://ftdt.io/cv")
    log.info("="*60)

    for s in STRATEGIES.values():
        s["status"] = "running"
    write_metrics()

    tick = 0
    strategy_names = list(STRATEGIES.keys())
    idx = 0

    try:
        while True:
            global prev_bids, prev_asks
            tick += 1

            # Fetch mainnet data
            if tick % 2 == 0:  # Every 2 seconds to respect rate limits
                prices = get_mainnet_prices()
                btc = prices.get("BTC", 0)
                eth = prices.get("ETH", 0)
                if btc > 0:
                    btc_prices.append(btc)
                if eth > 0:
                    eth_prices.append(eth)

                # Funding rates every 10 seconds
                if tick % 10 == 0:
                    fr = get_mainnet_funding()
                    if fr:
                        funding_rates.append(fr)

            # Compute signals every 5 ticks
            if tick % 5 == 0:
                current_regime = detect_regime()
                compute_signals()

            # Execute signals every 3-5 ticks
            if tick >= 10 and tick % random.randint(3, 6) == 0:
                btc = btc_prices[-1] if btc_prices else 0
                eth = eth_prices[-1] if eth_prices else 0
                if btc <= 0: continue

                # Get orderbook for A-S and Deep LOB
                btc_bid, btc_ask = get_mainnet_orderbook("BTC")
                bids, asks = get_deep_orderbook("BTC")

                # Avellaneda-Stoikov: simulate spread capture
                simulate_avellaneda(btc_bid, btc_ask)

                # Hawkes OFI: feed simulated trade to model
                hawkes_btc.update("B" if tick % 2 == 0 else "S", 0.001, btc)
                hawkes_sig = hawkes_btc.get_signal()
                if hawkes_sig["signal"]:
                    STRATEGIES["Hawkes OFI (new)"]["signals"].append({
                        "time": time.time(),
                        "signal": hawkes_sig["signal"],
                        "strength": hawkes_sig["strength"],
                    })

                # Deep LOB: analyze full orderbook
                if bids and asks:
                    lob_result = deep_lob.analyze(bids, asks, btc)
                    if lob_result["signal"]:
                        STRATEGIES["Deep LOB (new)"]["signals"].append({
                            "time": time.time(),
                            "signal": lob_result["signal"],
                            "strength": lob_result["strength"],
                        })

                # Queue Imbalance: weighted queue dynamics
                if bids and asks:
                    qi_result = queue_imb.analyze(
                        bids, asks, btc, prev_bids, prev_asks, 
                        btc_prices[-2] if len(btc_prices) >= 2 else 0)

                # Order Book Imbalance: real L2 bid/ask volume skew
                if bids and asks:
                    total_bids = sum(sz for _, sz in bids)
                    total_asks = sum(sz for _, sz in asks)
                    if total_asks > 0 and total_bids > total_asks * 1.5:
                        STRATEGIES["Order Book Imbalance"]["signals"].append({
                            "time": time.time(), "signal": "BUY",
                            "strength": min(1.0, (total_bids / total_asks - 1.0)),
                            "reason": "bid_skew_{:.1f}x".format(total_bids/total_asks)
                        })
                    elif total_bids > 0 and total_asks > total_bids * 1.5:
                        STRATEGIES["Order Book Imbalance"]["signals"].append({
                            "time": time.time(), "signal": "SELL",
                            "strength": min(1.0, (total_asks / total_bids - 1.0)),
                            "reason": "ask_skew_{:.1f}x".format(total_asks/total_bids)
                        })
                    if qi_result["signal"]:
                        STRATEGIES["Queue Imbalance"]["signals"].append({
                            "time": time.time(),
                            "signal": qi_result["signal"],
                            "strength": qi_result["strength"],
                        })
                    prev_bids, prev_asks = bids, asks

                # Cartea-Jaimungal: stochastic control with alpha estimate
                alpha_est = (btc_prices[-1] - btc_prices[-2]) / btc_prices[-2] \
                    if len(btc_prices) >= 2 and btc_prices[-2] > 0 else 0
                cj_inv = STRATEGIES["Cartea-Jaimungal"]["position"]
                cj_result = cartea.should_trade(btc, alpha_est, cj_inv, tick % 3600)
                if cj_result["signal"]:
                    STRATEGIES["Cartea-Jaimungal"]["signals"].append({
                        "time": time.time(),
                        "signal": cj_result["signal"],
                        "strength": cj_result["confidence"],
                    })

                # Guéant: closed-form market making
                gueant_inv = STRATEGIES["Guéant Market Making"]["position"]
                g_quotes = gueant.optimal_quotes(
                    btc, gueant_inv, tick % 3600,
                    adverse_prob=queue_imb.wqi_history[-1] if queue_imb.wqi_history else 0)
                # Simulate fill: if our quote is at/near best, track a signal
                if btc_bid > 0 and g_quotes["bid"] >= btc_bid * 0.999:
                    STRATEGIES["Guéant Market Making"]["signals"].append({
                        "time": time.time(), "signal": "BUY",
                        "strength": 0.5,
                    })
                elif btc_ask > 0 and g_quotes["ask"] <= btc_ask * 1.001:
                    STRATEGIES["Guéant Market Making"]["signals"].append({
                        "time": time.time(), "signal": "SELL",
                        "strength": 0.5,
                    })

                # Process next strategy's signals (round-robin 9 strategies)
                total_strats = len(strategy_names)
                name = strategy_names[idx % total_strats]
                idx += 1
                cfg = STRATEGIES[name]
                if name == "Avellaneda-Stoikov":
                    continue  # Already handled above

                # Check for signals with strength > fee barrier
                if not cfg["signals"]:
                    continue

                sig = cfg["signals"][-1]
                signal_str = str(sig["signal"])
                strength = abs(sig.get("strength", 0))
                signal_reason = sig.get("reason", signal_str)

                # Skip weak signals that can't overcome fees
                if strength < MIN_SIGNAL_STRENGTH:
                    continue

                coin = cfg["instrument"]
                px = btc if coin == "BTC" else eth
                if px <= 0: continue

                if "BUY" in signal_str.upper():
                    simulate_fill(name, "BUY", coin, px, signal_reason)
                    log.info(f"[{name[:4]:4s}] PAPER BUY  {cfg['size']} {coin} @ ${px:,.1f} | PnL: ${cfg['pnl']:+.2f} | {signal_reason}")
                elif "SELL" in signal_str.upper():
                    simulate_fill(name, "SELL", coin, px, signal_reason)
                    log.info(f"[{name[:4]:4s}] PAPER SELL {cfg['size']} {coin} @ ${px:,.1f} | PnL: ${cfg['pnl']:+.2f} | {signal_reason}")

            # Equity history
            total_pnl = sum(s["pnl"] for s in STRATEGIES.values())
            if tick % 3 == 0:
                equity_history.append({"t": time.time(), "v": STARTING_CAPITAL + total_pnl})

            write_metrics()

            if tick % 30 == 0:
                tp = sum(s["pnl"] for s in STRATEGIES.values())
                tr = sum(s["trades_today"] for s in STRATEGIES.values())
                tf = sum(s["fee_paid"] for s in STRATEGIES.values())
                btc_now = btc_prices[-1] if btc_prices else 0
                log.info(
                    f"Tick {tick:4d} | BTC: ${btc_now:,.0f} | "
                    f"PnL: ${tp:+.2f} | Trades: {tr:3d} | Fees: ${tf:.2f} | "
                    f"Regime: {current_regime}"
                )

            await asyncio.sleep(1)

    except KeyboardInterrupt:
        log.info("Stopping paper trader...")

    for s in STRATEGIES.values():
        s["status"] = "idle"
    write_metrics()
    tp = sum(s["pnl"] for s in STRATEGIES.values())
    tr = sum(s["trades_today"] for s in STRATEGIES.values())
    log.info(f"Paper trading stopped. Final PnL: ${tp:+.2f}, Trades: {tr}")


if __name__ == "__main__":
    asyncio.run(main())
