We've made changes to LEAN's default fill handling (PR 1, 2) to improve backtest accuracy, particularly for strategies that mix coarse resolutions (daily or hourly) with intraday minute bars or scheduled events. If your algorithm trades on daily/hourly data and places market orders during the day, your fills will become more realistic, and in some cases, your reported performance will change.

Root Problem

When an asset is subscribed to only at daily (or hourly) resolution, there is no fresh intraday price to fill a market order against. If you placed a market order mid-session, for example, from a scheduled event, or alongside a separate minute-resolution asset, LEAN would fill it immediately against the most recent bar available, which was often the stale previous close.

Filling against the stale bar means the order executes at a level the algorithm effectively "already knew," which is not something you could ever do live. It's an easy trap to fall into when combining daily or hourly assets with intraday minute bars, and it can quietly inflate backtest results.

This behavior was documented, and LEAN already emits warnings when it happens. But as more algorithms are being written with AI assistance, the warning was often going unseen, so we decided to enforce the correct behavior at the engine level.

Engine Updates

1. Intraday market orders on daily-only assets now convert order types. A market order placed during the session on a daily-subscribed asset is now converted to MarketOnClose (today's close), or to MarketOnOpen (the next open) if it's submitted too close to the cutoff. Orders placed while the market is closed continue to route to MarketOnOpen as before, and assets with intraday data remain as ordinary market orders. The result is a fill at a genuine daily open or close, rather than yesterday's stale print.

2. Market orders wait for fresh data rather than fill at a stale price. On hour and daily resolutions, the default fill models now hold a market order until genuinely fresh data arrives, filling it when the next bar closes rather than against a price older than the stale-price window. When such a resting order does fill, it fills at the open of the bar when trading resumes, when the market reopened, like a MarketOnOpen, which is the realistic execution for an order that was waiting in line. This was needed for the futures market, which doesn't have the MarketOnOpen order type. 

Important Notes

  • Higher resolutions are untouched. For minute, second, and tick subscriptions, stale data generally represents a genuine gap, and the existing bids and asks mean the orders are likely to be accurate.
  • The conversion behavior applies to backtesting only. In live-trading, a market order fills at the real current price, so nothing is converted there.
  • The stale data issue could still be exploited at finer frequencies. For example, filling on SPY minute bars, using SPX tick data for look-ahead. This has been explicitly skipped for now, as it won't affect the vast majority of the community who use minute only data.

You can tune the sensitivity of this behavior: currently self.settings.stale_price_time_span defaults to 1-hour, but you can tighten it (for example, to one minute self.settings.stale_price_time_span = timedelta(minutes=1)) to enforce the same behavior on lower frequencies.

Changing Behavior

If your strategy was relying on intraday fills against stale daily/hourly prices, you'll likely see your results change. This is expected and correct: those fills were never achievable in live trading. Concretely, you may notice:

  • Different fill prices on daily/hourly market orders placed intraday, and correspondingly updated PnL and order hashes.
  • Fewer fills in strategies that placed an order on (nearly) every coarse bar, since orders now land only on genuinely fresh bars rather than on repeated/fill-forwarded ones.
  • Warnings in your logs explaining when and why an order was converted or held for fresh data.

Difficult but Neccessary

We don't take this decision lightly, as we understand the frustration it might cause for people who have inadvertently stumbled into this pattern and see their backtest results change. We hope it is clear we do this in the best interest of the community - to serve the best possible simulation, so you do well in live, out-of-sample trading.

We're always open to feedback, let us know your thoughts in the comments.

Happy Trading,

QuantConnect Team