Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
limit order trigger condition and fill price with daily data

I am trying to create a trading algo that uses daily frequency data and limit style (buy) order execution. Trade signals are generated daily after the current day close and limit buy orders are placed before the next day open.

The problem that I am running into with the backtest is that limit orders will not trigger unless the limit price is set to be above the close price of the next daily bar. If the order trigger condition (limit buy price > next day close price) is met, the price at which the order fills (assuming no slippage) is the next daily bar close price.

What I expected to happen (and what happens in the real world) is that a limit buy order, placed before the open, will trigger and get filled at the open price (if the open price is below the limit price) or at the limit price (if the limit price is between the low price and the open price) or not at all (if the limit price is below the low price).

Is there a way to alter the limit buy order trigger condition to be something other than (limit buy price > next close price)? Could this problem be solved with a custom slippage model - or would that just pertain to the order fill price and not the order trigger condition?

I am familiar with using custom slippage models to alter the fill price for market style orders. The default market order trigger is the close of the next bar and the default order fill price (assuming no slippage) is the close price of the next bar. By way of example, with custom slippage, the order trigger (when the order is filled) can't be changed (as far as I know), but the fill price could be set to the close price of the current daily bar (to simulate market on close orders) or to the open price of the next daily bar (to simulate trade at the next open orders).

I know someone is going to suggest that I use minute frequency data, but in this case it is not an option. With minute frequency data, the trigger and fill price behavior that I describe above is still there but less noticeable and can generally be ignored as prices do not move much each minute.

3 responses

The zipline backtester on Quantopian does have limitations. Orders can only be placed during market hours and are placed as 'Good-Till-Day' (GTD) orders. One cannot enter an order before the market opens.

It's correct that one cannot change when an order is 'triggered'. Limit orders will trigger if the close price of the bar is better than the limit price. While one can create a custom slippage model, that only impacts the fill price and quantity. This trigger logic cannot be changed unless one has a local install of zipline. One could modify any of the zipline code in that case. It may not be ideal that the trigger attribute is based on the close price but that is the current design.

An overall comment and caution. If your algo is sensitive to the fill price being at the open or the close of a bar, I'd be skeptical. The backtester is not reality. For larger price swings averaged over many trades it can very closely approximate live trading. However, to always skew fills to a particular result may be misleading.

OK, all that said if all you are doing is calculating some quantities and associated limit prices for orders to place before market open, maybe forgo the backtester altogether. Do a simulation in a notebook. Take the pipeline output to determine the 'orders'. Fetch the open bar open-high-low-close prices perhaps with get_pricing. Then compare the 'orders' with the actual price to see what filled. At a minimum, this may give you a 'rule of thumb' difference in fill price using different fill assumptions. One could maybe use that to create a custom slippage model?

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.

Dan,

Thank you for your comments.

At this point in the development process I'm trying to get back test results to see if it is a system that is worth trading or not. The notebook suggestion for calculating orders is a good one if I can get beyond this phase.

I agree that order fill prices and slippage can be a problem but the main issue here is getting limit orders to trigger and be filled at the correct intraday price with daily data.

I'd like to find a workaround that does not involve making a local copy of zipline and changing the source code.

I'm thinking that handle data or a scheduled trade routine could take care of the decision to place a limit order or not and at what price. If the limit buy price is below the low price of the day, we would not place an order. If the limit buy price is between the low and the open, we would place a limit order using limit as the limit_price. If the limit buy price is above the open price, we would place a limit order using the open (plus a bit for slippage) as the limit_price.

To make sure that the order triggers we could use 10*limit (or 10*open) as the limit_price parameter for the order. This will satisfy the trigger condition that limit_price is above the next day close. Then before processing the order, we could use a custom slippage model to scale the price back down by 10X - i.e. price = order.price / 10.

This brings up another problem, though. My trading algo uses limit buy orders and market sell orders. I need a different slippage model to handle market orders.

Is there a way to have multiple slippage models and to associate the different slippage models to different orders or to different order types?

I think I found the answer to the question about custom slippage models - you can only have one and it must be called in the initialize section.

So this is what I came up with so far for market and limit orders using daily data.

# Custom slippage model is used to alter the default order execution price  
class MySlippage(SlippageModel):  
    # This needs to work for both market and limit orders  
    def process_order(self, data, order):  
        if order.limit is None:  
            # market on close  
            price = data.history(order.sid, 'close', 2, '1d')[0]  
            # alternative market on next open  
            #price = data.current(order.sid, 'open')  
        else:  
            # When constructing the order in the trade logic section:  
            #   For limit buys, set limit_price to 10*limit or 10*open for gaps  
            #   For limit sells, set limit price to limit/10 or open/10 for gaps  
            # This is done to make sure that the order triggers.  
            # Before the order is processed we scale it back down (or up) by 10X.  
            if order.amount > 0:         # buys  
                price = order.limit/10.0  
            if order.amount < 0:          # sells  
                price = order.limit*10.0  
        return (price, order.amount)  

Trade logic for a limit buy:

        # security, weight, limit_price, and open_price variables are assigned values before we get to this code segment  
        if open_price < limit_price:  # opening gap trigger  
            order_price = open_price  
            order_pct = weight  
            order_target_percent(security, order_pct, limit_price=10.0*order_price)  
        elif low_price < limit_price:  # intraday trigger  
            order_price = limit_price  
            order_pct = weight  
            order_target_percent(security, order_pct, limit_price=10.0*order_price)