""" Base classes and interfaces for technical indicators. This module provides the foundation for all technical indicators with common functionality and type definitions. """ from abc import ABC, abstractmethod from typing import List, Dict, Any import pandas as pd from utils.logger import get_logger from .result import IndicatorResult from ..data_types import OHLCVCandle class BaseIndicator(ABC): """ Abstract base class for all technical indicators. Provides common functionality and enforces consistent interface across all indicator implementations. """ def __init__(self, logger=None): """ Initialize base indicator. Args: logger: Optional logger instance """ if logger is None: self.logger = get_logger(__name__) self.logger = logger def prepare_dataframe(self, candles: List[OHLCVCandle]) -> pd.DataFrame: """ Convert OHLCV candles to pandas DataFrame for calculations. Args: candles: List of OHLCV candles (can be sparse) Returns: DataFrame with OHLCV data, sorted by timestamp """ if not candles: return pd.DataFrame() # Convert to DataFrame data = [] for candle in candles: data.append({ 'timestamp': candle.end_time, # Right-aligned timestamp 'symbol': candle.symbol, 'timeframe': candle.timeframe, 'open': float(candle.open), 'high': float(candle.high), 'low': float(candle.low), 'close': float(candle.close), 'volume': float(candle.volume), 'trade_count': candle.trade_count }) df = pd.DataFrame(data) # Sort by timestamp to ensure proper order df = df.sort_values('timestamp').reset_index(drop=True) # Set timestamp as index for time-series operations df.set_index('timestamp', inplace=True) return df @abstractmethod def calculate(self, df: pd.DataFrame, **kwargs) -> List[IndicatorResult]: """ Calculate the indicator values. Args: df: DataFrame with OHLCV data **kwargs: Additional parameters specific to each indicator Returns: List of indicator results """ pass def validate_dataframe(self, df: pd.DataFrame, min_periods: int) -> bool: """ Validate that DataFrame has sufficient data for calculation. Args: df: DataFrame to validate min_periods: Minimum number of periods required Returns: True if DataFrame is valid, False otherwise """ if df.empty or len(df) < min_periods: if self.logger: self.logger.warning( f"Insufficient data: got {len(df)} periods, need {min_periods}" ) return False return True