Sector Rotation Momentum + Mean Reversion

Hi! I am new to Q (and in fact quite new to algorithmic trading in general) and any help/input will be greatly appreciated. My algorithm is based on the common strategy where one goes long the best performing sector for the past month and holds it for a month. There is also evidence that stocks within a sector exhibit cross-sectional mean reversion behavior (Ernie Chan Chapter 4). It might be a good idea to long the worst performing stocks in the best performing sectors. I also shorten the time horizon to one week instead of one month. My basic strategy goes as follows:

1. Find the best performing sectors for the past week based on sector ETF.
2. Find the below-average performing stocks within that sector.
3. Calculate the weights based on
wi = (〈rj〉 - ri)/ Σk|〈rj〉 - rk|
where ri is the daily return of the ith stock, and is the average return of the corresponding sector ETF. This formula is inspired by Ernie Chan's book Example 4.3.
4. Apply the weights and hold it for a week.

Note that my basic algorithm is linear and has zero parameter. Therefore no data-scooping bias. However, due to the large draw-downs it may need a few improvements. For example:
1. Can add fundamental filters such as P/E ratio.
2. Can add stop loss and/or stop gain.
3. Can try different time horizon.
4. Maybe avoid trading around earning announcement?
5. Can hedge it with SPY or sector ETF.
6. Can do the mirror image: short the best performing stocks in the worst performing sector and turn it into a long-short strategy.

Also, my code is not very clean. Any suggestion is greatly appreciated.

1028
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 numpy as np
import scipy
import pandas as pd
from pytz import timezone

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline, CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume, Returns
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.classifiers.morningstar import Sector
import datetime

def initialize(context):

context.xly = sid(19662) #Consumer Discretionary 102
context.xlp = sid(19659) #Consumer Staples 205
context.xle = sid(19655) #Energy 309
context.xlf = sid(19656) #Financials 103
#context.xlv = sid(19661) #Health Care 306
context.xli = sid(19657) #Industrials 310
context.xlb = sid(19654) #Materials 101
#context.xlre = sid(49472) #Real estate 104
context.xlk = sid(19658) #Technology  311
context.xlu = sid(19660) #Utilities   207

context.sectors = [context.xly,
context.xlp,
context.xle,
context.xlf,
# context.xlv,
context.xli,
context.xlb,
#context.xlre,
context.xlk,
context.xlu
]

context.sector_id = [102,
205,
309,
103,
#306,
310,
101,
#104,
311,
207
]

context.stock_long = []
context.stock_short = []

schedule_function(func = liquidate, date_rule = date_rules.week_end(0), time_rule = time_rules.market_close(minutes = 1))

schedule_function(func = place_order, date_rule = date_rules.week_start(0), time_rule = time_rules.market_open())

attach_pipeline(make_pipeline(context), 'mean_reversion')

set_long_only()
context.enable_short_stock = False

context.today_wd = 0
context.today_month = 0

def make_pipeline(context):

pipe = Pipeline(
columns={
'weekly_return': weekly_return,
'sector': Sector(),
},
# combined_alpha will be NaN for all stocks not in our universe,
# but we also want to make sure that we have a sector code for everything
screen=weekly_return.notnull() & Sector().notnull(),
)
return pipe

print get_datetime('US/Eastern')
context.prev_day_wd = context.today_wd
context.today_wd = get_datetime('US/Eastern').weekday()

#context.prev_day_month = context.today_month
#context.today_month = get_datetime('US/Eastern').month
if context.today_wd > context.prev_day_wd:
#if context.today_month == context.prev_day_month:
return
else:
# week start
trackSectorPerformance(context, data)
if context.bullishSectorReturn > 0 and context.bearishSectorReturn < 0:
getBelowAverageStocksInBestPerformingSector(context, data)

def trackSectorPerformance(context, data):
log.info("in trackSectorPerformance.")
# Rank sectors based on weekly return
prices = data.history(context.sectors, 'price', 7, '1d')[:-1]
daily_ret = prices.pct_change(5)[1:].as_matrix(context.sectors)
weekly_ret = daily_ret[4]

bull = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[-1]
context.bullishSector = context.sector_id[bull]
context.bullishSectorReturn = weekly_ret[bull]
print context.bullishSectorReturn

log.info(context.bullishSector)

context.bearishSectorReturn = -99
if context.enable_short_stock:
bear = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[0]
context.bearishSector = context.sector_id[bear]
context.bearishSectorReturn = weekly_ret[bear]
print context.bullishSectorReturn

log.info("Done trackSectorPerformance.")

def getBelowAverageStocksInBestPerformingSector(context, data):
log.info("in getBelowAverageStocksInBestPerformingSector.")
pipeline_data = pipeline_output('mean_reversion')
bullish_data = pipeline_data[pipeline_data['sector'] == context.bullishSector]

#Filter out above average return stocks
below_average_bullish_data = bullish_data[bullish_data['weekly_return'] < context.bullishSectorReturn]
below_average_bullish_data = below_average_bullish_data.sort_values(by = 'weekly_return')
context.stock_to_long = below_average_bullish_data.index[:]
stock_to_long_returns  = below_average_bullish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_long_weights = context.bullishSectorReturn - stock_to_long_returns
context.stock_to_long_weights = context.stock_to_long_weights.div(context.stock_to_long_weights.sum())

print context.stock_to_long_weights.sum()
if context.enable_short_stock:
bearish_data = pipeline_data[pipeline_data['sector'] == context.bearishSector]

#context.stock_to_short = bearish_data.sort_values(by = 'weekly_return').index[-3:]
#Filter out above average return stocks
above_average_bearish_data = bearish_data[bearish_data['weekly_return'] > context.bearishSectorReturn]
above_average_bearish_data = above_average_bearish_data.sort_values(by = 'weekly_return')
context.stock_to_short = above_average_bearish_data.index[:]
stock_to_short_returns  = above_average_bearish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_short_weights = stock_to_short_returns - context.bearishSectorReturn
context.stock_to_short_weights = context.stock_to_short_weights.div(context.stock_to_short_weights.sum())

log.info("done getBelowAverageStocksInBestPerformingSector.")

def place_order(context, data):
log.info("in place order.")
try:
if len(context.stock_to_long)>0:
for i in range(len(context.stock_to_long)):
order_target_percent(context.stock_to_long[i], context.stock_to_long_weights[i])
context.stock_long = context.stock_to_long
context.stock_to_long = []

if context.enable_short_stock:
if len(context.stock_to_short)>0:
for i in range(len(context.stock_to_short)):
order_target_percent(context.stock_to_short[i], -context.stock_to_short_weights[i])
context.stock_short = context.stock_to_short
context.stock_to_short = []

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)

def liquidate(context, data):
log.info("in liquidate.")
try:
if len(context.stock_long)>0:
log.info("Liquidating longs.")
for i in range(len(context.stock_long)):
order_target_percent(context.stock_long[i], 0)

if context.enable_short_stock:
if len(context.stock_short)>0:
for i in range(len(context.stock_short)):
order_target_percent(context.stock_short[i], 0)

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)


There was a runtime error.
33 responses

Can you explain your weight formula? i.e what is rj, and rk, and k, and the <> brackets? I haven't read Ernie Chan's book.

Hi Stephen,

The basic idea is that if stocks exhibit cross-sectional mean reversion behavior, then the further they deviate from the sector mean return, the higher probability that the stocks will "catch up" to the mean. In my case, we want to apply more weights to those stocks that are furthest from the mean, i.e., the worst performing stocks. For example, if the sector return was 1% last week, and stock A's return was -0.1% and stock B's return was 0.2%, we put more weight in stock A than in stock B. Essentially the way we assign weights to a stock is proportional to how much the stock return deviates from the mean.

〈rj〉 denotes the mean return. ri denotes the return for individual stock i. Σk|〈rj〉 - rk|, which is the normalizer, is the sum of difference between the mean return and individual returns for all stocks whose returns are below mean.

Thanks Neo, makes sense.

Ill play around with this and see if I can improve those drawdowns. Adding stop losses and gains never seem to be a bad idea in my experience

Thanks for sharing this Neo. Has potential.

The mean referenced seems to be the long term average that the firm had been experiencing. Essay Writing Service UK

Neo. Take the red pill! Why not try to make this idea sector neutral and more likely to be eligible for the fund.

With Pipeline, you can rank stocks within each sector, and go short the top N and long the bottom N, same dollar weighting. You can use something like "Returns.groupby(Sector()).rank()" for this. If you did this across all sectors, and weighted each sector equally, you'll have a sector neutral mean reversion strategy, where each sector has equal capital allocated to it. This probably won't make any money, but...

How about if you weight each sector differently (but still dollar neutral within sectors)? Perhaps the most bearish sectors get the biggest divergence between the top and bottom N, as people panic and sell the worst stocks in that sector well below their fundamentals. Try overweighting the most bearish sectors.

I like to say openly that I don't understand clear idea about the algorithm explained above. It may be because of my fault. It is not a familiar area for me. I am working in the writing field. Now working for an essay writing service reviews company and writing is interesting field for me.

Neo,

What does context.bearishSectorReturn = -99 mean?

Watch your borrowing. Your returns are higher because of the amount of leverage. I'm going to play with it. I think it would be a better strategy if you looked at a longer time periods returns in the industry rather then 7 days. I think it will show more of a mean reversion, then just 7 days.

160
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 numpy as np
import scipy
import pandas as pd
from pytz import timezone

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline, CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume, Returns
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.classifiers.morningstar import Sector
import datetime

def initialize(context):

context.xly = sid(19662) #Consumer Discretionary 102
context.xlp = sid(19659) #Consumer Staples 205
context.xle = sid(19655) #Energy 309
context.xlf = sid(19656) #Financials 103
#context.xlv = sid(19661) #Health Care 306
context.xli = sid(19657) #Industrials 310
context.xlb = sid(19654) #Materials 101
#context.xlre = sid(49472) #Real estate 104
context.xlk = sid(19658) #Technology  311
context.xlu = sid(19660) #Utilities   207

context.sectors = [context.xly,
context.xlp,
context.xle,
context.xlf,
# context.xlv,
context.xli,
context.xlb,
#context.xlre,
context.xlk,
context.xlu
]

context.sector_id = [102,
205,
309,
103,
#306,
310,
101,
#104,
311,
207
]

context.stock_long = []
context.stock_short = []

schedule_function(func = liquidate, date_rule = date_rules.week_end(0), time_rule = time_rules.market_close(minutes = 1))

schedule_function(func = place_order, date_rule = date_rules.week_start(0), time_rule = time_rules.market_open())

attach_pipeline(make_pipeline(context), 'mean_reversion')
schedule_function(count_positions, date_rules.every_day(), time_rules.market_close())
set_long_only()
context.enable_short_stock = False

context.today_wd = 0
context.today_month = 0

def make_pipeline(context):

pipe = Pipeline(
columns={
'weekly_return': weekly_return,
'sector': Sector(),
},
# combined_alpha will be NaN for all stocks not in our universe,
# but we also want to make sure that we have a sector code for everything
screen=weekly_return.notnull() & Sector().notnull(),
)
return pipe

print get_datetime('US/Eastern')
context.prev_day_wd = context.today_wd
context.today_wd = get_datetime('US/Eastern').weekday()

#context.prev_day_month = context.today_month
#context.today_month = get_datetime('US/Eastern').month
if context.today_wd > context.prev_day_wd:
#if context.today_month == context.prev_day_month:
return
else:
# week start
trackSectorPerformance(context, data)
if context.bullishSectorReturn > 0 and context.bearishSectorReturn < 0:
getBelowAverageStocksInBestPerformingSector(context, data)

def trackSectorPerformance(context, data):
log.info("in trackSectorPerformance.")
# Rank sectors based on weekly return
prices = data.history(context.sectors, 'price', 7, '1d')[:-1]
daily_ret = prices.pct_change(5)[1:].as_matrix(context.sectors)
weekly_ret = daily_ret[4]

bull = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[-1]
context.bullishSector = context.sector_id[bull]
context.bullishSectorReturn = weekly_ret[bull]
print context.bullishSectorReturn

log.info(context.bullishSector)

context.bearishSectorReturn = -99
if context.enable_short_stock:
bear = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[0]
context.bearishSector = context.sector_id[bear]
context.bearishSectorReturn = weekly_ret[bear]
print context.bullishSectorReturn

log.info("Done trackSectorPerformance.")

def getBelowAverageStocksInBestPerformingSector(context, data):
log.info("in getBelowAverageStocksInBestPerformingSector.")
pipeline_data = pipeline_output('mean_reversion')
bullish_data = pipeline_data[pipeline_data['sector'] == context.bullishSector]

#Filter out above average return stocks
below_average_bullish_data = bullish_data[bullish_data['weekly_return'] < context.bullishSectorReturn]
below_average_bullish_data = below_average_bullish_data.sort_values(by = 'weekly_return')
context.stock_to_long = below_average_bullish_data.index[:]
stock_to_long_returns  = below_average_bullish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_long_weights = context.bullishSectorReturn - stock_to_long_returns
context.stock_to_long_weights = context.stock_to_long_weights.div(context.stock_to_long_weights.sum())

print context.stock_to_long_weights.sum()
if context.enable_short_stock:
bearish_data = pipeline_data[pipeline_data['sector'] == context.bearishSector]

#context.stock_to_short = bearish_data.sort_values(by = 'weekly_return').index[-3:]
#Filter out above average return stocks
above_average_bearish_data = bearish_data[bearish_data['weekly_return'] > context.bearishSectorReturn]
above_average_bearish_data = above_average_bearish_data.sort_values(by = 'weekly_return')
context.stock_to_short = above_average_bearish_data.index[:]
stock_to_short_returns  = above_average_bearish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_short_weights = stock_to_short_returns - context.bearishSectorReturn
context.stock_to_short_weights = context.stock_to_short_weights.div(context.stock_to_short_weights.sum())

log.info("done getBelowAverageStocksInBestPerformingSector.")

def place_order(context, data):
log.info("in place order.")
try:
if len(context.stock_to_long)>0:
for i in range(len(context.stock_to_long)):
order_target_percent(context.stock_to_long[i], context.stock_to_long_weights[i])
context.stock_long = context.stock_to_long
context.stock_to_long = []

if context.enable_short_stock:
if len(context.stock_to_short)>0:
for i in range(len(context.stock_to_short)):
order_target_percent(context.stock_to_short[i], -context.stock_to_short_weights[i])
context.stock_short = context.stock_to_short
context.stock_to_short = []

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)

def liquidate(context, data):
log.info("in liquidate.")
try:
if len(context.stock_long)>0:
log.info("Liquidating longs.")
for i in range(len(context.stock_long)):
order_target_percent(context.stock_long[i], 0)

if context.enable_short_stock:
if len(context.stock_short)>0:
for i in range(len(context.stock_short)):
order_target_percent(context.stock_short[i], 0)

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)

def count_positions(context,data):

longs = 0
for position in context.portfolio.positions.itervalues():
if position.amount > 0:
longs += 1
leverage=context.account.leverage
# asset=context.portfolio.portfolio_value
# record(asset=asset)
record(longs=longs)
record(leverage=leverage)


There was a runtime error.

Eric,

Do you know what context.bearishSectorReturn = -99 means in this code?

@Sam
I am not good at coding. Just trying to initialize it to a negative number. I guess you do not have to do that.

@ Neo ah I see gotchya. Thanks for reply!

@ Neo and @ Eric,

I am with Eric on the leverage. I will attempt to add a leverage constraint as well Eric, along with move time frame out from 7 days to maybe 20 days for a trading month instead of week and see how that plays into alpha, beta, and drawdown.....

@ Nero,

One last question. Why do you set context.prev_day_wd = context.today_wd ,

then define

if context.today_wd > context.prev_day_wd:
return

else

# week start

?

*@Neo

@Eric

Where do you find the case where leverage>1? Since the weights are distributed according to the said formula, the sum of the weights cannot exceed 1 by definition.
As for the holding period, yes you can experiment the algo with holding period longer than one week. It might also have good theoretical reasons to do so. I just have not got the chance to try it yet.

@ neo,

Why do you set context.prev_day_wd = context.today_wd ,
then define
if context.today_wd > context.prev_day_wd:
return
else

# week start

?

@Sam

I do that to figure out if today is the first business day of the week. I do nothing and return if it is NOT the week start.

@Eric @ Neo

Eric,

I see Neo's point on definition and leverage cannot exceed 1 with this trading rule. Am I missing something ? If so, we can apply a leverage constraint.

@Neo
I added leverage and the amount of long positions your algorithm had. Controlling Leverage is probably one of the most important things. Usually if your returns are really high you are borrowing a lot.

This is the code I added, in case you can't see my backtest.

def count_positions(context,data):

longs = 0
for position in context.portfolio.positions.itervalues():
if position.amount > 0:
longs += 1
leverage=context.account.leverage
# asset=context.portfolio.portfolio_value
# record(asset=asset)
record(longs=longs)
record(leverage=leverage)

160
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 numpy as np
import scipy
import pandas as pd
from pytz import timezone

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline, CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume, Returns
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.classifiers.morningstar import Sector
import datetime

def initialize(context):

context.xly = sid(19662) #Consumer Discretionary 102
context.xlp = sid(19659) #Consumer Staples 205
context.xle = sid(19655) #Energy 309
context.xlf = sid(19656) #Financials 103
#context.xlv = sid(19661) #Health Care 306
context.xli = sid(19657) #Industrials 310
context.xlb = sid(19654) #Materials 101
#context.xlre = sid(49472) #Real estate 104
context.xlk = sid(19658) #Technology  311
context.xlu = sid(19660) #Utilities   207

context.sectors = [context.xly,
context.xlp,
context.xle,
context.xlf,
# context.xlv,
context.xli,
context.xlb,
#context.xlre,
context.xlk,
context.xlu
]

context.sector_id = [102,
205,
309,
103,
#306,
310,
101,
#104,
311,
207
]

context.stock_long = []
context.stock_short = []

schedule_function(func = liquidate, date_rule = date_rules.week_end(0), time_rule = time_rules.market_close(minutes = 1))

schedule_function(func = place_order, date_rule = date_rules.week_start(0), time_rule = time_rules.market_open())

attach_pipeline(make_pipeline(context), 'mean_reversion')
schedule_function(count_positions, date_rules.every_day(), time_rules.market_close())
set_long_only()
context.enable_short_stock = False

context.today_wd = 0
context.today_month = 0

def make_pipeline(context):

pipe = Pipeline(
columns={
'weekly_return': weekly_return,
'sector': Sector(),
},
# combined_alpha will be NaN for all stocks not in our universe,
# but we also want to make sure that we have a sector code for everything
screen=weekly_return.notnull() & Sector().notnull(),
)
return pipe

print get_datetime('US/Eastern')
context.prev_day_wd = context.today_wd
context.today_wd = get_datetime('US/Eastern').weekday()

#context.prev_day_month = context.today_month
#context.today_month = get_datetime('US/Eastern').month
if context.today_wd > context.prev_day_wd:
#if context.today_month == context.prev_day_month:
return
else:
# week start
trackSectorPerformance(context, data)
if context.bullishSectorReturn > 0 and context.bearishSectorReturn < 0:
getBelowAverageStocksInBestPerformingSector(context, data)

def trackSectorPerformance(context, data):
log.info("in trackSectorPerformance.")
# Rank sectors based on weekly return
prices = data.history(context.sectors, 'price', 7, '1d')[:-1]
daily_ret = prices.pct_change(5)[1:].as_matrix(context.sectors)
weekly_ret = daily_ret[4]

bull = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[-1]
context.bullishSector = context.sector_id[bull]
context.bullishSectorReturn = weekly_ret[bull]
print context.bullishSectorReturn

log.info(context.bullishSector)

context.bearishSectorReturn = -99
if context.enable_short_stock:
bear = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[0]
context.bearishSector = context.sector_id[bear]
context.bearishSectorReturn = weekly_ret[bear]
print context.bullishSectorReturn

log.info("Done trackSectorPerformance.")

def getBelowAverageStocksInBestPerformingSector(context, data):
log.info("in getBelowAverageStocksInBestPerformingSector.")
pipeline_data = pipeline_output('mean_reversion')
bullish_data = pipeline_data[pipeline_data['sector'] == context.bullishSector]

#Filter out above average return stocks
below_average_bullish_data = bullish_data[bullish_data['weekly_return'] < context.bullishSectorReturn]
below_average_bullish_data = below_average_bullish_data.sort_values(by = 'weekly_return')
context.stock_to_long = below_average_bullish_data.index[:]
stock_to_long_returns  = below_average_bullish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_long_weights = context.bullishSectorReturn - stock_to_long_returns
context.stock_to_long_weights = context.stock_to_long_weights.div(context.stock_to_long_weights.sum())

print context.stock_to_long_weights.sum()
if context.enable_short_stock:
bearish_data = pipeline_data[pipeline_data['sector'] == context.bearishSector]

#context.stock_to_short = bearish_data.sort_values(by = 'weekly_return').index[-3:]
#Filter out above average return stocks
above_average_bearish_data = bearish_data[bearish_data['weekly_return'] > context.bearishSectorReturn]
above_average_bearish_data = above_average_bearish_data.sort_values(by = 'weekly_return')
context.stock_to_short = above_average_bearish_data.index[:]
stock_to_short_returns  = above_average_bearish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_short_weights = stock_to_short_returns - context.bearishSectorReturn
context.stock_to_short_weights = context.stock_to_short_weights.div(context.stock_to_short_weights.sum())

log.info("done getBelowAverageStocksInBestPerformingSector.")

def place_order(context, data):
log.info("in place order.")
try:
if len(context.stock_to_long)>0:
for i in range(len(context.stock_to_long)):
order_target_percent(context.stock_to_long[i], context.stock_to_long_weights[i])
context.stock_long = context.stock_to_long
context.stock_to_long = []

if context.enable_short_stock:
if len(context.stock_to_short)>0:
for i in range(len(context.stock_to_short)):
order_target_percent(context.stock_to_short[i], -context.stock_to_short_weights[i])
context.stock_short = context.stock_to_short
context.stock_to_short = []

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)

def liquidate(context, data):
log.info("in liquidate.")
try:
if len(context.stock_long)>0:
log.info("Liquidating longs.")
for i in range(len(context.stock_long)):
order_target_percent(context.stock_long[i], 0)

if context.enable_short_stock:
if len(context.stock_short)>0:
for i in range(len(context.stock_short)):
order_target_percent(context.stock_short[i], 0)

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)

def count_positions(context,data):

longs = 0
for position in context.portfolio.positions.itervalues():
if position.amount > 0:
longs += 1
leverage=context.account.leverage
# asset=context.portfolio.portfolio_value
# record(asset=asset)
record(longs=longs)
record(leverage=leverage)


There was a runtime error.

@ Eric,

What does this leverage set the algo at with this code you added? Is that maximum leverage 1, or 100% based on this? and no more?

@ Eric,

Reason I ask is because I do not see much change from his original algo, even when applying your code for leverage.

IF you run this algo back to jan 2003, even with the leverage code you have added, it tremendously returns compared to benchmark (roughly 700 % vs. benchmark at 200%)

That is even with the leverage code you have added.

I didn't fix the leverage. I recorded the amount of leverage in the algorithm so I could see the spikes. I see sometimes it hits 1.4 or 1.5. There's some flawed logic in the weighting structure. Usually it is the first thing I check when analyzing an algorithm. I am not sure how to fix it.

22
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 numpy as np
import scipy
import pandas as pd
from pytz import timezone

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline, CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume, Returns
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.classifiers.morningstar import Sector
import datetime

def initialize(context):

context.xly = sid(19662) #Consumer Discretionary 102
context.xlp = sid(19659) #Consumer Staples 205
context.xle = sid(19655) #Energy 309
context.xlf = sid(19656) #Financials 103
#context.xlv = sid(19661) #Health Care 306
context.xli = sid(19657) #Industrials 310
context.xlb = sid(19654) #Materials 101
#context.xlre = sid(49472) #Real estate 104
context.xlk = sid(19658) #Technology  311
context.xlu = sid(19660) #Utilities   207

context.sectors = [context.xly,
context.xlp,
context.xle,
context.xlf,
# context.xlv,
context.xli,
context.xlb,
#context.xlre,
context.xlk,
context.xlu
]

context.sector_id = [102,
205,
309,
103,
#306,
310,
101,
#104,
311,
207
]

context.stock_long = []
context.stock_short = []

schedule_function(func = liquidate, date_rule = date_rules.week_end(0), time_rule = time_rules.market_close(minutes = 1))

schedule_function(func = place_order, date_rule = date_rules.week_start(0), time_rule = time_rules.market_open())
schedule_function(count_positions, date_rules.every_day(), time_rules.market_close())
attach_pipeline(make_pipeline(context), 'mean_reversion')

set_long_only()
context.enable_short_stock = False

context.today_wd = 0
context.today_month = 0

def make_pipeline(context):

pipe = Pipeline(
columns={
'weekly_return': weekly_return,
'sector': Sector(),
},
# combined_alpha will be NaN for all stocks not in our universe,
# but we also want to make sure that we have a sector code for everything
screen=weekly_return.notnull() & Sector().notnull(),
)
return pipe

print get_datetime('US/Eastern')
context.prev_day_wd = context.today_wd
context.today_wd = get_datetime('US/Eastern').weekday()

#context.prev_day_month = context.today_month
#context.today_month = get_datetime('US/Eastern').month
if context.today_wd > context.prev_day_wd:
#if context.today_month == context.prev_day_month:
return
else:
# week start
trackSectorPerformance(context, data)
if context.bullishSectorReturn > 0 and context.bearishSectorReturn < 0:
getBelowAverageStocksInBestPerformingSector(context, data)

def trackSectorPerformance(context, data):
log.info("in trackSectorPerformance.")
# Rank sectors based on weekly return
prices = data.history(context.sectors, 'price', 7, '1d')[:-1]
daily_ret = prices.pct_change(5)[1:].as_matrix(context.sectors)
weekly_ret = daily_ret[4]

bull = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[-1]
context.bullishSector = context.sector_id[bull]
context.bullishSectorReturn = weekly_ret[bull]
print context.bullishSectorReturn

log.info(context.bullishSector)

context.bearishSectorReturn = -99
if context.enable_short_stock:
bear = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[0]
context.bearishSector = context.sector_id[bear]
context.bearishSectorReturn = weekly_ret[bear]
print context.bullishSectorReturn

log.info("Done trackSectorPerformance.")

def getBelowAverageStocksInBestPerformingSector(context, data):
log.info("in getBelowAverageStocksInBestPerformingSector.")
pipeline_data = pipeline_output('mean_reversion')
bullish_data = pipeline_data[pipeline_data['sector'] == context.bullishSector]

#Filter out above average return stocks
below_average_bullish_data = bullish_data[bullish_data['weekly_return'] < context.bullishSectorReturn]
below_average_bullish_data = below_average_bullish_data.sort_values(by = 'weekly_return')
context.stock_to_long = below_average_bullish_data.index[:]
stock_to_long_returns  = below_average_bullish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_long_weights = context.bullishSectorReturn - stock_to_long_returns
context.stock_to_long_weights = context.stock_to_long_weights.div(context.stock_to_long_weights.sum())

print context.stock_to_long_weights.sum()
if context.enable_short_stock:
bearish_data = pipeline_data[pipeline_data['sector'] == context.bearishSector]

#context.stock_to_short = bearish_data.sort_values(by = 'weekly_return').index[-3:]
#Filter out above average return stocks
above_average_bearish_data = bearish_data[bearish_data['weekly_return'] > context.bearishSectorReturn]
above_average_bearish_data = above_average_bearish_data.sort_values(by = 'weekly_return')
context.stock_to_short = above_average_bearish_data.index[:]
stock_to_short_returns  = above_average_bearish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_short_weights = stock_to_short_returns - context.bearishSectorReturn
context.stock_to_short_weights = context.stock_to_short_weights.div(context.stock_to_short_weights.sum())

log.info("done getBelowAverageStocksInBestPerformingSector.")

def place_order(context, data):
log.info("in place order.")
try:
if len(context.stock_to_long)>0:
for i in range(len(context.stock_to_long)):
order_target_percent(context.stock_to_long[i], context.stock_to_long_weights[i])
context.stock_long = context.stock_to_long
context.stock_to_long = []

if context.enable_short_stock:
if len(context.stock_to_short)>0:
for i in range(len(context.stock_to_short)):
order_target_percent(context.stock_to_short[i], -context.stock_to_short_weights[i])
context.stock_short = context.stock_to_short
context.stock_to_short = []

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)

def liquidate(context, data):
log.info("in liquidate.")
try:
if len(context.stock_long)>0:
log.info("Liquidating longs.")
for i in range(len(context.stock_long)):
order_target_percent(context.stock_long[i], 0)

if context.enable_short_stock:
if len(context.stock_short)>0:
for i in range(len(context.stock_short)):
order_target_percent(context.stock_short[i], 0)

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)

def count_positions(context,data):

longs = 0
for position in context.portfolio.positions.itervalues():
if position.amount > 0:
longs += 1
leverage=context.account.leverage
# asset=context.portfolio.portfolio_value
# record(asset=asset)
record(longs=longs)
record(leverage=leverage)


There was a runtime error.

@Eric

We can add some code to keep track of the sum of the weights to determine what went wrong and how leverage>1 happened.

hacked track_orders to include leverage value

2003-05-30 13:00 WARN Your order for -5518 shares of AYE failed to fill by the end of day and was canceled.
2003-06-02 06:31 _orders:278 INFO    1      Sold -341 KMI at 51.05   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1      Sold -2204 TXU at 20.20   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1         AYE -5518 unfilled canceled
2003-06-02 06:31 _orders:278 INFO    1   Buy 101 ED at 42.98   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1   Buy 590 KSE at 35.39   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1   Buy 141 PPL at 40.44   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1   Buy 312 SRE at 27.21   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1   Buy 339 EXC at 57.22   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1   Buy 97 CIN at 37.94   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1   Buy 8 ETR at 51.66   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1   Buy 159 D at 63.25   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1   Buy 261 DTE at 43.38   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1   Buy 39 AEE at 45.50   cash 61875  lv 0.44
2003-06-02 06:31 _orders:278 INFO    1   Buy 1174 TXU at 20.20   cash 61875  lv 0.44
2003-06-02 06:32 _orders:278 INFO    2         ED 101 unfilled
2003-06-02 06:32 _orders:278 INFO    2         TXU 1174 unfilled
2003-06-02 06:32 _orders:278 INFO    2         SRE 312 unfilled
2003-06-02 06:32 _orders:278 INFO    2         EXC 339 unfilled
2003-06-02 06:32 _orders:278 INFO    2         PPL 141 unfilled
2003-06-02 06:32 _orders:278 INFO    2         ETR 8 unfilled
2003-06-02 06:32 _orders:278 INFO    2         DTE 261 unfilled
2003-06-02 06:33 _orders:278 INFO    3      Bot 97 CIN at 38.10   cash -37812  lv 1.34
2003-06-02 06:33 _orders:278 INFO    3         ED 101 unfilled
2003-06-02 06:33 _orders:278 INFO    3      Bot 590 KSE at 35.31   cash -37812  lv 1.34
2003-06-02 06:33 _orders:278 INFO    3      Bot 159 D at 63.23   cash -37812  lv 1.34
2003-06-02 06:33 _orders:278 INFO    3         PPL 141 unfilled
2003-06-02 06:33 _orders:278 INFO    3         ETR 8 unfilled
2003-06-02 06:33 _orders:278 INFO    3      Bot 39 AEE at 45.50   cash -37812  lv 1.34
2003-06-02 06:34 _orders:278 INFO    4         ED 101 unfilled
2003-06-02 06:34 _orders:278 INFO    4      Bot 1174 TXU at 20.39   cash -43942  lv 1.40

88
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 numpy as np
import scipy
import pandas as pd

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline, CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume, Returns
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.classifiers.morningstar import Sector
import datetime

def initialize(context):
context.mx_lvrg  = 0
context.cash_low = 0

context.xly = sid(19662) #Consumer Discretionary 102
context.xlp = sid(19659) #Consumer Staples 205
context.xle = sid(19655) #Energy 309
context.xlf = sid(19656) #Financials 103
#context.xlv = sid(19661) #Health Care 306
context.xli = sid(19657) #Industrials 310
context.xlb = sid(19654) #Materials 101
#context.xlre = sid(49472) #Real estate 104
context.xlk = sid(19658) #Technology  311
context.xlu = sid(19660) #Utilities   207

context.sectors = [context.xly,
context.xlp,
context.xle,
context.xlf,
# context.xlv,
context.xli,
context.xlb,
#context.xlre,
context.xlk,
context.xlu
]

context.sector_id = [102,
205,
309,
103,
#306,
310,
101,
#104,
311,
207
]

context.stock_long = []
context.stock_short = []

schedule_function(func = liquidate, date_rule = date_rules.week_end(0), time_rule = time_rules.market_close(minutes = 1))

schedule_function(func = place_order, date_rule = date_rules.week_start(0), time_rule = time_rules.market_open())
schedule_function(count_positions, date_rules.every_day(), time_rules.market_close())
attach_pipeline(make_pipeline(context), 'mean_reversion')

set_long_only()
context.enable_short_stock = False

context.today_wd = 0
context.today_month = 0

def make_pipeline(context):

pipe = Pipeline(
columns={
'weekly_return': weekly_return,
'sector': Sector(),
},
# combined_alpha will be NaN for all stocks not in our universe,
# but we also want to make sure that we have a sector code for everything
screen=weekly_return.notnull() & Sector().notnull(),
)
return pipe

#print get_datetime('US/Eastern')
context.prev_day_wd = context.today_wd
context.today_wd = get_datetime('US/Eastern').weekday()

#context.prev_day_month = context.today_month
#context.today_month = get_datetime('US/Eastern').month
if context.today_wd > context.prev_day_wd:
#if context.today_month == context.prev_day_month:
return
else:
# week start
trackSectorPerformance(context, data)
if context.bullishSectorReturn > 0 and context.bearishSectorReturn < 0:
getBelowAverageStocksInBestPerformingSector(context, data)

#record(cash = context.portfolio.cash)

def trackSectorPerformance(context, data):
#log.info("in trackSectorPerformance.")
# Rank sectors based on weekly return
prices = data.history(context.sectors, 'price', 7, '1d')[:-1]
daily_ret = prices.pct_change(5)[1:].as_matrix(context.sectors)
weekly_ret = daily_ret[4]

bull = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[-1]
context.bullishSector = context.sector_id[bull]
context.bullishSectorReturn = weekly_ret[bull]
#print context.bullishSectorReturn

#log.info(context.bullishSector)

context.bearishSectorReturn = -99
if context.enable_short_stock:
bear = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[0]
context.bearishSector = context.sector_id[bear]
context.bearishSectorReturn = weekly_ret[bear]
#print context.bullishSectorReturn

#log.info("Done trackSectorPerformance.")

def getBelowAverageStocksInBestPerformingSector(context, data):
#log.info("in getBelowAverageStocksInBestPerformingSector.")
pipeline_data = pipeline_output('mean_reversion')
bullish_data = pipeline_data[pipeline_data['sector'] == context.bullishSector]

#Filter out above average return stocks
below_average_bullish_data = bullish_data[bullish_data['weekly_return'] < context.bullishSectorReturn]
below_average_bullish_data = below_average_bullish_data.sort_values(by = 'weekly_return')
context.stock_to_long = below_average_bullish_data.index[:]
stock_to_long_returns  = below_average_bullish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_long_weights = context.bullishSectorReturn - stock_to_long_returns
context.stock_to_long_weights = context.stock_to_long_weights.div(context.stock_to_long_weights.sum())

#print context.stock_to_long_weights.sum()
if context.enable_short_stock:
bearish_data = pipeline_data[pipeline_data['sector'] == context.bearishSector]

#context.stock_to_short = bearish_data.sort_values(by = 'weekly_return').index[-3:]
#Filter out above average return stocks
above_average_bearish_data = bearish_data[bearish_data['weekly_return'] > context.bearishSectorReturn]
above_average_bearish_data = above_average_bearish_data.sort_values(by = 'weekly_return')
context.stock_to_short = above_average_bearish_data.index[:]
stock_to_short_returns  = above_average_bearish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_short_weights = stock_to_short_returns - context.bearishSectorReturn
context.stock_to_short_weights = context.stock_to_short_weights.div(context.stock_to_short_weights.sum())

#log.info("done getBelowAverageStocksInBestPerformingSector.")

def place_order(context, data):
#log.info("in place order.")
try:
if len(context.stock_to_long)>0:
for i in range(len(context.stock_to_long)):
order_target_percent(context.stock_to_long[i], context.stock_to_long_weights[i])
context.stock_long = context.stock_to_long
context.stock_to_long = []

if context.enable_short_stock:
if len(context.stock_to_short)>0:
for i in range(len(context.stock_to_short)):
order_target_percent(context.stock_to_short[i], -context.stock_to_short_weights[i])
context.stock_short = context.stock_to_short
context.stock_to_short = []
track_orders(context, data)

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)

def liquidate(context, data):
#log.info("in liquidate.")
try:
if len(context.stock_long)>0:
log.info("Liquidating longs.")
for i in range(len(context.stock_long)):
order_target_percent(context.stock_long[i], 0)

if context.enable_short_stock:
if len(context.stock_short)>0:
for i in range(len(context.stock_short)):
order_target_percent(context.stock_short[i], 0)

track_orders(context, data)

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)

def count_positions(context,data):

longs = 0
for position in context.portfolio.positions.itervalues():
if position.amount > 0:
longs += 1
leverage=context.account.leverage
# asset=context.portfolio.portfolio_value
# record(asset=asset)
record(longs=longs)
record(leverage=leverage)

def handle_data(context, data):
track_orders(context, data)

if context.account.leverage > context.mx_lvrg:
context.mx_lvrg = context.account.leverage
record(mx_lvrg = context.mx_lvrg)    # Record maximum leverage encountered

if context.portfolio.cash < context.cash_low:
context.cash_low = context.portfolio.cash
record(cash_low = context.cash_low)    # Record lowest cash encountered

def track_orders(context, data):  # Log orders created, filled, unfilled or canceled.
'''      https://www.quantopian.com/posts/track-orders
Status:
0 - Unfilled
1 - Filled (can be partial)
2 - Canceled
'''
c = context
log_cash = 1    # Show cash values in logging window or not.
log_ids  = 0    # Include order id's in logging window or not.
log_unfilled = 1

''' Start and stop date options ...
To not overwhelm the logging window, start/stop dates can be entered
either below or in initialize() if you move to there for better efficiency.
Example:
c.dates  = {
'active': 0,
'start' : ['2007-05-07', '2010-04-26'],
'stop'  : ['2008-02-13', '2010-11-15']
}
'''
if 'orders' not in c:
c.orders = {}               # Move these to initialize() for better efficiency.
c.dates  = {
'active': 0,
'start' : [],           # Start dates, option
'stop'  : []            # Stop  dates, option
}
from pytz import timezone       # Python only does once, makes this portable.
#   Move to top of algo for better efficiency.

# If the dates 'start' or 'stop' lists have something in them, sets them.
if c.dates['start'] or c.dates['stop']:
date = str(get_datetime().date())
if   date in c.dates['start']:    # See if there's a match to start
c.dates['active'] = 1
elif date in c.dates['stop']:     #   ... or to stop
c.dates['active'] = 0
else:
c.dates['active'] = 1  # Set to active b/c no conditions

if c.dates['active'] == 0:
return                 # Skip if off

def _minute():   # To preface each line with the minute of the day.
bar_dt = get_datetime().astimezone(timezone('US/Eastern'))
minute = (bar_dt.hour * 60) + bar_dt.minute - 570  # (-570 = 9:31a)
return str(minute).rjust(3)

def _orders(to_log):    # So all logging comes from the same line number,
log.info(to_log)    #   for vertical alignment in the logging window.

ordrs = c.orders.copy()    # Independent copy to allow deletes
for id in ordrs:
o = get_order(id)
if o.dt == get_datetime(): continue  # Same minute as order, no chance of fill yet.
sec  = o.sid ; sym = sec.symbol
oid  = o.id if log_ids else ''

# hack for https://www.quantopian.com/posts/sector-rotation-momentum-plus-mean-reversion
cash = 'cash {}  lv {}'.format(int(c.portfolio.cash), '%.2f' % c.account.leverage) if log_cash else ''

prc  = '%.2f' % data.current(sec, 'price') if data.can_trade(sec) else 'unknwn'
if o.filled:        # Filled at least some
trade  = 'Bot' if o.amount > 0 else 'Sold'
filled = '{}'.format(o.amount)
filled_this = ''
if o.filled == o.amount:    # complete
if 0 < c.orders[o.id] < o.amount:
filled  = 'all {}/{}'.format(o.filled - c.orders[o.id], o.amount)
else:
filled  = '{}'.format(o.amount)
filled_this = 1
del c.orders[o.id]
else: # c.orders[o.id] is previously filled total
filled_this    = o.filled - c.orders[o.id]  # filled this time, can be 0
c.orders[o.id] = o.filled                   # save for increments math
filled         = '{}/{}'.format(filled_this, o.amount)
if filled_this:
_orders(' {}      {} {} {} at {}   {} {}'.format(_minute(),
trade, filled, sym, prc, cash, oid))
elif log_unfilled:
canceled = 'canceled' if o.status == 2 else ''
_orders(' {}         {} {} unfilled {} {}'.format(_minute(),
o.sid.symbol, o.amount, canceled, oid))
if canceled: del c.orders[o.id]

for oo_list in get_open_orders().values(): # Open orders list
for o in oo_list:
sec  = o.sid ; sym = sec.symbol
oid  = o.id if log_ids else ''

# hack for https://www.quantopian.com/posts/sector-rotation-momentum-plus-mean-reversion
cash = 'cash {}  lv {}'.format(int(c.portfolio.cash), '%.2f' % c.account.leverage) if log_cash else ''

prc  = '%.2f' % data.current(sec, 'price') if data.can_trade(sec) else 'unknwn'
if o.status == 2:                  # Canceled
_orders(' {}    Canceled {} {} order   {} {}'.format(_minute(),
trade, o.amount, sym, prc, cash, oid))
del c.orders[o.id]
elif o.id not in c.orders:         # New
c.orders[o.id] = 0
if o.limit:                    # Limit order
_orders(' {}   {} {} {} now {} limit {}   {} {}'.format(_minute(),
trade, o.amount, sym, prc, o.limit, cash, oid))
elif o.stop:                   # Stop order
_orders(' {}   {} {} {} now {} stop {}   {} {}'.format(_minute(),
trade, o.amount, sym, prc, o.stop, cash, oid))
else:                          # Market order
_orders(' {}   {} {} {} at {}   {} {}'.format(_minute(),
trade, o.amount, sym, prc, cash, oid))


There was a runtime error.

@ Blue

Thank you very much for this very helpful. So if we wanted to go about and restrict this algo to a maximum leverage of 2 at ALL times and never more,
what line of code would you include to allow for this?

def initialize(context):
context.target_leverage = 2

def place_order(context, data):
...
order_value(context.stock_to_long[i], context.stock_to_long_weights[i] * context.portfolio.cash * context.target_leverage)
#order_target_percent(context.stock_to_long[i], context.stock_to_long_weights[i])
...
order_value(context.stock_to_short[i], -context.stock_to_short_weights[i] * context.portfolio.cash * .5 * context.target_leverage)
#order_target_percent(context.stock_to_short[i], -context.stock_to_short_weights[i])
...


On shorting, the .5 is because I'm of the belief that it is best to keep 2x cash per short value for a leverage of 1. What if the target leverage is 2? You find out.

You can try something more dynamic with leverage as a divisor. As leverage drifts above the target, proportionally reduce allocations and below target, increase. As is, this leverage is often low. You can find out why if you want to work with it.

def initialize(context):
context.target_leverage = 2

def place_order(context, data):
c = context     # easier for me to read/edit
#log.info("in place order.")
try:
if len(c.stock_to_long)>0:
for i in range(len(c.stock_to_long)):
val = c.target_leverage * c.stock_to_long_weights[i] * c.portfolio.cash
if c.account.leverage > .7:     # in part avoiding divide by zero
val /= c.account.leverage
order_value(c.stock_to_long[i], val)
#order_target_percent(c.stock_to_long[i], c.stock_to_long_weights[i])
c.stock_long = c.stock_to_long
c.stock_to_long = []
if c.enable_short_stock:
if len(c.stock_to_short)>0:
for i in range(len(c.stock_to_short)):
val = c.target_leverage * (-c.stock_to_short_weights[i] * c.portfolio.cash * .5) / (c.account.leverage)
if c.account.leverage > .7:
val /= c.account.leverage
order_value(c.stock_to_short[i], val)
#order_target_percent(c.stock_to_short[i], -c.stock_to_short_weights[i])
c.stock_short = c.stock_to_short
c.stock_to_short = []
track_orders(context, data)


For more control and more of a solid footing, so unfilled orders won't toss you around in a land of unpredictability, schedule shorting separately and give them some time to fill and run a functon to cancel all open orders before longs.

As a best practice, I think we should all drop the common practice of selling and buying in the same minute, queue allocations that would result in a buy for later. 1) Not easy 2) The resulting certainty, clarity and control are worth the effort.

@ Blue this is great. I will play around with the first option (something more basic) first and see what spits out.

Will keep you updated on results. Thanks very much for assisting us here.

Sam

@ Blue. I am trying to use your second block of code and getting a strange error that says I have an unexpected unindent on line 214, which is
" def liquidate (context,data)" starting point. Any way you can take a look at my source and see where I could be going wrong? Thanks in advance.

5
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
# 1. Find the best performing sectors for the past week based on sector ETF.
# 2. Find the below-average performing stocks within that sector.
# 3. Calculate the weights based on
#wi = (〈rj〉 - ri)/ Σk|〈rj〉 - rk|
#where ri is the daily return of the ith stock, and is the average return of the corresponding sector ETF. This formula is inspired by Ernie Chan's book Example 4.3.
# 4. Apply the weights and hold it for a week.

#Note that my basic algorithm is linear and has zero parameter. Therefore no data-scooping bias. However, due to the large draw-downs it may need a few improvements. For example:

#1. Can add fundamental filters such as P/E ratio.
#2. Can add stop loss and/or stop gain.
#3. Can try different time horizon.
#4. Maybe avoid trading around earning announcement? SEARCH Earnings Announcement Risk Framework in HELP for this
#5. Can hedge it with SPY or sector ETF.
#6. Can do the mirror image: short the best performing stocks in the worst performing sector and turn it into a long-short strategy.

# The basic idea is that if stocks exhibit cross-sectional mean reversion behavior, then the further they deviate from the sector mean return, the higher probability that the stocks will "catch up" to the mean. In my case, we want to apply more weights to those stocks that are furthest from the mean, i.e., the worst performing stocks. For example, if the sector return was 1% last week, and stock A's return was -0.1% and stock B's return was 0.2%, we put more weight in stock A than in stock B. Essentially the way we assign weights to a stock is proportional to how much the stock return deviates from the mean.

# 〈rj〉 denotes the mean return. ri denotes the return for individual stock i. Σk|〈rj〉 - rk|, which is the normalizer, is the sum of difference between the mean return and individual returns for all stocks whose returns are below mean.

import numpy as np
import scipy
import pandas as pd
from pytz import timezone

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline, CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume, Returns
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.classifiers.morningstar import Sector
import datetime

def initialize(context):

context.xly = sid(19662) #Consumer Discretionary 102
context.xlp = sid(19659) #Consumer Staples 205
context.xle = sid(19655) #Energy 309
context.xlf = sid(19656) #Financials 103
#context.xlv = sid(19661) #Health Care 306
context.xli = sid(19657) #Industrials 310
context.xlb = sid(19654) #Materials 101
#context.xlre = sid(49472) #Real estate 104
context.xlk = sid(19658) #Technology  311
context.xlu = sid(19660) #Utilities   207

context.sectors = [context.xly,
context.xlp,
context.xle,
context.xlf,
# context.xlv,
context.xli,
context.xlb,
#context.xlre,
context.xlk,
context.xlu
]

context.sector_id = [102,
205,
309,
103,
#306,
310,
101,
#104,
311,
207
]

context.stock_long = []
context.stock_short = []

schedule_function(func = liquidate, date_rule = date_rules.week_end(0), time_rule = time_rules.market_close(minutes = 1))

schedule_function(func = place_order, date_rule = date_rules.week_start(0), time_rule = time_rules.market_open())

attach_pipeline(make_pipeline(context), 'mean_reversion')

set_long_only()
context.enable_short_stock = True

context.today_wd = 0
context.today_month = 0

def make_pipeline(context):

pipe = Pipeline(
columns={
'weekly_return': weekly_return,
'sector': Sector(),
},
# combined_alpha will be NaN for all stocks not in our universe,
# but we also want to make sure that we have a sector code for everything
screen=weekly_return.notnull() & Sector().notnull(),
)
return pipe

print get_datetime('US/Eastern')
context.prev_day_wd = context.today_wd
context.today_wd = get_datetime('US/Eastern').weekday()

#context.prev_day_month = context.today_month
#context.today_month = get_datetime('US/Eastern').month
if context.today_wd > context.prev_day_wd: # used to see if today is the first business day of the week; do nothing and return if it is NOT the week start
#if context.today_month == context.prev_day_month:
return
else: # if it is the first trading day of the week
# week start
trackSectorPerformance(context, data)
if context.bullishSectorReturn > 0 and context.bearishSectorReturn < 0:
getBelowAverageStocksInBestPerformingSector(context, data)

def trackSectorPerformance(context, data):
log.info("in trackSectorPerformance.")
# Rank sectors based on weekly return
prices = data.history(context.sectors, 'price', 7, '1d')[:-1]
daily_ret = prices.pct_change(5)[1:].as_matrix(context.sectors)
weekly_ret = daily_ret[4]

bull = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[-1] # lambda used to sort weekly returns
context.bullishSector = context.sector_id[bull] # identify bullish sectors by ID
context.bullishSectorReturn = weekly_ret[bull]  # identify weekly return for each bullish sector
print context.bullishSectorReturn

log.info(context.bullishSector)

context.bearishSectorReturn = -99
if context.enable_short_stock:
bear = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[0] # lambda used to sort weekly returns
context.bearishSector = context.sector_id[bear]  # identify bearish sectors by ID
context.bearishSectorReturn = weekly_ret[bear]  # identify weekly return for each bearish sector
print context.bullishSectorReturn

log.info("Done trackSectorPerformance.")

def getBelowAverageStocksInBestPerformingSector(context, data):
log.info("in getBelowAverageStocksInBestPerformingSector.")
pipeline_data = pipeline_output('mean_reversion')
bullish_data = pipeline_data[pipeline_data['sector'] == context.bullishSector]

#Filter out above average return stocks
below_average_bullish_data = bullish_data[bullish_data['weekly_return'] < context.bullishSectorReturn]
below_average_bullish_data = below_average_bullish_data.sort_values(by = 'weekly_return')
context.stock_to_long = below_average_bullish_data.index[:]
stock_to_long_returns  = below_average_bullish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_long_weights = context.bullishSectorReturn - stock_to_long_returns
context.stock_to_long_weights = context.stock_to_long_weights.div(context.stock_to_long_weights.sum())

print context.stock_to_long_weights.sum()

if context.enable_short_stock:
bearish_data = pipeline_data[pipeline_data['sector'] == context.bearishSector]

#context.stock_to_short = bearish_data.sort_values(by = 'weekly_return').index[-3:]
#Filter out above average return stocks
above_average_bearish_data = bearish_data[bearish_data['weekly_return'] > context.bearishSectorReturn]
above_average_bearish_data = above_average_bearish_data.sort_values(by = 'weekly_return')
context.stock_to_short = above_average_bearish_data.index[:]
stock_to_short_returns  = above_average_bearish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_short_weights = stock_to_short_returns - context.bearishSectorReturn
context.stock_to_short_weights = context.stock_to_short_weights.div(context.stock_to_short_weights.sum())

log.info("done getBelowAverageStocksInBestPerformingSector.")

def place_order(context, data):
log.info("in place order.")
try:
if len(context.stock_to_long)>0:
for i in range(len(context.stock_to_long)):
order_target_percent(context.stock_to_long[i], context.stock_to_long_weights[i])
context.stock_long = context.stock_to_long
context.stock_to_long = []

if context.enable_short_stock:
if len(context.stock_to_short)>0:
for i in range(len(context.stock_to_short)):
order_target_percent(context.stock_to_short[i], -context.stock_to_short_weights[i])
context.stock_short = context.stock_to_short
context.stock_to_short = []

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)

def liquidate(context, data):
log.info("in liquidate.")
try:
if len(context.stock_long)>0:
log.info("Liquidating longs.")
for i in range(len(context.stock_long)):
order_target_percent(context.stock_long[i], 0)

if context.enable_short_stock:
if len(context.stock_short)>0:
for i in range(len(context.stock_short)):
order_target_percent(context.stock_short[i], 0)

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)


There was a runtime error.

@ Blue sorry that is the wrong code. Correct code that Is not working shown below:

import numpy as np
import scipy
import pandas as pd
from pytz import timezone

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline, CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume, Returns
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.classifiers.morningstar import Sector
import datetime

def initialize(context):

context.target_leverage = 2
context.mx_lvrg  = 0
context.cash_low = 0

context.xly = sid(19662) #Consumer Discretionary 102
context.xlp = sid(19659) #Consumer Staples 205
context.xle = sid(19655) #Energy 309
context.xlf = sid(19656) #Financials 103
#context.xlv = sid(19661) #Health Care 306
context.xli = sid(19657) #Industrials 310
context.xlb = sid(19654) #Materials 101
#context.xlre = sid(49472) #Real estate 104
context.xlk = sid(19658) #Technology  311
context.xlu = sid(19660) #Utilities   207

context.sectors = [context.xly,
context.xlp,
context.xle,
context.xlf,
# context.xlv,
context.xli,
context.xlb,
#context.xlre,
context.xlk,
context.xlu
]

context.sector_id = [102,
205,
309,
103,
#306,
310,
101,
#104,
311,
207
]

context.stock_long = []
context.stock_short = []

schedule_function(func = liquidate, date_rule = date_rules.week_end(0), time_rule = time_rules.market_close(minutes = 1))

schedule_function(func = place_order, date_rule = date_rules.week_start(0), time_rule = time_rules.market_open())

schedule_function(count_positions, date_rules.every_day(), time_rules.market_close())

attach_pipeline(make_pipeline(context), 'mean_reversion')

#set_long_only()
context.enable_short_stock = True

context.today_wd = 0
context.today_month = 0


def make_pipeline(context):

weekly_return = Returns(window_length=6, mask=mask)
pipe = Pipeline(
columns={
'weekly_return': weekly_return,
'sector': Sector(),
},
# combined_alpha will be NaN for all stocks not in our universe,
# but we also want to make sure that we have a sector code for everything
screen=weekly_return.notnull() & Sector().notnull(),
)
return pipe


print get_datetime('US/Eastern')
context.prev_day_wd = context.today_wd
context.today_wd = get_datetime('US/Eastern').weekday()

#context.prev_day_month = context.today_month
#context.today_month = get_datetime('US/Eastern').month
if context.today_wd > context.prev_day_wd: # used to see if today is the first business day of the week; do nothing and return if it is NOT the week start
#if context.today_month == context.prev_day_month:
return
else: # if it is the first trading day of the week
# week start
trackSectorPerformance(context, data)
if context.bullishSectorReturn > 0 and context.bearishSectorReturn < 0:
getBelowAverageStocksInBestPerformingSector(context, data)


def trackSectorPerformance(context, data):
log.info("in trackSectorPerformance.")
# Rank sectors based on weekly return
prices = data.history(context.sectors, 'price', 7, '1d')[:-1]
daily_ret = prices.pct_change(5)[1:].as_matrix(context.sectors)
weekly_ret = daily_ret[4]

bull = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[-1] # lambda used to sort weekly returns
context.bullishSector = context.sector_id[bull] # identify bullish sectors by ID
context.bullishSectorReturn = weekly_ret[bull]  # identify weekly return for each bullish sector
print context.bullishSectorReturn

log.info(context.bullishSector)

context.bearishSectorReturn = -99
if context.enable_short_stock:
bear = sorted(range(len(weekly_ret)), key=lambda i: weekly_ret[i])[0] # lambda used to sort weekly returns
context.bearishSector = context.sector_id[bear]  # identify bearish sectors by ID
context.bearishSectorReturn = weekly_ret[bear]  # identify weekly return for each bearish sector
print context.bullishSectorReturn

log.info("Done trackSectorPerformance.")


def getBelowAverageStocksInBestPerformingSector(context, data):
log.info("in getBelowAverageStocksInBestPerformingSector.")
pipeline_data = pipeline_output('mean_reversion')
bullish_data = pipeline_data[pipeline_data['sector'] == context.bullishSector]

#Filter out above average return stocks
below_average_bullish_data = bullish_data[bullish_data['weekly_return'] < context.bullishSectorReturn]
below_average_bullish_data = below_average_bullish_data.sort_values(by = 'weekly_return')
context.stock_to_long = below_average_bullish_data.index[:]
stock_to_long_returns  = below_average_bullish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_long_weights = context.bullishSectorReturn - stock_to_long_returns
context.stock_to_long_weights = context.stock_to_long_weights.div(context.stock_to_long_weights.sum())

print context.stock_to_long_weights.sum()

if context.enable_short_stock:
bearish_data = pipeline_data[pipeline_data['sector'] == context.bearishSector]

#context.stock_to_short = bearish_data.sort_values(by = 'weekly_return').index[-3:]
#Filter out above average return stocks
above_average_bearish_data = bearish_data[bearish_data['weekly_return'] > context.bearishSectorReturn]
above_average_bearish_data = above_average_bearish_data.sort_values(by = 'weekly_return')
context.stock_to_short = above_average_bearish_data.index[:]
stock_to_short_returns  = above_average_bearish_data['weekly_return']

#Calculate weights based on returns
context.stock_to_short_weights = stock_to_short_returns - context.bearishSectorReturn
context.stock_to_short_weights = context.stock_to_short_weights.div(context.stock_to_short_weights.sum())

log.info("done getBelowAverageStocksInBestPerformingSector.")


def place_order(context, data):

log.info("in place order.")

try:
if len(context.stock_to_long)>0:
for i in range(len(c.stock_to_long)):
val = context.target_leverage * context.stock_to_long_weights[i] * context.portfolio.cash
if context.account.leverage > .7:     # in part avoiding divide by zero
val /= context.account.leverage
order_value(context.stock_to_long[i], val)
order_target_percent(context.stock_to_long[i], context.stock_to_long_weights[i])
context.stock_long = context.stock_to_long
context.stock_to_long = []

if context.enable_short_stock:
if len(context.stock_to_short)>0:
for i in range(len(context.stock_to_short)):
val = context.target_leverage * (-context.stock_to_short_weights[i] * context.portfolio.cash * .5) / (context.account.leverage)
if context.account.leverage > .7:
val /= context.account.leverage
order_value(context.stock_to_short[i], val)
order_target_percent(context.stock_to_short[i], -context.stock_to_short_weights[i])
context.stock_short = context.stock_to_short
context.stock_to_short = []
track_orders(context, data)


def liquidate(context, data):

log.info("in liquidate.")

try:
if len(context.stock_long)>0:
log.info("Liquidating longs.")
for i in range(len(context.stock_long)):
order_target_percent(context.stock_long[i], 0)

if context.enable_short_stock:
if len(context.stock_short)>0:
for i in range(len(context.stock_short)):
order_target_percent(context.stock_short[i], 0)

except KeyError as k:
# so what exactly is update_universe doing that I'm ending up here?
print(k)

# KEEP TRACK OF LEVERAGE BY SCHEDULING FUNCTION ASSIGNED ABOVE AS count_positions


def count_positions(context,data):

longs = 0
for position in context.portfolio.positions.itervalues():
if position.amount > 0:
longs += 1
leverage=context.account.leverage
# asset=context.portfolio.portfolio_value
# record(asset=asset)
record(longs=longs)
record(leverage=leverage)


@Sam Khorsand were you able to resolve the error?

Hi Viridian,
"... to a very reliable degree .... at the end of each December you short the best performing sector and long the second best"

Yes, maybe it works. Think of funds taking profit in best sector, etc. How many samples (1 year = 1 sample) backtest?

has anyone tried applying a simple market filter to this algo? i.e. only go long if SPY > 200 sma, only short if SPY < 200 sma...thinking about that 59% draw down specifically. I have no clue how to code that though.