/* * 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.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using QuantConnect.Data; using QuantConnect.Data.Market; using QuantConnect.Util; namespace QuantConnect.Securities { /// /// Base class caching caching spot for security data and any other temporary properties. /// /// /// This class is virtually unused and will soon be made obsolete. /// This comment made in a remark to prevent obsolete errors in all users algorithms /// public class SecurityCache { // this is used to prefer quote bar data over the tradebar data private DateTime _lastQuoteBarUpdate; private DateTime _lastOHLCUpdate; private BaseData _lastData; private IReadOnlyList _lastTickQuotes = new List(); private IReadOnlyList _lastTickTrades = new List(); private ConcurrentDictionary> _dataByType = new ConcurrentDictionary>(); /// /// Gets the most recent price submitted to this cache /// public decimal Price { get; private set; } /// /// Gets the most recent open submitted to this cache /// public decimal Open { get; private set; } /// /// Gets the most recent high submitted to this cache /// public decimal High { get; private set; } /// /// Gets the most recent low submitted to this cache /// public decimal Low { get; private set; } /// /// Gets the most recent close submitted to this cache /// public decimal Close { get; private set; } /// /// Gets the most recent bid submitted to this cache /// public decimal BidPrice { get; private set; } /// /// Gets the most recent ask submitted to this cache /// public decimal AskPrice { get; private set; } /// /// Gets the most recent bid size submitted to this cache /// public decimal BidSize { get; private set; } /// /// Gets the most recent ask size submitted to this cache /// public decimal AskSize { get; private set; } /// /// Gets the most recent volume submitted to this cache /// public decimal Volume { get; private set; } /// /// Gets the most recent open interest submitted to this cache /// public long OpenInterest { get; private set; } /// /// Add a list of market data points to the local security cache for the current market price. /// /// Internally uses using the last data point of the provided list /// and it stores by type the non fill forward points using public void AddDataList(IReadOnlyList data, Type dataType, bool? containsFillForwardData = null) { var nonFillForwardData = data; // maintaining regression requires us to NOT cache FF data if (containsFillForwardData != false) { var dataFiltered = new List(data.Count); for (var i = 0; i < data.Count; i++) { var dataPoint = data[i]; if (!dataPoint.IsFillForward) { dataFiltered.Add(dataPoint); } } nonFillForwardData = dataFiltered; } if (nonFillForwardData.Count != 0) { StoreData(nonFillForwardData, dataType); } else if (dataType == typeof(OpenInterest)) { StoreData(data, typeof(OpenInterest)); } var last = data[data.Count - 1]; AddDataImpl(last, cacheByType: false); } /// /// Add a new market data point to the local security cache for the current market price. /// Rules: /// Don't cache fill forward data. /// Always return the last observation. /// If two consecutive data has the same time stamp and one is Quotebars and the other Tradebar, prioritize the Quotebar. /// public void AddData(BaseData data) { AddDataImpl(data, cacheByType: true); } private void AddDataImpl(BaseData data, bool cacheByType) { var tick = data as Tick; if (tick?.TickType == TickType.OpenInterest) { if (cacheByType) { StoreDataPoint(data); } OpenInterest = (long)tick.Value; return; } // Only cache non fill-forward data. if (data.IsFillForward) return; if (cacheByType) { StoreDataPoint(data); } var isDefaultDataType = SubscriptionManager.IsDefaultDataType(data); // don't set _lastData if receive quotebar then tradebar w/ same end time. this // was implemented to grant preference towards using quote data in the fill // models and provide a level of determinism on the values exposed via the cache. if ((_lastData == null || _lastQuoteBarUpdate != data.EndTime || data.DataType != MarketDataType.TradeBar) // we will only set the default data type to preserve determinism and backwards compatibility && isDefaultDataType) { _lastData = data; } if (tick != null) { if (tick.Value != 0) Price = tick.Value; switch (tick.TickType) { case TickType.Trade: if (tick.Quantity != 0) Volume = tick.Quantity; break; case TickType.Quote: if (tick.BidPrice != 0) BidPrice = tick.BidPrice; if (tick.BidSize != 0) BidSize = tick.BidSize; if (tick.AskPrice != 0) AskPrice = tick.AskPrice; if (tick.AskSize != 0) AskSize = tick.AskSize; break; } return; } var bar = data as IBar; if (bar != null) { // we will only set OHLC values using the default data type to preserve determinism and backwards compatibility. // Gives priority to QuoteBar over TradeBar, to be removed when default data type completely addressed GH issue 4196 if ((_lastQuoteBarUpdate != data.EndTime || _lastOHLCUpdate != data.EndTime) && isDefaultDataType) { _lastOHLCUpdate = data.EndTime; if (bar.Open != 0) Open = bar.Open; if (bar.High != 0) High = bar.High; if (bar.Low != 0) Low = bar.Low; if (bar.Close != 0) { Price = bar.Close; Close = bar.Close; } } var tradeBar = bar as TradeBar; if (tradeBar != null) { if (tradeBar.Volume != 0) Volume = tradeBar.Volume; } var quoteBar = bar as QuoteBar; if (quoteBar != null) { _lastQuoteBarUpdate = quoteBar.EndTime; if (quoteBar.Ask != null && quoteBar.Ask.Close != 0) AskPrice = quoteBar.Ask.Close; if (quoteBar.Bid != null && quoteBar.Bid.Close != 0) BidPrice = quoteBar.Bid.Close; if (quoteBar.LastBidSize != 0) BidSize = quoteBar.LastBidSize; if (quoteBar.LastAskSize != 0) AskSize = quoteBar.LastAskSize; } } else if (data.DataType != MarketDataType.Auxiliary) { Price = data.Price; } } /// /// Stores the specified data list in the cache WITHOUT updating any of the cache properties, such as Price /// /// The collection of data to store in this cache /// The data type public void StoreData(IReadOnlyList data, Type dataType) { #if DEBUG // don't run this in release as we should never fail here, but it's also nice to have here as documentation of intent if (data.DistinctBy(d => d.GetType()).Skip(1).Any()) { throw new ArgumentException( "SecurityCache.StoreData data list must contain elements of the same type." ); } #endif if (dataType == typeof(Tick)) { var tick = data[data.Count - 1] as Tick; switch (tick?.TickType) { case TickType.Trade: _lastTickTrades = data; return; case TickType.Quote: _lastTickQuotes = data; return; } } _dataByType[dataType] = data; } /// /// Get last data packet received for this security /// /// BaseData type of the security public BaseData GetData() { return _lastData; } /// /// Get last data packet received for this security of the specified type /// /// The data type /// The last data packet, null if none received of type public T GetData() where T : BaseData { IReadOnlyList list; if (!TryGetValue(typeof(T), out list) || list.Count == 0) { return default(T); } return list[list.Count - 1] as T; } /// /// Gets all data points of the specified type from the most recent time step /// that produced data for that type /// public IEnumerable GetAll() { if (typeof(T) == typeof(Tick)) { return _lastTickTrades.Concat(_lastTickQuotes).Cast(); } IReadOnlyList list; if (!_dataByType.TryGetValue(typeof(T), out list)) { return new List(); } return list.Cast(); } /// /// Reset cache storage and free memory /// public void Reset() { _dataByType.Clear(); _lastTickQuotes = new List(); _lastTickTrades = new List(); } /// /// Gets whether or not this dynamic data instance has data stored for the specified type /// public bool HasData(Type type) { IReadOnlyList data; return TryGetValue(type, out data); } /// /// Gets whether or not this dynamic data instance has data stored for the specified type /// public bool TryGetValue(Type type, out IReadOnlyList data) { if (type == typeof(Tick)) { var quote = _lastTickQuotes.LastOrDefault(); var trade = _lastTickTrades.LastOrDefault(); var isQuoteDefaultDataType = quote != null && SubscriptionManager.IsDefaultDataType(quote); var isTradeDefaultDataType = trade != null && SubscriptionManager.IsDefaultDataType(trade); // Currently, IsDefaultDataType returns true for both cases, // So we will return the list with the tick with the most recent timestamp if (isQuoteDefaultDataType && isTradeDefaultDataType) { data = quote.EndTime > trade.EndTime ? _lastTickQuotes : _lastTickTrades; return true; } data = isQuoteDefaultDataType ? _lastTickQuotes : _lastTickTrades; return data?.Count > 0; } return _dataByType.TryGetValue(type, out data); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void StoreDataPoint(BaseData data) { if (data.GetType() == typeof(Tick)) { var tick = data as Tick; switch (tick?.TickType) { case TickType.Trade: _lastTickTrades = new List { tick }; break; case TickType.Quote: _lastTickQuotes = new List { tick }; break; } } else { // Always keep track of the last observation IReadOnlyList list; if (!_dataByType.TryGetValue(data.GetType(), out list)) { list = new List { data }; _dataByType[data.GetType()] = list; } else { // we KNOW this one is actually a list, so this is safe // we overwrite the zero entry so we're not constantly newing up lists ((List)list)[0] = data; } } } /// /// Helper method that modifies the target security cache instance to use the /// type cache of the source /// /// Will set in the source cache any data already present in the target cache /// This is useful for custom data securities which also have an underlying security, /// will allow both securities to access the same data by type /// The source cache to use /// The target security cache that will be modified public static void ShareTypeCacheInstance(SecurityCache sourceToShare, SecurityCache targetToModify) { foreach (var kvp in targetToModify._dataByType) { sourceToShare._dataByType.TryAdd(kvp.Key, kvp.Value); } targetToModify._dataByType = sourceToShare._dataByType; targetToModify._lastTickTrades = sourceToShare._lastTickTrades; targetToModify._lastTickQuotes = sourceToShare._lastTickQuotes; } } }