Back to Community
Quantified Strategy buy at open, sell on close, .. why am I getting different results in Quantopian...?

According to the study... @quantified.. the strategy based on the ticker XLP I am applying it in quantopian to check for correctness of the study.

Yesterday must have been a down day of at least 0.25%.  
If XLP opens down more than 0.1% today, go long and exit on the close.

Fairly simple rules. Here is the result from 2005 until present:

          **P/L       76.03854  
                #wins   147  
                Avg         0.34**  

source: http://www.quantifiedstrategies.com/trade-the-boring-consumer-stocks-when-they-open-down-and-yesterday-was-a-down-day/

AM I... missing something ? pls help...

13 responses

Firstly, you are not applying your rules, you just buying and selling at certain times during the day.

Secondly, you are buying and selling so much that you're paying a lot commissions, which is killing any profits.

You can disable commissions as shown below.

Clone Algorithm
10
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    context.spy = symbol('XLP')
    
    set_slippage(slippage.FixedSlippage(spread=.00))
    set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))

    # Sel an open position 1 minute aafter market open
    # BUy a new position 10 minutes after market open
    
    cT = 120
    oT = 10
    
    
    set_benchmark(sid(19659))
   
   # Monday Open - Sell SPY
    schedule_function(buySPY, 
                     date_rule = date_rules.week_start(),
                     time_rule = time_rules.market_close(minutes = oT))
    schedule_function(sellSPY, 
                     date_rule = date_rules.week_start(),
                     time_rule = time_rules.market_open(minutes = oT))
   
   
                      
   
    schedule_function(sellSPY, 
                      date_rule = date_rules.week_start(1),
                      time_rule = time_rules.market_open(minutes = oT))
   
   
    
    schedule_function(buySPY, 
                      date_rule = date_rules.week_start(1),
                      time_rule = time_rules.market_close(minutes =  oT))
                      
   
    schedule_function(sellSPY, 
                      date_rule = date_rules.week_start(2),
                      time_rule = time_rules.market_open(minutes =  oT))
                      
    
    schedule_function(buySPY, 
                     date_rule = date_rules.week_start(2),
                     time_rule = time_rules.market_close(minutes =  oT))
    
    
    
    schedule_function(sellSPY, 
                      date_rule = date_rules.week_start(3),
                      time_rule = time_rules.market_open(minutes =  oT))
                      
    
    schedule_function(buySPY, 
                     date_rule = date_rules.week_start(3),
                     time_rule = time_rules.market_close(minutes =  oT))
       
    schedule_function(sellSPY, 
                      date_rule = date_rules.week_start(4),
                      time_rule = time_rules.market_open(minutes =  oT))
                      
    
    schedule_function(buySPY, 
                     date_rule = date_rules.week_start(4),
                     time_rule = time_rules.market_close(minutes =  oT))
       
    
    
    
    
 
    
       
def buySPY(context, data):
    # dont do anything if we already have open orders
    if (get_open_orders()):
        return
    order_target_percent(context.spy, 1)
    


def sellSPY(context, data):
    # dont do anything if we already have open orders
    if (get_open_orders()):
        return
    order_target_percent(context.spy, 0)
    

    
    
# Will be called on every trade event for the securities you specify. 
#def handle_data(context, data):
    
  # pass
def handle_data(context, data):
    
    #track_orders(context, data)

    #pvr(context, data) ; return    # try uncommenting this

    record(current_cash = context.portfolio.cash)
    record(current_lvg = context.account.leverage)

   # if context.account.leverage > context.mx_lvrg:
       # context.mx_lvrg = context.account.leverage
      #  record(max_lvrg = context.mx_lvrg)
    #if context.portfolio.cash < context.cash_low:
      #  context.cash_low = context.portfolio.cash
      #  record(cash_low = context.cash_low)

    return

#def track_orders(context, data):  # Log orders created or filled.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    c.date_prv = date

    # https://www.quantopian.com/posts/track-orders
    #   modified here to log minute and cash

    #if 'orders' not in context:
    #    context.orders = {}

    to_delete = []
    for id in context.orders:
        o   = get_order(id)
        sec = o.sid
        sym = sec.symbol
        bar_dt = get_datetime().astimezone(timezone('US/Eastern'))
        minute = (bar_dt.hour * 60) + bar_dt.minute - 570  # (-570 = 9:30a)
        if o.filled:        # Filled at least some, status 1 is Filled
            trade = 'Bot' if o.amount > 0 else 'Sold'
            log.info(' {}      {} {} {} at {}   cash {}'.format(minute,
                    trade, o.filled, sym, data[sec].price, int(context.portfolio.cash)))
            to_delete.append(o.id)
        else:
            log.info(' {}         {} {} unfilled'.format(
                    minute, o.sid.symbol, o.amount))

    for sec, oo_for_sid in get_open_orders().iteritems(): # Open orders
        sym = sec.symbol
        for o in oo_for_sid: # Orders per security
            bar_dt = get_datetime().astimezone(timezone('US/Eastern'))
            minute = (bar_dt.hour * 60) + bar_dt.minute - 570  # (-570 = 9:30a)
            if o.id in to_delete:
                continue
            if o.status == 2:                 # Cancelled
                log.info('    Cancelled {} {} order'.format(
                        trade, o.amount, sym, data[sec].price))
                to_delete.append(o.id)
            elif o.id not in context.orders:  # New
                context.orders[o.id] = 1
                trade = 'Buy' if o.amount > 0 else 'Sell'
                if o.limit:        # Limit order
                    log.info('   {} {} {} now {} limit {}'.format(
                            trade, o.amount, sym, data[sec].price, o.limit))
                elif o.stop:       # Stop order
                    log.info('   {} {} {} now {} stop {}'.format(
                            trade, o.amount, sym, data[sec].price, o.stop))
                else:              # Market order
                    log.info(' {}   {} {} {} at {}   cash {}'.format(minute,
                        trade, o.amount, sym, data[sec].price, int(context.portfolio.cash)))
    for d in to_delete:
        del context.orders[d]
There was a runtime error.

it does not outperform... as shown...in there study....

What you ... posted ... is a ... completely different ... s ... trategy ... to the one ... you ... desc ... ribed ...

This algo more close to what is described in so called "paper".
It has low volatility, low beta ,very high consistency and stability (of loosing) .
Those four may bring it to front page of Quantopian open.
But.................... ...................... ........................... ...........

Clone Algorithm
5
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
# Buy Gap Down


def initialize(context):
    context.etf = symbol('XLP')
    # set_commission(commission.PerShare(cost=0))
    # et_slippage(slippage.FixedSlippage(spread=0.00))
    
    eT = 25  # 
    xT = 5
    
    schedule_function(Enter_Long, date_rules.every_day(), time_rules.market_open(minutes = eT))
    schedule_function(Exit_Long, date_rules.every_day(), time_rules.market_close(minutes = xT))

def Enter_Long(context, data):
    etf = context.etf
    if get_open_orders(): return
    
    close_s = history(3, '1d', 'close_price')[etf]
    open_s = history(3, '1d', 'open_price')[etf]
    Gap_Dn_Today = (open_s[-1]/close_s[-2])-1.0
    ROC_Yest = (close_s[-2]/close_s[-3])-1.0

    if (Gap_Dn_Today < -0.001 and ROC_Yest < -0.0025):   
        order_target_percent(etf, 1.0)

def Exit_Long(context, data):
    if get_open_orders(): return
    order_target_percent(context.etf, 0.0)

def handle_data(context, data):
    record(lvg = context.account.leverage) 
There was a runtime error.

@Simon.. its closely related.... than you think.... can you build an algo to prove your point of a better closely related to the study.... you can.. recycle my code... if needed... thanks....

@chan, pls edit your first msg and correct the title, it's buy at open, sell on close, thanks.
None of these backtests are actually doing that by the way, they always specify running some number of minutes away from open and close.
However, if you set one to try to do so (below), it barely does beat the benchmark yet would be worse with commissions.

However, I have a question aside from that:
Setting a breakpoint inside on_open(), the debugger will show a time of day relevant to your time zone.
As we know, the first minute of the trading day does not run for us to be able to enter an order then.
One would expect the minute that shows up in on_open() then to be :31.
Instead, it will be :35, :33, :34 etc. On 2005-01-31 it is :37.

The question is:
Assuming that's because XLP is thinly traded, is the backtester trying to be helpful and smart by not running on_open() until the first minute that a security in the universe is traded, and, do we always miss the first trade using schedule_function() this way ? (We can enter an order in that minute, however to trade in that minute we would have had to have entered the order in the previous minute or before)
Or will time_rules.market_open() cause the first run to be the minute before a first trade?
Assuming this is on target, can someone check online please, was the first trade of XLP on 2005-01-31 at US/Eastern 9:37 or 9:38? Thanks.

def initialize(context):  
    context.xlp = symbol('XLP')  
    set_slippage(slippage.FixedSlippage(spread=.00))  
    set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))  
    schedule_function(on_open, date_rules.every_day(), time_rules.market_open())  
    schedule_function(on_clos, date_rules.every_day(), time_rules.market_close())

def on_open(context, data):  
    if get_open_orders(): return  
    xlp = context.xlp  
    closes = history(3, '1d', 'close_price')[xlp]

    # From http://www.quantifiedstrategies.com/trade-the-boring-consumer-stocks-when-they-open-down-and-yesterday-was-a-down-day/  
    # "Yesterday must have been a down day of at least 0.25%."  
    # "If XLP opens down more than 0.1% today, go long and exit on the close."  
    if closes[-2] < closes[-3] * (100 - .25 / 100):  
        if data[xlp].price < closes[-2] * (100 - .1 / 100):  
            order_target_percent(xlp, 1.0)

def on_clos(context, data):  
    # This is a side-problem by the way, it should not wait 24 hrs to sell all, it should check again every minute  
    if get_open_orders(): return  # Live/real, if the sell doesn't go thru, the sell order would be canceled. Should re-open next morning, must do manually. I don't think anyone has been doing that, have not seen a function for it, and it didn't occur to me until now.  
    order_target_percent(context.xlp, 0)

def handle_data(context, data):  
    return  

@garyha.... I thought it was from "close to open...". since it w s replicated in Ipy thon notebook. ....as shown here...."http://nbviewer.ipython.org/urls/dl.dropboxusercontent.com/u/11352905/notebooks/trading%20with%20python%20example.ipynb " most of the returns ... come from... close to open... I don't know...if the same code... can be implemented in research.... or can we replicated it in research.... to achieve... the same results....

Gary,
This is a backtest of yours code with disabled slippage and commissions.

Clone Algorithm
8
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
# Buy Gap Down by gary

def initialize(context):  
    context.xlp = symbol('XLP')  
    set_slippage(slippage.FixedSlippage(spread=.00))  
    set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))  
    schedule_function(on_open, date_rules.every_day(), time_rules.market_open())  
    schedule_function(on_close, date_rules.every_day(), time_rules.market_close())

def on_open(context, data):  
    if get_open_orders(): return  
    xlp = context.xlp  
    closes = history(3, '1d', 'close_price')[xlp]

    # From http://www.quantifiedstrategies.com/trade-the-boring-consumer-stocks-when-they-open-down-and-yesterday-was-a-down-day/  
    # "Yesterday must have been a down day of at least 0.25%."  
    # "If XLP opens down more than 0.1% today, go long and exit on the close."  
    if closes[-2] < closes[-3] * (100 - .25 / 100):  
        if data[xlp].price < closes[-2] * (100 - .1 / 100):  
            order_target_percent(xlp, 1.0)

def on_close(context, data):  
    # This is a side-problem by the way, it should not wait till the next day to sell all, it should check again every minute  
    if get_open_orders(): return  # And live/real, if the sell doesn't go thru, the sell order would be canceled. Should re-open next morning.  
    order_target_percent(context.xlp, 0)

def handle_data(context, data):  
    return 
There was a runtime error.

And this is the same one with Quantopian default slippage and commissions.
Nether one is realistic.

Clone Algorithm
8
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

def initialize(context):  
    context.xlp = symbol('XLP')  
    # set_slippage(slippage.FixedSlippage(spread=.00))  
    # set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))  
    schedule_function(on_open, date_rules.every_day(), time_rules.market_open())  
    schedule_function(on_close, date_rules.every_day(), time_rules.market_close())

def on_open(context, data):  
    if get_open_orders(): return  
    xlp = context.xlp  
    closes = history(3, '1d', 'close_price')[xlp]

    # From http://www.quantifiedstrategies.com/trade-the-boring-consumer-stocks-when-they-open-down-and-yesterday-was-a-down-day/  
    # "Yesterday must have been a down day of at least 0.25%."  
    # "If XLP opens down more than 0.1% today, go long and exit on the close."  
    if closes[-2] < closes[-3] * (100 - .25 / 100):  
        if data[xlp].price < closes[-2] * (100 - .1 / 100):  
            order_target_percent(xlp, 1.0)

def on_close(context, data):  
    # This is a side-problem by the way, it should not wait till the next day to sell all, it should check again every minute  
    if get_open_orders(): return  # And live/real, if the sell doesn't go thru, the sell order would be canceled. Should re-open next morning.  
    order_target_percent(context.xlp, 0)

def handle_data(context, data):  
    return 
There was a runtime error.

"Nether one is realistic."

Correct! Way to nail that one.
And if you start it when the author stated, it's worse. Don't bother.

My question again (and the point) was:
Assuming this is on target, can someone check online please, was the first trade of XLP on 2005-01-31 at US/Eastern 9:37 or 9:38? Thanks

Chan's link at ipython.org says: "The rules of -0.25% / -0.1% can be further improved to achieve a Sharpe of 1.58. However, the strategy performance does not vary that much, it is very stable and performs well for a wide range of settings".

So it would seem odd that we can't replicate the good results reported by multiple other sources. Given (as Vladimir illustrated) this thing can be that widely sensitive to merely slippage and commissions, I would like to know whether that question I'm asking is relevant, because it might be harming people in live/real money, thus also hampering Quantopian's overall success the longer it sits around unnoticed/undealt with, possibly resulting in false conclusions in backtesting, steering developers down wrong paths and might be fixable.

Gary,
I found miscalculation in yours code:
This two lines

if closes[-2] < closes[-3] * (100 - .25 / 100):  
        if data[xlp].price < closes[-2] * (100 - .1 / 100):  

should be as

if closes[-2] < closes[-3] * (100 - .25 / 100)/100:  
        if data[xlp].price < closes[-2] * (100 - .1 / 100)/100:  

and it makes performance without commissions and slippage worse.

Clone Algorithm
8
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
# Buy Gap Down by gary

def initialize(context):  
    context.xlp = symbol('XLP')  
    set_slippage(slippage.FixedSlippage(spread=.00))  
    set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))  
    schedule_function(on_open, date_rules.every_day(), time_rules.market_open())  
    schedule_function(on_close, date_rules.every_day(), time_rules.market_close())

def on_open(context, data):  
    if get_open_orders(): return  
    xlp = context.xlp  
    closes = history(3, '1d', 'close_price')[xlp]

    # From http://www.quantifiedstrategies.com/trade-the-boring-consumer-stocks-when-they-open-down-and-yesterday-was-a-down-day/  
    # "Yesterday must have been a down day of at least 0.25%."  
    # "If XLP opens down more than 0.1% today, go long and exit on the close."  
    if closes[-2] < closes[-3] * (100 - .25 / 100)/100:  
        if data[xlp].price < closes[-2] * (100 - .1 / 100)/100:  
            order_target_percent(xlp, 1.0)

def on_close(context, data):  
    # This is a side-problem by the way, it should not wait till the next day to sell all, it should check again every minute  
    if get_open_orders(): return  # And live/real, if the sell doesn't go thru, the sell order would be canceled. Should re-open next morning.  
    order_target_percent(context.xlp, 0)

def handle_data(context, data):  
    return 
There was a runtime error.

with Quantopian default slippage and commissions

Clone Algorithm
8
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
# Buy Gap Down by gary

def initialize(context):  
    context.xlp = symbol('XLP')  
    # set_slippage(slippage.FixedSlippage(spread=.00))  
    # set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))  
    schedule_function(on_open, date_rules.every_day(), time_rules.market_open())  
    schedule_function(on_close, date_rules.every_day(), time_rules.market_close())

def on_open(context, data):  
    if get_open_orders(): return  
    xlp = context.xlp  
    closes = history(3, '1d', 'close_price')[xlp]

    # From http://www.quantifiedstrategies.com/trade-the-boring-consumer-stocks-when-they-open-down-and-yesterday-was-a-down-day/  
    # "Yesterday must have been a down day of at least 0.25%."  
    # "If XLP opens down more than 0.1% today, go long and exit on the close."  
    if closes[-2] < closes[-3] * (100 - .25 / 100)/100:  
        if data[xlp].price < closes[-2] * (100 - .1 / 100)/100:  
            order_target_percent(xlp, 1.0)

def on_close(context, data):  
    # This is a side-problem by the way, it should not wait till the next day to sell all, it should check again every minute  
    if get_open_orders(): return  # And live/real, if the sell doesn't go thru, the sell order would be canceled. Should re-open next morning.  
    order_target_percent(context.xlp, 0)

def handle_data(context, data):  
    return 
There was a runtime error.