702 lines
24 KiB
Markdown
702 lines
24 KiB
Markdown
# 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.* |