Skip to content

Daily continuous future: market orders on the mapped contract fill at the stale previous close Tuesday to Friday, wait only on Mondays #9827

Description

@AlexCatarino

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

  • I have completely filled out this template
  • I have confirmed that this issue exists on the current master branch
  • I have confirmed that this is not a duplicate issue by searching issues
  • I have provided detailed steps to reproduce the issue

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions