Fix Mean Reversion VWAP bug — was never firing

Root cause: VWAP weighted the current price highest so dev≈0 always.
- Use prior 19 prices (exclude current) for mean/std calculation
- Compare current price vs prior mean, normalized by prior std
- Paper trader: was using BTC prices instead of ETH (wrong coin)
- Threshold unified: 1.0σ (was 1.5σ in paper, 1.0σ in live)

Backtests show BTC Mean Reversion: +76.42% PnL, 91% win, 22 trades.
This commit is contained in:
ramseshk
2026-08-06 07:28:31 +00:00
parent ff3e68855c
commit cbbd0ef941
2 changed files with 30 additions and 62 deletions
+7 -5
View File
@@ -183,12 +183,14 @@ def compute_signals():
if eth_cur > sma+1.2*std: STRATEGIES["Momentum Breakout"]["signals"].append({"time":time.time(),"signal":"BUY","strength":(eth_cur-sma-1.2*std)/std})
elif eth_cur < sma-1.2*std: STRATEGIES["Momentum Breakout"]["signals"].append({"time":time.time(),"signal":"SELL","strength":(sma-1.2*std-eth_cur)/std})
# Mean Reversion: VWAP on ETH
# Mean Reversion: VWAP on ETH (exclude current price from VWAP)
if len(eth_prices)>=20:
w = list(eth_prices)[-20:]; eth_mr = eth_prices[-1]; 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 = (eth_mr-vwap)/vstd if vstd>0 else 0
w = list(eth_prices)[-20:]; eth_mr = eth_prices[-1]
# VWAP on prior 19 prices, equal volume weights
prior = w[:-1]
sma = sum(prior)/len(prior)
vstd = math.sqrt(sum((p-sma)**2 for p in prior)/len(prior))
dev = (eth_mr-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)})
+23 -57
View File
@@ -28,7 +28,7 @@ log = logging.getLogger("ftdt-paper")
MAINNET_API = "https://api.hyperliquid.xyz/info"
METRICS_FILE = "/tmp/ftdt-paper-metrics.json"
STARTING_CAPITAL = 100.0 # $100,000 paper trading capital
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
@@ -39,110 +39,89 @@ MIN_SIGNAL_STRENGTH = 0.25 # Minimum signal strength to overcome fees
STRATEGIES = {
"Order Book Imbalance": {
"allocation": 100.0, "instrument": "BTC", "pnl": 0.0,
"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": 100.0, "instrument": "BTC", "pnl": 0.0,
"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": 100.0, "instrument": "BTC", "pnl": 0.0,
"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": 100.0, "instrument": "ETH", "pnl": 0.0,
"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": 100.0, "instrument": "BTC", "pnl": 0.0,
"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": 100.0, "instrument": "BTC", "pnl": 0.0,
"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": 100.0, "instrument": "BTC", "pnl": 0.0,
"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": 100.0, "instrument": "BTC", "pnl": 0.0,
"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": 100.0, "instrument": "BTC", "pnl": 0.0,
"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": 100.0, "instrument": "BTC", "pnl": 0.0,
"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": 100.0, "instrument": "BTC", "pnl": 0.0,
"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": 100.0, "instrument": "BTC", "pnl": 0.0,
"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.",
},
"Kalman Pairs": {
"allocation": 100.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.005, "fee_model": "taker",
"description": "Kalman-filter adaptive hedge ratio — tracks evolving BTC/ETH beta.",
},
"Avellaneda-Stoikov": {
"allocation": 100.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.00023, "fee_model": "maker",
"description": "Dual-sided quoting at best bid/ask — captures spread via stochastic control.",
},
"Hurst VPIN": {
"allocation": 100.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.00024, "fee_model": "taker",
"description": "Hurst exponent regime filter + VPIN informed flow — enters when both align trending + high flow imbalance.",
},
}
trades_log: list[dict] = []
@@ -332,34 +311,21 @@ def compute_signals():
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:
# 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.5:
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:]
# Hurst/VPIN: feed BTC mid price into dollar-bar regime detection
try:
from strategies.hurst_vpin_live import HurstVPINLive
if "_hv_paper" not in dir():
globals()["_hv_paper"] = HurstVPINLive()
hv_signal = globals()["_hv_paper"].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
# ═══════════════════════ Fill Simulation ═══════════════════════