QuantConnect/Lean · error · AssertionError

Delisting notice received at an unexpected date: {self.time}

Error message

Delisting notice received at an unexpected date: {self.time} - expected {delisting.time}

What it means

Second DELISTED check: delisting.time must also equal datetime(self.time.year, self.time.month, self.time.day). It ensures the delisting notice and the algorithm's current bar share the same calendar day.

Source

Thrown at Algorithm.Python/FuturesAndFuturesOptionsExpiryTimeAndLiquidationRegressionAlgorithm.py:66

        self.es_future = self.add_future_contract(es, Resolution.MINUTE).symbol
        self.es_future_option = self.add_future_option_contract(es_option, Resolution.MINUTE).symbol

    def on_data(self, data: Slice):
        for delisting in data.delistings.values():
            self.delistings_received += 1

            if delisting.type == DelistingType.WARNING and delisting.time != self.expected_expiry_warning_time:
                raise AssertionError(f"Expiry warning with time {delisting.time} but is expected to be {self.expected_expiry_warning_time}")

            if delisting.type == DelistingType.WARNING and delisting.time != datetime(self.time.year, self.time.month, self.time.day):
                raise AssertionError(f"Delisting warning received at an unexpected date: {self.time} - expected {delisting.time}")

            if delisting.type == DelistingType.DELISTED and delisting.time != self.expected_expiry_delisting_time:
                raise AssertionError(f"Delisting occurred at unexpected time: {delisting.time} - expected: {self.expected_expiry_delisting_time}")

            if delisting.type == DelistingType.DELISTED and delisting.time != datetime(self.time.year, self.time.month, self.time.day):
                raise AssertionError(f"Delisting notice received at an unexpected date: {self.time} - expected {delisting.time}")

        if not self.invested and \
            (self.es_future in data.bars or self.es_future in data.quote_bars) and \
            (self.es_future_option in data.bars or self.es_future_option in data.quote_bars):

            self.invested = True

            self.market_order(self.es_future, 1)
            self.market_order(self.es_future_option, 1)

    def on_order_event(self, order_event: OrderEvent):
        if order_event.direction != OrderDirection.SELL or order_event.status != OrderStatus.FILLED:
            return

        # * Future Liquidation
        # * Future Option Exercise
        # * We expect NO Underlying Future Liquidation because we already hold a Long future position so the FOP Put selling leaves us breakeven
        self.liquidated += 1

View on GitHub (pinned to d2c3659f87)

Solutions

  1. Print delisting.time and self.time with tz info to find the day skew.
  2. Normalize both to the same timezone before comparing.
  3. Confirm the engine processes the delisting notice in the bar whose date matches.
  4. Update the assertion if notice-processing timing intentionally changed.
Defensive patterns

Strategy: validation

Validate before calling

# Cross-check delisted day against algorithm day
if delisting.type == DelistingType.DELISTED:
    if delisting.time.date() != self.time.date():
        self.debug(f"Delisted day {delisting.time.date()} != algo day {self.time.date()}")

Type guard

def delisted_on_current_day(delisting, algo_time: datetime) -> bool:
    return delisting.type == DelistingType.DELISTED and delisting.time.date() == algo_time.date()

Prevention

When it happens

Trigger: delisting.time and self.time disagree on the calendar day (timezone skew); the delisting notice arriving in a backtest bar from a different day than the notice timestamp; fill-forward artefacts.

Common situations: Timezone differences between the delisting timestamp and algorithm time; engine changes to when delisting notices are processed; data refreshes shifting notice timing.

Related errors


AI-assisted analysis of QuantConnect/Lean@d2c3659f87 (2026-08-13). Data as JSON: /api/errors/30c0a387b719b437. Report an issue: GitHub.