Cycles/scripts/plot_results.py

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()