Lean  $LEAN_TAG$
InsightCollection.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Linq;
18 using Python.Runtime;
19 using System.Collections;
20 using System.Collections.Generic;
21 
23 {
24  /// <summary>
25  /// Provides a collection for managing insights. This type provides collection access semantics
26  /// as well as dictionary access semantics through TryGetValue, ContainsKey, and this[symbol]
27  /// </summary>
28  public class InsightCollection : IEnumerable<Insight>
29  {
30  private int _totalInsightCount;
31  private int _openInsightCount;
32  private readonly List<Insight> _insightsComplete = new();
33  private readonly Dictionary<Symbol, List<Insight>> _insights = new();
34 
35  /// <summary>
36  /// The open insight count
37  /// </summary>
38  public int Count
39  {
40  get
41  {
42  lock (_insights)
43  {
44  return _openInsightCount;
45  }
46  }
47  }
48 
49  /// <summary>
50  /// The total insight count
51  /// </summary>
52  public int TotalCount
53  {
54  get
55  {
56  lock (_insights)
57  {
58  return _totalInsightCount;
59  }
60  }
61  }
62 
63  /// <summary>Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
64  /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
65  /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
66  public void Add(Insight item)
67  {
68  lock (_insights)
69  {
70  _openInsightCount++;
71  _totalInsightCount++;
72 
73  _insightsComplete.Add(item);
74 
75  if (!_insights.TryGetValue(item.Symbol, out var existingInsights))
76  {
77  _insights[item.Symbol] = existingInsights = new();
78  }
79  existingInsights.Add(item);
80  }
81  }
82 
83  /// <summary>
84  /// Adds each item in the specified enumerable of insights to this collection
85  /// </summary>
86  /// <param name="insights">The insights to add to this collection</param>
87  public void AddRange(IEnumerable<Insight> insights)
88  {
89  foreach (var insight in insights)
90  {
91  Add(insight);
92  }
93  }
94 
95  /// <summary>Determines whether the <see cref="T:System.Collections.Generic.ICollection`1" /> contains a specific value.</summary>
96  /// <returns>true if <paramref name="item" /> is found in the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, false.</returns>
97  /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
98  public bool Contains(Insight item)
99  {
100  lock(_insights)
101  {
102  return _insights.TryGetValue(item.Symbol, out var symbolInsights)
103  && symbolInsights.Contains(item);
104  }
105  }
106 
107  /// <summary>
108  /// Determines whether insights exist in this collection for the specified symbol
109  /// </summary>
110  /// <param name="symbol">The symbol key</param>
111  /// <returns>True if there are insights for the symbol in this collection</returns>
112  public bool ContainsKey(Symbol symbol)
113  {
114  lock (_insights)
115  {
116  return _insights.TryGetValue(symbol, out var symbolInsights)
117  && symbolInsights.Count > 0;
118  }
119  }
120 
121  /// <summary>Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
122  /// <returns>true if <paramref name="item" /> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, false. This method also returns false if <paramref name="item" /> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
123  /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
124  /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
125  public bool Remove(Insight item)
126  {
127  lock (_insights)
128  {
129  if (_insights.TryGetValue(item.Symbol, out var symbolInsights))
130  {
131  if (symbolInsights.Remove(item))
132  {
133  _openInsightCount--;
134 
135  // remove empty list from dictionary
136  if (symbolInsights.Count == 0)
137  {
138  _insights.Remove(item.Symbol);
139  }
140  return true;
141  }
142  }
143  }
144 
145  return false;
146  }
147 
148  /// <summary>
149  /// Dictionary accessor returns a list of insights for the specified symbol
150  /// </summary>
151  /// <param name="symbol">The symbol key</param>
152  /// <returns>List of insights for the symbol</returns>
153  public List<Insight> this[Symbol symbol]
154  {
155  get
156  {
157  lock(_insights)
158  {
159  return _insights[symbol]?.ToList();
160  }
161  }
162  set
163  {
164  lock (_insights)
165  {
166  if (_insights.TryGetValue(symbol, out var existingInsights))
167  {
168  _openInsightCount -= existingInsights?.Count ?? 0;
169  }
170 
171  if (value != null)
172  {
173  _openInsightCount += value.Count;
174  _totalInsightCount += value.Count;
175  }
176  _insights[symbol] = value;
177  }
178  }
179  }
180 
181  /// <summary>
182  /// Attempts to get the list of insights with the specified symbol key
183  /// </summary>
184  /// <param name="symbol">The symbol key</param>
185  /// <param name="insights">The insights for the specified symbol, or null if not found</param>
186  /// <returns>True if insights for the specified symbol were found, false otherwise</returns>
187  public bool TryGetValue(Symbol symbol, out List<Insight> insights)
188  {
189  lock (_insights)
190  {
191  var result = _insights.TryGetValue(symbol, out insights);
192  if (result)
193  {
194  // for thread safety we need to return a copy of the collection
195  insights = insights.ToList();
196  }
197  return result;
198  }
199  }
200 
201  /// <summary>Returns an enumerator that iterates through the collection.</summary>
202  /// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.</returns>
203  /// <filterpriority>1</filterpriority>
204  public IEnumerator<Insight> GetEnumerator()
205  {
206  lock (_insights)
207  {
208  return _insights.SelectMany(kvp => kvp.Value).ToList().GetEnumerator();
209  }
210  }
211 
212  /// <summary>Returns an enumerator that iterates through a collection.</summary>
213  /// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
214  /// <filterpriority>2</filterpriority>
215  IEnumerator IEnumerable.GetEnumerator()
216  {
217  return GetEnumerator();
218  }
219 
220  /// <summary>
221  /// Removes the symbol and its insights
222  /// </summary>
223  /// <param name="symbols">List of symbols that will be removed</param>
224  public void Clear(Symbol[] symbols)
225  {
226  lock (_insights)
227  {
228  foreach (var symbol in symbols)
229  {
230  if (_insights.Remove(symbol, out var existingInsights))
231  {
232  _openInsightCount -= existingInsights.Count;
233  }
234  }
235  }
236  }
237 
238  /// <summary>
239  /// Gets the next expiry time UTC
240  /// </summary>
241  public DateTime? GetNextExpiryTime()
242  {
243  lock(_insights)
244  {
245  if (_openInsightCount == 0)
246  {
247  return null;
248  }
249 
250  // we can't store expiration time because it can change
251  return _insights.Min(x => x.Value.Min(i => i.CloseTimeUtc));
252  }
253  }
254 
255  /// <summary>
256  /// Gets the last generated active insight
257  /// </summary>
258  /// <returns>Collection of insights that are active</returns>
259  public ICollection<Insight> GetActiveInsights(DateTime utcTime)
260  {
261  var activeInsights = new List<Insight>();
262  lock (_insights)
263  {
264  foreach (var kvp in _insights)
265  {
266  foreach (var insight in kvp.Value)
267  {
268  if (insight.IsActive(utcTime))
269  {
270  activeInsights.Add(insight);
271  }
272  }
273  }
274  return activeInsights;
275  }
276  }
277 
278  /// <summary>
279  /// Returns true if there are active insights for a given symbol and time
280  /// </summary>
281  /// <param name="symbol">The symbol key</param>
282  /// <param name="utcTime">Time that determines whether the insight has expired</param>
283  /// <returns></returns>
284  public bool HasActiveInsights(Symbol symbol, DateTime utcTime)
285  {
286  lock (_insights)
287  {
288  if(_insights.TryGetValue(symbol, out var existingInsights))
289  {
290  return existingInsights.Any(i => i.IsActive(utcTime));
291  }
292  }
293  return false;
294  }
295 
296  /// <summary>
297  /// Remove all expired insights from the collection and retuns them
298  /// </summary>
299  /// <param name="utcTime">Time that determines whether the insight has expired</param>
300  /// <returns>Expired insights that were removed</returns>
301  public ICollection<Insight> RemoveExpiredInsights(DateTime utcTime)
302  {
303  var removedInsights = new List<Insight>();
304  lock (_insights)
305  {
306  foreach (var kvp in _insights)
307  {
308  foreach (var insight in kvp.Value)
309  {
310  if (insight.IsExpired(utcTime))
311  {
312  removedInsights.Add(insight);
313  }
314  }
315  }
316  foreach (var insight in removedInsights)
317  {
318  Remove(insight);
319  }
320  }
321  return removedInsights;
322  }
323 
324  /// <summary>
325  /// Will remove insights from the complete insight collection
326  /// </summary>
327  /// <param name="filter">The function that will determine which insight to remove</param>
328  public void RemoveInsights(Func<Insight, bool> filter)
329  {
330  lock (_insights)
331  {
332  _insightsComplete.RemoveAll(insight => filter(insight));
333 
334  // for consistentcy remove from open insights just in case
335  List<Insight> insightsToRemove = null;
336  foreach (var insights in _insights.Values)
337  {
338  foreach (var insight in insights)
339  {
340  if (filter(insight))
341  {
342  insightsToRemove ??= new ();
343  insightsToRemove.Add(insight);
344  }
345  }
346  }
347  if(insightsToRemove != null)
348  {
349  foreach (var insight in insightsToRemove)
350  {
351  Remove(insight);
352  }
353  }
354  }
355  }
356 
357  /// <summary>
358  /// Will return insights from the complete insight collection
359  /// </summary>
360  /// <param name="filter">The function that will determine which insight to return</param>
361  /// <returns>A new list containing the selected insights</returns>
362  public List<Insight> GetInsights(Func<Insight, bool> filter = null)
363  {
364  lock (_insights)
365  {
366  if(filter == null)
367  {
368  return _insightsComplete.ToList();
369  }
370  return _insightsComplete.Where(filter).ToList();
371  }
372  }
373 
374  /// <summary>
375  /// Will return insights from the complete insight collection
376  /// </summary>
377  /// <param name="filter">The function that will determine which insight to return</param>
378  /// <returns>A new list containing the selected insights</returns>
379  public List<Insight> GetInsights(PyObject filter)
380  {
381  Func<Insight, bool> convertedFilter;
382  if (filter.TryConvertToDelegate(out convertedFilter))
383  {
384  return GetInsights(convertedFilter);
385  }
386  else
387  {
388  using (Py.GIL())
389  {
390  throw new ArgumentException($"InsightCollection.GetInsights: {filter.Repr()} is not a valid argument.");
391  }
392  }
393  }
394  }
395 }