Expected Behavior
Since #9535 / #9563, a market order that would fill on stale data waits for fresh data when the asset is subscribed only at hour/daily resolution (the stale bar is the previous close and a fresh bar is still expected), regardless of the time of day or the size of the gap.
A future added through AddFuture(..., Resolution.Daily) and traded on its mapped contract (future.Mapped) is a daily-only subscription in every practical sense, so an intraday market order on the mapped contract (e.g. from a scheduled event at 12:30) should wait for that session's daily bar on every weekday, not just on Mondays.
Actual Behavior
With AddFuture("MNQ", Resolution.Daily), no chain filter, Settings.StalePriceTimeSpan = TimeSpan.FromSeconds(60) and a scheduled event at 12:30 submitting MarketOrder(future.Mapped, qty):
- Tuesday to Friday the order fills immediately at 12:30 against the previous session's daily bar with
Warning: fill at stale price (01/08/2026 17:00:00 America/New_York) (19.5 hours old).
- Monday the order is held and fills at 17:00 on that day's bar.
Same algorithm with future.SetFilter(0, 90) added: every order waits and fills at 17:00, no stale warning. Verified on LEAN 2.5.0.0.18124 (cloud backtests 2026-01-05 to 2026-01-30, 16 stale fills Tue-Fri, 2 Monday waits; control run with the filter: 0 stale fills).
Root cause
The mapped contract of a continuous future only carries internal subscriptions: ContinuousContractUniverse.AddConfigurations creates them with isInternalFeed: !symbol.IsCanonical() || pair.Item2 == TickType.OpenInterest (Common/Data/UniverseSelection/ContinuousContractUniverse.cs, line 148), and GetSubscriptionRequests keeps that flag (line 109). Logging SubscriptionDataConfigService.GetSubscriptionDataConfigs(mapped, includeInternalConfigs: true) from the algorithm shows DAILY/QUOTE/internal=True; DAILY/TRADE/internal=True; DAILY/OPEN_INTEREST/internal=True.
FillModel.ShouldWaitForFreshData (Common/Orders/Fills/FillModel.cs, around line 1030) only lets non-internal configs decide whether the asset is coarse, and returns false when there is none (hasNonInternal stays false). ShouldWaitForFreshDataOnStale (around line 1072) then falls through to the generic test orderTimeUtc - dataEndTimeUtc > resolutionSpan with resolutionSpan = one day (all configs are daily). A Tuesday-to-Friday order sits 19.5 hours behind the previous 17:00 close, below one day, so it fills on the stale bar with the warning; a Monday order sits 67.5 hours behind Friday's close, above one day, so it waits. The day-of-week dependence is exactly this threshold.
The equivalent guard in QCAlgorithm.MarketOrder (Algorithm/QCAlgorithm.Trading.cs, IsDailyResolutionOnly, around line 404) also excludes internal configs, but futures never reach it: the MarketOnClose/MarketOnOpen conversion from #9534 is skipped for SecurityType.Future and FutureOption (line 249), so the fill model's wait is the only protection a daily future has. With a chain filter the contract gets a non-internal daily config from the chain universe and the wait works, which is why the behaviour depends on whether SetFilter was called.
Proposed change
In FillModel.ShouldWaitForFreshData, when the security has no non-internal subscription, decide from the internal ones instead of returning false:
var configs = subscriptionConfigs.Where(c => !c.IsInternalFeed).ToList();
if (configs.Count == 0)
{
// e.g. the mapped contract of a continuous future: only internal subscriptions
configs = subscriptionConfigs;
}
return configs.Count > 0 && configs.All(c => c.Resolution == Resolution.Hour || c.Resolution == Resolution.Daily);
Same fallback for IsDailyResolutionOnly in QCAlgorithm.Trading.cs if the futures exclusion is ever revisited. A FutureFillModelTests case with a daily internal-only config (stale by 19 hours, market open) would cover it; the existing MarketOrderWaitsForFreshDataWhenStaleByMoreThanResolution uses a non-internal minute config.
Open questions
- The "Closing the Stale-Price Loophole on Daily and Hourly Data" announcement does not mention that futures are excluded from the MarketOnClose conversion. If that exclusion is intentional (extended hours), the fill-model wait needs to cover the continuous-future case above; if not, the conversion could apply to futures too.
Reproduction
from AlgorithmImports import *
class StaleFillRepro(QCAlgorithm):
def initialize(self):
self.set_start_date(2026, 1, 5)
self.set_end_date(2026, 1, 30)
self.set_cash(1_000_000)
self.settings.stale_price_time_span = timedelta(seconds=60)
self._fut = self.add_future("MNQ", Resolution.DAILY)
# self._fut.set_filter(0, 90) # with this line every order waits for the 17:00 bar
self._spy = self.add_equity("SPY", Resolution.DAILY).symbol
self.schedule.on(self.date_rules.every_day(self._spy), self.time_rules.at(12, 30), self._trade)
def _trade(self):
mapped = self._fut.mapped
if mapped is None:
return
qty = -1 if self.portfolio[mapped].invested else 1
t = self.market_order(mapped, qty, True)
self.log(f"ORDER {t.order_id} {t.order_type} sent={self.time}")
def on_order_event(self, e):
if e.status == OrderStatus.FILLED:
self.log(f"FILL {e.order_id} at={self.time} px={e.fill_price} msg={e.message}")
System Information
LEAN 2.5.0.0.18124 (QuantConnect cloud backtest). Intercom conversation 215476075322715.
Checklist
Expected Behavior
Since #9535 / #9563, a market order that would fill on stale data waits for fresh data when the asset is subscribed only at hour/daily resolution (the stale bar is the previous close and a fresh bar is still expected), regardless of the time of day or the size of the gap.
A future added through
AddFuture(..., Resolution.Daily)and traded on its mapped contract (future.Mapped) is a daily-only subscription in every practical sense, so an intraday market order on the mapped contract (e.g. from a scheduled event at 12:30) should wait for that session's daily bar on every weekday, not just on Mondays.Actual Behavior
With
AddFuture("MNQ", Resolution.Daily), no chain filter,Settings.StalePriceTimeSpan = TimeSpan.FromSeconds(60)and a scheduled event at 12:30 submittingMarketOrder(future.Mapped, qty):Warning: fill at stale price (01/08/2026 17:00:00 America/New_York)(19.5 hours old).Same algorithm with
future.SetFilter(0, 90)added: every order waits and fills at 17:00, no stale warning. Verified on LEAN 2.5.0.0.18124 (cloud backtests 2026-01-05 to 2026-01-30, 16 stale fills Tue-Fri, 2 Monday waits; control run with the filter: 0 stale fills).Root cause
The mapped contract of a continuous future only carries internal subscriptions:
ContinuousContractUniverse.AddConfigurationscreates them withisInternalFeed: !symbol.IsCanonical() || pair.Item2 == TickType.OpenInterest(Common/Data/UniverseSelection/ContinuousContractUniverse.cs, line 148), andGetSubscriptionRequestskeeps that flag (line 109). LoggingSubscriptionDataConfigService.GetSubscriptionDataConfigs(mapped, includeInternalConfigs: true)from the algorithm showsDAILY/QUOTE/internal=True; DAILY/TRADE/internal=True; DAILY/OPEN_INTEREST/internal=True.FillModel.ShouldWaitForFreshData(Common/Orders/Fills/FillModel.cs, around line 1030) only lets non-internal configs decide whether the asset is coarse, and returnsfalsewhen there is none (hasNonInternalstaysfalse).ShouldWaitForFreshDataOnStale(around line 1072) then falls through to the generic testorderTimeUtc - dataEndTimeUtc > resolutionSpanwithresolutionSpan= one day (all configs are daily). A Tuesday-to-Friday order sits 19.5 hours behind the previous 17:00 close, below one day, so it fills on the stale bar with the warning; a Monday order sits 67.5 hours behind Friday's close, above one day, so it waits. The day-of-week dependence is exactly this threshold.The equivalent guard in
QCAlgorithm.MarketOrder(Algorithm/QCAlgorithm.Trading.cs,IsDailyResolutionOnly, around line 404) also excludes internal configs, but futures never reach it: the MarketOnClose/MarketOnOpen conversion from #9534 is skipped forSecurityType.FutureandFutureOption(line 249), so the fill model's wait is the only protection a daily future has. With a chain filter the contract gets a non-internal daily config from the chain universe and the wait works, which is why the behaviour depends on whetherSetFilterwas called.Proposed change
In
FillModel.ShouldWaitForFreshData, when the security has no non-internal subscription, decide from the internal ones instead of returningfalse:Same fallback for
IsDailyResolutionOnlyinQCAlgorithm.Trading.csif the futures exclusion is ever revisited. AFutureFillModelTestscase with a daily internal-only config (stale by 19 hours, market open) would cover it; the existingMarketOrderWaitsForFreshDataWhenStaleByMoreThanResolutionuses a non-internal minute config.Open questions
Reproduction
System Information
LEAN 2.5.0.0.18124 (QuantConnect cloud backtest). Intercom conversation 215476075322715.
Checklist
masterbranch