277 lines
12 KiB
Python
277 lines
12 KiB
Python
#!/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()
|