Back to Community
Matt's Breadth Indicator

Hello folks,

As a mean to train myself (I'm a novice coder) I decided to code this described strategy - http://throwinggoodmoney.com/matts-breadth-indicator/ credits to the author.

So how does it work? You count up all the stocks that are current members of the Russell 3000 and that are up >30% in the last 60 trading days. You also count the number that are more than 30% down. You do a breadth diffusion calculation like this:

up30 / (up30 + down30 ) * 100

The magic threshold is 75. If the last ten days have a value greater than 75, it’s a ‘green’ day and ok to trade. If the last ten days have a value less than 75, it’s a ‘red’ day and you should exit long-term trades immediately. If the last ten days are neither all above or below the line, then you continue the status quo with no change.

There is just a difference due to the max 500 stocks universe limitation, I couldn't screen the whole Russel3000 and reevaluate each day so I decided to pick 500 stocks in the top 1000 to 1500 US stocks listed by marketcap.

Here is the result "out from the book", but I'm sure some improvement or strategy can be built from this idea.

Clone Algorithm
31
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
# Idea from http://throwinggoodmoney.com/matts-breadth-indicator/

'''
So how does it work? You count up all the stocks that are current members of the Russell 3000 (top3000) and that are up >30% in the last 60 trading days. You also count the number that are more than 30% down. You do a breadth diffusion calculation like this:

up30 / (up30 + down30 ) * 100

The magic threshold is 75. If the last ten days have a value greater than 75, it’s a ‘green’ day and ok to trade. If the last ten days have a value less than 75, it’s a ‘red’ day and you should exit long-term trades immediately. If the last ten days are neither all above or below the line, then you continue the status quo with no change.
'''

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline, CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar

span = []


def initialize(context):
    set_slippage(slippage.FixedSlippage(spread=0.00))
    set_commission(commission.PerTrade(cost=0.00))
    context.my_leverage = 1
    
    # indicator breadth parameter
    context.days = 10
    context.pct = 30
    context.thsld = 75
    
    pipe = Pipeline()
    attach_pipeline(pipe, 'top500')
    
    # get the latest marketcap
    marketcap = morningstar.valuation.market_cap.latest
    pipe.add(marketcap, 'marketcap')
    
    # rank all companies based on desc. marketcap
    ranked_mrktcap = marketcap.rank(ascending=False)
    pipe.add(ranked_mrktcap, 'mrktcap_rank')
    
    # Get the share class using to get the most recent value
    share_class = morningstar.share_class_reference.is_primary_share.latest
    pipe.add(share_class, 'share_class')
    
    # filter out any companies without revenue and all non-primary share classes
    pipe.set_screen(share_class & (marketcap > 1))
    
    # Schedule rebalance function
    schedule_function(rebalance, 
                      date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_open())
    
def before_trading_start(context, data):
    
    # fillNaN with 0
    context.output = pipeline_output('top500')
    top500 = context.output.fillna(0)
    
    # Do some work to remove when issued securities from the universe
    top500['sid'] = top500.index
    top500['symbol'] = top500.sid.apply(lambda x: x.symbol)
    top500 = top500[top500.symbol.apply(lambda x: not x.endswith('_WI'))]
    
    # Narrow down the securities to the top 3000 & update my universe
    # !! top between 1000-1500 max due to limitation
    context.top500 = top500.sort(['mrktcap_rank'], ascending=True).iloc[1000:1500]
    update_universe(context.top500.index)

def rebalance(context, data):
    
    close_price_60ago = history(61, '1d', 'close_price').iloc[0]
    close_price_yesterday = history(61, '1d', 'close_price').iloc[-1]
    winner = 0
    loser = 0
    
    for sid in context.top500['sid']:
        if close_price_60ago[sid]*(1 + context.pct/100) < close_price_yesterday[sid]:
            winner += 1
        elif close_price_60ago[sid]*(1 - context.pct/100) > close_price_yesterday[sid]:
            loser += 1
            
    breadth = (100* winner / (winner + loser))
    span.append(breadth)
    record(breadth = breadth, thresold = context.thsld)

    up = 0
    down = 0
    
    if len(span) >= context.days:
        for x in span[-context.days:]:
            if x > context.thsld:
                up += 1
            else:
                down += 1
                
    if up == context.days:
        order_target_percent(symbol('SPY'), 1)
        print "time to buy (green)"
    elif down == context.days:
        print "time to sell (red)"
        order_target_percent(symbol('SPY'), 0)
    else:
        print "status quo (yellow)"
    
def handle_data(context, data):
    pass    

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
There was a runtime error.
2 responses

Here another attempt with different parameters and shorting allowed.

70 long threshold (instead of 75)
30 short threshold (instead of none)
12 days over/under (instead of 10)
20% (instead of 30%)

Clone Algorithm
31
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
# Idea from http://throwinggoodmoney.com/matts-breadth-indicator/

'''
So how does it work? You count up all the stocks that are current members of the Russell 3000 (top3000) and that are up >30% in the last 60 trading days. You also count the number that are more than 30% down. You do a breadth diffusion calculation like this:

up30 / (up30 + down30 ) * 100

The magic threshold is 75. If the last ten days have a value greater than 75, it’s a ‘green’ day and ok to trade. If the last ten days have a value less than 75, it’s a ‘red’ day and you should exit long-term trades immediately. If the last ten days are neither all above or below the line, then you continue the status quo with no change.
'''

from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline, CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar

span = []


def initialize(context):
    set_slippage(slippage.FixedSlippage(spread=0.00))
    set_commission(commission.PerTrade(cost=0.00))
    context.my_leverage = 1
    
    # indicator breadth parameter
    context.days = 12
    context.pct = 20
    context.thsld = 70
    
    pipe = Pipeline()
    attach_pipeline(pipe, 'top500')
    
    # get the latest marketcap
    marketcap = morningstar.valuation.market_cap.latest
    pipe.add(marketcap, 'marketcap')
    
    # rank all companies based on desc. marketcap
    ranked_mrktcap = marketcap.rank(ascending=False)
    pipe.add(ranked_mrktcap, 'mrktcap_rank')
    
    # Get the share class using to get the most recent value
    share_class = morningstar.share_class_reference.is_primary_share.latest
    pipe.add(share_class, 'share_class')
    
    # filter out any companies without revenue and all non-primary share classes
    pipe.set_screen(share_class & (marketcap > 1))
    
    # Schedule rebalance function
    schedule_function(rebalance, 
                      date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_open())
    
def before_trading_start(context, data):
    
    # fillNaN with 0
    context.output = pipeline_output('top500')
    top500 = context.output.fillna(0)
    
    # Do some work to remove when issued securities from the universe
    top500['sid'] = top500.index
    top500['symbol'] = top500.sid.apply(lambda x: x.symbol)
    top500 = top500[top500.symbol.apply(lambda x: not x.endswith('_WI'))]
    
    # Narrow down the securities to the top 3000 & update my universe
    # !! top between 1000-1500 max due to quantopian 500 max stocks limitation
    context.top500 = top500.sort(['mrktcap_rank'], ascending=True).iloc[1000:1500]
    update_universe(context.top500.index)

def rebalance(context, data):
    
    close_price_60ago = history(61, '1d', 'close_price').iloc[0]
    close_price_yesterday = history(61, '1d', 'close_price').iloc[-1]
    winner = 0
    loser = 0
    
    for sid in context.top500['sid']:
        if close_price_60ago[sid]*(1 + context.pct/100) < close_price_yesterday[sid]:
            winner += 1
        elif close_price_60ago[sid]*(1 - context.pct/100) > close_price_yesterday[sid]:
            loser += 1
            
    breadth = (100* winner / (winner + loser))
    span.append(breadth)
    record(breadth = breadth, longThsld = context.thsld, shortThsld = (100 - context.thsld))

    up = 0
    down = 0
    
    if len(span) >= context.days:
        for x in span[-context.days:]:
            if x > context.thsld:
                up += 1
            if x < 100 - context.thsld:
                down += 1
                
    if up == context.days:
        order_target_percent(symbol('SPY'), 1)
        print "time to buy (green)"
    elif down == context.days:
        print "time to short (red)"
        order_target_percent(symbol('SPY'), -1)
    else:
        print "status quo (yellow)"
    
def handle_data(context, data):
    pass    

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
There was a runtime error.

TYVM for sharing!

Do you have an updated version that uses new API calls?