QuantConnect/Lean · critical · RegressionTestException

[{UtcTime}] We hold a delisted securities: {string.Join(",",

Error message

[{UtcTime}] We hold a delisted securities: {string.Join(",", delistedSecurity)}

What it means

Once per month, the algorithm lists all invested securities and flags any whose contract expiry date (symbol.ID.Date) + 1 day is earlier than the current algorithm time. If any delisted/expired contract is still held in the portfolio, the regression fails — Lean should have liquidated or rolled the position at expiry.

Source

Thrown at Algorithm.CSharp/DelistingFutureOptionRegressionAlgorithm.cs:63

            // This is required to prevent the algorithm from automatically delisting the underlying. Without this, future options will be subscribed
            // with resolution default to Minute insted of this.Resolution. This could be replaced after GH issue #6491 is implemented.
            UniverseSettings.Resolution = Resolution;
        }

        public override void OnData(Slice slice)
        {
            if (Time.Month != _lastMonth)
            {
                _lastMonth = Time.Month;
                var investedSymbols = Securities.Values
                    .Where(security => security.Invested)
                    .Select(security => security.Symbol)
                    .ToList();

                var delistedSecurity = investedSymbols.Where(symbol => symbol.ID.Date.AddDays(1) < Time).ToList();
                if (delistedSecurity.Count > 0)
                {
                    throw new RegressionTestException($"[{UtcTime}] We hold a delisted securities: {string.Join(",", delistedSecurity)}");
                }
                Log($"Holdings({Time}): {string.Join(",", investedSymbols)}");
            }

            if (Portfolio.Invested)
            {
                return;
            }

            foreach (var chain in slice.OptionChains.Values)
            {
                foreach (var contractsValue in chain.Contracts.Values)
                {
                    MarketOrder(contractsValue.Symbol, 1);
                    _traded = true;
                }
            }
        }

View on GitHub (pinned to d2c3659f87)

Solutions

  1. Inspect the logged Holdings line for the previous month to see which symbol persisted past expiry.
  2. Verify Lean's delisting/automatic-position-close logic runs for future options on the expiry date.
  3. Check for a timezone offset: symbol.ID.Date is date-only, so compare against Time.Date rather than AddDays(1) < Time if timezone handling changed.
  4. Ensure the algorithm does not re-enter a position in a symbol that has already expired within the same month window.
Defensive patterns

Strategy: validation

Validate before calling

var delistedSecurity = investedSymbols
    .Where(symbol => symbol.ID.Date.AddDays(1) < Time)
    .ToList();
if (delistedSecurity.Count > 0)
{
    // Log before throwing so the stale position is visible
    Log($"Stale positions past expiry: {string.Join(",", delistedSecurity)}");
    Liquidate(delistedSecurity); // defensive cleanup
}

Prevention

When it happens

Trigger: Holding a future or future-option position past its expiry/settlement date without Lean's automatic delisting handling closing it. Occurs when delisting processing is delayed, when the position is reopened after expiry, or when symbol.ID.Date is misinterpreted relative to Time.

Common situations: A Lean bug where future-option or future delisting does not liquidate on the expiry day, timezone mismatches between symbol.ID.Date and algorithm Time, or a contract-roll logic gap that leaves a stale position.

Related errors


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