Open Up!

This is kind of a weird one.

I was mulling over the question of what happens when the market opens up, i.e. above its previous close. Is the day likely to be an up day? A down day? I got out my data and started poking around. I looked at all “open-up” days with an open at least 0.25% above the previous day’s close. I looked at only days that opened up after a previous close-to-close down day. And the reverse.

The statistics were not significant, although it appeared there was something of a shorting opportunity there. I therefore put together a backtest for shorting at the open and holding to the close, and that looked like utter garbage.

So I flipped it. Hmm, getting something interesting here. I made a few tweaks (making sure to only look at my in-sample data while doing so). I came up with an impractical* system that looks good in the out-of-sample testing as well.

Here’s the system:

  • After the close, calculate the 5-day Average True Range of SPY.
  • If it’s greater than than the value 10 days ago:
  • Multiply the current ATR(5) value by 0.4, and add that to the closing price.
  • Set your buy-stop-at-open** order for that value.
  • Sell at the close, not holding overnight.

I find it interesting that this system does ok during bull markets, and does even better – on the long side! – during bear markets.

*Why is this system impractical? It only traded 262 times. That’s only 6% of the total trading days where an order actually placed. That’s what I call a fussy little system. You would not be placing a buy-stop-on-open** order every day…only on the days where your ATR signal appeared. I don’t know how many days that would be, because I can’t be bothered to count them.

OK fine, I’ll count them. 2055 days. Roughly half of all trading days. Ten percent-ish of the time, you get a trade. I’d want that automated, thank you very much.

**Not to mention, “buy-stop on open” isn’t a real order type (that I know of). You’d have to have your buy-stop in place, and then cancel it immediately after the open. Or you’d have your robot monitor the price just as the market opens, and put a quick buy in. There would be slippage and missed buys and possibly buys when you really didn’t want to.

I present this to you because it might generate ideas of your own. The system is checking for an increase in ATR, which means volatility is increasing. Does that mean “open-up days” tend to have momentum when volatility is higher? Is this a result of slightly longer term mean-reversion during periods of higher volatility? Get your slipsticks out and figure it out for yourself!

Read also: What I Trade.


19 thoughts on “Open Up!”

  1. Just curious: what data source do you use for your SPY open prices? I find that Yahoo is not 100% reliable for Arca opens.

    1. Thanks for your comment, DS!

      I used yahoo data for this, because it was easier to import into the software I’m alpha-testing. To see if there was any difference, I imported Norgate data on SPY and used unadjusted prices as signals. I got similar, but slightly better results. Below you can see Norgate data (highlighted blue) vs yahoo data. Doesn’t seem to be an issue with this system.
      yahoo vs norgate

  2. What do you use to calculate atr?
    Options are high/low high/close low/close
    Diff libraries use diff approaches – having said that none of them seem to product a equity graph like yours when I test it

    1. I’m using wilder’s ATR. Max of ( H-C | abs( H – prevC ) | abs( prevC – L ) )

      What platform are you using to test it?

      ATR(5) of SPY at the end of 4/13/17 was 1.7061, as a reference check.

      1. I’m using Java/ta-lib but I’ve also tried with a custom function for ATR. Probably have some bug in my code – I’ll check it again.

        Not that it matters since it doesn’t trade that much but do you know if this is using dividend adjusted data from yahoo or just the regular data – I’m using dividend adjusted – wondering if that make the difference (shouldn’t since SPY only has a quarterly dividend).

        1. I’m using the unadjusted yahoo data for the trade signals. The software I use trades on the “as traded” price, but adjusts for any splits/dividends that happen during the trading period. Ie. splits or dividends don’t show up as a spurious signal.

          Are you having trouble getting the ATR to work, or the actual system?

  3. Java/ta-lib – also tried it with a custom function. Let me try it again – I probably did something wrong . Is the data you are using adjusted for dividend days? (There are not that many in spy anyway)

  4. It’s definitely the ATR function that’s causing the discrepancy.
    I think the formula listed above is off.
    Can you check your ATR(1) for 4/10 and for 4/13.

    If I run through ThinkOrSwim and calculate the ATR(5) for 4/13/17 it is also 1.7061. So ToS is in sync with your platform.

    Next thing I tried was ATR(1) – just to make sure the formula is solid since there’s some smoothing going on with longer time periods
    Here are the ToS ATR(1) vs. my calculated ATR(1). I checked with the Yahoo data – it seems even for a single day there’s something wrong on certain days.

    The recent values are in sync but the older ones are strange.
    If I manually calculate TR for 4/10 based on Yahoo I get
    .92 (H-C)/1.06(H-prevC)/.47(PrevC-L)
    The max of this is 1.06.
    ThinkOrSwim ATR(1) for 4/10 is 1.52
    1.52 is High-Low but not High-Close
    Are you using High-Low in your calculations for ATR or strictly the formula listed in your reply?

    1. I’m getting 1.53 for 4/10 and 1.98 for 4/13. Note that on 4/10, the close of 4/9 is contained within the high and low of 4/10. So the ATR for 4/10 would be the difference between high and low…the previous close doesn’t change the value any.

  5. Thanks Matt for sharing. Question for you. How would your results look if your strategy rule set were modified:

    o Just ahead of the close, calculate the 5-day Average True Range of SPY.
    o If it’s greater than than the value 10 days ago:
    o Multiply the current ATR(5) value by 0.4, and add that to the closing price.
    o Buy at EOD if at, or above, that value.
    o Sell at the close, very next TD.

    1. When you say “current ATR(5) value”, is that the previous day’s ATR or the “just ahead of close” ATR? When you say “add that to the closing price”, you must mean the previous day’s, right? Otherwise it becomes impossible. Not clear on your modification details.

  6. Very cool.

    Multiply the current ATR(5) value by 0.4, and add that to the closing price.

    Is there any reasoning behind multiplying by 0.4?


    1. Yes. An opening gap of $0.01 (for example) is not going to be a strong signal, whereas a gap that was 3x the ATR might be a great signal, but too rare to be useful. 0.4 (multiplied by the ATR) tested the best.

      1. K do I have thinking down correctly?

        The one I did was:

        1. Work out ATR5
        2. For entry signal: If today’s ADT 5 is the maximum of all previous 10 days value, enter market.

        Do you suggest I multiply 0.4 by every day ATR5? Or whats the steps I need to take to integrate the 0.4?


        1. Not quite, but close. 🙂

          Entry is at next day’s open, holding just for the day, exit at same day’s close.
          ATR is calculated after the close to determine if there’s a signal.
          ATR must be greater than ten trading days ago. It does NOT have to be the max of the 10 days, only greater than the 10th day prior.
          The day must open up from the previous day. That’s why you multiply the ATR by some factor (0.4 in this case), add it to the close of the previous day, and there’s your stop. If it doesn’t open at or above that level, you don’t enter.

          Make sense?

  7. Hi There,
    Few Q’s:
    1. Do you enter also short on the reverse conditions?
    2. Do you enter long next day only if you had a red day today?
    3. Tried to mimic this using TS, but without success..
    Can you share what am I missing?

    here is my code

    // run on SPY daily bars
    inputs: int _daysBack(10), double _factor(0.4);
    var: double _entryPrice(0);

    if AvgTrueRange(5) > AvgTrueRange(5)[_daysBack] then
    _entryPrice = c + (AvgTrueRange(5) * _factor);
    buy next bar at _entryPrice stop;


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.