Cycles/scripts/compare_same_logic.py

343 lines
14 KiB
Python
Raw Normal View History

#!/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()