# Bollinger Bands Indicators ## Overview Bollinger Bands are volatility indicators that consist of a moving average (middle band) and two standard deviation bands (upper and lower bands). They help identify overbought/oversold conditions and potential breakout opportunities. IncrementalTrader provides both simple price-based and OHLC-based implementations. ## BollingerBandsState Standard Bollinger Bands implementation using closing prices and simple moving average. ### Features - **Three Bands**: Upper, middle (SMA), and lower bands - **Volatility Measurement**: Bands expand/contract with volatility - **Mean Reversion Signals**: Price touching bands indicates potential reversal - **Breakout Detection**: Price breaking through bands signals trend continuation ### Mathematical Formula ``` Middle Band = Simple Moving Average (SMA) Upper Band = SMA + (Standard Deviation × Multiplier) Lower Band = SMA - (Standard Deviation × Multiplier) Standard Deviation = √(Σ(Price - SMA)² / Period) ``` ### Class Definition ```python from IncrementalTrader.strategies.indicators import BollingerBandsState class BollingerBandsState(IndicatorState): def __init__(self, period: int, std_dev_multiplier: float = 2.0): super().__init__(period) self.std_dev_multiplier = std_dev_multiplier self.values = [] self.sum = 0.0 self.sum_squares = 0.0 # Band values self.middle_band = 0.0 self.upper_band = 0.0 self.lower_band = 0.0 def update(self, value: float): self.values.append(value) self.sum += value self.sum_squares += value * value if len(self.values) > self.period: old_value = self.values.pop(0) self.sum -= old_value self.sum_squares -= old_value * old_value self.data_count += 1 self._calculate_bands() def _calculate_bands(self): if not self.is_ready(): return n = len(self.values) # Calculate SMA (middle band) self.middle_band = self.sum / n # Calculate standard deviation variance = (self.sum_squares / n) - (self.middle_band * self.middle_band) std_dev = math.sqrt(max(variance, 0)) # Calculate upper and lower bands band_width = std_dev * self.std_dev_multiplier self.upper_band = self.middle_band + band_width self.lower_band = self.middle_band - band_width def get_value(self) -> float: """Returns middle band (SMA) value.""" return self.middle_band def get_upper_band(self) -> float: return self.upper_band def get_lower_band(self) -> float: return self.lower_band def get_middle_band(self) -> float: return self.middle_band def get_band_width(self) -> float: """Get the width between upper and lower bands.""" return self.upper_band - self.lower_band def get_percent_b(self, price: float) -> float: """Calculate %B: position of price within the bands.""" if self.get_band_width() == 0: return 0.5 return (price - self.lower_band) / self.get_band_width() ``` ### Usage Examples #### Basic Bollinger Bands Usage ```python # Create 20-period Bollinger Bands with 2.0 standard deviation bb = BollingerBandsState(period=20, std_dev_multiplier=2.0) # Price data prices = [100, 101, 99, 102, 98, 103, 97, 104, 96, 105, 95, 106, 94, 107, 93] for price in prices: bb.update(price) if bb.is_ready(): print(f"Price: {price:.2f}") print(f" Upper: {bb.get_upper_band():.2f}") print(f" Middle: {bb.get_middle_band():.2f}") print(f" Lower: {bb.get_lower_band():.2f}") print(f" %B: {bb.get_percent_b(price):.2f}") print(f" Width: {bb.get_band_width():.2f}") ``` #### Bollinger Bands Trading Signals ```python class BollingerBandsSignals: def __init__(self, period: int = 20, std_dev: float = 2.0): self.bb = BollingerBandsState(period, std_dev) self.previous_price = None self.previous_percent_b = None def update(self, price: float): self.bb.update(price) self.previous_price = price def get_mean_reversion_signal(self, current_price: float) -> str: """Get mean reversion signals based on band touches.""" if not self.bb.is_ready(): return "HOLD" percent_b = self.bb.get_percent_b(current_price) # Oversold: price near or below lower band if percent_b <= 0.1: return "BUY" # Overbought: price near or above upper band elif percent_b >= 0.9: return "SELL" # Return to middle: exit positions elif 0.4 <= percent_b <= 0.6: return "EXIT" return "HOLD" def get_breakout_signal(self, current_price: float) -> str: """Get breakout signals based on band penetration.""" if not self.bb.is_ready() or self.previous_price is None: return "HOLD" upper_band = self.bb.get_upper_band() lower_band = self.bb.get_lower_band() # Bullish breakout: price breaks above upper band if self.previous_price <= upper_band and current_price > upper_band: return "BUY_BREAKOUT" # Bearish breakout: price breaks below lower band elif self.previous_price >= lower_band and current_price < lower_band: return "SELL_BREAKOUT" return "HOLD" def get_squeeze_condition(self) -> bool: """Detect Bollinger Band squeeze (low volatility).""" if not self.bb.is_ready(): return False # Simple squeeze detection: band width below threshold # You might want to compare with historical band width band_width = self.bb.get_band_width() middle_band = self.bb.get_middle_band() # Squeeze when band width is less than 4% of middle band return (band_width / middle_band) < 0.04 # Usage bb_signals = BollingerBandsSignals(period=20, std_dev=2.0) for price in prices: bb_signals.update(price) mean_reversion = bb_signals.get_mean_reversion_signal(price) breakout = bb_signals.get_breakout_signal(price) squeeze = bb_signals.get_squeeze_condition() if mean_reversion != "HOLD": print(f"Mean Reversion Signal: {mean_reversion} at {price:.2f}") if breakout != "HOLD": print(f"Breakout Signal: {breakout} at {price:.2f}") if squeeze: print(f"Bollinger Band Squeeze detected at {price:.2f}") ``` ### Performance Characteristics - **Time Complexity**: O(1) per update (after initial period) - **Space Complexity**: O(period) - **Memory Usage**: ~8 bytes per period + constant overhead ## BollingerBandsOHLCState OHLC-based Bollinger Bands implementation using typical price (HLC/3) for more accurate volatility measurement. ### Features - **OHLC Data Support**: Uses high, low, close for typical price calculation - **Better Volatility Measurement**: More accurate than close-only bands - **Intraday Analysis**: Accounts for intraday price action - **Enhanced Signals**: More reliable signals due to complete price information ### Mathematical Formula ``` Typical Price = (High + Low + Close) / 3 Middle Band = SMA(Typical Price) Upper Band = Middle Band + (Standard Deviation × Multiplier) Lower Band = Middle Band - (Standard Deviation × Multiplier) ``` ### Class Definition ```python class BollingerBandsOHLCState(OHLCIndicatorState): def __init__(self, period: int, std_dev_multiplier: float = 2.0): super().__init__(period) self.std_dev_multiplier = std_dev_multiplier self.typical_prices = [] self.sum = 0.0 self.sum_squares = 0.0 # Band values self.middle_band = 0.0 self.upper_band = 0.0 self.lower_band = 0.0 def _process_ohlc_data(self, high: float, low: float, close: float): # Calculate typical price typical_price = (high + low + close) / 3.0 self.typical_prices.append(typical_price) self.sum += typical_price self.sum_squares += typical_price * typical_price if len(self.typical_prices) > self.period: old_price = self.typical_prices.pop(0) self.sum -= old_price self.sum_squares -= old_price * old_price self._calculate_bands() def _calculate_bands(self): if not self.is_ready(): return n = len(self.typical_prices) # Calculate SMA (middle band) self.middle_band = self.sum / n # Calculate standard deviation variance = (self.sum_squares / n) - (self.middle_band * self.middle_band) std_dev = math.sqrt(max(variance, 0)) # Calculate upper and lower bands band_width = std_dev * self.std_dev_multiplier self.upper_band = self.middle_band + band_width self.lower_band = self.middle_band - band_width def get_value(self) -> float: """Returns middle band (SMA) value.""" return self.middle_band def get_upper_band(self) -> float: return self.upper_band def get_lower_band(self) -> float: return self.lower_band def get_middle_band(self) -> float: return self.middle_band def get_band_width(self) -> float: return self.upper_band - self.lower_band def get_percent_b_ohlc(self, high: float, low: float, close: float) -> float: """Calculate %B using OHLC data.""" typical_price = (high + low + close) / 3.0 if self.get_band_width() == 0: return 0.5 return (typical_price - self.lower_band) / self.get_band_width() ``` ### Usage Examples #### OHLC Bollinger Bands Analysis ```python # Create OHLC-based Bollinger Bands bb_ohlc = BollingerBandsOHLCState(period=20, std_dev_multiplier=2.0) # OHLC data: (high, low, close) ohlc_data = [ (105.0, 102.0, 104.0), (106.0, 103.0, 105.5), (107.0, 104.0, 106.0), (108.0, 105.0, 107.5), (109.0, 106.0, 108.0) ] for high, low, close in ohlc_data: bb_ohlc.update_ohlc(high, low, close) if bb_ohlc.is_ready(): typical_price = (high + low + close) / 3.0 percent_b = bb_ohlc.get_percent_b_ohlc(high, low, close) print(f"OHLC: H={high:.2f}, L={low:.2f}, C={close:.2f}") print(f" Typical Price: {typical_price:.2f}") print(f" Upper: {bb_ohlc.get_upper_band():.2f}") print(f" Middle: {bb_ohlc.get_middle_band():.2f}") print(f" Lower: {bb_ohlc.get_lower_band():.2f}") print(f" %B: {percent_b:.2f}") ``` #### Advanced OHLC Bollinger Bands Strategy ```python class OHLCBollingerStrategy: def __init__(self, period: int = 20, std_dev: float = 2.0): self.bb = BollingerBandsOHLCState(period, std_dev) self.previous_ohlc = None def update(self, high: float, low: float, close: float): self.bb.update_ohlc(high, low, close) self.previous_ohlc = (high, low, close) def analyze_candle_position(self, high: float, low: float, close: float) -> dict: """Analyze candle position relative to Bollinger Bands.""" if not self.bb.is_ready(): return {"analysis": "NOT_READY"} upper_band = self.bb.get_upper_band() lower_band = self.bb.get_lower_band() middle_band = self.bb.get_middle_band() # Analyze different price levels analysis = { "high_above_upper": high > upper_band, "low_below_lower": low < lower_band, "close_above_middle": close > middle_band, "body_outside_bands": high > upper_band and low < lower_band, "squeeze_breakout": False, "signal": "HOLD" } # Detect squeeze breakout band_width = self.bb.get_band_width() if band_width / middle_band < 0.03: # Very narrow bands if high > upper_band: analysis["squeeze_breakout"] = True analysis["signal"] = "BUY_BREAKOUT" elif low < lower_band: analysis["squeeze_breakout"] = True analysis["signal"] = "SELL_BREAKOUT" # Mean reversion signals percent_b = self.bb.get_percent_b_ohlc(high, low, close) if percent_b <= 0.1 and close > low: # Bounce from lower band analysis["signal"] = "BUY_BOUNCE" elif percent_b >= 0.9 and close < high: # Rejection from upper band analysis["signal"] = "SELL_REJECTION" return analysis def get_support_resistance_levels(self) -> dict: """Get dynamic support and resistance levels.""" if not self.bb.is_ready(): return {} return { "resistance": self.bb.get_upper_band(), "support": self.bb.get_lower_band(), "pivot": self.bb.get_middle_band(), "band_width": self.bb.get_band_width() } # Usage ohlc_strategy = OHLCBollingerStrategy(period=20, std_dev=2.0) for high, low, close in ohlc_data: ohlc_strategy.update(high, low, close) analysis = ohlc_strategy.analyze_candle_position(high, low, close) levels = ohlc_strategy.get_support_resistance_levels() if analysis.get("signal") != "HOLD": print(f"Signal: {analysis['signal']}") print(f"Analysis: {analysis}") print(f"S/R Levels: {levels}") ``` ### Performance Characteristics - **Time Complexity**: O(1) per update (after initial period) - **Space Complexity**: O(period) - **Memory Usage**: ~8 bytes per period + constant overhead ## Comparison: BollingerBandsState vs BollingerBandsOHLCState | Aspect | BollingerBandsState | BollingerBandsOHLCState | |--------|---------------------|-------------------------| | **Input Data** | Close prices only | High, Low, Close | | **Calculation Base** | Close price | Typical price (HLC/3) | | **Accuracy** | Good for trends | Better for volatility | | **Signal Quality** | Standard | Enhanced | | **Data Requirements** | Minimal | Complete OHLC | ### When to Use BollingerBandsState - **Simple Analysis**: When only closing prices are available - **Trend Following**: For basic trend and mean reversion analysis - **Memory Efficiency**: When OHLC data is not necessary - **Quick Implementation**: For rapid prototyping and testing ### When to Use BollingerBandsOHLCState - **Complete Analysis**: When full OHLC data is available - **Volatility Trading**: For more accurate volatility measurement - **Intraday Trading**: When intraday price action matters - **Professional Trading**: For more sophisticated trading strategies ## Advanced Usage Patterns ### Multi-Timeframe Bollinger Bands ```python class MultiBollingerBands: def __init__(self): self.bb_short = BollingerBandsState(period=10, std_dev_multiplier=2.0) self.bb_medium = BollingerBandsState(period=20, std_dev_multiplier=2.0) self.bb_long = BollingerBandsState(period=50, std_dev_multiplier=2.0) def update(self, price: float): self.bb_short.update(price) self.bb_medium.update(price) self.bb_long.update(price) def get_volatility_regime(self) -> str: """Determine volatility regime across timeframes.""" if not all([self.bb_short.is_ready(), self.bb_medium.is_ready(), self.bb_long.is_ready()]): return "UNKNOWN" # Compare band widths short_width = self.bb_short.get_band_width() / self.bb_short.get_middle_band() medium_width = self.bb_medium.get_band_width() / self.bb_medium.get_middle_band() long_width = self.bb_long.get_band_width() / self.bb_long.get_middle_band() avg_width = (short_width + medium_width + long_width) / 3 if avg_width > 0.08: return "HIGH_VOLATILITY" elif avg_width < 0.03: return "LOW_VOLATILITY" else: return "NORMAL_VOLATILITY" def get_trend_alignment(self, price: float) -> str: """Check trend alignment across timeframes.""" if not all([self.bb_short.is_ready(), self.bb_medium.is_ready(), self.bb_long.is_ready()]): return "UNKNOWN" # Check position relative to middle bands above_short = price > self.bb_short.get_middle_band() above_medium = price > self.bb_medium.get_middle_band() above_long = price > self.bb_long.get_middle_band() if all([above_short, above_medium, above_long]): return "STRONG_BULLISH" elif not any([above_short, above_medium, above_long]): return "STRONG_BEARISH" elif above_short and above_medium: return "BULLISH" elif not above_short and not above_medium: return "BEARISH" else: return "MIXED" # Usage multi_bb = MultiBollingerBands() for price in prices: multi_bb.update(price) volatility_regime = multi_bb.get_volatility_regime() trend_alignment = multi_bb.get_trend_alignment(price) print(f"Price: {price:.2f}, Volatility: {volatility_regime}, Trend: {trend_alignment}") ``` ### Bollinger Bands with RSI Confluence ```python class BollingerRSIStrategy: def __init__(self, bb_period: int = 20, rsi_period: int = 14): self.bb = BollingerBandsState(bb_period, 2.0) self.rsi = SimpleRSIState(rsi_period) def update(self, price: float): self.bb.update(price) self.rsi.update(price) def get_confluence_signal(self, price: float) -> dict: """Get signals based on Bollinger Bands and RSI confluence.""" if not (self.bb.is_ready() and self.rsi.is_ready()): return {"signal": "HOLD", "confidence": 0.0} percent_b = self.bb.get_percent_b(price) rsi_value = self.rsi.get_value() # Bullish confluence: oversold RSI + lower band touch if percent_b <= 0.1 and rsi_value <= 30: confidence = min(0.9, (30 - rsi_value) / 20 + (0.1 - percent_b) * 5) return { "signal": "BUY", "confidence": confidence, "reason": "oversold_confluence", "percent_b": percent_b, "rsi": rsi_value } # Bearish confluence: overbought RSI + upper band touch elif percent_b >= 0.9 and rsi_value >= 70: confidence = min(0.9, (rsi_value - 70) / 20 + (percent_b - 0.9) * 5) return { "signal": "SELL", "confidence": confidence, "reason": "overbought_confluence", "percent_b": percent_b, "rsi": rsi_value } # Exit signals: return to middle elif 0.4 <= percent_b <= 0.6 and 40 <= rsi_value <= 60: return { "signal": "EXIT", "confidence": 0.5, "reason": "return_to_neutral", "percent_b": percent_b, "rsi": rsi_value } return {"signal": "HOLD", "confidence": 0.0} # Usage bb_rsi_strategy = BollingerRSIStrategy(bb_period=20, rsi_period=14) for price in prices: bb_rsi_strategy.update(price) signal_info = bb_rsi_strategy.get_confluence_signal(price) if signal_info["signal"] != "HOLD": print(f"Confluence Signal: {signal_info['signal']}") print(f" Confidence: {signal_info['confidence']:.2f}") print(f" Reason: {signal_info['reason']}") print(f" %B: {signal_info.get('percent_b', 0):.2f}") print(f" RSI: {signal_info.get('rsi', 0):.1f}") ``` ## Integration with Strategies ### Bollinger Bands Mean Reversion Strategy ```python class BollingerMeanReversionStrategy(IncStrategyBase): def __init__(self, name: str, params: dict = None): super().__init__(name, params) # Initialize Bollinger Bands bb_period = self.params.get('bb_period', 20) bb_std_dev = self.params.get('bb_std_dev', 2.0) self.bb = BollingerBandsOHLCState(bb_period, bb_std_dev) # Strategy parameters self.entry_threshold = self.params.get('entry_threshold', 0.1) # %B threshold self.exit_threshold = self.params.get('exit_threshold', 0.5) # Return to middle # State tracking self.position_type = None def _process_aggregated_data(self, timestamp: int, ohlcv: tuple) -> IncStrategySignal: open_price, high, low, close, volume = ohlcv # Update Bollinger Bands self.bb.update_ohlc(high, low, close) # Wait for indicator to be ready if not self.bb.is_ready(): return IncStrategySignal.HOLD() # Calculate %B percent_b = self.bb.get_percent_b_ohlc(high, low, close) band_width = self.bb.get_band_width() middle_band = self.bb.get_middle_band() # Entry signals if percent_b <= self.entry_threshold and self.position_type != "LONG": # Oversold condition - buy signal confidence = min(0.9, (self.entry_threshold - percent_b) * 5) self.position_type = "LONG" return IncStrategySignal.BUY( confidence=confidence, metadata={ 'percent_b': percent_b, 'band_width': band_width, 'signal_type': 'mean_reversion_buy', 'upper_band': self.bb.get_upper_band(), 'lower_band': self.bb.get_lower_band() } ) elif percent_b >= (1.0 - self.entry_threshold) and self.position_type != "SHORT": # Overbought condition - sell signal confidence = min(0.9, (percent_b - (1.0 - self.entry_threshold)) * 5) self.position_type = "SHORT" return IncStrategySignal.SELL( confidence=confidence, metadata={ 'percent_b': percent_b, 'band_width': band_width, 'signal_type': 'mean_reversion_sell', 'upper_band': self.bb.get_upper_band(), 'lower_band': self.bb.get_lower_band() } ) # Exit signals elif abs(percent_b - 0.5) <= (0.5 - self.exit_threshold): # Return to middle - exit position if self.position_type is not None: exit_signal = IncStrategySignal.SELL() if self.position_type == "LONG" else IncStrategySignal.BUY() exit_signal.confidence = 0.6 exit_signal.metadata = { 'percent_b': percent_b, 'signal_type': 'mean_reversion_exit', 'previous_position': self.position_type } self.position_type = None return exit_signal return IncStrategySignal.HOLD() ``` ## Performance Optimization Tips ### 1. Choose the Right Implementation ```python # For simple price analysis bb = BollingerBandsState(period=20, std_dev_multiplier=2.0) # For comprehensive OHLC analysis bb_ohlc = BollingerBandsOHLCState(period=20, std_dev_multiplier=2.0) ``` ### 2. Optimize Standard Deviation Calculation ```python # Use incremental variance calculation for better performance def incremental_variance(sum_val: float, sum_squares: float, count: int, mean: float) -> float: """Calculate variance incrementally.""" if count == 0: return 0.0 return max(0.0, (sum_squares / count) - (mean * mean)) ``` ### 3. Cache Band Values for Multiple Calculations ```python class CachedBollingerBands: def __init__(self, period: int, std_dev: float = 2.0): self.bb = BollingerBandsState(period, std_dev) self._cached_bands = None self._cache_valid = False def update(self, price: float): self.bb.update(price) self._cache_valid = False def get_bands(self) -> tuple: if not self._cache_valid: self._cached_bands = ( self.bb.get_upper_band(), self.bb.get_middle_band(), self.bb.get_lower_band() ) self._cache_valid = True return self._cached_bands ``` --- *Bollinger Bands are versatile indicators for volatility analysis and mean reversion trading. Use BollingerBandsState for simple price analysis or BollingerBandsOHLCState for comprehensive volatility measurement with complete OHLC data.*