Files
zhutoutoutousan 5b44e14211 Update
2026-01-05 05:37:33 +01:00

213 lines
7.5 KiB
Python

"""
Performance Analysis and Reporting
This module provides tools for analyzing backtest results and generating reports.
"""
from typing import Dict, Any, List
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime
class PerformanceAnalyzer:
"""
Analyzes backtest performance and generates reports.
"""
def __init__(self, backtest_results: Dict[str, Any]):
"""
Initialize with backtest results.
Args:
backtest_results: Results dictionary from BacktestEngine.run()
"""
self.results = backtest_results
self.metrics = backtest_results['metrics']
self.trades = backtest_results['trades']
self.strategy_name = backtest_results['strategy_name']
def print_summary(self):
"""Print a summary of the backtest results."""
print("\n" + "="*60)
print(f"BACKTEST SUMMARY: {self.strategy_name}")
print("="*60)
print(f"\nInitial Balance: ${self.metrics['initial_balance']:,.2f}")
print(f"Final Balance: ${self.metrics['final_balance']:,.2f}")
print(f"Total Return: {self.metrics['total_return_pct']:.2f}%")
print(f"\nTotal Trades: {self.metrics['total_trades']}")
print(f"Winning Trades: {self.metrics['winning_trades']}")
print(f"Losing Trades: {self.metrics['losing_trades']}")
print(f"Win Rate: {self.metrics['win_rate_pct']:.2f}%")
print(f"\nTotal Profit: ${self.metrics['total_profit']:,.2f}")
print(f"Total Loss: ${self.metrics['total_loss']:,.2f}")
print(f"Profit Factor: {self.metrics['profit_factor']:.2f}")
print(f"\nAverage Win: ${self.metrics['avg_win']:,.2f}")
print(f"Average Loss: ${self.metrics['avg_loss']:,.2f}")
print(f"Max Drawdown: {self.metrics['max_drawdown_pct']:.2f}%")
if self.metrics.get('parameters'):
print(f"\nStrategy Parameters:")
for key, value in self.metrics['parameters'].items():
print(f" {key}: {value}")
print("="*60 + "\n")
def get_trades_dataframe(self) -> pd.DataFrame:
"""Convert trades list to pandas DataFrame."""
if not self.trades:
return pd.DataFrame()
df = pd.DataFrame(self.trades)
df['open_time'] = pd.to_datetime(df['open_time'])
df['close_time'] = pd.to_datetime(df['close_time'])
df['duration'] = df['close_time'] - df['open_time']
return df
def plot_equity_curve(self, save_path: str = None):
"""
Plot equity curve over time.
Args:
save_path: Optional path to save the plot
"""
if not self.trades:
print("No trades to plot")
return
df = self.get_trades_dataframe()
df = df.sort_values('close_time')
# Calculate cumulative equity
cumulative_profit = df['profit'].cumsum()
equity_curve = self.metrics['initial_balance'] + cumulative_profit
plt.figure(figsize=(12, 6))
plt.plot(df['close_time'], equity_curve, linewidth=2, label='Equity')
plt.axhline(y=self.metrics['initial_balance'], color='r', linestyle='--', label='Initial Balance')
plt.xlabel('Time')
plt.ylabel('Equity ($)')
plt.title(f'Equity Curve - {self.strategy_name}')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
print(f"Equity curve saved to {save_path}")
else:
plt.show()
def plot_drawdown(self, save_path: str = None):
"""
Plot drawdown over time.
Args:
save_path: Optional path to save the plot
"""
if not self.trades:
print("No trades to plot")
return
df = self.get_trades_dataframe()
df = df.sort_values('close_time')
# Calculate cumulative equity
cumulative_profit = df['profit'].cumsum()
equity_curve = self.metrics['initial_balance'] + cumulative_profit
# Calculate running maximum
running_max = equity_curve.expanding().max()
drawdown = (equity_curve - running_max) / running_max * 100
plt.figure(figsize=(12, 6))
plt.fill_between(df['close_time'], drawdown, 0, alpha=0.3, color='red', label='Drawdown')
plt.plot(df['close_time'], drawdown, linewidth=1, color='darkred')
plt.xlabel('Time')
plt.ylabel('Drawdown (%)')
plt.title(f'Drawdown Chart - {self.strategy_name}')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
print(f"Drawdown chart saved to {save_path}")
else:
plt.show()
def plot_monthly_returns(self, save_path: str = None):
"""
Plot monthly returns.
Args:
save_path: Optional path to save the plot
"""
if not self.trades:
print("No trades to plot")
return
df = self.get_trades_dataframe()
df = df.sort_values('close_time')
# Group by month
df['month'] = df['close_time'].dt.to_period('M')
monthly_returns = df.groupby('month')['profit'].sum()
monthly_returns_pct = (monthly_returns / self.metrics['initial_balance']) * 100
plt.figure(figsize=(12, 6))
colors = ['green' if x > 0 else 'red' for x in monthly_returns_pct]
plt.bar(range(len(monthly_returns_pct)), monthly_returns_pct, color=colors, alpha=0.7)
plt.xlabel('Month')
plt.ylabel('Return (%)')
plt.title(f'Monthly Returns - {self.strategy_name}')
plt.xticks(range(len(monthly_returns_pct)), [str(x) for x in monthly_returns_pct.index], rotation=45)
plt.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
plt.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
print(f"Monthly returns chart saved to {save_path}")
else:
plt.show()
def export_trades_csv(self, filepath: str):
"""
Export trades to CSV file.
Args:
filepath: Path to save CSV file
"""
df = self.get_trades_dataframe()
df.to_csv(filepath, index=False)
print(f"Trades exported to {filepath}")
def generate_report(self, output_dir: str = "backtest_results"):
"""
Generate a comprehensive report with all charts and data.
Args:
output_dir: Directory to save report files
"""
import os
os.makedirs(output_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
prefix = f"{self.strategy_name}_{timestamp}"
# Print summary
self.print_summary()
# Generate plots
self.plot_equity_curve(os.path.join(output_dir, f"{prefix}_equity_curve.png"))
self.plot_drawdown(os.path.join(output_dir, f"{prefix}_drawdown.png"))
self.plot_monthly_returns(os.path.join(output_dir, f"{prefix}_monthly_returns.png"))
# Export trades
self.export_trades_csv(os.path.join(output_dir, f"{prefix}_trades.csv"))
print(f"\nReport generated in {output_dir}/")