Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Problem with EOD closing: Runtime error

Hi all,

I know this question has been discussed before, I was basically doing a backtest of a pair of GOOG/AMZN with EOD closing (modified clone of gld/iau), I'm getting a runtime error at the end: exchange_time not defined, here is the code:

import datetime  
import pytz  
import numpy as np  
import pandas as pd  
import statsmodels.api as sm  
from pytz import timezone  

WINDOW_LENGTH = 50 

def ols_transform(prices, sid1, sid2): # receives constantly updated dataframe  
    """Computes regression coefficient (slope)  
    via Ordinary Least Squares between two SIDs.  
    """  
    prices = prices.fillna(method='bfill')  
    p0 = prices[sid1].values  
    p1 = prices[sid2].values  
    slope = sm.OLS(p0, p1).fit().params[0]  
    return slope

def initialize(context):  
    context.goog = sid(46631)  
    context.amzn = sid(16841)  
    context.rebalance_date = None  
    context.rebalance_trigger = 20

    # maximum total exposure (longs - shorts) in $US allowed  
    context.max_notional = 1000000 #$30,000

    context.spreads = []  
    set_commission(commission.PerTrade(cost=1.00))  
    set_slippage(slippage.FixedSlippage(spread=0.00))

def handle_data(context, data):  
    price_history = history(50, '1m', 'price')  
    price_history = price_history.fillna(method='ffill')  
    # Get the current exchange time, in local timezone  
    exchange_time = get_datetime().astimezone(pytz.timezone('US/Eastern'))  
        ######################################################  
    # 1. Compute regression coefficient between GOOG and AMZN using the Ordinary Least Squares method  
    # ref: http://en.wikipedia.org/wiki/Ordinary_least_squares  
    params = ols_transform(price_history, context.goog, context.amzn)  
    context.slope = params

    ######################################################  
    # 2. Compute zscore of spread (remove mean and divide by std), require at least 20 data points before 1st trade  
    zscore = compute_zscore(context, data)  
    record(zscore=zscore)  
    if len(context.spreads)<20:  
        log.info('fewer than 20 data points to z-score')  
        return  
    ######################################################  
    # 3. Place orders (if its been the required # days since the position was initiated)  
    if context.rebalance_date == None:  
       place_orders(context, data, zscore, exchange_time)  
    elif context.rebalance_date and exchange_time > context.rebalance_date + datetime.timedelta(days=context.rebalance_trigger):  
        place_orders(context, data, zscore, exchange_time)

def compute_zscore(context, data):  
    """1. Compute the spread given slope from the OLS regression  
       2. zscore the spread.  
    """  
    spread = data[context.goog].price - (context.slope * data[context.amzn].price)  
    # Positive spread means that GOOG is priced HIGHER than it should be relative to AMZN  
    # Negative spread means that GOOG is priced LOWER than it should be relative to AMZN  
    context.spreads.append(spread)  
    zscore = (spread - np.mean(context.spreads[-WINDOW_LENGTH:])) / np.std(context.spreads[-WINDOW_LENGTH:])  
    return zscore

def place_orders(context, data, zscore, exchange_time):  
    """Buy spread if zscore is > 2, sell if zscore < -2  
    """  
    # calculate the current notional value of each position  
    notional1 = context.portfolio.positions[context.goog].amount * data[context.goog].price  
    notional2 = context.portfolio.positions[context.amzn].amount * data[context.amzn].price  
    #record(goog_pos_x10k=(0.0001)*notional1,iau_pos_x10k=(0.0001)*notional2)  


    bet_size = 500000 #allocate $10,000 per side to the trade  
    bet_shares_goog = bet_size / data[context.goog].price  
    bet_shares_amzn = bet_size / data[context.amzn].price  
    # if our notional invested is non-zero check whether the spread has narrowed to where we want to close positions:  
    if abs(notional1) + abs(notional2) != 0:  
        if zscore <= 0.5 and zscore >= -0.5:  
            close_position(context, context.goog, data)  
            close_position(context, context.amzn, data)  
            log.info('closing positions')  
        else:  
            return  
    # if our notional invested is zero, check whether the spread has widened to where we want to open positions:  
    elif abs(notional1) + abs(notional2) == 0:  
        if zscore >= 2.0:  
            # sell the spread, betting it will narrow since it is over 2 std deviations  
            # away from the average  
            order(context.goog, -1 * bet_shares_goog)  
            order(context.amzn, bet_shares_amzn)  
            log.info('z-score > 2, selling the                     e spread')  
            context.rebalance_date = exchange_time  
        elif zscore <= -2.0:  
            # buy the spread  
            order(context.goog, bet_shares_goog)  
            order(context.amzn, -1 * bet_shares_amzn)  
            log.info('z-score < 2, buying the spread')  
            context.rebalance_date = exchange_time


def close_position(context, stock, data):  
        if exchange_time.hour == 15 and exchange_time.minute == 50:  
        pos_amount = context.portfolio.positions[stock].amount  
        order(stock, -1 * pos_amount)  
3 responses

problem here:
def close_position(context, stock, data):

if exchange_time.hour == 15 and exchange_time.minute == 50:  
    pos_amount = context.portfolio.positions[stock].amount  
    order(stock, -1 * pos_amount)  

Hi Maitreyi,

I slightly updated your code and it now runs successfully (see the backtest below). I added a time check in line 68 of handle_data() to exit the position EOD.

Also, instead of calculating the number of shares you own to close the position, you can the order methods to create a target value of 0 shares. I updated line 102 to:

order_target(stock, 0)  
Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

This is great, thanks very much.

Best Regards

maitreyi