""" Bollinger Bands indicator implementation. """ import pandas as pd from ..base import BaseIndicator class BollingerBandsIndicator(BaseIndicator): """ Bollinger Bands technical indicator. Calculates a set of lines plotted two standard deviations away from a simple moving average. Handles sparse data appropriately without interpolation. """ def calculate(self, df: pd.DataFrame, period: int = 20, std_dev: float = 2.0, price_column: str = 'close') -> pd.DataFrame: """ Calculate Bollinger Bands. Args: df: DataFrame with OHLCV data period: Number of periods for moving average (default 20) std_dev: Number of standard deviations (default 2.0) price_column: Price column to use ('open', 'high', 'low', 'close') Returns: DataFrame with Bollinger Bands values and metadata, indexed by timestamp """ # Validate input data if not self.validate_dataframe(df, period): return pd.DataFrame() try: df = df.copy() df['middle_band'] = df[price_column].rolling(window=period, min_periods=period).mean() df['std'] = df[price_column].rolling(window=period, min_periods=period).std() df['upper_band'] = df['middle_band'] + (df['std'] * std_dev) df['lower_band'] = df['middle_band'] - (df['std'] * std_dev) # Only keep rows with valid bands, and only 'timestamp', 'upper_band', 'middle_band', 'lower_band' columns result_df = df.loc[df['middle_band'].notna() & df['upper_band'].notna() & df['lower_band'].notna(), ['timestamp', 'upper_band', 'middle_band', 'lower_band']].copy() result_df.set_index('timestamp', inplace=True) return result_df except Exception: return pd.DataFrame()