diff --git a/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj b/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj index 975f634aa089..33ef01251290 100644 --- a/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj +++ b/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj @@ -32,7 +32,7 @@ portable - + diff --git a/Algorithm.CSharp/StringToSymbolImplicitConversionRegressionAlgorithm.cs b/Algorithm.CSharp/StringToSymbolImplicitConversionRegressionAlgorithm.cs index 681f686d3565..599b31677c93 100644 --- a/Algorithm.CSharp/StringToSymbolImplicitConversionRegressionAlgorithm.cs +++ b/Algorithm.CSharp/StringToSymbolImplicitConversionRegressionAlgorithm.cs @@ -49,7 +49,7 @@ public override void OnData(Slice slice) } catch (Exception exception) { - if (exception.Message.Contains("This asset symbol (PEPE 0) was not found in your security list") && !Portfolio.Invested) + if (exception.Message.Contains("PEPE was not found", StringComparison.InvariantCultureIgnoreCase) && !Portfolio.Invested) { SetHoldings("SPY", 1); } diff --git a/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj b/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj index 964576ff57b4..489c19902e38 100644 --- a/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj +++ b/Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj @@ -29,7 +29,7 @@ LICENSE - + diff --git a/Algorithm.Python/DynamicSecurityDataRegressionAlgorithm.py b/Algorithm.Python/DynamicSecurityDataRegressionAlgorithm.py index 5f7a08e1a3ab..6bd8b05a3df2 100644 --- a/Algorithm.Python/DynamicSecurityDataRegressionAlgorithm.py +++ b/Algorithm.Python/DynamicSecurityDataRegressionAlgorithm.py @@ -12,7 +12,6 @@ # limitations under the License. from AlgorithmImports import * -from System.Collections.Generic import List from QuantConnect.Data.Custom.IconicTypes import * ### @@ -28,24 +27,24 @@ def initialize(self): self._equity = self.add_equity(ticker, Resolution.DAILY) custom_linked_equity = self.add_data(LinkedData, ticker, Resolution.DAILY) - + first_linked_data = LinkedData() first_linked_data.count = 100 first_linked_data.symbol = custom_linked_equity.symbol first_linked_data.end_time = self.start_date - + second_linked_data = LinkedData() second_linked_data.count = 100 second_linked_data.symbol = custom_linked_equity.symbol second_linked_data.end_time = self.start_date - + # Adding linked data manually to cache for example purposes, since # LinkedData is a type used for testing and doesn't point to any real data. custom_linked_equity_type = list(custom_linked_equity.subscriptions)[0].type - custom_linked_data = List[LinkedData]() - custom_linked_data.add(first_linked_data) - custom_linked_data.add(second_linked_data) - self._equity.cache.add_data_list(custom_linked_data, custom_linked_equity_type, False) + custom_linked_data = list[LinkedData]() + custom_linked_data.append(first_linked_data) + custom_linked_data.append(second_linked_data) + self._equity.cache.add_data_list(custom_linked_data, custom_linked_equity_type, False) def on_data(self, data): # The Security object's Data property provides convenient access diff --git a/Algorithm.Python/HistoryWithDifferentDataMappingModeRegressionAlgorithm.py b/Algorithm.Python/HistoryWithDifferentDataMappingModeRegressionAlgorithm.py index 26c1b7fa200c..412f666cbaad 100644 --- a/Algorithm.Python/HistoryWithDifferentDataMappingModeRegressionAlgorithm.py +++ b/Algorithm.Python/HistoryWithDifferentDataMappingModeRegressionAlgorithm.py @@ -12,7 +12,6 @@ # limitations under the License. from AlgorithmImports import * -from System import * ### ### Regression algorithm illustrating how to request history data for different data mapping modes. @@ -25,13 +24,12 @@ def initialize(self): self._continuous_contract_symbol = self.add_future(Futures.Indices.SP_500_E_MINI, Resolution.DAILY).symbol def on_end_of_algorithm(self): - data_mapping_modes = [DataMappingMode(x) for x in Enum.get_values(DataMappingMode)] history_results = [ self.history([self._continuous_contract_symbol], self.start_date, self.end_date, Resolution.DAILY, data_mapping_mode=data_mapping_mode) .droplevel(0, axis=0) .loc[self._continuous_contract_symbol] .close - for data_mapping_mode in data_mapping_modes + for data_mapping_mode in DataMappingMode ] if any(x.size != history_results[0].size for x in history_results): @@ -40,5 +38,5 @@ def on_end_of_algorithm(self): # Check that close prices at each time are different for different data mapping modes for j in range(history_results[0].size): close_prices = set(history_results[i][j] for i in range(len(history_results))) - if len(close_prices) != len(data_mapping_modes): + if len(close_prices) != len(DataMappingMode): raise AssertionError("History results close prices should have been different for each data mapping mode at each time") diff --git a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj index 9b566b925e35..e7237a5620a3 100644 --- a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj +++ b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj @@ -37,7 +37,7 @@ - + diff --git a/Algorithm.Python/StringToSymbolImplicitConversionRegressionAlgorithm.py b/Algorithm.Python/StringToSymbolImplicitConversionRegressionAlgorithm.py index 12025f4da3c9..fe23b230a046 100644 --- a/Algorithm.Python/StringToSymbolImplicitConversionRegressionAlgorithm.py +++ b/Algorithm.Python/StringToSymbolImplicitConversionRegressionAlgorithm.py @@ -34,5 +34,5 @@ def on_data(self, data): try: self.market_order("PEPE", 1) except Exception as exception: - if "This asset symbol (PEPE 0) was not found in your security list" in str(exception) and not self.portfolio.invested: + if "PEPE was not found" in str(exception) and not self.portfolio.invested: self.set_holdings("SPY", 1) diff --git a/Algorithm/QuantConnect.Algorithm.csproj b/Algorithm/QuantConnect.Algorithm.csproj index 4b36a3533736..03881d8892cb 100644 --- a/Algorithm/QuantConnect.Algorithm.csproj +++ b/Algorithm/QuantConnect.Algorithm.csproj @@ -29,7 +29,7 @@ LICENSE - + diff --git a/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj b/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj index cb5ec33a6424..9dd8b33e2c61 100644 --- a/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj +++ b/AlgorithmFactory/QuantConnect.AlgorithmFactory.csproj @@ -28,7 +28,7 @@ LICENSE - + diff --git a/Common/Data/Market/BaseChain.cs b/Common/Data/Market/BaseChain.cs index 937b1411dbbd..1341d16f51a8 100644 --- a/Common/Data/Market/BaseChain.cs +++ b/Common/Data/Market/BaseChain.cs @@ -109,6 +109,21 @@ public HashSet FilteredContracts [PandasIgnore] public PyObject DataFrame => _dataframe.Value; + /// + /// The number of contracts in this chain + /// + public int Count => Contracts.Count; + + /// + /// Checks if the chain contains a contract with the specified symbol + /// + /// The symbol of the contract to check for + /// True if the chain contains a contract with the specified symbol; otherwise, false. + public bool ContainsKey(Symbol key) + { + return Contracts.ContainsKey(key); + } + /// /// Initializes a new default instance of the class /// diff --git a/Common/Data/Market/DataDictionary.cs b/Common/Data/Market/DataDictionary.cs index 22b4c1e4404b..5f2dc03d179f 100644 --- a/Common/Data/Market/DataDictionary.cs +++ b/Common/Data/Market/DataDictionary.cs @@ -24,7 +24,7 @@ namespace QuantConnect.Data.Market /// Provides a base class for types holding base data instances keyed by symbol /// [PandasNonExpandable] - public class DataDictionary : ExtendedDictionary, IDictionary + public class DataDictionary : ExtendedDictionary, IDictionary { // storage for the data private readonly IDictionary _data = new Dictionary(); @@ -146,7 +146,7 @@ public bool Remove(KeyValuePair item) /// /// The number of elements contained in the . /// - public int Count + public override int Count { get { return _data.Count; } } @@ -169,11 +169,17 @@ public override bool IsReadOnly /// true if the contains an element with the key; otherwise, false. /// /// The key to locate in the . is null. - public bool ContainsKey(Symbol key) + public override bool ContainsKey(Symbol key) { return _data.ContainsKey(key); } + /// + /// Gets all the items in the dictionary + /// + /// All the items in the dictionary + public override IEnumerable> GetItems() => _data; + /// /// Adds an element with the provided key and value to the . /// @@ -226,6 +232,7 @@ public override T this[Symbol symbol] { return data; } + CheckForImplicitlyCreatedSymbol(symbol); throw new KeyNotFoundException($"'{symbol}' wasn't found in the {GetType().GetBetterTypeName()} object, likely because there was no-data at this moment in time and it wasn't possible to fillforward historical data. Please check the data exists before accessing it with data.ContainsKey(\"{symbol}\")"); } set diff --git a/Common/Data/Market/Delistings.cs b/Common/Data/Market/Delistings.cs index 085c428d5a40..d9338011b1a2 100644 --- a/Common/Data/Market/Delistings.cs +++ b/Common/Data/Market/Delistings.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -38,25 +38,5 @@ public Delistings(DateTime frontier) : base(frontier) { } - - /// - /// Gets or sets the Delisting with the specified ticker. - /// - /// - /// The Delisting with the specified ticker. - /// - /// The ticker of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new Delisting this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } } - - /// - /// Gets or sets the Delisting with the specified Symbol. - /// - /// - /// The Delisting with the specified Symbol. - /// - /// The Symbol of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new Delisting this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } } } } diff --git a/Common/Data/Market/Dividends.cs b/Common/Data/Market/Dividends.cs index cf5906cefb30..b5ba9b3a8fad 100644 --- a/Common/Data/Market/Dividends.cs +++ b/Common/Data/Market/Dividends.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -38,25 +38,5 @@ public Dividends(DateTime frontier) : base(frontier) { } - - /// - /// Gets or sets the Dividend with the specified ticker. - /// - /// - /// The Dividend with the specified ticker. - /// - /// The ticker of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new Dividend this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } } - - /// - /// Gets or sets the Dividend with the specified Symbol. - /// - /// - /// The Dividend with the specified Symbol. - /// - /// The Symbol of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new Dividend this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } } } } diff --git a/Common/Data/Market/FuturesContracts.cs b/Common/Data/Market/FuturesContracts.cs index ba4ca9f459b5..c0d0f38a23fc 100644 --- a/Common/Data/Market/FuturesContracts.cs +++ b/Common/Data/Market/FuturesContracts.cs @@ -36,25 +36,5 @@ public FuturesContracts(DateTime time) : base(time) { } - - /// - /// Gets or sets the FuturesContract with the specified ticker. - /// - /// - /// The FuturesContract with the specified ticker. - /// - /// The ticker of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new FuturesContract this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } } - - /// - /// Gets or sets the FuturesContract with the specified Symbol. - /// - /// - /// The FuturesContract with the specified Symbol. - /// - /// The Symbol of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new FuturesContract this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } } } -} \ No newline at end of file +} diff --git a/Common/Data/Market/MarginInterestRates.cs b/Common/Data/Market/MarginInterestRates.cs index 271cfc2a00b4..58b7fba67d0c 100644 --- a/Common/Data/Market/MarginInterestRates.cs +++ b/Common/Data/Market/MarginInterestRates.cs @@ -38,25 +38,5 @@ public MarginInterestRates(DateTime frontier) : base(frontier) { } - - /// - /// Gets or sets the Dividend with the specified ticker. - /// - /// - /// The Dividend with the specified ticker. - /// - /// The ticker of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new MarginInterestRate this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } } - - /// - /// Gets or sets the Dividend with the specified Symbol. - /// - /// - /// The Dividend with the specified Symbol. - /// - /// The Symbol of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new MarginInterestRate this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } } } } diff --git a/Common/Data/Market/OptionContracts.cs b/Common/Data/Market/OptionContracts.cs index 7be31c12dcf9..6cd6b9c678c9 100644 --- a/Common/Data/Market/OptionContracts.cs +++ b/Common/Data/Market/OptionContracts.cs @@ -36,25 +36,5 @@ public OptionContracts(DateTime time) : base(time) { } - - /// - /// Gets or sets the OptionContract with the specified ticker. - /// - /// - /// The OptionContract with the specified ticker. - /// - /// The ticker of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new OptionContract this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } } - - /// - /// Gets or sets the OptionContract with the specified Symbol. - /// - /// - /// The OptionContract with the specified Symbol. - /// - /// The Symbol of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new OptionContract this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } } } -} \ No newline at end of file +} diff --git a/Common/Data/Market/QuoteBars.cs b/Common/Data/Market/QuoteBars.cs index bcdea1520a06..5bcacd5418dd 100644 --- a/Common/Data/Market/QuoteBars.cs +++ b/Common/Data/Market/QuoteBars.cs @@ -36,25 +36,5 @@ public QuoteBars(DateTime time) : base(time) { } - - /// - /// Gets or sets the QuoteBar with the specified ticker. - /// - /// - /// The QuoteBar with the specified ticker. - /// - /// The ticker of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new QuoteBar this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } } - - /// - /// Gets or sets the QuoteBar with the specified Symbol. - /// - /// - /// The QuoteBar with the specified Symbol. - /// - /// The Symbol of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new QuoteBar this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } } } -} \ No newline at end of file +} diff --git a/Common/Data/Market/Splits.cs b/Common/Data/Market/Splits.cs index 47bd2741369d..a952886ced72 100644 --- a/Common/Data/Market/Splits.cs +++ b/Common/Data/Market/Splits.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -38,25 +38,5 @@ public Splits(DateTime frontier) : base(frontier) { } - - /// - /// Gets or sets the Split with the specified ticker. - /// - /// - /// The Split with the specified ticker. - /// - /// The ticker of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new Split this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } } - - /// - /// Gets or sets the Split with the specified Symbol. - /// - /// - /// The Split with the specified Symbol. - /// - /// The Symbol of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new Split this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } } } -} \ No newline at end of file +} diff --git a/Common/Data/Market/SymbolChangedEvents.cs b/Common/Data/Market/SymbolChangedEvents.cs index 018ef9a55ff6..d15eb63f9cf6 100644 --- a/Common/Data/Market/SymbolChangedEvents.cs +++ b/Common/Data/Market/SymbolChangedEvents.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -38,25 +38,5 @@ public SymbolChangedEvents(DateTime frontier) : base(frontier) { } - - /// - /// Gets or sets the SymbolChangedEvent with the specified ticker. - /// - /// - /// The SymbolChangedEvent with the specified ticker. - /// - /// The ticker of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new SymbolChangedEvent this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } } - - /// - /// Gets or sets the SymbolChangedEvent with the specified Symbol. - /// - /// - /// The SymbolChangedEvent with the specified Symbol. - /// - /// The Symbol of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new SymbolChangedEvent this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } } } } diff --git a/Common/Data/Market/Ticks.cs b/Common/Data/Market/Ticks.cs index 92e53bf71b1e..b47130cd8a67 100644 --- a/Common/Data/Market/Ticks.cs +++ b/Common/Data/Market/Ticks.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -39,25 +39,5 @@ public Ticks(DateTime frontier) : base(frontier) { } - - /// - /// Gets or sets the list of Tick with the specified ticker. - /// - /// - /// The list of Tick with the specified ticker. - /// - /// The ticker of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new List this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } } - - /// - /// Gets or sets the list of Tick with the specified Symbol. - /// - /// - /// The list of Tick with the specified Symbol. - /// - /// The Symbol of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new List this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } } } } diff --git a/Common/Data/Market/TradeBars.cs b/Common/Data/Market/TradeBars.cs index cd12164e2667..57b65cd3d9d0 100644 --- a/Common/Data/Market/TradeBars.cs +++ b/Common/Data/Market/TradeBars.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -37,25 +37,5 @@ public TradeBars(DateTime frontier) : base(frontier) { } - - /// - /// Gets or sets the TradeBar with the specified ticker. - /// - /// - /// The TradeBar with the specified ticker. - /// - /// The ticker of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new TradeBar this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } } - - /// - /// Gets or sets the TradeBar with the specified Symbol. - /// - /// - /// The TradeBar with the specified Symbol. - /// - /// The Symbol of the element to get or set. - /// Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations - public new TradeBar this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } } } -} \ No newline at end of file +} diff --git a/Common/Data/Slice.cs b/Common/Data/Slice.cs index a0c696bada05..d7009e3342f4 100644 --- a/Common/Data/Slice.cs +++ b/Common/Data/Slice.cs @@ -30,7 +30,7 @@ namespace QuantConnect.Data /// /// Provides a data structure for all of an algorithm's data at a single time step /// - public class Slice : ExtendedDictionary, IEnumerable> + public class Slice : ExtendedDictionary, IEnumerable> { private Ticks _ticks; private TradeBars _bars; @@ -171,7 +171,7 @@ public MarginInterestRates MarginInterestRates /// /// Gets the number of symbols held in this slice /// - public virtual int Count + public override int Count { get { return _data.Value.Count; } } @@ -208,6 +208,13 @@ public virtual IReadOnlyList Values get { return GetKeyValuePairEnumerable().Select(x => x.Value).ToList(); } } + /// + /// Gets all the items in the dictionary + /// + /// All the items in the dictionary + public override IEnumerable> GetItems() => + GetKeyValuePairEnumerable().Select(kvp => KeyValuePair.Create(kvp.Key, kvp.Value)); + /// /// Initializes a new instance of the class, lazily /// instantiating the and @@ -332,8 +339,15 @@ public override dynamic this[Symbol symbol] { return value.GetData(); } + CheckForImplicitlyCreatedSymbol(symbol); throw new KeyNotFoundException($"'{symbol}' wasn't found in the Slice object, likely because there was no-data at this moment in time and it wasn't possible to fillforward historical data. Please check the data exists before accessing it with data.ContainsKey(\"{symbol}\")"); } + set + { + // this is a no-op, we don't want to allow setting data in the slice + // this is a read-only collection + throw new NotSupportedException("The Slice object is read-only. You cannot set data in the slice."); + } } /// @@ -511,7 +525,7 @@ public T Get(Symbol symbol) /// /// The symbol we seek data for /// True if this instance contains data for the symbol, false otherwise - public virtual bool ContainsKey(Symbol symbol) + public override bool ContainsKey(Symbol symbol) { return _data.Value.ContainsKey(symbol); } diff --git a/Common/ExtendedDictionary.cs b/Common/ExtendedDictionary.cs index c0d3d1e4f969..f5a8458b13b0 100644 --- a/Common/ExtendedDictionary.cs +++ b/Common/ExtendedDictionary.cs @@ -24,11 +24,18 @@ namespace QuantConnect { /// - /// Provides a base class for types holding instances keyed by + /// Provides a base class for types holding key value pairs with helper methods for easy usage in Python /// [PandasNonExpandable] - public abstract class ExtendedDictionary : IExtendedDictionary +#pragma warning disable CA1708 // Identifiers should differ by more than case + public abstract class ExtendedDictionary : IExtendedDictionary +#pragma warning restore CA1708 // Identifiers should differ by more than case { + /// + /// Gets the number of elements contained in the dictionary + /// + public abstract int Count { get; } + /// /// Removes all items from the . /// @@ -43,21 +50,39 @@ public virtual void Clear() } /// - /// Gets the value associated with the specified Symbol. + /// Gets the value associated with the specified key. /// /// - /// true if the object that implements contains an element with the specified Symbol; otherwise, false. + /// true if the object that implements contains an element with the specified key; otherwise, false. /// - /// The Symbol whose value to get.When this method returns, the value associated with the specified Symbol, if the Symbol is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. is null. - public abstract bool TryGetValue(Symbol symbol, out T value); + /// The key whose value to get. + /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. + /// is null. + public abstract bool TryGetValue(TKey key, out TValue value); + + /// + /// Checks if the dictionary contains the specified key. + /// + /// The key to locate in the dictionary + /// true if the dictionary contains an element with the specified key; otherwise, false. + public virtual bool ContainsKey(TKey key) + { + return TryGetValue(key, out _); + } + + /// + /// Gets all the items in the dictionary + /// + /// All the items in the dictionary + public abstract IEnumerable> GetItems(); /// - /// Gets an containing the Symbol objects of the . + /// Gets an containing the key objects of the . /// /// - /// An containing the Symbol objects of the object that implements . + /// A containing the key objects of the object that implements . /// - protected abstract IEnumerable GetKeys { get; } + protected abstract IEnumerable GetKeys { get; } /// /// Gets an containing the values in the . @@ -65,7 +90,7 @@ public virtual void Clear() /// /// An containing the values in the object that implements . /// - protected abstract IEnumerable GetValues { get; } + protected abstract IEnumerable GetValues { get; } /// /// Gets a value indicating whether the object is read-only. @@ -74,11 +99,11 @@ public virtual void Clear() public virtual bool IsReadOnly => true; /// - /// Removes the value with the specified Symbol + /// Removes the value with the specified key /// - /// The Symbol object of the element to remove. + /// The key object of the element to remove. /// true if the element is successfully found and removed; otherwise, false. - public virtual bool Remove(Symbol symbol) + public virtual bool Remove(TKey key) { if (IsReadOnly) { @@ -91,47 +116,9 @@ public virtual bool Remove(Symbol symbol) /// Indexer method for the base dictioanry to access the objects by their symbol. /// /// IDictionary implementation - /// Symbol object indexer - /// Object of - public virtual T this[Symbol symbol] - { - get - { - throw new NotImplementedException(Messages.ExtendedDictionary.IndexerBySymbolNotImplemented); - } - set - { - throw new NotImplementedException(Messages.ExtendedDictionary.IndexerBySymbolNotImplemented); - } - } - - /// - /// Indexer method for the base dictioanry to access the objects by their symbol. - /// - /// IDictionary implementation - /// string ticker symbol indexer - /// Object of - public virtual T this[string ticker] - { - get - { - Symbol symbol; - if (!SymbolCache.TryGetSymbol(ticker, out symbol)) - { - throw new KeyNotFoundException(Messages.ExtendedDictionary.TickerNotFoundInSymbolCache(ticker)); - } - return this[symbol]; - } - set - { - Symbol symbol; - if (!SymbolCache.TryGetSymbol(ticker, out symbol)) - { - throw new KeyNotFoundException(Messages.ExtendedDictionary.TickerNotFoundInSymbolCache(ticker)); - } - this[symbol] = value; - } - } + /// Key object indexer + /// Object of + public abstract TValue this[TKey key] { get; set; } /// /// Removes all keys and values from the . @@ -155,9 +142,9 @@ public PyDict copy() /// /// Sequence of elements which is to be used as keys for the new dictionary /// Returns a new dictionary with the given sequence of elements as the keys of the dictionary. - public PyDict fromkeys(Symbol[] sequence) + public PyDict fromkeys(TKey[] sequence) { - return fromkeys(sequence, default(T)); + return fromkeys(sequence, default); } /// @@ -167,7 +154,7 @@ public PyDict fromkeys(Symbol[] sequence) /// Value which is set to each each element of the dictionary /// Returns a new dictionary with the given sequence of elements as the keys of the dictionary. /// Each element of the newly created dictionary is set to the provided value. - public PyDict fromkeys(Symbol[] sequence, T value) + public PyDict fromkeys(TKey[] sequence, TValue value) { using (Py.GIL()) { @@ -182,29 +169,29 @@ public PyDict fromkeys(Symbol[] sequence, T value) } /// - /// Returns the value for the specified Symbol if Symbol is in dictionary. + /// Returns the value for the specified key if key is in dictionary. /// - /// Symbol to be searched in the dictionary - /// The value for the specified Symbol if Symbol is in dictionary. - /// None if the Symbol is not found and value is not specified. - public T get(Symbol symbol) + /// key to be searched in the dictionary + /// The value for the specified key if key is in dictionary. + /// None if the key is not found and value is not specified. + public TValue get(TKey key) { - T data; - TryGetValue(symbol, out data); + TValue data; + TryGetValue(key, out data); return data; } /// - /// Returns the value for the specified Symbol if Symbol is in dictionary. + /// Returns the value for the specified key if key is in dictionary. /// - /// Symbol to be searched in the dictionary - /// Value to be returned if the Symbol is not found. The default value is null. - /// The value for the specified Symbol if Symbol is in dictionary. - /// value if the Symbol is not found and value is specified. - public T get(Symbol symbol, T value) + /// key to be searched in the dictionary + /// Value to be returned if the key is not found. The default value is null. + /// The value for the specified key if key is in dictionary. + /// value if the key is not found and value is specified. + public TValue get(TKey key, TValue value) { - T data; - if (TryGetValue(symbol, out data)) + TValue data; + if (TryGetValue(key, out data)) { return data; } @@ -212,35 +199,29 @@ public T get(Symbol symbol, T value) } /// - /// Returns a view object that displays a list of dictionary's (Symbol, value) tuple pairs. + /// Returns a view object that displays a list of dictionary's (key, value) tuple pairs. /// - /// Returns a view object that displays a list of a given dictionary's (Symbol, value) tuple pair. + /// Returns a view object that displays a list of a given dictionary's (key, value) tuple pair. public PyList items() { using (Py.GIL()) { var pyList = new PyList(); - foreach (var key in GetKeys) + foreach (var (key, value) in GetItems()) { - using (var pyKey = key.ToPython()) - { - using (var pyValue = this[key].ToPython()) - { - using (var pyObject = new PyTuple(new PyObject[] { pyKey, pyValue })) - { - pyList.Append(pyObject); - } - } - } + using var pyKey = key.ToPython(); + using var pyValue = value.ToPython(); + using var pyKvp = new PyTuple([pyKey, pyValue]); + pyList.Append(pyKvp); } return pyList; } } /// - /// Returns and removes an arbitrary element (Symbol, value) pair from the dictionary. + /// Returns and removes an arbitrary element (key, value) pair from the dictionary. /// - /// Returns an arbitrary element (Symbol, value) pair from the dictionary + /// Returns an arbitrary element (key, value) pair from the dictionary /// removes an arbitrary element(the same element which is returned) from the dictionary. /// Note: Arbitrary elements and random elements are not same.The popitem() doesn't return a random element. public PyTuple popitem() @@ -249,74 +230,74 @@ public PyTuple popitem() } /// - /// Returns the value of a Symbol (if the Symbol is in dictionary). If not, it inserts Symbol with a value to the dictionary. + /// Returns the value of a key (if the key is in dictionary). If not, it inserts key with a value to the dictionary. /// - /// Key with null/None value is inserted to the dictionary if Symbol is not in the dictionary. - /// The value of the Symbol if it is in the dictionary - /// None if Symbol is not in the dictionary - public T setdefault(Symbol symbol) + /// Key with null/None value is inserted to the dictionary if key is not in the dictionary. + /// The value of the key if it is in the dictionary + /// None if key is not in the dictionary + public TValue setdefault(TKey key) { - return setdefault(symbol, default(T)); + return setdefault(key, default); } /// - /// Returns the value of a Symbol (if the Symbol is in dictionary). If not, it inserts Symbol with a value to the dictionary. + /// Returns the value of a key (if the key is in dictionary). If not, it inserts key with a value to the dictionary. /// - /// Key with a value default_value is inserted to the dictionary if Symbol is not in the dictionary. + /// Key with a value default_value is inserted to the dictionary if key is not in the dictionary. /// Default value - /// The value of the Symbol if it is in the dictionary - /// default_value if Symbol is not in the dictionary and default_value is specified - public T setdefault(Symbol symbol, T default_value) + /// The value of the key if it is in the dictionary + /// default_value if key is not in the dictionary and default_value is specified + public TValue setdefault(TKey key, TValue default_value) { - T data; - if (TryGetValue(symbol, out data)) + TValue data; + if (TryGetValue(key, out data)) { return data; } if (IsReadOnly) { - throw new KeyNotFoundException(Messages.ExtendedDictionary.SymbolNotFoundDueToNoData(this, symbol)); + throw new KeyNotFoundException(Messages.ExtendedDictionary.KeyNotFoundDueToNoData(this, key)); } - this[symbol] = default_value; + this[key] = default_value; return default_value; } /// - /// Removes and returns an element from a dictionary having the given Symbol. + /// Removes and returns an element from a dictionary having the given key. /// - /// Key which is to be searched for removal - /// If Symbol is found - removed/popped element from the dictionary - /// If Symbol is not found - KeyError exception is raised - public T pop(Symbol symbol) + /// Key which is to be searched for removal + /// If key is found - removed/popped element from the dictionary + /// If key is not found - KeyError exception is raised + public TValue pop(TKey key) { - return pop(symbol, default(T)); + return pop(key, default); } /// - /// Removes and returns an element from a dictionary having the given Symbol. + /// Removes and returns an element from a dictionary having the given key. /// - /// Key which is to be searched for removal - /// Value which is to be returned when the Symbol is not in the dictionary - /// If Symbol is found - removed/popped element from the dictionary - /// If Symbol is not found - value specified as the second argument(default) - public T pop(Symbol symbol, T default_value) + /// Key which is to be searched for removal + /// Value which is to be returned when the key is not in the dictionary + /// If key is found - removed/popped element from the dictionary + /// If key is not found - value specified as the second argument(default) + public TValue pop(TKey key, TValue default_value) { - T data; - if (TryGetValue(symbol, out data)) + TValue data; + if (TryGetValue(key, out data)) { - Remove(symbol); + Remove(key); return data; } return default_value; } /// - /// Updates the dictionary with the elements from the another dictionary object or from an iterable of Symbol/value pairs. - /// The update() method adds element(s) to the dictionary if the Symbol is not in the dictionary.If the Symbol is in the dictionary, it updates the Symbol with the new value. + /// Updates the dictionary with the elements from the another dictionary object or from an iterable of key/value pairs. + /// The update() method adds element(s) to the dictionary if the key is not in the dictionary.If the key is in the dictionary, it updates the key with the new value. /// - /// Takes either a dictionary or an iterable object of Symbol/value pairs (generally tuples). + /// Takes either a dictionary or an iterable object of key/value pairs (generally tuples). public void update(PyObject other) { if (IsReadOnly) @@ -324,7 +305,7 @@ public void update(PyObject other) throw new InvalidOperationException(Messages.ExtendedDictionary.UpdateInvalidOperation(this)); } - var dictionary = other.ConvertToDictionary(); + var dictionary = other.ConvertToDictionary(); foreach (var kvp in dictionary) { this[kvp.Key] = kvp.Value; @@ -332,9 +313,9 @@ public void update(PyObject other) } /// - /// Returns a view object that displays a list of all the Symbol objects in the dictionary + /// Returns a view object that displays a list of all the key objects in the dictionary /// - /// Returns a view object that displays a list of all the Symbol objects. + /// Returns a view object that displays a list of all the key objects. /// When the dictionary is changed, the view object also reflect these changes. public PyList keys() { @@ -349,5 +330,17 @@ public PyList values() { return GetValues.ToPyList(); } + + /// + /// Checks if the symbol is implicitly created from a string, in which case it is not in the symbol cache, + /// and throws a KeyNotFoundException. + /// + protected void CheckForImplicitlyCreatedSymbol(Symbol symbol) + { + if (symbol.ID == new SecurityIdentifier(symbol.ID.Symbol, 0)) + { + throw new KeyNotFoundException(Messages.ExtendedDictionary.TickerNotFoundInSymbolCache(symbol.ID.Symbol)); + } + } } } diff --git a/Common/Messages/Messages.QuantConnect.cs b/Common/Messages/Messages.QuantConnect.cs index 1d2a8bd8cf06..9456a74ae79d 100644 --- a/Common/Messages/Messages.QuantConnect.cs +++ b/Common/Messages/Messages.QuantConnect.cs @@ -160,7 +160,7 @@ public static class ExtendedDictionary /// is a read-only collection /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ClearInvalidOperation(ExtendedDictionary instance) + public static string ClearInvalidOperation(ExtendedDictionary instance) { return $"Clear/clear method call is an invalid operation. {instance.GetType().Name} is a read-only collection."; } @@ -170,7 +170,7 @@ public static string ClearInvalidOperation(ExtendedDictionary instance) /// is a read-only collection /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string RemoveInvalidOperation(ExtendedDictionary instance) + public static string RemoveInvalidOperation(ExtendedDictionary instance) { return $"Remove/pop method call is an invalid operation. {instance.GetType().Name} is a read-only collection."; } @@ -191,7 +191,7 @@ public static string TickerNotFoundInSymbolCache(string ticker) /// Returns a string message saying that the popitem method is not supported for the given instance /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string PopitemMethodNotSupported(ExtendedDictionary instance) + public static string PopitemMethodNotSupported(ExtendedDictionary instance) { return $"popitem method is not supported for {instance.GetType().Name}"; } @@ -201,11 +201,11 @@ public static string PopitemMethodNotSupported(ExtendedDictionary instance /// a recommendation for solving this problem /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SymbolNotFoundDueToNoData(ExtendedDictionary instance, QuantConnect.Symbol symbol) + public static string KeyNotFoundDueToNoData(ExtendedDictionary instance, TKey key) { - return $"'{symbol}' wasn't found in the {instance.GetType().Name} object, likely because there was no-data at this moment in " + + return $"'{key}' wasn't found in the {instance.GetType().Name} object, likely because there was no-data at this moment in " + "time and it wasn't possible to fillforward historical data. Please check the data exists before accessing it with " + - $"data.ContainsKey(\"{symbol}\"). The collection is read-only, cannot set default."; + $"data.ContainsKey(\"{key}\"). The collection is read-only, cannot set default."; } /// @@ -213,7 +213,7 @@ public static string SymbolNotFoundDueToNoData(ExtendedDictionary instance /// instance is a read-only collection /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string UpdateInvalidOperation(ExtendedDictionary instance) + public static string UpdateInvalidOperation(ExtendedDictionary instance) { return $"update method call is an invalid operation. {instance.GetType().Name} is a read-only collection."; } diff --git a/Common/QuantConnect.csproj b/Common/QuantConnect.csproj index 826d79c646b8..f58a3eeb3c76 100644 --- a/Common/QuantConnect.csproj +++ b/Common/QuantConnect.csproj @@ -35,7 +35,7 @@ - + diff --git a/Common/Securities/CashBook.cs b/Common/Securities/CashBook.cs index 6727d8b57f30..620950e03bac 100644 --- a/Common/Securities/CashBook.cs +++ b/Common/Securities/CashBook.cs @@ -29,7 +29,7 @@ namespace QuantConnect.Securities /// /// Provides a means of keeping track of the different cash holdings of an algorithm /// - public class CashBook : IDictionary, ICurrencyConverter + public class CashBook : ExtendedDictionary, IDictionary, ICurrencyConverter { private string _accountCurrency; @@ -209,7 +209,7 @@ public override string ToString() /// Gets the count of Cash items in this CashBook. /// /// The count. - public int Count + public override int Count { get { @@ -221,7 +221,7 @@ public int Count /// Gets a value indicating whether this instance is read only. /// /// true if this instance is read only; otherwise, false. - public bool IsReadOnly + public override bool IsReadOnly { get { return false; } } @@ -248,7 +248,7 @@ public void Add(string symbol, Cash value) /// /// Clear this instance of all Cash entries. /// - public void Clear() + public override void Clear() { _currencies = new(); OnUpdate(CashBookUpdateType.Removed, null); @@ -258,7 +258,7 @@ public void Clear() /// Remove the Cash item corresponding to the specified symbol /// /// The symbolto be removed - public bool Remove(string symbol) + public override bool Remove(string symbol) { return Remove(symbol, calledInternally: false); } @@ -277,7 +277,7 @@ public bool Remove(KeyValuePair item) /// /// true, if key was contained, false otherwise. /// Key. - public bool ContainsKey(string symbol) + public override bool ContainsKey(string symbol) { return _currencies.ContainsKey(symbol); } @@ -289,7 +289,7 @@ public bool ContainsKey(string symbol) /// true, if get value was tryed, false otherwise. /// The symbol. /// Value. - public bool TryGetValue(string symbol, out Cash value) + public override bool TryGetValue(string symbol, out Cash value) { return _currencies.TryGetValue(symbol, out value); } @@ -317,7 +317,7 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) /// Gets or sets the with the specified symbol. /// /// Symbol. - public Cash this[string symbol] + public override Cash this[string symbol] { get { @@ -350,6 +350,24 @@ public Cash this[string symbol] /// The values. public ICollection Values => _currencies.Values; + /// + /// Gets the keys. + /// + /// The keys. + protected override IEnumerable GetKeys => Keys; + + /// + /// Gets the values. + /// + /// The values. + protected override IEnumerable GetValues => Values; + + /// + /// Gets all the items in the dictionary + /// + /// All the items in the dictionary + public override IEnumerable> GetItems() => _currencies; + /// /// Gets the enumerator. /// diff --git a/Common/Securities/Positions/PositionGroupCollection.cs b/Common/Securities/Positions/PositionGroupCollection.cs index ae90511924f4..20ec848bcaa6 100644 --- a/Common/Securities/Positions/PositionGroupCollection.cs +++ b/Common/Securities/Positions/PositionGroupCollection.cs @@ -50,10 +50,22 @@ public bool IsOnlyDefaultGroups } } + /// + /// Gets the position groups keys in this collection + /// + public IReadOnlyCollection Keys => _groups.Keys; + + /// + /// Gets the position groups in this collection + /// + public IReadOnlyCollection Values => _groups.Values; + private bool? _hasNonDefaultGroups; private readonly Dictionary _groups; private readonly Dictionary> _groupsBySymbol; + internal IEnumerable> GetGroups() => _groups; + /// /// Initializes a new instance of the class /// diff --git a/Common/Securities/Positions/SecurityPositionGroupModel.cs b/Common/Securities/Positions/SecurityPositionGroupModel.cs index 20c8521ba129..2e26ad1d6744 100644 --- a/Common/Securities/Positions/SecurityPositionGroupModel.cs +++ b/Common/Securities/Positions/SecurityPositionGroupModel.cs @@ -24,7 +24,7 @@ namespace QuantConnect.Securities.Positions /// /// Responsible for managing the resolution of position groups for an algorithm /// - public class SecurityPositionGroupModel + public class SecurityPositionGroupModel : ExtendedDictionary { /// /// Gets an implementation of that will not group multiple securities @@ -42,7 +42,6 @@ public class SecurityPositionGroupModel /// protected virtual IPositionGroupBuyingPowerModel PositionGroupBuyingPowerModel { get; } = new SecurityPositionGroupBuyingPowerModel(); - /// /// Gets the set of currently resolved position groups /// @@ -64,6 +63,27 @@ private set /// public bool IsOnlyDefaultGroups => Groups.IsOnlyDefaultGroups; + /// + /// Gets the number of position groups in this collection + /// + public override int Count => Groups.Count; + + /// + /// Gets all the available position group keys + /// + protected override IEnumerable GetKeys => Groups.Keys; + + /// + /// Gets all the available position groups + /// + protected override IEnumerable GetValues => Groups.Values; + + /// + /// Gets all the items in the dictionary + /// + /// All the items in the dictionary + public override IEnumerable> GetItems() => Groups.GetGroups(); + /// /// Initializes a new instance of the class /// @@ -122,7 +142,11 @@ public virtual void Initialize(SecurityManager securities) /// Gets the matching the specified . If one is not found, /// then a new empty position group is returned. /// - public IPositionGroup this[PositionGroupKey key] => Groups[key]; + public override IPositionGroup this[PositionGroupKey key] + { + get => Groups[key]; + set => throw new NotImplementedException("Read-only collection. Cannot set value."); + } /// /// Creates a position group for the specified order, pulling @@ -213,5 +237,16 @@ private void ResolvePositionGroups() Groups = ResolvePositionGroups(positionsCollection); } } + + /// + /// Tries to get the position group matching the specified key + /// + /// The key to search for + /// The position group matching the specified key + /// True if a group with the specified key was found, false otherwise + public override bool TryGetValue(PositionGroupKey key, out IPositionGroup value) + { + return Groups.TryGetGroup(key, out value); + } } } diff --git a/Common/Securities/SecurityManager.cs b/Common/Securities/SecurityManager.cs index 13ad9b6cc519..eba37583b819 100644 --- a/Common/Securities/SecurityManager.cs +++ b/Common/Securities/SecurityManager.cs @@ -27,7 +27,7 @@ namespace QuantConnect.Securities /// Enumerable security management class for grouping security objects into an array and providing any common properties. /// /// Implements IDictionary for the index searching of securities by symbol - public class SecurityManager : ExtendedDictionary, IDictionary, INotifyCollectionChanged + public class SecurityManager : ExtendedDictionary, IDictionary, INotifyCollectionChanged { /// /// Event fired when a security is added or removed from this collection @@ -145,7 +145,7 @@ public bool Contains(KeyValuePair pair) /// Symbol we're checking for. /// IDictionary implementation /// Bool true if contains this symbol pair - public bool ContainsKey(Symbol symbol) + public override bool ContainsKey(Symbol symbol) { lock (_securityManager) { @@ -171,7 +171,7 @@ public void CopyTo(KeyValuePair[] array, int number) /// Count of the number of securities in the collection. /// /// IDictionary implementation - public int Count + public override int Count { get { @@ -332,7 +332,7 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumeratorImplementation(); } - private List>.Enumerator GetEnumeratorImplementation() + private List> GetEnumerable() { var result = _enumerator; if (result == null) @@ -342,9 +342,20 @@ private List>.Enumerator GetEnumeratorImplementat _enumerator = result = _securityManager.ToList(); } } - return result.GetEnumerator(); + return result; + } + + private List>.Enumerator GetEnumeratorImplementation() + { + return GetEnumerable().GetEnumerator(); } + /// + /// Gets all the items in the dictionary + /// + /// All the items in the dictionary + public override IEnumerable> GetItems() => GetEnumerable(); + /// /// Indexer method for the security manager to access the securities objects by their symbol. /// @@ -360,6 +371,7 @@ public override Security this[Symbol symbol] { if (!_completeSecuritiesCollection.TryGetValue(symbol, out security)) { + CheckForImplicitlyCreatedSymbol(symbol); throw new KeyNotFoundException(Messages.SecurityManager.SymbolNotFoundInSecurities(symbol)); } } diff --git a/Common/Securities/SecurityPortfolioManager.cs b/Common/Securities/SecurityPortfolioManager.cs index e0873175d639..2cbd6fee5b19 100644 --- a/Common/Securities/SecurityPortfolioManager.cs +++ b/Common/Securities/SecurityPortfolioManager.cs @@ -32,7 +32,7 @@ namespace QuantConnect.Securities /// Portfolio manager class groups popular properties and makes them accessible through one interface. /// It also provide indexing by the vehicle symbol to get the Security.Holding objects. /// - public class SecurityPortfolioManager : ExtendedDictionary, IDictionary, ISecurityProvider + public class SecurityPortfolioManager : ExtendedDictionary, IDictionary, ISecurityProvider { private Cash _baseCurrencyCash; private bool _setCashWasCalled; @@ -169,7 +169,7 @@ public SecurityPortfolioManager(SecurityManager securityManager, SecurityTransac /// /// String search symbol for the security /// Boolean true if portfolio contains this symbol - public bool ContainsKey(Symbol symbol) + public override bool ContainsKey(Symbol symbol) { return Securities.ContainsKey(symbol); } @@ -189,7 +189,7 @@ public bool Contains(KeyValuePair pair) /// Count the securities objects in the portfolio. /// /// IDictionary implementation calling the underlying Securities collection - public int Count + public override int Count { get { @@ -229,6 +229,13 @@ public void CopyTo(KeyValuePair[] array, int index) } } + /// + /// Gets all the items in the dictionary + /// + /// All the items in the dictionary + public override IEnumerable> GetItems() => + Securities.GetItems().Select(kvp => KeyValuePair.Create(kvp.Key, kvp.Value.Holdings)); + /// /// Gets an containing the Symbol objects of the . /// @@ -597,8 +604,14 @@ public decimal GetMarginRemaining(decimal totalPortfolioValue) /// SecurityHolding class from the algorithm securities public override SecurityHolding this[Symbol symbol] { - get { return Securities[symbol].Holdings; } - set { Securities[symbol].Holdings = value; } + get + { + return Securities[symbol].Holdings; + } + set + { + Securities[symbol].Holdings = value; + } } /// diff --git a/Engine/QuantConnect.Lean.Engine.csproj b/Engine/QuantConnect.Lean.Engine.csproj index 3f87eecfbf9b..b8fdc593b9a2 100644 --- a/Engine/QuantConnect.Lean.Engine.csproj +++ b/Engine/QuantConnect.Lean.Engine.csproj @@ -41,7 +41,7 @@ - + diff --git a/Indicators/QuantConnect.Indicators.csproj b/Indicators/QuantConnect.Indicators.csproj index e3e05e2ba5e8..f7d70dc72026 100644 --- a/Indicators/QuantConnect.Indicators.csproj +++ b/Indicators/QuantConnect.Indicators.csproj @@ -31,7 +31,7 @@ - + diff --git a/Report/QuantConnect.Report.csproj b/Report/QuantConnect.Report.csproj index e40043449b21..c4151f2ec9aa 100644 --- a/Report/QuantConnect.Report.csproj +++ b/Report/QuantConnect.Report.csproj @@ -39,7 +39,7 @@ LICENSE - + diff --git a/Research/QuantConnect.Research.csproj b/Research/QuantConnect.Research.csproj index cdab8dd9bdea..872bbcd1d67b 100644 --- a/Research/QuantConnect.Research.csproj +++ b/Research/QuantConnect.Research.csproj @@ -34,7 +34,7 @@ - + diff --git a/Tests/Common/ExtendedDictionaryTests.cs b/Tests/Common/ExtendedDictionaryTests.cs index 69b0b277d57d..cf9caf1afa5a 100644 --- a/Tests/Common/ExtendedDictionaryTests.cs +++ b/Tests/Common/ExtendedDictionaryTests.cs @@ -14,8 +14,10 @@ */ using NUnit.Framework; +using Python.Runtime; using QuantConnect.Statistics; using System.Collections.Generic; +using System.Linq; namespace QuantConnect.Tests.Common { @@ -58,5 +60,162 @@ public void RunPythonDictionaryFeatureRegressionAlgorithm() parameter.ExpectedFinalStatus, initialCash: 100000); } + + [Test] + public void ExtendedDictionaryBehavesAsPythonDictionary() + { + using var _ = Py.GIL(); + + var module = PyModule.FromString("ExtendedDictionaryBehavesAsPythonDictionary", + @" +def contains(dictionary, key): + return key in dictionary + +def get(dictionary, key): + return dictionary.get(key) + +def keys(dictionary): + return dictionary.keys() + +def values(dictionary): + return dictionary.values() + +def pop(dictionary, key): + return dictionary.pop(key) +"); + + var dict = new TestDictionary + { + ["a"] = 1, + ["b"] = 2, + ["c"] = 3 + }; + using var pyDict = dict.ToPython(); + + var expectedKeys = new[] { "a", "b", "c" }; + var keys = module.InvokeMethod("keys", pyDict).GetAndDispose>(); + CollectionAssert.AreEquivalent(expectedKeys, keys); + + var expectedValues = new[] { 1, 2, 3 }; + var values = module.InvokeMethod("values", pyDict).GetAndDispose>(); + CollectionAssert.AreEquivalent(expectedValues, values); + + foreach (var (key, value) in keys.Zip(values)) + { + using var pyKey = key.ToPython(); + Assert.IsTrue(module.InvokeMethod("contains", pyDict, pyKey).As()); + Assert.AreEqual(value, module.InvokeMethod("get", pyDict, pyKey).As()); + } + + using var pyNonExistingKey = "d".ToPython(); + Assert.IsFalse(module.InvokeMethod("contains", pyDict, pyNonExistingKey).As()); + Assert.IsFalse(module.InvokeMethod("contains", pyDict, PyObject.None).As()); + + using var pyExistingKey = keys[0].ToPython(); + using var pyExistingValue = values[0].ToPython(); + var popped = module.InvokeMethod("pop", pyDict, pyExistingKey).As(); + Assert.AreEqual(1, popped); + Assert.IsFalse(module.InvokeMethod("contains", pyDict, pyExistingKey).As()); + } + + [Test] + public void SymbolKeyCanBeIndexedWithStrings() + { + using var _ = Py.GIL(); + + var module = PyModule.FromString("SymbolKeyCanBeIndexedWithStrings", + @" +def get(dictionary, key): + return dictionary[key] + +def set(dictionary, key, value): + dictionary[key] = value +"); + + var symbol = Symbols.SPY; + using var pySymbol = symbol.ToPython(); + + SymbolCache.Set(symbol.Value, symbol); + + var dict = new TestDictionary + { + [symbol] = 1, + }; + using var pyDict = dict.ToPython(); + + var value = module.InvokeMethod("get", pyDict, pySymbol).As(); + Assert.AreEqual(1, value); + + using var pyStringSymbol = symbol.Value.ToPython(); + value = module.InvokeMethod("get", pyDict, pyStringSymbol).As(); + Assert.AreEqual(1, value); + + using var pyNewValue = 2.ToPython(); + module.InvokeMethod("set", pyDict, pySymbol, pyNewValue); + value = module.InvokeMethod("get", pyDict, pySymbol).As(); + Assert.AreEqual(2, value); + value = module.InvokeMethod("get", pyDict, pyStringSymbol).As(); + Assert.AreEqual(2, value); + + using var pyNewValue2 = 3.ToPython(); + module.InvokeMethod("set", pyDict, pyStringSymbol, pyNewValue2); + value = module.InvokeMethod("get", pyDict, pySymbol).As(); + Assert.AreEqual(3, value); + value = module.InvokeMethod("get", pyDict, pyStringSymbol).As(); + Assert.AreEqual(3, value); + + using var pyNonExistingSymbol = Symbols.AAPL.ToPython(); + using var pyStringNonExistingSymbol = Symbols.AAPL.Value.ToPython(); + + var exception = Assert.Throws(() => module.InvokeMethod("get", pyDict, pyNonExistingSymbol)); + Assert.IsInstanceOf(exception.InnerException); + + exception = Assert.Throws(() => module.InvokeMethod("get", pyDict, pyStringNonExistingSymbol)); + Assert.IsInstanceOf(exception.InnerException); + } + + private class TestDictionary : ExtendedDictionary + { + private readonly Dictionary _data = new(); + + public override int Count => _data.Count; + + public override bool IsReadOnly => false; + + public override TValue this[TKey key] + { + get => _data[key]; + set => _data[key] = value; + } + + protected override IEnumerable GetKeys => _data.Keys; + + protected override IEnumerable GetValues => _data.Values; + + public override bool TryGetValue(TKey key, out TValue value) + { + return _data.TryGetValue(key, out value); + } + + public override bool ContainsKey(TKey key) + { + return _data.ContainsKey(key); + } + + public override bool Remove(TKey key) + { + return _data.Remove(key); + } + + public override IEnumerable> GetItems() + { + return _data; + } + } + + private class TestSymbolDictionary : TestDictionary + { + + } } } diff --git a/Tests/QuantConnect.Tests.csproj b/Tests/QuantConnect.Tests.csproj index acb371684612..0f0762d3a859 100644 --- a/Tests/QuantConnect.Tests.csproj +++ b/Tests/QuantConnect.Tests.csproj @@ -31,7 +31,7 @@ - +