#!/usr/bin/env python3 """ Compare both strategies using identical all-in/all-out logic. This will help identify where the performance difference comes from. """ import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.dates as mdates from datetime import datetime import os import sys # Add project root to path sys.path.insert(0, os.path.abspath('..')) def process_trades_with_same_logic(trades_file, strategy_name, initial_usd=10000): """Process trades using identical all-in/all-out logic for both strategies.""" print(f"\nšŸ” Processing {strategy_name}...") # Load trades data trades_df = pd.read_csv(trades_file) # Convert timestamps trades_df['entry_time'] = pd.to_datetime(trades_df['entry_time']) trades_df['exit_time'] = pd.to_datetime(trades_df['exit_time'], errors='coerce') # Separate buy and sell signals buy_signals = trades_df[trades_df['type'] == 'BUY'].copy() sell_signals = trades_df[trades_df['type'] != 'BUY'].copy() print(f" šŸ“Š {len(buy_signals)} buy signals, {len(sell_signals)} sell signals") # Debug: Show first few trades print(f" šŸ” First few trades:") for i, (_, trade) in enumerate(trades_df.head(6).iterrows()): print(f" {i+1}. {trade['entry_time']} - {trade['type']} at ${trade.get('entry_price', trade.get('exit_price', 'N/A'))}") # Apply identical all-in/all-out logic portfolio_history = [] current_usd = initial_usd current_btc = 0.0 in_position = False # Combine all trades and sort by time all_trades = [] # Add buy signals for _, buy in buy_signals.iterrows(): all_trades.append({ 'timestamp': buy['entry_time'], 'type': 'BUY', 'price': buy['entry_price'], 'trade_data': buy }) # Add sell signals for _, sell in sell_signals.iterrows(): all_trades.append({ 'timestamp': sell['exit_time'], 'type': 'SELL', 'price': sell['exit_price'], 'profit_pct': sell['profit_pct'], 'trade_data': sell }) # Sort by timestamp all_trades = sorted(all_trades, key=lambda x: x['timestamp']) print(f" ā° Processing {len(all_trades)} trade events...") # Process each trade event trade_count = 0 for i, trade in enumerate(all_trades): timestamp = trade['timestamp'] trade_type = trade['type'] price = trade['price'] if trade_type == 'BUY' and not in_position: # ALL-IN: Use all USD to buy BTC current_btc = current_usd / price current_usd = 0.0 in_position = True trade_count += 1 portfolio_history.append({ 'timestamp': timestamp, 'portfolio_value': current_btc * price, 'usd_balance': current_usd, 'btc_balance': current_btc, 'trade_type': 'BUY', 'price': price, 'in_position': in_position }) if trade_count <= 3: # Debug first few trades print(f" BUY {trade_count}: ${current_usd:.0f} → {current_btc:.6f} BTC at ${price:.0f}") elif trade_type == 'SELL' and in_position: # ALL-OUT: Sell all BTC for USD old_usd = current_usd current_usd = current_btc * price current_btc = 0.0 in_position = False portfolio_history.append({ 'timestamp': timestamp, 'portfolio_value': current_usd, 'usd_balance': current_usd, 'btc_balance': current_btc, 'trade_type': 'SELL', 'price': price, 'profit_pct': trade.get('profit_pct', 0) * 100, 'in_position': in_position }) if trade_count <= 3: # Debug first few trades print(f" SELL {trade_count}: {current_btc:.6f} BTC → ${current_usd:.0f} at ${price:.0f}") # Convert to DataFrame portfolio_df = pd.DataFrame(portfolio_history) if len(portfolio_df) > 0: portfolio_df = portfolio_df.sort_values('timestamp').reset_index(drop=True) final_value = portfolio_df['portfolio_value'].iloc[-1] else: final_value = initial_usd print(f" āš ļø Warning: No portfolio history generated!") # Calculate performance metrics total_return = (final_value - initial_usd) / initial_usd * 100 num_trades = len(sell_signals) if num_trades > 0: winning_trades = len(sell_signals[sell_signals['profit_pct'] > 0]) win_rate = winning_trades / num_trades * 100 avg_trade = sell_signals['profit_pct'].mean() * 100 best_trade = sell_signals['profit_pct'].max() * 100 worst_trade = sell_signals['profit_pct'].min() * 100 else: win_rate = avg_trade = best_trade = worst_trade = 0 performance = { 'strategy_name': strategy_name, 'initial_value': initial_usd, 'final_value': final_value, 'total_return': total_return, 'num_trades': num_trades, 'win_rate': win_rate, 'avg_trade': avg_trade, 'best_trade': best_trade, 'worst_trade': worst_trade } print(f" šŸ’° Final Value: ${final_value:,.0f} ({total_return:+.1f}%)") print(f" šŸ“ˆ Portfolio events: {len(portfolio_df)}") return buy_signals, sell_signals, portfolio_df, performance def create_side_by_side_comparison(data1, data2, save_path="same_logic_comparison.png"): """Create side-by-side comparison plot.""" buy1, sell1, portfolio1, perf1 = data1 buy2, sell2, portfolio2, perf2 = data2 # Create figure with subplots fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(20, 16)) # Plot 1: Original Strategy Signals ax1.scatter(buy1['entry_time'], buy1['entry_price'], color='green', marker='^', s=60, label=f"Buy ({len(buy1)})", zorder=5, alpha=0.8) profitable_sells1 = sell1[sell1['profit_pct'] > 0] losing_sells1 = sell1[sell1['profit_pct'] <= 0] if len(profitable_sells1) > 0: ax1.scatter(profitable_sells1['exit_time'], profitable_sells1['exit_price'], color='blue', marker='v', s=60, label=f"Profitable Sells ({len(profitable_sells1)})", zorder=5, alpha=0.8) if len(losing_sells1) > 0: ax1.scatter(losing_sells1['exit_time'], losing_sells1['exit_price'], color='red', marker='v', s=60, label=f"Losing Sells ({len(losing_sells1)})", zorder=5, alpha=0.8) ax1.set_title(f'{perf1["strategy_name"]} - Trading Signals', fontsize=14, fontweight='bold') ax1.set_ylabel('Price (USD)', fontsize=12) ax1.legend(loc='upper left', fontsize=9) ax1.grid(True, alpha=0.3) ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}')) # Plot 2: Incremental Strategy Signals ax2.scatter(buy2['entry_time'], buy2['entry_price'], color='darkgreen', marker='^', s=60, label=f"Buy ({len(buy2)})", zorder=5, alpha=0.8) profitable_sells2 = sell2[sell2['profit_pct'] > 0] losing_sells2 = sell2[sell2['profit_pct'] <= 0] if len(profitable_sells2) > 0: ax2.scatter(profitable_sells2['exit_time'], profitable_sells2['exit_price'], color='darkblue', marker='v', s=60, label=f"Profitable Sells ({len(profitable_sells2)})", zorder=5, alpha=0.8) if len(losing_sells2) > 0: ax2.scatter(losing_sells2['exit_time'], losing_sells2['exit_price'], color='darkred', marker='v', s=60, label=f"Losing Sells ({len(losing_sells2)})", zorder=5, alpha=0.8) ax2.set_title(f'{perf2["strategy_name"]} - Trading Signals', fontsize=14, fontweight='bold') ax2.set_ylabel('Price (USD)', fontsize=12) ax2.legend(loc='upper left', fontsize=9) ax2.grid(True, alpha=0.3) ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}')) # Plot 3: Portfolio Value Comparison if len(portfolio1) > 0: ax3.plot(portfolio1['timestamp'], portfolio1['portfolio_value'], color='blue', linewidth=2, label=f'{perf1["strategy_name"]}', alpha=0.8) if len(portfolio2) > 0: ax3.plot(portfolio2['timestamp'], portfolio2['portfolio_value'], color='red', linewidth=2, label=f'{perf2["strategy_name"]}', alpha=0.8) ax3.axhline(y=10000, color='gray', linestyle='--', alpha=0.7, label='Initial Value ($10,000)') ax3.set_title('Portfolio Value Comparison (Same Logic)', fontsize=14, fontweight='bold') ax3.set_ylabel('Portfolio Value (USD)', fontsize=12) ax3.set_xlabel('Date', fontsize=12) ax3.legend(loc='upper left', fontsize=10) ax3.grid(True, alpha=0.3) ax3.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}')) # Plot 4: Performance Comparison Table ax4.axis('off') # Create detailed comparison table comparison_text = f""" IDENTICAL LOGIC COMPARISON {'='*50} {'Metric':<25} {perf1['strategy_name']:<15} {perf2['strategy_name']:<15} {'Difference':<15} {'-'*75} {'Initial Value':<25} ${perf1['initial_value']:>10,.0f} ${perf2['initial_value']:>12,.0f} ${perf2['initial_value'] - perf1['initial_value']:>12,.0f} {'Final Value':<25} ${perf1['final_value']:>10,.0f} ${perf2['final_value']:>12,.0f} ${perf2['final_value'] - perf1['final_value']:>12,.0f} {'Total Return':<25} {perf1['total_return']:>10.1f}% {perf2['total_return']:>12.1f}% {perf2['total_return'] - perf1['total_return']:>12.1f}% {'Number of Trades':<25} {perf1['num_trades']:>10} {perf2['num_trades']:>12} {perf2['num_trades'] - perf1['num_trades']:>12} {'Win Rate':<25} {perf1['win_rate']:>10.1f}% {perf2['win_rate']:>12.1f}% {perf2['win_rate'] - perf1['win_rate']:>12.1f}% {'Average Trade':<25} {perf1['avg_trade']:>10.2f}% {perf2['avg_trade']:>12.2f}% {perf2['avg_trade'] - perf1['avg_trade']:>12.2f}% {'Best Trade':<25} {perf1['best_trade']:>10.1f}% {perf2['best_trade']:>12.1f}% {perf2['best_trade'] - perf1['best_trade']:>12.1f}% {'Worst Trade':<25} {perf1['worst_trade']:>10.1f}% {perf2['worst_trade']:>12.1f}% {perf2['worst_trade'] - perf1['worst_trade']:>12.1f}% LOGIC APPLIED: • ALL-IN: Use 100% of USD to buy BTC on entry signals • ALL-OUT: Sell 100% of BTC for USD on exit signals • NO FEES: Pure price-based calculations • SAME COMPOUNDING: Each trade uses full available balance TIME PERIODS: {perf1['strategy_name']}: {buy1['entry_time'].min().strftime('%Y-%m-%d')} to {sell1['exit_time'].max().strftime('%Y-%m-%d')} {perf2['strategy_name']}: {buy2['entry_time'].min().strftime('%Y-%m-%d')} to {sell2['exit_time'].max().strftime('%Y-%m-%d')} ANALYSIS: If results differ significantly, it indicates: 1. Different entry/exit timing 2. Different price execution points 3. Different trade frequency or duration 4. Data inconsistencies between files """ ax4.text(0.05, 0.95, comparison_text, transform=ax4.transAxes, fontsize=10, verticalalignment='top', fontfamily='monospace', bbox=dict(boxstyle="round,pad=0.5", facecolor="lightgray", alpha=0.9)) # Format x-axis for signal plots for ax in [ax1, ax2, ax3]: ax.xaxis.set_major_locator(mdates.MonthLocator()) ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m')) plt.setp(ax.xaxis.get_majorticklabels(), rotation=45) # Adjust layout and save plt.tight_layout() plt.savefig(save_path, dpi=300, bbox_inches='tight') plt.show() print(f"Comparison plot saved to: {save_path}") def main(): """Main function to run the identical logic comparison.""" print("šŸš€ Starting Identical Logic Comparison") print("=" * 60) # File paths original_file = "../results/trades_15min(15min)_ST3pct.csv" incremental_file = "../results/trades_incremental_15min(15min)_ST3pct.csv" output_file = "../results/same_logic_comparison.png" # Check if files exist if not os.path.exists(original_file): print(f"āŒ Error: Original trades file not found: {original_file}") return if not os.path.exists(incremental_file): print(f"āŒ Error: Incremental trades file not found: {incremental_file}") return try: # Process both strategies with identical logic original_data = process_trades_with_same_logic(original_file, "Original Strategy") incremental_data = process_trades_with_same_logic(incremental_file, "Incremental Strategy") # Create comparison plot create_side_by_side_comparison(original_data, incremental_data, output_file) # Print summary comparison _, _, _, perf1 = original_data _, _, _, perf2 = incremental_data print(f"\nšŸ“Š IDENTICAL LOGIC COMPARISON SUMMARY:") print(f"Original Strategy: ${perf1['final_value']:,.0f} ({perf1['total_return']:+.1f}%)") print(f"Incremental Strategy: ${perf2['final_value']:,.0f} ({perf2['total_return']:+.1f}%)") print(f"Difference: ${perf2['final_value'] - perf1['final_value']:,.0f} ({perf2['total_return'] - perf1['total_return']:+.1f}%)") if abs(perf1['total_return'] - perf2['total_return']) < 1.0: print("āœ… Results are very similar - strategies are equivalent!") else: print("āš ļø Significant difference detected - investigating causes...") print(f" • Trade count difference: {perf2['num_trades'] - perf1['num_trades']}") print(f" • Win rate difference: {perf2['win_rate'] - perf1['win_rate']:+.1f}%") print(f" • Avg trade difference: {perf2['avg_trade'] - perf1['avg_trade']:+.2f}%") print(f"\nāœ… Analysis completed successfully!") except Exception as e: print(f"āŒ Error during analysis: {e}") import traceback traceback.print_exc() if __name__ == "__main__": main()