Back to Community
Momentum, Value, and a little bit of mean reverting

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.

Clone Algorithm
69
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
"""
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))
There was a runtime error.
39 responses

Just a thought instead of using the rolling mean logic could you put the simple20MA and simple240MA into the pipeline. Not sure if it would speed things up but i would look at that logic for slow down

i don't know, in the original idea MAVGs are used to select sectors and obtain the market strength based on the number of uptrending sectors, and only then i use this selection to do she stock picking into the pipeline.

Guiseppe

Instead of equal weight for your total_rank i weighted the score giving more weight to the returns
group['total_rank'] = (group['ranked_vol'] * .2) + (group['ranked_returns'] * .5) + (group['ranked_pio'] * .3)

i used a small time frame 01/01/2017 - 10/28/2019 only because it is taking so long to run needs more testing but and results improved

unweighted 01/01/2017-10/28/19 ret: 57.42 alpha .11 beta .41 sharp 1.46 draw -9.44
weighted 01/01/2017-10/28/19 ret:79.98 alpha.16 beta .46 sharp 1.56 draw -11.12

thank you Frank, happy to see some feedback.
the fact that weights are hard coded worries me because there are years where the market insist on best performing stocks and other years where it does not, but your idea of calibrating the weights makes absolutely sense.

I am still tying to uderstando this warning beacuse maybe it's the reason why backtest is so slow:
'A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead'

interesting strategy, were you able to see why the backtest is slow?

Giuseppe,

This algo is one of the old-fashioned gems on Quantopian forum.
To run it faster I just move, what you are doing in before_trading_start(), to rebalance().
I also change leverage to 1.0.
Thank you for sharing.

Loading notebook preview...
Notebook previews are currently unavailable.

The algo has a lot of space for improvement.
In this backtest I have changed just one parameter: number of stocks in sector from 2 to 3.
Guy Fleury can send it to the sky.
Here is backtest results.

Loading notebook preview...
Notebook previews are currently unavailable.

You guys are far ahead of me. Vladmir would you mind (and Giuseppe doesn't mind either) posting the new code with the before trading to rebalance. I made a few changes, very basic the weighting that as Giuseppe points out is not dynamic but could be based on market trend and I added one additional filter to only select issues where fcf_yield is > zero. This seems to be a good filter. the results for a 500k portfolio granted only for a short period of time went to

fcf_yield_filter and weighted 500k start: 01/01/2017- end:10/31/2019 ret:86.51% alpha: .17 beta:.43 sharpe: 1.74 draw: -9.32

thanks for sharing

frank,

For some reason, known only to Quantopian staff, I could not attach my backtests.
Here is my code.

# Momentum, Value, and a little bit of mean reverting by Giuseppe Moriconi, modified by Vladimir  
# https://www.quantopian.com/posts/momentum-value-and-a-little-bit-of-mean-reverting  
from quantopian.pipeline.factors import SimpleMovingAverage, Returns  
from quantopian.algorithm import attach_pipeline, pipeline_output  
from quantopian.pipeline.classifiers.morningstar import Sector  
from quantopian.pipeline.data.builtin import USEquityPricing  
from quantopian.pipeline.filters import QTradableStocksUS  
from quantopian.pipeline.data import Fundamentals  
from quantopian.pipeline import CustomFactor  
from quantopian.pipeline import Pipeline  
import pandas as pd  
import numpy as np  
import math

def initialize(context):  
    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 = {  
                        symbol('XLB'): 101,      # XLB Materials  
                        symbol('XLY'): 102,      # XLY Consumer Cyclical  
                        # symbol('XLF'): 103,    # XLF Financials  
                        symbol('XLP'): 205,      # XLP Consumer Defensive  
                        symbol('XLV'): 206,      # XLV Healthcare  
                        symbol('XLU'): 207,      # XLU Utilities  
                        symbol('XLE'): 309,      # XLE Energy  
                        symbol('XLI'): 310,      # XLI Industrials  
                        symbol('XLK'): 311,      # XLK Tech  
                        }  
    context.sectors_list = context.sectors_dict.keys()  
    context.bonds = [symbol('TLT'),              # TLT  
                     symbol('TIP'),              # TIP, IEF  
                     ]  
    schedule_function(rebalance, date_rules.month_start(), time_rules.market_open(hours=1),)  
    schedule_function(record_vars, date_rules.month_start(), time_rules.market_open(hours=1),)

    base_universe = QTradableStocksUS()  
    sector = Sector()                                            # sector code  
    ev_to_ebitda = Fundamentals.ev_to_ebitda.latest              # ev_ebitda  
    returns = Returns(window_length = 120, mask=base_universe)   # returns  
    volatility = Volatility_Daily_Annual()                       # std  
    market_cap = Fundamentals.market_cap.latest                  # market_cap  
    ev_screen = 0.0 < ev_to_ebitda < 10.0                        # fundamental_screen  
    # ev_screen = (0.0 < ev_to_ebitda) & (ev_to_ebitda < 10.0)   # fundamental_screen  
    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  
    columns = {'sector': sector, 'piotroski': piotroski, 'ret': returns, 'vol' : volatility}  
    screen = (base_universe & ev_screen & piotroski_screen & market_cap_screen)  
    pipe = Pipeline(columns, screen)  
    attach_pipeline(pipe, 'pipeline')

def rebalance(context, data):  
    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 = 1.0 - 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 = 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(3).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  


    '''  
    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)  
    '''  
    for bond in context.bonds:  
        order_target_percent(bond, context.hedge_allocation/len(context.bonds))

    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):  
    record(leverage=context.account.leverage, trend = context.market_trend, equity_allocation = context.equity_allocation, n_sectors = len(context.sectors_to_buy))  

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)  

Thank you Vladimir and Frank!
Frank, the fcf_yield filter makes a lot of sense, i will add it for shure.

Vladimir, the method i used was to work with ETFs only and then, once the ETF part was tradable, i added stocks with pipeline, so the parameters 'optimization' was done with ETF only, however having not so many stocks in portfolio increases very much his volatility maybe just because one of these stocks moves a lot, and that is what happend yesterday for example :(
So what i am considering is to follow your suggestion to increase to 3 the number of sectors to have more 'fundamental diversification' or to introduce a minimum variance optimization on the stocks in porfolio in order to allocate less money on more volatile stocks.
Adding secotrs makes much more sense (in my head) because after all volatility is kind of cyclical so less volatile portfolios today can be more volatile afer 2 weeks, and also like every optimization is very dependent on the lenght of the lookback window.

This is an updated version with the fcf_yield filter , num sectors up to 3 (with acceptable testing speed)

Clone Algorithm
14
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Momentum, Value, and a little bit of mean reverting by Giuseppe Moriconi, modified  
# https://www.quantopian.com/posts/momentum-value-and-a-little-bit-of-mean-reverting  
from quantopian.pipeline.factors import SimpleMovingAverage, Returns  
from quantopian.algorithm import attach_pipeline, pipeline_output  
from quantopian.pipeline.classifiers.morningstar import Sector  
from quantopian.pipeline.data.builtin import USEquityPricing  
from quantopian.pipeline.filters import QTradableStocksUS  
from quantopian.pipeline.data import Fundamentals  
from quantopian.pipeline import CustomFactor  
from quantopian.pipeline import Pipeline  
import pandas as pd  
import numpy as np  
import math

def initialize(context):  
    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 = {  
                        symbol('XLB'): 101,      # XLB Materials  
                        symbol('XLY'): 102,      # XLY Consumer Cyclical  
                        # symbol('XLF'): 103,    # XLF Financials  
                        symbol('XLP'): 205,      # XLP Consumer Defensive  
                        symbol('XLV'): 206,      # XLV Healthcare  
                        symbol('XLU'): 207,      # XLU Utilities  
                        symbol('XLE'): 309,      # XLE Energy  
                        symbol('XLI'): 310,      # XLI Industrials  
                        symbol('XLK'): 311,      # XLK Tech  
                        }  
    context.sectors_list = context.sectors_dict.keys()  
    context.bonds = [symbol('TLT'),              # TLT  
                     symbol('TIP'),              # TIP, IEF  
                     ]  
    schedule_function(rebalance, date_rules.month_start(), time_rules.market_open(hours=1),)  
    schedule_function(record_vars, date_rules.month_start(), time_rules.market_open(hours=1),)

    base_universe = QTradableStocksUS()  
    fcf_yield = Fundamentals.fcf_yield.latest
    sector = Sector()                                            # sector code  
    ev_to_ebitda = Fundamentals.ev_to_ebitda.latest              # ev_ebitda  
    returns = Returns(window_length = 120, mask=base_universe)   # returns  
    volatility = Volatility_Daily_Annual()                       # std  
    market_cap = Fundamentals.market_cap.latest                  # market_cap  
    ev_screen = 0.0 < ev_to_ebitda < 10.0                        # fundamental_screen  
    # ev_screen = (0.0 < ev_to_ebitda) & (ev_to_ebitda < 10.0)   # fundamental_screen  
    fcf_yield_screen = fcf_yield > 0                             # fundamental_screen  
    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  
    
    columns = {'sector': sector, 'piotroski': piotroski, 'ret': returns, 'vol' : volatility}  
    screen = (base_universe & ev_screen & piotroski_screen & market_cap_screen & fcf_yield_screen)  
    pipe = Pipeline(columns, screen)  
    attach_pipeline(pipe, 'pipeline')

def rebalance(context, data):  
    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[-3:].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[-3:].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 = 1.0 - 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 = 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(3).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  


    '''  
    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)  
    '''  
    for bond in context.bonds:  
        order_target_percent(bond, context.hedge_allocation/len(context.bonds))

    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):  
    record(leverage=context.account.leverage, trend = context.market_trend, equity_allocation = context.equity_allocation, n_sectors = len(context.sectors_to_buy))  

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)  
There was a runtime error.

looks good thanks for sharing

Giuseppe,

My recommendation was 3 stocks in 2 sectors without fcf_yield filter.
Try to backtest the code I posted above without any changes.
It will perform 1.5 times better.

my fault!
i have backtested your version right now and it's really impressive for me thank you!
(i attached your version)

Clone Algorithm
88
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Momentum, Value, and a little bit of mean reverting by Giuseppe Moriconi, modified by Vladimir  
# https://www.quantopian.com/posts/momentum-value-and-a-little-bit-of-mean-reverting  
from quantopian.pipeline.factors import SimpleMovingAverage, Returns  
from quantopian.algorithm import attach_pipeline, pipeline_output  
from quantopian.pipeline.classifiers.morningstar import Sector  
from quantopian.pipeline.data.builtin import USEquityPricing  
from quantopian.pipeline.filters import QTradableStocksUS  
from quantopian.pipeline.data import Fundamentals  
from quantopian.pipeline import CustomFactor  
from quantopian.pipeline import Pipeline  
import pandas as pd  
import numpy as np  
import math

def initialize(context):  
    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 = {  
                        symbol('XLB'): 101,      # XLB Materials  
                        symbol('XLY'): 102,      # XLY Consumer Cyclical  
                        # symbol('XLF'): 103,    # XLF Financials  
                        symbol('XLP'): 205,      # XLP Consumer Defensive  
                        symbol('XLV'): 206,      # XLV Healthcare  
                        symbol('XLU'): 207,      # XLU Utilities  
                        symbol('XLE'): 309,      # XLE Energy  
                        symbol('XLI'): 310,      # XLI Industrials  
                        symbol('XLK'): 311,      # XLK Tech  
                        }  
    context.sectors_list = context.sectors_dict.keys()  
    context.bonds = [symbol('TLT'),              # TLT  
                     symbol('TIP'),              # TIP, IEF  
                     ]  
    schedule_function(rebalance, date_rules.month_start(), time_rules.market_open(hours=1),)  
    schedule_function(record_vars, date_rules.month_start(), time_rules.market_open(hours=1),)

    base_universe = QTradableStocksUS()  
    sector = Sector()                                            # sector code  
    ev_to_ebitda = Fundamentals.ev_to_ebitda.latest              # ev_ebitda  
    returns = Returns(window_length = 120, mask=base_universe)   # returns  
    volatility = Volatility_Daily_Annual()                       # std  
    market_cap = Fundamentals.market_cap.latest                  # market_cap  
    ev_screen = 0.0 < ev_to_ebitda < 10.0                        # fundamental_screen  
    # ev_screen = (0.0 < ev_to_ebitda) & (ev_to_ebitda < 10.0)   # fundamental_screen  
    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  
    columns = {'sector': sector, 'piotroski': piotroski, 'ret': returns, 'vol' : volatility}  
    screen = (base_universe & ev_screen & piotroski_screen & market_cap_screen)  
    pipe = Pipeline(columns, screen)  
    attach_pipeline(pipe, 'pipeline')

def rebalance(context, data):  
    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 = 1.0 - 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 = 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(3).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  


    '''  
    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)  
    '''  
    for bond in context.bonds:  
        order_target_percent(bond, context.hedge_allocation/len(context.bonds))

    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):  
    record(leverage=context.account.leverage, trend = context.market_trend, equity_allocation = context.equity_allocation, n_sectors = len(context.sectors_to_buy))  

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)  
There was a runtime error.

Thank you, Giuseppe.

Here is its performance tear-sheet.

Loading notebook preview...
Notebook previews are currently unavailable.

@ Giuseppe

Just a question, why you are including the small and big P-Score numbers?

I was playing a little bit with the code and if you include only the >= 7 it will give almost the same result as including <=3 too.
Theoretically the <3 are the more unstable company’s...but as well strongly leveraged which could result in better short earnings and prices increase - interesting ideas
Could you please comment on your thinking about that.

Thanks
Carsten

Hi @Giuseppe & @Vladimir, nice work and of interest to me for my personal trading. Here in Q i always work with Equity Long-Short models. I will convert what you have done to that sort of algo and let you know what i find, if you are interested. Best wishes, ciao, TonyM.

Hi Giuseppe, as i'm going through your code, trying to work out exactly what you are doing and why, i have some questions for clarification, for example:

You define profit = ROA() + ROAChange() + CashFlow() + CashFlowFromOps(), with 0/1 output for each individual term, as i understand it, and therefore integer result between 0 and 4.
ROA comes from class ROA(CustomFactor) and this has an output 1 or 0 depending on whether ROA is > 0 or not. OK so far.
Now you have ROAChange, with window length 22. As i understand it, you are comparing current ROA with the ROA from 1 trading month (21 days) earlier, is that correct? It seems a plausible enough thing to do, but ROA is not a price-dependent variable and will only change whenever either the reported earnings (the "RO" part) changes or else the corporate assets (the "A" part) changes, neither of which will happen until the next corporate reporting period, presumably every 3 months, and then when it does happen you will have introduced a lag of another month due to the window length.
So most of the time, for periods of 3 months at a time, the ROA value will not vary and neither will the ROAchange vary either. Maybe that's OK for you, but why the 22d window length for the ROAchange? Please correct me if i'm wrong, but as i see it, all you are doing with that is introducing a completely unnecessary additional 1 month lag in your calculations.

In the "CashFlow" part, you are checking for positive CF, which is OK
In the "CashFlowFromOps" part, you are comparing "cash_flow_from_ops" data, which is a $ number vs "roa" which is a % or a ratio of return (i.e Earnings) vs Assets. Please correct me if i'm wrong here, but this does not seem to make sense because it is an "apples vs oranges" type calculation. Preferably you need to convert both to $ terms first, or else use Earnings instead of ROA.

The LongTerm Debt to Equity and short term CR calculations make sense. Either LTDebt going down or CR going up are indicative of financial strength and give you positive score contributions. OK, although again the issue of window length causing a lag so that you will not get to see any change until a month after it is reported.

Your Piotroski screen concept looks OK, and i use something similar myself, but i think your results might possibly be suffering because of the time lag in some of the individual terms, as described above.

All of the fundamental items like ROA, CFops, Debt ratios, and so on are all basically measures of Financial or Operational Strength, and therefore essentially they are measures of QUALITY of the company, as is the resultant Piotroski score. That's excellent, but none of these items are actually measures of VALUE as such, which requires a comparison or ratio between share price $ and something else such as earnings or whatever that is also expressed in $. So, what i think you are doing is a very nice job with looking at individual stock and the Sector price changes, and at the underlying quality or financial health of each company share, but that does not actually tell you whether the shares are currently under-priced or over-priced compared to some independent measures of value (such as PE or P-to-Book, or PEG ratio, or any other price ratios). I think you could potentially improve your algo results further by adding some more direct value measures.

I hope these comments might be helpful, and i look forward to more discussion with you. Best regards. Ciao.

Thank you very much Carsten and Tony! first of all i have made a test to check that the piotrosky score rank provides an actual value to the strategy before starting to test your indications, right now i have checked the CashFlowFromOps "dollars/percent" and i think it is actually an error thank you.
I also think that in this strategy the fundamental part is weak because it tells nothing about value, and i will try to substitute it with some meore direct factors like the ones described here but keeping the sectors logic as it is.
So if the original code is

group['total_rank'] = group['ranked_vol'] + group['ranked_returns'] + group['ranked_pio']  

this means that (after the sectors selection) i rank my universe on Low Volatility, High Returns, and High Piotrosky Score

  • and this (the vladimir version wich is the best one) gives
    Returns: 10.22
    Sharpe: 1.34
    Drawdown: -0.14
  • then i commented out the piotrosky score contibution to the rank and i got this
group['total_rank'] = group['ranked_vol'] + group['ranked_returns'] #+ group['ranked_pio']  
Clone Algorithm
6
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Momentum, Value, and a little bit of mean reverting by Giuseppe Moriconi, modified by Vladimir  
# https://www.quantopian.com/posts/momentum-value-and-a-little-bit-of-mean-reverting  
from quantopian.pipeline.factors import SimpleMovingAverage, Returns  
from quantopian.algorithm import attach_pipeline, pipeline_output  
from quantopian.pipeline.classifiers.morningstar import Sector  
from quantopian.pipeline.data.builtin import USEquityPricing  
from quantopian.pipeline.filters import QTradableStocksUS  
from quantopian.pipeline.data import Fundamentals  
from quantopian.pipeline import CustomFactor  
from quantopian.pipeline import Pipeline  
import pandas as pd  
import numpy as np  
import math

def initialize(context):  
    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 = {  
                        symbol('XLB'): 101,      # XLB Materials  
                        symbol('XLY'): 102,      # XLY Consumer Cyclical  
                        # symbol('XLF'): 103,    # XLF Financials  
                        symbol('XLP'): 205,      # XLP Consumer Defensive  
                        symbol('XLV'): 206,      # XLV Healthcare  
                        symbol('XLU'): 207,      # XLU Utilities  
                        symbol('XLE'): 309,      # XLE Energy  
                        symbol('XLI'): 310,      # XLI Industrials  
                        symbol('XLK'): 311,      # XLK Tech  
                        }  
    context.sectors_list = context.sectors_dict.keys()  
    context.bonds = [symbol('TLT'),              # TLT  
                     symbol('TIP'),              # TIP, IEF  
                     ]  
    schedule_function(rebalance, date_rules.month_start(), time_rules.market_open(hours=1),)  
    schedule_function(logging, date_rules.every_day(), time_rules.market_open(hours=1),)  
    schedule_function(record_vars, date_rules.month_start(), time_rules.market_open(hours=1),)

    base_universe = QTradableStocksUS()  
    sector = Sector()                                            # sector code  
    ev_to_ebitda = Fundamentals.ev_to_ebitda.latest              # ev_ebitda  
    returns = Returns(window_length = 120, mask=base_universe)   # returns  
    volatility = Volatility_Daily_Annual()                       # std  
    market_cap = Fundamentals.market_cap.latest                  # market_cap  
    ev_screen = 0.0 < ev_to_ebitda < 10.0                        # fundamental_screen  
    # ev_screen = (0.0 < ev_to_ebitda) & (ev_to_ebitda < 10.0)   # fundamental_screen  
    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  
    columns = {'sector': sector, 'piotroski': piotroski, 'ret': returns, 'vol' : volatility}  
    screen = (base_universe & ev_screen & piotroski_screen & market_cap_screen)  
    pipe = Pipeline(columns, screen)  
    attach_pipeline(pipe, 'pipeline')

def rebalance(context, data):  
    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 = 1.0 - 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 = 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(3).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  


     
    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)  
    
    for bond in context.bonds:  
        order_target_percent(bond, context.hedge_allocation/len(context.bonds))

    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):  
    
    record(leverage=context.account.leverage, trend = context.market_trend, equity_allocation = context.equity_allocation, n_sectors = len(context.sectors_to_buy))  
    
def logging(context, data):
    cpp = context.portfolio.positions
    for s in cpp:
        print s.symbol
        print s.asset_name
        
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)
There was a runtime error.

and if i leave the piotrosky ranking logic as the only one i have this

group['total_rank'] =  group['ranked_pio']  
Clone Algorithm
3
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Momentum, Value, and a little bit of mean reverting by Giuseppe Moriconi, modified by Vladimir  
# https://www.quantopian.com/posts/momentum-value-and-a-little-bit-of-mean-reverting  
from quantopian.pipeline.factors import SimpleMovingAverage, Returns  
from quantopian.algorithm import attach_pipeline, pipeline_output  
from quantopian.pipeline.classifiers.morningstar import Sector  
from quantopian.pipeline.data.builtin import USEquityPricing  
from quantopian.pipeline.filters import QTradableStocksUS  
from quantopian.pipeline.data import Fundamentals  
from quantopian.pipeline import CustomFactor  
from quantopian.pipeline import Pipeline  
import pandas as pd  
import numpy as np  
import math

def initialize(context):  
    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 = {  
                        symbol('XLB'): 101,      # XLB Materials  
                        symbol('XLY'): 102,      # XLY Consumer Cyclical  
                        # symbol('XLF'): 103,    # XLF Financials  
                        symbol('XLP'): 205,      # XLP Consumer Defensive  
                        symbol('XLV'): 206,      # XLV Healthcare  
                        symbol('XLU'): 207,      # XLU Utilities  
                        symbol('XLE'): 309,      # XLE Energy  
                        symbol('XLI'): 310,      # XLI Industrials  
                        symbol('XLK'): 311,      # XLK Tech  
                        }  
    context.sectors_list = context.sectors_dict.keys()  
    context.bonds = [symbol('TLT'),              # TLT  
                     symbol('TIP'),              # TIP, IEF  
                     ]  
    schedule_function(rebalance, date_rules.month_start(), time_rules.market_open(hours=1),)  
    schedule_function(logging, date_rules.every_day(), time_rules.market_open(hours=1),)  
    schedule_function(record_vars, date_rules.month_start(), time_rules.market_open(hours=1),)

    base_universe = QTradableStocksUS()  
    sector = Sector()                                            # sector code  
    ev_to_ebitda = Fundamentals.ev_to_ebitda.latest              # ev_ebitda  
    returns = Returns(window_length = 120, mask=base_universe)   # returns  
    volatility = Volatility_Daily_Annual()                       # std  
    market_cap = Fundamentals.market_cap.latest                  # market_cap  
    ev_screen = 0.0 < ev_to_ebitda < 10.0                        # fundamental_screen  
    # ev_screen = (0.0 < ev_to_ebitda) & (ev_to_ebitda < 10.0)   # fundamental_screen  
    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  
    columns = {'sector': sector, 'piotroski': piotroski, 'ret': returns, 'vol' : volatility}  
    screen = (base_universe & ev_screen & piotroski_screen & market_cap_screen)  
    pipe = Pipeline(columns, screen)  
    attach_pipeline(pipe, 'pipeline')

def rebalance(context, data):  
    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 = 1.0 - 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 = 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_pio']  
            stocks_to_buy_for_this_sector =group['total_rank'].dropna().nlargest(3).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  


     
    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)  
    
    for bond in context.bonds:  
        order_target_percent(bond, context.hedge_allocation/len(context.bonds))

    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):  
    
    record(leverage=context.account.leverage, trend = context.market_trend, equity_allocation = context.equity_allocation, n_sectors = len(context.sectors_to_buy))  
    
def logging(context, data):
    cpp = context.portfolio.positions
    for s in cpp:
        print s.symbol
        print s.asset_name
        
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)
There was a runtime error.

@ Tony of course i am very interested to see if the logic is good for long/short also, i have made quite some tests on this strategy just working on sectors and i have spent most of the time on that (no stocks) to see if that sort of short term mean reversion on a long term momentum makes some sense and actually it does consistently.
So i think that flipping that logic upside down is useful to have a first approach to the long/short strategy

@ Giuseppe

Sorry, I was not saying that you should delete the small p-score... :) no, that was not my intention!
I’m just saying I’m not understanding why you are doing that!

Ok, I was playing a little around with you code. It seems to me that there was a very small difference if you leave the <3 company’s out...but maybe it’s a smart idea to leave them in??? Maybe we get a little bit smoother ride. But I would like to understand the reason.

I was trying to run simulations on the quantopian platform, but it took very long...so I stopped.
I’m trying to install now everything on my Mac after the holidays. Do you think that’s faster? (The last months I was using MATLAB)

I initially started with factor investing as my target/holly grail after reading “what works on Wallstreet” from O’Shaughnessy.
He as well runs a Factor based fond. The book and the website from his company will give you a ton ideas about the value concept in different shades...
But probably you already know.
Especially if you search his recent presentations in the Internet you find new definitions about his composite factors value, quality and the other he uses.
The only bad news is, actually value does not work well during the last...at least 5 years.

If one wants to do factors investing, there is still one idea which worked quite nice:
GARP - growth at a reasonable price.
If you check for Liam Flavelle on seeking alpha you find some ideas which worked nicely.
Around 30% in backtest and I still saw them running paper trading in the beginning of 19 with around that amount.
(Unfortunately his Plattform investors edge does not exist anymore)

What I understand, after the big crash you have the first years value outperforming and the last years growth...and than the blow of and repeat... :)
Unfortunately during 2013, 2015/16 Value was a nice option.
That means you either can predict what you should select or run 2 strategies or a kind of hybrid.

Now coming back to the p-scores, and how you combined it, maybe that is a solution. Going for the >7 to get the value and <3 to get some growth stocks. Maybe....

Than I like very much the idea with the sectors...
I read the book from William O’Neil and tried the concept of buying momentum and growth.
There were moments were you could make a ton of money...April 18 to Sep 18 and Jan 19 to mid 19. In 2018 started to read a ton of books, so I just started that time and in Jan 19 I was to unsure if I should go in, and in April I was basically a bit late.(with money management I need alt least 2 month to get to 100%)
BUT if I would have followed that approach 100% would be possible, each year.
The only problem is to know if the boundary conditions are ok. If you are in a sideward market you will heavily loose.
If the S&P and the Russel 2000 go up in parallel you just need to check which sub sector goes up an buy the best performing stocks. 2018 it was mainly medical and 2019 it was software/security stocks.
It’s quite close what you are doing - if you skip the value and quality.
O’Neil use a momentum und a lot of other stuff, but I think the main driver are earnings acceleration and sales acceleration.

An other ideas would be, to decide on macro level in which market condition we are in and than allocate to different strategies, value, growth and so on and not to do everything in one strategy.

Complicated stuff...I still need to learn a lot...

@ Giuseppe

...and you might check https://alphaarchitect.com/

They show a method how you can evaluate which of the five factors had contributed to the market return.

Hi @ Giuseppe & Carsten & friends,

I have been working for several years with EquityLS models in Q using some concepts that are fairly similar to what you have here. Occasionally i make a few $ in the Contests, so i can assure you that the sort of ideas that you are using here do work well in EquityLS. Yes, you are on the right track :-)

As Carsten says, O’Shaughnessy's "What Works on Wallstreet" is a good book and i used various versions of it for many years. There are several other recent good easy reading books on the fundamentals side, as well as Aswath Damodaran's excellent books on valuation and some good free material on his website that is well worth looking at.

Carsten's comment : " ... actually value does not work well during the last...at least 5 years". is worth thinking a bit more about.......
Basically everyone in the market is looking for alpha, from whatever sources or factors they can find it. Some sources "wear out" as inefficiencies get arbitraged away and never reappear, but others come back again as different types of investing move in and out of fashion with the big funds. For example, sometimes Momentum stocks are most popular, at other times it is Big Caps, or Growth, or Value, or sometimes specific Sectors or other investing styles, and generally these tend to persist for a while, before gradually changing to the next fad or fashion.

So, as i see it, there are really 3 different components to successful investing or trading. The first component is Price Action, obviously, because this is what we profit directly from. The second component is Fundamentals and other non-price data and all the different associated Factors that can be related to alpha. The third component, as i see it, is what i call for want of a better name "Current Market Fashion" or what is currently the hottest investing style or sector or whatever else it is that is popular now. All other things equal, stocks that are in the current "Fashion" group will tend to do better than stocks that are not. And within that group of what is hot, "the rising tide tends to lift all boats", as they say.

So, what i believe is worth doing (and what i am working on myself at the moment) in addition to the price & fundamentals aspects that Giuseppe has already here in this algo, is ALSO to identify the current market fashion (e.g. the leading Investment Style), then Buy stocks in that group and Sell stocks that are not in that group, as part of an EquityLS strategy. Does this make sense to you? Anyone interested in picking up on this and continuing our discussion further with some experimental algos that involve other potential "Fashion" components in addition to just Sectors?

@ Tony

that was as well my finding after understanding the factor investing. One either has to wait and hope or try to find out what is the actual “fashion”.
The first ideas was to run the pure 5 factors, see if they were trending and than allocate accordingly.
I did this on the investors edge platform just for value long/short (similar to portfolio 123) and the first results were very noisy. Unfortunately the platform closed and I could not continue (the data vendor doubled price and there were to few users as it was new)
But maybe on could have picked up a trend an allocate monthly. But I could never test this.
An other option is to detect the main economic conditions and let’s say if purchase manager index below 50 one goes value + sometimes and above 50 we go for growth.
Or looking at the VIX and below it’s x days moving average go to momentum growth and above doing a mean reversal algo...

At the moment I’m a bit non productive as the platform I used stopped and than I moved to MATLAB (which is great but few private people using it) but due to the much higher popularity of Python it looks the way to go. I’m just figuring out what and how to set up. I narrowed down to Zipline or backtrader. I believe that I need to run everything locally as the platforms I tested are way to slow.(yes and in need to purchase my on data...)
For example, running a value momentum algo with first ranking 3 composite factors(with each 3-4 single factors) as filter for the universe than running either the momentum or the composite value and than as last step the selection ranking took around 5 minutes for the whole 20 years.
That’s what I’m looking for regarding speed.
All the free platforms are obviously slower, you get what you pay, but I would prefer to pay a bit and move faster :)
Do you have some suggestions?

...and I’m definitely interested in your offer!

Sometimes the fundamentals (e.g. ROA, PE, etc) are very different for different Sectors, and so instead of just taking the individual stock fundamental values on their own, a few years ago i tried comparing the stocks' values with the average for the relevant sector, either normalizing by taking ratios, or else by taking differences. This was partially successful, but i did not get around to completing the work. I think this comparative approach is worth coming back to again, not just for Sectors but also for Investment Styles or other ways of grouping stocks too. Anyone interested?

Hi Carsten, i wrote the post above before i saw yours. For my own personal trading outside of Q i use AmiBroker with its AFL language which is very good for price analysis work but weak on the fundamentals side. What i have done is enough Markov Chain / Transition Probability work with different Market Regimes to convince myself that finding whatever is hot and riding for as long as one can is generally a good way to go. I think this goes for "investment fashions" as well as for purely price action behavior. Therefore i consider that finding out what investment fashion (sector, factor, style or whatever) is currently the best is very much a worthwhile endeavor, and so it sounds like your ideas and mine are well aligned. As to what platform is best to use, i can't really comment. I will be working on this here on Q, even though my python skills are not particularly good. Do you have anything currently working (even if slow) here on Q along the lines you mentioned that we could perhaps look at together and share ideas for improving?

By using this algo i have found that the number of stocks is too small, and just one stock can bring down the entire portfolio very quickly wich is hard to withstand psicologically. So basically some improvements culd be putting stop losses or weight stocks on volatility or have a larger set of stocks in portfolio.

The idea of finding investment fashions is very interesting. @Tony but i cannot think of anything else but sectors, or fundamental fatcors can you make some examples?

i have given up un the idea of installig zipline locally because organizing data is too time consuming and also because just installing it is very difficoult

@Carsten are you shure that if you delete the p <= 3part of the filter you will get the same resoults?
As far as i can see basically all the stocks are chosen within the p <= 3 group infact if you delete the p > 7 part the algo makes nothing, so that is not really what i was looking for but if i have to learn something from that it would be that i will have to buy weak fundamendals with strong price action.

Hi Giuseppe,

I agree, the number of stocks you are using is too small. Especially if you go to Equity LS algos then try 100 or more.

"Investment Fashions"

  • Sectors,
  • Macro Economic factors, including interest rate regimes (High / Low, Rising / Falling, etc).
  • Fundamentals as in Big vs Small Caps, Growth vs Value, Regular dividend payers, Low debt during recessions vs highly geared during periods of strong economic growth, etc.
  • Commodity-related (e.g. precious metals, other futures data correlations, etc).
  • Price patterns & volatility effects,
    etc.

@ Giuseppe
I believe it was the <3 part... I played around with changing the numbers, deleting one part, than the other...it took always very long to run the code...
I definitely believe if you buy very over leveraged companies (<3) together with price action, the will definitely perform in the short run - but that’s not value (which did not work great in the last year’s anyway)
But if you would like to buy <3, a PE 0 < x < 20 does not make sense. That’s why I assume the strategy should not catch any stock <3 Z-score - theoretically.
Probably you z-score has a small error?
When I programed these multifactors I got huge amount of errors in the first place.
If you devide the numbers, be carful how you do it. if they get a negative sign, they might jump to the other side off the ranking. Took me a little bit. The best way to do it, just use every single factor, run it for long as possible and check that the gain vs percentages are looking smooth, no sudden spike or jumps or empty spaces - and best compare them how they should look like with other literature . If your sure there ok, combine them and check the combination again.

@ Tony
I send you a PM

Thank you very much Carsten, that totally make sense, i think i will gradually start to reconstruct the fundamentals part of the algo

@ Giuseppe

I was thinking a bit using z-score to select growth, meaning z<3...
I think this was not the idear behind the score. As I understand it should be only used in one direction, at least for the long side.
For shorting z<3 might be ok.

If you plan to select growth stocks and price action it might be not the best idear to use the z-score.
Other factors might be better.

I would recommend reading
QUANTITATIVE STRATEGIES FOR ACHIEVING ALPHA RICHARD TORTORIELLO
and/or
WHAT WORKS ON WALL STREET The Classic Guide to the Best-Performing Investment Strategies of All Time JAMES P. O’SHAUGHNESSY
- 4.th edition
and
https://www.osam.com/Commentary

Second book advocates value and the first book as well suggests some growth factors.
( both are a library for factors) For somebody new to all this, the second book was great for me to start. After reading a lot of things about factors investing I believe the first one has more information. I did not find any other book which is close to these two.

The home page of OSAM has some very good ideas for specific strategies.
(Unfortunately most is value)

“I believe in the discipline of mastering the best that other people have ever figured out. I don’t believe in just sitting there and trying to dream it up all yourself. Nobody’s that smart.”—Charlie Munger

:)

...and the blog from theses guys, as well some interesting articles...

https://alphaarchitect.com/research-category-list/

i have completely changed the fundamental part and i added a criteria to weight stocks wich is in the function 'm_v_p'
i am kind of annoyed by the behaviour in 2017 and i don't really understand where all that volatility came from, but i think the algo overall makes sense and hase space for improvements

Clone Algorithm
16
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Momentum, Value, and a little bit of mean reverting by Giuseppe Moriconi, modified by Vladimir  
# https://www.quantopian.com/posts/momentum-value-and-a-little-bit-of-mean-reverting  
from quantopian.pipeline.factors import SimpleMovingAverage, Returns  
from quantopian.algorithm import attach_pipeline, pipeline_output  
from quantopian.pipeline.classifiers.morningstar import Sector 
from quantopian.pipeline.factors      import SimpleMovingAverage, Returns
from quantopian.pipeline.data.builtin import USEquityPricing  
from quantopian.pipeline.filters import QTradableStocksUS  
from quantopian.pipeline.data import Fundamentals  
from quantopian.pipeline import CustomFactor  
from quantopian.pipeline import Pipeline  
import pandas as pd  
import numpy as np  
import math

def initialize(context):  
    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 = {  
                        symbol('XLB'): 101,      # XLB Materials  
                        symbol('XLY'): 102,      # XLY Consumer Cyclical  
                        symbol('XLF'): 103,      # XLF Financials  
                        symbol('XLP'): 205,      # XLP Consumer Defensive  
                        symbol('XLV'): 206,      # XLV Healthcare  
                        symbol('XLU'): 207,      # XLU Utilities  
                        symbol('XLE'): 309,      # XLE Energy  
                        symbol('XLI'): 310,      # XLI Industrials  
                        symbol('XLK'): 311,      # XLK Tech  
                        }  
    context.sectors_list = context.sectors_dict.keys()  
    context.bonds = [symbol('TLT'),              # TLT  
                     symbol('TIP')]              # TIP, IEF  
                       
    schedule_function(rebalance, date_rules.month_start(), time_rules.market_open(hours=1),)  
    schedule_function(logging, date_rules.every_day(), time_rules.market_open(hours=1),)  
    schedule_function(record_vars, date_rules.month_start(), time_rules.market_open(hours=1),)

    
    ##########################################
    #########        PIPELINE        #########
    ##########################################
    base_universe = QTradableStocksUS()  
    m = base_universe
    #TOP_N_ROE_TO_BUY = 200 #First sort by ROE
    RELATIVE_MOMENTUM_LOOKBACK = 126 #Momentum lookback
    MOMENTUM_SKIP_DAYS = 10

    sector = Sector()  # sector code   

    market_cap = Fundamentals.market_cap.latest # std  
    market_cap_screen = market_cap > 1e9 
    momentum = (Returns(window_length=MOMENTUM_SKIP_DAYS+RELATIVE_MOMENTUM_LOOKBACK, mask=m).log1p() - Returns(window_length=MOMENTUM_SKIP_DAYS, mask=m).log1p())
    
 
    roic = Fundamentals.roic.latest.rank()
    ltd_to_eq = Fundamentals.long_term_debt_equity_ratio.latest.rank(ascending=True, mask=m)
    value = (Fundamentals.cash_return.latest.rank(mask=m) + Fundamentals.fcf_yield.latest.rank(mask=m))
    
    quality = (
        roic + 
        ltd_to_eq +
        value
    )
    #m &= quality.top(TOP_N_ROE_TO_BUY, mask=m)

    columns = {'sector': sector, 'mom': momentum, 'roe' : quality} 
    screen = (base_universe & market_cap_screen)  
    pipe = Pipeline(columns, screen)  
    attach_pipeline(pipe, 'pipeline')

def rebalance(context, data): 
    
    ##########   VARIABLES  #############
    stocks_to_buy = []  
    sectors_to_buy = []  
    sectors_number_to_buy = [] 
    
    ##########   PARAMETERS  #############
    ROE = 10 #number of stocks selected by fundamentals
    MOM = 5  #number of stocks selected by momentum
    
    ########## 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]  
    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 
        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 = 1.0 - 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 = pipeline_output('pipeline')  

    grouped = pipe.groupby('sector')  
    for sector, group in grouped:  
        if sector in sectors_number_to_buy:  
            # first selection based on fundamentals: ROE
            #group['selected'] = (group['roe'].rank() + group['mom'].rank()).nlargest(5)
            group_roe = group.nlargest(ROE, 'roe')
            group_mom = group_roe.nlargest(MOM, 'mom')
            stocks_to_buy_for_this_sector = group_mom.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  
    
    equity_weigths = m_v_p(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)  
    '''
    for bond in context.bonds:  
        order_target_percent(bond, context.hedge_allocation/len(context.bonds))

    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, equity_weigths[stock])  
def record_vars(context, data):  
    
    record(leverage=context.account.leverage, trend = context.market_trend, equity_allocation = context.equity_allocation, n_sectors = len(context.sectors_to_buy))  
    
def logging(context, data):
    cpp = context.portfolio.positions
    for s in cpp:
        print (s.symbol)
        print (s.asset_name)
        
def m_v_p(context, data):
    port_returns = []
    port_volatility = []
    stock_weights = []
    num_assets = len(context.stocks_to_buy)
    num_portfolios = 50000
    p = data.history(context.stocks_to_buy, 'close', 252,'1d')  
    r = p.pct_change().dropna()
    c = r.cov()
    returns_annual = r.mean() * 250
    cov_annual = c * 250
    
    for single_portfolio in range(num_portfolios):
        weights = np.random.random(num_assets)     # genero pesi casuali
        weights = context.equity_allocation *(weights/np.sum(weights)  )               
        returns = np.dot(weights, returns_annual)  # rendimenti pesati
        volatility = np.sqrt(np.dot(weights.T, np.dot(cov_annual, weights))) # volatilità
        port_returns.append(returns)               
        port_volatility.append(volatility)
        stock_weights.append(weights)
    portfolio_ = {'Returns': port_returns,
             'Volatility': port_volatility}
    for counter,symbol in enumerate(context.stocks_to_buy):
        portfolio_[symbol] = [weight[counter] for weight in stock_weights]
    df = pd.DataFrame(portfolio_)
    column_order = ['Returns', 'Volatility'] + [stock for stock in context.stocks_to_buy]
    df = df[column_order]
    df['Sharpe'] = df['Returns'] / df['Volatility']
    ok =df.nlargest(100, 'Sharpe').mean() 
    '''
    w = ok[context.stocks_to_buy]
    dict_weigths = {}
    for s in context.stocks_to_buy:
        dict_weigths[s]= w[s].values[0]
    '''
    return ok
There was a runtime error.

@Guiseppe
Just an additional idea, you could try to use earnings and sales growth with price momentum.
To determine the growth you would always calculate n quarter / n - 4 quarter ago.
To get the growth for 1 year, you could than build the sum of all 4 and rank them accordingly (or 8 for 2 years)
Doing the same with the second element.
Than you add both ranks and rank the result again.
Or you could add the ranks of earnings, sales and momentum and rank the results to find your top stocks.
This is how IBD roughly is doing it. They use more than earnings, sales and momentum, but I believe the three items are the main driver.
You could as well use more than 1 year for earnings and sales and one could filter out stocks were the earnings and sales fluctuate to much.
Minimum yearly growth for the last year should be 20-30% each.
And one should only buy if the S&P 500 is above its 200 moving average.
For reverence you could google William O'Neil screen or CAN-Slim.

@Guiseppe

I have tried to clone the algorithm and get an error on line 88. It is an error with the dict_keys. Do you know if what might be the reason for this? I would love to get the algorithm running to try and make improvements.

Thanks,
Matt

Matt, it's a problem related to python 3.5, if you compile with python 2 there are no error, by the way
replaxe line 35 with

context.sectors_list = list(context.sectors_dict.keys()  )  

and replace line 132 with
for k, v in context.sectors_dict.items():