This is the base system for a Momentum Monthly Rotational Trading System
For simplicity and to avoid curve fitting, we will just use a 200 day lookback period for all moving averages
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import Q500US
from quantopian.pipeline.factors import CustomFactor
import numpy as np
market_gate_on = 1 # set this option to 1 to turn on a market gate based on close above/below SPY 200 day MA
bond_portfolio_on = 0 # set this option to 1 to go to 20 year treasury bonds ETF when gate closes
use_new_momentum_calc = 0
# set this option to 0 to use a standard percent change momentum calculation
# set this option to 1 to use a smoothed momentum calculation normalized
# CUSTOM FACTORS
# We want to use certain custom factors in our ranking process with the Pipeline
# Can test these custom factors in a research notebook to check if they are performing as expected
# 0. Standard momentum - based on percent change
inputs = [USEquityPricing.close]
window_length = 200 # 200 day lookback for simplicity, try to standardize at this
def compute(self, today, assets, out, close):
pct_change = (close[-1] - close) / close
out[:] = pct_change
# 1. Modified momentum
# Default inputs
inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]
window_length = 400 # 200 day lookback period, preceded by 200 day MA, so need 400 days of data.
def compute(self, today, assets, out, high, low, close):
# the inputs will be np arrays, not pandas dataframes
# calculate the difference between the SMA200 now and 200 days ago
diff = np.mean(close[-200:],axis=0) - np.mean(close[:-200], axis=0)
# compute the ATR (credit to Burrito Dan's ATR code https://www.quantopian.com/posts/custom-factor-atr and Nathan Pawelczyk's use of 10 day ATR)
hml = high - low
hmpc = np.abs(high - np.roll(close, 1, axis=0))
lmpc = np.abs(low - np.roll(close, 1, axis=0))
tr = np.maximum(hml, np.maximum(hmpc, lmpc))
atr = np.mean(tr[-10:], axis=0) # use 10 day ATR
# return the velocity normalized by ATR
out[:] = diff/atr
Called once at the start of the algorithm.
# Rebalance every month, 1st trading day, 1 minute after open.
# Record tracking variables at the end of each day
# Create our dynamic stock selector, will depend on which momentum measure we use
Can also use a research notebook to follow the creation of the pipeline.
The pipeline is created using the new momentum measure.
This includes smoothing using a SMA and then normalizing using the volatilty (ATR).
Filters out anything with a negative momentum.
momentum = Modified_Momentum(inputs=[USEquityPricing.high, USEquityPricing.low, USEquityPricing.close], window_length=400) # based on 200 day SMA and 200 day lookback period
momentum_positive_filter = ( momentum > 0.0 ) # don't want stocks with falling momentum
momentum_top_30 = momentum.top(30, mask=Q500US()) # take the top 30 stocks by modified momentum, only choosing from Q500US stocks.
tradeable = momentum_positive_filter & momentum_top_30 # combine both filters
'Modified Momentum': momentum
This pipeline is the traditionla percentage change momentum.
momentum = Momentum(inputs=[USEquityPricing.close], window_length=200) # based on 200 day lookback
momentum_top_30 = momentum.top(30, mask=Q500US()) # take the top 30 stocks by percentage change standard momentum, only choosing from Q500US stocks
'Standard Momentum': momentum
def before_trading_start(context, data):
Called every day before market open.
def rebalance(context, data):
Called at start of month 1 minute after market open.
# MARKET GATE - a simple 1 year MA gate
spy = symbol('SPY') # returns a security object
if data.current(spy,'price') < data.history(spy,'price', 200, '1d').mean(): # use 200 day SMA for market gate as well
print ("Market Gate is shut, exit all positions.")
for stock in context.portfolio.positions: # for each stock in our portfolio of positions
order_target(stock, 0) # exit trade
order_target_percent(symbol('TLT'), 1) # fully stock up on TLT
print ("Enter a full position of TLT, 20 year bonds ETF")
return # this will break the whole rebalance loop, as we don't want to reblance once we know the market gate is shut
context.output = pipeline_output('pipeline')
# These are the securities that we are interested in trading each month.
context.security_list = context.output.index
# same as context.output.index.get_level_values(0), because only 1 level when called in algorithm
# this gives a pandas index class, with each element being a zipline equity object
weight = 0.99/len(context.security_list) # calculate the weight for each stock we want
# Exit all positions before starting new ones
for stock in context.portfolio.positions:
# where each stock in the context.portfolio.positions is a zipline equity object
# same as what we have in context.security_list
if stock not in context.security_list: # if the stock is not in the index of the context.security_list (top 30 stocks)
order_target(stock, 0) # set the order_target to 0
# Rebalance all stocks to target weights
for stock in context.security_list: # for each stock in our top 30 list
if weight != 0 and data.can_trade(stock): # if we can trade the stock
order_target_percent(stock, weight) # place an order
def record_vars(context, data):
Plot variables at the end of each day.
long_count = 0
for position in context.portfolio.positions.itervalues():
if position.amount > 0:
long_count += 1
record(num_longs = long_count, leverage = context.account.leverage)
def handle_data(context, data):
Called every minute.