ok, kind of incremental trading and backtester, but result not alligning

This commit is contained in:
Ajasra
2025-05-27 16:51:43 +08:00
parent ed6d668a8a
commit 8055f46328
29 changed files with 7825 additions and 13 deletions

View File

@@ -0,0 +1,343 @@
#!/usr/bin/env python3
"""
Compare both strategies using identical all-in/all-out logic.
This will help identify where the performance difference comes from.
"""
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime
import os
import sys
# Add project root to path
sys.path.insert(0, os.path.abspath('..'))
def process_trades_with_same_logic(trades_file, strategy_name, initial_usd=10000):
"""Process trades using identical all-in/all-out logic for both strategies."""
print(f"\n🔍 Processing {strategy_name}...")
# Load trades data
trades_df = pd.read_csv(trades_file)
# Convert timestamps
trades_df['entry_time'] = pd.to_datetime(trades_df['entry_time'])
trades_df['exit_time'] = pd.to_datetime(trades_df['exit_time'], errors='coerce')
# Separate buy and sell signals
buy_signals = trades_df[trades_df['type'] == 'BUY'].copy()
sell_signals = trades_df[trades_df['type'] != 'BUY'].copy()
print(f" 📊 {len(buy_signals)} buy signals, {len(sell_signals)} sell signals")
# Debug: Show first few trades
print(f" 🔍 First few trades:")
for i, (_, trade) in enumerate(trades_df.head(6).iterrows()):
print(f" {i+1}. {trade['entry_time']} - {trade['type']} at ${trade.get('entry_price', trade.get('exit_price', 'N/A'))}")
# Apply identical all-in/all-out logic
portfolio_history = []
current_usd = initial_usd
current_btc = 0.0
in_position = False
# Combine all trades and sort by time
all_trades = []
# Add buy signals
for _, buy in buy_signals.iterrows():
all_trades.append({
'timestamp': buy['entry_time'],
'type': 'BUY',
'price': buy['entry_price'],
'trade_data': buy
})
# Add sell signals
for _, sell in sell_signals.iterrows():
all_trades.append({
'timestamp': sell['exit_time'],
'type': 'SELL',
'price': sell['exit_price'],
'profit_pct': sell['profit_pct'],
'trade_data': sell
})
# Sort by timestamp
all_trades = sorted(all_trades, key=lambda x: x['timestamp'])
print(f" ⏰ Processing {len(all_trades)} trade events...")
# Process each trade event
trade_count = 0
for i, trade in enumerate(all_trades):
timestamp = trade['timestamp']
trade_type = trade['type']
price = trade['price']
if trade_type == 'BUY' and not in_position:
# ALL-IN: Use all USD to buy BTC
current_btc = current_usd / price
current_usd = 0.0
in_position = True
trade_count += 1
portfolio_history.append({
'timestamp': timestamp,
'portfolio_value': current_btc * price,
'usd_balance': current_usd,
'btc_balance': current_btc,
'trade_type': 'BUY',
'price': price,
'in_position': in_position
})
if trade_count <= 3: # Debug first few trades
print(f" BUY {trade_count}: ${current_usd:.0f}{current_btc:.6f} BTC at ${price:.0f}")
elif trade_type == 'SELL' and in_position:
# ALL-OUT: Sell all BTC for USD
old_usd = current_usd
current_usd = current_btc * price
current_btc = 0.0
in_position = False
portfolio_history.append({
'timestamp': timestamp,
'portfolio_value': current_usd,
'usd_balance': current_usd,
'btc_balance': current_btc,
'trade_type': 'SELL',
'price': price,
'profit_pct': trade.get('profit_pct', 0) * 100,
'in_position': in_position
})
if trade_count <= 3: # Debug first few trades
print(f" SELL {trade_count}: {current_btc:.6f} BTC → ${current_usd:.0f} at ${price:.0f}")
# Convert to DataFrame
portfolio_df = pd.DataFrame(portfolio_history)
if len(portfolio_df) > 0:
portfolio_df = portfolio_df.sort_values('timestamp').reset_index(drop=True)
final_value = portfolio_df['portfolio_value'].iloc[-1]
else:
final_value = initial_usd
print(f" ⚠️ Warning: No portfolio history generated!")
# Calculate performance metrics
total_return = (final_value - initial_usd) / initial_usd * 100
num_trades = len(sell_signals)
if num_trades > 0:
winning_trades = len(sell_signals[sell_signals['profit_pct'] > 0])
win_rate = winning_trades / num_trades * 100
avg_trade = sell_signals['profit_pct'].mean() * 100
best_trade = sell_signals['profit_pct'].max() * 100
worst_trade = sell_signals['profit_pct'].min() * 100
else:
win_rate = avg_trade = best_trade = worst_trade = 0
performance = {
'strategy_name': strategy_name,
'initial_value': initial_usd,
'final_value': final_value,
'total_return': total_return,
'num_trades': num_trades,
'win_rate': win_rate,
'avg_trade': avg_trade,
'best_trade': best_trade,
'worst_trade': worst_trade
}
print(f" 💰 Final Value: ${final_value:,.0f} ({total_return:+.1f}%)")
print(f" 📈 Portfolio events: {len(portfolio_df)}")
return buy_signals, sell_signals, portfolio_df, performance
def create_side_by_side_comparison(data1, data2, save_path="same_logic_comparison.png"):
"""Create side-by-side comparison plot."""
buy1, sell1, portfolio1, perf1 = data1
buy2, sell2, portfolio2, perf2 = data2
# Create figure with subplots
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(20, 16))
# Plot 1: Original Strategy Signals
ax1.scatter(buy1['entry_time'], buy1['entry_price'],
color='green', marker='^', s=60, label=f"Buy ({len(buy1)})",
zorder=5, alpha=0.8)
profitable_sells1 = sell1[sell1['profit_pct'] > 0]
losing_sells1 = sell1[sell1['profit_pct'] <= 0]
if len(profitable_sells1) > 0:
ax1.scatter(profitable_sells1['exit_time'], profitable_sells1['exit_price'],
color='blue', marker='v', s=60, label=f"Profitable Sells ({len(profitable_sells1)})",
zorder=5, alpha=0.8)
if len(losing_sells1) > 0:
ax1.scatter(losing_sells1['exit_time'], losing_sells1['exit_price'],
color='red', marker='v', s=60, label=f"Losing Sells ({len(losing_sells1)})",
zorder=5, alpha=0.8)
ax1.set_title(f'{perf1["strategy_name"]} - Trading Signals', fontsize=14, fontweight='bold')
ax1.set_ylabel('Price (USD)', fontsize=12)
ax1.legend(loc='upper left', fontsize=9)
ax1.grid(True, alpha=0.3)
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
# Plot 2: Incremental Strategy Signals
ax2.scatter(buy2['entry_time'], buy2['entry_price'],
color='darkgreen', marker='^', s=60, label=f"Buy ({len(buy2)})",
zorder=5, alpha=0.8)
profitable_sells2 = sell2[sell2['profit_pct'] > 0]
losing_sells2 = sell2[sell2['profit_pct'] <= 0]
if len(profitable_sells2) > 0:
ax2.scatter(profitable_sells2['exit_time'], profitable_sells2['exit_price'],
color='darkblue', marker='v', s=60, label=f"Profitable Sells ({len(profitable_sells2)})",
zorder=5, alpha=0.8)
if len(losing_sells2) > 0:
ax2.scatter(losing_sells2['exit_time'], losing_sells2['exit_price'],
color='darkred', marker='v', s=60, label=f"Losing Sells ({len(losing_sells2)})",
zorder=5, alpha=0.8)
ax2.set_title(f'{perf2["strategy_name"]} - Trading Signals', fontsize=14, fontweight='bold')
ax2.set_ylabel('Price (USD)', fontsize=12)
ax2.legend(loc='upper left', fontsize=9)
ax2.grid(True, alpha=0.3)
ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
# Plot 3: Portfolio Value Comparison
if len(portfolio1) > 0:
ax3.plot(portfolio1['timestamp'], portfolio1['portfolio_value'],
color='blue', linewidth=2, label=f'{perf1["strategy_name"]}', alpha=0.8)
if len(portfolio2) > 0:
ax3.plot(portfolio2['timestamp'], portfolio2['portfolio_value'],
color='red', linewidth=2, label=f'{perf2["strategy_name"]}', alpha=0.8)
ax3.axhline(y=10000, color='gray', linestyle='--', alpha=0.7, label='Initial Value ($10,000)')
ax3.set_title('Portfolio Value Comparison (Same Logic)', fontsize=14, fontweight='bold')
ax3.set_ylabel('Portfolio Value (USD)', fontsize=12)
ax3.set_xlabel('Date', fontsize=12)
ax3.legend(loc='upper left', fontsize=10)
ax3.grid(True, alpha=0.3)
ax3.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
# Plot 4: Performance Comparison Table
ax4.axis('off')
# Create detailed comparison table
comparison_text = f"""
IDENTICAL LOGIC COMPARISON
{'='*50}
{'Metric':<25} {perf1['strategy_name']:<15} {perf2['strategy_name']:<15} {'Difference':<15}
{'-'*75}
{'Initial Value':<25} ${perf1['initial_value']:>10,.0f} ${perf2['initial_value']:>12,.0f} ${perf2['initial_value'] - perf1['initial_value']:>12,.0f}
{'Final Value':<25} ${perf1['final_value']:>10,.0f} ${perf2['final_value']:>12,.0f} ${perf2['final_value'] - perf1['final_value']:>12,.0f}
{'Total Return':<25} {perf1['total_return']:>10.1f}% {perf2['total_return']:>12.1f}% {perf2['total_return'] - perf1['total_return']:>12.1f}%
{'Number of Trades':<25} {perf1['num_trades']:>10} {perf2['num_trades']:>12} {perf2['num_trades'] - perf1['num_trades']:>12}
{'Win Rate':<25} {perf1['win_rate']:>10.1f}% {perf2['win_rate']:>12.1f}% {perf2['win_rate'] - perf1['win_rate']:>12.1f}%
{'Average Trade':<25} {perf1['avg_trade']:>10.2f}% {perf2['avg_trade']:>12.2f}% {perf2['avg_trade'] - perf1['avg_trade']:>12.2f}%
{'Best Trade':<25} {perf1['best_trade']:>10.1f}% {perf2['best_trade']:>12.1f}% {perf2['best_trade'] - perf1['best_trade']:>12.1f}%
{'Worst Trade':<25} {perf1['worst_trade']:>10.1f}% {perf2['worst_trade']:>12.1f}% {perf2['worst_trade'] - perf1['worst_trade']:>12.1f}%
LOGIC APPLIED:
• ALL-IN: Use 100% of USD to buy BTC on entry signals
• ALL-OUT: Sell 100% of BTC for USD on exit signals
• NO FEES: Pure price-based calculations
• SAME COMPOUNDING: Each trade uses full available balance
TIME PERIODS:
{perf1['strategy_name']}: {buy1['entry_time'].min().strftime('%Y-%m-%d')} to {sell1['exit_time'].max().strftime('%Y-%m-%d')}
{perf2['strategy_name']}: {buy2['entry_time'].min().strftime('%Y-%m-%d')} to {sell2['exit_time'].max().strftime('%Y-%m-%d')}
ANALYSIS:
If results differ significantly, it indicates:
1. Different entry/exit timing
2. Different price execution points
3. Different trade frequency or duration
4. Data inconsistencies between files
"""
ax4.text(0.05, 0.95, comparison_text, transform=ax4.transAxes, fontsize=10,
verticalalignment='top', fontfamily='monospace',
bbox=dict(boxstyle="round,pad=0.5", facecolor="lightgray", alpha=0.9))
# Format x-axis for signal plots
for ax in [ax1, ax2, ax3]:
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)
# Adjust layout and save
plt.tight_layout()
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
print(f"Comparison plot saved to: {save_path}")
def main():
"""Main function to run the identical logic comparison."""
print("🚀 Starting Identical Logic Comparison")
print("=" * 60)
# File paths
original_file = "../results/trades_15min(15min)_ST3pct.csv"
incremental_file = "../results/trades_incremental_15min(15min)_ST3pct.csv"
output_file = "../results/same_logic_comparison.png"
# Check if files exist
if not os.path.exists(original_file):
print(f"❌ Error: Original trades file not found: {original_file}")
return
if not os.path.exists(incremental_file):
print(f"❌ Error: Incremental trades file not found: {incremental_file}")
return
try:
# Process both strategies with identical logic
original_data = process_trades_with_same_logic(original_file, "Original Strategy")
incremental_data = process_trades_with_same_logic(incremental_file, "Incremental Strategy")
# Create comparison plot
create_side_by_side_comparison(original_data, incremental_data, output_file)
# Print summary comparison
_, _, _, perf1 = original_data
_, _, _, perf2 = incremental_data
print(f"\n📊 IDENTICAL LOGIC COMPARISON SUMMARY:")
print(f"Original Strategy: ${perf1['final_value']:,.0f} ({perf1['total_return']:+.1f}%)")
print(f"Incremental Strategy: ${perf2['final_value']:,.0f} ({perf2['total_return']:+.1f}%)")
print(f"Difference: ${perf2['final_value'] - perf1['final_value']:,.0f} ({perf2['total_return'] - perf1['total_return']:+.1f}%)")
if abs(perf1['total_return'] - perf2['total_return']) < 1.0:
print("✅ Results are very similar - strategies are equivalent!")
else:
print("⚠️ Significant difference detected - investigating causes...")
print(f" • Trade count difference: {perf2['num_trades'] - perf1['num_trades']}")
print(f" • Win rate difference: {perf2['win_rate'] - perf1['win_rate']:+.1f}%")
print(f" • Avg trade difference: {perf2['avg_trade'] - perf1['avg_trade']:+.2f}%")
print(f"\n✅ Analysis completed successfully!")
except Exception as e:
print(f"❌ Error during analysis: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()

271
scripts/plot_old.py Normal file
View File

@@ -0,0 +1,271 @@
#!/usr/bin/env python3
"""
Plot original strategy results from trades CSV file.
Shows buy/sell signals and portfolio value over time.
"""
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime
import os
import sys
# Add project root to path
sys.path.insert(0, os.path.abspath('..'))
def load_and_process_trades(trades_file, initial_usd=10000):
"""Load trades and calculate portfolio value over time."""
# Load trades data
trades_df = pd.read_csv(trades_file)
# Convert timestamps
trades_df['entry_time'] = pd.to_datetime(trades_df['entry_time'])
trades_df['exit_time'] = pd.to_datetime(trades_df['exit_time'], errors='coerce')
# Separate buy and sell signals
buy_signals = trades_df[trades_df['type'] == 'BUY'].copy()
sell_signals = trades_df[trades_df['type'] != 'BUY'].copy()
print(f"Loaded {len(buy_signals)} buy signals and {len(sell_signals)} sell signals")
# Calculate portfolio value using compounding
portfolio_value = initial_usd
portfolio_history = []
# Create timeline from all trade times
all_times = []
all_times.extend(buy_signals['entry_time'].tolist())
all_times.extend(sell_signals['exit_time'].dropna().tolist())
all_times = sorted(set(all_times))
print(f"Processing {len(all_times)} trade events...")
# Track portfolio value at each trade
current_value = initial_usd
for sell_trade in sell_signals.itertuples():
# Apply the profit/loss from this trade
profit_pct = sell_trade.profit_pct
current_value *= (1 + profit_pct)
portfolio_history.append({
'timestamp': sell_trade.exit_time,
'portfolio_value': current_value,
'trade_type': 'SELL',
'price': sell_trade.exit_price,
'profit_pct': profit_pct * 100
})
# Convert to DataFrame
portfolio_df = pd.DataFrame(portfolio_history)
portfolio_df = portfolio_df.sort_values('timestamp').reset_index(drop=True)
# Calculate performance metrics
final_value = current_value
total_return = (final_value - initial_usd) / initial_usd * 100
num_trades = len(sell_signals)
winning_trades = len(sell_signals[sell_signals['profit_pct'] > 0])
win_rate = winning_trades / num_trades * 100 if num_trades > 0 else 0
avg_trade = sell_signals['profit_pct'].mean() * 100 if num_trades > 0 else 0
best_trade = sell_signals['profit_pct'].max() * 100 if num_trades > 0 else 0
worst_trade = sell_signals['profit_pct'].min() * 100 if num_trades > 0 else 0
performance = {
'initial_value': initial_usd,
'final_value': final_value,
'total_return': total_return,
'num_trades': num_trades,
'win_rate': win_rate,
'avg_trade': avg_trade,
'best_trade': best_trade,
'worst_trade': worst_trade
}
return buy_signals, sell_signals, portfolio_df, performance
def create_comprehensive_plot(buy_signals, sell_signals, portfolio_df, performance, save_path="original_strategy_analysis.png"):
"""Create comprehensive plot with signals and portfolio value."""
# Create figure with subplots
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(16, 12),
gridspec_kw={'height_ratios': [2, 1]})
# Plot 1: Price chart with buy/sell signals
# Get price range for the chart
all_prices = []
all_prices.extend(buy_signals['entry_price'].tolist())
all_prices.extend(sell_signals['exit_price'].tolist())
price_min = min(all_prices)
price_max = max(all_prices)
# Create a price line by connecting buy and sell points
price_timeline = []
value_timeline = []
# Combine and sort all signals by time
all_signals = []
for _, buy in buy_signals.iterrows():
all_signals.append({
'time': buy['entry_time'],
'price': buy['entry_price'],
'type': 'BUY'
})
for _, sell in sell_signals.iterrows():
all_signals.append({
'time': sell['exit_time'],
'price': sell['exit_price'],
'type': 'SELL'
})
all_signals = sorted(all_signals, key=lambda x: x['time'])
# Create price line
for signal in all_signals:
price_timeline.append(signal['time'])
value_timeline.append(signal['price'])
# Plot price line
if price_timeline:
ax1.plot(price_timeline, value_timeline, color='black', linewidth=1.5, alpha=0.7, label='Price Action')
# Plot buy signals
ax1.scatter(buy_signals['entry_time'], buy_signals['entry_price'],
color='green', marker='^', s=80, label=f"Buy Signals ({len(buy_signals)})",
zorder=5, alpha=0.9, edgecolors='white', linewidth=1)
# Plot sell signals with different colors based on profit/loss
profitable_sells = sell_signals[sell_signals['profit_pct'] > 0]
losing_sells = sell_signals[sell_signals['profit_pct'] <= 0]
if len(profitable_sells) > 0:
ax1.scatter(profitable_sells['exit_time'], profitable_sells['exit_price'],
color='blue', marker='v', s=80, label=f"Profitable Sells ({len(profitable_sells)})",
zorder=5, alpha=0.9, edgecolors='white', linewidth=1)
if len(losing_sells) > 0:
ax1.scatter(losing_sells['exit_time'], losing_sells['exit_price'],
color='red', marker='v', s=80, label=f"Losing Sells ({len(losing_sells)})",
zorder=5, alpha=0.9, edgecolors='white', linewidth=1)
ax1.set_title('Original Strategy - Trading Signals', fontsize=16, fontweight='bold')
ax1.set_ylabel('Price (USD)', fontsize=12)
ax1.legend(loc='upper left', fontsize=10)
ax1.grid(True, alpha=0.3)
# Format y-axis for price
ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
# Plot 2: Portfolio Value Over Time
if len(portfolio_df) > 0:
ax2.plot(portfolio_df['timestamp'], portfolio_df['portfolio_value'],
color='purple', linewidth=2, label='Portfolio Value')
# Add horizontal line for initial value
ax2.axhline(y=performance['initial_value'], color='gray',
linestyle='--', alpha=0.7, label='Initial Value ($10,000)')
# Add profit/loss shading
initial_value = performance['initial_value']
profit_mask = portfolio_df['portfolio_value'] > initial_value
loss_mask = portfolio_df['portfolio_value'] < initial_value
if profit_mask.any():
ax2.fill_between(portfolio_df['timestamp'], portfolio_df['portfolio_value'], initial_value,
where=profit_mask, color='green', alpha=0.2, label='Profit Zone')
if loss_mask.any():
ax2.fill_between(portfolio_df['timestamp'], portfolio_df['portfolio_value'], initial_value,
where=loss_mask, color='red', alpha=0.2, label='Loss Zone')
ax2.set_title('Portfolio Value Over Time', fontsize=14, fontweight='bold')
ax2.set_ylabel('Portfolio Value (USD)', fontsize=12)
ax2.set_xlabel('Date', fontsize=12)
ax2.legend(loc='upper left', fontsize=10)
ax2.grid(True, alpha=0.3)
# Format y-axis for portfolio value
ax2.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
# Format x-axis for both plots
for ax in [ax1, ax2]:
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)
# Add performance text box
perf_text = f"""
PERFORMANCE SUMMARY
{'='*30}
Initial Value: ${performance['initial_value']:,.0f}
Final Value: ${performance['final_value']:,.0f}
Total Return: {performance['total_return']:+.1f}%
Trading Statistics:
• Number of Trades: {performance['num_trades']}
• Win Rate: {performance['win_rate']:.1f}%
• Average Trade: {performance['avg_trade']:+.2f}%
• Best Trade: {performance['best_trade']:+.1f}%
• Worst Trade: {performance['worst_trade']:+.1f}%
Period: {buy_signals['entry_time'].min().strftime('%Y-%m-%d')} to {sell_signals['exit_time'].max().strftime('%Y-%m-%d')}
"""
# Add text box to the plot
ax2.text(1.02, 0.98, perf_text, transform=ax2.transAxes, fontsize=10,
verticalalignment='top', fontfamily='monospace',
bbox=dict(boxstyle="round,pad=0.5", facecolor="lightgray", alpha=0.9))
# Adjust layout and save
plt.tight_layout()
plt.subplots_adjust(right=0.75) # Make room for text box
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
print(f"Plot saved to: {save_path}")
def main():
"""Main function to run the analysis."""
print("🚀 Starting Original Strategy Analysis")
print("=" * 50)
# File paths
trades_file = "../results/trades_15min(15min)_ST3pct.csv"
output_file = "../results/original_strategy_analysis.png"
if not os.path.exists(trades_file):
print(f"❌ Error: Trades file not found: {trades_file}")
return
try:
# Load and process trades
buy_signals, sell_signals, portfolio_df, performance = load_and_process_trades(trades_file)
# Print performance summary
print(f"\n📊 PERFORMANCE SUMMARY:")
print(f"Initial Value: ${performance['initial_value']:,.0f}")
print(f"Final Value: ${performance['final_value']:,.0f}")
print(f"Total Return: {performance['total_return']:+.1f}%")
print(f"Number of Trades: {performance['num_trades']}")
print(f"Win Rate: {performance['win_rate']:.1f}%")
print(f"Average Trade: {performance['avg_trade']:+.2f}%")
# Create plot
create_comprehensive_plot(buy_signals, sell_signals, portfolio_df, performance, output_file)
print(f"\n✅ Analysis completed successfully!")
except Exception as e:
print(f"❌ Error during analysis: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()

276
scripts/plot_results.py Normal file
View File

@@ -0,0 +1,276 @@
#!/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()