Files
quantconnect--lean/Tests/Common/ProtobufSerializationTests.cs
T
2026-07-13 13:02:50 +08:00

477 lines
19 KiB
C#

/*
* 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<Type, BaseData> _iconicInstances = new Dictionary<Type, BaseData>
{
{ 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<IEnumerable<BaseData>>(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<Symbol>(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<Tick>();
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<List<Tick>>(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<IEnumerable<BaseData>>(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<IEnumerable<BaseData>>(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<IEnumerable<BaseData>>(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<IEnumerable<BaseData>>(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<IEnumerable<BaseData>>(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<IEnumerable<BaseData>>(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>
{
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<Tick>();
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<List<Tick>>(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));
}
}
}
}
}