Cycles/test/compare_strategies_same_data.py

454 lines
18 KiB
Python

#!/usr/bin/env python3
"""
Compare Original vs Incremental Strategies on Same Data
======================================================
This script runs both strategies on the exact same data period from btcusd_1-min_data.csv
to ensure a fair comparison.
"""
import sys
import os
import json
import pandas as pd
import numpy as np
from datetime import datetime
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
# Add the parent directory to the path to import cycles modules
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from cycles.utils.storage import Storage
from cycles.IncStrategies.inc_backtester import IncBacktester, BacktestConfig
from cycles.IncStrategies.metatrend_strategy import IncMetaTrendStrategy
from cycles.utils.data_utils import aggregate_to_minutes
def run_original_strategy_via_main(start_date: str, end_date: str, initial_usd: float, stop_loss_pct: float):
"""Run the original strategy using the main.py system."""
print(f"\n🔄 Running Original Strategy via main.py...")
# Create a temporary config file for the original strategy
config = {
"start_date": start_date,
"stop_date": end_date,
"initial_usd": initial_usd,
"timeframes": ["15min"],
"strategies": [
{
"name": "default",
"weight": 1.0,
"params": {
"stop_loss_pct": stop_loss_pct,
"timeframe": "15min"
}
}
],
"combination_rules": {
"min_strategies": 1,
"min_confidence": 0.5
}
}
# Save temporary config
temp_config_file = "temp_config.json"
with open(temp_config_file, 'w') as f:
json.dump(config, f, indent=2)
try:
# Import and run the main processing function
from main import process_timeframe_data
from cycles.utils.storage import Storage
storage = Storage()
# Load data using absolute path
data_file = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "btcusd_1-min_data.csv")
print(f"Loading data from: {data_file}")
if not os.path.exists(data_file):
print(f"❌ Data file not found: {data_file}")
return None
data_1min = storage.load_data(data_file, start_date, end_date)
print(f"Loaded {len(data_1min)} minute-level data points")
if len(data_1min) == 0:
print(f"❌ No data loaded for period {start_date} to {end_date}")
return None
# Run the original strategy
results_rows, trade_rows = process_timeframe_data(data_1min, "15min", config, debug=False)
if not results_rows:
print("❌ No results from original strategy")
return None
result = results_rows[0]
trades = [trade for trade in trade_rows if trade['timeframe'] == result['timeframe']]
return {
'strategy_name': 'Original MetaTrend',
'n_trades': result['n_trades'],
'win_rate': result['win_rate'],
'avg_trade': result['avg_trade'],
'max_drawdown': result['max_drawdown'],
'initial_usd': result['initial_usd'],
'final_usd': result['final_usd'],
'profit_ratio': (result['final_usd'] - result['initial_usd']) / result['initial_usd'],
'total_fees_usd': result['total_fees_usd'],
'trades': trades,
'data_points': len(data_1min)
}
finally:
# Clean up temporary config file
if os.path.exists(temp_config_file):
os.remove(temp_config_file)
def run_incremental_strategy(start_date: str, end_date: str, initial_usd: float, stop_loss_pct: float):
"""Run the incremental strategy using the new backtester."""
print(f"\n🔄 Running Incremental Strategy...")
storage = Storage()
# Use absolute path for data file
data_file = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "btcusd_1-min_data.csv")
# Create backtester configuration
config = BacktestConfig(
data_file=data_file,
start_date=start_date,
end_date=end_date,
initial_usd=initial_usd,
stop_loss_pct=stop_loss_pct,
take_profit_pct=0.0
)
# Create strategy
strategy = IncMetaTrendStrategy(
name="metatrend",
weight=1.0,
params={
"timeframe": "15min",
"enable_logging": False
}
)
# Run backtest
backtester = IncBacktester(config, storage)
result = backtester.run_single_strategy(strategy)
result['strategy_name'] = 'Incremental MetaTrend'
return result
def save_comparison_results(original_result: dict, incremental_result: dict, output_dir: str):
"""Save comparison results to files."""
os.makedirs(output_dir, exist_ok=True)
# Save original trades
original_trades_file = os.path.join(output_dir, "original_trades.csv")
if original_result and original_result['trades']:
trades_df = pd.DataFrame(original_result['trades'])
trades_df.to_csv(original_trades_file, index=False)
print(f"Saved original trades to: {original_trades_file}")
# Save incremental trades
incremental_trades_file = os.path.join(output_dir, "incremental_trades.csv")
if incremental_result['trades']:
# Convert to same format as original
trades_data = []
for trade in incremental_result['trades']:
trades_data.append({
'entry_time': trade.get('entry_time'),
'exit_time': trade.get('exit_time'),
'entry_price': trade.get('entry_price'),
'exit_price': trade.get('exit_price'),
'profit_pct': trade.get('profit_pct'),
'type': trade.get('type'),
'fee_usd': trade.get('fee_usd')
})
trades_df = pd.DataFrame(trades_data)
trades_df.to_csv(incremental_trades_file, index=False)
print(f"Saved incremental trades to: {incremental_trades_file}")
# Save comparison summary
comparison_file = os.path.join(output_dir, "strategy_comparison.json")
# Convert numpy types to Python types for JSON serialization
def convert_numpy_types(obj):
if hasattr(obj, 'item'): # numpy scalar
return obj.item()
elif isinstance(obj, dict):
return {k: convert_numpy_types(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [convert_numpy_types(v) for v in obj]
else:
return obj
comparison_data = {
'test_date': datetime.now().isoformat(),
'data_file': 'btcusd_1-min_data.csv',
'original_strategy': {
'name': original_result['strategy_name'] if original_result else 'Failed',
'n_trades': int(original_result['n_trades']) if original_result else 0,
'win_rate': float(original_result['win_rate']) if original_result else 0,
'avg_trade': float(original_result['avg_trade']) if original_result else 0,
'max_drawdown': float(original_result['max_drawdown']) if original_result else 0,
'initial_usd': float(original_result['initial_usd']) if original_result else 0,
'final_usd': float(original_result['final_usd']) if original_result else 0,
'profit_ratio': float(original_result['profit_ratio']) if original_result else 0,
'total_fees_usd': float(original_result['total_fees_usd']) if original_result else 0,
'data_points': int(original_result['data_points']) if original_result else 0
},
'incremental_strategy': {
'name': incremental_result['strategy_name'],
'n_trades': int(incremental_result['n_trades']),
'win_rate': float(incremental_result['win_rate']),
'avg_trade': float(incremental_result['avg_trade']),
'max_drawdown': float(incremental_result['max_drawdown']),
'initial_usd': float(incremental_result['initial_usd']),
'final_usd': float(incremental_result['final_usd']),
'profit_ratio': float(incremental_result['profit_ratio']),
'total_fees_usd': float(incremental_result['total_fees_usd']),
'data_points': int(incremental_result.get('data_points_processed', 0))
}
}
if original_result:
comparison_data['comparison'] = {
'profit_difference': float(incremental_result['profit_ratio'] - original_result['profit_ratio']),
'trade_count_difference': int(incremental_result['n_trades'] - original_result['n_trades']),
'win_rate_difference': float(incremental_result['win_rate'] - original_result['win_rate'])
}
with open(comparison_file, 'w') as f:
json.dump(comparison_data, f, indent=2)
print(f"Saved comparison summary to: {comparison_file}")
return comparison_data
def create_comparison_plot(original_result: dict, incremental_result: dict,
start_date: str, end_date: str, output_dir: str):
"""Create a comparison plot showing both strategies."""
print(f"\n📊 Creating comparison plot...")
# Load price data for plotting
storage = Storage()
data_file = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "btcusd_1-min_data.csv")
data_1min = storage.load_data(data_file, start_date, end_date)
aggregated_data = aggregate_to_minutes(data_1min, 15)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(15, 12))
# Plot 1: Price with trade signals
ax1.plot(aggregated_data.index, aggregated_data['close'], 'k-', alpha=0.7, linewidth=1, label='BTC Price')
# Plot original strategy trades
if original_result and original_result['trades']:
original_trades = original_result['trades']
for trade in original_trades:
entry_time = pd.to_datetime(trade.get('entry_time'))
exit_time = pd.to_datetime(trade.get('exit_time'))
entry_price = trade.get('entry_price')
exit_price = trade.get('exit_price')
if entry_time and entry_price:
# Buy signal (above price line)
ax1.scatter(entry_time, entry_price * 1.02, color='green', marker='^',
s=50, alpha=0.8, label='Original Buy' if trade == original_trades[0] else "")
if exit_time and exit_price:
# Sell signal (above price line)
color = 'red' if trade.get('profit_pct', 0) < 0 else 'blue'
ax1.scatter(exit_time, exit_price * 1.02, color=color, marker='v',
s=50, alpha=0.8, label='Original Sell' if trade == original_trades[0] else "")
# Plot incremental strategy trades
incremental_trades = incremental_result['trades']
if incremental_trades:
for trade in incremental_trades:
entry_time = pd.to_datetime(trade.get('entry_time'))
exit_time = pd.to_datetime(trade.get('exit_time'))
entry_price = trade.get('entry_price')
exit_price = trade.get('exit_price')
if entry_time and entry_price:
# Buy signal (below price line)
ax1.scatter(entry_time, entry_price * 0.98, color='lightgreen', marker='^',
s=50, alpha=0.8, label='Incremental Buy' if trade == incremental_trades[0] else "")
if exit_time and exit_price:
# Sell signal (below price line)
exit_type = trade.get('type', 'STRATEGY_EXIT')
if exit_type == 'STOP_LOSS':
color = 'orange'
elif exit_type == 'TAKE_PROFIT':
color = 'purple'
else:
color = 'lightblue'
ax1.scatter(exit_time, exit_price * 0.98, color=color, marker='v',
s=50, alpha=0.8, label=f'Incremental {exit_type}' if trade == incremental_trades[0] else "")
ax1.set_title(f'Strategy Comparison: {start_date} to {end_date}', fontsize=14, fontweight='bold')
ax1.set_ylabel('Price (USD)', fontsize=12)
ax1.legend(loc='upper left')
ax1.grid(True, alpha=0.3)
# Format x-axis
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax1.xaxis.set_major_locator(mdates.MonthLocator())
plt.setp(ax1.xaxis.get_majorticklabels(), rotation=45)
# Plot 2: Performance comparison
strategies = ['Original', 'Incremental']
profits = [
original_result['profit_ratio'] * 100 if original_result else 0,
incremental_result['profit_ratio'] * 100
]
colors = ['blue', 'green']
bars = ax2.bar(strategies, profits, color=colors, alpha=0.7)
ax2.set_title('Profit Comparison', fontsize=14, fontweight='bold')
ax2.set_ylabel('Profit (%)', fontsize=12)
ax2.grid(True, alpha=0.3, axis='y')
# Add value labels on bars
for bar, profit in zip(bars, profits):
height = bar.get_height()
ax2.text(bar.get_x() + bar.get_width()/2., height + (0.5 if height >= 0 else -1.5),
f'{profit:.2f}%', ha='center', va='bottom' if height >= 0 else 'top', fontweight='bold')
plt.tight_layout()
# Save plot
plot_file = os.path.join(output_dir, "strategy_comparison.png")
plt.savefig(plot_file, dpi=300, bbox_inches='tight')
plt.close()
print(f"Saved comparison plot to: {plot_file}")
def print_comparison_summary(original_result: dict, incremental_result: dict):
"""Print a detailed comparison summary."""
print("\n" + "="*80)
print("STRATEGY COMPARISON SUMMARY")
print("="*80)
if not original_result:
print("❌ Original strategy failed to run")
print(f"✅ Incremental strategy: {incremental_result['profit_ratio']*100:.2f}% profit")
return
print(f"\n📊 PERFORMANCE METRICS:")
print(f"{'Metric':<20} {'Original':<15} {'Incremental':<15} {'Difference':<15}")
print("-" * 65)
# Profit comparison
orig_profit = original_result['profit_ratio'] * 100
inc_profit = incremental_result['profit_ratio'] * 100
profit_diff = inc_profit - orig_profit
print(f"{'Profit %':<20} {orig_profit:<15.2f} {inc_profit:<15.2f} {profit_diff:<15.2f}")
# Final USD comparison
orig_final = original_result['final_usd']
inc_final = incremental_result['final_usd']
usd_diff = inc_final - orig_final
print(f"{'Final USD':<20} ${orig_final:<14.2f} ${inc_final:<14.2f} ${usd_diff:<14.2f}")
# Trade count comparison
orig_trades = original_result['n_trades']
inc_trades = incremental_result['n_trades']
trade_diff = inc_trades - orig_trades
print(f"{'Total Trades':<20} {orig_trades:<15} {inc_trades:<15} {trade_diff:<15}")
# Win rate comparison
orig_wr = original_result['win_rate'] * 100
inc_wr = incremental_result['win_rate'] * 100
wr_diff = inc_wr - orig_wr
print(f"{'Win Rate %':<20} {orig_wr:<15.2f} {inc_wr:<15.2f} {wr_diff:<15.2f}")
# Average trade comparison
orig_avg = original_result['avg_trade'] * 100
inc_avg = incremental_result['avg_trade'] * 100
avg_diff = inc_avg - orig_avg
print(f"{'Avg Trade %':<20} {orig_avg:<15.2f} {inc_avg:<15.2f} {avg_diff:<15.2f}")
# Max drawdown comparison
orig_dd = original_result['max_drawdown'] * 100
inc_dd = incremental_result['max_drawdown'] * 100
dd_diff = inc_dd - orig_dd
print(f"{'Max Drawdown %':<20} {orig_dd:<15.2f} {inc_dd:<15.2f} {dd_diff:<15.2f}")
# Fees comparison
orig_fees = original_result['total_fees_usd']
inc_fees = incremental_result['total_fees_usd']
fees_diff = inc_fees - orig_fees
print(f"{'Total Fees USD':<20} ${orig_fees:<14.2f} ${inc_fees:<14.2f} ${fees_diff:<14.2f}")
print("\n" + "="*80)
# Determine winner
if profit_diff > 0:
print(f"🏆 WINNER: Incremental Strategy (+{profit_diff:.2f}% better)")
elif profit_diff < 0:
print(f"🏆 WINNER: Original Strategy (+{abs(profit_diff):.2f}% better)")
else:
print(f"🤝 TIE: Both strategies performed equally")
print("="*80)
def main():
"""Main comparison function."""
print("🚀 Comparing Original vs Incremental Strategies on Same Data")
print("=" * 80)
# Configuration
start_date = "2025-01-01"
end_date = "2025-05-01"
initial_usd = 10000
stop_loss_pct = 0.03 # 3% stop loss
print(f"📅 Test Period: {start_date} to {end_date}")
print(f"💰 Initial Capital: ${initial_usd:,}")
print(f"🛑 Stop Loss: {stop_loss_pct*100:.1f}%")
print(f"📊 Data Source: btcusd_1-min_data.csv")
try:
# Run both strategies
original_result = run_original_strategy_via_main(start_date, end_date, initial_usd, stop_loss_pct)
incremental_result = run_incremental_strategy(start_date, end_date, initial_usd, stop_loss_pct)
# Print comparison summary
print_comparison_summary(original_result, incremental_result)
# Save results
output_dir = "results/strategy_comparison"
comparison_data = save_comparison_results(original_result, incremental_result, output_dir)
# Create comparison plot
create_comparison_plot(original_result, incremental_result, start_date, end_date, output_dir)
print(f"\n📁 Results saved to: {output_dir}/")
print(f" - strategy_comparison.json")
print(f" - strategy_comparison.png")
print(f" - original_trades.csv")
print(f" - incremental_trades.csv")
return True
except Exception as e:
print(f"\n❌ Error during comparison: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)