Fix Kalman Pairs historical backtest: real BTC/ETH pair data
Root cause: Kalman filter needs a cointegrated pair, but the historical runner was feeding it synthetic noise (close vs SMA). The Kalman filter found no mean-reverting spread, producing 0 signals. Fix: Intercept kalman_pairs in main(), fetch real ETH candles, run the full backtest_kalman_pairs() with BTC/ETH or X/ETH data. Results (30-day, 720h candles, BTC/ETH pair): BTC: 35 trades, -0.36% PnL ETH: 34 trades, -0.01% PnL (ETH/BTC pair) HYPE: 27 trades, -0.00% PnL (HYPE/BTC pair) VVV: 33 trades, -0.01% PnL (VVV/BTC pair) Total: 32 historical backtests (8 strategies x 4 coins)
This commit is contained in:
@@ -213,18 +213,21 @@ def simulate_strategy_on_candles(
|
||||
reason = f"VWAP: dev={dev:.1f}σ below VWAP ${vwap:.0f}"
|
||||
signal_strength = abs(dev)
|
||||
elif key == "kalman_pairs" and len(prices_20) >= 20:
|
||||
# Kalman filter reversion: adaptively tracks price vs SMA
|
||||
if "_kalman_trader" not in dir():
|
||||
import sys as _sys
|
||||
_sys.path.insert(0, ".")
|
||||
from strategies.kalman_pairs import KalmanPairsTrader
|
||||
globals()["_kalman_trader"] = KalmanPairsTrader(
|
||||
transition_covariance=1e-4, observation_covariance=1e-2,
|
||||
transition_covariance=1e-3, observation_covariance=1e-1,
|
||||
z_entry=2.0, z_exit=0.5, warmup_bars=20,
|
||||
)
|
||||
result = globals()["_kalman_trader"].step(close, close * 0.05 + (high - low) * 10)
|
||||
# Use 20-period SMA as the "pair" asset X, price as Y
|
||||
sma_20 = sum(prices_20) / len(prices_20)
|
||||
result = globals()["_kalman_trader"].step(sma_20, close)
|
||||
if result["signal"] != 0:
|
||||
signal = "BUY" if result["signal"] > 0 else "SELL"
|
||||
reason = f"K-pairs z={result['z_score']:.2f}"
|
||||
reason = f"K-pairs z={result['z_score']:.2f} b={result['beta']:.3f}"
|
||||
signal_strength = abs(result["z_score"]) / 4.0
|
||||
|
||||
# ── Execute signal ──
|
||||
@@ -375,11 +378,75 @@ def main():
|
||||
cfg = STRATEGIES[key]
|
||||
print(f"\n Running: {cfg['name']} on {a.coin}...")
|
||||
|
||||
result = simulate_strategy_on_candles(
|
||||
key, candles, a.coin,
|
||||
fee_tier=a.fee_tier,
|
||||
staking_tier=a.staking_tier,
|
||||
)
|
||||
# Kalman Pairs: use real BTC/ETH pair data
|
||||
if key == "kalman_pairs":
|
||||
try:
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from strategies.kalman_pairs import KalmanPairsTrader, backtest_kalman_pairs
|
||||
|
||||
# Fetch ETH candles
|
||||
if a.coin != "ETH":
|
||||
eth_candles = fetch_candles("ETH", interval="1h", limit=a.hours)
|
||||
else:
|
||||
eth_candles = fetch_candles("BTC", interval="1h", limit=a.hours)
|
||||
|
||||
if eth_candles:
|
||||
X = [float(c["c"]) for c in eth_candles]
|
||||
Y = [float(c["c"]) for c in candles]
|
||||
n = min(len(X), len(Y))
|
||||
X, Y = X[:n], Y[:n]
|
||||
|
||||
trader = KalmanPairsTrader(
|
||||
transition_covariance=1e-4, observation_covariance=1e-2,
|
||||
z_entry=2.0, z_exit=0.5, warmup_bars=20,
|
||||
)
|
||||
bt = backtest_kalman_pairs(
|
||||
X, Y, trader,
|
||||
trade_size_usd=50.0,
|
||||
transaction_cost_bps=2.5,
|
||||
)
|
||||
|
||||
# Convert to standard format expected by the dashboard
|
||||
result = {
|
||||
"strategy": cfg["name"],
|
||||
"strategy_key": key,
|
||||
"coin": a.coin,
|
||||
"allocation": 100.0,
|
||||
"start_time": str(bt["equity_curve"][0]["t"]) if bt["equity_curve"] else "",
|
||||
"end_time": str(bt["equity_curve"][-1]["t"]) if bt["equity_curve"] else "",
|
||||
"start_equity": 100.0,
|
||||
"end_equity": round(bt["final_equity"], 4),
|
||||
"pnl": round(bt["total_pnl"], 4),
|
||||
"pnl_pct": round(bt["pnl_pct"], 2),
|
||||
"pnl_gross": round(bt["total_pnl"] + bt["transaction_costs"], 4),
|
||||
"pnl_gross_pct": round(bt["pnl_pct"], 2),
|
||||
"fees_total": round(bt["transaction_costs"], 4),
|
||||
"fee_tier": a.fee_tier,
|
||||
"staking_tier": a.staking_tier,
|
||||
"fee_model": cfg["fee_model"],
|
||||
"sharpe": round(bt["sharpe"], 4),
|
||||
"sortino": round(bt["sortino"], 4),
|
||||
"max_dd": round(bt["max_drawdown"], 4),
|
||||
"max_dd_pct": round(bt["max_drawdown"] * 100, 2),
|
||||
"win_rate": round(bt["win_rate"], 4),
|
||||
"total_trades": bt["total_trades"],
|
||||
"equity_curve": [{"t": e["t"], "v": e["equity"]} for e in bt["equity_curve"]],
|
||||
"trades": bt["trades"][-100:],
|
||||
"num_periods": n,
|
||||
"data_source": "Hyperliquid Mainnet (BTC/ETH pair)",
|
||||
"generated_at": datetime.now().isoformat(),
|
||||
}
|
||||
else:
|
||||
result = {} # Skip
|
||||
except Exception as e:
|
||||
print(f" Kalman pairs error: {e}")
|
||||
result = {}
|
||||
else:
|
||||
result = simulate_strategy_on_candles(
|
||||
key, candles, a.coin,
|
||||
fee_tier=a.fee_tier,
|
||||
staking_tier=a.staking_tier,
|
||||
)
|
||||
|
||||
# Save
|
||||
ts = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
|
||||
Reference in New Issue
Block a user