Problem Description
Solution
To add a Bollinger Band indicator in QuantConnect using the Python API, you use the self.bb helper method within the initialize function.
Here is a complete, runnable example of a Mean Reversion strategy using Bollinger Bands.
Bollinger Band Strategy Implementation
# region imports
from AlgorithmImports import *
# endregion
class BollingerBandAlgorithm(QCAlgorithm):
def initialize(self):
# 1. Set Setup parameters
self.set_start_date(2023, 1, 1)
self.set_end_date(2024, 1, 1)
self.set_cash(100000)
# 2. Add Security
self.symbol = self.add_equity("SPY", Resolution.DAILY).symbol
# 3. Create the Bollinger Band Indicator
# Parameters: Symbol, Period, k (StdDev multiplier), MovingAverageType, Resolution
self.bollinger = self.bb(self.symbol, 20, 2, MovingAverageType.SIMPLE, Resolution.DAILY)
# 4. Set WarmUp to ensure indicator is ready immediately upon start
self.set_warm_up(20)
def on_data(self, data: Slice):
# Ensure data exists for the symbol and indicator is ready
if not self.bollinger.is_ready or self.symbol not in data:
return
# 5. Access Indicator Values
# The main .current.value is the Middle Band (Moving Average)
middle_band = self.bollinger.current.value
# Access Upper and Lower bands specifically
upper_band = self.bollinger.upper_band.current.value
lower_band = self.bollinger.lower_band.current.value
current_price = self.securities[self.symbol].price
# 6. Trading Logic (Mean Reversion)
# If not invested, check for entry signals
if not self.portfolio.invested:
# Buy if price drops below the lower band (oversold)
if current_price < lower_band:
self.set_holdings(self.symbol, 1.0)
self.debug(f"Buy at {current_price} | Lower Band: {lower_band}")
# If invested, check for exit signals
else:
# Sell if price rises above the upper band (overbought)
if current_price > upper_band:
self.liquidate(self.symbol)
self.debug(f"Sell at {current_price} | Upper Band: {upper_band}")
# Optional: Plotting for visual debugging
self.plot("Trade Plot", "Price", current_price)
self.plot("Trade Plot", "Upper", upper_band)
self.plot("Trade Plot", "Lower", lower_band)
Key Implementation Details
-
Creation (
self.bb):
The method signature isself.bb(symbol, period, k, moving_average_type, resolution).period: The lookback period for the moving average (standard is 20).k: The number of standard deviations for the bands (standard is 2).moving_average_type: UsuallyMovingAverageType.SIMPLEorMovingAverageType.EXPONENTIAL.
-
Accessing Values:
The Bollinger Band object is a composite indicator.self.bollinger.current.value: Returns the Middle Band value.self.bollinger.upper_band.current.value: Returns the Upper Band value.self.bollinger.lower_band.current.value: Returns the Lower Band value.
-
Warm Up:
Usingself.set_warm_up(20)ensures that the algorithm processes historical data before the start date so the indicator is ready to use immediately on the first day of the backtest.
Q&A: Common Questions about Bollinger Bands in QuantConnect
Q: How do I use an Exponential Moving Average (EMA) for the middle band instead of Simple?
A: Change the MovingAverageType parameter in the initialization:
self.bollinger = self.bb(self.symbol, 20, 2, MovingAverageType.EXPONENTIAL, Resolution.DAILY)
Q: How can I get historical values of the Bollinger Bands (e.g., yesterday's value)?
A: You can use the Window functionality of indicators.
- In
initialize:self.bollinger.window.size = 2 - In
on_data: Access yesterday's upper band viaself.bollinger.upper_band[1]. Note that index0is current, and1is the previous bar.
Q: Can I manually update the indicator with data from a different source?
A: Yes. If you are not using the helper self.bb method (which auto-updates), you can instantiate the class BollingerBands manually and call self.bollinger.update(time, price) inside on_data. However, the helper method used in the example above is recommended for standard equity/forex data.
Q: Why is self.bollinger.is_ready returning False?
A: The indicator requires at least period amount of data points to calculate the first value. If you set the period to 20, the indicator will not be ready until it has received 20 bars of data. Using self.set_warm_up(20) resolves this for the start of the simulation.