""" Moving Average Convergence Divergence (MACD) indicator implementation. """ from typing import List import pandas as pd from ..base import BaseIndicator from ..result import IndicatorResult class MACDIndicator(BaseIndicator): """ Moving Average Convergence Divergence (MACD) technical indicator. Calculates trend-following momentum indicator that shows the relationship between two moving averages of a security's price. Handles sparse data appropriately without interpolation. """ def calculate(self, df: pd.DataFrame, fast_period: int = 12, slow_period: int = 26, signal_period: int = 9, price_column: str = 'close') -> List[IndicatorResult]: """ Calculate Moving Average Convergence Divergence (MACD). Args: df: DataFrame with OHLCV data fast_period: Fast EMA period (default 12) slow_period: Slow EMA period (default 26) signal_period: Signal line EMA period (default 9) price_column: Price column to use ('open', 'high', 'low', 'close') Returns: List of indicator results with MACD, signal, and histogram values """ # Validate input data if not self.validate_dataframe(df, slow_period): return [] try: # Calculate fast and slow EMAs df['ema_fast'] = df[price_column].ewm(span=fast_period, adjust=False).mean() df['ema_slow'] = df[price_column].ewm(span=slow_period, adjust=False).mean() # Calculate MACD line df['macd'] = df['ema_fast'] - df['ema_slow'] # Calculate signal line (EMA of MACD) df['signal'] = df['macd'].ewm(span=signal_period, adjust=False).mean() # Calculate histogram df['histogram'] = df['macd'] - df['signal'] # Convert results to IndicatorResult objects results = [] for i, (timestamp, row) in enumerate(df.iterrows()): # Only return results after minimum period if i >= slow_period - 1: if not (pd.isna(row['macd']) or pd.isna(row['signal']) or pd.isna(row['histogram'])): result = IndicatorResult( timestamp=timestamp, symbol=row['symbol'], timeframe=row['timeframe'], values={ 'macd': row['macd'], 'signal': row['signal'], 'histogram': row['histogram'] }, metadata={ 'fast_period': fast_period, 'slow_period': slow_period, 'signal_period': signal_period, 'price_column': price_column } ) results.append(result) return results except Exception as e: if self.logger: self.logger.error(f"Error calculating MACD: {e}") return []