- Extend regime detection to top 10 cryptocurrencies (45 pairs) - Dynamic pair selection based on divergence score (|z_score| * probability) - Universal ML model trained on all pairs - Correlation-based filtering to avoid redundant positions - Funding rate integration from OKX for all 10 assets - ATR-based dynamic stop-loss and take-profit - Walk-forward training with 70/30 split Performance: +35.69% return (vs +28.66% baseline), 63.6% win rate
157 lines
4.5 KiB
Python
157 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Run Multi-Pair Divergence Strategy backtest and compare with baseline.
|
|
|
|
Compares the multi-pair strategy against the single-pair BTC/ETH regime strategy.
|
|
"""
|
|
import sys
|
|
sys.path.insert(0, '.')
|
|
|
|
from engine.backtester import Backtester
|
|
from engine.data_manager import DataManager
|
|
from engine.logging_config import setup_logging, get_logger
|
|
from engine.reporting import Reporter
|
|
from strategies.multi_pair import MultiPairDivergenceStrategy, MultiPairConfig
|
|
from strategies.regime_strategy import RegimeReversionStrategy
|
|
from engine.market import MarketType
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
def run_baseline():
|
|
"""Run baseline BTC/ETH regime strategy."""
|
|
logger.info("=" * 60)
|
|
logger.info("BASELINE: BTC/ETH Regime Reversion Strategy")
|
|
logger.info("=" * 60)
|
|
|
|
dm = DataManager()
|
|
bt = Backtester(dm)
|
|
|
|
strategy = RegimeReversionStrategy()
|
|
|
|
result = bt.run_strategy(
|
|
strategy,
|
|
'okx',
|
|
'ETH-USDT',
|
|
timeframe='1h',
|
|
init_cash=10000
|
|
)
|
|
|
|
logger.info("Baseline Results:")
|
|
logger.info(" Total Return: %.2f%%", result.portfolio.total_return() * 100)
|
|
logger.info(" Total Trades: %d", result.portfolio.trades.count())
|
|
logger.info(" Win Rate: %.1f%%", result.portfolio.trades.win_rate() * 100)
|
|
|
|
return result
|
|
|
|
|
|
def run_multi_pair(assets: list[str] | None = None):
|
|
"""Run multi-pair divergence strategy."""
|
|
logger.info("=" * 60)
|
|
logger.info("MULTI-PAIR: Divergence Selection Strategy")
|
|
logger.info("=" * 60)
|
|
|
|
dm = DataManager()
|
|
bt = Backtester(dm)
|
|
|
|
# Use provided assets or default
|
|
if assets:
|
|
config = MultiPairConfig(assets=assets)
|
|
else:
|
|
config = MultiPairConfig()
|
|
|
|
logger.info("Configured %d assets, %d pairs", len(config.assets), config.get_pair_count())
|
|
|
|
strategy = MultiPairDivergenceStrategy(config=config)
|
|
|
|
result = bt.run_strategy(
|
|
strategy,
|
|
'okx',
|
|
'ETH-USDT', # Reference asset (not used for trading, just index alignment)
|
|
timeframe='1h',
|
|
init_cash=10000
|
|
)
|
|
|
|
logger.info("Multi-Pair Results:")
|
|
logger.info(" Total Return: %.2f%%", result.portfolio.total_return() * 100)
|
|
logger.info(" Total Trades: %d", result.portfolio.trades.count())
|
|
logger.info(" Win Rate: %.1f%%", result.portfolio.trades.win_rate() * 100)
|
|
|
|
return result
|
|
|
|
|
|
def compare_results(baseline, multi_pair):
|
|
"""Compare and display results."""
|
|
logger.info("=" * 60)
|
|
logger.info("COMPARISON")
|
|
logger.info("=" * 60)
|
|
|
|
baseline_return = baseline.portfolio.total_return() * 100
|
|
multi_return = multi_pair.portfolio.total_return() * 100
|
|
|
|
improvement = multi_return - baseline_return
|
|
|
|
logger.info("Baseline Return: %.2f%%", baseline_return)
|
|
logger.info("Multi-Pair Return: %.2f%%", multi_return)
|
|
logger.info("Improvement: %.2f%% (%.1fx)",
|
|
improvement,
|
|
multi_return / baseline_return if baseline_return != 0 else 0)
|
|
|
|
baseline_trades = baseline.portfolio.trades.count()
|
|
multi_trades = multi_pair.portfolio.trades.count()
|
|
|
|
logger.info("Baseline Trades: %d", baseline_trades)
|
|
logger.info("Multi-Pair Trades: %d", multi_trades)
|
|
|
|
return {
|
|
'baseline_return': baseline_return,
|
|
'multi_pair_return': multi_return,
|
|
'improvement': improvement,
|
|
'baseline_trades': baseline_trades,
|
|
'multi_pair_trades': multi_trades
|
|
}
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
setup_logging()
|
|
|
|
# Check available assets
|
|
dm = DataManager()
|
|
available = []
|
|
|
|
for symbol in MultiPairConfig().assets:
|
|
try:
|
|
dm.load_data('okx', symbol, '1h', market_type=MarketType.PERPETUAL)
|
|
available.append(symbol)
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
if len(available) < 2:
|
|
logger.error(
|
|
"Need at least 2 assets to run multi-pair strategy. "
|
|
"Run: uv run python scripts/download_multi_pair_data.py"
|
|
)
|
|
return
|
|
|
|
logger.info("Found data for %d assets: %s", len(available), available)
|
|
|
|
# Run baseline
|
|
baseline_result = run_baseline()
|
|
|
|
# Run multi-pair
|
|
multi_result = run_multi_pair(available)
|
|
|
|
# Compare
|
|
comparison = compare_results(baseline_result, multi_result)
|
|
|
|
# Save reports
|
|
reporter = Reporter()
|
|
reporter.save_reports(multi_result, "multi_pair_divergence")
|
|
|
|
logger.info("Reports saved to backtest_logs/")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|