Hi everyone, I think it would be nice to share and maybe improve together this idea.

My goal is make a portfolio rebalanced monthly that I can trade with real money.

I have tried for quite some time to make momentum strategy but they all have serious problems in some years even if timed with moving averages.

So my idea is this:

Identify with phase the market is by counting sectors that trade above the 240 moving average, and allocate more money to equity by trading these sectors.

For example if uptrending sectors are more than 8 i say that market stregth is high so i allocate a lot in equity and i buy sectors, BUT not all of them , just the 2 worst performing in the short period, so to have a kind of long term momentum and short term mean reverting.

If there are less than 5 sectors up trending it means that the market is not strong so I buy the 2 best performing in the short term, because it’s like they are ‘leading the market in times of uncertainty.

And so on, going to a phase where no sectors are uptrending so all money are on bonds

Actually Money that are not allocated in equity are always allocated in bonds.

Then once I have the sectors that I want to trade (long term trending and short term counter trending most of the time) I select for each of them the best stocks based on piotroski score and volatility.

I had to use single stocks instead of entire sectors because in Italy a retail investor cannot have access to all major ETFs like SPY or XLP, XLU etc.. and the alternatives are expansive and illiquid.

My goals are basically to stay under 20% drawdown, be approximately at 1 for Sharpe ratio, beat the SPY for performance , don’t fall down completely in 2008, 2015 and 2017/18 and I do not really care about alpha and beta as long as the strategy criteria makes sense in my head.

Another thing is that backtesting this code is very slow compared to other backtest I have done and I cannot figure out why.

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 |

""" Sector uptrending means his 20 days mavg is > than his 240 mavg Market trend strenght goes from -2 to 2 depending on the mnumber of sectors uptrending n: number of sectors uptrending ********************************************** Market trend -2 >>>> NO SECTORS UPTREND ALLOCATION : Equity 0% - Bonds 100% Market trend -1 >>>> 0 < n < 2 ALLOCATION: Equity 20% - Bonds 80% Market trend 0 >>>> 2 < n < 5 ALLOCATION: Equity 40% - Bonds 60% Market trend 1 >>>> 5 < n < 8 ALLOCATION: Equity 60% - Bonds 40% Market trend 2 >>>> n > 8 ALLOCATION: Equity 80% - Bonds 20% ********************************************** PARAMETERS USED for picking sectors among the uptrening ones: Market trend = 2 >>>> [-2] the 2 worst performance over last month Market trend = 1 >>>> [-2:] the 2worst performance over last month Market trend = 0 >>>> [:2] the 2 best performance over last month Market trend = -1 >>>> all sectors avaliable Market trend = -2 >>>> no sectors available ********************************************** PERFORMANCE with ETF Returns >>>> 249,73% Alpha >>>> 0,06 Beta >>>> 0,26 Sharpe >>>> 0,83 Drawdown >>>> -14,09% PERFORMANCE with STOCKS Returns >>>> 642,02% Alpha >>>> 0,11 Beta >>>> 0,23 Sharpe >>>> 1,1 Drawdown >>>> -16,57% """ import math import pandas as pd import numpy as np import quantopian.algorithm as algo from quantopian.pipeline import Pipeline from quantopian.pipeline import CustomFactor from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.filters import QTradableStocksUS from quantopian.pipeline.data import Fundamentals from quantopian.pipeline.classifiers.morningstar import Sector from quantopian.pipeline.factors import SimpleMovingAverage, Returns class Piotroski(CustomFactor): inputs = [ Fundamentals.roa, Fundamentals.operating_cash_flow, Fundamentals.cash_flow_from_continuing_operating_activities, Fundamentals.long_term_debt_equity_ratio, Fundamentals.current_ratio, Fundamentals.shares_outstanding, Fundamentals.gross_margin, Fundamentals.assets_turnover, ] window_length = 22 def compute(self, today, assets, out, roa, cash_flow, cash_flow_from_ops, long_term_debt_ratio, current_ratio, shares_outstanding, gross_margin, assets_turnover): profit = ( (roa[-1] > 0).astype(int) + (cash_flow[-1] > 0).astype(int) + (roa[-1] > roa[0]).astype(int) + (cash_flow_from_ops[-1] > roa[-1]).astype(int) ) leverage = ( (long_term_debt_ratio[-1] < long_term_debt_ratio[0]).astype(int) + (current_ratio[-1] > current_ratio[0]).astype(int) + (shares_outstanding[-1] <= shares_outstanding[0]).astype(int) ) operating = ( (gross_margin[-1] > gross_margin[0]).astype(int) + (assets_turnover[-1] > assets_turnover[0]).astype(int) ) out[:] = profit + leverage + operating class ROA(CustomFactor): window_length = 1 inputs = [Fundamentals.roa] def compute(self, today, assets, out, roa): out[:] = (roa[-1] > 0).astype(int) class ROAChange(CustomFactor): window_length = 22 inputs = [Fundamentals.roa] def compute(self, today, assets, out, roa): out[:] = (roa[-1] > roa[0]).astype(int) class CashFlow(CustomFactor): window_length = 1 inputs = [Fundamentals.operating_cash_flow] def compute(self, today, assets, out, cash_flow): out[:] = (cash_flow[-1] > 0).astype(int) class CashFlowFromOps(CustomFactor): window_length = 1 inputs = [Fundamentals.cash_flow_from_continuing_operating_activities, Fundamentals.roa] def compute(self, today, assets, out, cash_flow_from_ops, roa): out[:] = (cash_flow_from_ops[-1] > roa[-1]).astype(int) class LongTermDebtRatioChange(CustomFactor): window_length = 22 inputs = [Fundamentals.long_term_debt_equity_ratio] def compute(self, today, assets, out, long_term_debt_ratio): out[:] = (long_term_debt_ratio[-1] < long_term_debt_ratio[0]).astype(int) class CurrentDebtRatioChange(CustomFactor): window_length = 22 inputs = [Fundamentals.current_ratio] def compute(self, today, assets, out, current_ratio): out[:] = (current_ratio[-1] > current_ratio[0]).astype(int) class SharesOutstandingChange(CustomFactor): window_length = 22 inputs = [Fundamentals.shares_outstanding] def compute(self, today, assets, out, shares_outstanding): out[:] = (shares_outstanding[-1] <= shares_outstanding[0]).astype(int) class GrossMarginChange(CustomFactor): window_length = 22 inputs = [Fundamentals.gross_margin] def compute(self, today, assets, out, gross_margin): out[:] = (gross_margin[-1] > gross_margin[0]).astype(int) class AssetsTurnoverChange(CustomFactor): window_length = 22 inputs = [Fundamentals.assets_turnover] def compute(self, today, assets, out, assets_turnover): out[:] = (assets_turnover[-1] > assets_turnover[0]).astype(int) class Volatility_Daily_Annual(CustomFactor): inputs = [USEquityPricing.close] window_length = 120 def compute(self, today, assets, out, close): # [0:-1] is needed to remove last close since diff is one element shorter daily_returns = np.diff(close, axis = 0) / close[0:-1] out[:] = daily_returns.std(axis = 0) * math.sqrt(252) def initialize(context): """ Called once at the start of the algorithm. """ context.equity_allocation = 0 context.hedge_allocation = 0 context.sectors_weigth = {} context.market_trend = 0 context.sectors_to_buy = [] context.sectors_weight = {} context.sectors_number_to_buy = [] context.sectors_dict = {sid(19654) : 101, #XLB Materials sid(19662): 102, #XLY Consumer Cyclical #sid(19656): 103, #XLF Financials sid(19659): 205, #XLP Consumer Defensive sid(19661): 206, #XLV Healthcare sid(19660): 207, #XLU Utilities sid(19655): 309, #XLE Energy sid(19657): 310, #XLI Industrials sid(19658): 311} #XLK Tech context.sectors_list = context.sectors_dict.keys() context.bonds = [sid(23921), #TLT sid(25801) #TIP ] # Rebalance every month, 1 hour after market open. algo.schedule_function( rebalance, algo.date_rules.month_start(), algo.time_rules.market_open(hours=1), ) # Record tracking variables at the end of each day. algo.schedule_function( record_vars, algo.date_rules.month_start(), algo.time_rules.market_open(hours=1), ) # Create our dynamic stock selector. algo.attach_pipeline(make_pipeline(), 'pipeline') def make_pipeline(): """ A function to create our dynamic stock selector (pipeline). Documentation on pipeline can be found here: https://www.quantopian.com/help#pipeline-title """ # Base universe set to the QTradableStocksUS base_universe = QTradableStocksUS() # sector code sector = Sector() # ev_ebitda ev_to_ebitda = Fundamentals.ev_to_ebitda.latest #returns returns = Returns(window_length = 120, mask=base_universe) # std volatility = Volatility_Daily_Annual() #market_cap market_cap = Fundamentals.market_cap.latest # fundamental_screen ev_screen = 0.0 < ev_to_ebitda < 10.0 profit = ROA() + ROAChange() + CashFlow() + CashFlowFromOps() leverage = LongTermDebtRatioChange() + CurrentDebtRatioChange() + SharesOutstandingChange() operating = GrossMarginChange() + AssetsTurnoverChange() piotroski = profit + leverage + operating piotroski_screen = ((piotroski >= 7) | (piotroski <= 3)) market_cap_screen = market_cap > 1e9 pipe = Pipeline( columns={ 'sector': sector, 'piotroski': piotroski, 'ret': returns, 'vol' : volatility }, screen= (base_universe & ev_screen & piotroski_screen & market_cap_screen ) ) return pipe def before_trading_start(context, data): """ Called every day before market open. """ stocks_to_buy = [] sectors_to_buy = [] sectors_number_to_buy = [] ########## ETF SELECTION ############# p = data.history(context.sectors_list, 'close', 252,'1d') mean_20= p.rolling(20).mean().iloc[-1] mean_240 = p.rolling(240).mean().iloc[-1] ratio = mean_20/mean_240 uptrending_sectors = ratio.where(ratio > 1.0).dropna() last_month_performance = p.pct_change(periods= 30).iloc[-1] #ascending = false significa in alto i maggiori valori last_month_sorted = last_month_performance.sort_values(ascending = False) last_month_sorted = last_month_sorted[last_month_sorted.index.isin(uptrending_sectors.index)] uptrending_sectors_list = uptrending_sectors.index.tolist() ######### EQUITY ALLOCATION BASED ON TREND ################ n = len(uptrending_sectors_list) # ----------- MOST OF THE SECTORS ARE UPTRENDING ------------- # if n >= 8: market_trend = 2 equity_allocation = 0.8 #peggiori rendimenti ultimo mese sectors_to_buy = last_month_sorted[-2:].index.tolist() # ----------- MORE THAN HALF OF THE SECTORS ARE UPTRENDING ------------- # elif 5 < n < 8: market_trend = 1 equity_allocation = 0.6 sectors_to_buy = last_month_sorted[-2:].index.tolist() # ----------- LESS THAN HALF OF THE SECTORS ARE UPTRENDING ------------- # elif 2 < n <= 5: market_trend = 0 equity_allocation = 0.4 sectors_to_buy = last_month_sorted[:2].index.tolist() # ----------- FEW OF THE SECTORS ARE UPTRENDING ------------- # elif 1 < n <= 2: market_trend = -1 equity_allocation = 0.2 sectors_to_buy = last_month_sorted.index.tolist() # ----------- NO SECTOR UPTREND ------------- # elif n <= 1: market_trend = -2 equity_allocation = 0 sectors_to_buy = [] hedge_allocation = 0.9 - equity_allocation for k, v in context.sectors_dict.iteritems(): if k in sectors_to_buy: sectors_number_to_buy.append(v) ########## PIPELINE FILTER BASED ON PREVIOUSLY SELECTED SECTORS ######### pipe = algo.pipeline_output('pipeline') grouped = pipe.groupby('sector') for sector, group in grouped: if sector in sectors_number_to_buy: group['ranked_vol'] = group['vol'].rank(ascending = False) #low volatility group['ranked_returns'] = group['ret'].rank(ascending = True) # high returns group['ranked_pio'] = group['piotroski'].rank(ascending = True) #high piotroski score group['total_rank'] = group['ranked_vol'] + group['ranked_returns'] + group['ranked_pio'] stocks_to_buy_for_this_sector =group['total_rank'].dropna().nlargest(2).index.tolist() stocks_to_buy = stocks_to_buy + stocks_to_buy_for_this_sector # Make global variables to plot lines etc context.stocks_to_buy = stocks_to_buy context.hedge_allocation = hedge_allocation context.market_trend = market_trend context.equity_allocation = equity_allocation context.sectors_to_buy = sectors_to_buy def rebalance(context, data): ''' print '-------------REBALANCE-------------' print 'equity allocation %s' %(context.equity_allocation) print 'bonds allocation %s' %(context.hedge_allocation) print 'sectors to buy %s' %( context.sectors_to_buy) print 'portfolio positions %s' %(context.portfolio.positions.keys()) print 'stocks to buy: %s' %(context.stocks_to_buy) ''' # ///////////// HEDGING /////////// for bond in context.bonds: order_target_percent(bond, context.hedge_allocation/len(context.bonds)) # ******* STOCKS ORDERS ****** # for stock in context.portfolio.positions : if stock not in context.stocks_to_buy and stock not in context.bonds: order_target_percent(stock, 0) for stock in context.stocks_to_buy : if get_open_orders(stock): continue else: order_target_percent(stock, context.equity_allocation / len(context.stocks_to_buy)) def record_vars(context, data): """ Plot variables at the end of each day. """ pass def handle_data(context, data): """ Called every minute. """ record(leverage=context.account.leverage, trend = context.market_trend, equity_allocation = context.equity_allocation, n_sectors = len(context.sectors_to_buy))