343 lines
14 KiB
Python
343 lines
14 KiB
Python
#!/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() |