""" Moving Average Convergence Divergence (MACD) indicator implementation. """ import pandas as pd from ..base import BaseIndicator 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') -> pd.DataFrame: """ 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: DataFrame with MACD values and metadata, indexed by timestamp """ # Validate input data if not self.validate_dataframe(df, slow_period): return pd.DataFrame() try: df = df.copy() df['macd'] = df[price_column].ewm(span=fast_period, adjust=False).mean() - \ df[price_column].ewm(span=slow_period, adjust=False).mean() df['signal'] = df['macd'].ewm(span=signal_period, adjust=False).mean() df['histogram'] = df['macd'] - df['signal'] # Only keep rows with valid MACD, and only 'timestamp', 'macd', 'signal', 'histogram' columns result_df = df.loc[df['macd'].notna() & df['signal'].notna() & df['histogram'].notna(), ['timestamp', 'macd', 'signal', 'histogram']].copy() # Only keep rows after enough data for MACD and signal min_required = max(slow_period, signal_period) result_df = result_df.iloc[min_required-1:] result_df.set_index('timestamp', inplace=True) return result_df except Exception: return pd.DataFrame()