""" Base classes for the strategy management system. This module contains the fundamental building blocks for all trading strategies: - StrategySignal: Represents trading signals with confidence and metadata - StrategyBase: Abstract base class that all strategies must inherit from """ import pandas as pd from abc import ABC, abstractmethod from typing import Dict, Optional, List, Union class StrategySignal: """ Represents a trading signal from a strategy. A signal encapsulates the strategy's recommendation along with confidence level, optional price target, and additional metadata. Attributes: signal_type (str): Type of signal - "ENTRY", "EXIT", or "HOLD" confidence (float): Confidence level from 0.0 to 1.0 price (Optional[float]): Optional specific price for the signal metadata (Dict): Additional signal data and context Example: # Entry signal with high confidence signal = StrategySignal("ENTRY", confidence=0.8) # Exit signal with stop loss price signal = StrategySignal("EXIT", confidence=1.0, price=50000, metadata={"type": "STOP_LOSS"}) """ def __init__(self, signal_type: str, confidence: float = 1.0, price: Optional[float] = None, metadata: Optional[Dict] = None): """ Initialize a strategy signal. Args: signal_type: Type of signal ("ENTRY", "EXIT", "HOLD") confidence: Confidence level (0.0 to 1.0) price: Optional specific price for the signal metadata: Additional signal data and context """ self.signal_type = signal_type self.confidence = max(0.0, min(1.0, confidence)) # Clamp to [0,1] self.price = price self.metadata = metadata or {} def __repr__(self) -> str: """String representation of the signal.""" return (f"StrategySignal(type={self.signal_type}, " f"confidence={self.confidence:.2f}, " f"price={self.price}, metadata={self.metadata})") class StrategyBase(ABC): """ Abstract base class for all trading strategies. This class defines the interface that all strategies must implement: - get_timeframes(): Specify required timeframes for the strategy - initialize(): Setup strategy with backtester data - get_entry_signal(): Generate entry signals - get_exit_signal(): Generate exit signals - get_confidence(): Optional confidence calculation Attributes: name (str): Strategy name weight (float): Strategy weight for combination params (Dict): Strategy parameters initialized (bool): Whether strategy has been initialized timeframes_data (Dict): Resampled data for different timeframes Example: class MyStrategy(StrategyBase): def get_timeframes(self): return ["15min"] # This strategy works on 15-minute data def initialize(self, backtester): # Setup strategy indicators using self.timeframes_data["15min"] self.initialized = True def get_entry_signal(self, backtester, df_index): # Return StrategySignal based on analysis if should_enter: return StrategySignal("ENTRY", confidence=0.7) return StrategySignal("HOLD", confidence=0.0) """ def __init__(self, name: str, weight: float = 1.0, params: Optional[Dict] = None): """ Initialize the strategy base. Args: name: Strategy name/identifier weight: Strategy weight for combination (default: 1.0) params: Strategy-specific parameters """ self.name = name self.weight = weight self.params = params or {} self.initialized = False self.timeframes_data = {} # Will store resampled data for each timeframe def get_timeframes(self) -> List[str]: """ Get the list of timeframes required by this strategy. Override this method to specify which timeframes your strategy needs. The base class will automatically resample the 1-minute data to these timeframes and make them available in self.timeframes_data. Returns: List[str]: List of timeframe strings (e.g., ["1min", "15min", "1h"]) Example: def get_timeframes(self): return ["15min"] # Strategy needs 15-minute data def get_timeframes(self): return ["5min", "15min", "1h"] # Multi-timeframe strategy """ return ["1min"] # Default to 1-minute data def _resample_data(self, original_data: pd.DataFrame) -> None: """ Resample the original 1-minute data to all required timeframes. This method is called automatically during initialization to create resampled versions of the data for each timeframe the strategy needs. Args: original_data: Original 1-minute OHLCV data with DatetimeIndex """ self.timeframes_data = {} for timeframe in self.get_timeframes(): if timeframe == "1min": # For 1-minute data, just use the original self.timeframes_data[timeframe] = original_data.copy() else: # Resample to the specified timeframe resampled = original_data.resample(timeframe).agg({ 'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum' }).dropna() self.timeframes_data[timeframe] = resampled def get_data_for_timeframe(self, timeframe: str) -> Optional[pd.DataFrame]: """ Get resampled data for a specific timeframe. Args: timeframe: Timeframe string (e.g., "15min", "1h") Returns: pd.DataFrame: Resampled OHLCV data or None if timeframe not available """ return self.timeframes_data.get(timeframe) def get_primary_timeframe_data(self) -> pd.DataFrame: """ Get data for the primary (first) timeframe. Returns: pd.DataFrame: Data for the first timeframe in get_timeframes() list """ primary_timeframe = self.get_timeframes()[0] return self.timeframes_data[primary_timeframe] @abstractmethod def initialize(self, backtester) -> None: """ Initialize strategy with backtester data. This method is called once before backtesting begins. The original 1-minute data will already be resampled to all required timeframes and available in self.timeframes_data. Strategies should setup indicators, validate data, and set self.initialized = True when complete. Args: backtester: Backtest instance with data and configuration """ pass @abstractmethod def get_entry_signal(self, backtester, df_index: int) -> StrategySignal: """ Generate entry signal for the given data index. The df_index refers to the index in the backtester's working dataframe, which corresponds to the primary timeframe data. Args: backtester: Backtest instance with current state df_index: Current index in the primary timeframe dataframe Returns: StrategySignal: Entry signal with confidence level """ pass @abstractmethod def get_exit_signal(self, backtester, df_index: int) -> StrategySignal: """ Generate exit signal for the given data index. The df_index refers to the index in the backtester's working dataframe, which corresponds to the primary timeframe data. Args: backtester: Backtest instance with current state df_index: Current index in the primary timeframe dataframe Returns: StrategySignal: Exit signal with confidence level """ pass def get_confidence(self, backtester, df_index: int) -> float: """ Get strategy confidence for the current market state. Default implementation returns 1.0. Strategies can override this to provide dynamic confidence based on market conditions. Args: backtester: Backtest instance with current state df_index: Current index in the primary timeframe dataframe Returns: float: Confidence level (0.0 to 1.0) """ return 1.0 def __repr__(self) -> str: """String representation of the strategy.""" timeframes = self.get_timeframes() return (f"{self.__class__.__name__}(name={self.name}, " f"weight={self.weight}, timeframes={timeframes}, " f"initialized={self.initialized})")