#!/usr/bin/env python3 """ Comprehensive comparison plotting script for trading strategies. Compares original strategy vs incremental strategy results. """ import os import sys import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.dates as mdates from datetime import datetime import warnings warnings.filterwarnings('ignore') # Add the project root to the path sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('.')) from cycles.utils.storage import Storage from cycles.utils.data_utils import aggregate_to_minutes def load_trades_data(trades_file): """Load and process trades data.""" if not os.path.exists(trades_file): print(f"File not found: {trades_file}") return None df = pd.read_csv(trades_file) # Convert timestamps df['entry_time'] = pd.to_datetime(df['entry_time']) if 'exit_time' in df.columns: df['exit_time'] = pd.to_datetime(df['exit_time'], errors='coerce') # Separate buy and sell signals buy_signals = df[df['type'] == 'BUY'].copy() sell_signals = df[df['type'] != 'BUY'].copy() return { 'all_trades': df, 'buy_signals': buy_signals, 'sell_signals': sell_signals } def calculate_strategy_performance(trades_data): """Calculate basic performance metrics.""" if trades_data is None: return None sell_signals = trades_data['sell_signals'] if len(sell_signals) == 0: return None total_profit_pct = sell_signals['profit_pct'].sum() num_trades = len(sell_signals) win_rate = len(sell_signals[sell_signals['profit_pct'] > 0]) / num_trades avg_profit = sell_signals['profit_pct'].mean() # Exit type breakdown exit_types = sell_signals['type'].value_counts().to_dict() return { 'total_profit_pct': total_profit_pct * 100, 'num_trades': num_trades, 'win_rate': win_rate * 100, 'avg_profit_pct': avg_profit * 100, 'exit_types': exit_types, 'best_trade': sell_signals['profit_pct'].max() * 100, 'worst_trade': sell_signals['profit_pct'].min() * 100 } def plot_strategy_comparison(original_file, incremental_file, price_data, output_file="strategy_comparison.png"): """Create comprehensive comparison plot of both strategies on the same chart.""" print(f"Loading original strategy: {original_file}") original_data = load_trades_data(original_file) print(f"Loading incremental strategy: {incremental_file}") incremental_data = load_trades_data(incremental_file) if original_data is None or incremental_data is None: print("Error: Could not load one or both trade files") return # Calculate performance metrics original_perf = calculate_strategy_performance(original_data) incremental_perf = calculate_strategy_performance(incremental_data) # Create figure with subplots fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(20, 16), gridspec_kw={'height_ratios': [3, 1]}) # Plot 1: Combined Strategy Comparison on Same Chart ax1.plot(price_data.index, price_data['close'], label='BTC Price', color='black', linewidth=2, zorder=1) # Calculate price range for offset positioning price_min = price_data['close'].min() price_max = price_data['close'].max() price_range = price_max - price_min offset = price_range * 0.02 # 2% offset # Original strategy signals (ABOVE the price) if len(original_data['buy_signals']) > 0: buy_prices_offset = original_data['buy_signals']['entry_price'] + offset ax1.scatter(original_data['buy_signals']['entry_time'], buy_prices_offset, color='darkgreen', marker='^', s=80, label=f"Original Buy ({len(original_data['buy_signals'])})", zorder=6, alpha=0.9, edgecolors='white', linewidth=1) if len(original_data['sell_signals']) > 0: # Separate by exit type for original strategy for exit_type in original_data['sell_signals']['type'].unique(): exit_data = original_data['sell_signals'][original_data['sell_signals']['type'] == exit_type] exit_prices_offset = exit_data['exit_price'] + offset if exit_type == 'STOP_LOSS': color, marker, size = 'red', 'X', 100 elif exit_type == 'TAKE_PROFIT': color, marker, size = 'gold', '*', 120 elif exit_type == 'EOD': color, marker, size = 'gray', 's', 70 else: color, marker, size = 'blue', 'v', 80 ax1.scatter(exit_data['exit_time'], exit_prices_offset, color=color, marker=marker, s=size, label=f"Original {exit_type} ({len(exit_data)})", zorder=6, alpha=0.9, edgecolors='white', linewidth=1) # Incremental strategy signals (BELOW the price) if len(incremental_data['buy_signals']) > 0: buy_prices_offset = incremental_data['buy_signals']['entry_price'] - offset ax1.scatter(incremental_data['buy_signals']['entry_time'], buy_prices_offset, color='lime', marker='^', s=80, label=f"Incremental Buy ({len(incremental_data['buy_signals'])})", zorder=5, alpha=0.9, edgecolors='black', linewidth=1) if len(incremental_data['sell_signals']) > 0: # Separate by exit type for incremental strategy for exit_type in incremental_data['sell_signals']['type'].unique(): exit_data = incremental_data['sell_signals'][incremental_data['sell_signals']['type'] == exit_type] exit_prices_offset = exit_data['exit_price'] - offset if exit_type == 'STOP_LOSS': color, marker, size = 'darkred', 'X', 100 elif exit_type == 'TAKE_PROFIT': color, marker, size = 'orange', '*', 120 elif exit_type == 'EOD': color, marker, size = 'darkgray', 's', 70 else: color, marker, size = 'purple', 'v', 80 ax1.scatter(exit_data['exit_time'], exit_prices_offset, color=color, marker=marker, s=size, label=f"Incremental {exit_type} ({len(exit_data)})", zorder=5, alpha=0.9, edgecolors='black', linewidth=1) # Add horizontal reference lines to show offset zones ax1.axhline(y=price_data['close'].mean() + offset, color='darkgreen', linestyle='--', alpha=0.3, linewidth=1) ax1.axhline(y=price_data['close'].mean() - offset, color='lime', linestyle='--', alpha=0.3, linewidth=1) # Add text annotations ax1.text(0.02, 0.98, 'Original Strategy (Above Price)', transform=ax1.transAxes, fontsize=12, fontweight='bold', color='darkgreen', bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.8)) ax1.text(0.02, 0.02, 'Incremental Strategy (Below Price)', transform=ax1.transAxes, fontsize=12, fontweight='bold', color='lime', bbox=dict(boxstyle="round,pad=0.3", facecolor="black", alpha=0.8)) ax1.set_title('Strategy Comparison - Trading Signals Overlay', fontsize=16, fontweight='bold') ax1.set_ylabel('Price (USD)', fontsize=12) ax1.legend(loc='upper right', fontsize=9, ncol=2) ax1.grid(True, alpha=0.3) # Plot 2: Performance Comparison and Statistics ax2.axis('off') # Create detailed comparison table stats_text = f""" STRATEGY COMPARISON SUMMARY - {price_data.index[0].strftime('%Y-%m-%d')} to {price_data.index[-1].strftime('%Y-%m-%d')} {'Metric':<25} {'Original':<15} {'Incremental':<15} {'Difference':<15} {'-'*75} {'Total Profit':<25} {original_perf['total_profit_pct']:>10.1f}% {incremental_perf['total_profit_pct']:>12.1f}% {incremental_perf['total_profit_pct'] - original_perf['total_profit_pct']:>12.1f}% {'Number of Trades':<25} {original_perf['num_trades']:>10} {incremental_perf['num_trades']:>12} {incremental_perf['num_trades'] - original_perf['num_trades']:>12} {'Win Rate':<25} {original_perf['win_rate']:>10.1f}% {incremental_perf['win_rate']:>12.1f}% {incremental_perf['win_rate'] - original_perf['win_rate']:>12.1f}% {'Average Trade Profit':<25} {original_perf['avg_profit_pct']:>10.2f}% {incremental_perf['avg_profit_pct']:>12.2f}% {incremental_perf['avg_profit_pct'] - original_perf['avg_profit_pct']:>12.2f}% {'Best Trade':<25} {original_perf['best_trade']:>10.1f}% {incremental_perf['best_trade']:>12.1f}% {incremental_perf['best_trade'] - original_perf['best_trade']:>12.1f}% {'Worst Trade':<25} {original_perf['worst_trade']:>10.1f}% {incremental_perf['worst_trade']:>12.1f}% {incremental_perf['worst_trade'] - original_perf['worst_trade']:>12.1f}% EXIT TYPE BREAKDOWN: Original Strategy: {original_perf['exit_types']} Incremental Strategy: {incremental_perf['exit_types']} SIGNAL POSITIONING: • Original signals are positioned ABOVE the price line (darker colors) • Incremental signals are positioned BELOW the price line (brighter colors) • Both strategies use the same 15-minute timeframe and 3% stop loss TOTAL DATA POINTS: {len(price_data):,} bars ({len(price_data)*15:,} minutes) """ ax2.text(0.05, 0.95, stats_text, transform=ax2.transAxes, fontsize=11, verticalalignment='top', fontfamily='monospace', bbox=dict(boxstyle="round,pad=0.5", facecolor="lightgray", alpha=0.9)) # Format x-axis for price plot ax1.xaxis.set_major_locator(mdates.MonthLocator()) ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m')) plt.setp(ax1.xaxis.get_majorticklabels(), rotation=45) # Adjust layout and save plt.tight_layout() # plt.savefig(output_file, dpi=300, bbox_inches='tight') # plt.close() # Show interactive plot for manual exploration plt.show() print(f"Comparison plot saved to: {output_file}") # Print summary to console print(f"\nšŸ“Š STRATEGY COMPARISON SUMMARY:") print(f"Original Strategy: {original_perf['total_profit_pct']:.1f}% profit, {original_perf['num_trades']} trades, {original_perf['win_rate']:.1f}% win rate") print(f"Incremental Strategy: {incremental_perf['total_profit_pct']:.1f}% profit, {incremental_perf['num_trades']} trades, {incremental_perf['win_rate']:.1f}% win rate") print(f"Difference: {incremental_perf['total_profit_pct'] - original_perf['total_profit_pct']:.1f}% profit, {incremental_perf['num_trades'] - original_perf['num_trades']} trades") # Signal positioning explanation print(f"\nšŸŽÆ SIGNAL POSITIONING:") print(f"• Original strategy signals are positioned ABOVE the price line") print(f"• Incremental strategy signals are positioned BELOW the price line") print(f"• This allows easy visual comparison of timing differences") def main(): """Main function to run the comparison.""" print("šŸš€ Starting Strategy Comparison Analysis") print("=" * 60) # File paths original_file = "results/trades_15min(15min)_ST3pct.csv" incremental_file = "results/trades_incremental_15min(15min)_ST3pct.csv" output_file = "results/strategy_comparison_analysis.png" # Load price data print("Loading price data...") storage = Storage() try: # Load data for the same period as the trades price_data = storage.load_data("btcusd_1-min_data.csv", "2025-01-01", "2025-05-01") print(f"Loaded {len(price_data)} minute-level data points") # Aggregate to 15-minute bars for cleaner visualization print("Aggregating to 15-minute bars...") price_data = aggregate_to_minutes(price_data, 15) print(f"Aggregated to {len(price_data)} bars") # Create comparison plot plot_strategy_comparison(original_file, incremental_file, price_data, output_file) print(f"\nāœ… Analysis completed successfully!") print(f"šŸ“ Check the results: {output_file}") except Exception as e: print(f"āŒ Error during analysis: {e}") import traceback traceback.print_exc() if __name__ == "__main__": main()