Back to Community
What does the Class "BollingerBands()" return?

I understand how the Bollinger Bands indicator works, but I am having trouble implementing it. This problem is common for me when looking at most the API documentation. There is just no info on what anything returns. It shows inputs and parameters, but beyond that, nothing. When I look over the examples, they often use different ways to get the same things. Such as moving averages, and creating a pipeline. I am just really confused.

I want to know how to get the upper and lower bands. I can kinda understand what values to pass it, but beyond that, I am totally lost.

My goal is to buy when the price goes above the bands, and sell when it goes below. Then pass it as a screen to the pipeline to only gets stocks that meet that parameter.

Thanks.

5 responses

I now realize that it's a factor for use with the pipeline. But I would still like to know how to implement it into the pipeline.

This is my base bollinger band so far. Im having trouble optimizing it for actual trading times. If you want i can collaborate the algo with you, and we can see how to set it up. It works in the long run but places many bids that just won't work. I have also removed shorting from the equation because It looses big or wins big on it, and has a very High draw down

Clone Algorithm
8
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
"""
This is a template algorithm on Quantopian for you to adapt and fill in.
"""
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume
from quantopian.pipeline.filters.morningstar import Q500US
from quantopian.pipeline.data.psychsignal import stocktwits
import talib
import numpy as np
import pandas as pd
 
def initialize(context):
    """
    Called once at the start of the algorithm.
    """   
    context.test = context.tlt = sid(14848) #bond etf #the 
    schedule_function(rebalance, date_rules.every_day(), time_rules.market_open())
    
    #sma_10 = s(inputs=[context.test], window_length=4)
    # Create our dynamic stock selector.
    
    attach_pipeline(make_pipeline(), 'my_pipeline')
    
                 
def make_pipeline():
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """
    
    # Base universe set to the Q500US
    base_universe = Q500US()

    # Factor of yesterday's close price.
    yesterday_close = USEquityPricing.close.latest
     
    pipe = Pipeline(
        screen = base_universe,
        columns = {
            'close': yesterday_close,
        }
    )
    return pipe
 
def before_trading_start(context, data):
    """
    Called every day before market open.
    """
    context.output = pipeline_output('my_pipeline')
  
    # These are the securities that we are interested in trading each day.
    context.security_list = context.output.index
     
def my_assign_weights(context, data):
    """
    Assign weights to securities that we want to order.
    """
    pass
 
# Rebalance daily.

def rebalance(context, data):'''
    current_position = context.portfolio.positions[context.test].amount
    price=data.current(context.test, 'price')
    
    # Load historical data for the stocks
    prices = data.history(context.test, 'price', 15, '1d')
    
    upper, middle, lower = talib.BBANDS(
        prices, 
        timeperiod=5,
        # number of non-biased standard deviations from the mean
        nbdevup=1,
        nbdevdn=1,
        # Moving average type: weighted moving average here
        matype=2)
    
    # If price is below the recent lower band and we have
    # no long positions then invest the entire
    # portfolio value into SPY
    if price <= lower[-1] and current_position >= 0 and data.can_trade(context.test):
        order_target_percent(context.test, 1.0)
    
    # If price is above the recent upper band and we have
    # no short positions then invest the entire
    # portfolio value to short SPY
    elif price >= upper[-1] and current_position <= 0 and data.can_trade(context.test):
        order_target_percent(context.test, -1.0)
        
    record(upper=upper[-1],
           lower=lower[-1],
           mean=middle[-1],
           price=price,
           position_size=current_position)
    return upper, lower, price '''

def my_record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    # Create Variables to Be plotted
     
    record(upper,lower,price)
    pass
 
def handle_data(context,data):
    """
    Called every minute.
    """
    current_position = context.portfolio.positions[context.test].amount
    price=data.current(context.test, 'price')
    
    # Load historical data for the stocks
    prices = data.history(context.test, 'price', 15, '1d')
 

    upper, middle, lower = talib.BBANDS(
        prices, 
        timeperiod=12, # using 9-12 b/c bollinger reccomended it. 
        # number of non-biased standard deviations from the mean
        nbdevup=1,
        nbdevdn=1,
        # Moving average type: weighted moving average here
        matype=1)
    #needs to be adjusted
    
    
    
    # If price is below the recent lower band and we have
    # no long positions then invest the entire
    # portfolio value into SPY
    if price < lower[-1] and current_position >= 0 and data.can_trade(context.test):
        order_target_percent(context.test, 1) #set position to buy 100%
        log.info("buying %s" % price)
        
            
    # If price is above the recent upper band and we have
    # no short positions then invest the entire
    # portfolio value to short SPY
    #"""and current_position"""<= 0'
    elif price >= upper[-1]: #and data.can_trade(context.test):
        order_target_percent(context.test, -1) #change the position to sell 100%
        log.info("Selling %s" % price)
        
    record(upper = upper[-1], #dispay upper band on chart
           lower = lower[-1],
           middle = middle[-1],
           price = price,
           )
    '''record(position_size= current_position
           )'''
    pass
There was a runtime error.

Aamir, thanks for sharing that. It's something concrete for people to work from.

It looks like you create a pipeline of the Q500US, but you never actually use it - you only look at context.test. So first off, I think you need to resolve your intent for your trading universe.

My next recommendation is to move most of the code you have in handle_data() into rebalance(). handle_data() runs every minute, but the data.history() call you use to create your bands is using daily data - so you're recomputing the same thing every minute. That will speed up your backtest a lot.

Then, I think you need to re-think your ordering logic. It looks like you have two different situations when you open positions, but you don't have any logic to close positions.

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

I didn't really consider much of the pipeline code, I just copied it from the API.
I followed your suggestions and moved everything to the Handle data section. I want to eventually use this for intraday trading.

so my logic to buy is:
if price < lower[-1] and current_position >= 0:

my logic to sell should be: price >= middle[-1] or you could hold till the upper band.

and my price to short should be price > upper[-1]

I thought about integrating RSI calculations into the algorithm. But I don't know if it would be effective.
On the backtest I attached.
Ideally I'd want it to run intra day instead of day end, but for now I will just use handle data

if price < lower[-1]: #and current_position >= 0: #and rsi[0] < context.LOW_RSI: #data.can_trade(context.test) and '  
        order_target_percent(context.test, 1) #set position to buy 100%  
        log.info("buying %s" % price)  

    # If price is above the recent upper band and we have  
    # no short positions then invest the entire  
    # portfolio value to short SPY  
    #and current_position<= 0'  
    if price >= middle[-1]:# and data.can_trade(context.test):  
        order_target_percent(context.test, 0) #change the position to sell 100%  
        log.info("Selling %s" % price)  
    if price >= upper[-1]:# and data.can_trade(context.test):  
        order_target_percent(context.test, -1) #change the position to short 100%  
        log.info("Shorting %s" % price)  

Hi @sean, the BollingerBands class is what we call a multi-output factor, meaning its output is accessed slightly differently than ordinary pipeline factors.

To create the factor, it requires a window_length parameter (the length of the lookback window over which to compute the bollinger bands) and a k parameter (the number of standard deviations to add or subtract to create the upper and lower bands). By default it uses close price as its input. Once we create an instance of the factor we can extract the lower, middle and upper bands as individual factors like so:

def initialize(context):  
    bollinger_bands = BollingerBands(window_length=20, k=2)

    # Access each band as an attribute on our bollinger_bands instance.  
    # Here, `lower_band` and `upper_band` are themselves pipeline factors.  
    lower_band = bollinger_bands.lower  
    upper_band = bollinger_bands.upper

    # Add an optional screen.  
    screen = (USEquityPricing.close > upper_band) | (USEquityPricing.close < lower_band)

    # Add the upper and lower bands to our pipeline.  
    pipe = Pipeline(  
        columns = {  
            'lower': lower_band,  
            'upper': upper_band,  
        },  
        screen=screen,  
    )  
    attach_pipeline(pipe, 'my_pipeline')  

In this example, the bollinger_bands instance is not actually meant to be added to a pipeline as a column. Rather, it's better thought of as a way to consolidate related outputs. I agree that this information should be noted in more detail in our help docs on the BollingerBands built-in factor; I've made a note of it for us to work on. For more information on multi-output factors and examples, check out these posts:
https://www.quantopian.com/posts/new-feature-multiple-output-pipeline-custom-factors
https://www.quantopian.com/posts/new-correlation-and-linear-regression-factors
and this section of the help docs:
https://www.quantopian.com/help#quantopian_pipeline_CustomFactor

Hope this helps,
David

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.