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
41
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
"""
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.
15 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
10
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# 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
40
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# 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.