2025-05-28 22:37:53 +08:00

702 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.*