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
- Inspect the logged Holdings line for the previous month to see which symbol persisted past expiry.
- Verify Lean's delisting/automatic-position-close logic runs for future options on the expiry date.
- 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.
- 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
- Liquidate positions before contract expiry rather than relying solely on automatic delisting.
- Log holdings monthly to detect stale positions early.
- Compare expiry dates against algorithm Time.Date to avoid timezone off-by-one.
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
- Expiry warning with time {delisting.time} but is expected to
- Delisting warning received at an unexpected date: {self.time
- Delisting occurred at unexpected time: {delisting.time} - ex
- Delisting notice received at an unexpected date: {self.time}
- Expected 4 delisting events received, found: {self.delisting
AI-assisted analysis of QuantConnect/Lean@d2c3659f87 (2026-08-13).
Data as JSON: /api/errors/38e6fdcc45a586d0.
Report an issue: GitHub.