""" Backtester Configuration This module provides configuration classes and utilities for backtesting incremental trading strategies. """ import os import pandas as pd from dataclasses import dataclass from typing import Optional, Dict, Any, List import logging logger = logging.getLogger(__name__) @dataclass class BacktestConfig: """ Configuration for backtesting runs. This class encapsulates all configuration parameters needed for running backtests, including data settings, trading parameters, and performance options. Attributes: data_file: Path to the data file (relative to data directory) start_date: Start date for backtesting (YYYY-MM-DD format) end_date: End date for backtesting (YYYY-MM-DD format) initial_usd: Initial USD balance for trading timeframe: Data timeframe (e.g., "1min", "5min", "15min") stop_loss_pct: Default stop loss percentage (0.0 to disable) take_profit_pct: Default take profit percentage (0.0 to disable) max_workers: Maximum number of worker processes for parallel execution chunk_size: Chunk size for data processing data_dir: Directory containing data files results_dir: Directory for saving results Example: config = BacktestConfig( data_file="btc_1min_2023.csv", start_date="2023-01-01", end_date="2023-12-31", initial_usd=10000, stop_loss_pct=0.02 ) """ data_file: str start_date: str end_date: str initial_usd: float = 10000 timeframe: str = "1min" # Risk management parameters stop_loss_pct: float = 0.0 take_profit_pct: float = 0.0 # Performance settings max_workers: Optional[int] = None chunk_size: int = 1000 # Directory settings data_dir: str = "data" results_dir: str = "results" def __post_init__(self): """Validate configuration after initialization.""" self._validate_config() self._ensure_directories() def _validate_config(self): """Validate configuration parameters.""" # Validate dates try: start_dt = pd.to_datetime(self.start_date) end_dt = pd.to_datetime(self.end_date) if start_dt >= end_dt: raise ValueError("start_date must be before end_date") except Exception as e: raise ValueError(f"Invalid date format: {e}") # Validate financial parameters if self.initial_usd <= 0: raise ValueError("initial_usd must be positive") if not (0 <= self.stop_loss_pct <= 1): raise ValueError("stop_loss_pct must be between 0 and 1") if not (0 <= self.take_profit_pct <= 1): raise ValueError("take_profit_pct must be between 0 and 1") # Validate performance parameters if self.max_workers is not None and self.max_workers <= 0: raise ValueError("max_workers must be positive") if self.chunk_size <= 0: raise ValueError("chunk_size must be positive") def _ensure_directories(self): """Ensure required directories exist.""" os.makedirs(self.data_dir, exist_ok=True) os.makedirs(self.results_dir, exist_ok=True) def get_data_path(self) -> str: """Get full path to data file.""" return os.path.join(self.data_dir, self.data_file) def get_results_path(self, filename: str) -> str: """Get full path for results file.""" return os.path.join(self.results_dir, filename) def to_dict(self) -> Dict[str, Any]: """Convert configuration to dictionary.""" return { "data_file": self.data_file, "start_date": self.start_date, "end_date": self.end_date, "initial_usd": self.initial_usd, "timeframe": self.timeframe, "stop_loss_pct": self.stop_loss_pct, "take_profit_pct": self.take_profit_pct, "max_workers": self.max_workers, "chunk_size": self.chunk_size, "data_dir": self.data_dir, "results_dir": self.results_dir } @classmethod def from_dict(cls, config_dict: Dict[str, Any]) -> 'BacktestConfig': """Create configuration from dictionary.""" return cls(**config_dict) def copy(self, **kwargs) -> 'BacktestConfig': """Create a copy of the configuration with optional parameter overrides.""" config_dict = self.to_dict() config_dict.update(kwargs) return self.from_dict(config_dict) def __repr__(self) -> str: """String representation of the configuration.""" return (f"BacktestConfig(data_file={self.data_file}, " f"date_range={self.start_date} to {self.end_date}, " f"initial_usd=${self.initial_usd})") class OptimizationConfig: """ Configuration for parameter optimization runs. This class provides additional configuration options specifically for parameter optimization and grid search operations. """ def __init__(self, base_config: BacktestConfig, strategy_param_grid: Dict[str, List], trader_param_grid: Optional[Dict[str, List]] = None, max_workers: Optional[int] = None, save_individual_results: bool = True, save_detailed_logs: bool = False): """ Initialize optimization configuration. Args: base_config: Base backtesting configuration strategy_param_grid: Grid of strategy parameters to test trader_param_grid: Grid of trader parameters to test max_workers: Maximum number of worker processes save_individual_results: Whether to save individual strategy results save_detailed_logs: Whether to save detailed action logs """ self.base_config = base_config self.strategy_param_grid = strategy_param_grid self.trader_param_grid = trader_param_grid or {} self.max_workers = max_workers self.save_individual_results = save_individual_results self.save_detailed_logs = save_detailed_logs def get_total_combinations(self) -> int: """Calculate total number of parameter combinations.""" from itertools import product # Calculate strategy combinations strategy_values = list(self.strategy_param_grid.values()) strategy_combinations = len(list(product(*strategy_values))) if strategy_values else 1 # Calculate trader combinations trader_values = list(self.trader_param_grid.values()) trader_combinations = len(list(product(*trader_values))) if trader_values else 1 return strategy_combinations * trader_combinations def to_dict(self) -> Dict[str, Any]: """Convert optimization configuration to dictionary.""" return { "base_config": self.base_config.to_dict(), "strategy_param_grid": self.strategy_param_grid, "trader_param_grid": self.trader_param_grid, "max_workers": self.max_workers, "save_individual_results": self.save_individual_results, "save_detailed_logs": self.save_detailed_logs, "total_combinations": self.get_total_combinations() } def __repr__(self) -> str: """String representation of the optimization configuration.""" return (f"OptimizationConfig(combinations={self.get_total_combinations()}, " f"max_workers={self.max_workers})")