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:
ramseshk
2026-08-05 07:32:41 +00:00
parent 803a38b237
commit f7f47b5484
9 changed files with 13541 additions and 11664 deletions
+75 -8
View File
@@ -213,18 +213,21 @@ def simulate_strategy_on_candles(
reason = f"VWAP: dev={dev:.1f}σ below VWAP ${vwap:.0f}" reason = f"VWAP: dev={dev:.1f}σ below VWAP ${vwap:.0f}"
signal_strength = abs(dev) signal_strength = abs(dev)
elif key == "kalman_pairs" and len(prices_20) >= 20: elif key == "kalman_pairs" and len(prices_20) >= 20:
# Kalman filter reversion: adaptively tracks price vs SMA
if "_kalman_trader" not in dir(): if "_kalman_trader" not in dir():
import sys as _sys import sys as _sys
_sys.path.insert(0, ".") _sys.path.insert(0, ".")
from strategies.kalman_pairs import KalmanPairsTrader from strategies.kalman_pairs import KalmanPairsTrader
globals()["_kalman_trader"] = 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, 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: if result["signal"] != 0:
signal = "BUY" if result["signal"] > 0 else "SELL" 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 signal_strength = abs(result["z_score"]) / 4.0
# ── Execute signal ── # ── Execute signal ──
@@ -375,11 +378,75 @@ def main():
cfg = STRATEGIES[key] cfg = STRATEGIES[key]
print(f"\n Running: {cfg['name']} on {a.coin}...") print(f"\n Running: {cfg['name']} on {a.coin}...")
result = simulate_strategy_on_candles( # Kalman Pairs: use real BTC/ETH pair data
key, candles, a.coin, if key == "kalman_pairs":
fee_tier=a.fee_tier, try:
staking_tier=a.staking_tier, 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 # Save
ts = datetime.now().strftime("%Y%m%d-%H%M%S") ts = datetime.now().strftime("%Y%m%d-%H%M%S")
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff