#!/usr/bin/env python3 """ Simple Strategy Comparison for 2025 Data This script runs both the original and incremental strategies on the same 2025 timeframe and creates side-by-side comparison plots. """ import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.dates as mdates import logging from typing import Dict, List, Tuple import os import sys from datetime import datetime import json # Add project root to path sys.path.insert(0, os.path.abspath('..')) from cycles.IncStrategies.metatrend_strategy import IncMetaTrendStrategy from cycles.IncStrategies.inc_backtester import IncBacktester, BacktestConfig from cycles.utils.storage import Storage # Configure logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) class SimpleStrategyComparison: """Simple comparison between original and incremental strategies for 2025 data.""" def __init__(self, start_date: str = "2025-01-01", end_date: str = "2025-05-01"): """Initialize the comparison.""" self.start_date = start_date self.end_date = end_date self.storage = Storage(logging=logger) # Results storage self.original_results = None self.incremental_results = None self.test_data = None def load_data(self) -> pd.DataFrame: """Load test data for the specified date range.""" logger.info(f"Loading data from {self.start_date} to {self.end_date}") try: # Load data directly from CSV file data_file = "../data/btcusd_1-min_data.csv" logger.info(f"Loading data from: {data_file}") # Read CSV file df = pd.read_csv(data_file) # Convert timestamp column df['timestamp'] = pd.to_datetime(df['Timestamp'], unit='s') # Rename columns to match expected format df = df.rename(columns={ 'Open': 'open', 'High': 'high', 'Low': 'low', 'Close': 'close', 'Volume': 'volume' }) # Filter by date range start_dt = pd.to_datetime(self.start_date) end_dt = pd.to_datetime(self.end_date) df = df[(df['timestamp'] >= start_dt) & (df['timestamp'] < end_dt)] if df.empty: raise ValueError(f"No data found for the specified date range: {self.start_date} to {self.end_date}") # Keep only required columns df = df[['timestamp', 'open', 'high', 'low', 'close', 'volume']] self.test_data = df logger.info(f"Loaded {len(df)} data points") logger.info(f"Date range: {df['timestamp'].min()} to {df['timestamp'].max()}") logger.info(f"Price range: ${df['close'].min():.0f} - ${df['close'].max():.0f}") return df except Exception as e: logger.error(f"Failed to load test data: {e}") import traceback traceback.print_exc() raise def load_original_results(self) -> Dict: """Load original strategy results from existing CSV file.""" logger.info("šŸ“‚ Loading Original Strategy results from CSV...") try: # Load the original trades file original_file = "../results/trades_15min(15min)_ST3pct.csv" if not os.path.exists(original_file): logger.warning(f"Original trades file not found: {original_file}") return None df = pd.read_csv(original_file) df['entry_time'] = pd.to_datetime(df['entry_time']) df['exit_time'] = pd.to_datetime(df['exit_time'], errors='coerce') # Calculate performance metrics buy_signals = df[df['type'] == 'BUY'] sell_signals = df[df['type'] != 'BUY'] # Calculate final value using compounding logic initial_usd = 10000 final_usd = initial_usd for _, trade in sell_signals.iterrows(): profit_pct = trade['profit_pct'] final_usd *= (1 + profit_pct) total_return = (final_usd - initial_usd) / initial_usd * 100 # Convert to standardized format trades = [] for _, row in df.iterrows(): trades.append({ 'timestamp': row['entry_time'], 'type': row['type'], 'price': row.get('entry_price', row.get('exit_price')), 'exit_time': row['exit_time'], 'exit_price': row.get('exit_price'), 'profit_pct': row.get('profit_pct', 0), 'source': 'original' }) performance = { 'strategy_name': 'Original Strategy', 'initial_value': initial_usd, 'final_value': final_usd, 'total_return': total_return, 'num_trades': len(sell_signals), 'trades': trades } logger.info(f"āœ… Original strategy loaded: {len(sell_signals)} trades, {total_return:.2f}% return") self.original_results = performance return performance except Exception as e: logger.error(f"āŒ Error loading original strategy: {e}") import traceback traceback.print_exc() return None def run_incremental_strategy(self, initial_usd: float = 10000) -> Dict: """Run the incremental strategy using the backtester.""" logger.info("šŸ”„ Running Incremental Strategy...") try: # Create strategy instance strategy = IncMetaTrendStrategy("metatrend", weight=1.0, params={ "timeframe": "1min", "enable_logging": False }) # Save our data to a temporary CSV file for the backtester temp_data_file = "../data/temp_2025_data.csv" # Prepare data in the format expected by Storage class temp_df = self.test_data.copy() temp_df['Timestamp'] = temp_df['timestamp'].astype('int64') // 10**9 # Convert to Unix timestamp temp_df = temp_df.rename(columns={ 'open': 'Open', 'high': 'High', 'low': 'Low', 'close': 'Close', 'volume': 'Volume' }) temp_df = temp_df[['Timestamp', 'Open', 'High', 'Low', 'Close', 'Volume']] temp_df.to_csv(temp_data_file, index=False) # Create backtest configuration with correct parameters config = BacktestConfig( data_file="temp_2025_data.csv", start_date=self.start_date, end_date=self.end_date, initial_usd=initial_usd, stop_loss_pct=0.03, take_profit_pct=0.0 ) # Create backtester backtester = IncBacktester(config) # Run backtest results = backtester.run_single_strategy(strategy) # Clean up temporary file if os.path.exists(temp_data_file): os.remove(temp_data_file) # Extract results trades = results.get('trades', []) # Convert trades to standardized format standardized_trades = [] for trade in trades: standardized_trades.append({ 'timestamp': trade.entry_time, 'type': 'BUY', 'price': trade.entry_price, 'exit_time': trade.exit_time, 'exit_price': trade.exit_price, 'profit_pct': trade.profit_pct, 'source': 'incremental' }) # Add sell signal if trade.exit_time: standardized_trades.append({ 'timestamp': trade.exit_time, 'type': 'SELL', 'price': trade.exit_price, 'exit_time': trade.exit_time, 'exit_price': trade.exit_price, 'profit_pct': trade.profit_pct, 'source': 'incremental' }) # Calculate performance metrics final_value = results.get('final_usd', initial_usd) total_return = (final_value - initial_usd) / initial_usd * 100 performance = { 'strategy_name': 'Incremental MetaTrend', 'initial_value': initial_usd, 'final_value': final_value, 'total_return': total_return, 'num_trades': results.get('n_trades', 0), 'trades': standardized_trades } logger.info(f"āœ… Incremental strategy completed: {results.get('n_trades', 0)} trades, {total_return:.2f}% return") self.incremental_results = performance return performance except Exception as e: logger.error(f"āŒ Error running incremental strategy: {e}") import traceback traceback.print_exc() return None def create_side_by_side_comparison(self, save_path: str = "../results/strategy_comparison_2025_simple.png"): """Create side-by-side comparison plots.""" logger.info("šŸ“Š Creating side-by-side comparison plots...") # Create figure with subplots fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(20, 16)) # Plot 1: Original Strategy Signals self._plot_strategy_signals(ax1, self.original_results, "Original Strategy", 'blue') # Plot 2: Incremental Strategy Signals self._plot_strategy_signals(ax2, self.incremental_results, "Incremental Strategy", 'red') # Plot 3: Performance Comparison self._plot_performance_comparison(ax3) # Plot 4: Trade Statistics self._plot_trade_statistics(ax4) # Overall title fig.suptitle(f'Strategy Comparison: {self.start_date} to {self.end_date}', fontsize=20, fontweight='bold', y=0.98) # Adjust layout and save plt.tight_layout() plt.savefig(save_path, dpi=300, bbox_inches='tight') plt.show() logger.info(f"šŸ“ˆ Comparison plot saved to: {save_path}") def _plot_strategy_signals(self, ax, results: Dict, title: str, color: str): """Plot price data with trading signals for a single strategy.""" if not results: ax.text(0.5, 0.5, f"No data for {title}", ha='center', va='center', transform=ax.transAxes) return # Plot price data ax.plot(self.test_data['timestamp'], self.test_data['close'], color='black', linewidth=1, alpha=0.7, label='BTC Price') # Plot trading signals trades = results['trades'] buy_signals = [t for t in trades if t['type'] == 'BUY'] sell_signals = [t for t in trades if t['type'] == 'SELL' or t['type'] != 'BUY'] if buy_signals: buy_times = [t['timestamp'] for t in buy_signals] buy_prices = [t['price'] for t in buy_signals] ax.scatter(buy_times, buy_prices, color='green', marker='^', s=80, label=f'Buy ({len(buy_signals)})', zorder=5, alpha=0.8) if sell_signals: # Separate profitable and losing sells profitable_sells = [t for t in sell_signals if t.get('profit_pct', 0) > 0] losing_sells = [t for t in sell_signals if t.get('profit_pct', 0) <= 0] if profitable_sells: profit_times = [t['timestamp'] for t in profitable_sells] profit_prices = [t['price'] for t in profitable_sells] ax.scatter(profit_times, profit_prices, color='blue', marker='v', s=80, label=f'Profitable Sell ({len(profitable_sells)})', zorder=5, alpha=0.8) if losing_sells: loss_times = [t['timestamp'] for t in losing_sells] loss_prices = [t['price'] for t in losing_sells] ax.scatter(loss_times, loss_prices, color='red', marker='v', s=80, label=f'Loss Sell ({len(losing_sells)})', zorder=5, alpha=0.8) ax.set_title(title, fontsize=14, fontweight='bold') ax.set_ylabel('Price (USD)', fontsize=12) ax.legend(loc='upper left', fontsize=10) ax.grid(True, alpha=0.3) ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}')) # Format x-axis ax.xaxis.set_major_locator(mdates.DayLocator(interval=7)) ax.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d')) plt.setp(ax.xaxis.get_majorticklabels(), rotation=45) def _plot_performance_comparison(self, ax): """Plot performance comparison bar chart.""" if not self.original_results or not self.incremental_results: ax.text(0.5, 0.5, "Performance data not available", ha='center', va='center', transform=ax.transAxes, fontsize=14) return strategies = ['Original', 'Incremental'] returns = [self.original_results['total_return'], self.incremental_results['total_return']] colors = ['blue', 'red'] bars = ax.bar(strategies, returns, color=colors, alpha=0.7) # Add value labels on bars for bar, return_val in zip(bars, returns): height = bar.get_height() ax.text(bar.get_x() + bar.get_width()/2., height + (1 if height >= 0 else -3), f'{return_val:.1f}%', ha='center', va='bottom' if height >= 0 else 'top', fontweight='bold', fontsize=12) ax.set_title('Total Return Comparison', fontsize=14, fontweight='bold') ax.set_ylabel('Return (%)', fontsize=12) ax.grid(True, alpha=0.3, axis='y') ax.axhline(y=0, color='black', linestyle='-', alpha=0.5) def _plot_trade_statistics(self, ax): """Create trade statistics table.""" ax.axis('off') if not self.original_results or not self.incremental_results: ax.text(0.5, 0.5, "Trade data not available", ha='center', va='center', transform=ax.transAxes, fontsize=14) return # Create comparison table orig = self.original_results incr = self.incremental_results comparison_text = f""" STRATEGY COMPARISON SUMMARY {'='*50} {'Metric':<20} {'Original':<15} {'Incremental':<15} {'Difference':<15} {'-'*65} {'Initial Value':<20} ${orig['initial_value']:>10,.0f} ${incr['initial_value']:>12,.0f} ${incr['initial_value'] - orig['initial_value']:>12,.0f} {'Final Value':<20} ${orig['final_value']:>10,.0f} ${incr['final_value']:>12,.0f} ${incr['final_value'] - orig['final_value']:>12,.0f} {'Total Return':<20} {orig['total_return']:>10.1f}% {incr['total_return']:>12.1f}% {incr['total_return'] - orig['total_return']:>12.1f}% {'Number of Trades':<20} {orig['num_trades']:>10} {incr['num_trades']:>12} {incr['num_trades'] - orig['num_trades']:>12} TIMEFRAME: {self.start_date} to {self.end_date} DATA POINTS: {len(self.test_data):,} minute bars PRICE RANGE: ${self.test_data['close'].min():,.0f} - ${self.test_data['close'].max():,.0f} Both strategies use MetaTrend logic with 3% stop loss. Differences indicate implementation variations. """ ax.text(0.05, 0.95, comparison_text, transform=ax.transAxes, fontsize=10, verticalalignment='top', fontfamily='monospace', bbox=dict(boxstyle="round,pad=0.5", facecolor="lightgray", alpha=0.9)) def save_results(self, output_dir: str = "../results"): """Save detailed results to files.""" logger.info("šŸ’¾ Saving detailed results...") os.makedirs(output_dir, exist_ok=True) # Save performance summary summary = { 'timeframe': f"{self.start_date} to {self.end_date}", 'data_points': len(self.test_data) if self.test_data is not None else 0, 'original_strategy': self.original_results, 'incremental_strategy': self.incremental_results, 'comparison_timestamp': datetime.now().isoformat() } summary_file = f"{output_dir}/strategy_comparison_2025_simple.json" with open(summary_file, 'w') as f: json.dump(summary, f, indent=2, default=str) logger.info(f"Performance summary saved to: {summary_file}") def run_full_comparison(self, initial_usd: float = 10000): """Run the complete comparison workflow.""" logger.info("šŸš€ Starting Simple Strategy Comparison for 2025") logger.info("=" * 60) try: # Load data self.load_data() # Load original results and run incremental strategy self.load_original_results() self.run_incremental_strategy(initial_usd) # Create comparison plots self.create_side_by_side_comparison() # Save results self.save_results() # Print summary if self.original_results and self.incremental_results: logger.info("\nšŸ“Š COMPARISON SUMMARY:") logger.info(f"Original Strategy: ${self.original_results['final_value']:,.0f} ({self.original_results['total_return']:+.2f}%)") logger.info(f"Incremental Strategy: ${self.incremental_results['final_value']:,.0f} ({self.incremental_results['total_return']:+.2f}%)") logger.info(f"Difference: ${self.incremental_results['final_value'] - self.original_results['final_value']:,.0f} ({self.incremental_results['total_return'] - self.original_results['total_return']:+.2f}%)") logger.info("āœ… Simple comparison completed successfully!") except Exception as e: logger.error(f"āŒ Error during comparison: {e}") import traceback traceback.print_exc() def main(): """Main function to run the strategy comparison.""" # Create comparison instance comparison = SimpleStrategyComparison( start_date="2025-01-01", end_date="2025-05-01" ) # Run full comparison comparison.run_full_comparison(initial_usd=10000) if __name__ == "__main__": main()