feat: Multi-Pair Divergence Selection Strategy
- 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
This commit is contained in:
156
scripts/run_multi_pair_backtest.py
Normal file
156
scripts/run_multi_pair_backtest.py
Normal file
@@ -0,0 +1,156 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user