Support Count and Python len() on user-facing lazy enumerables#9631
Merged
jhonabreul merged 7 commits intoJul 21, 2026
Merged
Conversation
Wrap the enumerables returned by GetOrders, GetOrderTickets and GetOpenOrderTickets in MemoizingEnumerable so they expose a Count property, which also enables len() on them in Python algorithms.
Wrap SubscriptionManager.Subscriptions, SecurityCache.GetAll, Cash.SecuritySymbols and ICurrencyConversion.ConversionRateSecurities in MemoizingEnumerable so they expose a Count property, which also enables len() on them in Python algorithms.
Exposes the count of stored objects without loading their content, which also enables len() on the object store in Python algorithms.
This reverts commit 84c0a08.
jhonabreul
marked this pull request as ready for review
July 21, 2026 18:01
- Revert SecurityCache.GetAll memoization: fill models enumerate it once per open order on every fill scan, so memoizing only adds allocations and per-item locking on a hot path. - SecurityTransactionManager.GetOpenOrdersRemainingQuantity and CancelOpenOrders go directly to the order processor since they enumerate a single time, keeping memoization only for the enumerables returned to the user.
Martin-Molinero
approved these changes
Jul 21, 2026
Replace direct _orderProcessor calls with private GetOrderTickets/ GetOpenOrderTickets overloads taking a required memoize flag, so every internal call site states whether the enumerable is single-pass (engine paths) or user-facing (memoized). Also drop the redundant x => true fallback: IOrderProvider documents null as match-all and every implementation handles it.
Preserves the previous behavior where IOrderProcessor implementations never received a null filter from the transaction manager.
Martin-Molinero
approved these changes
Jul 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Python.NET enables
len()on anyIEnumerablewhose runtime type exposes a publicCountproperty, but several user-facing APIs return lazy LINQ/yieldenumerables, so Python algorithms getTypeError: object of type '...' has no len()and C# users force a full re-enumeration on everyCount()call.The change, one commit per area:
SecurityTransactionManager.GetOrders,GetOrderTicketsandGetOpenOrderTickets(all overloads) wrap their results inMemoizingEnumerable, following the same patternQCAlgorithm.Historyalready uses. Declared return types stayIEnumerable<T>, soIOrderProvideris unchanged.QCAlgorithmare wrapped the same way:SubscriptionManager.Subscriptions,Cash.SecuritySymbolsandICurrencyConversion.ConversionRateSecurities(both implementations).No interfaces are modified, so existing implementations (including external ones) are unaffected.
Engine usages of the wrapped members were audited so memoization adds no overhead to hot paths:
SecurityCache.GetAll<T>is intentionally NOT wrapped: fill models enumerate it once per open order on every fill scan, so memoizing would only add allocations and per-item locking.SecurityTransactionManager.GetOpenOrdersRemainingQuantityandCancelOpenOrdersenumerate a single time, so they go directly to the order processor and skip the memoizing wrapper.AlgorithmManagercustom-data setup and split handling,QCAlgorithm.SetHoldingsImpl/Shortable) run once at startup or per order placement, not per data point, and enumerate once.Related Issue
N/A
Motivation and Context
len()is the idiomatic way for Python algorithm authors to count orders, tickets or subscriptions; it should work on these APIs instead of requiring alist(...)conversion. Memoizing also makes repeated enumeration of a returned enumerable consistent (single snapshot) and cheaper.Requires Documentation Change
No.
How Has This Been Tested?
SecurityTransactionManagerTests.OrderEnumerablesAreMemoizedExposingCount: asserts the returned enumerables areMemoizingEnumerableandCountmatches the submitted orders.SecurityTransactionManagerTests.OrderEnumerablesSupportPythonLen: assertsPyObject.Length()(the samemp_lengthslot as Python'slen()) on all overloads, including thePyObjectfilter ones.SubscriptionManagerTests.SubscriptionsAreMemoizedExposingCount,CashTests.SecuritySymbolsAreMemoizedExposingCount,SecurityCurrencyConversionTests.ConversionRateSecuritiesAreMemoizedExposingCount/ConstantConversionRateSecuritiesAreMemoizedExposingCount: assert type andCountfor each wrapped API.InternalSubscriptionManagerTests.UniverseSelectionAddAndRemove, a live-mode timing test excluded from CI (TravisExclude) that passes on re-run and touches none of the changed code.Types of changes
Checklist:
bug-<issue#>-<description>orfeature-<issue#>-<description>