Back to Community
Long/Short trading strategy w/ ML forecasted stock returns

Current status: Algo has been taken down while we fix an issue with the code!

Hey folks,

We recently released Logitbot's 100/500 ML stock return predictions datafeed into Pipeline and wanted to show you how to use it in an algorithm. This is a version of Logitbot's algorithm that's been ported over to be Q2 and utilizes the pipeline.

This algorithm hosts a few improvements over the original:

  • Limiting the number of stocks held to 200 to control leverage (which is settable)
  • Closing the positions that are held for longer than 5 days whereas the original strategy simply held securities
  • Picking the highest and lowest predicted returns (rather than just ones that pass the threshold)
  • Buying long term bonds when enough stocks don’t fit the criteria
  • Using Logitbot's data through Pipeline with data since 2010 rather than their sample file

For those curious about the data, they've also published a notebook detailing a bit more of their methodology.

Happy coding!

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.

9 responses

thanks. Cant run the algo. I get following error:

Error Runtime exception: UsageNotAllowed: You do not have access to the following dataset(s): logitbot.precog_top_500_free (enable access at https://www.quantopian.com/data/logitbot/precog_top_500 )

when going to the link there is no button to enable access for free dates specified available for free

Joe the Logitbot data is not available for free - it is premium data and you would need to pay for a subscription.

Thanks for the response, however, this is not what is written in the link https://www.quantopian.com/data/logitbot/precog_top_500

it says "Free data availability 01 Jan 2010 - 28 Sep 2016", it just they dont give a way to activate it, seems like bug.

Hey folks, sorry about that - mind giving it a shot now?

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.

You're right Joe, I missed that.

@Seong - I just tested it up until 27th Sep and it worked - thanks.

thanks, now it works. I just re-run the algo as is and the results are very bad and differen from the above. something is wrong with the data . Seong, can you please check ?

Clone Algorithm
7
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
import math
from datetime import datetime
import pandas as pd
import numpy as np
import pytz

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume, SimpleMovingAverage
from quantopian.pipeline.data.logitbot import (
    precog_top_500,
    precog_top_100
)

def initialize(context):
    """
    Called once at the start of the program. Initialize trading parameters.
    """
    # Define context variables that can be accessed in other methods 
    context.min_hold_period = 5
    context.leverage = 1.0
    context.max_longs = 100
    context.max_shorts = 100
    context.max_stocks = 200
    context.std_thresh = .1
    
    # avoid illiquid stocks
    context.min_dollar_volume = 5e6
    context.min_volume        = 1e6

    context.stock_dict = {}
    
    context.bonds = symbols('TLT')

    # Trade daily at 10am
    schedule_function(func=ml_trades,
                      date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_open(hours=0, minutes=30))
    schedule_function(func=buy_bonds, 
                      date_rule=date_rules.every_day())
    # Record tracking variables at the end of each day.
    schedule_function(plot_variables,
                      date_rules.every_day(),
                      time_rules.market_close(minutes=1))

    # Create and attach our pipeline (dynamic stock selector), defined below.
    attach_pipeline(make_pipeline(context), 'ml_example')
    

def buy_bonds(context, data):
    P = context.portfolio    
    market_value = sum(data.current(i, 'price') * abs(P.positions[i].amount) for i in context.portfolio.positions)
    leverage = market_value / max(1, P.portfolio_value)
    gap = max(context.leverage - leverage, 0.0)
    weight = gap / len(context.bonds)
    record(bond_pct=gap*100)
    for bnd in context.bonds:
        if not math.isnan(weight):
            order_target_percent(bnd, weight)


def ml_trades(context,data):
    """
    Buys the stocks with highest and lowest predicted 5 day returns
    Only buys up to 100 stocks in either direction
    """
    today = get_datetime()
    
    # close all positions that have been held > than min_hold_period
    for stock in context.stock_dict.keys():
        if (today - context.stock_dict[stock]).days > context.min_hold_period and data.can_trade(stock):
            order_target_percent(stock, 0)
            del context.stock_dict[stock]
    
    mean = context.universe['pred_5_day_return'].mean() 
    std = context.universe['pred_5_day_return'].std()
            
    buy_thresh = (mean + std) * context.std_thresh
    short_thresh = (mean - std) * context.std_thresh
    
    print ("Buy thresh", buy_thresh)
    print ("Short thresh", short_thresh)

    longs = context.long_predictions.index.tolist()
    shorts = context.short_predictions.index.tolist()
    num_longs = sum(1 for i in context.portfolio.positions if context.portfolio.positions[i].amount > 0)
    
    # buy stocks > buy threshold
    for stock in longs:
        if num_longs > context.max_longs:
            break           
        if stock not in context.portfolio.positions and data.can_trade(stock):
            pred_return = context.predictions[stock]
            if pred_return >  buy_thresh:
                order_target_percent(stock, context.leverage / context.max_stocks)
                num_longs = num_longs + 1
                context.stock_dict[stock] = today
    
    num_shorts = sum(1 for i in context.portfolio.positions if context.portfolio.positions[i].amount < 0)
    
    # buy stocks < sell threshold
    for stock in shorts:
        if num_shorts > context.max_shorts:
            break
        if stock not in context.portfolio.positions and data.can_trade(stock): 
            pred_return = context.predictions[stock]
            if pred_return < short_thresh:
                order_target_percent(stock, -(context.leverage / context.max_stocks))
                num_shorts = num_shorts + 1
                context.stock_dict[stock] = today
    

def plot_variables(context, data):
    record(leverage=context.account.leverage,
           exposure=context.account.net_leverage)


def make_pipeline(context):
    """
    Pipeline which filters based on dollar volume, and captures logitbot 5 day prediction data
    """
    dollar_volume = AverageDollarVolume(window_length=30)
    volume_30 =  SimpleMovingAverage(inputs=[USEquityPricing.volume], window_length=10)
    pred_5_day_return = precog_top_500.predicted_five_day_log_return.latest

    high_dollar_volume = dollar_volume > context.min_dollar_volume
    high_volume = volume_30 > context.min_volume
    
    return Pipeline(columns={
                       "dollar volume": dollar_volume,
                       "volume_30" : volume_30,
                       "pred_5_day_return": pred_5_day_return,
                    },
                    screen=high_dollar_volume & high_volume)


def before_trading_start(context, data):
    """
    Called every day before market open - sorts the predictions into highest and lowest
    for use in ml_trades
    """
    context.universe = pipeline_output('ml_example')
    context.universe = context.universe.dropna()
    context.predictions = context.universe['pred_5_day_return']
    context.long_predictions = context.universe['pred_5_day_return'].sort_values(ascending = False)
    context.short_predictions = context.universe['pred_5_day_return'].sort_values()

    context.security_list = context.universe.index.tolist()
There was a runtime error.

To figure out what secret ML Long-Short part of the algo is making itself, just replace TLT by SHV and compared to 3 month TBill ETF SHY.
Conclusion: Not accounting borrowing costs ML Long-Short part making less then SHY and more volatile.
Do not recommend anybody to use this ($135/month) junk.

Clone Algorithm
6
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
import math
from datetime import datetime
import pandas as pd
import numpy as np
import pytz

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume, SimpleMovingAverage
from quantopian.pipeline.data.logitbot import (
    precog_top_500,
    precog_top_100
)

def initialize(context):
    """
    Called once at the start of the program. Initialize trading parameters.
    """
    # Define context variables that can be accessed in other methods 
    context.min_hold_period = 5
    context.leverage = 1.0
    context.max_longs = 100
    context.max_shorts = 100
    context.max_stocks = 200
    context.std_thresh = .1
    
    # avoid illiquid stocks
    context.min_dollar_volume = 5e6
    context.min_volume        = 1e6

    context.stock_dict = {}
    
    context.bonds = symbols('SHV')

    # Trade daily at 10am
    schedule_function(func=ml_trades,
                      date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_open(hours=0, minutes=30))
    schedule_function(func=buy_bonds, 
                      date_rule=date_rules.every_day())
    # Record tracking variables at the end of each day.
    schedule_function(plot_variables,
                      date_rules.every_day(),
                      time_rules.market_close(minutes=1))

    # Create and attach our pipeline (dynamic stock selector), defined below.
    attach_pipeline(make_pipeline(context), 'ml_example')
    
    set_benchmark(symbol('SHY'))
                  
def buy_bonds(context, data):
    P = context.portfolio    
    market_value = sum(data.current(i, 'price') * abs(P.positions[i].amount) for i in context.portfolio.positions)
    leverage = market_value / max(1, P.portfolio_value)
    gap = max(context.leverage - leverage, 0.0)
    weight = gap / len(context.bonds)
    record(bond_pct=gap)
    for bnd in context.bonds:
        if not math.isnan(weight):
            order_target_percent(bnd, weight)


def ml_trades(context,data):
    """
    Buys the stocks with highest and lowest predicted 5 day returns
    Only buys up to 100 stocks in either direction
    """
    today = get_datetime()
    
    # close all positions that have been held > than min_hold_period
    for stock in context.stock_dict.keys():
        if (today - context.stock_dict[stock]).days > context.min_hold_period and data.can_trade(stock):
            order_target_percent(stock, 0)
            del context.stock_dict[stock]
    
    mean = context.universe['pred_5_day_return'].mean() 
    std = context.universe['pred_5_day_return'].std()
            
    buy_thresh = (mean + std) * context.std_thresh
    short_thresh = (mean - std) * context.std_thresh
    
    print ("Buy thresh", buy_thresh)
    print ("Short thresh", short_thresh)

    longs = context.long_predictions.index.tolist()
    shorts = context.short_predictions.index.tolist()
    num_longs = sum(1 for i in context.portfolio.positions if context.portfolio.positions[i].amount > 0)
    
    # buy stocks > buy threshold
    for stock in longs:
        if num_longs > context.max_longs:
            break           
        if stock not in context.portfolio.positions and data.can_trade(stock):
            pred_return = context.predictions[stock]
            if pred_return >  buy_thresh:
                order_target_percent(stock, context.leverage / context.max_stocks)
                num_longs = num_longs + 1
                context.stock_dict[stock] = today
    
    num_shorts = sum(1 for i in context.portfolio.positions if context.portfolio.positions[i].amount < 0)
    
    # buy stocks < sell threshold
    for stock in shorts:
        if num_shorts > context.max_shorts:
            break
        if stock not in context.portfolio.positions and data.can_trade(stock): 
            pred_return = context.predictions[stock]
            if pred_return < short_thresh:
                order_target_percent(stock, -(context.leverage / context.max_stocks))
                num_shorts = num_shorts + 1
                context.stock_dict[stock] = today
    

def plot_variables(context, data):
    record(leverage=context.account.leverage,
           exposure=context.account.net_leverage)


def make_pipeline(context):
    """
    Pipeline which filters based on dollar volume, and captures logitbot 5 day prediction data
    """
    dollar_volume = AverageDollarVolume(window_length=30)
    volume_30 =  SimpleMovingAverage(inputs=[USEquityPricing.volume], window_length=10)
    pred_5_day_return = precog_top_500.predicted_five_day_log_return.latest

    high_dollar_volume = dollar_volume > context.min_dollar_volume
    high_volume = volume_30 > context.min_volume
    
    return Pipeline(columns={
                       "dollar volume": dollar_volume,
                       "volume_30" : volume_30,
                       "pred_5_day_return": pred_5_day_return,
                    },
                    screen=high_dollar_volume & high_volume)


def before_trading_start(context, data):
    """
    Called every day before market open - sorts the predictions into highest and lowest
    for use in ml_trades
    """
    context.universe = pipeline_output('ml_example')
    context.universe = context.universe.dropna()
    context.predictions = context.universe['pred_5_day_return']
    context.long_predictions = context.universe['pred_5_day_return'].sort_values(ascending = False)
    context.short_predictions = context.universe['pred_5_day_return'].sort_values()

    context.security_list = context.universe.index.tolist()
There was a runtime error.

Vladimir,

Thanks for taking the time to look into it, the logic of the strategy definitely seems to be volatile!

I think our goal with this example strategy, was just that, an example. We fully believe that folks like yourselves are much more capable of building fully fledged algorithms and hope to do so!

Thanks again for your feedback, and if you had thoughts on how to improve the logic of this strategy, we'd love to hear it!!

Seong

@joe - Your performance might be impacted because of the smaller capital size and different backtest time frame

Thanks for all the feedback! Again, this is more of a showcase of how to implement and use the data with pipeline in the IDE. By no means is this a robust trading strategy - just an example of how to incorporate Logitbot's signals.