Back to Community
SPY & SH, minute data

This is a continuation of the thread https://www.quantopian.com/posts/spy-and-sh-algorithm-please-review, except the algo was run on minute data. It's kinda cobbled together; I need to do some refactoring and commenting. In any case, the results appear to be valid.

The basic idea is to maintain matching long (short) and short (long) positions in SPY & SH. Rebalancing is based on a z-score difference test. In this example, trading is limited to noon only (i.e. one rebalancing per day).

Grant

Clone Algorithm
912
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold

window = 16*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.stocks:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    capital = capital_invested(context, data)
    record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    record(cash = cash)
    
    # plot positions value
    positions_value = context.portfolio.positions_value
    record(positions_value = positions_value)
       
    # trade using z-score difference indicator (set global threshold above)
    for i, stock in enumerate(context.stocks):
        
        position = context.portfolio.positions[stock].amount     # current number of shares
        # shares = cash/data[stock].price/len(context.stocks)/.5   # desired position (using margin)
        shares = cash/data[stock].price/len(context.stocks)  # desired position (no margin)
        long_short = (-1)**i
        
        if delta_z > threshold:
            order(stock,(long_short*shares)-position)
            context.new_day = False
            
        elif delta_z < -threshold:       
            order(stock,(-long_short*shares)-position)
            context.new_day = False
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for stock in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[stock].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[stock].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.
80 responses

Here's the result with margin. Seems too good to be true. Grant

Clone Algorithm
912
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold

window = 16*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.stocks:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    capital = capital_invested(context, data)
    record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    record(cash = cash)
    
    # plot positions value
    positions_value = context.portfolio.positions_value
    record(positions_value = positions_value)
       
    # trade using z-score difference indicator (set global threshold above)
    for i, stock in enumerate(context.stocks):
        
        position = context.portfolio.positions[stock].amount     # current number of shares
        shares = cash/data[stock].price/len(context.stocks)/.5   # desired position (using margin)
        # shares = cash/data[stock].price/len(context.stocks)  # desired position (no margin)
        long_short = (-1)**i
        
        if delta_z > threshold:
            order(stock,(long_short*shares)-position)
            context.new_day = False
            
        elif delta_z < -threshold:       
            order(stock,(-long_short*shares)-position)
            context.new_day = False
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for stock in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[stock].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[stock].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

I am not sure how easy it is to short these inverse/leveraged ETFs. Good avenue of research though!

EDIT: I should add, the reason the SH has a persistent downward trend is due to the geometric return penalty of rebalancing the ETF's position, which worsens linearly with the variance of the asset and the square of the leverage (if I remember correctly).

I'm curious - why did you bring dollar-volume into the equation as part of your mean reversion scores?

EDIT: also, it's so hard to tell with this IDE, but it looks like what it's doing is calculating the z-score of price*volume, using only that of the second stock (SH), and when that is higher than normal, it goes long SPY and short SH. I'm not entirely sure what you are aiming for here (I mean no disrespect!), but it looks like this strategy boils down to buying the dips (and shorting the equivalent SH pops) in what has been essentially a low-vol bull market for the last few years?

Thanks Simon,

Good point on shorting SH; I've wondered how a broker would treat it.

Regarding the dollar-volume, I started playing around with just prices, but it wasn't productive. I did some work with a cointegration test to understand what might be going on (https://www.quantopian.com/posts/augmented-dickey-fuller-adf-test-spy-and-sh-dollar-volume). I can't say I have a good feel for the fundamentals yet (and Quantopian isn't really a platform for data exploration).

I'll have a look at the code again. The algorithm should compute z-scores of the dollar-volumes of both SPY and SH. Then, it should compute the difference in the z-scores and use the difference to determine if they are "out of whack."

Grant

Agreed re: data exploration. What is the rationale for dollar-volume though, is it a mean-reverting process, and if so, does it revert via price or via volume?

I recall reading a paper which mentioned that the shorting/borrow costs for leveraged ETFs was in the neighborhood of 50%/year. I thought it was http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2211387&download=yes but it doesn't seem to be in that one.

Also:

http://falkenblog.blogspot.com/2011/10/shorting-leveraged-etf-pairs.html
http://www.math.nyu.edu/faculty/avellane/LETF_Dobi_Avellaneda_Sept2012.pdf
http://blog.quantumfading.com/2009/08/17/leveraged-etf-myths-1-shorting-both-bull-and-bear/

Lots of information out there about this strategy, in general!

Thanks Simon,

At this point, I don't have a rationale for the dollar-volume, other than a guess. My intuition is that the volume provides a weighting, so that only significant departures from the norm are used as a trading signal. But I haven't dug into the details yet.

Grant

Hello Simon,

Here's the backtest using volumes only--appears to be the dominant factor. I'll try prices next and post the result.

Grant

Clone Algorithm
912
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold

window = 15*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.stocks:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    # dv = np.multiply(p,v) # compute dollar volumes
    
    dv = v
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    capital = capital_invested(context, data)
    record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    record(cash = cash)
    
    # plot positions value
    positions_value = context.portfolio.positions_value
    record(positions_value = positions_value)
       
    # trade using z-score difference indicator (set global threshold above)
    for i, stock in enumerate(context.stocks):
        
        position = context.portfolio.positions[stock].amount     # current number of shares
        # shares = cash/data[stock].price/len(context.stocks)/.5   # desired position (using margin)
        shares = cash/data[stock].price/len(context.stocks)  # desired position (no margin)
        long_short = (-1)**i
        
        if delta_z > threshold:
            order(stock,long_short*shares-position)
            context.new_day = False
            
        elif delta_z < -threshold:       
            order(stock,-long_short*shares-position)
            context.new_day = False
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for stock in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[stock].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[stock].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Here's the result run on prices (I changed the threshold to 5 to get a decent return). --Grant

Clone Algorithm
912
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 5 # z-score difference threshold

window = 15*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.stocks:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    # dv = np.multiply(p,v) # compute dollar volumes
    
    dv = p
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    capital = capital_invested(context, data)
    record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    record(cash = cash)
    
    # plot positions value
    positions_value = context.portfolio.positions_value
    record(positions_value = positions_value)
       
    # trade using z-score difference indicator (set global threshold above)
    for i, stock in enumerate(context.stocks):
        
        position = context.portfolio.positions[stock].amount     # current number of shares
        # shares = cash/data[stock].price/len(context.stocks)/.5   # desired position (using margin)
        shares = cash/data[stock].price/len(context.stocks)  # desired position (no margin)
        long_short = (-1)**i
        
        if delta_z > threshold:
            order(stock,long_short*shares-position)
            context.new_day = False
            
        elif delta_z < -threshold:       
            order(stock,-long_short*shares-position)
            context.new_day = False
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for stock in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[stock].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[stock].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Here's a long-only version with no margin that goes in and out of SPY. --Grant

Clone Algorithm
210
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold

window = 15*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.stocks:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    capital = capital_invested(context, data)
    record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    record(cash = cash)
    
    # plot positions value
    positions_value = context.portfolio.positions_value
    record(positions_value = positions_value)
    
    positions_value += context.portfolio.cash
    current_amount = context.portfolio.positions[context.stocks[0]].amount
    
    if delta_z > threshold:
        desired_amount = positions_value/data[context.stocks[0]].price
        diff_amount = desired_amount - current_amount
        order(context.stocks[0],diff_amount)
        context.new_day = False
        
    elif delta_z < -threshold:
        desired_amount = 0
        diff_amount = desired_amount - current_amount
        order(context.stocks[0],diff_amount)
        context.new_day = False
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for stock in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[stock].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[stock].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Here's the same algorithm as immediately above, but with both long and short SPY positions allowed. --Grant

Clone Algorithm
210
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold

window = 15*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.stocks:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    capital = capital_invested(context, data)
    record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    record(cash = cash)
    
    # plot positions value
    positions_value = context.portfolio.positions_value
    record(positions_value = positions_value)
    
    positions_value += context.portfolio.cash
    current_amount = context.portfolio.positions[context.stocks[0]].amount
    
    if delta_z > threshold:
        desired_amount = positions_value/data[context.stocks[0]].price
        diff_amount = desired_amount - current_amount
        order(context.stocks[0],diff_amount)
        context.new_day = False
        
    elif delta_z < -threshold:
        # desired_amount = 0
        desired_amount = -positions_value/data[context.stocks[0]].price
        diff_amount = desired_amount - current_amount
        order(context.stocks[0],diff_amount)
        context.new_day = False
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for stock in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[stock].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[stock].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Interesting, I'll have to look more closely at this, definitely seems to be be something there...

Are you sure which stock's dollar volume you are using for the zscore? Eyeballing it looks like it's using SH. If so, then a rise in the volume of a short ETF might be a plausible indicator of market bottoms/turning points.

Thanks Simon,

I appreciate your input. I've gotta put it aside for now, but hope to get back to it shortly. My intent is to compute the SPY-SH z-score difference as an indicator, but it is possible that I botched the code. I'll have a look.

Grant

Hi Simon,

This code looks correct:

    # normalize dollar volume with z-score  
    dv_z = stats.zscore(dv, axis=0, ddof=1)  
    # compute z-score difference  
    end = dv_z.shape[0]-1  
    delta_z = dv_z[end,1] - dv_z[end,0]  

Why do you think otherwise?

Grant

I must simply not understand what you are trying to do, you're multiplying the mean of the price with the the sum of volume of each, so you end up with a matrix of P-V x stock, which you z-score for each stock. Then delta_z is supposed to be the difference between the P-V z-score of SPY vs the z-score of SH? So, if the P-V of one is an outlier but the other is not, you enter? Or is delta_z supposed to be the difference between z-scores of now vs last period for SH? (it's not clear to me what the dimensions of these axes are and I haven't had time to look into it).

The delta_z is supposed to be the current SPY-SH dollar-volume z-score difference (using the pandas rolling stats to reduce the data, prior to computing the z-score). My thinking is that the z-score difference should stay within a fairly tight range, unless something is amiss. So when the z-score difference is large, I treat it as a signal to trade. Unfortunately, the whole approach is murky at this point, since Quantopian doesn't allow free-form analysis of their proprietary data. I'll think about how we could get a more fundamental understanding of the mechanism.

Yeah, there definitely seems to be something there, I'll play around in matlab when I have some time, but swamped with work and studies at the moment.

Simon,

Here's a first attempt to get a feel for what's going on.

Grant

Clone Algorithm
9
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
import pandas as pd

window = 15*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
def handle_data(context, data):
   
    # accumulate tic data
    accumulator(context, data)
    
    if context.tic_count < window:
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    delta_z = dv_z[:,1] - dv_z[:,0]
    
    print len(delta_z)
    
    delta_z_min = np.amin(delta_z)
    delta_z_max = np.amax(delta_z)
    
    record(delta_z_min = delta_z_min, delta_z_max = delta_z_max)
       
def accumulator(context, data):
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Hello Grant,

I checked this idea in my own system and discovered a significant difference between quantopian and original (from bloomberg, yahoo) data. The difference in traded volumes significantly decreases performance of strategy. Probably quantopian uses some adjustments for data, do you know about it?

Hello Ilya,

I am unaware of adjustments, but the Quantopian folks tend to be pretty responsive, so hopefully they'll have a look at your other post and help sort it out. Kinda strange that the difference would be so large.

Grant

Hi Grant,
Sorry, I'm new here and do not have some knowledge of the codes written in Python. (I rather understand things in C++ better...)
Can you briefly explain your strategy on this SPY & SH pair trading?
What time frame is this based on? 1 min.? 5 min.? and Intraday only?

Thanks.

Hello Kyu,

The basic idea is to normalize the dollar-volumes of both SPY & SH with a z-score. The z-score difference is used as an indicator.

if delta_z > threshold:  
        desired_amount = positions_value/data[context.stocks[0]].price  
        diff_amount = desired_amount - current_amount  
        order(context.stocks[0],diff_amount)  
        context.new_day = False  

    elif delta_z  

Kyu,

Sorry, part of my post got chopped off...I'll fill you in later.

Grant

Kyu,

When the z-score difference is above a threshold, SPY is bought, and when the z-score difference is below the threshold, SPY is shorted.

Trading is limited to once per day.

Hope this helps.

Grant

Grant,
So do you actually trade SPY only? But you are using SH with a z-score as an indicator?
What time frame is this based on?
Does this include transaction costs?
Have you tried this strategy with other pairs? If so, how did it go?

Thanks.

Hello Kyu,

I can't say that I have anything spectacular here. The trading is very infrequent, and for more recent timeframes, I haven't had much luck. I have tried other pairs (e.g. SPY & BND) but with the same mixed results. If you have any insights, please post them.

Grant

Hi Grant,

I've got one question here, why would you "skip tic if any orders are open or any stocks did not trade" ? (line 49)

Qingwei

Qingwei,

That is just a sanity check. If you have an open order, you can't place another order. If a stock does not trade, you can't trade anyway.

-Huapu

http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2362971

wrt short selling strategies and borrow costs

Hello Qingwei,

Regarding your question, as Huapu said, the idea is to avoid placing new orders if submitted ones have not been filled. The check to see if actual trade data exists is a bit more subtle. Quantopian forward-fills missing bars (see the discussion https://www.quantopian.com/posts/thinly-traded-stocks-why-no-gaps), so the check ensures that only historical trade data are used; if any filling occurred, the tic is skipped.

Grant

Grant,

I still didn't completely understand the logic of your algorithm. You will buy SPY and short SH if the z-score of SH's price-volume product is larger than that of SPY by 1.5, right? As you mentioned before the dominant factor is volume and price doesn't matter much. Then your algo basically is saying that if the volume of SH has a much larger increase than that of SPY, you should buy SPY and short SH? Logically it doesn't make much sense. If it is price rather than volume, it actually make more sense logically.

Also buy SPY and short SH may double your risk. If you buy SPY and SH at the same time, it is kind of like pair trading and it should reduce your risk. But if you always buy SPY and short SH, or the other way round, aren't you basically doubling your earnings/risk? Why you didn't try to pair trade SPY and SH, but the other way round?

Hi Grant, Simon & Huapu

Thanks you guys! I've got it! I never take it into account when writing my algo.

Hello Huapu,

Thanks for your comments. You are likely correct regarding shorting SH, and I abandoned the idea awhile back (above, you'll see algorithms that are long only or long/short SPY, with no trading in SH). One approach that might be viable would be to go long in either SPY or SH, for a long-only portfolio (rather than shorting SPY, for example).

Grant

Does this work well with other ETF pairs (i.e. SPDR Sector ETFs, country ETFS, etc.)?

Hello Nihar,

It's been awhile since I played around with this, but I do recall trying other pairs. In the end, I concluded that backtesting probably isn't the best approach to gain insight. An offline statistical analysis would be nice (which I have not done). Nevertheless, you could clone the algo and try alternative securities and settings.

Grant

unfortunatly i've needed to start using multicharts because of this exact reason. quantopian doesn't provide me enough detail on what's going at minute level. Seems like this should be trivial to add to quantopian. :(

I've been playing around with variants of this algorithm and noticed that the results vary widely depending on what rebalancing point you pick throughout the day. For example, rebalancing at 10am or 3pm produces wildly different returns (both short term and long term) than rebalancing at noon. Was there some logic to the choice of 12pm ET as the rebalance trigger?

Hi Matt,

I don't recall any logic, other than noon ensures that if the market closes early, a trade can still be executed.

Grant

Hi Grant,
Are you trading live with this strategy?

Will

Nope. --Grant

Hey guys!

I tried backtesting Grant's original strategy with margin, except I used UPRO and SPXU for the etf's. This should have the effect of tripling returns, right?
I still used the volume and price data from SPY and SH, however, because they work better for the signals.

How does this look?

Clone Algorithm
112
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold

window = 16*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    context.etfs = [sid(38533),sid(38532)]
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    set_commission(commission.PerTrade(cost=1))
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.stocks:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    capital = capital_invested(context, data)
    record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    record(cash = cash)
    
    # plot positions value
    positions_value = context.portfolio.positions_value
    record(positions_value = positions_value)
       
    # trade using z-score difference indicator (set global threshold above)
    for i, etf in enumerate(context.etfs):
        
        position = context.portfolio.positions[etf].amount     # current number of shares
        # shares = cash/data[stock].price/len(context.stocks)/.5   # desired position (using margin)
        shares = cash/data[etf].price/len(context.etfs)/.5  # desired position (no margin)
        long_short = (-1)**i
        
        if delta_z > threshold:
            order(etf,(long_short*shares)-position)
            context.new_day = False
            
        elif delta_z < -threshold:       
            order(etf,(-long_short*shares)-position)
            context.new_day = False
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for etf in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[etf].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[etf].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
There was a runtime error.

I ran a clone of this on some other time-frames, and its performance was dubious. Caveat emptor.

This backtest uses all the available data for UPRO and SPXU.
The 66.8% drawdown is pretty scary.

Clone Algorithm
112
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold

window = 16*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    context.etfs = [sid(38533),sid(38532)]

    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    set_commission(commission.PerTrade(cost=1))
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.stocks:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    #capital = capital_invested(context, data)
    #record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    #record(cash = cash)
    
    # plot positions value
    #positions_value = context.portfolio.positions_value
    #record(positions_value = positions_value)
  

    # trade using z-score difference indicator (set global threshold above)
    for i, etf in enumerate(context.etfs):
        
        position = context.portfolio.positions[etf].amount     # current number of shares
        # shares = cash/data[stock].price/len(context.stocks)/.5   # desired position (using margin)
        shares = cash/data[etf].price/len(context.etfs)/.5  # desired position (no margin)
        long_short = (-1)**i
        
        if delta_z > threshold:
            order(etf,(long_short*shares)-position)
            context.new_day = False
            
        elif delta_z < -threshold:       
            order(etf,(-long_short*shares)-position)
            context.new_day = False
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for etf in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[etf].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[etf].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
There was a runtime error.

This is what I got...

Clone Algorithm
2
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold

window = 16*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    context.etfs = [sid(38533),sid(38532)]
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    set_commission(commission.PerTrade(cost=1))
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.stocks:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    capital = capital_invested(context, data)
    record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    record(cash = cash)
    
    # plot positions value
    positions_value = context.portfolio.positions_value
    record(positions_value = positions_value)
       
    # trade using z-score difference indicator (set global threshold above)
    for i, etf in enumerate(context.etfs):
        
        position = context.portfolio.positions[etf].amount     # current number of shares
        # shares = cash/data[stock].price/len(context.stocks)/.5   # desired position (using margin)
        shares = cash/data[etf].price/len(context.etfs)/.5  # desired position (no margin)
        long_short = (-1)**i
        
        if delta_z > threshold:
            order(etf,(long_short*shares)-position)
            context.new_day = False
            
        elif delta_z < -threshold:       
            order(etf,(-long_short*shares)-position)
            context.new_day = False
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for etf in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[etf].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[etf].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 15 and loc_dt.minute == 45:
        return True
    else:
        return False
There was a runtime error.

Your backtest is different because you changed the time of executions to 3:45 instead of noon. I also get these terrible results when executing at that time. Noon appears to work best, though I have not thoroughly optimized it.

Now that is interesting.

I'm not sure exactly what influence it would have, but the volumes stocks trade is quite different through the day (even ignoring the first 5 minutes of the trading day).

EDIT: I got it wrong - most stocks have the least volume midday. SPY has least volume from 12:45 to 13:15

Has somebody traded this system in live trading successfully?

Hello Javier,

I have not traded this live, and I haven't heard of anyone else doing so.

Grant

Interactive Brokers lists 750,000 shares of SH are available to short, any security can be looked up on their list here:

https://www.interactivebrokers.com/en/index.php?f=shortableStocks&p=shortable

[Edited] Could be helpful for someone to cover how any shorting limits could be addressed in code? At some point in buying them back, do margin limits apply?

I am not sure I follow, what are you talking about? To short shares, you need to have shortable shares from the broker, you need to abide by the current uptick rules (not even sure what they are right now), pay the short interest rate and pay any dividends. To cover, you just buy them back? Is there any more to it than that?

Thats is all there is.

If you're shorting in Q, you have to monitor your own margin intraday, they don't do it for you. I also don't see a way to model margin interest yet, but IB only charges 1.56% APY, which from an engineering perspective is essentially zero, so I'd be comfortable ignoring it a model and just focus on proof of concept.

Also, Q will not tell you that you do not have sufficient initial margin available to initiate a short sale of a particular size, you have to track that on your own.

Keep in mind as well that the proceeds from the short sale show up in "cash" in the Q model, which my be reinvested, as long as maintenance margin requirements are still satisfied, which, once again, you have to monitor on your own.

Hi everyone,

Shorting is nothing more than what Simon talks about (more or less), the only drawback could be that a)there are not enought shares to short (which is not the case because we have more than 15 USD million in IB from SH) or b) big interest charged (this will depend on Fed funds rates and also is dependent on leverage, making leveraged ETFs more expensive to short, which is not the case because SH is not leveraged but inverse). Finally regarding margin requirements, if you leverage your account proportionally to the drawdown of the algo, you shouldnt have to suffer much thinking in potential margin calls.

Taking this into account, is there any reason for not trading this algo live? I can't see any at the moment (even including commissions and slippage).

Thanks.

Here would be my top reason:

We don't know why this algo is successful. It is using the volume of ETFs, which doesn't seem like it ought to have a lot of explanatory power on the face of it. So, that fact needs to be isolated. Furthermore, it's very sensitive to the time of day the trade is placed at, which again is a mystery - is it exploiting some intraday seasonality? Is there some sort of late-day reaction to volume in the past 24 hours? Is this related to the effects of leveraged ETFs rebalancing?

Without knowing why an algo works, it's impossible to know when it has stopped working, which won't inspire confidence when it's in an expected drawdown or might incite hope when it's broken for good...

Well, I think that most patterns in financial markets (specially intraday patterns in price and volume) dont obey to a fundamental reason, but just to the strategy that big players are using to make money. One example is the Gold Silo strategy (short intraday long overnight), the strategy had amusing returns for about 10 years until the author of zerohedge made the strategy public and suddenly it became unprofitable (the reason is that Deutsch Bank among others retired from the strategy when it bacame public). Is there a fundamental reason to justify GoldĀ“s higher price during the overnight session? Not at all, its just market manipulation, banks observed that lots of retail traders bought gold during the day session to sell it at the end, so banks decided to play the opposite game, move the price down intraday (when theres lots of retail volume) and make the money overnight (when there is no retail volume), this implies that banks would keep most profit from gold momentum for them.

So basically in my opinion, the fact that I dont find the fundamental logic behind the process doesnt let me down because the strategy seems to be robust. The strategy is robust simply because the out sample performance seems to be like the in sample one, therefore I believe the strategy is profitable (taking into account we have more than one year of out sample performance).

Agree?Disagree?

I have to agree with Simon that this algo would benefit from more development, including establishing some sort of working model that explains the performance. This way, one can apply the scientific method of collecting evidence and either confirming the model, modifying it, or scrapping it for a new one. In my mind, the algo results above are more of a clue that it might be worth doing a thorough study. My suggestion is that if anyone has time on your hands, give it a go and publish your results here for review.

My 2 cents:
The market price is determined by millions of humans. Some of them think the market should rise for reason X, others think it will fall for reason Y, and some don't care which way it goes so long as you can make money from it. I don't think there is anything wrong with having a trading system which works for unknown reasons so long as the risk can be capped. One day the system will stop working, but that is probably true of most systems. So rather than let your brain demand the satisfaction of a supposedly logical answer, just trade it and realise the clock is ticking (It has been posted publicly, after all). Even if you get an answer to why you think it works, there's no way of knowing for sure.

I agree with you James. I believe this system has proven enough to give it a chance, of course limiting the risk you take based on how the system performs up to date. Also, most systems before they die they show some symptoms which can give us a clue that some unusual bahaviour is going on which should trigger our alarms, at that point I would stop trading the system without waiting for it to fail completly. The problem is some systems use such a high leverage that, when one of those negative symptoms show (eg.a drawdown never seen before or an unsual increase in volatility), the account is already blown up. Managing the risk you take (eg, using margin as a function of volatility) is always crucial when trading whatever system.

When I run this backtest, the first transaction (2014-02-03) is selling 143 shares of SPXU that are not held. The market wouldn't allow this. Why does quantopian? Maybe that is an API bug. Anyway on to the problem.

In your order command, it seems as though the "position" value even when 0 causes some shares to be sellable.

order(etf,(-long_short*shares)-position)  

Am I reading that correctly?

Clone Algorithm
7
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold

window = 16*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    context.etfs = [sid(38533),sid(38532)]

    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    set_commission(commission.PerTrade(cost=1))
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.stocks:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    #capital = capital_invested(context, data)
    #record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    #record(cash = cash)
    
    # plot positions value
    #positions_value = context.portfolio.positions_value
    #record(positions_value = positions_value)
  

    # trade using z-score difference indicator (set global threshold above)
    for i, etf in enumerate(context.etfs):
        
        position = context.portfolio.positions[etf].amount     # current number of shares
        # shares = cash/data[stock].price/len(context.stocks)/.5   # desired position (using margin)
        shares = cash/data[etf].price/len(context.etfs)/.5  # desired position (no margin)
        long_short = (-1)**i
        
        if delta_z > threshold:
            order(etf,(long_short*shares)-position)
            context.new_day = False
            
        elif delta_z < -threshold:       
            order(etf,(-long_short*shares)-position)
            context.new_day = False
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for etf in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[etf].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[etf].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
There was a runtime error.

Daniel - presumably it is short-selling?

SPXU is an ultrashort etf.... would be strange to sell the ultrashort instead of buying the SPXL. Shorting leverages etfs is very expensive. if you set set_long_only() it wont short

There is no API bug. Just like in real life, you are responsible for verifying that you are able to short the shares. Checking and monitoring your own initial margin available is simple enough.

80k shares of SPXU are shown as available on IB, It's possible, but limited.

Hi Daniel,

I have hacked the algo such that is does not sell short but buys the opposite. still decent performance but the code is really a hack so caveat emperor

Clone Algorithm
78
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold
max_to_invest = .49 # use for cash accounts
#max_to_invest = .70 # use for margin accounts
#max_to_invest = 1 # use for testing to see the relative alpha and returns

window = 16*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    #context.stocks = [symbol('GLD'),symbol('DGZ')] # gold and short gold
    context.etfs = [sid(38533),sid(38532)]
    #hack
    context.etfshort = sid(38532)
    context.etflong = sid(38533)
    #context.etfs = [symbol('NUGT'),symbol('DUST')]
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    set_commission(commission.PerTrade(cost=1))
    set_long_only()
    schedule_function(summary, date_rules.every_day())
    
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.etfs:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    capital = capital_invested(context, data)
    record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    record(cash = cash)
    
    # plot positions value
    positions_value = context.portfolio.positions_value
    record(positions_value = positions_value)
       
    # trade using z-score difference indicator (set global threshold above)
    for i, etf in enumerate(context.etfs):
        
        etf_to_order = etf

        long_short = (-1)**i
        if long_short == -1 and etf == context.etfshort:
            etf_to_order = context.etflong
            etf_to_close = context.etfshort
        elif long_short == -1 and etf == context.etflong:
            etf_to_order = context.etfshort
            etf_to_close = context.etflong
        elif etf == context.etflong:
            etf_to_order = context.etflong
            etf_to_close = context.etfshort
        elif etf == context.etfshort:
            etf_to_order = context.etfshort
            etf_to_close = context.etflong
        
        #log.info(delta_z)
        
        if delta_z > threshold:
            order_target_percent(etf_to_close,0)
            summary(context, data)
            order_target_percent(etf_to_order,max_to_invest)
            summary(context, data)
            context.new_day = False
            
        elif delta_z < -threshold:  
            order_target_percent(etf_to_close,0)
            summary(context, data)
            order_target_percent(etf_to_order,max_to_invest)
            summary(context, data)
            context.new_day = False
            
            
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for etf in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[etf].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[etf].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
    
    
def summary(context, data):
    '''
        Summary processing

        https://www.quantopian.com/posts/run-summary
    '''
    if 'books' not in context:
        '''
            Preparation. Initialize one time.
        '''
        cash = context.portfolio.starting_cash
        context.books = {   # Starting cash value from GUI or live restart...
            'cash'          : cash,
            'init_cash'     : cash,
            'cash_low'      : cash,
            'shares'        : 0,
            'shares_value'  : 0,
            'count_buy'     : 0,       # Overall buy count, number of shares.
            'count_sell'    : 0,       # Overall sell count.
            'cnt_buy_evnts' : 0,       # Overall buy events count.
            'cnt_sel_evnts' : 0,
            'summary_print' : 0,
            'costs_total'   : 0,       # Commissions.
            'prep_prnt'     : '',
            'sids_seen'     : [],      # For set_universe since dynamic.
            'orders'        : {},      # Keep orders for accounting,
        }                              #   orders not completely filled yet.
        b = context.books

        # Environment   First/last dates and
        #   Arena: backtest or live.  Mode: daily or minute.
        env = get_environment('*')
        b['first_trading_date'] = str(env['start'].date())
        b['last_trading_date']  = str(env['end']  .date())
        b['last_trading_time']  = str(env['end']  .time())
        b['arena'] = env['arena']
        b['mode']  = env['data_frequency']

        if b['arena'] == 'live':
            b['arena'] = 'paper'
        elif b['arena'] != 'backtest': # ie like 'IB'
            b['arena'] = 'live'

        # Show environment at the beginning of the run
        b['prep_prnt'] = ' {}\n  {}  {} to {}  {}  {}\n'.format(
            b['arena'],
            b['mode'],
            b['first_trading_date'],
            b['last_trading_date'],
            '   $' + '%.0f' % b['cash'],
            '  First bar stocks ({}) ...'.format(len(data)),
        )

        # Show current universe once
        for sec in data:
            if isinstance(sec, basestring):
                continue   # Skip any injected fetcher string keys.
            b['prep_prnt'] += (sec.symbol + ' ')
        log.info(b['prep_prnt'])

    '''
        Prepare individual securities dictionaries
          with dynamic set_universe, fetcher, IPO's appearing etc.
    '''
    b = context.books   # For brevity.
    for sec in data:
        if isinstance(sec, basestring):
            continue   # Skip any injected fetcher string keys.
        sym = sec.symbol
        if sym in b:
            continue
        if sec not in b['sids_seen']:
            # Scenarios with price missing ...
            price = data[sec].price if 'price' in data[sec] else 0
            b['sids_seen'].append(sec)
            b[sym] = {
                'init_price'    : price,  # Save for summary.
                'price'         : price,  # Most recent price.
                'cash_low'      : 0,      # Lowest level of cash.
                'balance'       : 0,      # For individual 'x' return.
                'shares'        : 0,
                'count_buy'     : 0,      # Individual buy number of shares.
                'count_sell'    : 0,
                'cnt_buy_evnts' : 0,      # Individual buy events count.
                'cnt_sel_evnts' : 0,
            }
    '''
        Accounting. Update the numbers, manage orders if any.
    '''
    accounting = {}  # Local, any orders ready to be counted.

    # Read open orders
    for security, oo_for_sid in get_open_orders().iteritems():
        sym = security.symbol
        for order_obj in oo_for_sid:
            # If an order not seen before, add for tracking
            if order_obj.id not in b['orders']:
                b['orders'][order_obj.id] = order_obj.filled

    # Take a look at current orders
    for id in b['orders']:
        o = get_order(id)         # Current order, might be updated.

        # If filled is not zero, account for it
        if o.filled != 0:
            accounting[id] = o    # Set to account for filled.

            # Bugbug: The only way I could make sense of things so far ...
            # If filled is not amount (shares), that's a partial fill,
            #   cancelling remainder to simplify life.
            # ToDo: Not sure of official actual fill prices.
            if o.filled != o.amount:
                cancel_order(id)  # You might want to change/remove this.

    # Do any accounting, into books{}
    for id in accounting:
        sec = accounting[id]['sid']
        sym = sec.symbol
        if sec in data and 'price' in data[sec]:    # Update if available.
            b[sym]['price'] = data[sec].price
        commission         = accounting[id]['commission']
        filled             = accounting[id]['filled']  # Number filled, sell neg.
        lkp                = b[sym]['price']           # Last known price.
        transaction        = filled * lkp
        b[sym]['shares']  += filled      # The transaction on sell is negative
        b[sym]['balance'] -= transaction #   so this line adds to balance then.
        b[sym]['balance'] -= commission
        b['costs_total']  += commission

        if filled > 0:                          # Buy
            b[sym]['cnt_buy_evnts'] += 1
            b[sym]['count_buy']     += filled
        elif filled < 0:                        # Sell
            b[sym]['cnt_sel_evnts'] += 1
            b[sym]['count_sell']    += abs(filled)

        del b['orders'][id]    # Remove from the list, accounting done

        # Keep track of lowest cash per symbol
        if b[sym]['balance'] < b[sym]['cash_low']:
            b[sym]['cash_low'] = b[sym]['balance']

        # And overall
        cash_now = context.portfolio.cash
        if cash_now < b['cash_low']:
            b['cash_low'] = cash_now

            # An alert for negative cash unless you like "leverage"
            leverage_alert = 1    # Lowest cash points reached ...
            if leverage_alert and b['cash_low'] < 0:
                log.info(str(sym).ljust(5) \
                    + ' order for ' + (('$' + '%.0f' % transaction) \
                    + ',').ljust(8) + ' cash low: ' + str(int(b['cash_low']))
                )
    '''
        Show summary if this is the last bar
    '''
    last_bar_now = 0

    if not b['summary_print']:
        if context.books['arena'] == 'live':
            # When paper/live print summary every day end of day
            last_bar_now = 1
        elif context.books['arena'] == 'backtest':
            # Flag for summary output if last bar now
            bar = get_datetime()
            if b['last_trading_date'] == str(bar.date()):
                if b['mode'] == 'daily':
                    last_bar_now = 1
                elif b['mode'] == 'minute':
                    # This is not ideal. 
                    # How can minute mode be printed only on last bar?
                    log.info('Algo time is ' + str(bar.time()))
                    last_bar_now = 1

    if last_bar_now or b['summary_print']:
        '''
            Summary output to the logging window
        '''
        # Independent copy of context.books using dict() in case summary print
        #   is set to happen more than once in a run, due to concats below (+=)
        b    = dict(context.books)
        done = {}   # Protect against any listed twice.

        # Some overall values by adding individual values
        for sec in b['sids_seen']:
            if sec in done:
                continue

            # There's a problem with a dynamic run where a security can have 
            #   dropped out of the picture, all sold, not in current universe, 
            #   and its price is no longer accessible. Need help from Q.
            if sec in data and 'price' in data[sec]:
                b[sec.symbol]['price'] = data[sec].price
            sym    = sec.symbol
            shares = b[sym]['shares']
            b['count_buy']     += b[sym]['count_buy']
            b['count_sell']    += b[sym]['count_sell']
            b['cnt_buy_evnts'] += b[sym]['cnt_buy_evnts']
            b['cnt_sel_evnts'] += b[sym]['cnt_sel_evnts']
            b['shares']        += shares
            b['shares_value']  += (shares * b[sym]['price'])
            done[sec] = 1

        q__portfolio = str(int(context.portfolio.portfolio_value))
        cash_end     = context.portfolio.cash
        init_cash    = b['init_cash']
        cash_low     = b['cash_low']
        cash_profit  = cash_end - init_cash
        my_portfolio = cash_end + b['shares_value']
        xval         = 'x0'
        b_evts       = ('  (' + str(b['cnt_buy_evnts']) + ' trades)').rjust(17)
        s_evts       = ('  (' + str(b['cnt_sel_evnts']) + ' trades)').rjust(17)
        untouchd     = '' if int(cash_low) <= 0 else \
                           '  (' + str(int(cash_low)) + ' unused)'
        neg_cash     = '' if int(cash_low) >= 0 else '                 ' \
                           + '%.0f' % cash_low + ' max negative cash'
        drawdown     = init_cash - cash_low
        if drawdown != 0:          # Pure profit over input used.
            xval     = '   x' + '%.3f' % ((my_portfolio - drawdown) / drawdown)

        w1 = 16; w2 = 8  # Widths of columns
        outs = [
            '  QPortfolio: '.rjust(w1) + ('$' + str(q__portfolio))   .rjust(w2),
            '   Buy Count: '.rjust(w1) + str(b['count_buy'])         .rjust(w2)+b_evts,
            '  Sell Count: '.rjust(w1) + str(b['count_sell'])        .rjust(w2)+s_evts,
            '  Shares Now: '.rjust(w1) + str(b['shares'])            .rjust(w2),
            'Shares Value: '.rjust(w1) + str(int(b['shares_value'])) .rjust(w2),
            '    Cash Now: '.rjust(w1) + str(int(cash_end))          .rjust(w2),
            ' Cash Profit: '.rjust(w1) + str(int(cash_profit))       .rjust(w2),
            ' Commissions: '.rjust(w1) + str(int(b['costs_total']))  .rjust(w2),
            '   Max Spent: '.rjust(w1) + str(int(drawdown))          .rjust(w2)+neg_cash,
            'Initial Cash: '.rjust(w1) + str(int(init_cash))         .rjust(w2)+untouchd,
            '   Portfolio: '.rjust(w1) + ('$'+str(int(my_portfolio))).rjust(w2),
            '      Return: '.rjust(w1) + xval + '   Profit/Drawdown' .rjust(w2),
        ]
        out  = '_\r\n'
        for o in outs: # Pad and add `'s for replace-all in an editor after copy/paste
            out += (o + ' ' * (82 - len(o)) + '`\r\n')

        # -------------------------------
        # Individual securities detail
        # -------------------------------
        out_content_collections = []
        count_sids    = len(b['sids_seen'])
        avg_init_cash = init_cash / len(b['sids_seen'])
        sec_word      = ' security' if count_sids == 1 else ' securities'
        out_content   = '_      ' + '%.0f' % int(avg_init_cash)
        out_content  += ' average initial cash, ' + str(count_sids) + sec_word
        out_content  += ' ' * (53 - len(out_content)) + '`\r\n'
        lines_out     = 11    # Log in clumps to stay under logging limits.
        count_lines   = 0
        filter_zeros  = 0     # 0 or 1 to filter out those with no buy and no sell.
        if filter_zeros:
            count_lines += 1
            out_content += '.\r\n\tZero buy/sell filtered out `\r\n\r\n.'
        header1 = [
        '',      'Return','Buy|','By|Sl','By|Sl','Price',   'Draw','Cash','Shrs','Shrs ']
        header2 = [
        'Symbol',' Ratio','Hold','Count','Evnts','Strt|Now','Down',' Now',' Now','Value']
        contents_list = [header1, header2] # To be lines per sym as a list of lists.

        for sym in sorted(s.symbol for s in b['sids_seen']):
            if filter_zeros and not b[sym]['count_buy'] and not b[sym]['count_sell']:
                continue
            balance    = avg_init_cash + b[sym]['balance'] # balance started at zero
            cash_low   = b[sym]['cash_low'] + avg_init_cash
            init_price = b[sym]['init_price']
            shares     = b[sym]['shares']
            shares_val = shares * b[sym]['price']
            buy_hold   = 0.0
            xval       = 0
            if b[sym]['count_buy'] or b[sym]['count_sell'] and avg_init_cash:
                portf = balance + shares_val
                xval  = '%.1f' % ((portf - avg_init_cash) / avg_init_cash)
                # I'm against the use of avg_init_cash in the above line however 
                #   I wasn't able to solve the puzzle of actual spent vs profit, can you?
                #   avg_init_cash is *theoretical* for amount per share, not real spent.
                #   The individual Return Ratio is relative to each other, suspect.
                if xval == '-0.0' or xval == '0.0':  # Mainly clearing -0.0
                    xval = '0'    # -0.0 would have been something like -0.02
            if init_price:
                buy_hold = '%.1f' % ((b[sym]['price'] - init_price) / init_price)
                if buy_hold == '-0.0' or buy_hold == '0.0':
                    buy_hold = '0'
            content = [
                sym,
                ' ' + str(xval),
                buy_hold,
                str(b[sym]['count_buy']) + '|' \
                    + str(b[sym]['count_sell']),
                str(b[sym]['cnt_buy_evnts']) + '|' \
                    + str(b[sym]['cnt_sel_evnts']),
                '%.0f' % init_price + '|' + '%.0f' % b[sym]['price'],
                '%.0f' % b[sym]['cash_low'],
                '%.0f' % balance,
                '%.0f' % shares,
                int(shares_val)
            ]
            # Collect lines per sym as a list of lists
            contents_list.append(content)

        # Set widths
        col_widths = {}
        for i in range(len(contents_list[0])):
            col_widths[i + 1] = 7 # Defaults
        col_widths[1] = 6         # Symbol
        col_widths[3] = 6         # Buy|Hold
        for line_list in contents_list:
            ec = 1  # element count
            for element in line_list:
                if len(str(element)) > col_widths[ec]:
                    col_widths[ec] = len(str(element)) # Set width to largest seen.
                ec  += 1

        line_count = 0
        for line in contents_list:  # Piece together the output lines formatted.
            cc          = 1  # Column count
            line_count += 1
            out_line    = ''
            for column in line:
                if cc in [3, 4, 5, 6] or line_count in [1, 2]:
                    out_line += str(column).center(col_widths[cc] + 1)
                else:
                    out_line += str(column).rjust(col_widths[cc] + 1)
                cc += 1
            out_line    += ' `\r\n'
            out_content += out_line
            count_lines += 1

            # Backticks at the end of line are for replace-all in an editor
            #   later after copy/paste, since new lines are gone at least on Windows.
            #   Unfortunate to not be able to copy and paste results easily.

            # Decide when to tuck a group away for later and start a new group,
            #   due to logging limits, using modulus (remainder).
            if count_lines % lines_out == 0:
                out_content_collections.append(out_content)
                out_content = '_\r\n'       # Restart a group

        if count_lines % lines_out != 0:    # A few remaining lines.
            out_content_collections.append(out_content)

        log.info(out)        # The top, general overall output first.

        # Show the stored groups
        for occ in out_content_collections:
            log.info(occ)

        # Add any other content you want ---------------------------
        out_content  = '_\n' # Underscore to a new line for left alignment,
                             #   '\n' by itself would be ignored/dropped.
        # Some variables or whatever you might want to add...
        out_content += ''

        log.info(out_content)

There was a runtime error.

Peter, it looks like your algo is functioning correctly for the +1.5 z-score threshold; albeit strangely in that is executes two UPRO orders individually (i.e., in the enclosed interval, 2014-02-03 two orders are placed for 59 shares instead of one, 118 share order).

However, at the -1.5 threshold the algo sells UPRO, again in two orders, but does not buy the short, SPXU. Instead it holds cash until the next +1.5 event. Just an FYI.

Clone Algorithm
38
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold
max_to_invest = .49 # use for cash accounts
#max_to_invest = .70 # use for margin accounts
#max_to_invest = 1 # use for testing to see the relative alpha and returns

window = 16*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
   
    context.etfs = [sid(38533),sid(38532)]
    
    context.etfshort = sid(38532)
    context.etflong = sid(38533)
    
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    set_commission(commission.PerTrade(cost=1))
    set_long_only()
    schedule_function(summary, date_rules.every_day())
    
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.etfs:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

   
       
    # trade using z-score difference indicator (set global threshold above)
    for i, etf in enumerate(context.etfs):
        
        etf_to_order = etf

        long_short = (-1)**i
        if long_short == -1 and etf == context.etfshort:
            etf_to_order = context.etflong
            etf_to_close = context.etfshort
        elif long_short == -1 and etf == context.etflong:
            etf_to_order = context.etfshort
            etf_to_close = context.etflong
        elif etf == context.etflong:
            etf_to_order = context.etflong
            etf_to_close = context.etfshort
        elif etf == context.etfshort:
            etf_to_order = context.etfshort
            etf_to_close = context.etflong
        
        #log.info(delta_z)
        
        if delta_z > threshold:
            order_target_percent(etf_to_close,0)
            summary(context, data)
            order_target_percent(etf_to_order,max_to_invest)
            summary(context, data)
            context.new_day = False
            
        elif delta_z < -threshold:  
            order_target_percent(etf_to_close,0)
            summary(context, data)
            order_target_percent(etf_to_order,max_to_invest)
            summary(context, data)
            context.new_day = False
            
            
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for etf in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[etf].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[etf].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
    
    
def summary(context, data):
    '''
        Summary processing

        https://www.quantopian.com/posts/run-summary
    '''
    if 'books' not in context:
        '''
            Preparation. Initialize one time.
        '''
        cash = context.portfolio.starting_cash
        context.books = {   # Starting cash value from GUI or live restart...
            'cash'          : cash,
            'init_cash'     : cash,
            'cash_low'      : cash,
            'shares'        : 0,
            'shares_value'  : 0,
            'count_buy'     : 0,       # Overall buy count, number of shares.
            'count_sell'    : 0,       # Overall sell count.
            'cnt_buy_evnts' : 0,       # Overall buy events count.
            'cnt_sel_evnts' : 0,
            'summary_print' : 0,
            'costs_total'   : 0,       # Commissions.
            'prep_prnt'     : '',
            'sids_seen'     : [],      # For set_universe since dynamic.
            'orders'        : {},      # Keep orders for accounting,
        }                              #   orders not completely filled yet.
        b = context.books

        # Environment   First/last dates and
        #   Arena: backtest or live.  Mode: daily or minute.
        env = get_environment('*')
        b['first_trading_date'] = str(env['start'].date())
        b['last_trading_date']  = str(env['end']  .date())
        b['last_trading_time']  = str(env['end']  .time())
        b['arena'] = env['arena']
        b['mode']  = env['data_frequency']

        if b['arena'] == 'live':
            b['arena'] = 'paper'
        elif b['arena'] != 'backtest': # ie like 'IB'
            b['arena'] = 'live'

        # Show environment at the beginning of the run
        b['prep_prnt'] = ' {}\n  {}  {} to {}  {}  {}\n'.format(
            b['arena'],
            b['mode'],
            b['first_trading_date'],
            b['last_trading_date'],
            '   $' + '%.0f' % b['cash'],
            '  First bar stocks ({}) ...'.format(len(data)),
        )

        # Show current universe once
        for sec in data:
            if isinstance(sec, basestring):
                continue   # Skip any injected fetcher string keys.
            b['prep_prnt'] += (sec.symbol + ' ')
        log.info(b['prep_prnt'])

    '''
        Prepare individual securities dictionaries
          with dynamic set_universe, fetcher, IPO's appearing etc.
    '''
    b = context.books   # For brevity.
    for sec in data:
        if isinstance(sec, basestring):
            continue   # Skip any injected fetcher string keys.
        sym = sec.symbol
        if sym in b:
            continue
        if sec not in b['sids_seen']:
            # Scenarios with price missing ...
            price = data[sec].price if 'price' in data[sec] else 0
            b['sids_seen'].append(sec)
            b[sym] = {
                'init_price'    : price,  # Save for summary.
                'price'         : price,  # Most recent price.
                'cash_low'      : 0,      # Lowest level of cash.
                'balance'       : 0,      # For individual 'x' return.
                'shares'        : 0,
                'count_buy'     : 0,      # Individual buy number of shares.
                'count_sell'    : 0,
                'cnt_buy_evnts' : 0,      # Individual buy events count.
                'cnt_sel_evnts' : 0,
            }
    '''
        Accounting. Update the numbers, manage orders if any.
    '''
    accounting = {}  # Local, any orders ready to be counted.

    # Read open orders
    for security, oo_for_sid in get_open_orders().iteritems():
        sym = security.symbol
        for order_obj in oo_for_sid:
            # If an order not seen before, add for tracking
            if order_obj.id not in b['orders']:
                b['orders'][order_obj.id] = order_obj.filled

    # Take a look at current orders
    for id in b['orders']:
        o = get_order(id)         # Current order, might be updated.

        # If filled is not zero, account for it
        if o.filled != 0:
            accounting[id] = o    # Set to account for filled.

            # Bugbug: The only way I could make sense of things so far ...
            # If filled is not amount (shares), that's a partial fill,
            #   cancelling remainder to simplify life.
            # ToDo: Not sure of official actual fill prices.
            if o.filled != o.amount:
                cancel_order(id)  # You might want to change/remove this.

    # Do any accounting, into books{}
    for id in accounting:
        sec = accounting[id]['sid']
        sym = sec.symbol
        if sec in data and 'price' in data[sec]:    # Update if available.
            b[sym]['price'] = data[sec].price
        commission         = accounting[id]['commission']
        filled             = accounting[id]['filled']  # Number filled, sell neg.
        lkp                = b[sym]['price']           # Last known price.
        transaction        = filled * lkp
        b[sym]['shares']  += filled      # The transaction on sell is negative
        b[sym]['balance'] -= transaction #   so this line adds to balance then.
        b[sym]['balance'] -= commission
        b['costs_total']  += commission

        if filled > 0:                          # Buy
            b[sym]['cnt_buy_evnts'] += 1
            b[sym]['count_buy']     += filled
        elif filled < 0:                        # Sell
            b[sym]['cnt_sel_evnts'] += 1
            b[sym]['count_sell']    += abs(filled)

        del b['orders'][id]    # Remove from the list, accounting done

        # Keep track of lowest cash per symbol
        if b[sym]['balance'] < b[sym]['cash_low']:
            b[sym]['cash_low'] = b[sym]['balance']

        # And overall
        cash_now = context.portfolio.cash
        if cash_now < b['cash_low']:
            b['cash_low'] = cash_now

            # An alert for negative cash unless you like "leverage"
            leverage_alert = 1    # Lowest cash points reached ...
            if leverage_alert and b['cash_low'] < 0:
                log.info(str(sym).ljust(5) \
                    + ' order for ' + (('$' + '%.0f' % transaction) \
                    + ',').ljust(8) + ' cash low: ' + str(int(b['cash_low']))
                )
    '''
        Show summary if this is the last bar
    '''
    last_bar_now = 0

    if not b['summary_print']:
        if context.books['arena'] == 'live':
            # When paper/live print summary every day end of day
            last_bar_now = 1
        elif context.books['arena'] == 'backtest':
            # Flag for summary output if last bar now
            bar = get_datetime()
            if b['last_trading_date'] == str(bar.date()):
                if b['mode'] == 'daily':
                    last_bar_now = 1
                elif b['mode'] == 'minute':
                    # This is not ideal. 
                    # How can minute mode be printed only on last bar?
                    log.info('Algo time is ' + str(bar.time()))
                    last_bar_now = 1

    if last_bar_now or b['summary_print']:
        '''
            Summary output to the logging window
        '''
        # Independent copy of context.books using dict() in case summary print
        #   is set to happen more than once in a run, due to concats below (+=)
        b    = dict(context.books)
        done = {}   # Protect against any listed twice.

        # Some overall values by adding individual values
        for sec in b['sids_seen']:
            if sec in done:
                continue

            # There's a problem with a dynamic run where a security can have 
            #   dropped out of the picture, all sold, not in current universe, 
            #   and its price is no longer accessible. Need help from Q.
            if sec in data and 'price' in data[sec]:
                b[sec.symbol]['price'] = data[sec].price
            sym    = sec.symbol
            shares = b[sym]['shares']
            b['count_buy']     += b[sym]['count_buy']
            b['count_sell']    += b[sym]['count_sell']
            b['cnt_buy_evnts'] += b[sym]['cnt_buy_evnts']
            b['cnt_sel_evnts'] += b[sym]['cnt_sel_evnts']
            b['shares']        += shares
            b['shares_value']  += (shares * b[sym]['price'])
            done[sec] = 1

        q__portfolio = str(int(context.portfolio.portfolio_value))
        cash_end     = context.portfolio.cash
        init_cash    = b['init_cash']
        cash_low     = b['cash_low']
        cash_profit  = cash_end - init_cash
        my_portfolio = cash_end + b['shares_value']
        xval         = 'x0'
        b_evts       = ('  (' + str(b['cnt_buy_evnts']) + ' trades)').rjust(17)
        s_evts       = ('  (' + str(b['cnt_sel_evnts']) + ' trades)').rjust(17)
        untouchd     = '' if int(cash_low) <= 0 else \
                           '  (' + str(int(cash_low)) + ' unused)'
        neg_cash     = '' if int(cash_low) >= 0 else '                 ' \
                           + '%.0f' % cash_low + ' max negative cash'
        drawdown     = init_cash - cash_low
        if drawdown != 0:          # Pure profit over input used.
            xval     = '   x' + '%.3f' % ((my_portfolio - drawdown) / drawdown)

        w1 = 16; w2 = 8  # Widths of columns
        outs = [
            '  QPortfolio: '.rjust(w1) + ('$' + str(q__portfolio))   .rjust(w2),
            '   Buy Count: '.rjust(w1) + str(b['count_buy'])         .rjust(w2)+b_evts,
            '  Sell Count: '.rjust(w1) + str(b['count_sell'])        .rjust(w2)+s_evts,
            '  Shares Now: '.rjust(w1) + str(b['shares'])            .rjust(w2),
            'Shares Value: '.rjust(w1) + str(int(b['shares_value'])) .rjust(w2),
            '    Cash Now: '.rjust(w1) + str(int(cash_end))          .rjust(w2),
            ' Cash Profit: '.rjust(w1) + str(int(cash_profit))       .rjust(w2),
            ' Commissions: '.rjust(w1) + str(int(b['costs_total']))  .rjust(w2),
            '   Max Spent: '.rjust(w1) + str(int(drawdown))          .rjust(w2)+neg_cash,
            'Initial Cash: '.rjust(w1) + str(int(init_cash))         .rjust(w2)+untouchd,
            '   Portfolio: '.rjust(w1) + ('$'+str(int(my_portfolio))).rjust(w2),
            '      Return: '.rjust(w1) + xval + '   Profit/Drawdown' .rjust(w2),
        ]
        out  = '_\r\n'
        for o in outs: # Pad and add `'s for replace-all in an editor after copy/paste
            out += (o + ' ' * (82 - len(o)) + '`\r\n')

        # -------------------------------
        # Individual securities detail
        # -------------------------------
        out_content_collections = []
        count_sids    = len(b['sids_seen'])
        avg_init_cash = init_cash / len(b['sids_seen'])
        sec_word      = ' security' if count_sids == 1 else ' securities'
        out_content   = '_      ' + '%.0f' % int(avg_init_cash)
        out_content  += ' average initial cash, ' + str(count_sids) + sec_word
        out_content  += ' ' * (53 - len(out_content)) + '`\r\n'
        lines_out     = 11    # Log in clumps to stay under logging limits.
        count_lines   = 0
        filter_zeros  = 0     # 0 or 1 to filter out those with no buy and no sell.
        if filter_zeros:
            count_lines += 1
            out_content += '.\r\n\tZero buy/sell filtered out `\r\n\r\n.'
        header1 = [
        '',      'Return','Buy|','By|Sl','By|Sl','Price',   'Draw','Cash','Shrs','Shrs ']
        header2 = [
        'Symbol',' Ratio','Hold','Count','Evnts','Strt|Now','Down',' Now',' Now','Value']
        contents_list = [header1, header2] # To be lines per sym as a list of lists.

        for sym in sorted(s.symbol for s in b['sids_seen']):
            if filter_zeros and not b[sym]['count_buy'] and not b[sym]['count_sell']:
                continue
            balance    = avg_init_cash + b[sym]['balance'] # balance started at zero
            cash_low   = b[sym]['cash_low'] + avg_init_cash
            init_price = b[sym]['init_price']
            shares     = b[sym]['shares']
            shares_val = shares * b[sym]['price']
            buy_hold   = 0.0
            xval       = 0
            if b[sym]['count_buy'] or b[sym]['count_sell'] and avg_init_cash:
                portf = balance + shares_val
                xval  = '%.1f' % ((portf - avg_init_cash) / avg_init_cash)
                # I'm against the use of avg_init_cash in the above line however 
                #   I wasn't able to solve the puzzle of actual spent vs profit, can you?
                #   avg_init_cash is *theoretical* for amount per share, not real spent.
                #   The individual Return Ratio is relative to each other, suspect.
                if xval == '-0.0' or xval == '0.0':  # Mainly clearing -0.0
                    xval = '0'    # -0.0 would have been something like -0.02
            if init_price:
                buy_hold = '%.1f' % ((b[sym]['price'] - init_price) / init_price)
                if buy_hold == '-0.0' or buy_hold == '0.0':
                    buy_hold = '0'
            content = [
                sym,
                ' ' + str(xval),
                buy_hold,
                str(b[sym]['count_buy']) + '|' \
                    + str(b[sym]['count_sell']),
                str(b[sym]['cnt_buy_evnts']) + '|' \
                    + str(b[sym]['cnt_sel_evnts']),
                '%.0f' % init_price + '|' + '%.0f' % b[sym]['price'],
                '%.0f' % b[sym]['cash_low'],
                '%.0f' % balance,
                '%.0f' % shares,
                int(shares_val)
            ]
            # Collect lines per sym as a list of lists
            contents_list.append(content)

        # Set widths
        col_widths = {}
        for i in range(len(contents_list[0])):
            col_widths[i + 1] = 7 # Defaults
        col_widths[1] = 6         # Symbol
        col_widths[3] = 6         # Buy|Hold
        for line_list in contents_list:
            ec = 1  # element count
            for element in line_list:
                if len(str(element)) > col_widths[ec]:
                    col_widths[ec] = len(str(element)) # Set width to largest seen.
                ec  += 1

        line_count = 0
        for line in contents_list:  # Piece together the output lines formatted.
            cc          = 1  # Column count
            line_count += 1
            out_line    = ''
            for column in line:
                if cc in [3, 4, 5, 6] or line_count in [1, 2]:
                    out_line += str(column).center(col_widths[cc] + 1)
                else:
                    out_line += str(column).rjust(col_widths[cc] + 1)
                cc += 1
            out_line    += ' `\r\n'
            out_content += out_line
            count_lines += 1

            # Backticks at the end of line are for replace-all in an editor
            #   later after copy/paste, since new lines are gone at least on Windows.
            #   Unfortunate to not be able to copy and paste results easily.

            # Decide when to tuck a group away for later and start a new group,
            #   due to logging limits, using modulus (remainder).
            if count_lines % lines_out == 0:
                out_content_collections.append(out_content)
                out_content = '_\r\n'       # Restart a group

        if count_lines % lines_out != 0:    # A few remaining lines.
            out_content_collections.append(out_content)

        log.info(out)        # The top, general overall output first.

        # Show the stored groups
        for occ in out_content_collections:
            log.info(occ)

        # Add any other content you want ---------------------------
        out_content  = '_\n' # Underscore to a new line for left alignment,
                             #   '\n' by itself would be ignored/dropped.
        # Some variables or whatever you might want to add...
        out_content += ''

        log.info(out_content)

There was a runtime error.

Hey guys I know this in an old thread, but I had some crazy results with dank meme's original leveraged etf post when I switched to SPXS, SPXL. Can someone help me update this to the most recent API. I want to integrate this strategy into robinhood after I limit the margin account

Clone Algorithm
15
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold

window = 16*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):
    set_benchmark(sid(37514))
    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    context.etfs = [sid(37514),sid(37083)]
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ
    
    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])
    
    context.tic_count = 0
    
    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False
    
    print 'threshold = ' + str(threshold)
    set_commission(commission.PerTrade(cost=1))
def handle_data(context, data):
    
    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))
    
    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data[stock].price
            context.volumes[context.tic_count,i] = data[stock].volume
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i] = data[stock].price
            context.volumes[-1,i] = data[stock].volume  
    
    context.tic_count += 1
    
    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return
    
    # skip tic if any orders are open or any stocks did not trade  
    for stock in context.stocks:  
        if bool(get_open_orders(stock)) or data[stock].datetime < get_datetime():  
            return
        
    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime
    
    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return
    
    # if all conditions met, execute trading logic
    
    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]
    
    dv = np.multiply(p,v) # compute dollar volumes
    
    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)
    
    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]
    
    record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    capital = capital_invested(context, data)
    record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    record(cash = cash)
    
    # plot positions value
    positions_value = context.portfolio.positions_value
    record(positions_value = positions_value)
       
    # trade using z-score difference indicator (set global threshold above)
    for i, etf in enumerate(context.etfs):
        
        position = context.portfolio.positions[etf].amount     # current number of shares
        # shares = cash/data[stock].price/len(context.stocks)/.5   # desired position (using margin)
        shares = cash/data[etf].price/len(context.etfs)/.5  # desired position (no margin)
        long_short = (-1)**i
        
        if delta_z > threshold:
            order(etf,(long_short*shares)-position)
            context.new_day = False
            
        elif delta_z < -threshold:       
            order(etf,(-long_short*shares)-position)
            context.new_day = False
       
def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for etf in context.portfolio.positions:
        
        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[etf].amount
        
        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[etf].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number
                
        # add dollar amount to the 'spent' total
        capital += amount * cost_basis
    
    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
There was a runtime error.

Ryan,

The algo is pretty dated. I'd recommend starting with a fresh sheet of paper and doing some major re-factoring (I have some other stuff to do). You might take a more fundamental approach to understand what might be going on in the first place, by using the research platform to understand if there is actually any information to trade off of.

Grant

Thanks Grant, I just started writing this weekend, so I'm having hard time understanding what is the theory for the original quant. To my best understanding, it buys/sells based off minutely data with portfolio%= (price-mean/std)/1.5 for long and short positions respectively? And then normalizing the z score (price-mean/std) with dollar volume?

Ryan -

There was no "theory" behind my hack. I just decided to try something. The basic idea was that since SPY & SH are trading off of the same underlying S&P 500, they should track closely on a dollar-volume basis, and when they do not, maybe there is some useful information. If you are just trying to get a feel for the platform, it is as good a starting place as any, but there was no deep thinking or careful analysis involved in the original formulation. And as other posters point out, in practice, it may not work at all.

Grant

Thanks Greg,

This is a pretty old thread, or I wouldn't change the subject, but I think I have stumbled onto a useful indicator formula.
Check it out and see if you can find it useful, but it kind of goes like this:
Dma= day moving average
std = 50dma standard deviation
(20dma - 50dma)/(2*std ) = x

2*std is the bollinger band, by dividing it I can detect squeezes. For SPY, a large value above around 1.9 seems to detect large random drops, but has to take a while to reset and find the next drop. Buying below zero seems to fair well as it is below the 50dma. Shorting with SPXL and closing after 5 days on those drops really seemed to boost returns significantly. It did detect one instance when it wasn't supposed to, but if you set a timer with that counts days holding cash/60 that increases the buy threshold to a max 0.5 it will buy back in accordingly.

Just what my project has been for the last couple of days. Had bugs in my minutely versions and might scratch it for a new one. Just thought I'd share.

Best,
RC

ha just realized I made a z-score

I think this falls into the general category of "ETF arbitrage" (e.g. http://www.investopedia.com/articles/investing/032615/how-etf-arbitrage-works.asp?lgl=bt1tn-above-textnote ), in case you are wanting to dig into it.

Ryan -

FYI -

https://www.quantopian.com/posts/analysis-of-minute-bar-trading-volumes-of-the-etfs-spy-and-sh

There's probably some way to apply the Quantopian alphalens thingy here, as well, if you formulate the "alpha factor" appropriately:

https://www.quantopian.com/posts/alphalens-a-new-tool-for-analyzing-alpha-factors

I also don't want to change the thread topic here too much. One theme however, throughout, is why dollar-volume would have any predictive ability with ETFs? Grant found an interesting correlation.

One way to look at index ETFs is that their price is simply a convenient way to track an index. The market makers for an ETF do their best to keep the price tracking pretty close to the underlying index or basket of stocks. One could create a factor to calculate this same thing directly from the stocks (in theory). Does the the price of an ETF therefore have any 'new' informational value? No. Also, the price is completely historical. For the technical traders out there, that is all that matters and past prices DO predict the future. As for me, I'm a little skeptical.

Note therefore, that index ETFs don't follow the same rules of supply and demand as regular securities. If there was a huge demand for a security it's price would go up. Conversely, if there was little demand, the price would go down. Index ETFs, however, have their price set 'artificially' to an external index. Index ETF volume is independent of price. If more people buy into an index ETF it's price doesn't necessarily go up (and vice-versa). There is no 'feedback' to influence the volume. Volume becomes almost a true 'independent' variable. Index ETF volume IS new information.

If a lot of people think a stock will go up (or a group of stocks like the S&P500), then they would all buy in hopes of making a profit. However, that profit is put in check by the Adam Smiths supply-demand rules. As they buy the price goes up. As the price goes up their returns go down.

Now, think of the case of an index ETF. A lot of people think the S&P500 will go up and buy SPY. As they buy there is no impact on SPY, or for that matter, no impact on the S&P500. The returns on their bet the S&P500 will go up aren't impacted by this volume.

I conjecture that index ETF volume is a good predictor of market sentiment and could be a future indicator.

As an aside, volume seems to be an under-utilized metric in general. Looking over the algorithms posted on the forums here virtually none use volume to derive factors other than in conjunction with price to get dollar-volume. I randomly took some of my algorithms and globally replaced 'close_price' with 'volume' - even for a factors like returns or volatility. The results were never too dissimilar and, in many cases, actually better. Go figure.

Dan -

I also don't want to change the thread topic here too much.

As the originator of the thread, I'd say your comments are right on the mark! For example, in the limit that SPY volume goes to zero, or captures all of the market volume, there would be some information, I'd think. So, more modest changes in SPY volume (perhaps relative some other indicator) might carry information, as well.

Grant, do you know why there are so many unfilled orders eod in the backtest?

Tony,

No, not sure why there are unfilled orders. You might try turning off the slippage.

Some tools added to the original code. Partial fills are visible in the logging window.
Move trading earlier toward market open to reduce those.

Clone Algorithm
45
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
from scipy import stats
import numpy as np
from pytz import timezone
import pandas as pd

threshold = 1.5 # z-score difference threshold

window = 16*390 # window length in minutes
window_roll = 30 # window length for rolling stats

def initialize(context):

    context.stocks = [sid(8554),sid(32268)] # SPY & SH
    # context.stocks = [sid(19920),sid(32265)] # QQQ & PSQ

    context.prices = np.zeros([window,len(context.stocks)])
    context.volumes = np.zeros([window,len(context.stocks)])

    context.tic_count = 0

    context.previous_datetime = None
    context.new_day = None
    context.order_submitted = False

    print 'threshold = ' + str(threshold)

    schedule_function(record_pnl, date_rules.every_day())

    context.pnl_sids  = []  
    context.day_count = 0
        
def record_pnl(context, data):  

    def _pnl_value(sec, context, data):  
        pos = context.portfolio.positions[sec]  
        return pos.amount * (data.current(sec, 'price') - pos.cost_basis)

    context.day_count += 1

    for s in context.portfolio.positions:  
        if not data.can_trade(s): continue

        # periodically log all  
        if context.day_count % 126 == 0:  
            log.info('{} {}'.format(s.symbol, int(_pnl_value(s, context, data))))

        # add up to 5 securities for record  
        if len(context.pnl_sids) < 5 and s not in context.pnl_sids:  
            context.pnl_sids.append(s)  
        if s not in context.pnl_sids: continue     # limit to only them

        # record their profit and loss  
        who  = s.symbol  
        what = _pnl_value(s, context, data)  
        record( **{ who: what } )  
        
def handle_data(context, data):
    #pvr(context, data)
    track_orders(context, data)

    current_datetime = get_datetime().astimezone(timezone('US/Eastern'))

    if context.tic_count < window:
        for i, stock in enumerate(context.stocks):
            context.prices[context.tic_count,i] = data.current(stock, 'price')
            context.volumes[context.tic_count,i] = data.current(stock, 'volume')
    else:
        context.prices = np.roll(context.prices,-1,axis=0)
        context.volumes = np.roll(context.volumes,-1,axis=0)
        for i, stock in enumerate(context.stocks):
            context.prices[-1,i]  = data.current(stock, 'price')
            context.volumes[-1,i] = data.current(stock, 'volume')

    context.tic_count += 1

    if context.tic_count < window:
        context.previous_datetime = current_datetime
        return

    # skip tic if any orders are open or any stocks did not trade
    for stock in context.stocks:
        if bool(get_open_orders(stock)): # or (stock.end_date - get_datetime()).days < 5:
            return

    # detect new trading day
    if current_datetime.day != context.previous_datetime.day:
        context.new_day = True
        context.previous_datetime = current_datetime

    # limit trading to once per day and within specified time window
    if not context.new_day:
        return
    elif not intradingwindow_check(context):
        return

    # if all conditions met, execute trading logic

    p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
    # p = context.prices[window_roll-1:]
    v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]

    dv = np.multiply(p,v) # compute dollar volumes

    # normalize dollar volume with z-score
    dv_z = stats.zscore(dv, axis=0, ddof=1)

    # compute z-score difference
    end = dv_z.shape[0]-1
    delta_z = dv_z[end,1] - dv_z[end,0]

    #record(delta_z = delta_z)

    # plot capital invested (e.g. how much capital is invested)
    #capital = capital_invested(context, data)
    #record(capital_invested = capital)

    # plot cash
    cash = context.portfolio.cash
    #record(cash = cash)

    # plot positions value
    #positions_value = context.portfolio.positions_value
    #record(positions_value = positions_value)

    # trade using z-score difference indicator (set global threshold above)
    for i, stock in enumerate(context.stocks):

        position = context.portfolio.positions[stock].amount     # current number of shares
        # shares = cash/data.current(stock, 'price')/len(context.stocks)/.5   # desired position (using margin)
        shares = cash / data.current(stock, 'price') / len(context.stocks)  # desired position (no margin)
        long_short = (-1)**i

        if delta_z > threshold:
            order(stock,(long_short*shares)-position)
            context.new_day = False

        elif delta_z < -threshold:
            order(stock,(-long_short*shares)-position)
            context.new_day = False

    track_orders(context, data)

def capital_invested(context, data):

    # get a sum total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    # check every stock in current positions (also works with set_universe)
    for stock in context.portfolio.positions:

        # get amount of shares in current position for this stock
        amount = context.portfolio.positions[stock].amount

        # get the cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[stock].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number

        # add dollar amount to the 'spent' total
        capital += amount * cost_basis

    # return amount of capital tied up in positions
    return capital

def intradingwindow_check(context):
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    # if loc_dt.hour > 10 and loc_dt.hour < 15:
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False

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

    import time
    from datetime import datetime
    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
        c.cagr    = 0.0
        c.pvr     = {
            'pvr'        : 0,      # Profit vs Risk returns based on maximum spent
            'max_lvrg'   : 0,
            'risk_hi'    : 0,
            'days'       : 0.0,
            'date_prv'   : '',
            'date_end'   : date_end,
            'cash_low'   : cash_low,
            'cash'       : cash_low,
            'start'      : cash_low,
            'begin'      : time.time(),  # For run time
            'log_summary': 126,          # Summary every x days
            'run_str'    : '{} to {}  ${}  {} US/Eastern'.format(date_strt, date_end, int(cash_low), datetime.now(timezone('US/Eastern')).strftime("%Y-%m-%d %H:%M"))
        }
        log.info(c.pvr['run_str'])

    def _pvr_(c):
        c.cagr = ((c.portfolio.portfolio_value / c.pvr['start']) ** (1 / (c.pvr['days'] / 252.))) - 1
        ptype = 'PvR' if record_pvr else 'PvRp'
        log.info('{} {} %/day   cagr {}   Portfolio value {}'.format(ptype, '%.4f' % (c.pvr['pvr'] / c.pvr['days']), '%.1f' % c.cagr, '%.0f' % c.portfolio.portfolio_value))
        log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format('%.0f' % (c.portfolio.portfolio_value - c.pvr['start']), '%.0f' % c.pvr['risk_hi'], '%.1f' % c.pvr['pvr']))
        log.info('  QRet {} PvR {} CshLw {} MxLv {} RskHi {} Shrts {}'.format('%.2f' % q_rtrn, '%.2f' % c.pvr['pvr'], '%.0f' % c.pvr['cash_low'], '%.2f' % c.pvr['max_lvrg'], '%.0f' % c.pvr['risk_hi'], '%.0f' % shorts))

    def _minut():   # To preface each line with the minute of the day.
        dt = get_datetime().astimezone(timezone('US/Eastern'))
        minute = (dt.hour * 60) + dt.minute - 570  # (-570 = 9:31a)
        return str(minute).rjust(3)

    date = get_datetime().date()
    if c.pvr['date_prv'] != date: c.pvr['days'] += 1.0
    do_summary = 0
    if c.pvr['log_summary'] and c.pvr['days'] % c.pvr['log_summary'] == 0 and _minut() == '100':
        do_summary = 1              # Log summary every x days
    c.pvr['date_prv'] = date        # Next line for speed
    if c.pvr['cash'] == c.portfolio.cash and not do_summary and date != c.pvr['date_end']: return
    c.pvr['cash'] = c.portfolio.cash

    longs         = 0               # Longs  value
    shorts        = 0               # Shorts value
    overshorts    = 0               # Shorts value beyond longs plus cash
    new_cash_low  = 0               # To trigger logging in cash_low case
    new_risk_hi   = 0
    q_rtrn        = 100 * (c.portfolio.portfolio_value - c.pvr['start']) / c.pvr['start']
    cash          = c.portfolio.cash
    cash_dip      = int(max(0, c.pvr['start'] - cash))
    if record_pvrp and cash < 0:    # Let negative cash ding less when portfolio is up.
        cash_dip = int(max(0, c.pvr['start'] - cash * c.pvr['start'] / c.portfolio.portfolio_value))
        # Imagine: Start with 10, grows to 1000, goes negative to -10, shud not be 200% risk.

    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 c.account.leverage > c.pvr['max_lvrg']:
        c.pvr['max_lvrg'] = c.account.leverage
        if record_max_lvrg:
            record(MaxLv = c.pvr['max_lvrg'])        # Maximum intraday leverage

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

    if shorts > longs + cash: overshorts = shorts             # Shorts when too high
    if record_overshrt: record(OvrShrt = overshorts)          # Shorts beyond payable
    if record_shorting: record(Shorts  = shorts)              # Shorts value as a positve
    if record_leverage: record(Lvrg = c.account.leverage)     # Leverage
    if record_cash:     record(Cash = int(cash))              # Cash
    if record_q_return: record(QRet = q_rtrn) # Quantopian returns to compare to pvr returns curve
    if record_pnl:                            # "Profit and Loss" in dollars
        record(PnL = int(min(0, c.pvr['cash_low']) + context.portfolio.pnl) )

    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

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

    if logging:
        if new_risk_hi or new_cash_low:
            qret    = ' QRet '   + '%.1f' % q_rtrn
            lv      = ' Lv '     + '%.1f' % c.account.leverage if record_leverage else ''
            pvr     = ' PvR '    + '%.1f' % c.pvr['pvr']       if record_pvr      else ''
            pnl     = ' PnL '    + '%.0f' % c.portfolio.pnl    if record_pnl      else ''
            csh     = ' Cash '   + '%.0f' % cash               if record_cash     else ''
            shrt    = ' Shrt '   + '%.0f' % shorts             if record_shorting else ''
            ovrshrt = ' oShrt '  + '%.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(_minut(), lv, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, ovrshrt, risk, rsk_hi))
    if do_summary: _pvr_(c)
    if get_datetime() == get_environment('end'):    # Summary at end of run
        if 'pvr_summary_done' not in c: c.pvr_summary_done = 0
        if not c.pvr_summary_done:
            _pvr_(c)
            elapsed = (time.time() - c.pvr['begin']) / 60  # minutes
            log.info( '{}\nRuntime {} hr {} min'.format(c.pvr['run_str'], int(elapsed / 60), '%.1f' % (elapsed % 60)))
            c.pvr_summary_done = 1

def track_orders(context, data):  # Log orders created, filled, unfilled or canceled.
    '''      https://www.quantopian.com/posts/track-orders
    Status:
       0 - Unfilled
       1 - Filled (can be partial)
       2 - Canceled
    '''
    c = context
    log_cash = 0    # Show cash values in logging window or not.
    log_ids  = 0    # Include order id's in logging window or not.
    log_unfilled = 1

    ''' Start and stop date options ...
    To not overwhelm the logging window, start/stop dates can be entered
      either below or in initialize() if you move to there for better efficiency.
    Example:
        c.dates  = {
            'active': 0,
            'start' : ['2007-05-07', '2010-04-26'],
            'stop'  : ['2008-02-13', '2010-11-15']
        }
    '''
    if 'orders' not in c:
        c.orders = {}               # Move these to initialize() for better efficiency.
        c.dates  = {
            'active': 0,
            'start' : [],           # Start dates, option
            'stop'  : []            # Stop  dates, option
        }
    from pytz import timezone       # Python only does once, makes this portable.
                                    #   Move to top of algo for better efficiency.

    # If the dates 'start' or 'stop' lists have something in them, sets them.
    if c.dates['start'] or c.dates['stop']:
        date = str(get_datetime().date())
        if   date in c.dates['start']:    # See if there's a match to start
            c.dates['active'] = 1
        elif date in c.dates['stop']:     #   ... or to stop
            c.dates['active'] = 0
    else:
        c.dates['active'] = 1  # Set to active b/c no conditions

    if c.dates['active'] == 0:
        return                 # Skip if off

    def _minute():   # To preface each line with the minute of the day.
        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)

    def _orders(to_log):    # So all logging comes from the same line number,
        log.info(to_log)    #   for vertical alignment in the logging window.

    ordrs = c.orders.copy()    # Independent copy to allow deletes
    for id in ordrs:
        o = get_order(id)
        if o.dt == get_datetime(): continue  # Same minute as order, no chance of fill yet.
        sec  = o.sid ; sym = sec.symbol
        oid  = o.id if log_ids else ''
        cash = 'cash {}'.format(int(c.portfolio.cash)) if log_cash else ''
        prc  = '%.2f' % data.current(sec, 'price') if data.can_trade(sec) else 'unknwn'
        if o.filled:        # Filled at least some
            trade  = 'Bot' if o.amount > 0 else 'Sold'
            filled = '{}'.format(o.amount)
            filled_this = ''
            if o.filled == o.amount:    # complete
                if 0 < c.orders[o.id] < o.amount:
                    filled  = 'all/{}'.format(o.amount)
                else:
                    filled  = '{}'.format(o.amount)
                filled_this = 1
                del c.orders[o.id]
            else:
                done_prv       = c.orders[o.id]       # previously filled ttl
                filled_this    = o.filled - done_prv  # filled this time, can be 0
                c.orders[o.id] = o.filled             # save for increments math
                filled         = '{}/{}'.format(filled_this, o.amount)
            if filled_this:
                _orders(' {}      {} {} {} at {}   {} {}'.format(_minute(),
                    trade, filled, sym, prc, cash, oid))
        elif log_unfilled:
            canceled = 'canceled' if o.status == 2 else ''
            _orders(' {}         {} {} unfilled {} {}'.format(_minute(),
                    o.sid.symbol, o.amount, canceled, oid))
            if canceled: del c.orders[o.id]

    for oo_list in get_open_orders().values(): # Open orders list
        for o in oo_list:
            sec  = o.sid ; sym = sec.symbol
            oid  = o.id if log_ids else ''
            shrs = c.portfolio.positions[sec].amount if sec in c.portfolio.positions else 0
            cash = 'cash {}'.format(int(c.portfolio.cash)) if log_cash else ''
            prc  = '%.2f' % data.current(sec, 'price') if data.can_trade(sec) else 'unknwn'
            if o.status == 2:                  # Canceled
                _orders(' {}    Canceled {} {} order   {} {}'.format(_minute(),
                        trade, o.amount, sym, prc, cash, oid))
                del c.orders[o.id]
            elif o.id not in c.orders:         # New
                c.orders[o.id] = 0
                trade = 'Buy' if o.amount > 0 else 'Sell'
                if o.limit:                    # Limit order
                    _orders(' {}   {} {} {} now {} limit {}   {} {}'.format(_minute(),
                        trade, o.amount, sym, prc, o.limit, cash, oid))
                elif o.stop:                   # Stop order
                    _orders(' {}   {} {} {} now {} stop {}   {} {}'.format(_minute(),
                        trade, o.amount, sym, prc, o.stop, cash, oid))
                else:                          # Market order
                    _orders(' {}   {} {}/{} {} at {}   {} {}'.format(_minute(),
                        trade, o.amount, shrs, sym, prc, cash, oid))

There was a runtime error.

Ryan, some fixes you requested plus scheduling. A start to simplify zscore normalization.

Clone Algorithm
48
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
'''
Before scheduling
2016-11-22 13:00 _pvr_:226 INFO PvRp 0.0633 %/day   cagr 1.1   Portfolio value 4634802
2016-11-22 13:00 _pvr_:227 INFO   Profited 4619802 on 3660583 activated/transacted for PvR of 126.2%
2016-11-22 13:00 _pvr_:228 INFO   QRet 30798.68 PvR 126.20 CshLw -237381 MxLv 3.17 RskHi 3660583 Shrts 3250394
2016-11-22 13:00 pvr:318 INFO 2008-12-22 to 2016-11-22  $15000  2017-02-16 07:11 US/Eastern
Runtime 0 hr 41.7 min

After scheduling and zscore changes
2016-11-22 13:00 _pvr_:164 INFO PvRp 0.0683 %/day   cagr 0.6   Portfolio value 603996
2016-11-22 13:00 _pvr_:165 INFO   Profited 588996 on 432253 activated/transacted for PvR of 136.3%
2016-11-22 13:00 _pvr_:166 INFO   QRet 3926.64 PvR 136.26 CshLw 4589 MxLv 4.28 RskHi 432253 Shrts 401828
2016-11-22 13:00 pvr:256 INFO 2008-12-22 to 2016-11-22  $15000  2017-02-16 10:17 US/Eastern
Runtime 0 hr 7.5 min
'''

def initialize(context):
    set_commission(commission.PerTrade(cost=1))
    #set_benchmark(sid(37514))
    
    #context.stocks = [sid(8554),sid(32268)] # SPY & SH
    context.stocks = [sid(37514),sid(37083)] # SPXL & SPXS

    context.window    = 16
    context.threshold = 1.5 # z-score difference threshold
    print 'threshold = ' + str(context.threshold)

    schedule_function(balance, date_rules.every_day(), time_rules.market_open(minutes=150))

    schedule_function(record_pnl, date_rules.every_day())
    context.pnl_sids  = []
    context.day_count = 0

def balance(context, data):
    if get_open_orders(): return

    context.prices  = data.history(context.stocks, 'close',  30, '1d')
    context.volumes = data.history(context.stocks, 'volume', 30, '1d')
    
    dv = context.prices * context.volumes     # dollar volume, presumably
    dv = (dv - dv.mean()) / dv.std(ddof=0)    # normalize dollar volume with z-score?
    # http://stackoverflow.com/questions/23451244/how-to-zscore-normalize-pandas-column-with-nans
    # https://www.google.com/search?q=pandas+zscore+normalization+site%3Astackoverflow.com&ie=utf-8&oe=utf-8

    delta_z = dv.iloc[-1][1] - dv.iloc[-1][0]    # nah, couldn't be, could it?
    
    cash = context.portfolio.cash
    #record(cash = cash)
    #record(delta_z = delta_z)
    #record(capital_invested = capital_invested(context, data))
    record(positions_value = context.portfolio.positions_value)

    # trade using z-score difference indicator (set threshold above)
    for i, stock in enumerate(context.stocks):
        position = context.portfolio.positions[stock].amount     # current number of shares
        
        shares = cash/data.current(stock, 'price')/len(context.stocks)/.5  # desired position (no margin)
        long_short = (-1)**i

        if delta_z > context.threshold:
            order(stock,(long_short*shares)-position)

        elif delta_z < -context.threshold:
            order(stock,(-long_short*shares)-position)

    track_orders(context, data)

def capital_invested(context, data):    # total of capital spent or borrowed for all current positions
    capital = 0.0  # initialize to zero

    for stock in context.portfolio.positions:
        # amount of shares in current position for this stock
        amount = context.portfolio.positions[stock].amount

        # cost basis of the shares (how much we spent on average per share)
        cost_basis = context.portfolio.positions[stock].cost_basis

        # check if position is a short trade (negative amount)
        amount = max(amount, -amount) # change amount to a positive number

        # add dollar amount to the 'spent' total
        capital += amount * cost_basis

    return capital    # amount of capital tied up in positions

def record_pnl(context, data):

    def _pnl_value(sec, context, data):
        pos = context.portfolio.positions[sec]
        return pos.amount * (data.current(sec, 'price') - pos.cost_basis)

    context.day_count += 1

    for s in context.portfolio.positions:
        if not data.can_trade(s): continue

        # periodically log all
        if context.day_count % 126 == 0:
            log.info('{} {}'.format(s.symbol, int(_pnl_value(s, context, data))))

        # add up to 5 securities for record
        if len(context.pnl_sids) < 5 and s not in context.pnl_sids:
            context.pnl_sids.append(s)
        if s not in context.pnl_sids: continue     # limit to only them

        # record their profit and loss
        who  = s.symbol
        what = _pnl_value(s, context, data)
        record( **{ who: what } )

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

    import time
    from datetime import datetime
    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
        c.cagr    = 0.0
        c.pvr     = {
            'pvr'        : 0,      # Profit vs Risk returns based on maximum spent
            'max_lvrg'   : 0,
            'risk_hi'    : 0,
            'days'       : 0.0,
            'date_prv'   : '',
            'date_end'   : date_end,
            'cash_low'   : cash_low,
            'cash'       : cash_low,
            'start'      : cash_low,
            'begin'      : time.time(),  # For run time
            'log_summary': 126,          # Summary every x days
            'run_str'    : '{} to {}  ${}  {} US/Eastern'.format(date_strt, date_end, int(cash_low), datetime.now(timezone('US/Eastern')).strftime("%Y-%m-%d %H:%M"))
        }
        log.info(c.pvr['run_str'])

    def _pvr_(c):
        c.cagr = ((c.portfolio.portfolio_value / c.pvr['start']) ** (1 / (c.pvr['days'] / 252.))) - 1
        ptype = 'PvR' if record_pvr else 'PvRp'
        log.info('{} {} %/day   cagr {}   Portfolio value {}'.format(ptype, '%.4f' % (c.pvr['pvr'] / c.pvr['days']), '%.1f' % c.cagr, '%.0f' % c.portfolio.portfolio_value))
        log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format('%.0f' % (c.portfolio.portfolio_value - c.pvr['start']), '%.0f' % c.pvr['risk_hi'], '%.1f' % c.pvr['pvr']))
        log.info('  QRet {} PvR {} CshLw {} MxLv {} RskHi {} Shrts {}'.format('%.2f' % q_rtrn, '%.2f' % c.pvr['pvr'], '%.0f' % c.pvr['cash_low'], '%.2f' % c.pvr['max_lvrg'], '%.0f' % c.pvr['risk_hi'], '%.0f' % shorts))

    def _minut():   # To preface each line with the minute of the day.
        dt = get_datetime().astimezone(timezone('US/Eastern'))
        minute = (dt.hour * 60) + dt.minute - 570  # (-570 = 9:31a)
        return str(minute).rjust(3)

    date = get_datetime().date()
    if c.pvr['date_prv'] != date: c.pvr['days'] += 1.0
    do_summary = 0
    if c.pvr['log_summary'] and c.pvr['days'] % c.pvr['log_summary'] == 0 and _minut() == '100':
        do_summary = 1              # Log summary every x days
    c.pvr['date_prv'] = date        # Next line for speed
    if c.pvr['cash'] == c.portfolio.cash and not do_summary and date != c.pvr['date_end']: return
    c.pvr['cash'] = c.portfolio.cash

    longs         = 0               # Longs  value
    shorts        = 0               # Shorts value
    overshorts    = 0               # Shorts value beyond longs plus cash
    new_cash_low  = 0               # To trigger logging in cash_low case
    new_risk_hi   = 0
    q_rtrn        = 100 * (c.portfolio.portfolio_value - c.pvr['start']) / c.pvr['start']
    cash          = c.portfolio.cash
    cash_dip      = int(max(0, c.pvr['start'] - cash))
    if record_pvrp and cash < 0:    # Let negative cash ding less when portfolio is up.
        cash_dip = int(max(0, c.pvr['start'] - cash * c.pvr['start'] / c.portfolio.portfolio_value))
        # Imagine: Start with 10, grows to 1000, goes negative to -10, shud not be 200% risk.

    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 c.account.leverage > c.pvr['max_lvrg']:
        c.pvr['max_lvrg'] = c.account.leverage
        if record_max_lvrg:
            record(MaxLv = c.pvr['max_lvrg'])        # Maximum intraday leverage

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

    if shorts > longs + cash: overshorts = shorts             # Shorts when too high
    if record_overshrt: record(OvrShrt = overshorts)          # Shorts beyond payable
    if record_shorting: record(Shorts  = shorts)              # Shorts value as a positve
    if record_leverage: record(Lvrg = c.account.leverage)     # Leverage
    if record_cash:     record(Cash = int(cash))              # Cash
    if record_q_return: record(QRet = q_rtrn) # Quantopian returns to compare to pvr returns curve
    if record_pnl:                            # "Profit and Loss" in dollars
        record(PnL = int(min(0, c.pvr['cash_low']) + context.portfolio.pnl) )

    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

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

    if logging:
        if new_risk_hi or new_cash_low:
            qret    = ' QRet '   + '%.1f' % q_rtrn
            lv      = ' Lv '     + '%.1f' % c.account.leverage if record_leverage else ''
            pvr     = ' PvR '    + '%.1f' % c.pvr['pvr']       if record_pvr      else ''
            pnl     = ' PnL '    + '%.0f' % c.portfolio.pnl    if record_pnl      else ''
            csh     = ' Cash '   + '%.0f' % cash               if record_cash     else ''
            shrt    = ' Shrt '   + '%.0f' % shorts             if record_shorting else ''
            ovrshrt = ' oShrt '  + '%.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(_minut(), lv, mxlv, qret, pvr, pnl, csh, csh_lw, shrt, ovrshrt, risk, rsk_hi))
    if do_summary: _pvr_(c)
    if get_datetime() == get_environment('end'):    # Summary at end of run
        if 'pvr_summary_done' not in c: c.pvr_summary_done = 0
        if not c.pvr_summary_done:
            _pvr_(c)
            elapsed = (time.time() - c.pvr['begin']) / 60  # minutes
            log.info( '{}\nRuntime {} hr {} min'.format(c.pvr['run_str'], int(elapsed / 60), '%.1f' % (elapsed % 60)))
            c.pvr_summary_done = 1

def track_orders(context, data):  # Log orders created, filled, unfilled or canceled.
    '''      https://www.quantopian.com/posts/track-orders
    Status:
       0 - Unfilled
       1 - Filled (can be partial)
       2 - Canceled
    '''
    c = context
    log_cash = 0    # Show cash values in logging window or not.
    log_ids  = 0    # Include order id's in logging window or not.
    log_unfilled = 1

    ''' Start and stop date options ...
    To not overwhelm the logging window, start/stop dates can be entered
      either below or in initialize() if you move to there for better efficiency.
    Example:
        c.dates  = {
            'active': 0,
            'start' : ['2007-05-07', '2010-04-26'],
            'stop'  : ['2008-02-13', '2010-11-15']
        }
    '''
    if 'orders' not in c:
        c.orders = {}               # Move these to initialize() for better efficiency.
        c.dates  = {
            'active': 0,
            'start' : [],           # Start dates, option
            'stop'  : []            # Stop  dates, option
        }
    from pytz import timezone       # Python only does once, makes this portable.
                                    #   Move to top of algo for better efficiency.

    # If the dates 'start' or 'stop' lists have something in them, sets them.
    if c.dates['start'] or c.dates['stop']:
        date = str(get_datetime().date())
        if   date in c.dates['start']:    # See if there's a match to start
            c.dates['active'] = 1
        elif date in c.dates['stop']:     #   ... or to stop
            c.dates['active'] = 0
    else:
        c.dates['active'] = 1  # Set to active b/c no conditions

    if c.dates['active'] == 0:
        return                 # Skip if off

    def _minute():   # To preface each line with the minute of the day.
        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)

    def _orders(to_log):    # So all logging comes from the same line number,
        log.info(to_log)    #   for vertical alignment in the logging window.

    ordrs = c.orders.copy()    # Independent copy to allow deletes
    for id in ordrs:
        o = get_order(id)
        if o.dt == get_datetime(): continue  # Same minute as order, no chance of fill yet.
        sec  = o.sid ; sym = sec.symbol
        oid  = o.id if log_ids else ''
        cash = 'cash {}'.format(int(c.portfolio.cash)) if log_cash else ''
        prc  = '%.2f' % data.current(sec, 'price') if data.can_trade(sec) else 'unknwn'
        if o.filled:        # Filled at least some
            trade  = 'Bot' if o.amount > 0 else 'Sold'
            filled = '{}'.format(o.amount)
            filled_this = ''
            if o.filled == o.amount:    # complete
                if 0 < c.orders[o.id] < o.amount:
                    filled  = 'all/{}'.format(o.amount)
                else:
                    filled  = '{}'.format(o.amount)
                filled_this = 1
                del c.orders[o.id]
            else:
                done_prv       = c.orders[o.id]       # previously filled ttl
                filled_this    = o.filled - done_prv  # filled this time, can be 0
                c.orders[o.id] = o.filled             # save for increments math
                filled         = '{}/{}'.format(filled_this, o.amount)
            if filled_this:
                _orders(' {}      {} {} {} at {}   {} {}'.format(_minute(),
                    trade, filled, sym, prc, cash, oid))
        elif log_unfilled:
            canceled = 'canceled' if o.status == 2 else ''
            _orders(' {}         {} {} unfilled {} {}'.format(_minute(),
                    o.sid.symbol, o.amount, canceled, oid))
            if canceled: del c.orders[o.id]

    for oo_list in get_open_orders().values(): # Open orders list
        for o in oo_list:
            sec  = o.sid ; sym = sec.symbol
            oid  = o.id if log_ids else ''
            shrs = c.portfolio.positions[sec].amount if sec in c.portfolio.positions else 0
            cash = 'cash {}'.format(int(c.portfolio.cash)) if log_cash else ''
            prc  = '%.2f' % data.current(sec, 'price') if data.can_trade(sec) else 'unknwn'
            if o.status == 2:                  # Canceled
                _orders(' {}    Canceled {} {} order   {} {}'.format(_minute(),
                        trade, o.amount, sym, prc, cash, oid))
                del c.orders[o.id]
            elif o.id not in c.orders:         # New
                c.orders[o.id] = 0
                trade = 'Buy' if o.amount > 0 else 'Sell'
                if o.limit:                    # Limit order
                    _orders(' {}   {} {} {} now {} limit {}   {} {}'.format(_minute(),
                        trade, o.amount, sym, prc, o.limit, cash, oid))
                elif o.stop:                   # Stop order
                    _orders(' {}   {} {} {} now {} stop {}   {} {}'.format(_minute(),
                        trade, o.amount, sym, prc, o.stop, cash, oid))
                else:                          # Market order
                    _orders(' {}   {} {}/{} {} at {}   {} {}'.format(_minute(),
                        trade, o.amount, shrs, sym, prc, cash, oid))
                        
def handle_data(context, data):
    pvr(context, data)
    track_orders(context, data)

There was a runtime error.

p = pd.rolling_mean(context.prices,window_roll)[window_roll-1:]
v = pd.rolling_sum(context.volumes,window_roll)[window_roll-1:]

are going away - how do we replace these?