Cycles/test/align_strategy_timing.py

321 lines
12 KiB
Python

#!/usr/bin/env python3
"""
Align Strategy Timing for Fair Comparison
=========================================
This script aligns the timing between original and incremental strategies
by removing early trades from the original strategy that occur before
the incremental strategy starts trading (warmup period).
"""
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
import json
def load_trade_files():
"""Load both strategy trade files."""
print("📊 LOADING TRADE FILES")
print("=" * 60)
# Load original strategy trades
original_file = "../results/trades_15min(15min)_ST3pct.csv"
incremental_file = "../results/trades_incremental_15min(15min)_ST3pct.csv"
print(f"Loading original trades: {original_file}")
original_df = pd.read_csv(original_file)
original_df['entry_time'] = pd.to_datetime(original_df['entry_time'])
original_df['exit_time'] = pd.to_datetime(original_df['exit_time'])
print(f"Loading incremental trades: {incremental_file}")
incremental_df = pd.read_csv(incremental_file)
incremental_df['entry_time'] = pd.to_datetime(incremental_df['entry_time'])
incremental_df['exit_time'] = pd.to_datetime(incremental_df['exit_time'])
print(f"Original trades: {len(original_df)} total")
print(f"Incremental trades: {len(incremental_df)} total")
return original_df, incremental_df
def find_alignment_point(original_df, incremental_df):
"""Find the point where both strategies should start for fair comparison."""
print(f"\n🕐 FINDING ALIGNMENT POINT")
print("=" * 60)
# Find when incremental strategy starts trading
incremental_start = incremental_df[incremental_df['type'] == 'BUY']['entry_time'].min()
print(f"Incremental strategy first trade: {incremental_start}")
# Find original strategy trades before this point
original_buys = original_df[original_df['type'] == 'BUY']
early_trades = original_buys[original_buys['entry_time'] < incremental_start]
print(f"Original trades before incremental start: {len(early_trades)}")
if len(early_trades) > 0:
print(f"First original trade: {original_buys['entry_time'].min()}")
print(f"Last early trade: {early_trades['entry_time'].max()}")
print(f"Time gap: {incremental_start - original_buys['entry_time'].min()}")
# Show the early trades that will be excluded
print(f"\n📋 EARLY TRADES TO EXCLUDE:")
for i, trade in early_trades.iterrows():
print(f" {trade['entry_time']} - ${trade['entry_price']:.0f}")
return incremental_start
def align_strategies(original_df, incremental_df, alignment_time):
"""Align both strategies to start at the same time."""
print(f"\n⚖️ ALIGNING STRATEGIES")
print("=" * 60)
# Filter original strategy to start from alignment time
aligned_original = original_df[original_df['entry_time'] >= alignment_time].copy()
# Incremental strategy remains the same (already starts at alignment time)
aligned_incremental = incremental_df.copy()
print(f"Original trades after alignment: {len(aligned_original)}")
print(f"Incremental trades: {len(aligned_incremental)}")
# Reset indices for clean comparison
aligned_original = aligned_original.reset_index(drop=True)
aligned_incremental = aligned_incremental.reset_index(drop=True)
return aligned_original, aligned_incremental
def calculate_aligned_performance(aligned_original, aligned_incremental):
"""Calculate performance metrics for aligned strategies."""
print(f"\n💰 CALCULATING ALIGNED PERFORMANCE")
print("=" * 60)
def calculate_strategy_performance(df, strategy_name):
"""Calculate performance for a single strategy."""
# Filter to complete trades (buy + sell pairs)
buy_signals = df[df['type'] == 'BUY'].copy()
sell_signals = df[df['type'].str.contains('EXIT|EOD', na=False)].copy()
print(f"\n{strategy_name}:")
print(f" Buy signals: {len(buy_signals)}")
print(f" Sell signals: {len(sell_signals)}")
if len(buy_signals) == 0:
return {
'final_value': 10000,
'total_return': 0.0,
'trade_count': 0,
'win_rate': 0.0,
'avg_trade': 0.0
}
# Calculate performance using same logic as comparison script
initial_usd = 10000
current_usd = initial_usd
for i, buy_trade in buy_signals.iterrows():
# Find corresponding sell trade
sell_trades = sell_signals[sell_signals['entry_time'] == buy_trade['entry_time']]
if len(sell_trades) == 0:
continue
sell_trade = sell_trades.iloc[0]
# Calculate trade performance
entry_price = buy_trade['entry_price']
exit_price = sell_trade['exit_price']
profit_pct = sell_trade['profit_pct']
# Apply profit/loss
current_usd *= (1 + profit_pct)
total_return = ((current_usd - initial_usd) / initial_usd) * 100
# Calculate trade statistics
profits = sell_signals['profit_pct'].values
winning_trades = len(profits[profits > 0])
win_rate = (winning_trades / len(profits)) * 100 if len(profits) > 0 else 0
avg_trade = np.mean(profits) * 100 if len(profits) > 0 else 0
print(f" Final value: ${current_usd:,.0f}")
print(f" Total return: {total_return:.1f}%")
print(f" Win rate: {win_rate:.1f}%")
print(f" Average trade: {avg_trade:.2f}%")
return {
'final_value': current_usd,
'total_return': total_return,
'trade_count': len(profits),
'win_rate': win_rate,
'avg_trade': avg_trade,
'profits': profits.tolist()
}
# Calculate performance for both strategies
original_perf = calculate_strategy_performance(aligned_original, "Aligned Original")
incremental_perf = calculate_strategy_performance(aligned_incremental, "Incremental")
# Compare performance
print(f"\n📊 PERFORMANCE COMPARISON:")
print("=" * 60)
print(f"Original (aligned): ${original_perf['final_value']:,.0f} ({original_perf['total_return']:+.1f}%)")
print(f"Incremental: ${incremental_perf['final_value']:,.0f} ({incremental_perf['total_return']:+.1f}%)")
difference = incremental_perf['total_return'] - original_perf['total_return']
print(f"Difference: {difference:+.1f}%")
if abs(difference) < 5:
print("✅ Performance is now closely aligned!")
elif difference > 0:
print("📈 Incremental strategy outperforms after alignment")
else:
print("📉 Original strategy still outperforms")
return original_perf, incremental_perf
def save_aligned_results(aligned_original, aligned_incremental, original_perf, incremental_perf):
"""Save aligned results for further analysis."""
print(f"\n💾 SAVING ALIGNED RESULTS")
print("=" * 60)
# Save aligned trade files
aligned_original.to_csv("../results/trades_original_aligned.csv", index=False)
aligned_incremental.to_csv("../results/trades_incremental_aligned.csv", index=False)
print("Saved aligned trade files:")
print(" - ../results/trades_original_aligned.csv")
print(" - ../results/trades_incremental_aligned.csv")
# Save performance comparison
comparison_results = {
'alignment_analysis': {
'original_performance': original_perf,
'incremental_performance': incremental_perf,
'performance_difference': incremental_perf['total_return'] - original_perf['total_return'],
'trade_count_difference': incremental_perf['trade_count'] - original_perf['trade_count'],
'win_rate_difference': incremental_perf['win_rate'] - original_perf['win_rate']
},
'timestamp': datetime.now().isoformat()
}
with open("../results/aligned_performance_comparison.json", "w") as f:
json.dump(comparison_results, f, indent=2)
print(" - ../results/aligned_performance_comparison.json")
def create_aligned_visualization(aligned_original, aligned_incremental):
"""Create visualization of aligned strategies."""
print(f"\n📊 CREATING ALIGNED VISUALIZATION")
print("=" * 60)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(15, 10))
# Get buy signals for plotting
orig_buys = aligned_original[aligned_original['type'] == 'BUY']
inc_buys = aligned_incremental[aligned_incremental['type'] == 'BUY']
# Plot 1: Trade timing comparison
ax1.scatter(orig_buys['entry_time'], orig_buys['entry_price'],
alpha=0.7, label='Original (Aligned)', color='blue', s=40)
ax1.scatter(inc_buys['entry_time'], inc_buys['entry_price'],
alpha=0.7, label='Incremental', color='red', s=40)
ax1.set_title('Aligned Strategy Trade Timing Comparison')
ax1.set_xlabel('Date')
ax1.set_ylabel('Entry Price ($)')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Plot 2: Cumulative performance
def calculate_cumulative_returns(df):
"""Calculate cumulative returns over time."""
buy_signals = df[df['type'] == 'BUY'].copy()
sell_signals = df[df['type'].str.contains('EXIT|EOD', na=False)].copy()
cumulative_returns = []
current_value = 10000
dates = []
for i, buy_trade in buy_signals.iterrows():
sell_trades = sell_signals[sell_signals['entry_time'] == buy_trade['entry_time']]
if len(sell_trades) == 0:
continue
sell_trade = sell_trades.iloc[0]
current_value *= (1 + sell_trade['profit_pct'])
cumulative_returns.append(current_value)
dates.append(sell_trade['exit_time'])
return dates, cumulative_returns
orig_dates, orig_returns = calculate_cumulative_returns(aligned_original)
inc_dates, inc_returns = calculate_cumulative_returns(aligned_incremental)
if orig_dates:
ax2.plot(orig_dates, orig_returns, label='Original (Aligned)', color='blue', linewidth=2)
if inc_dates:
ax2.plot(inc_dates, inc_returns, label='Incremental', color='red', linewidth=2)
ax2.set_title('Aligned Strategy Cumulative Performance')
ax2.set_xlabel('Date')
ax2.set_ylabel('Portfolio Value ($)')
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('../results/aligned_strategy_comparison.png', dpi=300, bbox_inches='tight')
print("Visualization saved: ../results/aligned_strategy_comparison.png")
def main():
"""Main alignment function."""
print("🚀 ALIGNING STRATEGY TIMING FOR FAIR COMPARISON")
print("=" * 80)
try:
# Load trade files
original_df, incremental_df = load_trade_files()
# Find alignment point
alignment_time = find_alignment_point(original_df, incremental_df)
# Align strategies
aligned_original, aligned_incremental = align_strategies(
original_df, incremental_df, alignment_time
)
# Calculate aligned performance
original_perf, incremental_perf = calculate_aligned_performance(
aligned_original, aligned_incremental
)
# Save results
save_aligned_results(aligned_original, aligned_incremental,
original_perf, incremental_perf)
# Create visualization
create_aligned_visualization(aligned_original, aligned_incremental)
print(f"\n✅ ALIGNMENT COMPLETED SUCCESSFULLY!")
print("=" * 80)
print("The strategies are now aligned for fair comparison.")
print("Check the results/ directory for aligned trade files and analysis.")
return True
except Exception as e:
print(f"\n❌ Error during alignment: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
success = main()
exit(0 if success else 1)