/*
* 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.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Python.Runtime;
using QuantConnect.Data.Custom.IconicTypes;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Python;
namespace QuantConnect.Data
{
///
/// Provides a data structure for all of an algorithm's data at a single time step
///
public class Slice : ExtendedDictionary, IEnumerable>
{
private Ticks _ticks;
private TradeBars _bars;
private QuoteBars _quoteBars;
private OptionChains _optionChains;
private FuturesChains _futuresChains;
// aux data
private Splits _splits;
private Dividends _dividends;
private Delistings _delistings;
private SymbolChangedEvents _symbolChangedEvents;
private MarginInterestRates _marginInterestRates;
// string -> data for non-tick data
// string -> list{data} for tick data
private Lazy> _data;
// UnlinkedData -> DataDictonary
private Dictionary _dataByType;
///
/// All the data hold in this slice
///
public List AllData { get; private set; }
///
/// Gets the timestamp for this slice of data
///
public DateTime Time
{
get; private set;
}
///
/// Gets the timestamp for this slice of data in UTC
///
public DateTime UtcTime
{
get; private set;
}
///
/// Gets whether or not this slice has data
///
public bool HasData
{
get; private set;
}
///
/// Gets the for this slice of data
///
public TradeBars Bars
{
get { return _bars; }
}
///
/// Gets the for this slice of data
///
public QuoteBars QuoteBars
{
get { return _quoteBars; }
}
///
/// Gets the for this slice of data
///
public Ticks Ticks
{
get { return _ticks; }
}
///
/// Gets the for this slice of data
///
public OptionChains OptionChains
{
get { return _optionChains; }
}
///
/// Gets the for this slice of data
///
public FuturesChains FuturesChains
{
get { return _futuresChains; }
}
///
/// Gets the for this slice of data
///
public FuturesChains FutureChains
{
get { return _futuresChains; }
}
///
/// Gets the for this slice of data
///
public Splits Splits
{
get { return _splits; }
}
///
/// Gets the for this slice of data
///
public Dividends Dividends
{
get { return _dividends; }
}
///
/// Gets the for this slice of data
///
public Delistings Delistings
{
get { return _delistings; }
}
///
/// Gets the for this slice of data
///
public SymbolChangedEvents SymbolChangedEvents
{
get { return _symbolChangedEvents; }
}
///
/// Gets the for this slice of data
///
public MarginInterestRates MarginInterestRates
{
get { return _marginInterestRates; }
}
///
/// Gets the number of symbols held in this slice
///
public override int Count
{
get { return _data.Value.Count; }
}
///
/// Gets all the symbols in this slice
///
public virtual IReadOnlyList Keys
{
get { return new List(_data.Value.Keys); }
}
///
/// Gets an containing the Symbol objects of the .
///
///
/// An containing the Symbol objects of the object that implements .
///
protected override IEnumerable GetKeys => _data.Value.Keys;
///
/// Gets an containing the values in the .
///
///
/// An containing the values in the object that implements .
///
protected override IEnumerable GetValues => GetKeyValuePairEnumerable().Select(data => (dynamic)data.Value);
///
/// Gets a list of all the data in this slice
///
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
/// collections on demand
///
/// The timestamp for this slice of data
/// The raw data in this slice
/// The timestamp for this slice of data in UTC
public Slice(DateTime time, IEnumerable data, DateTime utcTime)
: this(time, data.ToList(), utcTime: utcTime)
{
}
///
/// Initializes a new instance of the class, lazily
/// instantiating the and
/// collections on demand
///
/// The timestamp for this slice of data
/// The raw data in this slice
/// The timestamp for this slice of data in UTC
public Slice(DateTime time, List data, DateTime utcTime)
: this(time, data, CreateCollection(time, data),
CreateCollection(time, data),
CreateTicksCollection(time, data),
CreateCollection(time, data),
CreateCollection(time, data),
CreateCollection(time, data),
CreateCollection(time, data),
CreateCollection(time, data),
CreateCollection(time, data),
CreateCollection(time, data),
utcTime: utcTime)
{
}
///
/// Initializes a new instance used by the
///
/// slice object to wrap
/// This is required so that python slice enumeration works correctly since it relies on the private collection
protected Slice(Slice slice)
{
Time = slice.Time;
UtcTime = slice.UtcTime;
AllData = slice.AllData;
_dataByType = slice._dataByType;
_data = slice._data;
HasData = slice.HasData;
_ticks = slice._ticks;
_bars = slice._bars;
_quoteBars = slice._quoteBars;
_optionChains = slice._optionChains;
_futuresChains = slice._futuresChains;
// auxiliary data
_splits = slice._splits;
_dividends = slice._dividends;
_delistings = slice._delistings;
_symbolChangedEvents = slice._symbolChangedEvents;
_marginInterestRates = slice._marginInterestRates;
}
///
/// Initializes a new instance of the class
///
/// The timestamp for this slice of data
/// The raw data in this slice
/// The trade bars for this slice
/// The quote bars for this slice
/// This ticks for this slice
/// The option chains for this slice
/// The futures chains for this slice
/// The splits for this slice
/// The dividends for this slice
/// The delistings for this slice
/// The symbol changed events for this slice
/// The margin interest rates for this slice
/// The timestamp for this slice of data in UTC
/// true if this slice contains data
public Slice(DateTime time, List data, TradeBars tradeBars, QuoteBars quoteBars, Ticks ticks, OptionChains optionChains, FuturesChains futuresChains, Splits splits, Dividends dividends, Delistings delistings, SymbolChangedEvents symbolChanges, MarginInterestRates marginInterestRates, DateTime utcTime, bool? hasData = null)
{
Time = time;
UtcTime = utcTime;
AllData = data;
// market data
_data = new Lazy>(() => CreateDynamicDataDictionary(AllData));
HasData = hasData ?? _data.Value.Count > 0;
_ticks = ticks;
_bars = tradeBars;
_quoteBars = quoteBars;
_optionChains = optionChains;
_futuresChains = futuresChains;
// auxiliary data
_splits = splits;
_dividends = dividends;
_delistings = delistings;
_symbolChangedEvents = symbolChanges;
_marginInterestRates = marginInterestRates;
}
///
/// Gets the data corresponding to the specified symbol. If the requested data
/// is of , then a will
/// be returned, otherwise, it will be the subscribed type, for example,
/// or event for custom data.
///
/// The data's symbols
/// The data for the specified symbol
public override dynamic this[Symbol symbol]
{
get
{
SymbolData value;
if (_data.Value.TryGetValue(symbol, out value))
{
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.");
}
}
///
/// Gets the for all data of the specified type
///
/// The type of data we want, for example, or , etc...
/// The containing the data of the specified type
public DataDictionary Get()
where T : IBaseData
{
return GetImpl(typeof(T));
}
///
/// Gets the data of the specified type.
///
/// The type of data we seek
/// The instance for the requested type
public dynamic Get(Type type)
{
return GetImpl(type);
}
///
/// Gets the data of the specified symbol and type.
///
/// The type of data we seek
/// The specific symbol was seek
/// The data point for the requested symbol
public PyObject Get(PyObject type, Symbol symbol)
{
using var _ = Py.GIL();
var datapoint = (object)GetImpl(type.CreateType())[symbol];
return datapoint.ToPython();
}
///
/// Gets the data of the specified data type.
///
/// The type of data we seek
/// The data dictionary for the requested data type
public PyObject Get(PyObject type)
{
using var _ = Py.GIL();
var dataDictionary = (object)GetImpl(type.CreateType());
return dataDictionary.ToPython();
}
///
/// Gets the data of the specified type.
///
/// Supports both C# and Python use cases
private dynamic GetImpl(Type type)
{
if (type == typeof(Fundamentals))
{
// backwards compatibility for users doing a get of Fundamentals type
type = typeof(FundamentalUniverse);
}
else if (type == typeof(ETFConstituentData))
{
// backwards compatibility for users doing a get of ETFConstituentData type
type = typeof(ETFConstituentUniverse);
}
if (_dataByType == null)
{
// for performance we only really create this collection if someone used it
_dataByType = new Dictionary(1);
}
object dictionary;
if (!_dataByType.TryGetValue(type, out dictionary))
{
var requestedOpenInterest = type == typeof(OpenInterest);
if (type == typeof(Tick) || requestedOpenInterest)
{
var dataDictionaryCache = GenericDataDictionary.Get(type, isPythonData: false);
dictionary = Activator.CreateInstance(dataDictionaryCache.GenericType);
((dynamic)dictionary).Time = Time;
foreach (var data in Ticks)
{
var symbol = data.Key;
// preserving existing behavior we will return the last data point, users expect a 'DataDictionary : IDictionary'.
// openInterest is stored with the Ticks collection
var lastDataPoint = data.Value.LastOrDefault(tick => requestedOpenInterest && tick.TickType == TickType.OpenInterest || !requestedOpenInterest && tick.TickType != TickType.OpenInterest);
if (lastDataPoint == null)
{
continue;
}
dataDictionaryCache.MethodInfo.Invoke(dictionary, new object[] { symbol, lastDataPoint });
}
}
else if (type == typeof(TradeBar))
{
dictionary = Bars;
}
else if (type == typeof(QuoteBar))
{
dictionary = QuoteBars;
}
else if (type == typeof(Delisting))
{
dictionary = Delistings;
}
else if (type == typeof(Split))
{
dictionary = Splits;
}
else if (type == typeof(OptionChain))
{
dictionary = OptionChains;
}
else if (type == typeof(FuturesChain))
{
dictionary = FuturesChains;
}
else if (type == typeof(Dividend))
{
dictionary = Dividends;
}
else if (type == typeof(SymbolChangedEvent))
{
dictionary = SymbolChangedEvents;
}
else if (type == typeof(MarginInterestRate))
{
dictionary = MarginInterestRates;
}
else
{
var isPythonData = type.IsAssignableTo(typeof(PythonData));
var dataDictionaryCache = GenericDataDictionary.Get(type, isPythonData);
dictionary = Activator.CreateInstance(dataDictionaryCache.GenericType);
((dynamic)dictionary).Time = Time;
foreach (var data in _data.Value.Values)
{
// let's first check custom data, else double check the user isn't requesting auxiliary data we have
if (IsDataPointOfType(data.Custom, type, isPythonData))
{
dataDictionaryCache.MethodInfo.Invoke(dictionary, new object[] { data.Symbol, data.Custom });
}
else
{
foreach (var auxiliaryData in data.AuxilliaryData.Where(x => IsDataPointOfType(x, type, isPythonData)))
{
dataDictionaryCache.MethodInfo.Invoke(dictionary, new object[] { data.Symbol, auxiliaryData });
}
}
}
}
_dataByType[type] = dictionary;
}
return dictionary;
}
///
/// Gets the data of the specified symbol and type.
///
/// The type of data we seek
/// The specific symbol was seek
/// The data for the requested symbol
public T Get(Symbol symbol)
where T : BaseData
{
return Get()[symbol];
}
///
/// Determines whether this instance contains data for the specified symbol
///
/// The symbol we seek data for
/// True if this instance contains data for the symbol, false otherwise
public override bool ContainsKey(Symbol symbol)
{
return _data.Value.ContainsKey(symbol);
}
///
/// Gets the data associated with the specified symbol
///
/// The symbol we want data for
/// The data for the specifed symbol, or null if no data was found
/// True if data was found, false otherwise
public override bool TryGetValue(Symbol symbol, out dynamic data)
{
data = null;
SymbolData symbolData;
if (_data.Value.TryGetValue(symbol, out symbolData))
{
data = symbolData.GetData();
return data != null;
}
return false;
}
///
/// Merge two slice with same Time
///
/// slice instance
/// Will change the input collection for re-use
public void MergeSlice(Slice inputSlice)
{
if (UtcTime != inputSlice.UtcTime)
{
throw new InvalidOperationException($"Slice with time {UtcTime} can't be merged with given slice with different {inputSlice.UtcTime}");
}
_bars = (TradeBars)UpdateCollection(_bars, inputSlice.Bars);
_quoteBars = (QuoteBars)UpdateCollection(_quoteBars, inputSlice.QuoteBars);
_ticks = (Ticks)UpdateCollection(_ticks, inputSlice.Ticks);
_optionChains = (OptionChains)UpdateCollection(_optionChains, inputSlice.OptionChains);
_futuresChains = (FuturesChains)UpdateCollection(_futuresChains, inputSlice.FuturesChains);
_splits = (Splits)UpdateCollection(_splits, inputSlice.Splits);
_dividends = (Dividends)UpdateCollection(_dividends, inputSlice.Dividends);
_delistings = (Delistings)UpdateCollection(_delistings, inputSlice.Delistings);
_symbolChangedEvents = (SymbolChangedEvents)UpdateCollection(_symbolChangedEvents, inputSlice.SymbolChangedEvents);
_marginInterestRates = (MarginInterestRates)UpdateCollection(_marginInterestRates, inputSlice.MarginInterestRates);
if (inputSlice.AllData.Count != 0)
{
if (AllData.Count == 0)
{
AllData = inputSlice.AllData;
_data = inputSlice._data;
}
else
{
// Should keep this._rawDataList last so that selected data points are not overriden
// while creating _data
inputSlice.AllData.AddRange(AllData);
AllData = inputSlice.AllData;
_data = new Lazy>(() => CreateDynamicDataDictionary(AllData));
}
}
}
private static DataDictionary UpdateCollection(DataDictionary baseCollection, DataDictionary inputCollection)
{
if (baseCollection == null || baseCollection.Count == 0)
{
return inputCollection;
}
if (inputCollection?.Count > 0)
{
foreach (var kvp in inputCollection)
{
if (!baseCollection.ContainsKey(kvp.Key))
{
baseCollection.Add(kvp.Key, kvp.Value);
}
}
}
return baseCollection;
}
///
/// Produces the dynamic data dictionary from the input data
///
private static DataDictionary CreateDynamicDataDictionary(IEnumerable data)
{
var allData = new DataDictionary();
foreach (var datum in data)
{
// we only will cache the default data type to preserve determinism and backwards compatibility
if (!SubscriptionManager.IsDefaultDataType(datum))
{
continue;
}
SymbolData symbolData;
if (!allData.TryGetValue(datum.Symbol, out symbolData))
{
symbolData = new SymbolData(datum.Symbol);
allData[datum.Symbol] = symbolData;
}
switch (datum.DataType)
{
case MarketDataType.Base:
symbolData.Type = SubscriptionType.Custom;
symbolData.Custom = datum;
break;
case MarketDataType.TradeBar:
symbolData.Type = SubscriptionType.TradeBar;
symbolData.TradeBar = (TradeBar)datum;
break;
case MarketDataType.QuoteBar:
symbolData.Type = SubscriptionType.QuoteBar;
symbolData.QuoteBar = (QuoteBar)datum;
break;
case MarketDataType.Tick:
symbolData.Type = SubscriptionType.Tick;
symbolData.Ticks.Add((Tick)datum);
break;
case MarketDataType.Auxiliary:
symbolData.AuxilliaryData.Add(datum);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
return allData;
}
///
/// Dynamically produces a data dictionary using the provided data
///
private static Ticks CreateTicksCollection(DateTime time, IEnumerable data)
{
var ticks = new Ticks(time);
foreach (var tick in data.OfType())
{
List listTicks;
if (!ticks.TryGetValue(tick.Symbol, out listTicks))
{
ticks[tick.Symbol] = listTicks = new List();
}
listTicks.Add(tick);
}
return ticks;
}
///
/// Dynamically produces a data dictionary for the requested type using the provided data
///
/// The data dictionary type
/// The item type of the data dictionary
/// The current slice time
/// The data to create the collection
/// The data dictionary of containing all the data of that type in this slice
private static T CreateCollection(DateTime time, IEnumerable data)
where T : DataDictionary, new()
where TItem : BaseData
{
var collection = new T
{
#pragma warning disable 618 // This assignment is left here until the Time property is removed.
Time = time
#pragma warning restore 618
};
foreach (var item in data.OfType())
{
collection[item.Symbol] = item;
}
return collection;
}
///
/// Returns an enumerator that iterates through the collection.
///
///
/// A that can be used to iterate through the collection.
///
/// 1
public IEnumerator> GetEnumerator()
{
return GetKeyValuePairEnumerable().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();
}
private IEnumerable> GetKeyValuePairEnumerable()
{
// this will not enumerate auxilliary data!
foreach (var kvp in _data.Value)
{
var data = kvp.Value.GetData();
var dataPoints = data as IEnumerable;
if (dataPoints != null)
{
foreach (var dataPoint in dataPoints)
{
yield return new KeyValuePair(kvp.Key, dataPoint);
}
}
else if (data != null)
{
yield return new KeyValuePair(kvp.Key, data);
}
}
}
///
/// Determines if the given data point is of a specific type
///
private static bool IsDataPointOfType(BaseData o, Type type, bool isPythonData)
{
if (o == null)
{
return false;
}
if (isPythonData && o is PythonData data)
{
return data.IsOfType(type);
}
return o.GetType() == type;
}
private enum SubscriptionType { TradeBar, QuoteBar, Tick, Custom };
private class SymbolData
{
public SubscriptionType Type;
public readonly Symbol Symbol;
// data
public BaseData Custom;
public TradeBar TradeBar;
public QuoteBar QuoteBar;
public readonly List Ticks;
public readonly List AuxilliaryData;
public SymbolData(Symbol symbol)
{
Symbol = symbol;
Ticks = new List();
AuxilliaryData = new List();
}
public dynamic GetData()
{
switch (Type)
{
case SubscriptionType.TradeBar:
return TradeBar;
case SubscriptionType.QuoteBar:
return QuoteBar;
case SubscriptionType.Tick:
return Ticks;
case SubscriptionType.Custom:
return Custom;
default:
throw new ArgumentOutOfRangeException();
}
}
}
///
/// Helper class for generic
///
/// The value of this class is primarily performance since it keeps a cache
/// of the generic types instances and there add methods.
private class GenericDataDictionary
{
private static Dictionary _genericCache = new Dictionary();
///
/// The method
///
public MethodInfo MethodInfo { get; }
///
/// The type
///
public Type GenericType { get; }
private GenericDataDictionary(Type genericType, MethodInfo methodInfo)
{
GenericType = genericType;
MethodInfo = methodInfo;
}
///
/// Provides a instance for a given
///
/// The requested data type
/// True if data is of type
/// A new instance or retrieved from the cache
public static GenericDataDictionary Get(Type type, bool isPythonData)
{
if (!_genericCache.TryGetValue(type, out var dataDictionaryCache))
{
var dictionaryType = type;
if (isPythonData)
{
// let's create a python data dictionary because the data itself will be a PythonData type in C#
dictionaryType = typeof(PythonData);
}
var generic = typeof(DataDictionary<>).MakeGenericType(dictionaryType);
var method = generic.GetMethod("Add", new[] { typeof(Symbol), dictionaryType });
// Replace the cache instance with a new one instead of locking in order to avoid the overhead
var temp = new Dictionary(_genericCache);
temp[type] = dataDictionaryCache = new GenericDataDictionary(generic, method);
_genericCache = temp;
}
return dataDictionaryCache;
}
}
}
}