QuantConnect/Lean · error · RegressionTestException

Index is not tradable.

Error message

Index is not tradable.

What it means

Identical assertion to error 21 but in BasicTemplateIndexOptionsAlgorithm. Thrown when Portfolio[_spx].TotalSaleVolume > 0, meaning the engine filled orders on the SPX index itself. Index symbols must remain non-tradable — only their options should generate fills.

Source

Thrown at Algorithm.CSharp/BasicTemplateIndexOptionsAlgorithm.cs:106

                    if (_emaFast > _emaSlow && contract.Right == OptionRight.Call)
                    {
                        Liquidate(InvertOption(contract.Symbol));
                        MarketOrder(contract.Symbol, 1);
                    }
                    else if (_emaFast < _emaSlow && contract.Right == OptionRight.Put)
                    {
                        Liquidate(InvertOption(contract.Symbol));
                        MarketOrder(contract.Symbol, 1);
                    }
                }
            }
        }

        public override void OnEndOfAlgorithm()
        {
            if (Portfolio[_spx].TotalSaleVolume > 0)
            {
                throw new RegressionTestException("Index is not tradable.");
            }
            if (Portfolio.TotalSaleVolume == 0)
            {
                throw new RegressionTestException("Trade volume should be greater than zero by the end of this algorithm");
            }
            AssertIndicators();
        }

        public Symbol InvertOption(Symbol symbol)
        {
            return QuantConnect.Symbol.CreateOption(
                symbol.Underlying,
                symbol.ID.Market,
                symbol.ID.OptionStyle,
                symbol.ID.OptionRight == OptionRight.Call ? OptionRight.Put : OptionRight.Call,
                symbol.ID.StrikePrice,
                symbol.ID.Date);
        }

View on GitHub (pinned to d2c3659f87)

Solutions

  1. Audit all order method calls (MarketOrder, LimitOrder, Liquidate, SetHoldings) to confirm they use option contract symbols, not _spx.
  2. Log Securities[_spx].IsTradable after Initialize to verify it is false.
  3. Check for InvertOption() misuse — ensure it returns option symbols, not the underlying index.
  4. Review the IndexSecurity/SecurityService code path for IsTradable default assignment.

Example fix

// before — Liquidate(_spx) is called somewhere
Liquidate(_spx);

// after — liquidate specific option positions
Liquidate(contract.Symbol);
Defensive patterns

Strategy: validation

Validate before calling

// Verify index is non-tradable before running
if (Securities[_spx].IsTradable)
{
    Log($"WARNING: {_spx} is tradable — index should not be");
}
// Verify no order calls reference _spx
foreach (var kvp in Securities)
{
    if (kvp.Key == _spx && kvp.Value.Holdings.Quantity != 0)
        Log($"Unexpected position on index {_spx}: {kvp.Value.Holdings.Quantity}");
}

Type guard

bool IsTradableSecurity(Symbol sym) =>
    Securities[sym].IsTradable && Securities[sym].Type != SecurityType.Index;

Prevention

When it happens

Trigger: The OnData logic calls MarketOrder on the _spx index symbol instead of a contract symbol, the engine's IsTradable flag for index securities was reset, or a Liquidate() call inadvertently targets the index.

Common situations: Code refactoring that passes _spx to MarketOrder instead of contract.Symbol, brokerage model changes affecting index tradability, or engine-level security initialization bugs.

Related errors


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