-
-
Saved Backtests click to view
+
+
+
+
+
+
+
+
+
Trade History
+
| Time | Side | Size | Price | PnL | Fee | Reason / Signal |
|---|
+
-
diff --git a/live/paper_trader.py b/live/paper_trader.py
index e69de29..25d6219 100644
--- a/live/paper_trader.py
+++ b/live/paper_trader.py
@@ -0,0 +1,660 @@
+"""
+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.002, "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.001, "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.005, "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.05, "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.001, "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.002, "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.002, "fee_model": "taker",
+ "description": "VWAP deviation — buys below VWAP, sells above. Oscillates around fair value.",
+ },
+ "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
+
+ # OFI
+ if len(btc_prices) >= 5:
+ ret = (btc - btc_prices[-5]) / btc_prices[-5]
+ if ret > 0.0005:
+ STRATEGIES["Order Book Imbalance"]["signals"].append({"time":time.time(),"signal":"SELL","strength":ret})
+ elif ret < -0.0005:
+ STRATEGIES["Order Book Imbalance"]["signals"].append({"time":time.time(),"signal":"BUY","strength":abs(ret)})
+
+ # 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 Arb — use actual mainnet funding rate
+ if funding_rates:
+ btc_fr = funding_rates[-1].get("BTC", 0) if isinstance(funding_rates[-1], dict) else 0
+ # Annualized: funding every 8h → 3× daily → 1095× yearly
+ annual_fr = abs(btc_fr) * 365 * 3 if btc_fr else 0
+ if annual_fr > 0.05: # >5% APR
+ STRATEGIES["Funding Rate Arb"]["signals"].append(
+ {"time":time.time(),"signal":"SELL" if btc_fr > 0 else "BUY",
+ "strength":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)})
+
+ # 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
+ if len(btc_prices) >= 20:
+ w = list(btc_prices)[-20:]; vols = [1 + i/len(w) for i in range(len(w))]
+ vwap = sum(p*v for p,v in zip(w, vols)) / sum(vols)
+ vstd = math.sqrt(sum((p-vwap)**2 for p in w) / len(w))
+ dev = (btc - vwap) / vstd if vstd > 0 else 0
+ if dev > 1.5:
+ STRATEGIES["Mean Reversion"]["signals"].append({"time":time.time(),"signal":"SELL","strength":dev})
+ elif dev < -1.5:
+ 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": abs(cfg["position"] if cfg["position"] < 0 else sz),
+ "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 * TAKER_FEE
+ 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)
+ 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())