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