Why you can’t test intraday strategies with daily data

Let’s use this strategy trading XLP ETF as an example. We want to buy XLP at the open if it falls more than -0.1% and if the previous day it lost more than -0.25%. In appearance we could test this strategy with the following simple python code:

import numpy as np
import pandas as pd
import quandl


    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.
signal =( ( -1 + xlp_daily.Open / xlp_daily.shift(1).Close < -0.001) &
          ( -1 + xlp_daily.shift(1).Close / xlp_daily.shift(2).Close < -0.0025) )




And plotting the profits curve we get the same result as the linked blog post:

What happens if we code this strategy in Quantopian?

import numpy as np

def initialize(context):

def handle_data(context, data):
    xlp = sid(19659)
    open_today = data.history(xlp,'open',1,'1d')[-1]
    if (xlp not in context.portfolio.positions) and (not np.isnan(open_today)) and (not get_open_orders(xlp)):
        price = data.current(xlp,'price')
        closes = data.history(xlp,'close',3,'1d')
        pct_yesterday = -1 + closes[-2]/closes[-3]
        pct_today = -1 + open_today/closes[-2]
        if (pct_yesterday <= -0.0025) & (pct_today < -0.001):
            order_target(xlp, 5000/price)
            log.info('buying @%s' % price)
def sell_at_close(context, data):
    for stock in context.portfolio.positions:
        order_target_percent(stock, 0.0, style=MarketOrder())
        log.info('selling @%s' % data.current(stock,'price'))

def record_daily_values(context,data):
    # The record() function allows you to create a custom time-series plot of any variable in your simulation.
    # In this example we will record and plot the market exposure and leverage of our portfolio over time. 
    record(leverage = context.account.leverage)

First we notice that even trading a small position of 5000$, the platform warns us that lots of sell orders near the close (5 min before) cannot be filled:

2005-02-18 15:34 handle_data:32 INFO buying @23.42
2005-02-18 21:55 sell_at_close:37 INFO selling @23.47
2005-02-18 22:00 WARN Your order for -213 shares of XLP failed to fill by the end of day and was canceled.
2005-02-22 21:55 sell_at_close:37 INFO selling @23.1
2005-03-10 15:31 handle_data:32 INFO buying @23.33
2005-03-10 21:55 sell_at_close:37 INFO selling @23.4
2005-03-10 22:00 WARN Your order for -214 shares of XLP has been partially filled. 145 shares were successfully sold. 69 shares were not filled by the end of day and were canceled.
2005-03-11 21:55 sell_at_close:37 INFO selling @23.26
2005-03-16 15:33 handle_data:32 INFO buying @23.04
2005-03-16 21:55 sell_at_close:37 INFO selling @23.09
2005-03-16 22:00 WARN Your order for -217 shares of XLP has been partially filled. 75 shares were successfully sold. 142 shares were not filled by the end of day and were canceled.

If we look at the cumulative returns, they look much worse than our simple backtest:

Why such different results?

  • Bad data: Yahoo Open prices have lots of errors
  • Low liquidity: the simple backtest assumes you can trade at open and close prices. Most of the time this is not the case, even with small positions.


SPY ETF: Three down days and gap up?

Backtesting this overnight mean reversion trading idea from Quantified Strategies:

  • SPY must be down 3 days in row (from close to close).
  • Entry on close on the 3rd doen day
  • Exit next day open

signal =( (spy_daily.Close < spy_daily.shift(1).Close) &
          (spy_daily.shift(1).Close < spy_daily.shift(2).Close) &
          (spy_daily.shift(2).Close < spy_daily.shift(3).Close))




# hit rate

# sharpe
np.sqrt(252) * (np.mean(returns)) / np.std(returns)

Looks like it has lost its edge in the last three years:

Backtesting S&P 500 futures (ES) long-only 1 day hold strategy

I am going to backtest the following strategy from @Dburgh in python:

First we import the basic packages: pandas and numpy for time series manipulation and quandl for retrieving data:

import numpy as np
import pandas as pd
import quandl

Then we need to obtain daily data for ES futures from Quandl:

sp500_daily = quandl.get("CHRIS/CME_ES1", 
sp500_daily.columns=['Open', 'High', 'Low', 'Close', 
                     'Change', 'Settle', 'Volume', 
                     'Open Interest']

Since the algorithm logic is pretty simple, we can backtest using a vectored approach instead of an event driven loop:


# close[0] <= close[9] && 
# low[0] <= low[1] && 
# low[3] <= high[6] && 
# volume[0] <= volume[1]
signal = ((sp500_daily.Close <= sp500_daily.shift(9).Close) & 
          (sp500_daily.Low <= sp500_daily.shift(1).Low) & 
          (sp500_daily.shift(3).Low <= sp500_daily.shift(6).High) & 
          (sp500_daily.Volume <= sp500_daily.shift(1).Volume))
# hold time 1 day        

Now we can plot the cumulative returns:


And this is what we get:

Want to know the hitrate of this strategy? Around 60%


Sharpe ratio? Near 1

np.sqrt(252) * (np.mean(returns)) / np.std(returns)