454 lines
18 KiB
Python
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) |