Skip to content
All library documents

Forward-Only Data Fill Model for More Conservative Backtests

Article Strategy library · Author: QuantConnect

Summary

This example demonstrates a custom equity fill model that prevents an order from filling against market data whose bar ended before the order was placed. It converts the order timestamp to the security exchange’s local time, checks cached quote bars, trade bars, and ticks, and delegates to the standard equity fill model only when the available data is eligible. Otherwise, it returns an unfilled result.

The accompanying SPY example schedules an order at the weekly market open and asserts that the order fills on the subsequent hourly data event. The stated purpose is to encourage more conservative simulations by avoiding fills based on stale data. This addresses one source of unrealistic backtest fills, but the example does not quantify performance impact or model other execution frictions such as spread, market impact, or partial fills.

Key ideas

  • A custom fill model can reject data that ended before an order was placed.
  • The example checks cached quote bars, trade bars, and ticks against the order time in the exchange time zone.
  • Eligible data is passed to the standard equity fill model; otherwise the model leaves the order unfilled.
  • The SPY example expects a scheduled order to fill on the next hourly data event.
  • Forward-only eligibility can make fills more conservative, but it does not represent every execution cost.

Tags

Full text
# ForwardDataOnlyFillModelAlgorithm


# ForwardDataOnlyFillModelAlgorithm









Example of custom fill model for security to only fill bars of data obtained after the order was placed. This is to encourage more pessimistic fill models and eliminate the possibility to fill on old market data that may not be relevant.

## Source (Apache-2.0)

```python
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from AlgorithmImports import *

### <summary>
### Example of custom fill model for security to only fill bars of data obtained after the order was placed. This is to encourage more
### pessimistic fill models and eliminate the possibility to fill on old market data that may not be relevant.
### </summary>
class ForwardDataOnlyFillModelAlgorithm(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2013,10,1)
        self.set_end_date(2013,10,31)

        self.security = self.add_equity("SPY", Resolution.HOUR)
        self.security.set_fill_model(ForwardDataOnlyFillModel())

        self.schedule.on(self.date_rules.week_start(), self.time_rules.after_market_open(self.security.symbol), self.trade)

    def trade(self):
        if not self.portfolio.invested:
            if self.time.hour != 9 or self.time.minute != 30:
                raise AssertionError(f"Unexpected event time {self.time}")

            ticket = self.buy("SPY", 1)
            if ticket.status != OrderStatus.SUBMITTED:
                raise AssertionError(f"Unexpected order status {ticket.status}")

    def on_order_event(self, order_event: OrderEvent):
        self.debug(f"OnOrderEvent:: {order_event}")
        if order_event.status == OrderStatus.FILLED and (self.time.hour != 10 or self.time.minute != 0):
            raise AssertionError(f"Unexpected fill time {self.time}")

class ForwardDataOnlyFillModel(EquityFillModel):
    def fill(self, parameters: FillModelParameters):
        order_local_time = Extensions.convert_from_utc(parameters.order.time, parameters.security.exchange.time_zone)
        for data_type in [ QuoteBar, TradeBar, Tick ]:
            data = parameters.security.cache.get_data(data_type)
            if not data is None and order_local_time <= data.end_time:
                return super().fill(parameters)
        return Fill([])

```

Shown in full with attribution under the source's licence. Licence: Apache-2.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.