Skip to content

Handle null keys gracefully in dictionary lookup methods - #9679

Open
jhonabreul wants to merge 3 commits into
QuantConnect:masterfrom
jhonabreul:bug-null-key-dictionary-lookups
Open

Handle null keys gracefully in dictionary lookup methods#9679
jhonabreul wants to merge 3 commits into
QuantConnect:masterfrom
jhonabreul:bug-null-key-dictionary-lookups

Conversation

@jhonabreul

@jhonabreul jhonabreul commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Description

A Python algorithm guarding a securities lookup with contains_key crashes when the key is None:

RuntimeError at 01/03/2023 15:00:00 UTC. Context: Scheduled event: 'SPX: EveryDay: 10' at 1/3/2023 3:00:00 PM ArgumentNullException: Value cannot be null. (Parameter 'key')
    spx_price = self.securities[self._spx_underlying].price if self.securities.contains_key(self._spx_underlying) else None
                                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This is easy to hit when a symbol field is initialized to None and only assigned later (e.g. when the first option chain arrives), while a scheduled event fires before that assignment.

Cause: the ContainsKey/TryGetValue overrides in the ExtendedDictionary<TKey, TValue> family pass the key straight into the backing .NET dictionary, which throws ArgumentNullException for a null key — while the ExtendedDictionary base and its get/pop helpers already treat a null/None key as a missing key.

The fix — a null key now behaves like a missing key (false/default) in the lookup methods:

  • SecurityManager.ContainsKey/TryGetValue return false for a null symbol (this also covers SecurityPortfolioManager, which delegates to Securities).
  • BaseExtendedDictionary.ContainsKey/TryGetValue return false for a null key, covering DataDictionary collections (TradeBars, chains, etc.), UniverseManager and ReadOnlyExtendedDictionary.
  • Slice.ContainsKey/TryGetValue return false for a null symbol.
  • CashBook.ContainsKey/TryGetValue return false for a null symbol.
  • OptionChains.GetCanonicalOptionSymbol passes a null symbol through instead of throwing NullReferenceException, so its ContainsKey/TryGetValue fall back to the now-guarded base implementations.
  • SecurityPositionGroupModel.TryGetValue returns false for a null key.

Related Issue

N/A — user-reported runtime error, reproduction included in the description.

Motivation and Context

self.securities.contains_key(symbol) is the natural Python idiom for defensively guarding a lookup; in a Python dictionary None in d / d.get(None) returns False/None, so the guard itself throwing is surprising and defeats its purpose.

Requires Documentation Change

No.

How Has This Been Tested?

  • ExtendedDictionaryTests.DictionariesHandleNullKeysGracefully (new): parameterized with one test case per dictionary type — SecurityManager, SecurityPortfolioManager, CashBook, Slice, DataDictionary, TradeBars, OptionChains, FuturesChains, UniverseManager and SecurityPositionGroupModel — asserting ContainsKey(null) is false, TryGetValue(null) is false and get(null) is null.
  • PythonDictionaryFeatureRegressionAlgorithm (extended): exercises the exact reported pattern from Python — contains_key(None)/get(None) on Securities, Portfolio, the Slice and slice.bars — and fails if any of them throws or returns a non-missing result. Expected statistics unchanged.
  • Reproduced the reported error with a scratch algorithm (scheduled event calling contains_key(None) on Securities) before the fix; after the fix the same algorithm completes and the guard returns None.
  • Test suites for all touched classes (ExtendedDictionaryTests, SliceTests, CashBookTests, SecurityManagerTests, SecurityPortfolioManagerTests, OptionChainsTests, DataDictionaryTests, UniverseManagerTests, position group tests): 3,381 passed, 0 failed.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Refactor (non-breaking change which improves implementation)
  • Performance (non-breaking change which improves performance. Please add associated performance test and results)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Non-functional change (xml comments/documentation/etc)

Checklist:

  • My code follows the code style of this project.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • My branch follows the naming convention bug-<issue#>-<description> or feature-<issue#>-<description>

ContainsKey and TryGetValue overrides across the ExtendedDictionary
family (SecurityManager, CashBook, Slice, BaseExtendedDictionary,
OptionChains, SecurityPositionGroupModel) passed the key straight into
the backing .NET dictionary, which throws ArgumentNullException for a
null key. From Python, a defensive guard like
self.securities.contains_key(symbol) would itself throw when the symbol
was None instead of returning False like a Python dictionary would.
@jhonabreul
jhonabreul marked this pull request as ready for review August 12, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant