Back to Community
Trading SPY based on the slope (zscore of) and the HiLo index of the SPY components (Tradeable?)

After 100+ back tests over different periods I'm confident that this strategy is tradeable and I'm seeking collaboration with others to either improve the drawdown or add other elements to the algo that it makes more robust.
To be in the contest we need to add a hedge to isolate Alpha but for my personal trading I'm not so interested in isolating Alpha.

Any Takers?

With algorithmic regards, Peter Bakker

Clone Algorithm
515
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
import numpy as np
import math
from pytz import timezone
import datetime
from sqlalchemy import or_
import scipy.stats as stats
import talib as ta


#compute online moving average with basket of 15 Fortune 500 companies
def before_trading_start(context, data):
    sp_500 = get_fundamentals(
                query(fundamentals.valuation.market_cap,
                     fundamentals.company_reference.primary_exchange_id)
                .filter(fundamentals.valuation.market_cap > 4e9)
                .filter(fundamentals.share_class_reference.is_primary_share == True)
                .filter(fundamentals.company_reference.country_id == "USA")
                .filter(or_(fundamentals.company_reference.primary_exchange_id == "NAS", fundamentals.company_reference.primary_exchange_id == "NYS"))
                .order_by(fundamentals.valuation.market_cap.desc())
                .limit(499))  # S&P 500 has 500 but I need 1 for the SPY  
    context.fundamental_df = sp_500

    # Update the universe of securities
    update_universe(context.fundamental_df.columns.values)

def initialize(context):
    context.stocks = ''
    context.spy = symbol('SPY')
    context.history_close = None
    context.history_max  = None
    context.history_min  = None
    context.lookback = 62 
    context.smoothing = 40 #40 is standard
    context.holdtilpositive = False
    context.holdtilnegative = False
    context.buytomorrow = False
    
    context.threshold = 0.0035 #5% is standard
    context.hilo_index = []
    context.hilo_MAindex = []
    context.slope_index = []
    context.spyslope_index = []
    
    
    context.current_hilo = -10
    
    context.m = len(context.stocks)
    context.b_t = np.ones(context.m) / context.m
    context.eps = 0.75 # change epsilon here
    context.init = False
    context.previous_datetime = None
    #for making the history available in before trade start:
    schedule_function(Algo_hilo, date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_close(minutes=5))

    # schedule_function(daily, date_rule=date_rules.week_start(2),
    #                   time_rule=time_rules.market_open(minutes=30))
    
    
     
def handle_data(context, data):
    pass
    
def Algo_hilo(context, data):
    cash = context.portfolio.cash
    leverage = context.account.leverage
    context.history_close = history(202, '1d', 'price')
    #Calc spy slope
    x1 = list(xrange(22))
    spyslope, spyintercept, psyr_value, spyp_value, spystd_err = stats.linregress(x1,context.history_close[context.spy][-22:])
    context.spyslope_index.append(spyslope)
    #Calc zscore of spy slope
    z_spyslope= stats.zscore(context.spyslope_index, axis=0, ddof=1)[-1]
    accspyslope = 0
    #Calc accelleration of spy
    if len(context.spyslope_index) > 5:
        x2 = list(xrange(5))
        accspyslope, accspyintercept, accpsyr_value, accspyp_value, accspystd_err = stats.linregress(x2,context.spyslope_index[-5:])
    #Calc long and short mavg spy
    mavg_short = np.mean(context.history_close[context.spy][-22:])
    mavg_long = np.mean(context.history_close[context.spy][-200:])
    current_price = context.history_close[context.spy][-1]        
    
    #get maxes and minimums
    context.history_max = context.history_close.idxmax(axis=0, skipna=True)
    context.history_min = context.history_close.idxmin(axis=0, skipna=True)
    minctr = 0
    maxctr = 0
    stockctr = len(context.history_close.columns)
    #find number of hi's and Lows
    for stock in context.history_max.index:
        datemax = context.history_max[stock]
        if str(type(datemax))=="<class 'pandas.tslib.Timestamp'>" and datemax.date() == get_datetime().date(): maxctr += 1
    for stock in context.history_min.index:
         datemin = context.history_min[stock] 
         if str(type(datemin))=="<class 'pandas.tslib.Timestamp'>" and datemin.date() == get_datetime().date(): minctr += 1          
    hi = float(maxctr)/stockctr
    lo = float(minctr)/stockctr
    ratio = (float(maxctr - minctr))/stockctr
    context.hilo_index.append(ratio)
    #calc zscore of ratio
    z_ratio= stats.zscore(context.hilo_index[-context.smoothing:], axis=0, ddof=1)[-1]
    ma_ratio = np.mean(context.hilo_index[-context.smoothing:])
    context.hilo_MAindex.append(ma_ratio)
    context.current_hilo = ratio
    #calc slope of MA HiLo index
    if len(context.hilo_MAindex)> 7:
        x = list(xrange(7))
        slope, intercept, r_value, p_value, std_err = stats.linregress(x,context.hilo_MAindex[-7:])
    else:
        slope = 0
        p_value =0
        
    context.slope_index.append(slope)
    
    if len(context.slope_index) > 7:
        slope_ma = np.mean(context.slope_index[-7:])
    else:
        slope_ma = 0
   

    if mavg_long > current_price: 
        record(mavg_long_vs_price=-1) 
    else:
        record(mavg_long_vs_price=1)
    if context.holdtilpositive: 
        record(holdtilpositive= 1) 
    else: 
        record(holdtilpositive=0)   
    record(zspyslope = z_spyslope)
    record(zratio_hi_lo = z_ratio)
    record(slopespy = spyslope)    
    
    if (spyslope > 0 and slope_ma > 0) and context.holdtilpositive:
        context.holdtilpositive = False
    elif spyslope < 0 and context.holdtilnegative:
        context.holdtilnegative = False
    
    
    if context.buytomorrow:
        order_target_percent(context.spy, 1)
        context.buytomorrow = False
        return

        
         
    no_spys = context.portfolio.positions[context.spy].amount
    if  z_spyslope > 0:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days > 0: we are moving up')
        context.buytomorrow = True #Buy with delay of 1 day
        return
    elif  z_spyslope < -.7:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days < -0.7: mean reverting')
        order_target_percent(context.spy, 1)       
        context.holdtilpositive = True
        return
    elif (z_spyslope < 0 and not context.holdtilpositive) or mavg_long > current_price or (z_spyslope > 1.0 or z_ratio > 1.5):
        if no_spys>0 and mavg_long > current_price: log.info('Selling: price below Long mavg 122 days')
        if no_spys>0 and z_spyslope > 1.0: log.info('Selling: z score of the Slope of SPY over 22 days > 1.0')
        if no_spys>0 and z_ratio > 1.5: log.info('Selling: z score of the Ratio HILO of SPY over 22 days > 1.5')
        if no_spys>0 and (z_spyslope < 0 and not context.holdtilpositive): log.info('Selling: z score of the Slope of SPY over 22 days < -0.0 and not holding till positive')
        order_target_percent(context.spy, 0)
        return
    return

    
    
There was a runtime error.
35 responses

and the tearsheet

Loading notebook preview...

The system does well during QE - but then everyone does well during this period. Between Jan 2006 and Jan 2009 the system makes no money at all - That's 3 YEARS of no returns.

Basically, this system has way too high a beta - I would be careful when trading it in it's current state.

I think the next step would be to add a shorting component to hedge and make it market neutral.

Correct, high beta, but some people cannot short on their account (retail investors in Australia). The question is wether the Alpha of 0.12 in this system is QE dependent and I can't hypothesise why that would be. The system looks at the slope of the price of SPY and how many stocks reach a high and a low, that doesnt seem QE dependent.

How can we improve it: clone and improve!

Congratulations on the spot-on full use of starting capital.

Peter Bakker and Jamie Lunn code combo below, where I did not make the attempt to.
Pro's: Beta is 43% lower, volatility lower, less drawdown.
Didn't spend a lot of time tinkering further so the nearly 50 percentage point loss (see PvR) might be improvable.

Clone Algorithm
120
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
'''
Original by Peter Bakker
Cloned from "Trading SPY based on the slope (zscore of) and the HiLo index of the SPY components (Tradeable?)"
https://www.quantopian.com/posts/trading-spy-based-on-the-slope-zscore-of-and-the-hilo-index-of-the-spy-components-tradeable

Adding Jamie Lunn code like
https://www.quantopian.com/posts/worthy-of-q-fund#560422c2193372ceb2000605

Mods by garyha
'''
import math
import datetime
import numpy as np
import scipy.stats as stats
import talib as ta
from pytz import timezone
from sqlalchemy import or_
import pandas as pd

# Fortune 500 companies
def before_trading_start(context, data):
    sp_500 = get_fundamentals(
                query()
                #.filter(fundamentals.valuation.market_cap > 4e9)
                .filter(fundamentals.valuation.market_cap > 3e9)
                .filter(fundamentals.valuation.market_cap < 5e9)
                .filter(fundamentals.share_class_reference.is_primary_share == True)
                .filter(fundamentals.company_reference.country_id == "USA")
                .filter(or_(fundamentals.company_reference.primary_exchange_id == "NAS", fundamentals.company_reference.primary_exchange_id == "NYS"))
                .order_by(fundamentals.valuation.market_cap.desc())
                .limit(299))  # S&P 500 has 500 but I need 1 for the SPY  
    context.fundamental_df = sp_500
    update_universe(context.fundamental_df.columns.values)

def initialize(context):
    context.safe =   [ sid(23921),  # TLT 20+ Year T Bonds
                       sid(23870),  # IEF 7-10 Year T Notes
                       #sid(40779),  # HDGE
                       #sid(36385),  # SEF
                       #sid(32267),  # DOG
    ]
    context.secs =   [ sid(19662),  # XLY Consumer Discrectionary SPDR Fund   
                       sid(19656),  # XLF Financial SPDR Fund  
                       sid(19658),  # XLK Technology SPDR Fund  
                       sid(19655),  # XLE Energy SPDR Fund  
                       sid(19661),  # XLV Health Care SPRD Fund  
                       sid(19657),  # XLI Industrial SPDR Fund  
                       sid(19659),  # XLP Consumer Staples SPDR Fund   
                       sid(19654),  # XLB Materials SPDR Fund  
                       sid(19660)]  # XLU Utilities SPRD Fund
    context.spy_sid = sid(8554)  
    set_commission(commission.PerShare(cost=0.005, min_trade_cost=1))
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))
    schedule_function(trade,date_rules.month_start(),time_rules.market_open())
    
    c = context
    c.stocks            = ''
    c.spy               = context.spy_sid
    c.history_close     = None
    c.history_max       = None
    c.history_min       = None
    c.lookback          = 62 
    c.smoothing         = 40     # 40 is standard
    c.holdtilpositive   = False
    c.holdtilnegative   = False
    c.buytomorrow       = False
    c.threshold         = 0.0035 # 5% is standard
    c.hilo_index        = []
    c.hilo_MAindex      = []
    c.slope_index       = []
    c.spyslope_index    = []
    c.current_hilo      = -10
    c.m                 = len(context.stocks)
    c.b_t               = np.ones(context.m) / context.m
    c.eps               = 0.75 # change epsilon here
    c.init              = False
    c.previous_datetime = None
    #for making the history available in before trade start:
    schedule_function(Algo_hilo, date_rules.every_day(), time_rules.market_close(minutes=5))
    # schedule_function(daily, date_rules.week_start(2), time_rules.market_open(minutes=30))
    schedule_function(info, date_rules.every_day(), time_rules.market_close())
    
    c.cash_low = c.portfolio.starting_cash    # for info()
    c.max_lvrg = 0
    c.max_shrt = 0
    c.risk_hi  = 0
    c.date_prv = ''
    c.date_end = str(get_environment('end').date())
    print '{} to {}  {}'.format(str(get_datetime().date()) , c.date_end, int(c.cash_low))
    
def trade(context, data):
    #exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')   
    num = 120    
    spy_z = (data[context.spy_sid].price - data[context.spy_sid].mavg(num)) \
        / data[context.spy_sid].stddev(num)  
    
    for bond in context.safe:    ## Risk on/off logic:
        if get_open_orders(bond): continue 
        if spy_z < -1.0:
            order_target_percent(bond, .25 / 2.)
            #log.info("Risk OFF: allocate %s" % (bond.symbol) + " at %s" % str(exchange_time))
        else:
            order_target(bond, 0)
            #log.info("Risk ON: zero weight %s" % (bond.symbol) + " at %s" % str(exchange_time))
      
    for stock in context.secs:
        if get_open_orders(stock): continue 
        sect_z = (data[stock].price - data[stock].mavg(num)) / data[stock].stddev(num) 
  
        if sect_z < spy_z and (1.0 > sect_z > -1.0):        ## sector trade logic   
            order_target_percent(stock, .13 / 2.)  
            #log.info("Allocate %s" % (stock.symbol) + " at %s" % str(exchange_time)) 
        else: 
            order_target(stock, 0)
            #log.info("Zero weight %s" % (stock.symbol) + " at %s" % str(exchange_time))
            
def Algo_hilo(context, data):
    c = context
    #cash     = c.portfolio.cash
    #leverage = c.account.leverage
    c.history_close = history(202, '1d', 'price')
    x1 = list(xrange(22))
    spyslope, spyintercept, psyr_value, spyp_value, spystd_err = stats.linregress(
        x1,c.history_close[c.spy][-22:])    # spy slope info
    c.spyslope_index.append(spyslope)
    z_spyslope  = stats.zscore(c.spyslope_index, axis=0, ddof=1)[-1]    # zscore of spy slope
    accspyslope = 0
    # accelleration of spy
    if len(c.spyslope_index) > 5:
        x2 = list(xrange(5))
        accspyslope, accspyintercept, accpsyr_value, accspyp_value, accspystd_err = stats.linregress(x2,c.spyslope_index[-5:])
    # long and short mavg spy
    #mavg_short    = np.mean(c.history_close[c.spy][-22:])
    mavg_long     = np.mean(c.history_close[c.spy][-200:])
    current_price = c.history_close[c.spy][-1]        
    
    # maxes and minimums
    c.history_max = c.history_close.idxmax(axis=0, skipna=True)
    c.history_min = c.history_close.idxmin(axis=0, skipna=True)
    minctr   = 0
    maxctr   = 0
    stockctr = len(c.history_close.columns)

    for stock in c.history_max.index:    # number of hi's and Lows
        datemax = c.history_max[stock]
        if str(type(datemax)) == "<class 'pandas.tslib.Timestamp'>" \
          and datemax.date() == get_datetime().date(): maxctr += 1
    for stock in c.history_min.index:
         datemin = c.history_min[stock] 
         if str(type(datemin)) == "<class 'pandas.tslib.Timestamp'>" \
           and datemin.date() == get_datetime().date(): minctr += 1          
    #hi = float(maxctr) / stockctr
    #lo = float(minctr) / stockctr
    c.current_hilo = float(maxctr - minctr) / stockctr
    c.hilo_index.append(c.current_hilo)
    z_ratio  = stats.zscore(c.hilo_index[-c.smoothing:], axis=0, ddof=1)[-1] # zscore current_hilo
    ma_ratio = np.mean(c.hilo_index[-c.smoothing:])
    c.hilo_MAindex.append(ma_ratio)
    if len(c.hilo_MAindex)> 7:    # calc slope of MA HiLo index
        x = list(xrange(7))
        slope, intercept, r_value, p_value, std_err = stats.linregress(
            x, c.hilo_MAindex[-7:])
    else:
        slope   = 0
        #p_value = 0
        
    c.slope_index.append(slope)
    
    if len(c.slope_index) > 7:
        slope_ma = np.mean(c.slope_index[-7:])
    else:
        slope_ma = 0

    '''
    if mavg_long > current_price: 
        record(mavg_long_vs_price=-1) 
    else:
        record(mavg_long_vs_price=1)
    if c.holdtilpositive: 
        record(holdtilpositive= 1) 
    else: 
        record(holdtilpositive=0)   
    record(zspyslope = z_spyslope)
    record(zratio_hi_lo = z_ratio)
    record(slopespy = spyslope)    
    '''    
    if (spyslope > 0 and slope_ma > 0) and c.holdtilpositive:
        c.holdtilpositive = False
    elif spyslope < 0 and c.holdtilnegative:
        c.holdtilnegative = False
    
    if c.buytomorrow:
        order_target_percent(c.spy, 1 / 2.)
        c.buytomorrow = False
        return
         
    no_spys = c.portfolio.positions[c.spy].amount
    if  z_spyslope > 0:
        if 0 and no_spys == 0: 
            log.info('Buying: z score of the Slope of SPY over 22 days > 0: we are moving up')
        c.buytomorrow = True # Buy with delay of 1 day
    elif  z_spyslope < -.7:
        if 0 and no_spys == 0: 
            log.info('Buying: z score of the Slope of SPY over 22 days < -0.7: mean reverting')
        order_target_percent(c.spy, 1 / 2.)       
        c.holdtilpositive = True
    elif (z_spyslope < 0 and not c.holdtilpositive) or mavg_long > current_price or (z_spyslope > 1.0 or z_ratio > 1.5):
        '''
        if no_spys > 0 and mavg_long > current_price: 
            log.info('Selling: price below Long mavg 122 days')
        if no_spys > 0 and z_spyslope > 1.0: 
            log.info('Selling: z score of the Slope of SPY over 22 days > 1.0')
        if no_spys > 0 and z_ratio > 1.5: 
            log.info('Selling: z score of the Ratio HILO of SPY over 22 days > 1.5')
        if no_spys > 0 and (z_spyslope < 0 and not c.holdtilpositive): 
            log.info('Selling: z score of the Slope of SPY over 22 days < -0.0 and not holding till positive')
        '''
        order_target(c.spy, 0)

def handle_data(context, data):
    return    # possibly faster than pass?
    
def info(context, data):
    ''' Custom chart and/or log of profit_vs_risk returns and related information
    '''
    # # # # # # # # # #  Options  # # # # # # # # # #
    record_max_lvrg = 1          # maximum leverage encountered
    record_leverage = 0          # Leverage (context.account.leverage)
    record_q_return = 1          # Quantopian returns (percentage)
    record_pvr      = 1          # Profit vs Risk returns (percentage)
    record_pnl      = 0          # Profit-n-Loss
    record_shorting = 0          # Total value of any shorts
    record_risk     = 0          # Risked, maximum cash spent or shorts in excess of cash at any time
    record_risk_hi  = 1          # Highest risk overall
    record_cash     = 0          # Cash available
    record_cash_low = 1          # Any new lowest cash level
    logging         = 1          # Also log to the logging window conditionally (1) or not (0)
    log_method      = 'risk_hi'  # 'daily' or 'risk_hi'

    c = context                          # For brevity
    new_cash_low = 0                     # To trigger logging in cash_low case
    date = str(get_datetime().date())    # To trigger logging in daily case
    cash = c.portfolio.cash

    if int(cash) < c.cash_low:    # New cash low
        new_cash_low = 1
        c.cash_low   = int(cash)
        if record_cash_low:
            record(CashLow = int(c.cash_low))

    pvr_rtrn      = 0        # Profit vs Risk returns based on maximum spent
    q_rtrn        = 0        # Returns by Quantopian
    profit_loss   = 0        # Profit-n-loss
    shorts        = 0        # Shorts value
    start         = c.portfolio.starting_cash
    cash_dip      = int(max(0, start - cash))

    if record_cash:
        record(cash = int(c.portfolio.cash))  # Cash

    if record_leverage:
        record(Lvrg = c.account.leverage)     # Leverage

    if record_max_lvrg:
        if c.account.leverage > c.max_lvrg:
            c.max_lvrg = c.account.leverage
            record(MaxLvrg = c.max_lvrg)      # Maximum leverage

    if record_pnl:
        profit_loss = c.portfolio.pnl
        record(PnL = profit_loss)             # "Profit and Loss" in dollars

    for p in c.portfolio.positions:
        shrs = c.portfolio.positions[p].amount
        if shrs < 0:
            shorts += int(abs(shrs * data[p].price))

    if record_shorting:
        record(Shorts = shorts)               # Shorts value as a positve

    # Shorts in excess of cash to cover them, a positive value
    shorts_excess = int(shorts - cash) if shorts > cash else 0
    c.max_shrt    = int(max(c.max_shrt, shorts_excess))

    risk = int(max(cash_dip, shorts_excess, shorts))
    if record_risk:
        record(Risk = risk)                   # Amount in play, maximum of shorts or cash used

    new_risk_hi = 0
    if risk > c.risk_hi:
        c.risk_hi = risk
        new_risk_hi = 1

        if record_risk_hi:
            record(Risk_hi = c.risk_hi)       # Highest risk overall

    if record_pvr:      # Profit_vs_Risk returns based on max amount actually spent (risk high)
        if c.risk_hi != 0:     # Avoid zero-divide
            pvr_rtrn = 100 * (c.portfolio.portfolio_value - start) / c.risk_hi
            record(PvR = pvr_rtrn)        # Profit_vs_Risk returns

    if record_q_return:
        q_rtrn = 100 * (c.portfolio.portfolio_value - start) / start
        record(QRet = q_rtrn)                 # Quantopian returns to compare to pvr returns curve

    #from pytz import timezone
    if logging:
        if log_method == 'risk_hi' and new_risk_hi \
          or log_method == 'daily' and c.date_prv != date \
          or c.date_end == date \
          or new_cash_low:
            mxlv   = 'MaxLv '   + '%.1f' % c.max_lvrg   if record_max_lvrg else ''
            qret   = 'QRet '    + '%.1f' % q_rtrn       if record_q_return else ''
            pvr    = 'PvR_Ret ' + '%.1f' % pvr_rtrn     if record_pvr      else ''
            pnl    = 'PnL '     + '%.0f' % profit_loss  if record_pnl      else ''
            csh    = 'Cash '    + '%.0f' % cash         if record_cash     else ''
            csh_lw = 'CshLw '   + '%.0f' % c.cash_low   if record_cash_low else ''
            shrt   = 'Shrt '    + '%.0f' % shorts       if record_shorting else ''
            risk   = 'Risk '    + '%.0f' % risk         if record_risk     else ''
            rsk_hi = 'RskHi '   + '%.0f' % c.risk_hi    if record_risk_hi  else ''
            minute = get_datetime().astimezone(timezone('US/Eastern')).time().minute
            log.info('{} {} {} {} {} {} {} {} {} {}'.format(
                    minute, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, risk, rsk_hi))

    if c.date_end == date:    # Log on last day, like cash 125199  portfolio 126890
        log.info('cash {}  portfolio {}'.format(
                int(cash), int(c.portfolio.portfolio_value)))

    c.date_prv = date

    
    
There was a runtime error.

Hi, Peter, thanks for sharing the code, are you still using this algorithm now?
According to my test, your algo is better than gary's modified version with higher return. I want to enhance your algo by adding shorting component, any ideas about how to implement that? Thanks.

As Lucas Lee suggests, let's try adding a shorting component.

We may eventually want to trade this strategy on Robinhood, which doesn't let us take a short position. But that's not too much of a problem, because we're trading SPY, an index fund with a solid inverse in SH (just like NDX has an inverse in PSQ). So whenever we want to short, we just sell the index fund and immediately buy its inverse fund.

Starting in 2006 - when the inverse funds became available - let's run some backtests. This will tell us how each strategy performs during the recession, as well as when the market rebounds afterwards.


BAKKER'S LONG ONLY
Original. Buys the market when zscor/hilo signals that it will improve. Otherwise holds cash.

  • RETURNS: 195%
  • ALPHA: 0.09
  • BETA: 0.85
  • SHARPE: 0.86
  • MAX DRAWDOWN: 44.3%

BAKKER'S LONG + SHORT
Buys the market when zscor/hilo signals that it will improve. Otherwise shorts the market.

  • RETURNS: 279.4%
  • ALPHA: 0.18
  • BETA: 0.72
  • SHARPE: 1.20
  • MAX DRAWDOWN: 45.1%

BAKKER'S LONG + SHORT IN BEAR MARKET
Buys the market when zscor/hilo signals that it will improve. Shorts market if price below long mavg. Otherwise holds cash.

  • RETURNS: 283.1%
  • ALPHA: 0.17
  • BETA: 0.79
  • SHARPE: 1.22
  • MAX DRAWDOWN: 43.8%

Thanks, Eric. I did not notice adding short is that easy.

I just changed a little bit of code to hold short position instead of hold cash. Total return is 258.8% from 2006.6.21 to 2016.5.6.
I also tried to use SPY/SH pair to avoid short directly, total return is 180.3% for the same period.
Two questions:
1. The max drawdown is too high (more than 40%), is there anyway to improve?
2. Why the performance of using SPY/SH is much worse than long/short SPY?

Any comments? thanks.

Clone Algorithm
43
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
import numpy as np
import math
import datetime
from sqlalchemy import or_
import scipy.stats as stats
import talib as ta


#compute online moving average with basket of 15 Fortune 500 companies
def before_trading_start(context, data):
    sp_500 = get_fundamentals(
                query(fundamentals.valuation.market_cap,
                     fundamentals.company_reference.primary_exchange_id)
                .filter(fundamentals.valuation.market_cap > 4e9)
                .filter(fundamentals.share_class_reference.is_primary_share == True)
                .filter(fundamentals.company_reference.country_id == "USA")
                .filter(or_(fundamentals.company_reference.primary_exchange_id == "NAS", fundamentals.company_reference.primary_exchange_id == "NYS"))
                .order_by(fundamentals.valuation.market_cap.desc())
                .limit(499))  # S&P 500 has 500 but I need 1 for the SPY  
    context.fundamental_df = sp_500
    

    # Update the universe of securities
    #update_universe(context.fundamental_df.columns.values)

def initialize(context):
    context.stocks = ''
    context.spy = symbol('SPY')
    context.history_close = None
    context.history_max  = None
    context.history_min  = None
    context.lookback = 62 
    context.smoothing = 40 #40 is standard
    context.holdtilpositive = False
    context.holdtilnegative = False
    context.buytomorrow = False
    
    context.threshold = 0.0035 #5% is standard
    context.hilo_index = []
    context.hilo_MAindex = []
    context.slope_index = []
    context.spyslope_index = []
    
    
    context.current_hilo = -10
    
    context.m = len(context.stocks)
    context.b_t = np.ones(context.m) / context.m
    context.eps = 0.75 # change epsilon here
    context.init = False
    context.previous_datetime = None
    context.min_shares        = 2
    
    #for ib trades
    set_commission(commission.PerShare(cost=0.005, min_trade_cost=1))
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))
    
    #for making the history available in before trade start:
    schedule_function(Algo_hilo, date_rules.every_day(),
                      time_rules.market_close(minutes=5))

    # schedule_function(daily, date_rule=date_rules.week_start(2),
    #                   time_rule=time_rules.market_open(minutes=30))

    schedule_function(pvr, date_rules.every_day(), time_rules.market_close())
    
    
def Algo_hilo(context, data):
    c = context
    #cash = c.portfolio.cash
    #leverage = c.account.leverage
    c.history_close = data.history(c.fundamental_df.columns.values, 'price', 202, '1d')
    c.spy_close = data.history(c.spy, 'price', 202, '1d')
    #Calc spy slope
    x1 = list(xrange(22))
    spyslope, spyintercept, psyr_value, spyp_value, spystd_err = stats.linregress(
        x1,c.spy_close[-22:])    # spy slope info
    c.spyslope_index.append(spyslope)
    #Calc zscore of spy slope
    z_spyslope = stats.zscore(c.spyslope_index, axis=0, ddof=1)[-1]
    accspyslope = 0
    #Calc accelleration of spy
    if len(c.spyslope_index) > 5:
        x2 = list(xrange(5))
        accspyslope, accspyintercept, accpsyr_value, accspyp_value, accspystd_err = stats.linregress(x2,context.spyslope_index[-5:])
    #Calc long and short mavg spy
    #mavg_short = np.mean(c.spy_close[-22:])
    mavg_long = np.mean(c.spy_close[-200:])
    current_price = c.spy_close[-1]        
    
    #get maxes and minimums
    c.history_max = c.history_close.idxmax(axis=0, skipna=True)
    c.history_min = c.history_close.idxmin(axis=0, skipna=True)
    minctr = 0
    maxctr = 0
    stockctr = len(c.history_close.columns)
    #find number of hi's and Lows
    for stock in c.history_max.index:
        datemax = c.history_max[stock]
        if str(type(datemax)) == "<class 'pandas.tslib.Timestamp'>" \
          and datemax.date() == get_datetime().date(): maxctr += 1
    for stock in c.history_min.index:
         datemin = c.history_min[stock] 
         if str(type(datemin)) == "<class 'pandas.tslib.Timestamp'>" \
           and datemin.date() == get_datetime().date(): minctr += 1          
    #hi = float(maxctr)/stockctr
    #lo = float(minctr)/stockctr
    ratio = (float(maxctr - minctr))/stockctr
    c.hilo_index.append(ratio)
    #calc zscore of ratio
    z_ratio= stats.zscore(c.hilo_index[-c.smoothing:], axis=0, ddof=1)[-1]
    ma_ratio = np.mean(c.hilo_index[-c.smoothing:])
    c.hilo_MAindex.append(ma_ratio)
    c.current_hilo = ratio
    #calc slope of MA HiLo index
    if len(c.hilo_MAindex)> 7:
        x = list(xrange(7))
        slope, intercept, r_value, p_value, std_err = stats.linregress(
            x, c.hilo_MAindex[-7:])
    else:
        slope = 0
        #p_value =0
        
    c.slope_index.append(slope)
    
    if len(c.slope_index) > 7:
        slope_ma = np.mean(c.slope_index[-7:])
    else:
        slope_ma = 0
   
    '''
    if mavg_long > current_price: 
        record(mavg_long_vs_price=-1) 
    else:
        record(mavg_long_vs_price=1)
    if c.holdtilpositive: 
        record(holdtilpositive= 1) 
    else: 
        record(holdtilpositive=0)   
    record(zspyslope = z_spyslope)
    record(zratio_hi_lo = z_ratio)
    record(slopespy = spyslope)    
    '''

    if (spyslope > 0 and slope_ma > 0) and c.holdtilpositive:
        c.holdtilpositive = False
    elif spyslope < 0 and c.holdtilnegative:
        c.holdtilnegative = False
    
    
    if c.buytomorrow:
        order_target_percent(context.spy, 1)
        c.buytomorrow = False
        return

    no_spys = context.portfolio.positions[c.spy].amount
    if  z_spyslope > 0:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days > 0: we are moving up')
        c.buytomorrow = True #Buy with delay of 1 day
        return
    elif  z_spyslope < -.7:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days < -0.7: mean reverting')
        order_target_percent(context.spy, 1)       
        c.holdtilpositive = True
        return
    elif (z_spyslope < 0 and not c.holdtilpositive) or mavg_long > current_price or (z_spyslope > 1.0 or z_ratio > 1.5):
        if no_spys>0 and mavg_long > current_price: log.info('Selling: price below Long mavg 122 days')
        if no_spys>0 and z_spyslope > 1.0: log.info('Selling: z score of the Slope of SPY over 22 days > 1.0')
        if no_spys>0 and z_ratio > 1.5: log.info('Selling: z score of the Ratio HILO of SPY over 22 days > 1.5')
        if no_spys>0 and (z_spyslope < 0 and not context.holdtilpositive): log.info('Selling: z score of the Slope of SPY over 22 days < -0.0 and not holding till positive')
        #order_target_percent(context.spy, 0)
        order_target_percent(context.spy, -1)
        return
    return

def pvr(context, data):  
    ''' Custom chart and/or log of profit_vs_risk returns and related information  
    '''  
    # # # # # # # # # #  Options  # # # # # # # # # #  
    record_max_lvrg = 1         # Maximum leverage encountered  
    record_leverage = 0         # Leverage (context.account.leverage)  
    record_q_return = 0         # Quantopian returns (percentage)  
    record_pvr      = 1         # Profit vs Risk returns (percentage)  
    record_pnl      = 0         # Profit-n-Loss  
    record_shorting = 1         # Total value of any shorts  
    record_overshrt = 0         # Shorts beyond longs+cash  
    record_risk     = 0         # Risked, max cash spent or shorts beyond longs+cash  
    record_risk_hi  = 1         # Highest risk overall  
    record_cash     = 0         # Cash available  
    record_cash_low = 1         # Any new lowest cash level  
    logging         = 1         # Also to logging window conditionally (1) or not (0)  
    log_method      = 'risk_hi' # 'daily' or 'risk_hi'

    from pytz import timezone   # Python will only do once, makes this portable.  
                                #   Move to top of algo for better efficiency.  
    c = context  # Brevity is the soul of wit -- Shakespeare [for efficiency, readability]  
    if 'pvr' not in c:  
        date_strt = get_environment('start').date()  
        date_end  = get_environment('end').date()  
        cash_low  = c.portfolio.starting_cash  
        mode      = get_environment('data_frequency')  
        c.pvr = {  
            'max_lvrg': 0,  
            'risk_hi' : 0,  
            'days'    : 0.0,  
            'date_prv': '',  
            'cash_low': cash_low,  
            'date_end': date_end,  
            'mode'    : mode,  
            'run_str' : '{} to {}  {}  {}'.format(date_strt,date_end,int(cash_low),mode)  
        }  
        log.info(c.pvr['run_str'])  
    pvr_rtrn     = 0            # Profit vs Risk returns based on maximum spent  
    profit_loss  = 0            # Profit-n-loss  
    shorts       = 0            # Shorts value  
    longs        = 0            # Longs  value  
    overshorts   = 0            # Shorts value beyond longs plus cash  
    new_risk_hi  = 0  
    new_cash_low = 0                           # To trigger logging in cash_low case  
    lvrg         = c.account.leverage          # Standard leverage, in-house  
    date         = get_datetime().date()       # To trigger logging in daily case  
    cash         = c.portfolio.cash  
    start        = c.portfolio.starting_cash  
    cash_dip     = int(max(0, start - cash))  
    q_rtrn       = 100 * (c.portfolio.portfolio_value - start) / start

    if int(cash) < c.pvr['cash_low']:                # New cash low  
        new_cash_low = 1  
        c.pvr['cash_low']   = int(cash)  
        if record_cash_low:  
            record(CashLow = int(c.pvr['cash_low'])) # Lowest cash level hit

    if record_max_lvrg:  
        if c.account.leverage > c.pvr['max_lvrg']:  
            c.pvr['max_lvrg'] = c.account.leverage  
            record(MaxLv = c.pvr['max_lvrg'])        # Maximum leverage

    if record_pnl:  
        profit_loss = c.portfolio.pnl  
        record(PnL = profit_loss)                    # "Profit and Loss" in dollars

    for p in c.portfolio.positions:  
        shrs = c.portfolio.positions[p].amount  
        if shrs < 0:  
            shorts += int(abs(shrs * data.current(p, "price")))  
        if shrs > 0:  
            longs  += int(shrs * data.current(p, "price"))

    if shorts > longs + cash: overshorts = shorts             # Shorts when too high  
    if record_shorting: record(Shorts  = shorts)              # Shorts value as a positve  
    if record_overshrt: record(OvrShrt = overshorts)          # Shorts value as a positve  
    if record_cash:     record(Cash = int(c.portfolio.cash))  # Cash  
    if record_leverage: record(Lvrg = c.account.leverage)     # Leverage

    risk = int(max(cash_dip, shorts))  
    if record_risk: record(Risk = risk)       # Amount in play, maximum of shorts or cash used

    if risk > c.pvr['risk_hi']:  
        c.pvr['risk_hi'] = risk  
        new_risk_hi = 1

        if record_risk_hi:  
            record(RiskHi = c.pvr['risk_hi']) # Highest risk overall

    if record_pvr:      # Profit_vs_Risk returns based on max amount actually spent (risk high)  
        if c.pvr['risk_hi'] != 0:     # Avoid zero-divide  
            pvr_rtrn = 100 * (c.portfolio.portfolio_value - start) / c.pvr['risk_hi']  
            record(PvR = pvr_rtrn)            # Profit_vs_Risk returns

    if record_q_return:  
        record(QRet = q_rtrn)                 # Quantopian returns to compare to pvr returns curve

    def _minute():   # To preface each line with minute of the day.  
        if get_environment('data_frequency') == 'minute':  
            bar_dt = get_datetime().astimezone(timezone('US/Eastern'))  
            minute = (bar_dt.hour * 60) + bar_dt.minute - 570  # (-570 = 9:31a)  
            return str(minute).rjust(3)  
        return ''    # Daily mode, just leave it out.

    def _pvr_():  
            log.info('PvR {} %/day     {}'.format(  
                '%.4f' % (pvr_rtrn / c.pvr['days']), c.pvr['run_str']))  
            log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format(  
                '%.0f' % (c.portfolio.portfolio_value - start), '%.0f' % c.pvr['risk_hi'],  
                '%.1f' % pvr_rtrn))  
            log.info('  QRet {} PvR {} CshLw {} MxLv {} RskHi {} Shrts {}'.format(  
                '%.2f' % q_rtrn, '%.2f' % pvr_rtrn, '%.0f' % c.pvr['cash_low'],  
                '%.2f' % c.pvr['max_lvrg'], '%.0f' % c.pvr['risk_hi'], '%.0f' % shorts))

    if logging:  
        if log_method == 'risk_hi' and new_risk_hi \
          or log_method == 'daily' and c.pvr['date_prv'] != date \
          or new_cash_low:  
            qret    = ' QRet '   + '%.1f' % q_rtrn  
            lv      = ' Lv '     + '%.1f' % lvrg              if record_leverage else ''  
            pvr     = ' PvR '    + '%.1f' % pvr_rtrn          if record_pvr      else ''  
            pnl     = ' PnL '    + '%.0f' % profit_loss       if record_pnl      else ''  
            csh     = ' Cash '   + '%.0f' % cash              if record_cash     else ''  
            shrt    = ' Shrt '   + '%.0f' % shorts            if record_shorting else ''  
            ovrshrt = ' Shrt '   + '%.0f' % overshorts        if record_overshrt else ''  
            risk    = ' Risk '   + '%.0f' % risk              if record_risk     else ''  
            mxlv    = ' MaxLv '  + '%.2f' % c.pvr['max_lvrg'] if record_max_lvrg else ''  
            csh_lw  = ' CshLw '  + '%.0f' % c.pvr['cash_low'] if record_cash_low else ''  
            rsk_hi  = ' RskHi '  + '%.0f' % c.pvr['risk_hi']  if record_risk_hi  else ''  
            log.info('{}{}{}{}{}{}{}{}{}{}{}{}'.format(_minute(),  
               lv, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, ovrshrt, risk, rsk_hi))  
    if c.pvr['date_prv'] != date: c.pvr['days'] += 1.0  
    if c.pvr['days'] % 130 == 0 and _minute() == '100': _pvr_()  
    c.pvr['date_prv'] = date  
    if c.pvr['date_end'] == date:  
        # Summary on last minute of last day.  
        # If using schedule_function(), backtest last day/time may need to match for this to execute.  
        if 'pvr_summary_done' not in c: c.pvr_summary_done = 0  
        log_summary = 0  
        if c.pvr['mode'] == 'daily' and get_datetime().date() == c.pvr['date_end']:  
            log_summary = 1  
        elif c.pvr['mode'] == 'minute' and get_datetime() == get_environment('end'):  
            log_summary = 1  
        if log_summary and not c.pvr_summary_done:  
            _pvr_()  
            c.pvr_summary_done = 1
There was a runtime error.

Lucas, Would it be possible to modify the SPY/SH version you speak of, to allow multiple intraday trades? Utilizing a Robinhood instant account with a 25K+ balance would remove the obstacle of settlement delays and commissions.

I would suggest altering the algo to seek out periods of the day that are more liquid. Seems like the algo is being held hostage to whatever liquidity exists in the market at the designated order time (5 minutes before close). I experimented (on the most recent algo by Lucas) by switching the order time to 45 minutes before market close. Results are below. I would posit that the better solution would be to dynamically change order times based on market depth.

Clone Algorithm
86
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
import numpy as np
import math
import datetime
from sqlalchemy import or_
import scipy.stats as stats
import talib as ta


#compute online moving average with basket of 15 Fortune 500 companies
def before_trading_start(context, data):
    sp_500 = get_fundamentals(
                query(fundamentals.valuation.market_cap,
                     fundamentals.company_reference.primary_exchange_id)
                .filter(fundamentals.valuation.market_cap > 4e9)
                .filter(fundamentals.share_class_reference.is_primary_share == True)
                .filter(fundamentals.company_reference.country_id == "USA")
                .filter(or_(fundamentals.company_reference.primary_exchange_id == "NAS", fundamentals.company_reference.primary_exchange_id == "NYS"))
                .order_by(fundamentals.valuation.market_cap.desc())
                .limit(499))  # S&P 500 has 500 but I need 1 for the SPY  
    context.fundamental_df = sp_500
    

    # Update the universe of securities
    #update_universe(context.fundamental_df.columns.values)

def initialize(context):
    context.stocks = ''
    context.spy = symbol('SPY')
    context.history_close = None
    context.history_max  = None
    context.history_min  = None
    context.lookback = 62 
    context.smoothing = 40 #40 is standard
    context.holdtilpositive = False
    context.holdtilnegative = False
    context.buytomorrow = False
    
    context.threshold = 0.0035 #5% is standard
    context.hilo_index = []
    context.hilo_MAindex = []
    context.slope_index = []
    context.spyslope_index = []
    
    
    context.current_hilo = -10
    
    context.m = len(context.stocks)
    context.b_t = np.ones(context.m) / context.m
    context.eps = 0.75 # change epsilon here
    context.init = False
    context.previous_datetime = None
    context.min_shares        = 2
    
    #for ib trades
    set_commission(commission.PerShare(cost=0.005, min_trade_cost=1))
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))
    
    #for making the history available in before trade start:
    schedule_function(Algo_hilo, date_rules.every_day(),
                      time_rules.market_close(minutes=45))

    # schedule_function(daily, date_rule=date_rules.week_start(2),
    #                   time_rule=time_rules.market_open(minutes=30))

    schedule_function(pvr, date_rules.every_day(), time_rules.market_close())
    
    
def Algo_hilo(context, data):
    c = context
    #cash = c.portfolio.cash
    #leverage = c.account.leverage
    c.history_close = data.history(c.fundamental_df.columns.values, 'price', 202, '1d')
    c.spy_close = data.history(c.spy, 'price', 202, '1d')
    #Calc spy slope
    x1 = list(xrange(22))
    spyslope, spyintercept, psyr_value, spyp_value, spystd_err = stats.linregress(
        x1,c.spy_close[-22:])    # spy slope info
    c.spyslope_index.append(spyslope)
    #Calc zscore of spy slope
    z_spyslope = stats.zscore(c.spyslope_index, axis=0, ddof=1)[-1]
    accspyslope = 0
    #Calc accelleration of spy
    if len(c.spyslope_index) > 5:
        x2 = list(xrange(5))
        accspyslope, accspyintercept, accpsyr_value, accspyp_value, accspystd_err = stats.linregress(x2,context.spyslope_index[-5:])
    #Calc long and short mavg spy
    #mavg_short = np.mean(c.spy_close[-22:])
    mavg_long = np.mean(c.spy_close[-200:])
    current_price = c.spy_close[-1]        
    
    #get maxes and minimums
    c.history_max = c.history_close.idxmax(axis=0, skipna=True)
    c.history_min = c.history_close.idxmin(axis=0, skipna=True)
    minctr = 0
    maxctr = 0
    stockctr = len(c.history_close.columns)
    #find number of hi's and Lows
    for stock in c.history_max.index:
        datemax = c.history_max[stock]
        if str(type(datemax)) == "<class 'pandas.tslib.Timestamp'>" \
          and datemax.date() == get_datetime().date(): maxctr += 1
    for stock in c.history_min.index:
         datemin = c.history_min[stock] 
         if str(type(datemin)) == "<class 'pandas.tslib.Timestamp'>" \
           and datemin.date() == get_datetime().date(): minctr += 1          
    #hi = float(maxctr)/stockctr
    #lo = float(minctr)/stockctr
    ratio = (float(maxctr - minctr))/stockctr
    c.hilo_index.append(ratio)
    #calc zscore of ratio
    z_ratio= stats.zscore(c.hilo_index[-c.smoothing:], axis=0, ddof=1)[-1]
    ma_ratio = np.mean(c.hilo_index[-c.smoothing:])
    c.hilo_MAindex.append(ma_ratio)
    c.current_hilo = ratio
    #calc slope of MA HiLo index
    if len(c.hilo_MAindex)> 7:
        x = list(xrange(7))
        slope, intercept, r_value, p_value, std_err = stats.linregress(
            x, c.hilo_MAindex[-7:])
    else:
        slope = 0
        #p_value =0
        
    c.slope_index.append(slope)
    
    if len(c.slope_index) > 7:
        slope_ma = np.mean(c.slope_index[-7:])
    else:
        slope_ma = 0
   
    '''
    if mavg_long > current_price: 
        record(mavg_long_vs_price=-1) 
    else:
        record(mavg_long_vs_price=1)
    if c.holdtilpositive: 
        record(holdtilpositive= 1) 
    else: 
        record(holdtilpositive=0)   
    record(zspyslope = z_spyslope)
    record(zratio_hi_lo = z_ratio)
    record(slopespy = spyslope)    
    '''

    if (spyslope > 0 and slope_ma > 0) and c.holdtilpositive:
        c.holdtilpositive = False
    elif spyslope < 0 and c.holdtilnegative:
        c.holdtilnegative = False
    
    
    if c.buytomorrow:
        order_target_percent(context.spy, 1)
        c.buytomorrow = False
        return

    no_spys = context.portfolio.positions[c.spy].amount
    if  z_spyslope > 0:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days > 0: we are moving up')
        c.buytomorrow = True #Buy with delay of 1 day
        return
    elif  z_spyslope < -.7:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days < -0.7: mean reverting')
        order_target_percent(context.spy, 1)       
        c.holdtilpositive = True
        return
    elif (z_spyslope < 0 and not c.holdtilpositive) or mavg_long > current_price or (z_spyslope > 1.0 or z_ratio > 1.5):
        if no_spys>0 and mavg_long > current_price: log.info('Selling: price below Long mavg 122 days')
        if no_spys>0 and z_spyslope > 1.0: log.info('Selling: z score of the Slope of SPY over 22 days > 1.0')
        if no_spys>0 and z_ratio > 1.5: log.info('Selling: z score of the Ratio HILO of SPY over 22 days > 1.5')
        if no_spys>0 and (z_spyslope < 0 and not context.holdtilpositive): log.info('Selling: z score of the Slope of SPY over 22 days < -0.0 and not holding till positive')
        #order_target_percent(context.spy, 0)
        order_target_percent(context.spy, -1)
        return
    return

def pvr(context, data):  
    ''' Custom chart and/or log of profit_vs_risk returns and related information  
    '''  
    # # # # # # # # # #  Options  # # # # # # # # # #  
    record_max_lvrg = 1         # Maximum leverage encountered  
    record_leverage = 0         # Leverage (context.account.leverage)  
    record_q_return = 0         # Quantopian returns (percentage)  
    record_pvr      = 1         # Profit vs Risk returns (percentage)  
    record_pnl      = 0         # Profit-n-Loss  
    record_shorting = 1         # Total value of any shorts  
    record_overshrt = 0         # Shorts beyond longs+cash  
    record_risk     = 0         # Risked, max cash spent or shorts beyond longs+cash  
    record_risk_hi  = 1         # Highest risk overall  
    record_cash     = 0         # Cash available  
    record_cash_low = 1         # Any new lowest cash level  
    logging         = 1         # Also to logging window conditionally (1) or not (0)  
    log_method      = 'risk_hi' # 'daily' or 'risk_hi'

    from pytz import timezone   # Python will only do once, makes this portable.  
                                #   Move to top of algo for better efficiency.  
    c = context  # Brevity is the soul of wit -- Shakespeare [for efficiency, readability]  
    if 'pvr' not in c:  
        date_strt = get_environment('start').date()  
        date_end  = get_environment('end').date()  
        cash_low  = c.portfolio.starting_cash  
        mode      = get_environment('data_frequency')  
        c.pvr = {  
            'max_lvrg': 0,  
            'risk_hi' : 0,  
            'days'    : 0.0,  
            'date_prv': '',  
            'cash_low': cash_low,  
            'date_end': date_end,  
            'mode'    : mode,  
            'run_str' : '{} to {}  {}  {}'.format(date_strt,date_end,int(cash_low),mode)  
        }  
        log.info(c.pvr['run_str'])  
    pvr_rtrn     = 0            # Profit vs Risk returns based on maximum spent  
    profit_loss  = 0            # Profit-n-loss  
    shorts       = 0            # Shorts value  
    longs        = 0            # Longs  value  
    overshorts   = 0            # Shorts value beyond longs plus cash  
    new_risk_hi  = 0  
    new_cash_low = 0                           # To trigger logging in cash_low case  
    lvrg         = c.account.leverage          # Standard leverage, in-house  
    date         = get_datetime().date()       # To trigger logging in daily case  
    cash         = c.portfolio.cash  
    start        = c.portfolio.starting_cash  
    cash_dip     = int(max(0, start - cash))  
    q_rtrn       = 100 * (c.portfolio.portfolio_value - start) / start

    if int(cash) < c.pvr['cash_low']:                # New cash low  
        new_cash_low = 1  
        c.pvr['cash_low']   = int(cash)  
        if record_cash_low:  
            record(CashLow = int(c.pvr['cash_low'])) # Lowest cash level hit

    if record_max_lvrg:  
        if c.account.leverage > c.pvr['max_lvrg']:  
            c.pvr['max_lvrg'] = c.account.leverage  
            record(MaxLv = c.pvr['max_lvrg'])        # Maximum leverage

    if record_pnl:  
        profit_loss = c.portfolio.pnl  
        record(PnL = profit_loss)                    # "Profit and Loss" in dollars

    for p in c.portfolio.positions:  
        shrs = c.portfolio.positions[p].amount  
        if shrs < 0:  
            shorts += int(abs(shrs * data.current(p, "price")))  
        if shrs > 0:  
            longs  += int(shrs * data.current(p, "price"))

    if shorts > longs + cash: overshorts = shorts             # Shorts when too high  
    if record_shorting: record(Shorts  = shorts)              # Shorts value as a positve  
    if record_overshrt: record(OvrShrt = overshorts)          # Shorts value as a positve  
    if record_cash:     record(Cash = int(c.portfolio.cash))  # Cash  
    if record_leverage: record(Lvrg = c.account.leverage)     # Leverage

    risk = int(max(cash_dip, shorts))  
    if record_risk: record(Risk = risk)       # Amount in play, maximum of shorts or cash used

    if risk > c.pvr['risk_hi']:  
        c.pvr['risk_hi'] = risk  
        new_risk_hi = 1

        if record_risk_hi:  
            record(RiskHi = c.pvr['risk_hi']) # Highest risk overall

    if record_pvr:      # Profit_vs_Risk returns based on max amount actually spent (risk high)  
        if c.pvr['risk_hi'] != 0:     # Avoid zero-divide  
            pvr_rtrn = 100 * (c.portfolio.portfolio_value - start) / c.pvr['risk_hi']  
            record(PvR = pvr_rtrn)            # Profit_vs_Risk returns

    if record_q_return:  
        record(QRet = q_rtrn)                 # Quantopian returns to compare to pvr returns curve

    def _minute():   # To preface each line with minute of the day.  
        if get_environment('data_frequency') == 'minute':  
            bar_dt = get_datetime().astimezone(timezone('US/Eastern'))  
            minute = (bar_dt.hour * 60) + bar_dt.minute - 570  # (-570 = 9:31a)  
            return str(minute).rjust(3)  
        return ''    # Daily mode, just leave it out.

    def _pvr_():  
            log.info('PvR {} %/day     {}'.format(  
                '%.4f' % (pvr_rtrn / c.pvr['days']), c.pvr['run_str']))  
            log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format(  
                '%.0f' % (c.portfolio.portfolio_value - start), '%.0f' % c.pvr['risk_hi'],  
                '%.1f' % pvr_rtrn))  
            log.info('  QRet {} PvR {} CshLw {} MxLv {} RskHi {} Shrts {}'.format(  
                '%.2f' % q_rtrn, '%.2f' % pvr_rtrn, '%.0f' % c.pvr['cash_low'],  
                '%.2f' % c.pvr['max_lvrg'], '%.0f' % c.pvr['risk_hi'], '%.0f' % shorts))

    if logging:  
        if log_method == 'risk_hi' and new_risk_hi \
          or log_method == 'daily' and c.pvr['date_prv'] != date \
          or new_cash_low:  
            qret    = ' QRet '   + '%.1f' % q_rtrn  
            lv      = ' Lv '     + '%.1f' % lvrg              if record_leverage else ''  
            pvr     = ' PvR '    + '%.1f' % pvr_rtrn          if record_pvr      else ''  
            pnl     = ' PnL '    + '%.0f' % profit_loss       if record_pnl      else ''  
            csh     = ' Cash '   + '%.0f' % cash              if record_cash     else ''  
            shrt    = ' Shrt '   + '%.0f' % shorts            if record_shorting else ''  
            ovrshrt = ' Shrt '   + '%.0f' % overshorts        if record_overshrt else ''  
            risk    = ' Risk '   + '%.0f' % risk              if record_risk     else ''  
            mxlv    = ' MaxLv '  + '%.2f' % c.pvr['max_lvrg'] if record_max_lvrg else ''  
            csh_lw  = ' CshLw '  + '%.0f' % c.pvr['cash_low'] if record_cash_low else ''  
            rsk_hi  = ' RskHi '  + '%.0f' % c.pvr['risk_hi']  if record_risk_hi  else ''  
            log.info('{}{}{}{}{}{}{}{}{}{}{}{}'.format(_minute(),  
               lv, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, ovrshrt, risk, rsk_hi))  
    if c.pvr['date_prv'] != date: c.pvr['days'] += 1.0  
    if c.pvr['days'] % 130 == 0 and _minute() == '100': _pvr_()  
    c.pvr['date_prv'] = date  
    if c.pvr['date_end'] == date:  
        # Summary on last minute of last day.  
        # If using schedule_function(), backtest last day/time may need to match for this to execute.  
        if 'pvr_summary_done' not in c: c.pvr_summary_done = 0  
        log_summary = 0  
        if c.pvr['mode'] == 'daily' and get_datetime().date() == c.pvr['date_end']:  
            log_summary = 1  
        elif c.pvr['mode'] == 'minute' and get_datetime() == get_environment('end'):  
            log_summary = 1  
        if log_summary and not c.pvr_summary_done:  
            _pvr_()  
            c.pvr_summary_done = 1
There was a runtime error.

@April, to be honest, I don't know how to implement an intraday version, I attach the SPY/SH algo here, so you could try to modify yourself.
@Frank, nice try! Your total return is 291.1% comparing with my 258.8%, but max drawdown goes up to 45.4% from 40.5%. As Eric mentioned, adding a mavg check doesn't help for reducing maxdd. There might be some other ways though.

Clone Algorithm
29
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
import numpy as np
import math
import datetime
from sqlalchemy import or_
import scipy.stats as stats
import talib as ta


#compute online moving average with basket of 15 Fortune 500 companies
def before_trading_start(context, data):
    sp_500 = get_fundamentals(
                query(fundamentals.valuation.market_cap,
                     fundamentals.company_reference.primary_exchange_id)
                .filter(fundamentals.valuation.market_cap > 4e9)
                .filter(fundamentals.share_class_reference.is_primary_share == True)
                .filter(fundamentals.company_reference.country_id == "USA")
                .filter(or_(fundamentals.company_reference.primary_exchange_id == "NAS", fundamentals.company_reference.primary_exchange_id == "NYS"))
                .order_by(fundamentals.valuation.market_cap.desc())
                .limit(499))  # S&P 500 has 500 but I need 1 for the SPY  
    context.fundamental_df = sp_500
    

    # Update the universe of securities
    #update_universe(context.fundamental_df.columns.values)

def initialize(context):
    context.stocks = ''
    context.spy = symbol('SPY')
    context.sh = symbol('SH')
    context.history_close = None
    context.history_max  = None
    context.history_min  = None
    context.lookback = 62 
    context.smoothing = 40 #40 is standard
    context.holdtilpositive = False
    context.holdtilnegative = False
    context.buytomorrow = False
    
    context.threshold = 0.0035 #5% is standard
    context.hilo_index = []
    context.hilo_MAindex = []
    context.slope_index = []
    context.spyslope_index = []
    
    
    context.current_hilo = -10
    
    context.m = len(context.stocks)
    context.b_t = np.ones(context.m) / context.m
    context.eps = 0.75 # change epsilon here
    context.init = False
    context.previous_datetime = None
    context.min_shares        = 2
    
    set_commission(commission.PerShare(cost=0.005, min_trade_cost=1))
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))
    
    #for making the history available in before trade start:
    schedule_function(Algo_hilo, date_rules.every_day(),
                      time_rules.market_close(minutes=5))

    # schedule_function(daily, date_rule=date_rules.week_start(2),
    #                   time_rule=time_rules.market_open(minutes=30))

    schedule_function(pvr, date_rules.every_day(), time_rules.market_close())
    
    
def order_target_percent_ex(context, data, security, percent):
    target_value = context.portfolio.portfolio_value * percent
    last_price = data.current(security, 'price')
    amount = target_value / last_price
    if security in context.portfolio.positions:
        current_position = context.portfolio.positions[security].amount
        req_shares = amount - current_position
    else:
        req_shares = amount
    req_shares = int(req_shares)
    # at least 2 shares
    if abs(req_shares) > context.min_shares:
        order_target_percent(security, percent)
    #else:
    #    log.info('skip order {}  for {} shares'.format(security.symbol, req_shares))

def Algo_hilo(context, data):
    c = context
    #cash = c.portfolio.cash
    #leverage = c.account.leverage
    c.history_close = data.history(c.fundamental_df.columns.values, 'price', 202, '1d')
    c.spy_close = data.history(c.spy, 'price', 202, '1d')
    #Calc spy slope
    x1 = list(xrange(22))
    spyslope, spyintercept, psyr_value, spyp_value, spystd_err = stats.linregress(
        x1,c.spy_close[-22:])    # spy slope info
    c.spyslope_index.append(spyslope)
    #Calc zscore of spy slope
    z_spyslope = stats.zscore(c.spyslope_index, axis=0, ddof=1)[-1]
    accspyslope = 0
    #Calc accelleration of spy
    if len(c.spyslope_index) > 5:
        x2 = list(xrange(5))
        accspyslope, accspyintercept, accpsyr_value, accspyp_value, accspystd_err = stats.linregress(x2,context.spyslope_index[-5:])
    #Calc long and short mavg spy
    #mavg_short = np.mean(c.spy_close[-22:])
    mavg_long = np.mean(c.spy_close[-200:])
    current_price = c.spy_close[-1]        
    
    #get maxes and minimums
    c.history_max = c.history_close.idxmax(axis=0, skipna=True)
    c.history_min = c.history_close.idxmin(axis=0, skipna=True)
    minctr = 0
    maxctr = 0
    stockctr = len(c.history_close.columns)
    #find number of hi's and Lows
    for stock in c.history_max.index:
        datemax = c.history_max[stock]
        if str(type(datemax)) == "<class 'pandas.tslib.Timestamp'>" \
          and datemax.date() == get_datetime().date(): maxctr += 1
    for stock in c.history_min.index:
         datemin = c.history_min[stock] 
         if str(type(datemin)) == "<class 'pandas.tslib.Timestamp'>" \
           and datemin.date() == get_datetime().date(): minctr += 1          
    #hi = float(maxctr)/stockctr
    #lo = float(minctr)/stockctr
    ratio = (float(maxctr - minctr))/stockctr
    c.hilo_index.append(ratio)
    #calc zscore of ratio
    z_ratio= stats.zscore(c.hilo_index[-c.smoothing:], axis=0, ddof=1)[-1]
    ma_ratio = np.mean(c.hilo_index[-c.smoothing:])
    c.hilo_MAindex.append(ma_ratio)
    c.current_hilo = ratio
    #calc slope of MA HiLo index
    if len(c.hilo_MAindex)> 7:
        x = list(xrange(7))
        slope, intercept, r_value, p_value, std_err = stats.linregress(
            x, c.hilo_MAindex[-7:])
    else:
        slope = 0
        #p_value =0
        
    c.slope_index.append(slope)
    
    if len(c.slope_index) > 7:
        slope_ma = np.mean(c.slope_index[-7:])
    else:
        slope_ma = 0
   
    '''
    if mavg_long > current_price: 
        record(mavg_long_vs_price=-1) 
    else:
        record(mavg_long_vs_price=1)
    if c.holdtilpositive: 
        record(holdtilpositive= 1) 
    else: 
        record(holdtilpositive=0)   
    record(zspyslope = z_spyslope)
    record(zratio_hi_lo = z_ratio)
    record(slopespy = spyslope)    
    '''

    if (spyslope > 0 and slope_ma > 0) and c.holdtilpositive:
        c.holdtilpositive = False
    elif spyslope < 0 and c.holdtilnegative:
        c.holdtilnegative = False
    
    
    if c.buytomorrow:
        order_target_percent(context.sh, 0)
        order_target_percent_ex(c, data, context.spy, 1)
        c.buytomorrow = False
        return

    no_spys = context.portfolio.positions[c.spy].amount
    no_shs = context.portfolio.positions[c.sh].amount
    if  z_spyslope > 0:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days > 0: we are moving up')
        c.buytomorrow = True #Buy with delay of 1 day
        return
    elif  z_spyslope < -.7:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days < -0.7: mean reverting')
        order_target_percent(context.sh, 0)    
        order_target_percent_ex(context, data, context.spy, 1)       
        c.holdtilpositive = True
        return
    elif (z_spyslope < 0 and not c.holdtilpositive) or mavg_long > current_price or (z_spyslope > 1.0 or z_ratio > 1.5):
        if no_spys>0 and mavg_long > current_price: log.info('Selling: price below Long mavg 122 days')
        if no_spys>0 and z_spyslope > 1.0: log.info('Selling: z score of the Slope of SPY over 22 days > 1.0')
        if no_spys>0 and z_ratio > 1.5: log.info('Selling: z score of the Ratio HILO of SPY over 22 days > 1.5')
        if no_spys>0 and (z_spyslope < 0 and not context.holdtilpositive): log.info('Selling: z score of the Slope of SPY over 22 days < -0.0 and not holding till positive')
        order_target_percent(context.spy, 0)
        order_target_percent_ex(context, data, context.sh, 1)    
        return
    return

def pvr(context, data):  
    ''' Custom chart and/or log of profit_vs_risk returns and related information  
    '''  
    # # # # # # # # # #  Options  # # # # # # # # # #  
    record_max_lvrg = 1         # Maximum leverage encountered  
    record_leverage = 0         # Leverage (context.account.leverage)  
    record_q_return = 0         # Quantopian returns (percentage)  
    record_pvr      = 1         # Profit vs Risk returns (percentage)  
    record_pnl      = 0         # Profit-n-Loss  
    record_shorting = 1         # Total value of any shorts  
    record_overshrt = 0         # Shorts beyond longs+cash  
    record_risk     = 0         # Risked, max cash spent or shorts beyond longs+cash  
    record_risk_hi  = 1         # Highest risk overall  
    record_cash     = 0         # Cash available  
    record_cash_low = 1         # Any new lowest cash level  
    logging         = 1         # Also to logging window conditionally (1) or not (0)  
    log_method      = 'risk_hi' # 'daily' or 'risk_hi'

    from pytz import timezone   # Python will only do once, makes this portable.  
                                #   Move to top of algo for better efficiency.  
    c = context  # Brevity is the soul of wit -- Shakespeare [for efficiency, readability]  
    if 'pvr' not in c:  
        date_strt = get_environment('start').date()  
        date_end  = get_environment('end').date()  
        cash_low  = c.portfolio.starting_cash  
        mode      = get_environment('data_frequency')  
        c.pvr = {  
            'max_lvrg': 0,  
            'risk_hi' : 0,  
            'days'    : 0.0,  
            'date_prv': '',  
            'cash_low': cash_low,  
            'date_end': date_end,  
            'mode'    : mode,  
            'run_str' : '{} to {}  {}  {}'.format(date_strt,date_end,int(cash_low),mode)  
        }  
        log.info(c.pvr['run_str'])  
    pvr_rtrn     = 0            # Profit vs Risk returns based on maximum spent  
    profit_loss  = 0            # Profit-n-loss  
    shorts       = 0            # Shorts value  
    longs        = 0            # Longs  value  
    overshorts   = 0            # Shorts value beyond longs plus cash  
    new_risk_hi  = 0  
    new_cash_low = 0                           # To trigger logging in cash_low case  
    lvrg         = c.account.leverage          # Standard leverage, in-house  
    date         = get_datetime().date()       # To trigger logging in daily case  
    cash         = c.portfolio.cash  
    start        = c.portfolio.starting_cash  
    cash_dip     = int(max(0, start - cash))  
    q_rtrn       = 100 * (c.portfolio.portfolio_value - start) / start

    if int(cash) < c.pvr['cash_low']:                # New cash low  
        new_cash_low = 1  
        c.pvr['cash_low']   = int(cash)  
        if record_cash_low:  
            record(CashLow = int(c.pvr['cash_low'])) # Lowest cash level hit

    if record_max_lvrg:  
        if c.account.leverage > c.pvr['max_lvrg']:  
            c.pvr['max_lvrg'] = c.account.leverage  
            record(MaxLv = c.pvr['max_lvrg'])        # Maximum leverage

    if record_pnl:  
        profit_loss = c.portfolio.pnl  
        record(PnL = profit_loss)                    # "Profit and Loss" in dollars

    for p in c.portfolio.positions:  
        shrs = c.portfolio.positions[p].amount  
        if shrs < 0:  
            shorts += int(abs(shrs * data.current(p, "price")))  
        if shrs > 0:  
            longs  += int(shrs * data.current(p, "price"))

    if shorts > longs + cash: overshorts = shorts             # Shorts when too high  
    if record_shorting: record(Shorts  = shorts)              # Shorts value as a positve  
    if record_overshrt: record(OvrShrt = overshorts)          # Shorts value as a positve  
    if record_cash:     record(Cash = int(c.portfolio.cash))  # Cash  
    if record_leverage: record(Lvrg = c.account.leverage)     # Leverage

    risk = int(max(cash_dip, shorts))  
    if record_risk: record(Risk = risk)       # Amount in play, maximum of shorts or cash used

    if risk > c.pvr['risk_hi']:  
        c.pvr['risk_hi'] = risk  
        new_risk_hi = 1

        if record_risk_hi:  
            record(RiskHi = c.pvr['risk_hi']) # Highest risk overall

    if record_pvr:      # Profit_vs_Risk returns based on max amount actually spent (risk high)  
        if c.pvr['risk_hi'] != 0:     # Avoid zero-divide  
            pvr_rtrn = 100 * (c.portfolio.portfolio_value - start) / c.pvr['risk_hi']  
            record(PvR = pvr_rtrn)            # Profit_vs_Risk returns

    if record_q_return:  
        record(QRet = q_rtrn)                 # Quantopian returns to compare to pvr returns curve

    def _minute():   # To preface each line with minute of the day.  
        if get_environment('data_frequency') == 'minute':  
            bar_dt = get_datetime().astimezone(timezone('US/Eastern'))  
            minute = (bar_dt.hour * 60) + bar_dt.minute - 570  # (-570 = 9:31a)  
            return str(minute).rjust(3)  
        return ''    # Daily mode, just leave it out.

    def _pvr_():  
            log.info('PvR {} %/day     {}'.format(  
                '%.4f' % (pvr_rtrn / c.pvr['days']), c.pvr['run_str']))  
            log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format(  
                '%.0f' % (c.portfolio.portfolio_value - start), '%.0f' % c.pvr['risk_hi'],  
                '%.1f' % pvr_rtrn))  
            log.info('  QRet {} PvR {} CshLw {} MxLv {} RskHi {} Shrts {}'.format(  
                '%.2f' % q_rtrn, '%.2f' % pvr_rtrn, '%.0f' % c.pvr['cash_low'],  
                '%.2f' % c.pvr['max_lvrg'], '%.0f' % c.pvr['risk_hi'], '%.0f' % shorts))

    if logging:  
        if log_method == 'risk_hi' and new_risk_hi \
          or log_method == 'daily' and c.pvr['date_prv'] != date \
          or new_cash_low:  
            qret    = ' QRet '   + '%.1f' % q_rtrn  
            lv      = ' Lv '     + '%.1f' % lvrg              if record_leverage else ''  
            pvr     = ' PvR '    + '%.1f' % pvr_rtrn          if record_pvr      else ''  
            pnl     = ' PnL '    + '%.0f' % profit_loss       if record_pnl      else ''  
            csh     = ' Cash '   + '%.0f' % cash              if record_cash     else ''  
            shrt    = ' Shrt '   + '%.0f' % shorts            if record_shorting else ''  
            ovrshrt = ' Shrt '   + '%.0f' % overshorts        if record_overshrt else ''  
            risk    = ' Risk '   + '%.0f' % risk              if record_risk     else ''  
            mxlv    = ' MaxLv '  + '%.2f' % c.pvr['max_lvrg'] if record_max_lvrg else ''  
            csh_lw  = ' CshLw '  + '%.0f' % c.pvr['cash_low'] if record_cash_low else ''  
            rsk_hi  = ' RskHi '  + '%.0f' % c.pvr['risk_hi']  if record_risk_hi  else ''  
            log.info('{}{}{}{}{}{}{}{}{}{}{}{}'.format(_minute(),  
               lv, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, ovrshrt, risk, rsk_hi))  
    if c.pvr['date_prv'] != date: c.pvr['days'] += 1.0  
    if c.pvr['days'] % 130 == 0 and _minute() == '100': _pvr_()  
    c.pvr['date_prv'] = date  
    if c.pvr['date_end'] == date:  
        # Summary on last minute of last day.  
        # If using schedule_function(), backtest last day/time may need to match for this to execute.  
        if 'pvr_summary_done' not in c: c.pvr_summary_done = 0  
        log_summary = 0  
        if c.pvr['mode'] == 'daily' and get_datetime().date() == c.pvr['date_end']:  
            log_summary = 1  
        elif c.pvr['mode'] == 'minute' and get_datetime() == get_environment('end'):  
            log_summary = 1  
        if log_summary and not c.pvr_summary_done:  
            _pvr_()  
            c.pvr_summary_done = 1
There was a runtime error.

Hi Lucas,

Good point, but my only intention was to point out that the algo seems to be arbitrarily anchored to a 3:55 (E.T.) execution time daily. I guess I am still trying to figure out why it only makes trades at that specific time?

Perhaps we can use a SPY-based volatility index (like VIX) to exit the market during periods of unusually high risk?

https://www.quantopian.com/data/quandl/cboe_vix

Drawdown seems to occur when the VIX is high. The three VIX spikes in Oct. '08, May '10, and Aug '11 coincide with when we lose money.

At points in time the algo's here dip below $0 cash, which would reject trades in a cash account.

No, it dips below zero returns - to as much as 40% losses. So a $100 initial investment, for example, would only be worth $60 in October '08, the lowest point of drawdown. Trades will continue to execute, you're just worse off than when you started.

But you're right; ideally we wouldn't let it get to that point. We want to take our money out of the S&P during bad market conditions and instead make our returns on well-placed shorts. So I'm going to see about making the algo more cautious during times of high volatility.

Eric -

Yes. I understand your point, but what I am trying to say is that if you have a cash account (and ca not use margin, like for an IRA account, or an Australian account) and you place an order for greater than the amount of money you have in your account, it will be rejected. The algo's above are not taking rhat into consideration as leverage sometimes goes above 1. Leverage needs to be below 1 at all times.

havent seen this in real trading yet but I'll create some guards while I post it to Q2. i have some other ideas to improve the algo and will post as soon as work is less mad

Peter -

It would be great to see your algo just as it is in Q2 :)

Rafael -- I see what you mean now; you're talking about unsettled cash, which raises a really good point.

Our cash will be tied up for a three-day period in settlement (T-3) after each sale of stock, and during that time we might decide to buy the market again. If we use a SPY/SH strategy, there will definitely be times when we want to turn around and buy an inverse quickly, and the order won't fill because we have no cash.

Robinhood Instant accounts let you use unsettled funds as though they were already settled (technically, they provide 100% margin at no interest on unsettled funds, then call them in when they settle), but not everyone has Instant and it's a pretty long queue to get into the beta. For normal cash accounts, here's how to factor in T-3 into your models: https://www.quantopian.com/help#sample-robinhood.

Eric, when the drawdown is close to -40%, is it possible in the program to add cash to average down, i.e., add money? When it go back to 0% or higher withdraw the added money? I am a beginner and very interest to see your post. And I started to learn the coding. Great work. Thanks.

No, there's no way to trigger a withdrawal / deposit from the Robinhood API.

Glad you're figuring things out! Good luck and have fun!

@Tony, if you really want to do that, as a workaround, you could manually deposit money to your account when you find the draw down is too high (-40% for example), the live trading algo will automatically buy SPY for you; or you could change this algo to always keep some cash unless the high draw down really happens.

@Lucas Lee , i thought it is a good idea to keep some cash as when drawdown is high. If this algo is changed to rebalance once a day, will it do better job? I am learning programming here and about to try that later.

I am new to quantopian, just trying to figure things out. Why is it when I backtest this algo, it will produce completely different signals based on the time frame I choose? I noticed this when I started to run in live mode, it was long SPY but the backtest ended yesterday is currently short SPY. I would think if I ran a backtest from 1/1/2016 to 5/19/2016, the signals in may should match the signals in a shorter backtest ran from 5/1/2016 to 5/19/2016 and should match live signals. I tried various timeframes and always get different results. Is this expected? Is anyone running this algo live?

Thanks.

After debugging a bit more, i found that the arrays during initialization are only appended to, so these arrays build over time.

For example in the line here:
spyslope= stats.zscore(context.spyslopeindex, axis=0, ddof=1)[-1]

The context.spyslope_index an array with a length equal to the number of days the algo has been running.

Seems like a bug to me, truncating this array to say the last 22 days yields very different results.

@io trader: if I would put it live I would preload/generate a decent history.

I ported the algo to Q2 and I added a Market Bias indicator and fixed and documented the code. I didn't optimise the parameters yet and I didn't align them ... I leave that for someone who thinks that makes a difference. The current parameters are a bit standard, a trading week and a half, a monthly period and a 200 day period, works for me

Happy to see improvements!

Clone Algorithm
323
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
#v1: pure market hilo
#v2: nornalised with Z scores
#v3: added MArket Bias indicator from : https://www.quantopian.com/posts/market-bias-indicator


import numpy as np
import math
from pytz import timezone
import datetime
import talib
from sqlalchemy import or_
import scipy.stats as stats
import talib as ta


#compute online moving average with basket of 15 Fortune 500 companies
def before_trading_start(context, data):
    sp_500 = get_fundamentals(
                query(fundamentals.valuation.market_cap,
                     fundamentals.company_reference.primary_exchange_id)
                .filter(fundamentals.valuation.market_cap > 4e9)
                .filter(fundamentals.share_class_reference.is_primary_share == True)
                .filter(fundamentals.company_reference.country_id == "USA")
                .filter(or_(fundamentals.company_reference.primary_exchange_id == "NAS", fundamentals.company_reference.primary_exchange_id == "NYS"))
                .order_by(fundamentals.valuation.market_cap.desc())
                .limit(100))  # S&P 500 has 500 but I need 1 for the SPY  
    context.fundamental_df = sp_500


def initialize(context):
    context.stocks = ''
    context.spy = symbol('SPY')
    context.stock =  context.spy
    set_benchmark(context.spy)
    context.max_lvrg            = 0.98
    context.bias_lookback1      = 5
    context.bias_lookback2      = 30
    context.biasSMA             = 400 # Moving Average value as input to bias calc
    context.collectionperiod    = 100
    context.trackbias           = []
    context.trackbias_strength  = []
    context.avgup               = []
    context.avgdown             = []
    context.history_close = None
    context.spy_close = None
    context.history_max  = None
    context.history_min  = None
    context.lookback = 62 
    context.smoothing = 50 #40 is standard
    context.holdtilpositive = False
    context.holdtilnegative = False
    context.buytomorrow = False
    context.sold_x_days_ago = 0
    context.delay=-3
    context.threshold = 0.0035 #5% is standard
    context.hilo_index = []
    context.hilo_MAindex = []
    context.slope_index = []
    context.spyslope_index = []
    context.current_hilo = -10
    context.m = len(context.stocks)
    context.b_t = np.ones(context.m) / context.m
    context.eps = 0.75 # change epsilon here
    context.init = False
    context.previous_datetime = None
    #for making the history available in before trade start:
    schedule_function(Algo_hilo, date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_close(minutes=5))

    # schedule_function(daily, date_rule=date_rules.week_start(2),
    #                   time_rule=time_rules.market_open(minutes=30))
    
    
     
def handle_data(context, data):
    pass
    
def Algo_hilo(context, data):
    context.sold_x_days_ago += 1
    no_spys = context.portfolio.positions[context.spy].amount
    
    cash = context.portfolio.cash
    leverage = context.account.leverage
    assetlist=context.fundamental_df.columns.values
    context.history_close = data.history(assetlist, bar_count=202, frequency='1d', fields='price')
    
    context.spy_close = data.history(context.spy, fields='price', bar_count=500, frequency= '1d')
    #spy_close = list(spy_close.values.flatten())
    
    short_SMA = talib.SMA(context.spy_close, timeperiod=context.biasSMA)   # EMA changed to SMA
    short_SMA = short_SMA.tolist()
    bias      = get_bias(short_SMA,context.spy_close, context)
    
    if bias[0] == 1:
        record(bias = bias[1]/50)
        context.trackbias.append(bias[0])
        context.trackbias_strength.append(bias[1])
        context.avgup.append(bias[1])
    elif bias[0] == 2:
        record(bias = -bias[1]/50)
        context.trackbias.append(bias[0])
        context.trackbias_strength.append(-bias[1])
    biasslope=0
    if len(context.trackbias_strength) > 7:
        x0 = list(xrange(7))
        biasslope, biasintercept, biasr_value, bias_pvalue, biasstd_err = stats.linregress(x0,context.trackbias_strength[-7:])
        record(biasslope = biasslope)
        
    #Calc spy slope
    x1 = list(xrange(22))
    spyslope, spyintercept, psyr_value, spyp_value, spystd_err = stats.linregress(x1,context.spy_close[-22:])
    context.spyslope_index.append(spyslope)
    #Calc zscore of spy slope
    z_spyslope= stats.zscore(context.spyslope_index, axis=0, ddof=1)[-1]
    
    #Calc accelleration of spy
    accspyslope = 0
    if len(context.spyslope_index) > 5:
        x2 = list(xrange(5))
        accspyslope, accspyintercept, accpsyr_value, accspyp_value, accspystd_err = stats.linregress(x2,context.spyslope_index[-5:])
    
    #Calc long and short mavg spy
    mavg_short = np.mean(context.spy_close[-8:])
    mavg_long = np.mean(context.spy_close[-300:])
    current_price = context.spy_close[-1]        
    
    #get maxes and minimums
    context.history_max = context.history_close.idxmax(axis=0, skipna=True)
    context.history_min = context.history_close.idxmin(axis=0, skipna=True)
    minctr = 0
    maxctr = 0
    stockctr = len(context.history_close.columns)
    #find number of hi's and Lows
    for stock in context.history_max.index:
        datemax = context.history_max[stock]
        if str(type(datemax))=="<class 'pandas.tslib.Timestamp'>" and datemax.date() == get_datetime().date(): maxctr += 1
    for stock in context.history_min.index:
         datemin = context.history_min[stock] 
         if str(type(datemin))=="<class 'pandas.tslib.Timestamp'>" and datemin.date() == get_datetime().date(): minctr += 1          
    hi = float(maxctr)/stockctr
    lo = float(minctr)/stockctr
    ratio = (float(maxctr - minctr))/stockctr
    context.hilo_index.append(ratio)
    
    #calc zscore of HiLo  ratio
    z_ratio= stats.zscore(context.hilo_index[-context.smoothing:], axis=0, ddof=1)[-1]
    ma_ratio = np.mean(context.hilo_index[-context.smoothing:])
    context.hilo_MAindex.append(ma_ratio)
    context.current_hilo = ratio
    
    #calc slope of MA HiLo index, 7 day
    if len(context.hilo_MAindex)> 7:
        x = list(xrange(7))
        slope, intercept, r_value, p_value, std_err = stats.linregress(x,context.hilo_MAindex[-7:])
    else:
        slope = 0.0
        p_value = 0
        
    context.slope_index.append(slope)
    
    if len(context.slope_index) > 7:
        slope_ma = np.mean(context.slope_index[-7:])
    else:
        slope_ma = 0.0
   

    if mavg_long > current_price: 
        record(mavg_long_vs_price=-1) 
    else:
        record(mavg_long_vs_price=1)
    # if context.holdtilpositive: 
    #     record(holdtilpositive= 1) 
    # else: 
    #     record(holdtilpositive=0)   
    record(zspyslope = z_spyslope)
    record(zratio_hi_lo = z_ratio)
    #record(slopespy = spyslope)    
    

    
    
    #execute old buy commands if they are there   
    if context.buytomorrow and context.sold_x_days_ago > 0 and no_spys==0:
        order_target_percent(context.spy, context.max_lvrg)
        context.buytomorrow = False
        log.info('> Executed BUY')
        return

    
   #generate new buy commands
    
    if  z_ratio > 0.8 and biasslope > 0 and not z_ratio < -2.2:
        if no_spys==0: log.info('Buying: z score of HiLo  of SPY over 22 days > 0.5 and slope of bias > 0: we are moving up')
        context.buytomorrow = True #Buy with delay of 1 or T+3 day
        return
    elif  z_spyslope > 0.05 and bias[0] == 1 and not z_ratio < -2.2:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days > 0: we are moving up')
        context.buytomorrow = True #Buy with delay of 1 or T+3 day
        return
    elif  z_spyslope < -0.9 and bias[0] == 1 and not z_ratio < -2.2:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days < -0.7: mean reverting')
        context.buytomorrow = True #Buy with delay of 1 or T+3 day      
        context.holdtilpositive = True
        return
    
    if context.holdtilpositive and (spyslope < 0 or slope_ma < 0):     #can I sell?
        #no I cannot sell (and I bough already so escape)
        log.info('-- Cannot Sell, I am waiting till things get positive')
        return
    else:
        if context.holdtilpositive:
            context.holdtilpositive =False
            log.info('Condition met, hold-to-positive is false now')
        if  z_ratio < -1.2 or (z_spyslope < 0 and not context.holdtilpositive) or mavg_long > current_price or (z_spyslope > 1.5 or z_ratio > 1.5) or (bias[0]==2  and biasslope < 0):

            if no_spys>0 and mavg_long > current_price: 
                log.info('Selling: price below Long mavg 122 days')
            elif no_spys>0 and z_spyslope > 1.5: 
                log.info('Selling: z score of the Slope of SPY over 22 days > 1.2')
            elif no_spys>0 and z_ratio > 1.5: 
                log.info('Selling: z score of the Ratio HILO of SPY over 22 days > 1.2')
            elif no_spys>0 and (z_spyslope < 0): 
                log.info('Selling: z score of the Slope of SPY over 22 days < -0.0 and not holding till positive')
            elif no_spys>0 and ( bias[0] == 2 and biasslope < 0): 
                log.info('Selling: Bias is negative and bias slope is negative')
            elif no_spys > 0:
                log.info('Selling: Unknown condition!')

            context.buytomorrow = False
            context.holdtilpositive = False
            
            if no_spys > 0:
                log.info('< Sold')
                order_target_percent(context.spy, 0)
            else:
                log.info('-- No Position')         
            #when a sell condition is discovered, always wait X days to buy:
            context.sold_x_days_ago = context.delay
    
    
     
    



def get_bias(ma,pc,context):
    ma1 = ma[-context.bias_lookback1:]    # array of moving average values
    pc1 = pc[-context.bias_lookback1:]    # array of price close values
    ma2 = ma[-context.bias_lookback2:]
    pc2 = pc[-context.bias_lookback2:]
    ma_mean1 = np.mean(ma1)    # mean of moving averages
    pc_mean1 = np.mean(pc1)    # mean of price values
    ma_mean2 = np.mean(ma2)
    pc_mean2 = np.mean(pc2)
    ma_mean  = (ma_mean1 + ma_mean2) / 2               # avg of averages
    pc_mean  = (pc_mean1 + pc_mean2) / 2
    ma_mean  = ma_mean2                                # override, back to original, changed my mind
    pc_mean  = pc_mean2
    if ma_mean > pc_mean:    # determines down bias and gets strength
        strength = ma_mean - pc_mean
        return 2, strength   # short
    elif pc_mean > ma_mean:  # determines down bias and gets strength
        strength = pc_mean - ma_mean
        return 1, strength   # long
    return 3, 0    # same or null    
    
There was a runtime error.

last version

Clone Algorithm
323
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
#v1: pure market hilo
#v2: nornalised with Z scores
#v3: added MArket Bias indicator from : https://www.quantopian.com/posts/market-bias-indicator


import numpy as np
import math
from pytz import timezone
import datetime
import talib
from sqlalchemy import or_
import scipy.stats as stats
import talib as ta


#compute online moving average with basket of 15 Fortune 500 companies
def before_trading_start(context, data):
    sp_500 = get_fundamentals(
                query(fundamentals.valuation.market_cap,
                     fundamentals.company_reference.primary_exchange_id)
                .filter(fundamentals.valuation.market_cap > 4e9)
                .filter(fundamentals.share_class_reference.is_primary_share == True)
                .filter(fundamentals.company_reference.country_id == "USA")
                .filter(or_(fundamentals.company_reference.primary_exchange_id == "NAS", fundamentals.company_reference.primary_exchange_id == "NYS"))
                .order_by(fundamentals.valuation.market_cap.desc())
                .limit(100))  # S&P 500 has 500 but I need 1 for the SPY  
    context.fundamental_df = sp_500


def initialize(context):
    context.stocks = ''
    context.spy = symbol('SPY')
    context.stock =  context.spy
    set_benchmark(context.spy)
    context.max_lvrg            = 0.98
    context.bias_lookback1      = 5
    context.bias_lookback2      = 30
    context.biasSMA             = 400 # Moving Average value as input to bias calc
    context.collectionperiod    = 100
    context.trackbias           = []
    context.trackbias_strength  = []
    context.avgup               = []
    context.avgdown             = []
    context.history_close = None
    context.spy_close = None
    context.history_max  = None
    context.history_min  = None
    context.lookback = 62 
    context.smoothing = 40 #40 is standard
    context.holdtilpositive = False
    context.holdtilnegative = False
    context.buytomorrow = False
    context.sold_x_days_ago = 0
    context.delay=-3
    context.threshold = 0.0035 #5% is standard
    context.hilo_index = []
    context.hilo_MAindex = []
    context.slope_index = []
    context.spyslope_index = []
    context.current_hilo = -10
    context.m = len(context.stocks)
    context.b_t = np.ones(context.m) / context.m
    context.eps = 0.75 # change epsilon here
    context.init = False
    context.previous_datetime = None
    #for making the history available in before trade start:
    schedule_function(Algo_hilo, date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_close(minutes=5))

    # schedule_function(daily, date_rule=date_rules.week_start(2),
    #                   time_rule=time_rules.market_open(minutes=30))
    
    
     
def handle_data(context, data):
    pass
    
def Algo_hilo(context, data):
    context.sold_x_days_ago += 1
    no_spys = context.portfolio.positions[context.spy].amount
    
    cash = context.portfolio.cash
    leverage = context.account.leverage
    assetlist=context.fundamental_df.columns.values
    context.history_close = data.history(assetlist, bar_count=202, frequency='1d', fields='price')
    
    context.spy_close = data.history(context.spy, fields='price', bar_count=500, frequency= '1d')
    #spy_close = list(spy_close.values.flatten())
    
    short_SMA = talib.SMA(context.spy_close, timeperiod=context.biasSMA)   # EMA changed to SMA
    short_SMA = short_SMA.tolist()
    bias      = get_bias(short_SMA,context.spy_close, context)
    
    if bias[0] == 1:
        record(bias = bias[1]/50)
        context.trackbias.append(bias[0])
        context.trackbias_strength.append(bias[1])
        context.avgup.append(bias[1])
    elif bias[0] == 2:
        record(bias = -bias[1]/50)
        context.trackbias.append(bias[0])
        context.trackbias_strength.append(-bias[1])
    biasslope=0
    if len(context.trackbias_strength) > 7:
        x0 = list(xrange(7))
        biasslope, biasintercept, biasr_value, bias_pvalue, biasstd_err = stats.linregress(x0,context.trackbias_strength[-7:])
        record(biasslope = biasslope)
        
    #Calc spy slope
    x1 = list(xrange(22))
    spyslope, spyintercept, psyr_value, spyp_value, spystd_err = stats.linregress(x1,context.spy_close[-22:])
    context.spyslope_index.append(spyslope)
    #Calc zscore of spy slope
    z_spyslope= stats.zscore(context.spyslope_index, axis=0, ddof=1)[-1]
    
    #Calc accelleration of spy
    accspyslope = 0
    if len(context.spyslope_index) > 5:
        x2 = list(xrange(5))
        accspyslope, accspyintercept, accpsyr_value, accspyp_value, accspystd_err = stats.linregress(x2,context.spyslope_index[-5:])
    
    #Calc long and short mavg spy
    mavg_short = np.mean(context.spy_close[-8:])
    mavg_long = np.mean(context.spy_close[-300:])
    current_price = context.spy_close[-1]        
    
    #get maxes and minimums
    context.history_max = context.history_close.idxmax(axis=0, skipna=True)
    context.history_min = context.history_close.idxmin(axis=0, skipna=True)
    minctr = 0
    maxctr = 0
    stockctr = len(context.history_close.columns)
    #find number of hi's and Lows
    for stock in context.history_max.index:
        datemax = context.history_max[stock]
        if str(type(datemax))=="<class 'pandas.tslib.Timestamp'>" and datemax.date() == get_datetime().date(): maxctr += 1
    for stock in context.history_min.index:
         datemin = context.history_min[stock] 
         if str(type(datemin))=="<class 'pandas.tslib.Timestamp'>" and datemin.date() == get_datetime().date(): minctr += 1          
    hi = float(maxctr)/stockctr
    lo = float(minctr)/stockctr
    ratio = (float(maxctr - minctr))/stockctr
    context.hilo_index.append(ratio)
    
    #calc zscore of HiLo  ratio
    z_ratio= stats.zscore(context.hilo_index[-context.smoothing:], axis=0, ddof=1)[-1]
    ma_ratio = np.mean(context.hilo_index[-context.smoothing:])
    context.hilo_MAindex.append(ma_ratio)
    context.current_hilo = ratio
    
    #calc slope of MA HiLo index, 7 day
    if len(context.hilo_MAindex)> 7:
        x = list(xrange(7))
        slope, intercept, r_value, p_value, std_err = stats.linregress(x,context.hilo_MAindex[-7:])
    else:
        slope = 0.0
        p_value = 0
        
    context.slope_index.append(slope)
    
    if len(context.slope_index) > 7:
        slope_ma = np.mean(context.slope_index[-7:])
    else:
        slope_ma = 0.0
   

    if mavg_long > current_price: 
        record(mavg_long_vs_price=-1) 
    else:
        record(mavg_long_vs_price=1)
    # if context.holdtilpositive: 
    #     record(holdtilpositive= 1) 
    # else: 
    #     record(holdtilpositive=0)   
    record(zspyslope = z_spyslope)
    record(zratio_hi_lo = z_ratio)
    #record(slopespy = spyslope)    
    

    
    
    #execute old buy commands if they are there   
    if context.buytomorrow and context.sold_x_days_ago > 0 and no_spys==0:
        order_target_percent(context.spy, context.max_lvrg)
        context.buytomorrow = False
        log.info('> Executed BUY')
        return

    
   #generate new buy commands
    
    if  z_ratio > 0.8 and biasslope > 0 and not z_ratio < -2.2:
        if no_spys==0: log.info('Buying: z score of HiLo  of SPY over 22 days > 0.5 and slope of bias > 0: we are moving up')
        context.buytomorrow = True #Buy with delay of 1 or T+3 day
        return
    elif  z_spyslope > 0.05 and bias[0] == 1 and not z_ratio < -2.2:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days > 0: we are moving up')
        context.buytomorrow = True #Buy with delay of 1 or T+3 day
        return
    elif  z_spyslope < -0.9 and bias[0] == 1 and not z_ratio < -2.2:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days < -0.7: mean reverting')
        context.buytomorrow = True #Buy with delay of 1 or T+3 day      
        context.holdtilpositive = True
        return
    
    if context.holdtilpositive and (spyslope < 0 or slope_ma < 0):     #can I sell?
        #no I cannot sell (and I bough already so escape)
        log.info('-- Cannot Sell, I am waiting till things get positive')
        return
    else:
        if context.holdtilpositive:
            context.holdtilpositive =False
            log.info('Condition met, hold-to-positive is false now')
        if  z_ratio < -1.2 or (z_spyslope < 0 and not context.holdtilpositive) or mavg_long > current_price or (z_spyslope > 1.5 or z_ratio > 1.5) or (bias[0]==2  and biasslope < 0):

            if no_spys>0 and mavg_long > current_price: 
                log.info('Selling: price below Long mavg 122 days')
            elif no_spys>0 and z_spyslope > 1.5: 
                log.info('Selling: z score of the Slope of SPY over 22 days > 1.2')
            elif no_spys>0 and z_ratio > 1.5: 
                log.info('Selling: z score of the Ratio HILO of SPY over 22 days > 1.2')
            elif no_spys>0 and (z_spyslope < 0): 
                log.info('Selling: z score of the Slope of SPY over 22 days < -0.0 and not holding till positive')
            elif no_spys>0 and ( bias[0] == 2 and biasslope < 0): 
                log.info('Selling: Bias is negative and bias slope is negative')
            elif no_spys > 0:
                log.info('Selling: Unknown condition!')

            context.buytomorrow = False
            context.holdtilpositive = False
            
            if no_spys > 0:
                log.info('< Sold')
                order_target_percent(context.spy, 0)
            else:
                log.info('-- No Position')         
            #when a sell condition is discovered, always wait X days to buy:
            context.sold_x_days_ago = context.delay
    
    
     
    



def get_bias(ma,pc,context):
    ma1 = ma[-context.bias_lookback1:]    # array of moving average values
    pc1 = pc[-context.bias_lookback1:]    # array of price close values
    ma2 = ma[-context.bias_lookback2:]
    pc2 = pc[-context.bias_lookback2:]
    ma_mean1 = np.mean(ma1)    # mean of moving averages
    pc_mean1 = np.mean(pc1)    # mean of price values
    ma_mean2 = np.mean(ma2)
    pc_mean2 = np.mean(pc2)
    ma_mean  = (ma_mean1 + ma_mean2) / 2               # avg of averages
    pc_mean  = (pc_mean1 + pc_mean2) / 2
    ma_mean  = ma_mean2                                # override, back to original, changed my mind
    pc_mean  = pc_mean2
    if ma_mean > pc_mean:    # determines down bias and gets strength
        strength = ma_mean - pc_mean
        return 2, strength   # short
    elif pc_mean > ma_mean:  # determines down bias and gets strength
        strength = pc_mean - ma_mean
        return 1, strength   # long
    return 3, 0    # same or null    
    
There was a runtime error.

@Peter Bakker, some awesome improvement in the max draw-down and the initial first years of performance without a huge hit to the ending performance. I am interested in the modifications that you made and plan to go through the algo as much as possible. Great job!

@Peter, thanks a lot, brilliant idea to use market bias indicator to control maxdd. I will try to add short and do some optimization.

What about buying SH instead of going all to cash?

@Dave: In one of my own variants, I've modified each SPY buy order to also zero out SH:

order_target_percent(context.sh, 0)

And each SPY sell order to buy up SH at the same time:

order_target_percent(context.sh, 1)

I'm currently testing whether this works with real money trading via Robinhood Instant. If you're interested in getting in on the strategy, be sure to get in line for Robinhood Instant (otherwise you'll have to wait 3 days for settlement before switching funds between SPY and SH, which may impact returns).

Thanks for the advice Eric. I have Robinhood Instant. I'm new to coding Python though, but know Perl, PHP, BASH very well, so I'm hoping it won't take me long to learn.

Here's a combination of SPY/SH using one of Bakker's earlier versions (v2) of the momentum algo. It's a pure long-inverse combination which never holds cash - you're either with the market or against it.

It doesn't perform as well as a variant of v3, since there are a much larger number of conditions in which we sell SPY (like market bias), some of which don't always make sense to short.

Comment out 238 and/or 235 if you only want it to pick up SH when long mavg is on a negative trend, not during short-term downturns.

Clone Algorithm
108
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
import numpy as np
import math
from pytz import timezone
import datetime
from sqlalchemy import or_
import scipy.stats as stats
import talib as ta

def initialize(context):
    
    set_symbol_lookup_date('2016-05-01')
    
    log.info(get_environment('arena'))
    if get_environment('arena') == 'backtest':
        context.backtest_mode = True
        log.info('We are backtesting.')
    else:
        context.backtest_mode = False
    # Keeping track of the last sale that you have.
    context.last_sale = None
    
    context.stocks = ''
    context.spy = symbol('SPY')
    context.sh = symbol('SH')
    context.assets = [context.spy]
    context.history_close = None
    context.history_max  = None
    context.history_min  = None
    context.lookback = 62 
    context.smoothing = 40 #40 is standard
    context.holdtilpositive = False
    context.holdtilnegative = False
    context.buytomorrow = False
    
    context.threshold = 0.0035 #5% is standard
    context.hilo_index = []
    context.hilo_MAindex = []
    context.slope_index = []
    context.spyslope_index = []
    
    
    context.current_hilo = -10
    
    context.m = len(context.stocks)
    context.b_t = np.ones(context.m) / context.m
    context.eps = 0.75 # change epsilon here
    context.init = False
    context.previous_datetime = None
    #for making the history available in before trade start:
    schedule_function(Algo_hilo, date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_close(minutes=5))

    # schedule_function(daily, date_rule=date_rules.week_start(2),
    #                   time_rule=time_rules.market_open(minutes=30)
    # Set a trading guard that will prevent any short positions
    # from being placed. This is to insure that algorithms that
    # depend on short positions are not accidently deployed.
    set_long_only()
    
     
def handle_data(context, data):
    # Because most Robinhood accounts are cash accounts,
    # trades(and most other brokerages) settle 
    # on a T+3 settlement date. This snippet of code prevents
    # a trade from happening when you still have unsettled cash
    # by checking if total cash (settled & unsettled) matches your
    # settled cash amount.

    # [IMPORTANT] During backtests, `settled_cash` will always equal
    # `cash`. In order to simulate a similar check, please also 
    # incorporate `simulate_cash_settlement` in handle data as you will
    # see in this algorithm.
    # For live trading only:
    # if do_unsettled_funds_exist(context):
        # return

    # `cash_settlement_date` simulates a T+3 settlement date. This
    # should be used at the beginning of any handle_data or method
    # used for schedule_function that places orders. At the end of
    # of that method should be `check_last_sale`. Only for
    # backtesting purposes!
    # `check_last_sale` is what `cash_settlement_date` needs in
    # order to work properly. Only for backtesting purposes!
    
    pass

def do_unsettled_funds_exist(context):
    """
    For Robinhood users. In order to prevent you from attempting
    to trade on unsettled cash (settlement dates are T+3) from
    sale of proceeds. You can use this snippet of code which
    checks for whether or not you currently have unsettled funds
    
    To only be used for live trading!
    """
    if context.portfolio.cash != context.account.settled_cash:
        return True

def check_last_sale(context):
    """
    To be used at the end of each bar. This checks if there were
    any sales made and sets that to `context.last_sale`.
    `context.last_sale` is then used in `cash_settlement_date` to
    simulate a T+3 Cash Settlement date
    
    To only be used for backtesting!
    """
    open_orders = get_open_orders()
    most_recent_trade = []
    # If there are open orders check for the most recent sale
    if open_orders:
        for sec, order in open_orders.iteritems():
            for oo in order:
                if oo.amount < 0:
                    most_recent_trade.append(oo.created)
    if len(most_recent_trade) > 0:
        context.last_sale = max(most_recent_trade)
    
def cash_settlement_date(context):
    """
    This will simulate Robinhood's T+3 cash settlement. If the 
    most recent sale is less than 3 trading days from the current
    day, assume we have unsettled funds and exit
    
    To only be used for backtesting!
    """
    if context.last_sale and (get_datetime() - context.last_sale).days < 3:
        return True

def Algo_hilo(context, data):
    cash = context.portfolio.cash
    leverage = context.account.leverage
    context.history_close = data.history(context.assets, 'price', 202, '1d')
    #Calc spy slope
    x1 = list(xrange(22))
    spyslope, spyintercept, psyr_value, spyp_value, spystd_err = stats.linregress(x1,context.history_close[context.spy][-22:])
    context.spyslope_index.append(spyslope)
    #Calc zscore of spy slope
    z_spyslope= stats.zscore(context.spyslope_index, axis=0, ddof=1)[-1]
    accspyslope = 0
    #Calc accelleration of spy
    if len(context.spyslope_index) > 5:
        x2 = list(xrange(5))
        accspyslope, accspyintercept, accpsyr_value, accspyp_value, accspystd_err = stats.linregress(x2,context.spyslope_index[-5:])
    #Calc long and short mavg spy
    mavg_short = np.mean(context.history_close[context.spy][-22:])
    mavg_med = np.mean(context.history_close[context.spy][-122:])
    mavg_long = np.mean(context.history_close[context.spy][-200:])
    current_price = context.history_close[context.spy][-1]        
    
    #get maxes and minimums
    context.history_max = context.history_close.idxmax(axis=0, skipna=True)
    context.history_min = context.history_close.idxmin(axis=0, skipna=True)
    minctr = 0
    maxctr = 0
    stockctr = len(context.history_close.columns)
    #find number of hi's and Lows
    for stock in context.history_max.index:
        datemax = context.history_max[stock]
        if str(type(datemax))=="<class 'pandas.tslib.Timestamp'>" and datemax.date() == get_datetime().date(): maxctr += 1
    for stock in context.history_min.index:
         datemin = context.history_min[stock] 
         if str(type(datemin))=="<class 'pandas.tslib.Timestamp'>" and datemin.date() == get_datetime().date(): minctr += 1          
    hi = float(maxctr)/stockctr
    lo = float(minctr)/stockctr
    ratio = (float(maxctr - minctr))/stockctr
    context.hilo_index.append(ratio)
    #calc zscore of ratio
    z_ratio= stats.zscore(context.hilo_index[-context.smoothing:], axis=0, ddof=1)[-1]
    ma_ratio = np.mean(context.hilo_index[-context.smoothing:])
    context.hilo_MAindex.append(ma_ratio)
    context.current_hilo = ratio
    #calc slope of MA HiLo index
    if len(context.hilo_MAindex)> 7:
        x = list(xrange(7))
        slope, intercept, r_value, p_value, std_err = stats.linregress(x,context.hilo_MAindex[-7:])
    else:
        slope = 0
        p_value =0
        
    context.slope_index.append(slope)
    
    if len(context.slope_index) > 7:
        slope_ma = np.mean(context.slope_index[-7:])
    else:
        slope_ma = 0
   

    if mavg_long > current_price: 
        record(mavg_long_vs_price=-1) 
    else:
        record(mavg_long_vs_price=1)
    if context.holdtilpositive: 
        record(holdtilpositive= 1) 
    elif context.holdtilnegative:
        record(holdtilpositive= -1) 
    else:
        record(holdtilpositive=0)   
    record(zspyslope = z_spyslope)
    record(zratio_hi_lo = z_ratio)
    record(slopespy = spyslope)    
    
    if (spyslope > 0 and slope_ma > 0) and context.holdtilpositive:
        context.holdtilpositive = False
    elif spyslope < 0 and context.holdtilnegative:
        context.holdtilnegative = False
    
         
    no_spys = context.portfolio.positions[context.spy].amount
    
    if context.buytomorrow:
        order_target_percent(context.sh, 0)
        order_target_percent(context.spy, 1)
        context.buytomorrow = False
        return
    
    if  z_spyslope > 0:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days > 0: we are moving up')
        context.buytomorrow = True #Buy with delay of 1 day
        context.holdtilnegative = True
        return
    elif  z_spyslope < -.7:
        if no_spys==0: log.info('Buying: z score of the Slope of SPY over 22 days < -0.7: mean reverting')
        order_target_percent(context.sh, 0)
        order_target_percent(context.spy, 1)
        context.holdtilpositive = True
        return
    elif (z_spyslope < 0 and not context.holdtilpositive) or mavg_long > current_price or (z_spyslope > 1.0 or z_ratio > 1.5):
        if no_spys>0:
            if mavg_long > current_price: 
                log.info('Selling: price below Long mavg 122 days')
                order_target_percent(context.sh, 1)
            if z_spyslope > 1.0: 
                log.info('Selling: z score of the Slope of SPY over 22 days > 1.0')
                order_target_percent(context.sh, 1)
            if z_ratio > 1.5: 
                log.info('Selling: z score of the Ratio HILO of SPY over 22 days > 1.5')
                order_target_percent(context.sh, 1)
            if z_spyslope < 0 and not context.holdtilpositive: log.info('Selling: z score of the Slope of SPY over 22 days < -0.0 and not holding till positive')
        order_target_percent(context.spy, 0)
        return
    return

    
    
There was a runtime error.

Thanks Eric. I'll take a look.

FYI, buy TLT instead of SH have good performance while reducing the maxdd

@Peter do you have any step by step on how to do live trading with any broker . Thanks