Back to Community
New Strategy - Combining Sentdex & Bollinger Bands

I would like to share with the Quantopian community an algorithm named “Sentdex coupled to technical factors”.

This non-optimized, long and short strategy looking for stocks in the universe USEquityPricing with high average dollar volume. At a first step, I am using Bollinger Bands strategy coupled with sentdex sentiment score. The strategy aims to look for stocks with a neutral score in order to enter stocks which respect the most technical indicators.

The pipeline outputs longs & shorts in the notebook but when I run it in on the IDE, I have the following error, I couldn't fix it despite many tries.

KeyError: 'longs'
There was a runtime error on line 115.

I'd appreciate your help and feedback on the strategy.

Clone Algorithm
2
Loading...
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
from quantopian.algorithm import order_optimal_portfolio
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data.sentdex import sentiment
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume, BollingerBands
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt

def initialize(context):
    # Schedule our rebalance function to run at the start of
    # each day, when the market opens.
    
    schedule_function(
        my_rebalance,
        date_rules.every_day(),
        time_rules.market_open(minutes=30)
    )

    # Create our pipeline and attach it to our algorithm.
    my_pipe = make_pipeline()
    attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    """SENTIMENT
    r= -1 negative / NOT IMPLEMENTED YET
    r=  0 neutral
    r=  1 positive / NOT IMPLEMENTED YET
    """
    r=0

    #30 DAY Dollar volume average
    dollar_volume=AverageDollarVolume(window_length=30)

    #Get top 5% average volume
    high_dollar_Volume=dollar_volume.percentile_between(95,100)

    # Define a previous simple moving average sentiment factor.
    mean_previous_day = SimpleMovingAverage(inputs=[sentiment.sentiment_signal], window_length=1,mask=high_dollar_Volume)

    # 20 DAY Bollinger Bands
    (Lower_Band,DMA20,Upper_Band)=BollingerBands(inputs=[USEquityPricing.close],window_length=20,k=2)

    #**************************************************************
    #Sentiments scores
    if r==1:
        sentiment_score= mean_previous_day > 5.5
    elif r==-1:
        sentiment_score= mean_previous_day < -2
    elif r==0:

        cond1=mean_previous_day > 1
        cond2=mean_previous_day < 3
        sentiment_score=  cond1 & cond2
        
        longs = USEquityPricing.close.latest < Lower_Band
        shorts= USEquityPricing.close.latest > Upper_Band

        securities_to_trade= sentiment_score & (shorts | longs)
        
    # Add sentiment factor to a pipeline.
    return Pipeline(columns={
        #'mean_sentiment_day': mean_previous_day,
        'Longs': longs,
        'Shorts': shorts,
        #'LowerBand': Lower_Band,
        #'UpperBand': Upper_Band,
        #'Revenue growth %': Rev_growth*100
            },screen=securities_to_trade)

def compute_target_weights(context, data):
    """
    Compute ordering weights.
    """

    # Initialize empty target weights dictionary.
    # This will map securities to their target weight.
    weights = {}

    # If there are securities in our longs and shorts lists,
    # compute even target weights for each security.
    if context.longs and context.shorts:
        long_weight = 0.5 / len(context.longs)
        short_weight = -0.5 / len(context.shorts)
    else:
        return weights

    # Exit positions in our portfolio if they are not
    # in our longs or shorts lists.
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight

    for security in context.shorts:
        weights[security] = short_weight

    return weights

def before_trading_start(context, data):
    """
    Get pipeline results.
    """

    # Gets our pipeline output every day.
    pipe_results = pipeline_output('my_pipeline')
    #print(pipe_results[pipe_results['longs']].index.tolist())
    
    # Go long in securities for which the 'longs' value is True,
    # and check if they can be traded.
    
    context.longs = []
    for sec in pipe_results[pipe_results['longs']].index.tolist():
        if data.can_trade(sec):
            context.longs.append(sec)

    # Go short in securities for which the 'shorts' value is True,
    # and check if they can be traded.
    context.shorts = []
    for sec in pipe_results[pipe_results['shorts']].index.tolist():
        if data.can_trade(sec):
            context.shorts.append(sec)
    
def my_rebalance(context, data):
    """
    Rebalance daily.
    """
    
    # Calculate target weights to rebalance
    target_weights = compute_target_weights(context, data)

    # If we have target weights, rebalance our portfolio
    if target_weights:
        order_optimal_portfolio(
            objective=opt.TargetWeights(target_weights),
            constraints=[],
        )
    
There was a runtime error.
2 responses

Python... it can be unforgiving at times. Especially when it comes to lower and upper case letters. The problem is 'longs' needs to be capitalized. So instead of

    for sec in pipe_results[pipe_results['longs']].index.tolist():

It should be

    for sec in pipe_results[pipe_results['Longs']].index.tolist():

Notice the capitalized 'Longs'. There is also the same problem a few lines after that with 'Shorts'.

    for sec in pipe_results[pipe_results['Shorts']].index.tolist():

However, there is faster way to accomplish this. Use the pandas query method and simply create a list.

    # context.longs = []  
    # no need for the for loop below  
    # just query the dataframe  
    # for sec in pipe_results[pipe_results['longs']].index.tolist():  
    #    if data.can_trade(sec):  
    #        context.longs.append(sec)

    context.longs = pipe_results.query('Longs').index.tolist()

Do something similar for the shorts

    context.shorts = pipe_results.query('Shorts').index.tolist()

Because 'Longs' and 'Shorts' are boolean values, the above is really shorthand for

context.longs = pipe_results.query('Longs == True').index.tolist()  
context.shorts = pipe_results.query('Shorts == True').index.tolist()

I believe the algo runs now.

Clone Algorithm
11
Loading...
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
from quantopian.algorithm import order_optimal_portfolio
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data.sentdex import sentiment
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume, BollingerBands
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt

def initialize(context):
    # Schedule our rebalance function to run at the start of
    # each day, when the market opens.
    
    schedule_function(
        my_rebalance,
        date_rules.every_day(),
        time_rules.market_open(minutes=30)
    )

    # Create our pipeline and attach it to our algorithm.
    my_pipe = make_pipeline()
    attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    """SENTIMENT
    r= -1 negative / NOT IMPLEMENTED YET
    r=  0 neutral
    r=  1 positive / NOT IMPLEMENTED YET
    """
    r=0

    #30 DAY Dollar volume average
    dollar_volume=AverageDollarVolume(window_length=30)

    #Get top 5% average volume
    high_dollar_Volume=dollar_volume.percentile_between(95,100)

    # Define a previous simple moving average sentiment factor.
    mean_previous_day = SimpleMovingAverage(inputs=[sentiment.sentiment_signal], window_length=1,mask=high_dollar_Volume)

    # 20 DAY Bollinger Bands
    (Lower_Band,DMA20,Upper_Band)=BollingerBands(inputs=[USEquityPricing.close],window_length=20,k=2)

    #**************************************************************
    #Sentiments scores
    if r==1:
        sentiment_score= mean_previous_day > 5.5
    elif r==-1:
        sentiment_score= mean_previous_day < -2
    elif r==0:

        cond1=mean_previous_day > 1
        cond2=mean_previous_day < 3
        sentiment_score=  cond1 & cond2
        
        longs = USEquityPricing.close.latest < Lower_Band
        shorts= USEquityPricing.close.latest > Upper_Band

        securities_to_trade= sentiment_score & (shorts | longs)
        
    # Add sentiment factor to a pipeline.
    return Pipeline(columns={
        #'mean_sentiment_day': mean_previous_day,
        'Longs': longs,
        'Shorts': shorts,
        #'LowerBand': Lower_Band,
        #'UpperBand': Upper_Band,
        #'Revenue growth %': Rev_growth*100
            },screen=securities_to_trade)

def compute_target_weights(context, data):
    """
    Compute ordering weights.
    """

    # Initialize empty target weights dictionary.
    # This will map securities to their target weight.
    weights = {}

    # If there are securities in our longs and shorts lists,
    # compute even target weights for each security.
    if context.longs and context.shorts:
        long_weight = 0.5 / len(context.longs)
        short_weight = -0.5 / len(context.shorts)
    else:
        return weights

    # Exit positions in our portfolio if they are not
    # in our longs or shorts lists.
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight

    for security in context.shorts:
        weights[security] = short_weight

    return weights

def before_trading_start(context, data):
    """
    Get pipeline results.
    """

    # Gets our pipeline output every day.
    pipe_results = pipeline_output('my_pipeline')
    #print(pipe_results[pipe_results['longs']].index.tolist())
    
    # Go long in securities for which the 'longs' value is True,
    # and check if they can be traded.
    
    context.longs = []
    # no need for the for loop below
    # just query the dataframe
    #for sec in pipe_results[pipe_results['longs']].index.tolist():
    #    if data.can_trade(sec):
    #        context.longs.append(sec)
    context.longs = pipe_results.query('Longs').index.tolist()

    # Go short in securities for which the 'shorts' value is True,
    # and check if they can be traded.
    context.shorts = []
       
    # no need for the for loop below
    # just query the dataframe
    #for sec in pipe_results[pipe_results['shorts']].index.tolist():
    #    if data.can_trade(sec):
    #        context.shorts.append(sec)
    context.shorts = pipe_results.query('Shorts').index.tolist()

def my_rebalance(context, data):
    """
    Rebalance daily.
    """
    
    # Calculate target weights to rebalance
    target_weights = compute_target_weights(context, data)

    # If we have target weights, rebalance our portfolio
    if target_weights:
        order_optimal_portfolio(
            objective=opt.TargetWeights(target_weights),
            constraints=[],
        )
There was a runtime error.
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.

Silly me! Thank your for such enlightenments and showing me a better way to optimize the algorithm.

I have runned a backtest of the algorithm for the year 2019 and had a poor return comparing to the SPX . My next step would be looking for other factors to test.

Regards,

Clone Algorithm
1
Loading...
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
from quantopian.algorithm import order_optimal_portfolio
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data.sentdex import sentiment
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume, BollingerBands
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt

def initialize(context):
    # Schedule our rebalance function to run at the start of
    # each day, when the market opens.
    set_max_leverage(1.10)
    schedule_function(
        my_rebalance,
        date_rules.every_day(),
        time_rules.market_open(minutes=30)
    )

    # Create our pipeline and attach it to our algorithm.
    my_pipe = make_pipeline()
    attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    """SENTIMENT
    r= -1 negative / NOT IMPLEMENTED YET
    r=  0 neutral
    r=  1 positive / NOT IMPLEMENTED YET
    """
    r=0

    #30 DAY Dollar volume average
    dollar_volume=AverageDollarVolume(window_length=30)

    #Get top 5% average volume
    high_dollar_Volume=dollar_volume.percentile_between(95,100)

    # Define a previous simple moving average sentiment factor.
    mean_previous_day = SimpleMovingAverage(inputs=[sentiment.sentiment_signal], window_length=1,mask=high_dollar_Volume)

    # 20 DAY Bollinger Bands
    (Lower_Band,DMA20,Upper_Band)=BollingerBands(inputs=[USEquityPricing.close],window_length=20,k=2)

    #**************************************************************
    #Sentiments scores
    if r==1:
        sentiment_score= mean_previous_day > 5.5
    elif r==-1:
        sentiment_score= mean_previous_day < -2
    elif r==0:

        cond1=mean_previous_day > 1
        cond2=mean_previous_day < 3
        sentiment_score=  cond1 & cond2
        
        longs = USEquityPricing.close.latest < Lower_Band
        shorts= USEquityPricing.close.latest > Upper_Band

        securities_to_trade= sentiment_score & (shorts | longs)
        
    # Add sentiment factor to a pipeline.
    return Pipeline(columns={
        #'mean_sentiment_day': mean_previous_day,
        'Longs': longs,
        'Shorts': shorts,
        #'LowerBand': Lower_Band,
        #'UpperBand': Upper_Band,
        #'Revenue growth %': Rev_growth*100
            },screen=securities_to_trade)

def compute_target_weights(context, data):
    """
    Compute ordering weights.
    """

    # Initialize empty target weights dictionary.
    # This will map securities to their target weight.
    weights = {}

    # If there are securities in our longs and shorts lists,
    # compute even target weights for each security.
    if context.longs and context.shorts:
        long_weight = 0.5 / len(context.longs)
        short_weight = -0.5 / len(context.shorts)
    else:
        return weights

    # Exit positions in our portfolio if they are not
    # in our longs or shorts lists.
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight

    for security in context.shorts:
        weights[security] = short_weight

    return weights

def before_trading_start(context, data):
    """
    Get pipeline results.
    """

    # Gets our pipeline output every day.
    pipe_results = pipeline_output('my_pipeline')
    #print(pipe_results[pipe_results['longs']].index.tolist())
    
    # Go long in securities for which the 'longs' value is True,
    # and check if they can be traded.
    
    context.longs = []
    context.longs = pipe_results.query('Longs').index.tolist()

    # Go short in securities for which the 'shorts' value is True,
    # and check if they can be traded.
    context.shorts = []
    context.shorts = pipe_results.query('Shorts').index.tolist()

def my_rebalance(context, data):
    """
    Rebalance daily.
    """
    
    # Calculate target weights to rebalance
    target_weights = compute_target_weights(context, data)

    # If we have target weights, rebalance our portfolio
    if target_weights:
        order_optimal_portfolio(
            objective=opt.TargetWeights(target_weights),
            constraints=[],
        )
There was a runtime error.