Back to Community
Help with S&P "momentum" strategy!

Hey there, I am very new to programming but am tasked with backtesting a very simple strategy right now that I think Quantopian will help with rather than spending hours with Excel. Basically the strategy goes like this: On January 2nd, 2019, take the top 50 performing stocks in the S&P 500 from 2018 (by percentage gain) and allocate an equal amount of each in the portfolio (each stock will have a 2% weight in the new portfolio). This strategy needs to be backtested as far as possible. Any ideas??

7 responses

So, the strategy is to rebalance the first of every year. The portfolio should contain the top 50 best performing stocks in the S&P500 from the previous year. Performance is measured as the total price gain (in percent including dividends) from Jan 1 to Dec 31 of the previous year. Each stock is equally weighted (ie 2%). Is that correct?

Here's something that's close. There isn't a good way to get the exact S&P500 but this takes the 500 largest companies by market cap (very close to the S&P500). Also, there isn't an easy way to get the exact returns from Jan 1. This uses a window of 252 trading days which is typically within a day of a full year (again very close).

Look at the record variable which shows the number of stocks. A lot get delisted during the year (the amount just goes into cash) which was a bit of a surprise. A lot of mergers and acquisitions I guess.

Depends upon where one starts this strategy but looks like over the past 15 years it would have returned only about half of the SP500.

Clone Algorithm
14
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
'''
Simple algorithm with annual rebalance.
'''

# import pipeline methods 
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline

# import built in factors and filters
import quantopian.pipeline.factors as Factors
import quantopian.pipeline.filters as Filters

# import optimize
import quantopian.optimize as opt

# import any datasets we need
from quantopian.pipeline.data.builtin import USEquityPricing

# import numpy and pandas just in case
import numpy as np
import pandas as pd

# define any constants. 
TOTAL_STOCKS = 50


def initialize(context):
    """
    Called once at the start of the algorithm.
    """   
    
    # Create and attach pipeline to get data
    attach_pipeline(my_pipeline(), name='my_pipeline')
    
    schedule_function(  
            rebalance,  
            date_rules.month_start(),  
            time_rules.market_open())
 
    schedule_function(  
            record_and_log,  
            date_rules.every_day(),  
            time_rules.market_close())
    
def my_pipeline():
        
    returns_1_yr = Factors.Returns(window_length = 252)
    top_returns = returns_1_yr.top(TOTAL_STOCKS, mask = Filters.Q500US())
        
    return Pipeline(screen = top_returns)


def before_trading_start(context, data):
    
    context.output = pipeline_output('my_pipeline')
    # These are the securities that we are interested in trading.
    context.security_list = context.output.index
    
    total_stocks = len(context.security_list)
    
    # Create weights (equal weighted)
    context.output = context.output.assign(weights = 1.0/total_stocks)
  
    
def rebalance(context, data):
    current_month = get_datetime().month
    
    if current_month not in [1]:
        return
    else:
    
        # Create a target weight objective
        weight_objective = opt.TargetWeights(context.output.weights)  

        # Execute the order_optimal_portfolio method with above objective and constraint
        order_optimal_portfolio(objective = weight_objective, constraints = [])
    
    
def record_and_log(context, data):
             
    record(positions=len(context.portfolio.positions))
There was a runtime error.

Maybe would have done better choosing the bottom 50?

Clone Algorithm
14
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
'''
Simple algorithm with annual rebalance.
'''

# import pipeline methods 
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline

# import built in factors and filters
import quantopian.pipeline.factors as Factors
import quantopian.pipeline.filters as Filters

# import optimize
import quantopian.optimize as opt

# import any datasets we need
from quantopian.pipeline.data.builtin import USEquityPricing

# import numpy and pandas just in case
import numpy as np
import pandas as pd

# define any constants. 
TOTAL_STOCKS = 50


def initialize(context):
    """
    Called once at the start of the algorithm.
    """   
    
    # Create and attach pipeline to get data
    attach_pipeline(my_pipeline(), name='my_pipeline')
    
    schedule_function(  
            rebalance,  
            date_rules.month_start(),  
            time_rules.market_open())
 
    schedule_function(  
            record_and_log,  
            date_rules.every_day(),  
            time_rules.market_close())
    
def my_pipeline():
        
    returns_1_yr = Factors.Returns(window_length = 252)
    top_returns = returns_1_yr.top(TOTAL_STOCKS, mask = Filters.Q500US())
    bottom_returns = returns_1_yr.bottom(TOTAL_STOCKS, mask = Filters.Q500US())
    return Pipeline(screen = bottom_returns)


def before_trading_start(context, data):
    
    context.output = pipeline_output('my_pipeline')
    # These are the securities that we are interested in trading.
    context.security_list = context.output.index
    
    total_stocks = len(context.security_list)
    
    # Create weights (equal weighted)
    context.output = context.output.assign(weights = 1.0/total_stocks)
  
    
def rebalance(context, data):
    current_month = get_datetime().month
    
    if current_month not in [1]:
        return
    else:
    
        # Create a target weight objective
        weight_objective = opt.TargetWeights(context.output.weights)  

        # Execute the order_optimal_portfolio method with above objective and constraint
        order_optimal_portfolio(objective = weight_objective, constraints = [])
    
    
def record_and_log(context, data):
             
    record(positions=len(context.portfolio.positions))
There was a runtime error.

Q500 322%
Q1500 563%
QTradableStocksUS 546%

Dan, thanks so much. That is a great start for me and I appreciate it. It's too bad that there isn't a way to pull up the S&P. Regardless, is there any way to pull the top performers from Q500 from the previous 252 days, rather than pull them by market cap? That is the point of the "momentum" strategy (I know - terrible strategy - but I was tasked into backtesting it:(

you can pull the sp500 with my trick: https://www.quantopian.com/posts/sp500-filter-from-2000-to-2018, just need to update it in the future. Its not the prettiest way as one could do a delta version which would be easier to maintain

Thank you, Peter!

The momentum strategy from above might work if rebalanced once per month not once per year. Any idea how the code must be adapted to rebalance monthly (or quarterly) instead of yearly? Thanks!