QuantConnect/Lean · error · AssertionError

Expected symbol {expected_symbol} at index {i} on {date}, bu

Error message

Expected symbol {expected_symbol} at index {i} on {date}, but got {symbol}

What it means

Within a date's history chain, the regression iterates contracts in order and checks each symbol equals expected_chain[i] from the option chain provider for that date. A mismatch means the two providers return the same count but in a different order, or return different Symbols (e.g. different SecurityIdentifier/expiry/strike encoding). It guards determinism of contract ordering and symbol construction between the history and chain-provider paths.

Source

Thrown at Algorithm.Python/OptionUniverseHistoryRegressionAlgorithm.py:47

        # Level 0 of the multi-index is the date, we expect 3 dates, 3 option chains
        if historical_options_data_df.index.levshape[0] != 3:
            raise AssertionError(f"Expected 3 option chains from history request, but got {historical_options_data_df.index.levshape[1]}")

        for date in historical_options_data_df.index.levels[0]:
            expected_chain = list(self.option_chain_provider.get_option_contract_list(option, date))
            expected_chain_count = len(expected_chain)

            actual_chain = historical_options_data_df.loc[date]
            actual_chain_count = len(actual_chain)

            if expected_chain_count != actual_chain_count:
                raise AssertionError(f"Expected {expected_chain_count} options in chain on {date}, but got {actual_chain_count}")

            for i, symbol in enumerate(actual_chain.index):
                expected_symbol = expected_chain[i]
                if symbol != expected_symbol:
                    raise AssertionError(f"Expected symbol {expected_symbol} at index {i} on {date}, but got {symbol}")

View on GitHub (pinned to d2c3659f87)

Solutions

  1. Ensure both the history provider and the option chain provider sort contracts by the same key (e.g. expiry, then strike, then right).
  2. Verify Symbol/SecurityIdentifier construction is identical in both paths.
  3. If order is intentionally unspecified, compare as sets instead of positional index.
  4. Log the differing pair (expected vs actual) to see whether it is ordering or symbol content.

Example fix

# before: positional comparison assumes identical ordering
for i, symbol in enumerate(actual_chain.index):
    if symbol != expected_chain[i]: raise AssertionError(...)
# after: order-independent set comparison
def to_key(s): return (s.id.date, s.id.strike_price, s.id.option_right)
if sorted(map(to_key, actual_chain.index)) != sorted(map(to_key, expected_chain)):
    raise AssertionError('chain symbols differ from provider')
Defensive patterns

Strategy: validation

Validate before calling

# Order-independent comparison using stable keys
def key(s): return (s.id.date, s.id.strike_price, s.id.option_right)
expected = sorted(map(key, expected_chain))
actual = sorted(map(key, actual_chain.index))
if expected != actual:
    raise AssertionError('chain symbols differ from provider')

Prevention

When it happens

Trigger: actual_chain.index[i] != expected_chain[i] for some i, where counts match. Caused by a sort-order difference between the history DataFrame index and get_option_contract_list, or by Symbol construction diverging (e.g. SID, strike, expiry, right) between the two paths.

Common situations: Changing the sort key (by expiry vs strike vs SID) in one path but not the other; a Symbol/SID encoding change; daylight-savings or expiry-date normalization differences producing different expiry Symbols.

Related errors


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