/*
* 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.");
}
}
}
}
}