ok, kind of incremental trading and backtester, but result not alligning
This commit is contained in:
343
scripts/compare_same_logic.py
Normal file
343
scripts/compare_same_logic.py
Normal 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
271
scripts/plot_old.py
Normal 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
276
scripts/plot_results.py
Normal 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()
|
||||
Reference in New Issue
Block a user