Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Backtest results inaccurate to live trading by 1 minute - big difference

I have an algorithm running live that I have been testing. One thing that I have noticed is that the backtest results does not match the the live results. In fact, there is a pretty substantial difference. For example, one algorithm I have been testing live has a 6%+ difference in results to the backtest in just a week. After some investigation, the problem is that the backtest is off by one minute because minutely execution time is rounded up. Let me demonstrate the problem with an actual example that occurred to me:

This is a screenshot showing the difference between my live and backtest fills. Notice how the backtest filled at 9:36 AM while my real fill occurred at 9:35:13 AM. This resulted in a massive difference. In live - 9:35:13 fill at $39.50; In backtest - 9:36 fill at $37.77 - a 4.4% difference! The difference would have been closer if the backtest filled at 9:35 AM since the stock was at $39.99. This would have been only a 1% difference versus the 4.4%.

This is a significant problem because inaccurate backtests is extremely misleading and could cause significant risk of real money when trading live. So what I recommend is until Quantopian supports the second time resolution, backtest minutes should be rounded down since actual execution occurs in approximately 10 seconds.

9 responses

Interesting to hear of real results better than backtest and/or paper. Probably needed would be a chart basically showing your data of differences to decide both whether there are enough data points and whether the peak of the bell curve is high enough above the mean to make it worth looking into. And if convincing enough to be worth it, could Q's supplier of minute data shift to providing that data by 10-second ticks later technically speaking without too much trouble, and at what--if any--cost?

I may be wrong, but I believe that the quantopian backtest/paper trading is off by a minute due to the fact that the first tradeable bar is 9:31.

Say for example you have an order execution setup for open(minutes=1): Live trading would be 9:31 whereas the backtest and paper trading will be 9:32.
So you would need to have your live trading setup for minutes=1 and backtest set to open() and it would give you trades at 9:31

The question I have for Quantopian is: if the first bar is referenced does it still pull 9:30 or would it pull 9:31 for backtest/paper trading?

@Blue Apologize for the confusion with the 6%+. This was just the delta, so in actuality the real results could be better or worse - just very inaccurate.

@Tyler Wilson The issue isn't that 9:31 is the first tradeable bar. The minutely historical data in the backtester is accurate - e.g. MTZ at 9:36 AM was actually $37.77, but the backtester should've executed at 9:35 AM instead for more accurate results.

I tried shifting trades back by one minute. The problem with this is that the requirements are checked at the minute you specify and then executed at the next minute. For example, lets say your algo is the pseudocode below. Now imagine the stock is $9.80 at 9:59 AM and $10.05 at 10:00AM. If I shifted the backtest by one minute, then the we have open (minutes=29) and backtest would not puchase the stock since stock A < $10 at 9:59 AM. Meanwhile, the live algo would purchase the stock since stock A > $10 at 10:00 AM.

schedule_function(buyfunction, date_rules.every_day(), time_rules.market_open(minutes=30))

def buyfunction(context):  
     //buy if stock A > $10  
//Since stock A is $10.05 at 10:00AM, this would get purchased
schedule_function(buyfunction, date_rules.every_day(), time_rules.market_open(minutes=29))

def buyfunction(context):  
     //buy if stock A > $10  
//Since stock A is $9.80 at 9:59AM, this would not get purchased if I shifted the backtest back by one minute

Do any Quantopian staff have any feedback as to why the backtester rounds the minute up even though live executes within seconds?

Hi Sofyan,

This is a limitation of the backtester. In live trading, your orders are sent to the broker as soon as the order function is executed in the code. In backtesting, all orders are triggered at the end of each minute bar. The backtester is event-driven (each minute bar is an event), so it becomes difficult to model fills between these events. Since we only have minute pricing, we wouldn't be able to pick a fill price. Using the price from the start of the minute isn't necessarily always better. It's also tough for us to model when we think a trade would fill intra-minute.

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.

Note that a custom slippage model can be devised. For example, see:

https://www.quantopian.com/posts/trade-at-the-open-slippage-model

This model also works for minutely trading (which is the only thing supported now). Note that one can set the fill price to exactly the open price of the minute, which would correspond to no price variation between the open of the minute and the receipt and fill of the order by the simulated broker.

For stocks that don't trade every minute, I'm not sure about the backtester behavior. Presumably, if an order is submitted, it wouldn't be filled until the next historical trade is encountered, and so one would still have this built-in slippage. In other words, I think the backtester will accept orders at any time, but only fill them when historical price data are available. Is this correct?

@Grant This is perfect! Unfortunately, the class seems to no longer work and returns the error below. Would you happen to know the fix?

AttributeError: 'zipline.protocol.BarData' object has no attribute 'open_price'
... USER ALGORITHM:27, in process
orderGo to IDE
openPrice = trade_bar.open_price

I suggest having a look at the help page. A custom slippage model is still supported, and an example one is provided. If you get something to work, I suggest posting here (and perhaps to https://www.quantopian.com/posts/trade-at-the-open-slippage-model ).

A couple minor changes were necessary. I also added standard spread slippage in the model since setting another slippage model seems to overwrite the previous rather than adding onto it.

Updated Class

# Slippage model to trade at the open or at a fraction of the open - close range.  
class TradeAtTheOpenSlippageModel(slippage.SlippageModel):  
    '''Class for slippage model to allow trading at the open  
       or at a fraction of the open to close range.  
    '''  
    # Constructor, self and fraction of the open to close range to add (subtract)  
    #   from the open to model executions more optimistically  
    def __init__(self, fractionOfOpenCloseRange, spread):

        # Store the percent of open - close range to take as the execution price  
        self.fractionOfOpenCloseRange = fractionOfOpenCloseRange

        # Store bid/ask spread  
        self.spread = spread

    def process_order(self, data, order):  
        # Apply fractional slippage  
        openPrice = data.current(order.sid, 'open')  
        closePrice = data.current(order.sid, 'close')  
        ocRange = closePrice - openPrice  
        ocRange = ocRange * self.fractionOfOpenCloseRange  
        targetExecutionPrice = openPrice + ocRange  
        log.info('\nOrder:{0} open:{1} close:{2} exec:{3} side:{4}'.format(  
            order.sid, openPrice, closePrice, targetExecutionPrice, order.direction))

        # Apply spread slippage  
        targetExecutionPrice += self.spread * order.direction

        # Create the transaction using the new price we've calculated.  
        return (targetExecutionPrice, order.amount)  

Usage
set_slippage(TradeAtTheOpenSlippageModel(0.2,.05))