/* * 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.Diagnostics; using System.IO; using System.Linq; using Newtonsoft.Json; using NUnit.Framework; using ProtoBuf; using QuantConnect.Data; using QuantConnect.Data.Custom.IconicTypes; using QuantConnect.Data.Market; using QuantConnect.Logging; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Securities; namespace QuantConnect.Tests.Common { [TestFixture, Parallelizable(ParallelScope.All)] public class ProtobufSerializationTests { private static readonly Dictionary _iconicInstances = new Dictionary { { typeof(IndexedLinkedData), new IndexedLinkedData { Count = 1024 } }, { typeof(IndexedLinkedData2), new IndexedLinkedData2 { Count = 2048 } }, { typeof(LinkedData), new LinkedData { Count = 4096 } }, { typeof(UnlinkedData), new UnlinkedData { Ticker = "ABCDEF" } }, { typeof(UnlinkedDataTradeBar), new UnlinkedDataTradeBar { Open = 10m, High = 11m, Low = 9m, Close = 10.99m, Volume = 9999m } } }; [TestCase(typeof(IndexedLinkedData), true)] [TestCase(typeof(IndexedLinkedData2), true)] [TestCase(typeof(LinkedData), true)] [TestCase(typeof(UnlinkedData), false)] [TestCase(typeof(UnlinkedDataTradeBar), false)] public void SerializeRoundTripIconicDataTypes(Type baseDataType, bool hasUnderlyingSymbol) { var item = CreateNewInstance(baseDataType, hasUnderlyingSymbol); var serialized = item.ProtobufSerialize(new Guid()); using (var stream = new MemoryStream(serialized)) { var deserialized = Serializer.Deserialize>(stream).Single(); AssertAreEqual(item, deserialized); } } [Test] public void SymbolRoundTrip() { var symbol = Symbols.AAPL; using (var stream = new MemoryStream()) { Serializer.Serialize(stream, symbol); stream.Position = 0; var result = Serializer.Deserialize(stream); Assert.AreEqual(symbol, result); Assert.AreEqual(symbol.GetHashCode(), result.GetHashCode()); } } [TestCase(10)] [TestCase(100)] [TestCase(1000)] public void TickListSerializationRoundTrip(int tickCount) { var time = DateTime.UtcNow; var ticks = new List(); for (int i = 0; i < tickCount; i++) { var tick = new Tick { Symbol = Symbols.AAPL, AskPrice = i, AskSize = i, Time = time + TimeSpan.FromMilliseconds(i), Quantity = i, DataType = MarketDataType.Tick, Exchange = "NASDAQ", SaleCondition = "VerySold", TickType = TickType.Quote, Value = i, BidPrice = i, BidSize = i }; ticks.Add(tick); } var stopwatch = new Stopwatch(); stopwatch.Start(); var serializedTick = ticks.ProtobufSerialize(new Guid()); stopwatch.Stop(); Log.Trace($"Took {stopwatch.ElapsedMilliseconds}ms. TickCount : {tickCount}."); // verify its correct using (var stream = new MemoryStream(serializedTick)) { var results = Serializer.Deserialize>(stream); Assert.AreEqual(tickCount, results.Count); for (int i = 0; i < tickCount; i++) { var result = results[i]; Assert.AreEqual(i, result.AskPrice); Assert.AreEqual(i, result.AskSize); Assert.AreEqual(time + TimeSpan.FromMilliseconds(i), result.Time); Assert.AreEqual(i, result.Quantity); Assert.AreEqual(MarketDataType.Tick, result.DataType); Assert.AreEqual("NASDAQ", result.Exchange); Assert.AreEqual("VerySold", result.SaleCondition); Assert.AreEqual(TickType.Quote, result.TickType); Assert.AreEqual(time + TimeSpan.FromMilliseconds(i), result.EndTime); Assert.AreEqual(i, result.Value); Assert.AreEqual(i, result.BidPrice); Assert.AreEqual(i, result.BidSize); Assert.IsNull(result.Symbol); } } } [Test] public void OpenInterestSerializationRoundTrip() { var openInterest = new OpenInterest(DateTime.UtcNow, Symbols.AAPL, 10); var serializedTick = openInterest.ProtobufSerialize(new Guid()); // verify its correct using (var stream = new MemoryStream(serializedTick)) { var result = (Tick)Serializer.Deserialize>(stream).First(); Assert.IsNull(result.Symbol); Assert.AreEqual(openInterest.Time, result.Time); Assert.AreEqual(openInterest.EndTime, result.EndTime); Assert.AreEqual(openInterest.Value, result.Value); } } [Test] public void TickSerializationRoundTrip() { var tick = new Tick { Symbol = Symbols.AAPL, AskPrice = 10, AskSize = 10, Time = DateTime.UtcNow, Quantity = 10, DataType = MarketDataType.Tick, Exchange = "NASDAQ", SaleCondition = "VerySold", TickType = TickType.Quote, EndTime = DateTime.UtcNow, Value = 10, BidPrice = 100, BidSize = 100 }; var serializedTick = tick.ProtobufSerialize(new Guid()); // verify its correct using (var stream = new MemoryStream(serializedTick)) { var result = (Tick) Serializer.Deserialize>(stream).First(); Assert.AreEqual(tick.AskPrice, result.AskPrice); Assert.AreEqual(tick.AskSize, result.AskSize); Assert.AreEqual(tick.Time, result.Time); Assert.AreEqual(tick.Quantity, result.Quantity); Assert.AreEqual(tick.DataType, result.DataType); Assert.AreEqual("NASDAQ", result.Exchange); Assert.AreEqual(tick.SaleCondition, result.SaleCondition); Assert.AreEqual(tick.TickType, result.TickType); Assert.AreEqual(tick.EndTime, result.EndTime); Assert.AreEqual(tick.Value, result.Value); Assert.AreEqual(tick.BidPrice, result.BidPrice); Assert.AreEqual(tick.BidSize, result.BidSize); } } [Test] public void TradeBarSerializationRoundTrip() { var tradeBar = new TradeBar { Symbol = Symbols.AAPL, Volume = 10, Time = DateTime.UtcNow, EndTime = DateTime.UtcNow, Value = 10, Close = 10, High = 100, Low = 100, Open = 100, Period = TimeSpan.FromMinutes(1) }; var serializedTradeBar = tradeBar.ProtobufSerialize(new Guid()); using (var stream = new MemoryStream(serializedTradeBar)) { // verify its correct var result = (TradeBar) Serializer.Deserialize>(stream).First(); Assert.AreEqual(tradeBar.Time, result.Time); Assert.AreEqual(tradeBar.DataType, result.DataType); Assert.AreEqual(tradeBar.EndTime, result.EndTime); Assert.AreEqual(tradeBar.Value, result.Value); Assert.AreEqual(tradeBar.Volume, result.Volume); Assert.AreEqual(tradeBar.Close, result.Close); Assert.AreEqual(tradeBar.High, result.High); Assert.AreEqual(tradeBar.Low, result.Low); Assert.AreEqual(tradeBar.Open, result.Open); Assert.AreEqual(tradeBar.Period, result.Period); } } [Test] public void QuoteBarSerializationRoundTrip() { var quoteBar = new QuoteBar { Symbol = Symbols.AAPL, Time = DateTime.UtcNow, EndTime = DateTime.UtcNow, Value = 10, LastAskSize = 10, LastBidSize = 100, Ask = new Bar(1, 2, 3, 4), Bid = new Bar(11, 22, 33, 44), Period = TimeSpan.FromMinutes(1) }; var serializedQuoteBar = quoteBar.ProtobufSerialize(new Guid()); using (var stream = new MemoryStream(serializedQuoteBar)) { // verify its correct var result = (QuoteBar)Serializer.Deserialize>(stream).First(); Assert.AreEqual(quoteBar.Time, result.Time); Assert.AreEqual(quoteBar.DataType, result.DataType); Assert.AreEqual(quoteBar.EndTime, result.EndTime); Assert.AreEqual(quoteBar.Value, result.Value); Assert.AreEqual(quoteBar.Close, result.Close); Assert.AreEqual(quoteBar.High, result.High); Assert.AreEqual(quoteBar.Low, result.Low); Assert.AreEqual(quoteBar.Open, result.Open); Assert.AreEqual(quoteBar.Period, result.Period); Assert.AreEqual(quoteBar.Ask.Close, result.Ask.Close); Assert.AreEqual(quoteBar.Ask.High, result.Ask.High); Assert.AreEqual(quoteBar.Ask.Low, result.Ask.Low); Assert.AreEqual(quoteBar.Ask.Open, result.Ask.Open); Assert.AreEqual(quoteBar.Bid.Close, result.Bid.Close); Assert.AreEqual(quoteBar.Bid.High, result.Bid.High); Assert.AreEqual(quoteBar.Bid.Low, result.Bid.Low); Assert.AreEqual(quoteBar.Bid.Open, result.Bid.Open); } } [Test] public void DividendRoundTrip() { var dividend = new Dividend { DataType = MarketDataType.Auxiliary, Distribution = 0.5m, ReferencePrice = decimal.MaxValue - 10000m, Symbol = Symbols.AAPL, Time = DateTime.UtcNow, Value = 0.5m }; var serializedDividend = dividend.ProtobufSerialize(new Guid()); using (var stream = new MemoryStream(serializedDividend)) { var result = (Dividend)Serializer.Deserialize>(stream).First(); Assert.AreEqual(dividend.DataType, result.DataType); Assert.AreEqual(dividend.Distribution, result.Distribution); Assert.AreEqual(dividend.ReferencePrice, result.ReferencePrice); Assert.AreEqual(dividend.Time, result.Time); Assert.AreEqual(dividend.EndTime, result.EndTime); Assert.AreEqual(dividend.Value, result.Value); } } [Test] public void SplitRoundTrip() { var split = new Split(Symbols.AAPL, DateTime.UtcNow, decimal.MaxValue, decimal.MinValue, SplitType.SplitOccurred); var serializedSplit = split.ProtobufSerialize(new Guid()); using (var stream = new MemoryStream(serializedSplit)) { var result = (Split)Serializer.Deserialize>(stream).First(); Assert.AreEqual(split.Type, result.Type); Assert.AreEqual(split.DataType, result.DataType); Assert.AreEqual(split.SplitFactor, result.SplitFactor); Assert.AreEqual(split.ReferencePrice, result.ReferencePrice); Assert.AreEqual(split.Time, result.Time); Assert.AreEqual(split.EndTime, result.EndTime); Assert.AreEqual(split.Value, result.Value); } } [Test, Ignore("Performance test")] public void SpeedTest() { var symbols = new List { Symbol.Create("SPY", SecurityType.Equity, Market.USA), Symbol.Create("DE30EUR", SecurityType.Cfd, Market.Oanda), Symbol.Create("BTCUSD", SecurityType.Crypto, Market.Coinbase), Symbol.Create("BTCUSD", SecurityType.Crypto, Market.Bitfinex), Symbol.Create("EURUSD", SecurityType.Forex, Market.FXCM), Symbol.Create("EURUSD", SecurityType.Forex, Market.Oanda), Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, OptionRight.Call, 1, DateTime.UtcNow), Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, DateTime.UtcNow) }; var now = DateTime.UtcNow; var ticks = new List(); for (var i = 0; i < 10000; i++) { foreach (var symbol in symbols) { ticks.Add(new Tick { Symbol = symbol, AskPrice = i * 10, AskSize = i * 10, Time = now, Quantity = 10, DataType = MarketDataType.Tick, Exchange = "Pinocho", SaleCondition = "VerySold", TickType = TickType.Quote, EndTime = now, Value = i * 10, BidPrice = i * 100, BidSize = i * 100 }); } } var guid = new Guid(); { // warmup var serialized = ticks.ProtobufSerialize(guid); using (var stream = new MemoryStream(serialized)) { // verify its correct var result = Serializer.Deserialize>(stream); } var start = DateTime.UtcNow; for (var i = 0; i < 10; i++) { serialized = ticks.ProtobufSerialize(guid); } var end = DateTime.UtcNow; Log.Trace($"PROTO BUF TOOK {end - start}"); } { // warmup var serialized = JsonConvert.SerializeObject(ticks); var start = DateTime.UtcNow; for (var i = 0; i < 10; i++) { serialized = JsonConvert.SerializeObject(ticks); } var end = DateTime.UtcNow; Log.Trace($"JSON TOOK {end - start}"); } } private static BaseData CreateNewInstance(Type baseDataType, bool hasUnderlyingSymbol) { var instance = _iconicInstances[baseDataType]; instance.Symbol = hasUnderlyingSymbol ? Symbol.CreateBase(baseDataType, Symbols.AAPL, QuantConnect.Market.USA) : Symbol.Create("ABCDEF", SecurityType.Base, Market.USA, baseDataType: baseDataType); instance.Time = new DateTime(2021, 6, 5); return instance; } private void AssertAreEqual(object expected, object result) { foreach (var propertyInfo in expected.GetType().GetProperties()) { if (propertyInfo.CustomAttributes.Any(data => data.AttributeType == typeof(ProtoMemberAttribute))) { var expectedValue = propertyInfo.GetValue(expected); var resultValue = propertyInfo.GetValue(result); if (expectedValue is IList) { var expectedValueList = (IList) expectedValue; var resultValueList = (IList) resultValue; for (var i = 0; i < expectedValueList.Count; i++) { AssertAreEqual(expectedValueList[i], resultValueList[i]); } } else if (expectedValue is IDictionary) { var expectedValueDictionary = (IDictionary) expectedValue; var resultValueDictionary = (IDictionary) resultValue; foreach (dynamic kvp in expectedValueDictionary) { AssertAreEqual(kvp.Key, resultValueDictionary.Contains(kvp.Key)); AssertAreEqual(kvp.Value, resultValueDictionary[kvp.Key]); } } else { if (expectedValue is OrderEvent || expectedValue is OrderFee) { AssertAreEqual(expectedValue, resultValue); } else { Assert.AreEqual(expectedValue, resultValue); } } } } foreach (var fieldInfo in expected.GetType().GetFields()) { if (fieldInfo.CustomAttributes.Any(data => data.AttributeType == typeof(ProtoMemberAttribute))) { Assert.AreEqual(fieldInfo.GetValue(expected), fieldInfo.GetValue(result)); } } } } }