/* * 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; using System.Linq; using Python.Runtime; using System.Collections; using System.Collections.Generic; namespace QuantConnect.Algorithm.Framework.Alphas { /// /// Provides a collection for managing insights. This type provides collection access semantics /// as well as dictionary access semantics through TryGetValue, ContainsKey, and this[symbol] /// public class InsightCollection : IEnumerable { private int _totalInsightCount; private int _openInsightCount; private readonly List _insightsComplete = new(); private readonly Dictionary> _insights = new(); /// /// The open insight count /// public int Count { get { lock (_insights) { return _openInsightCount; } } } /// /// The total insight count /// public int TotalCount { get { lock (_insights) { return _totalInsightCount; } } } /// Adds an item to the . /// The object to add to the . /// The is read-only. public void Add(Insight item) { lock (_insights) { _openInsightCount++; _totalInsightCount++; _insightsComplete.Add(item); if (!_insights.TryGetValue(item.Symbol, out var existingInsights)) { _insights[item.Symbol] = existingInsights = new(); } existingInsights.Add(item); } } /// /// Adds each item in the specified enumerable of insights to this collection /// /// The insights to add to this collection public void AddRange(IEnumerable insights) { foreach (var insight in insights) { Add(insight); } } /// Determines whether the contains a specific value. /// true if is found in the ; otherwise, false. /// The object to locate in the . public bool Contains(Insight item) { lock(_insights) { return _insights.TryGetValue(item.Symbol, out var symbolInsights) && symbolInsights.Contains(item); } } /// /// Determines whether insights exist in this collection for the specified symbol /// /// The symbol key /// True if there are insights for the symbol in this collection public bool ContainsKey(Symbol symbol) { lock (_insights) { return _insights.TryGetValue(symbol, out var symbolInsights) && symbolInsights.Count > 0; } } /// Removes the first occurrence of a specific object from the . /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . /// The object to remove from the . /// The is read-only. public bool Remove(Insight item) { lock (_insights) { if (_insights.TryGetValue(item.Symbol, out var symbolInsights)) { if (symbolInsights.Remove(item)) { _openInsightCount--; // remove empty list from dictionary if (symbolInsights.Count == 0) { _insights.Remove(item.Symbol); } return true; } } } return false; } /// /// Dictionary accessor returns a list of insights for the specified symbol /// /// The symbol key /// List of insights for the symbol public List this[Symbol symbol] { get { lock(_insights) { return _insights[symbol]?.ToList(); } } set { lock (_insights) { if (_insights.TryGetValue(symbol, out var existingInsights)) { _openInsightCount -= existingInsights?.Count ?? 0; } if (value != null) { _openInsightCount += value.Count; _totalInsightCount += value.Count; } _insights[symbol] = value; } } } /// /// Attempts to get the list of insights with the specified symbol key /// /// The symbol key /// The insights for the specified symbol, or null if not found /// True if insights for the specified symbol were found, false otherwise public bool TryGetValue(Symbol symbol, out List insights) { lock (_insights) { var result = _insights.TryGetValue(symbol, out insights); if (result) { // for thread safety we need to return a copy of the collection insights = insights.ToList(); } return result; } } /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. /// 1 public IEnumerator GetEnumerator() { lock (_insights) { return _insights.SelectMany(kvp => kvp.Value).ToList().GetEnumerator(); } } /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. /// 2 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Removes the symbol and its insights /// /// List of symbols that will be removed public void Clear(Symbol[] symbols) { lock (_insights) { foreach (var symbol in symbols) { if (_insights.Remove(symbol, out var existingInsights)) { _openInsightCount -= existingInsights.Count; } } } } /// /// Gets the next expiry time UTC /// public DateTime? GetNextExpiryTime() { lock(_insights) { if (_openInsightCount == 0) { return null; } // we can't store expiration time because it can change return _insights.Min(x => x.Value.Min(i => i.CloseTimeUtc)); } } /// /// Gets the last generated active insight /// /// Collection of insights that are active public ICollection GetActiveInsights(DateTime utcTime) { var activeInsights = new List(); lock (_insights) { foreach (var kvp in _insights) { foreach (var insight in kvp.Value) { if (insight.IsActive(utcTime)) { activeInsights.Add(insight); } } } return activeInsights; } } /// /// Returns true if there are active insights for a given symbol and time /// /// The symbol key /// Time that determines whether the insight has expired /// public bool HasActiveInsights(Symbol symbol, DateTime utcTime) { lock (_insights) { if(_insights.TryGetValue(symbol, out var existingInsights)) { return existingInsights.Any(i => i.IsActive(utcTime)); } } return false; } /// /// Remove all expired insights from the collection and retuns them /// /// Time that determines whether the insight has expired /// Expired insights that were removed public ICollection RemoveExpiredInsights(DateTime utcTime) { var removedInsights = new List(); lock (_insights) { foreach (var kvp in _insights) { foreach (var insight in kvp.Value) { if (insight.IsExpired(utcTime)) { removedInsights.Add(insight); } } } foreach (var insight in removedInsights) { Remove(insight); } } return removedInsights; } /// /// Will remove insights from the complete insight collection /// /// The function that will determine which insight to remove public void RemoveInsights(Func filter) { lock (_insights) { _insightsComplete.RemoveAll(insight => filter(insight)); // for consistentcy remove from open insights just in case List insightsToRemove = null; foreach (var insights in _insights.Values) { foreach (var insight in insights) { if (filter(insight)) { insightsToRemove ??= new (); insightsToRemove.Add(insight); } } } if(insightsToRemove != null) { foreach (var insight in insightsToRemove) { Remove(insight); } } } } /// /// Will return insights from the complete insight collection /// /// The function that will determine which insight to return /// A new list containing the selected insights public List GetInsights(Func filter = null) { lock (_insights) { if(filter == null) { return _insightsComplete.ToList(); } return _insightsComplete.Where(filter).ToList(); } } /// /// Will return insights from the complete insight collection /// /// The function that will determine which insight to return /// A new list containing the selected insights public List GetInsights(PyObject filter) { Func convertedFilter; if (filter.TryConvertToDelegate(out convertedFilter)) { return GetInsights(convertedFilter); } else { using (Py.GIL()) { throw new ArgumentException($"InsightCollection.GetInsights: {filter.Repr()} is not a valid argument."); } } } } }