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