From 42d1b4a11e81d49f2062bfd26ee90cb0d19a2a6a Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 18 May 2026 10:20:29 -0400 Subject: [PATCH 1/3] Support indexing memoizing enumerable Pass MemoizingEnumerable to universe selectors --- .../CoarseFundamentalUniverse.cs | 3 +- .../FineFundamentalUniverse.cs | 3 +- Common/Data/UniverseSelection/FuncUniverse.cs | 3 +- .../FundamentalUniverseFactory.cs | 3 +- Common/Util/MemoizingEnumerable.cs | 36 +++++++++++++++++++ Tests/Common/Util/MemoizingEnumerableTests.cs | 24 +++++++++++++ 6 files changed, 68 insertions(+), 4 deletions(-) diff --git a/Common/Data/UniverseSelection/CoarseFundamentalUniverse.cs b/Common/Data/UniverseSelection/CoarseFundamentalUniverse.cs index ed67a03a2834..09b6f786bcbb 100644 --- a/Common/Data/UniverseSelection/CoarseFundamentalUniverse.cs +++ b/Common/Data/UniverseSelection/CoarseFundamentalUniverse.cs @@ -17,6 +17,7 @@ using System.Linq; using Python.Runtime; using System.Collections.Generic; +using QuantConnect.Util; namespace QuantConnect.Data.UniverseSelection { @@ -87,7 +88,7 @@ public CoarseFundamentalUniverse(Symbol symbol, UniverseSettings universeSetting /// The data that passes the filter public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data) { - return _selector(data.Data.OfType()); + return _selector(new MemoizingEnumerable(data.Data.OfType())); } /// diff --git a/Common/Data/UniverseSelection/FineFundamentalUniverse.cs b/Common/Data/UniverseSelection/FineFundamentalUniverse.cs index 8231a672f65e..0fe2e4199fed 100644 --- a/Common/Data/UniverseSelection/FineFundamentalUniverse.cs +++ b/Common/Data/UniverseSelection/FineFundamentalUniverse.cs @@ -17,6 +17,7 @@ using System.Linq; using System.Collections.Generic; using QuantConnect.Data.Fundamental; +using QuantConnect.Util; namespace QuantConnect.Data.UniverseSelection { @@ -60,7 +61,7 @@ public FineFundamentalUniverse(Symbol symbol, UniverseSettings universeSettings, /// The data that passes the filter public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data) { - return _selector(data.Data.OfType()); + return _selector(new MemoizingEnumerable(data.Data.OfType())); } /// diff --git a/Common/Data/UniverseSelection/FuncUniverse.cs b/Common/Data/UniverseSelection/FuncUniverse.cs index 572f2db4eabf..29f6b323e60c 100644 --- a/Common/Data/UniverseSelection/FuncUniverse.cs +++ b/Common/Data/UniverseSelection/FuncUniverse.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Linq; using Python.Runtime; +using QuantConnect.Util; namespace QuantConnect.Data.UniverseSelection { @@ -62,7 +63,7 @@ public FuncUniverse(SubscriptionDataConfig configuration, UniverseSettings unive /// The data that passes the filter public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data) { - return _universeSelector(data.Data.Cast()); + return _universeSelector(new MemoizingEnumerable(data.Data.Cast())); } } diff --git a/Common/Data/UniverseSelection/FundamentalUniverseFactory.cs b/Common/Data/UniverseSelection/FundamentalUniverseFactory.cs index 8d5288c7c2a7..34d23e0e63f6 100644 --- a/Common/Data/UniverseSelection/FundamentalUniverseFactory.cs +++ b/Common/Data/UniverseSelection/FundamentalUniverseFactory.cs @@ -18,6 +18,7 @@ using Python.Runtime; using System.Collections.Generic; using QuantConnect.Data.Fundamental; +using QuantConnect.Util; namespace QuantConnect.Data.UniverseSelection { @@ -84,7 +85,7 @@ public FundamentalUniverseFactory(Symbol symbol, UniverseSettings universeSettin /// The data that passes the filter public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data) { - return _selector(data.Data.OfType()); + return _selector(new MemoizingEnumerable(data.Data.OfType())); } /// diff --git a/Common/Util/MemoizingEnumerable.cs b/Common/Util/MemoizingEnumerable.cs index c7ec97cf4a67..a735791569b8 100644 --- a/Common/Util/MemoizingEnumerable.cs +++ b/Common/Util/MemoizingEnumerable.cs @@ -55,6 +55,42 @@ public int Count } } + /// + /// Gets the element at the specified index. + /// + /// The zero-based index of the element to get. + /// The element at the specified index. + public T this[int index] + { + get + { + if (!Enabled) + { + throw new InvalidOperationException("Indexer is not supported when memoization is disabled"); + } + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index), "Index cannot be negative."); + } + + if (_buffer != null && index < _buffer.Count) + { + return _buffer[index]; + } + + var i = 0; + foreach (var item in this) + { + if (i++ == index) + { + return item; + } + } + + throw new ArgumentOutOfRangeException(nameof(index), "Index was out of range. Must be non-negative and less than the size of the collection."); + } + } + /// /// Initializes a new instance of the class /// diff --git a/Tests/Common/Util/MemoizingEnumerableTests.cs b/Tests/Common/Util/MemoizingEnumerableTests.cs index 4ba3946b9e34..4f9f1ec10c90 100644 --- a/Tests/Common/Util/MemoizingEnumerableTests.cs +++ b/Tests/Common/Util/MemoizingEnumerableTests.cs @@ -13,6 +13,7 @@ * limitations under the License. */ +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -51,6 +52,29 @@ public void EnumeratesOnce() CollectionAssert.AreEqual(memoized.ToList(), memoized.ToList()); } + [Test] + public void IndexerRetrievesCorrectValues() + { + var list = new List { 10, 20, 30, 40, 50 }; + var memoized = new MemoizingEnumerable(list); + + Assert.AreEqual(10, memoized[0]); + Assert.AreEqual(30, memoized[2]); + Assert.AreEqual(50, memoized[4]); + + Assert.Throws(() => { var x = memoized[-1]; }); + Assert.Throws(() => { var x = memoized[5]; }); + } + + [Test] + public void IndexerDisablesWithoutMemoization() + { + var list = new List { 1, 2, 3 }; + var memoized = new MemoizingEnumerable(list) { Enabled = false }; + + Assert.Throws(() => { var x = memoized[0]; }); + } + [Test] public void GetsCount() { From 602249c5d4182193ba0022a77cfafdbc0369b6ad Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 18 May 2026 13:59:10 -0400 Subject: [PATCH 2/3] Add CastingEnumerable class --- .../CoarseFundamentalUniverse.cs | 2 +- .../FineFundamentalUniverse.cs | 2 +- Common/Data/UniverseSelection/FuncUniverse.cs | 2 +- .../FundamentalUniverseFactory.cs | 2 +- .../UniverseSelection/FuturesChainUniverse.cs | 3 +- .../UniverseSelection/OptionChainUniverse.cs | 3 +- .../ContractSecurityFilterUniverse.cs | 8 +- .../Securities/Future/FutureFilterUniverse.cs | 2 +- .../Securities/Option/OptionFilterUniverse.cs | 6 +- Common/Util/CastingEnumerable.cs | 81 +++++++++++++++++++ Common/Util/MemoizingEnumerable.cs | 36 --------- Research/QuantBook.cs | 8 +- Tests/Common/Securities/FutureFilterTests.cs | 18 ++--- Tests/Common/Securities/OptionFilterTests.cs | 32 ++++---- .../Securities/OptionStrategyFilterTests.cs | 2 +- Tests/Common/Util/CastingEnumerableTests.cs | 65 +++++++++++++++ Tests/Common/Util/MemoizingEnumerableTests.cs | 23 ------ 17 files changed, 192 insertions(+), 103 deletions(-) create mode 100644 Common/Util/CastingEnumerable.cs create mode 100644 Tests/Common/Util/CastingEnumerableTests.cs diff --git a/Common/Data/UniverseSelection/CoarseFundamentalUniverse.cs b/Common/Data/UniverseSelection/CoarseFundamentalUniverse.cs index 09b6f786bcbb..42b032d32c22 100644 --- a/Common/Data/UniverseSelection/CoarseFundamentalUniverse.cs +++ b/Common/Data/UniverseSelection/CoarseFundamentalUniverse.cs @@ -88,7 +88,7 @@ public CoarseFundamentalUniverse(Symbol symbol, UniverseSettings universeSetting /// The data that passes the filter public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data) { - return _selector(new MemoizingEnumerable(data.Data.OfType())); + return _selector(new CastingEnumerable(data.Data)); } /// diff --git a/Common/Data/UniverseSelection/FineFundamentalUniverse.cs b/Common/Data/UniverseSelection/FineFundamentalUniverse.cs index 0fe2e4199fed..59e67b49621e 100644 --- a/Common/Data/UniverseSelection/FineFundamentalUniverse.cs +++ b/Common/Data/UniverseSelection/FineFundamentalUniverse.cs @@ -61,7 +61,7 @@ public FineFundamentalUniverse(Symbol symbol, UniverseSettings universeSettings, /// The data that passes the filter public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data) { - return _selector(new MemoizingEnumerable(data.Data.OfType())); + return _selector(new CastingEnumerable(data.Data)); } /// diff --git a/Common/Data/UniverseSelection/FuncUniverse.cs b/Common/Data/UniverseSelection/FuncUniverse.cs index 29f6b323e60c..e4c7c04ac723 100644 --- a/Common/Data/UniverseSelection/FuncUniverse.cs +++ b/Common/Data/UniverseSelection/FuncUniverse.cs @@ -63,7 +63,7 @@ public FuncUniverse(SubscriptionDataConfig configuration, UniverseSettings unive /// The data that passes the filter public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data) { - return _universeSelector(new MemoizingEnumerable(data.Data.Cast())); + return _universeSelector(new CastingEnumerable(data.Data)); } } diff --git a/Common/Data/UniverseSelection/FundamentalUniverseFactory.cs b/Common/Data/UniverseSelection/FundamentalUniverseFactory.cs index 34d23e0e63f6..2608cd377195 100644 --- a/Common/Data/UniverseSelection/FundamentalUniverseFactory.cs +++ b/Common/Data/UniverseSelection/FundamentalUniverseFactory.cs @@ -85,7 +85,7 @@ public FundamentalUniverseFactory(Symbol symbol, UniverseSettings universeSettin /// The data that passes the filter public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data) { - return _selector(new MemoizingEnumerable(data.Data.OfType())); + return _selector(new CastingEnumerable(data.Data)); } /// diff --git a/Common/Data/UniverseSelection/FuturesChainUniverse.cs b/Common/Data/UniverseSelection/FuturesChainUniverse.cs index 36d222b80a42..81e292c97878 100644 --- a/Common/Data/UniverseSelection/FuturesChainUniverse.cs +++ b/Common/Data/UniverseSelection/FuturesChainUniverse.cs @@ -19,6 +19,7 @@ using System.Linq; using QuantConnect.Securities; using QuantConnect.Securities.Future; +using QuantConnect.Util; namespace QuantConnect.Data.UniverseSelection { @@ -84,7 +85,7 @@ public override UniverseSettings UniverseSettings public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data) { var localEndTime = utcTime.ConvertFromUtc(Future.Exchange.TimeZone); - var availableContracts = data.Data.Cast().ToList(); + var availableContracts = new CastingEnumerable(data.Data); var results = Future.ContractFilter.Filter(new FutureFilterUniverse(availableContracts, localEndTime)); return results.Select(x => x.Symbol); diff --git a/Common/Data/UniverseSelection/OptionChainUniverse.cs b/Common/Data/UniverseSelection/OptionChainUniverse.cs index 64640f77dd65..de2083b72b7a 100644 --- a/Common/Data/UniverseSelection/OptionChainUniverse.cs +++ b/Common/Data/UniverseSelection/OptionChainUniverse.cs @@ -20,6 +20,7 @@ using QuantConnect.Securities; using System.Collections.Generic; using QuantConnect.Securities.Option; +using QuantConnect.Util; namespace QuantConnect.Data.UniverseSelection { @@ -92,7 +93,7 @@ public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataColl { var localEndTime = utcTime.ConvertFromUtc(Option.Exchange.TimeZone); // we will only update unique strikes when there is an exchange date change - _optionFilterUniverse.Refresh(data.Data.Cast().ToList(), data.Underlying, localEndTime); + _optionFilterUniverse.Refresh(new CastingEnumerable(data.Data), data.Underlying, localEndTime); var results = Option.ContractFilter.Filter(_optionFilterUniverse); diff --git a/Common/Securities/ContractSecurityFilterUniverse.cs b/Common/Securities/ContractSecurityFilterUniverse.cs index 7611150f0d7f..252884d1729b 100644 --- a/Common/Securities/ContractSecurityFilterUniverse.cs +++ b/Common/Securities/ContractSecurityFilterUniverse.cs @@ -32,7 +32,7 @@ public abstract class ContractSecurityFilterUniverse : IDerivativeSecu { private bool _alreadyAppliedTypeFilters; - private IEnumerable _data; + private IReadOnlyList _data; /// /// Defines listed contract types with Flags attribute @@ -74,7 +74,7 @@ protected enum ContractExpirationType : int /// /// Setting it will also set AllSymbols /// - internal IEnumerable Data + internal IReadOnlyList Data { get { @@ -118,7 +118,7 @@ protected ContractSecurityFilterUniverse() /// /// Constructs ContractSecurityFilterUniverse /// - protected ContractSecurityFilterUniverse(IEnumerable allData, DateTime localTime) + protected ContractSecurityFilterUniverse(IReadOnlyList allData, DateTime localTime) { Data = allData; LocalTime = localTime; @@ -188,7 +188,7 @@ internal T ApplyTypesFilter() /// /// All data for contracts in the Universe /// The local exchange current time - public virtual void Refresh(IEnumerable allData, DateTime localTime) + public virtual void Refresh(IReadOnlyList allData, DateTime localTime) { Data = allData; LocalTime = localTime; diff --git a/Common/Securities/Future/FutureFilterUniverse.cs b/Common/Securities/Future/FutureFilterUniverse.cs index 3af6b50747da..8a30010d1a6d 100644 --- a/Common/Securities/Future/FutureFilterUniverse.cs +++ b/Common/Securities/Future/FutureFilterUniverse.cs @@ -31,7 +31,7 @@ public class FutureFilterUniverse : ContractSecurityFilterUniverse /// Constructs FutureFilterUniverse /// - public FutureFilterUniverse(IEnumerable allData, DateTime localTime) + public FutureFilterUniverse(IReadOnlyList allData, DateTime localTime) : base(allData, localTime) { } diff --git a/Common/Securities/Option/OptionFilterUniverse.cs b/Common/Securities/Option/OptionFilterUniverse.cs index 7f193be0342c..5cb946a97286 100644 --- a/Common/Securities/Option/OptionFilterUniverse.cs +++ b/Common/Securities/Option/OptionFilterUniverse.cs @@ -71,7 +71,7 @@ public OptionFilterUniverse(Option.Option option) /// Constructs OptionFilterUniverse /// /// Used for testing only - public OptionFilterUniverse(Option.Option option, IEnumerable allData, BaseData underlying, decimal underlyingScaleFactor = 1) + public OptionFilterUniverse(Option.Option option, IReadOnlyList allData, BaseData underlying, decimal underlyingScaleFactor = 1) : base(allData, underlying.EndTime) { _option = option; @@ -86,7 +86,7 @@ public OptionFilterUniverse(Option.Option option, IEnumerable al /// All data for the option contracts /// The current underlying last data point /// The current local time - public void Refresh(IEnumerable allContractsData, BaseData underlying, DateTime localTime) + public void Refresh(IReadOnlyList allContractsData, BaseData underlying, DateTime localTime) { base.Refresh(allContractsData, localTime); @@ -1037,7 +1037,7 @@ private IEnumerable GetContractsForExpiry(IEnumerable symbols, i /// private OptionFilterUniverse Empty() { - Data = Enumerable.Empty(); + Data = Enumerable.Empty().ToList(); return this; } diff --git a/Common/Util/CastingEnumerable.cs b/Common/Util/CastingEnumerable.cs new file mode 100644 index 000000000000..2e1bb4822a82 --- /dev/null +++ b/Common/Util/CastingEnumerable.cs @@ -0,0 +1,81 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace QuantConnect.Util +{ + /// + /// Defines a list that casts the elements of a source list to a derived type. + /// This is useful to avoid materializing another list after using, for example, the LINQ method. + /// + /// The base type of the elements in the source enumerable. + /// The type to cast the elements to. + public class CastingEnumerable : IReadOnlyList + where TDerived : class, TBase + { + private IReadOnlyList _data; + + /// + /// Gets the count of items in the enumerable. + /// + public int Count => _data.Count; + + /// + /// Gets the element at the specified index. + /// + /// The zero-based index of the element to get. + /// The element at the specified index. + public TDerived this[int index] => _data[index] as TDerived; + + /// + /// Initializes a new instance of the class + /// + public CastingEnumerable(IReadOnlyList data) + { + _data = data; + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// An enumerator that can be used to iterate through the collection. + /// + /// 1 + public IEnumerator GetEnumerator() + { + foreach (var item in _data) + { + yield return (TDerived)item; + } + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An enumerator object that can be used to iterate through the collection. + /// + /// 2 + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Common/Util/MemoizingEnumerable.cs b/Common/Util/MemoizingEnumerable.cs index a735791569b8..c7ec97cf4a67 100644 --- a/Common/Util/MemoizingEnumerable.cs +++ b/Common/Util/MemoizingEnumerable.cs @@ -55,42 +55,6 @@ public int Count } } - /// - /// Gets the element at the specified index. - /// - /// The zero-based index of the element to get. - /// The element at the specified index. - public T this[int index] - { - get - { - if (!Enabled) - { - throw new InvalidOperationException("Indexer is not supported when memoization is disabled"); - } - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index), "Index cannot be negative."); - } - - if (_buffer != null && index < _buffer.Count) - { - return _buffer[index]; - } - - var i = 0; - foreach (var item in this) - { - if (i++ == index) - { - return item; - } - } - - throw new ArgumentOutOfRangeException(nameof(index), "Index was out of range. Must be non-negative and less than the size of the collection."); - } - } - /// /// Initializes a new instance of the class /// diff --git a/Research/QuantBook.cs b/Research/QuantBook.cs index dafdea31339e..d7577cad7e7e 100644 --- a/Research/QuantBook.cs +++ b/Research/QuantBook.cs @@ -864,7 +864,7 @@ public PyDict GetPortfolioStatistics(PyObject dataFrame) /// /// Get's the universe data for the specified date /// - private IEnumerable GetChainHistory(Symbol canonicalSymbol, DateTime date, out BaseData underlyingData) + private IReadOnlyList GetChainHistory(Symbol canonicalSymbol, DateTime date, out BaseData underlyingData) where T : BaseChainUniverseData { // Use this GetEntry extension method since it's data type dependent, so we get the correct entry for the option universe @@ -878,17 +878,17 @@ private IEnumerable GetChainHistory(Symbol canonicalSymbol, DateTime date, if (universeData is not null) { underlyingData = universeData.Underlying; - return universeData.Data.Cast(); + return new CastingEnumerable(universeData.Data); } underlyingData = null; - return Enumerable.Empty(); + return Enumerable.Empty().ToList(); } /// /// Helper method to get option/future chain historical data for a given date range /// - private IEnumerable<(DateTime Date, IEnumerable ChainData, BaseData UnderlyingData)> GetChainHistory( + private IEnumerable<(DateTime Date, IReadOnlyList ChainData, BaseData UnderlyingData)> GetChainHistory( Security security, DateTime start, DateTime end, bool extendedMarketHours) where T : BaseChainUniverseData { diff --git a/Tests/Common/Securities/FutureFilterTests.cs b/Tests/Common/Securities/FutureFilterTests.cs index 5ea286e62867..e95868e9d116 100644 --- a/Tests/Common/Securities/FutureFilterTests.cs +++ b/Tests/Common/Securities/FutureFilterTests.cs @@ -53,7 +53,7 @@ public void FiltersExpiryRange() }; var data = symbols.Select(x => new FutureUniverse() { Symbol = x }); - var filtered = filter.Filter(new FutureFilterUniverse(data, time)).Select(x => x.Symbol).ToList(); + var filtered = filter.Filter(new FutureFilterUniverse(data.ToList(), time)).Select(x => x.Symbol).ToList(); Assert.AreEqual(5, filtered.Count); Assert.AreEqual(symbols[3], filtered[0]); Assert.AreEqual(symbols[4], filtered[1]); @@ -86,7 +86,7 @@ public void FutureContractFiltering(bool standardsOnly, int expectedCount) }; var data = symbols.Select(x => new FutureUniverse() { Symbol = x }); - var filtered = filter.Filter(new FutureFilterUniverse(data, time)).Select(x => x.Symbol).ToList(); + var filtered = filter.Filter(new FutureFilterUniverse(data.ToList(), time)).Select(x => x.Symbol).ToList(); Assert.AreEqual(expectedCount, filtered.Count); @@ -126,7 +126,7 @@ public void WeeklysFilterDoesNotFilterStandardContractWithExpiryMonthPriorOrAfte }; var data = symbols.Select(x => new FutureUniverse() { Symbol = x }); - var standardContracts = filter.Filter(new FutureFilterUniverse(data, new DateTime(2020, 1, 1))).Select(x => x.Symbol).ToList(); + var standardContracts = filter.Filter(new FutureFilterUniverse(data.ToList(), new DateTime(2020, 1, 1))).Select(x => x.Symbol).ToList(); Assert.AreEqual(6, standardContracts.Count); Assert.AreEqual(symbols[0], standardContracts[0]); Assert.AreEqual(symbols[1], standardContracts[1]); @@ -159,7 +159,7 @@ public void FilterAllowBothTypes() }; var data = symbols.Select(x => new FutureUniverse() { Symbol = x }); - var filtered = filter.Filter(new FutureFilterUniverse(data, time)).Select(x => x.Symbol).ToList(); + var filtered = filter.Filter(new FutureFilterUniverse(data.ToList(), time)).Select(x => x.Symbol).ToList(); Assert.AreEqual(6, filtered.Count); Assert.AreEqual(symbols, filtered); } @@ -187,7 +187,7 @@ public void FilterOutStandards() }; var data = symbols.Select(x => new FutureUniverse() { Symbol = x }); - var filtered = filter.Filter(new FutureFilterUniverse(data, time)).Select(x => x.Symbol).ToList(); + var filtered = filter.Filter(new FutureFilterUniverse(data.ToList(), time)).Select(x => x.Symbol).ToList(); Assert.AreEqual(4, filtered.Count); Assert.AreEqual(symbols[1], filtered[0]); Assert.AreEqual(symbols[2], filtered[1]); @@ -224,7 +224,7 @@ public void FiltersFrontMonth() }; var data = symbols.Select(x => new FutureUniverse() { Symbol = x }); - var filtered = filter.Filter(new FutureFilterUniverse(data, new DateTime(2016, 02, 26))).ToList(); + var filtered = filter.Filter(new FutureFilterUniverse(data.ToList(), new DateTime(2016, 02, 26))).ToList(); Assert.AreEqual(4, filtered.Count); } @@ -257,7 +257,7 @@ public void FiltersBackMonth() }; var data = symbols.Select(x => new FutureUniverse() { Symbol = x }); - var filtered = filter.Filter(new FutureFilterUniverse(data, new DateTime(2016, 02, 26))).ToList(); + var filtered = filter.Filter(new FutureFilterUniverse(data.ToList(), new DateTime(2016, 02, 26))).ToList(); Assert.AreEqual(3, filtered.Count); } @@ -290,7 +290,7 @@ public void FiltersExpirationCycles() }; var data = symbols.Select(x => new FutureUniverse() { Symbol = x }); - var filtered = filter.Filter(new FutureFilterUniverse(data, new DateTime(2016, 02, 26))).ToList(); + var filtered = filter.Filter(new FutureFilterUniverse(data.ToList(), new DateTime(2016, 02, 26))).ToList(); Assert.AreEqual(5, filtered.Count); } @@ -314,7 +314,7 @@ public void FilterTypeDoesNotBreakOnMissingExpiryFunction() var data = symbols.Select(x => new FutureUniverse() { Symbol = x }); // Since this is a unidentifiable symbol for our expiry functions it will return true and be passed through - var filtered = filter.Filter(new FutureFilterUniverse(data, time)).Select(x => x.Symbol).ToList(); + var filtered = filter.Filter(new FutureFilterUniverse(data.ToList(), time)).Select(x => x.Symbol).ToList(); Assert.AreEqual(1, filtered.Count); Assert.AreEqual(symbols[0], filtered[0]); } diff --git a/Tests/Common/Securities/OptionFilterTests.cs b/Tests/Common/Securities/OptionFilterTests.cs index a8e2eff009bc..8e89f0b55fcd 100644 --- a/Tests/Common/Securities/OptionFilterTests.cs +++ b/Tests/Common/Securities/OptionFilterTests.cs @@ -52,7 +52,7 @@ public void FiltersStrikeRange(decimal underlyingPrice, Symbol[] symbols, int fi var canonical = symbols[0].Canonical; var option = CreateOptionSecurity(canonical); - var filterUniverse = new OptionFilterUniverse(option, data, underlying, underlyingScaleFactor); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying, underlyingScaleFactor); var filtered = filter.Filter(filterUniverse).ToList(); Assert.AreEqual(filteredNumber, filtered.Count); Assert.AreEqual(symbols[3], filtered[0].Symbol); @@ -100,7 +100,7 @@ public void FiltersStrikeRangeWithVaryingDistance(decimal underlyingPrice) var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(option, data, underlying); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying); var filtered = filter.Filter(filterUniverse).ToList(); Assert.AreEqual(underlyingPrice == 8 ? 5 : 4, filtered.Count); Assert.AreEqual(symbols[1], filtered[0].Symbol); @@ -147,7 +147,7 @@ public void FiltersStrikeRangeWithNegativeMaxStrike(decimal underlyingPrice) var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(option, data, underlying); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying); var filtered = filter.Filter(filterUniverse).ToList(); Assert.AreEqual(3, filtered.Count); Assert.AreEqual(symbols[5], filtered[0].Symbol); @@ -181,7 +181,7 @@ public void FiltersStrikeRangeWithNegativeMaxStrikeOutOfRange(decimal underlying var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(option, data, underlying); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying); var filtered = filter.Filter(filterUniverse).ToList(); Assert.AreEqual(0, filtered.Count); } @@ -220,7 +220,7 @@ public void FiltersStrikeRangeWithPositiveMinStrike(decimal underlyingPrice) var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(option, data, underlying); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying); var filtered = filter.Filter(filterUniverse).ToList(); Assert.AreEqual(3, filtered.Count); Assert.AreEqual(symbols[2], filtered[0].Symbol); @@ -254,7 +254,7 @@ public void FiltersStrikeRangeWithPositiveMinStrikeOutOfRange(decimal underlying var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(option, data, underlying); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying); var filtered = filter.Filter(filterUniverse).ToList(); Assert.AreEqual(0, filtered.Count); } @@ -279,7 +279,7 @@ public void FiltersStrikeRangeWhenEmpty() var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(option, data, underlying); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying); var filtered = filter.Filter(filterUniverse).ToList(); Assert.AreEqual(0, filtered.Count); } @@ -316,7 +316,7 @@ public void FiltersExpiryRange() var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(option, data, underlying); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying); var filtered = filter.Filter(filterUniverse).ToList(); Assert.AreEqual(5, filtered.Count); Assert.AreEqual(symbols[3], filtered[0].Symbol); @@ -347,7 +347,7 @@ public void FiltersExpiryRangeAfterNonTradableDay() var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(option, data, underlying); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying); var filtered = filter.Filter(filterUniverse).ToList(); // Expiry range is 0 to 5 days, so 6 days times 3 strikes per day @@ -397,7 +397,7 @@ public void FiltersOutWeeklys() var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filtered = filter.Filter(new OptionFilterUniverse(option, data, underlying)).ToList(); + var filtered = filter.Filter(new OptionFilterUniverse(option, data.ToList(), underlying)).ToList(); Assert.AreEqual(3, filtered.Count); Assert.AreEqual(symbols[5], filtered[0].Symbol); Assert.AreEqual(symbols[6], filtered[1].Symbol); @@ -442,7 +442,7 @@ public void FiltersOutWeeklysIfFridayHoliday() var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filtered = filter.Filter(new OptionFilterUniverse(option, data, underlying)).ToList(); + var filtered = filter.Filter(new OptionFilterUniverse(option, data.ToList(), underlying)).ToList(); Assert.AreEqual(3, filtered.Count); Assert.AreEqual(symbols[5], filtered[0].Symbol); Assert.AreEqual(symbols[6], filtered[1].Symbol); @@ -483,7 +483,7 @@ public void FiltersOutStandardContracts() var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filtered = filter.Filter(new OptionFilterUniverse(option, data, underlying)).ToList(); + var filtered = filter.Filter(new OptionFilterUniverse(option, data.ToList(), underlying)).ToList(); Assert.AreEqual(8, filtered.Count); } @@ -521,7 +521,7 @@ public void FiltersOutNothingAfterFilteringByType() var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(option, data, underlying); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying); var filtered = filter.Filter(filterUniverse).ToList(); Assert.AreEqual(10, filtered.Count); } @@ -560,7 +560,7 @@ public void FiltersFrontMonth() var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filtered = filter.Filter(new OptionFilterUniverse(option, data, underlying)).ToList(); + var filtered = filter.Filter(new OptionFilterUniverse(option, data.ToList(), underlying)).ToList(); Assert.AreEqual(4, filtered.Count); } @@ -598,7 +598,7 @@ public void FiltersBackMonth() var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(option, data, underlying); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying); var filtered = filter.Filter(filterUniverse).ToList(); Assert.AreEqual(3, filtered.Count); } @@ -653,7 +653,7 @@ def set_filter(universe: OptionFilterUniverse) -> OptionFilterUniverse: var option = CreateOptionSecurity(canonical); var data = symbols.Select(x => new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(option, data, underlying); + var filterUniverse = new OptionFilterUniverse(option, data.ToList(), underlying); var filtered = filter.Filter(filterUniverse).ToList(); Assert.AreEqual(5, filtered.Count); } diff --git a/Tests/Common/Securities/OptionStrategyFilterTests.cs b/Tests/Common/Securities/OptionStrategyFilterTests.cs index 98f82c6d2fce..446eec6d5164 100644 --- a/Tests/Common/Securities/OptionStrategyFilterTests.cs +++ b/Tests/Common/Securities/OptionStrategyFilterTests.cs @@ -962,7 +962,7 @@ private List BaseFiltering(decimal underlyingPrice, Func new OptionUniverse() { Symbol = x }); - var filterUniverse = new OptionFilterUniverse(null, data, underlying); + var filterUniverse = new OptionFilterUniverse(null, data.ToList(), underlying); filterUniverse.Refresh(filterUniverse.Data, underlying, underlying.EndTime); if (fails) diff --git a/Tests/Common/Util/CastingEnumerableTests.cs b/Tests/Common/Util/CastingEnumerableTests.cs new file mode 100644 index 000000000000..629f3b0c6b76 --- /dev/null +++ b/Tests/Common/Util/CastingEnumerableTests.cs @@ -0,0 +1,65 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System.Collections.Generic; +using NUnit.Framework; +using QuantConnect.Util; + +namespace QuantConnect.Tests.Common.Util +{ + [TestFixture] + public class CastingEnumerableTests + { + private class BaseClass { } + private class DerivedClass : BaseClass { } + + [Test] + public void CastsOnEnumeration() + { + var list = new List { new DerivedClass(), new DerivedClass(), new DerivedClass() }; + var casting = new CastingEnumerable(list); + + Assert.DoesNotThrow(() => + { + foreach (var item in casting) { } + }); + + CollectionAssert.AreEqual(list, casting); + Assert.AreEqual(list.Count, casting.Count); + } + + [Test] + public void CastsOnIndexing() + { + var list = new List { new DerivedClass(), new DerivedClass(), new DerivedClass() }; + var casting = new CastingEnumerable(list); + + DerivedClass casted0 = null; + DerivedClass casted1 = null; + DerivedClass casted2 = null; + + Assert.DoesNotThrow(() => + { + casted0 = casting[0]; + casted1 = casting[1]; + casted2 = casting[2]; + }); + + Assert.AreEqual(list[0], casted0); + Assert.AreEqual(list[1], casted1); + Assert.AreEqual(list[2], casted2); + } + } +} diff --git a/Tests/Common/Util/MemoizingEnumerableTests.cs b/Tests/Common/Util/MemoizingEnumerableTests.cs index 4f9f1ec10c90..c8d568eb9f15 100644 --- a/Tests/Common/Util/MemoizingEnumerableTests.cs +++ b/Tests/Common/Util/MemoizingEnumerableTests.cs @@ -52,29 +52,6 @@ public void EnumeratesOnce() CollectionAssert.AreEqual(memoized.ToList(), memoized.ToList()); } - [Test] - public void IndexerRetrievesCorrectValues() - { - var list = new List { 10, 20, 30, 40, 50 }; - var memoized = new MemoizingEnumerable(list); - - Assert.AreEqual(10, memoized[0]); - Assert.AreEqual(30, memoized[2]); - Assert.AreEqual(50, memoized[4]); - - Assert.Throws(() => { var x = memoized[-1]; }); - Assert.Throws(() => { var x = memoized[5]; }); - } - - [Test] - public void IndexerDisablesWithoutMemoization() - { - var list = new List { 1, 2, 3 }; - var memoized = new MemoizingEnumerable(list) { Enabled = false }; - - Assert.Throws(() => { var x = memoized[0]; }); - } - [Test] public void GetsCount() { From b1a9920494e3a44a47844ad583e09724780309ad Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 18 May 2026 16:29:22 -0400 Subject: [PATCH 3/3] Minor change --- Common/Util/CastingEnumerable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/Util/CastingEnumerable.cs b/Common/Util/CastingEnumerable.cs index 2e1bb4822a82..167ea776ba0b 100644 --- a/Common/Util/CastingEnumerable.cs +++ b/Common/Util/CastingEnumerable.cs @@ -41,7 +41,7 @@ public class CastingEnumerable : IReadOnlyList /// /// The zero-based index of the element to get. /// The element at the specified index. - public TDerived this[int index] => _data[index] as TDerived; + public TDerived this[int index] => (TDerived)_data[index]; /// /// Initializes a new instance of the class