756 lines
30 KiB
C#
756 lines
30 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.Generic;
|
|
using System.Linq;
|
|
using Newtonsoft.Json.Linq;
|
|
using NUnit.Framework;
|
|
using Python.Runtime;
|
|
using QuantConnect.Algorithm.CSharp;
|
|
using QuantConnect.Data;
|
|
using QuantConnect.Data.Fundamental;
|
|
using QuantConnect.Data.Market;
|
|
using QuantConnect.Data.UniverseSelection;
|
|
using QuantConnect.Python;
|
|
using QuantConnect.Securities;
|
|
using QuantConnect.Tests.Common.Data.Fundamental;
|
|
using QuantConnect.Util;
|
|
|
|
namespace QuantConnect.Tests.Common.Securities
|
|
{
|
|
[TestFixture, Parallelizable(ParallelScope.All)]
|
|
public class SecurityCacheTests
|
|
{
|
|
private static readonly DateTime ReferenceTime = new DateTime(2000, 01, 01);
|
|
|
|
private readonly Random _rng = new Random(Seed: 123);
|
|
|
|
[SetUp]
|
|
public void Setup()
|
|
{
|
|
FundamentalService.Initialize(TestGlobals.DataProvider, new NullFundamentalDataProvider(), false);
|
|
}
|
|
|
|
[TestCase(MarketDataType.TradeBar, 10, true)]
|
|
[TestCase(MarketDataType.TradeBar, 10, false)]
|
|
[TestCase(MarketDataType.QuoteBar, 10, true)]
|
|
[TestCase(MarketDataType.QuoteBar, 10, false)]
|
|
[TestCase(MarketDataType.Tick, 10, true)]
|
|
[TestCase(MarketDataType.Tick, 10, false)]
|
|
public void AlwaysReturnTheLastData(MarketDataType marketDataType, int quantity, bool sameTime)
|
|
{
|
|
// Arrange
|
|
var securityCache = new SecurityCache();
|
|
var quotes = GenerateData(marketDataType, quantity, sameTime);
|
|
// Act
|
|
foreach (var quoteBar in quotes)
|
|
{
|
|
quoteBar.Symbol = Symbols.SPY;
|
|
securityCache.AddData(quoteBar);
|
|
}
|
|
|
|
var lastData = securityCache.GetData();
|
|
if (marketDataType == MarketDataType.QuoteBar)
|
|
{
|
|
Assert.IsNull(lastData);
|
|
}
|
|
else
|
|
{
|
|
Assert.True(lastData.Equals(quotes.Last()));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void GivenSameTimeStampForTradeBarAndQuoteQuotebarPrioritizeQuoteBar()
|
|
{
|
|
// Arrange
|
|
var securityCache = new SecurityCache();
|
|
var time = DateTime.Now;
|
|
var quotes = GenerateData(MarketDataType.QuoteBar, 5, false, time);
|
|
var trades = GenerateData(MarketDataType.TradeBar, 5, false, time);
|
|
var data = quotes.Concat(trades);
|
|
data = data.OrderBy(d => d.EndTime);
|
|
// Act
|
|
foreach (var baseData in data)
|
|
{
|
|
securityCache.AddData(baseData);
|
|
}
|
|
// Assert
|
|
Assert.True(securityCache.GetData().Equals(quotes.Last()));
|
|
Assert.True(securityCache.GetData<TradeBar>().Equals(trades.Last()));
|
|
}
|
|
|
|
[Test]
|
|
public void UseLatestTradebarIfThereIsntAvailableQuotebar()
|
|
{
|
|
// Arrange
|
|
var securityCache = new SecurityCache();
|
|
var time = DateTime.Now;
|
|
var quotes = GenerateData(MarketDataType.QuoteBar, 5, false, time);
|
|
foreach (var baseData in quotes)
|
|
{
|
|
securityCache.AddData(baseData);
|
|
}
|
|
// Add one last tradebar with a later timestamp
|
|
var laterTrade = GenerateData(MarketDataType.TradeBar, 1, true, quotes.Last().Time.AddSeconds(1)).First();
|
|
// Act
|
|
securityCache.AddData(laterTrade);
|
|
// Assert
|
|
Assert.True(securityCache.GetData().Equals(laterTrade));
|
|
Assert.True(securityCache.GetData<QuoteBar>().Equals(quotes.Last()));
|
|
}
|
|
|
|
[Test]
|
|
public void AddData_SecurityCacheHasTradeAndQuoteTick()
|
|
{
|
|
// Arrange
|
|
var securityCache = new SecurityCache();
|
|
var time = DateTime.Now;
|
|
|
|
var quote = new Tick(time, Symbol.Empty, 100, 102) { TickType = TickType.Quote };
|
|
securityCache.AddData(quote);
|
|
|
|
var trade = new Tick(time, Symbol.Empty, 100, 100, 102) {TickType = TickType.Trade};
|
|
securityCache.AddData(trade);
|
|
|
|
var openInterest = new OpenInterest(time, Symbol.Empty, 1000);
|
|
securityCache.AddData(openInterest);
|
|
|
|
// Assert
|
|
Assert.True(securityCache.GetData().Equals(trade));
|
|
Assert.True(securityCache.GetData<Tick>().Equals(trade));
|
|
|
|
Assert.True(securityCache.GetAll<Tick>().LastOrDefault(x => x.TickType == TickType.Quote).Equals(quote));
|
|
Assert.True(securityCache.GetAll<Tick>().LastOrDefault(x => x.TickType == TickType.Trade).Equals(trade));
|
|
}
|
|
|
|
[Test]
|
|
public void StoreData_SecurityCacheHasTradeAndQuoteTick()
|
|
{
|
|
// Arrange
|
|
var securityCache = new SecurityCache();
|
|
var time = DateTime.Now;
|
|
|
|
var quote = new Tick(time, Symbol.Empty, 100, 102) { TickType = TickType.Quote };
|
|
securityCache.StoreData(new[] {quote}, typeof(Tick));
|
|
|
|
var trade = new Tick(time.AddMilliseconds(1), Symbol.Empty, 100, 100, 102) { TickType = TickType.Trade };
|
|
securityCache.StoreData(new[] { trade }, typeof(Tick));
|
|
|
|
// Adding OpenInterest as Tick or OpenInterest should not matter
|
|
var openInterest = new OpenInterest(time, Symbol.Empty, 1000);
|
|
securityCache.StoreData(new[] { openInterest }, typeof(Tick)); // Add as Tick
|
|
securityCache.StoreData(new[] { openInterest }, typeof(OpenInterest)); // Add as OI
|
|
|
|
// Assert
|
|
Assert.IsTrue(securityCache.HasData(typeof(Tick)));
|
|
Assert.True(securityCache.GetData<Tick>().Equals(trade));
|
|
Assert.True(securityCache.GetData<OpenInterest>().Equals(openInterest));
|
|
|
|
Assert.True(securityCache.GetAll<Tick>().LastOrDefault(x => x.TickType == TickType.Quote).Equals(quote));
|
|
Assert.True(securityCache.GetAll<Tick>().LastOrDefault(x => x.TickType == TickType.Trade).Equals(trade));
|
|
}
|
|
|
|
[Test]
|
|
public void StoreData_TargetToModify_SecurityCacheHasTradeAndQuoteTick()
|
|
{
|
|
// Arrange
|
|
var sourceToShare = new SecurityCache();
|
|
var time = DateTime.Now;
|
|
|
|
var quote = new Tick(time, Symbol.Empty, 100, 102) { TickType = TickType.Quote };
|
|
sourceToShare.StoreData(new[] { quote }, typeof(Tick));
|
|
|
|
var trade = new Tick(time, Symbol.Empty, 100, 100, 102) { TickType = TickType.Trade };
|
|
sourceToShare.StoreData(new[] { trade }, typeof(Tick));
|
|
|
|
// Adding OpenInterest as Tick or OpenInterest should not matter
|
|
var openInterest = new OpenInterest(time, Symbol.Empty, 1000);
|
|
sourceToShare.StoreData(new[] { openInterest }, typeof(Tick)); // Add as Tick
|
|
sourceToShare.StoreData(new[] { openInterest }, typeof(OpenInterest)); // Add as OI
|
|
|
|
var targetToModify = new SecurityCache();
|
|
SecurityCache.ShareTypeCacheInstance(sourceToShare, targetToModify);
|
|
|
|
// Assert
|
|
Assert.IsTrue(targetToModify.HasData(typeof(Tick)));
|
|
Assert.True(targetToModify.GetData<Tick>().Equals(trade));
|
|
Assert.True(targetToModify.GetData<OpenInterest>().Equals(openInterest));
|
|
|
|
Assert.True(targetToModify.GetAll<Tick>().LastOrDefault(x => x.TickType == TickType.Quote).Equals(quote));
|
|
Assert.True(targetToModify.GetAll<Tick>().LastOrDefault(x => x.TickType == TickType.Trade).Equals(trade));
|
|
}
|
|
|
|
[Test]
|
|
[TestCaseSource(nameof(GetSecurityCacheInitialStates))]
|
|
public void ResetTests(SecurityCache cache, SecuritySeedData seedType)
|
|
{
|
|
switch (seedType)
|
|
{
|
|
case SecuritySeedData.None:
|
|
case SecuritySeedData.OpenInterest:
|
|
case SecuritySeedData.OpenInterestTick:
|
|
break;
|
|
case SecuritySeedData.QuoteTick:
|
|
Assert.IsNotNull(cache.GetData());
|
|
Assert.Greater(cache.GetAll<Tick>().Count(x => x.TickType == TickType.Quote), 0);
|
|
cache.Reset();
|
|
Assert.IsFalse(cache.HasData(typeof(Tick)));
|
|
Assert.AreEqual(cache.GetAll<Tick>().Count(x => x.TickType == TickType.Quote), 0);
|
|
break;
|
|
case SecuritySeedData.TradeTick:
|
|
Assert.IsNotNull(cache.GetData());
|
|
Assert.Greater(cache.GetAll<Tick>().Count(x => x.TickType == TickType.Trade), 0);
|
|
cache.Reset();
|
|
Assert.IsFalse(cache.HasData(typeof(Tick)));
|
|
Assert.AreEqual(cache.GetAll<Tick>().Count(x => x.TickType == TickType.Trade), 0);
|
|
break;
|
|
case SecuritySeedData.Fundamentals:
|
|
Assert.IsNotNull(cache.GetData());
|
|
Assert.IsNotNull(cache.GetData<Fundamental>());
|
|
cache.Reset();
|
|
Assert.IsFalse(cache.HasData(typeof(Fundamental)));
|
|
break;
|
|
default:
|
|
Assert.IsNotNull(cache.GetData());
|
|
cache.Reset();
|
|
break;
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
[TestCaseSource(nameof(GetSecurityCacheInitialStates))]
|
|
public void AddDataWithSameEndTime_SetsOpenInterestValues(SecurityCache cache, SecuritySeedData seedType)
|
|
{
|
|
var map = new Dictionary<string, string> {{"OpenInterest", "Value"}};
|
|
AddDataAndAssertChanges(cache, seedType, SecuritySeedData.OpenInterest, new OpenInterest
|
|
{
|
|
Value = 101,
|
|
EndTime = ReferenceTime
|
|
}, map);
|
|
}
|
|
|
|
[Test]
|
|
[TestCaseSource(nameof(GetSecurityCacheInitialStates))]
|
|
public void AddDataWithSameEndTime_SetsOpenInterestTickValues(SecurityCache cache, SecuritySeedData seedType)
|
|
{
|
|
var map = new Dictionary<string, string> { { "OpenInterest", "Value" } };
|
|
AddDataAndAssertChanges(cache, seedType, SecuritySeedData.OpenInterestTick, new Tick
|
|
{
|
|
Value = 101,
|
|
TickType = TickType.OpenInterest,
|
|
EndTime = ReferenceTime
|
|
}, map);
|
|
}
|
|
|
|
[Test]
|
|
[TestCaseSource(nameof(GetSecurityCacheInitialStates))]
|
|
public void AddDataWithSameEndTime_SetsQuoteTickValues(SecurityCache cache, SecuritySeedData seedType)
|
|
{
|
|
AddDataAndAssertChanges(cache, seedType, SecuritySeedData.QuoteTick, new Tick
|
|
{
|
|
AskPrice = 101,
|
|
AskSize = 102,
|
|
BidPrice = 103,
|
|
BidSize = 104,
|
|
TickType = TickType.Quote,
|
|
EndTime = ReferenceTime
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
[TestCaseSource(nameof(GetSecurityCacheInitialStates))]
|
|
public void AddDataWithSameEndTime_SetsTradeTickValues(SecurityCache cache, SecuritySeedData seedType)
|
|
{
|
|
var map = new Dictionary<string, string> {{"Volume", "Quantity"}};
|
|
AddDataAndAssertChanges(cache, seedType, SecuritySeedData.TradeTick, new Tick
|
|
{
|
|
Value = 101,
|
|
Quantity = 102,
|
|
TickType = TickType.Trade,
|
|
EndTime = ReferenceTime
|
|
}, map);
|
|
}
|
|
|
|
[Test]
|
|
[TestCaseSource(nameof(GetSecurityCacheInitialStates))]
|
|
public void AddDataWithSameEndTime_SetsTradeBarValues(SecurityCache cache, SecuritySeedData seedType)
|
|
{
|
|
AddDataAndAssertChanges(cache, seedType, SecuritySeedData.TradeBar, new TradeBar
|
|
{
|
|
Open = 101,
|
|
High = 102,
|
|
Low = 103,
|
|
Close = 104,
|
|
Volume = 105,
|
|
EndTime = ReferenceTime,
|
|
Symbol = Symbols.SPY
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void AddDataEquity_OHLC_IgnoresQuoteBar()
|
|
{
|
|
var securityCache = new SecurityCache();
|
|
var quoteBar = new QuoteBar
|
|
{
|
|
Bid = new Bar(101, 102, 103, 104),
|
|
Ask = new Bar(105, 106, 107, 108),
|
|
LastAskSize = 109,
|
|
LastBidSize = 110,
|
|
EndTime = ReferenceTime,
|
|
Symbol = Symbols.SPY
|
|
};
|
|
securityCache.AddData(quoteBar);
|
|
|
|
var last = securityCache.GetData();
|
|
Assert.IsNull(last);
|
|
|
|
Assert.AreEqual(0, securityCache.High);
|
|
Assert.AreEqual(0, securityCache.Close);
|
|
Assert.AreEqual(0, securityCache.Low);
|
|
Assert.AreEqual(0, securityCache.Open);
|
|
Assert.AreEqual(0, securityCache.Volume);
|
|
|
|
var actualQuoteBar = securityCache.GetData<QuoteBar>();
|
|
Assert.IsNotNull(actualQuoteBar);
|
|
Assert.AreEqual(108, securityCache.AskPrice);
|
|
Assert.AreEqual(109, securityCache.AskSize);
|
|
Assert.AreEqual(104, securityCache.BidPrice);
|
|
Assert.AreEqual(110, securityCache.BidSize);
|
|
|
|
var tradeBar = new TradeBar
|
|
{
|
|
Open = 101,
|
|
High = 102,
|
|
Low = 103,
|
|
Close = 104,
|
|
Volume = 105,
|
|
EndTime = ReferenceTime,
|
|
Symbol = Symbols.SPY
|
|
};
|
|
securityCache.AddData(tradeBar);
|
|
|
|
last = securityCache.GetData();
|
|
Assert.IsNotNull(last);
|
|
|
|
var actualTradeBar = securityCache.GetData<TradeBar>();
|
|
Assert.IsNotNull(actualTradeBar);
|
|
Assert.AreEqual(102, securityCache.High);
|
|
Assert.AreEqual(104, securityCache.Close);
|
|
Assert.AreEqual(103, securityCache.Low);
|
|
Assert.AreEqual(101, securityCache.Open);
|
|
Assert.AreEqual(105, securityCache.Volume);
|
|
|
|
// quote bar data should still be the same
|
|
Assert.AreEqual(108, securityCache.AskPrice);
|
|
Assert.AreEqual(109, securityCache.AskSize);
|
|
Assert.AreEqual(104, securityCache.BidPrice);
|
|
Assert.AreEqual(110, securityCache.BidSize);
|
|
}
|
|
|
|
[Test]
|
|
[TestCaseSource(nameof(GetSecurityCacheInitialStates))]
|
|
public void AddDataWithSameEndTime_SetsQuoteBarValues(SecurityCache cache, SecuritySeedData seedType)
|
|
{
|
|
var map = new Dictionary<string, string>
|
|
{
|
|
{"Price", "Close"},
|
|
{"BidPrice", "Bid.Close"},
|
|
{"BidSize", "LastBidSize"},
|
|
{"AskPrice", "Ask.Close"},
|
|
{"AskSize", "LastAskSize"}
|
|
};
|
|
AddDataAndAssertChanges(cache, seedType, SecuritySeedData.QuoteBar, new QuoteBar
|
|
{
|
|
Bid = new Bar(101, 102, 103, 104),
|
|
Ask = new Bar(105, 106, 107, 108),
|
|
LastAskSize = 109,
|
|
LastBidSize = 110,
|
|
EndTime = ReferenceTime
|
|
}, map);
|
|
}
|
|
|
|
[Test]
|
|
[TestCaseSource(nameof(GetSecurityCacheInitialStates))]
|
|
public void AddDataFundamentals_DoesNotChangeCacheValues(SecurityCache cache, SecuritySeedData seedType)
|
|
{
|
|
var map = new Dictionary<string, string>();
|
|
AddDataAndAssertChanges(cache, seedType, SecuritySeedData.Fundamentals, new Fundamental(ReferenceTime, Symbols.AAPL)
|
|
{
|
|
Value = 111,
|
|
EndTime = ReferenceTime
|
|
}, map);
|
|
}
|
|
|
|
[Test]
|
|
public void TickTypeDependencyTests()
|
|
{
|
|
// Arrange
|
|
var time = DateTime.Now;
|
|
var price = 100m;
|
|
var bidPrice = 99m;
|
|
var askPrice = 101m;
|
|
var volume = 1m;
|
|
|
|
var tick = new Tick(time, Symbols.AAPL, price, bidPrice, askPrice) { Quantity = volume }; ;
|
|
|
|
var securityCache = new SecurityCache();
|
|
securityCache.AddData(tick);
|
|
Assert.AreEqual(securityCache.Price, price);
|
|
Assert.AreEqual(securityCache.BidPrice, bidPrice);
|
|
Assert.AreEqual(securityCache.AskPrice, askPrice);
|
|
Assert.AreEqual(securityCache.Volume, 0m);
|
|
|
|
tick.TickType = TickType.Trade;
|
|
securityCache = new SecurityCache();
|
|
securityCache.AddData(tick);
|
|
Assert.AreEqual(securityCache.Price, price);
|
|
Assert.AreEqual(securityCache.BidPrice, 0m);
|
|
Assert.AreEqual(securityCache.AskPrice, 0m);
|
|
Assert.AreEqual(securityCache.Volume, volume);
|
|
}
|
|
|
|
[Test]
|
|
public void GetAllData_ReturnsListOfData()
|
|
{
|
|
var cache = new SecurityCache();
|
|
cache.StoreData(new []
|
|
{
|
|
new CustomDataBitcoinAlgorithm.Bitcoin{Ask = 1m},
|
|
new CustomDataBitcoinAlgorithm.Bitcoin{Ask = 2m}
|
|
}, typeof(CustomDataBitcoinAlgorithm.Bitcoin));
|
|
|
|
var data = cache.GetAll<CustomDataBitcoinAlgorithm.Bitcoin>().ToList();
|
|
Assert.AreEqual(2, data.Count);
|
|
Assert.AreEqual(1m, data[0].Ask);
|
|
Assert.AreEqual(2m, data[1].Ask);
|
|
}
|
|
|
|
[Test]
|
|
public void GetAllData_ReturnsListOfDataOnTargetCache()
|
|
{
|
|
var cache = new SecurityCache();
|
|
cache.StoreData(new[]
|
|
{
|
|
new CustomDataBitcoinAlgorithm.Bitcoin{Ask = 1m},
|
|
new CustomDataBitcoinAlgorithm.Bitcoin{Ask = 2m}
|
|
}, typeof(CustomDataBitcoinAlgorithm.Bitcoin));
|
|
|
|
var targetToModify = new SecurityCache();
|
|
SecurityCache.ShareTypeCacheInstance(cache, targetToModify);
|
|
|
|
var data = targetToModify.GetAll<CustomDataBitcoinAlgorithm.Bitcoin>().ToList();
|
|
Assert.AreEqual(2, data.Count);
|
|
Assert.AreEqual(1m, data[0].Ask);
|
|
Assert.AreEqual(2m, data[1].Ask);
|
|
}
|
|
|
|
[Test]
|
|
public void SecurityCacheGetDataWorksCorrectly()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var testModule = PyModule.FromString("TestSecurityCacheGetData",
|
|
@"
|
|
from AlgorithmImports import *
|
|
from QuantConnect.Tests import *
|
|
|
|
class MyCustomDataType(PythonData):
|
|
def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live: bool) -> SubscriptionDataSource:
|
|
fileName = LeanData.GenerateZipFileName(Symbols.SPY, date, Resolution.MINUTE, config.TickType)
|
|
|
|
def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live: bool) -> BaseData:
|
|
data = line.split(',')
|
|
result = MyCustomDataType()
|
|
|
|
def get_security_cache_quote():
|
|
securityCache = SecurityCache()
|
|
quoteBar = QuoteBar()
|
|
securityCache.add_data(quoteBar)
|
|
return securityCache.get_data(QuoteBar)
|
|
|
|
def get_security_cache_trade():
|
|
securityCache = SecurityCache()
|
|
tradeBar = TradeBar()
|
|
securityCache.add_data(tradeBar)
|
|
return securityCache.get_data(TradeBar)
|
|
|
|
def get_security_cache_margin_interest_rate():
|
|
securityCache = SecurityCache()
|
|
marginInterestRate = MarginInterestRate()
|
|
securityCache.add_data(marginInterestRate)
|
|
return securityCache.get_data(MarginInterestRate)
|
|
|
|
def get_security_cache_custom_data_type():
|
|
securityCache = SecurityCache()
|
|
customData = PythonData(MyCustomDataType())
|
|
securityCache.add_data(customData)
|
|
return securityCache.get_data(MyCustomDataType)
|
|
");
|
|
|
|
// Test if the GetData method correctly handles QuoteBar type
|
|
var quoteType = testModule.GetAttr("get_security_cache_quote").Invoke();
|
|
Assert.IsFalse(quoteType.IsNone());
|
|
Assert.DoesNotThrow(() => quoteType.As<QuoteBar>());
|
|
|
|
// Test if the GetData method correctly handles TradeBar type
|
|
var tradeType = testModule.GetAttr("get_security_cache_trade").Invoke();
|
|
Assert.IsFalse(tradeType.IsNone());
|
|
Assert.DoesNotThrow(() => tradeType.As<TradeBar>());
|
|
|
|
// Test if the GetData method correctly handles MarginInterestRate type
|
|
var marginInterestType = testModule.GetAttr("get_security_cache_margin_interest_rate").Invoke();
|
|
Assert.IsFalse(marginInterestType.IsNone());
|
|
Assert.DoesNotThrow(() => marginInterestType.As<MarginInterestRate>());
|
|
|
|
// Test if the GetData method correctly handles custom data type (PythonData)
|
|
var customDataType = testModule.GetAttr("get_security_cache_custom_data_type").Invoke();
|
|
Assert.IsFalse(customDataType.IsNone());
|
|
Assert.DoesNotThrow(() => customDataType.As<PythonData>());
|
|
}
|
|
}
|
|
|
|
private void AddDataAndAssertChanges(SecurityCache cache, SecuritySeedData seedType, SecuritySeedData dataType, BaseData data, Dictionary<string, string> cacheToBaseDataPropertyMap = null)
|
|
{
|
|
var before = JObject.FromObject(cache);
|
|
var dataSnapshot = JObject.FromObject(data);
|
|
|
|
cache.AddData(data);
|
|
var after = JObject.FromObject(cache);
|
|
|
|
var updatedCacheProperties = GetPropertiesBy(dataType);
|
|
if (seedType == SecuritySeedData.QuoteBar && (dataType == SecuritySeedData.QuoteBar || dataType == SecuritySeedData.TradeBar))
|
|
{
|
|
// these properties aren't updated when previous data is quote bar at same time as a new IBar
|
|
updatedCacheProperties = updatedCacheProperties.Where(p =>
|
|
p != "Open" &&
|
|
p != "High" &&
|
|
p != "Low" &&
|
|
p != "Close" &&
|
|
p != "Price"
|
|
).ToArray();
|
|
}
|
|
|
|
foreach (var property in before.Properties())
|
|
{
|
|
string dataPropertyName = null;
|
|
if (updatedCacheProperties.Contains(property.Name))
|
|
{
|
|
if (cacheToBaseDataPropertyMap?.TryGetValue(property.Name, out dataPropertyName) == true)
|
|
{
|
|
// avoiding failures due to decimal <> long in JToken.DeepEquals
|
|
var e = dataSnapshot.SelectToken(dataPropertyName).ToString();
|
|
var a = after.SelectToken(property.Name).ToString();
|
|
Assert.AreEqual(e, a, $"{property.Name}: Expected {e}. Actual {a}");
|
|
}
|
|
else
|
|
{
|
|
dataSnapshot.IsEqualTo(after, property.Name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
before.IsEqualTo(after, property.Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum SecuritySeedData
|
|
{
|
|
None,
|
|
TradeTick,
|
|
QuoteTick,
|
|
OpenInterestTick,
|
|
OpenInterest,
|
|
TradeBar,
|
|
QuoteBar,
|
|
Fundamentals
|
|
}
|
|
|
|
public string[] GetPropertiesBy(SecuritySeedData type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case SecuritySeedData.None:
|
|
return new string[0];
|
|
|
|
case SecuritySeedData.OpenInterest:
|
|
return new[] { "OpenInterest" };
|
|
|
|
case SecuritySeedData.OpenInterestTick:
|
|
return new[] { "OpenInterest" };
|
|
|
|
case SecuritySeedData.TradeTick:
|
|
return new[] {"Price", "Volume"};
|
|
|
|
case SecuritySeedData.QuoteTick:
|
|
return new[] {"AskPrice", "AskSize", "BidPrice", "BidSize"};
|
|
|
|
case SecuritySeedData.TradeBar:
|
|
return new[] {"Price", "Volume", "Open", "High", "Low", "Close"};
|
|
|
|
case SecuritySeedData.QuoteBar:
|
|
return new[] { "Price", "Open", "High", "Low", "Close", "AskPrice", "AskSize", "BidPrice", "BidSize" };
|
|
|
|
case SecuritySeedData.Fundamentals:
|
|
// fundamentals data does not modify security cache properties
|
|
return new string[0];
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
|
}
|
|
}
|
|
|
|
private static TestCaseData[] GetSecurityCacheInitialStates()
|
|
{
|
|
var defaultInstance = new SecurityCache();
|
|
|
|
var tradeTick = new SecurityCache();
|
|
tradeTick.AddData(new Tick
|
|
{
|
|
Value = 5,
|
|
Quantity = 6,
|
|
EndTime = ReferenceTime,
|
|
TickType = TickType.Trade
|
|
});
|
|
|
|
var quoteTick = new SecurityCache();
|
|
quoteTick.AddData(new Tick
|
|
{
|
|
AskPrice = 1,
|
|
AskSize = 2,
|
|
BidPrice = 3,
|
|
BidSize = 4,
|
|
EndTime = ReferenceTime,
|
|
TickType = TickType.Quote
|
|
});
|
|
|
|
var openInterestTick = new SecurityCache();
|
|
openInterestTick.AddData(new Tick
|
|
{
|
|
Value = 24,
|
|
EndTime = ReferenceTime,
|
|
TickType = TickType.OpenInterest
|
|
});
|
|
|
|
var openInterest = new SecurityCache();
|
|
openInterest.AddData(new OpenInterest
|
|
{
|
|
Value = 23,
|
|
EndTime = ReferenceTime,
|
|
});
|
|
|
|
var tradeBar = new SecurityCache();
|
|
tradeBar.AddData(new TradeBar
|
|
{
|
|
Open = 7,
|
|
High = 8,
|
|
Low = 9,
|
|
Close = 10,
|
|
Volume = 11,
|
|
EndTime = ReferenceTime
|
|
});
|
|
|
|
var quoteBar = new SecurityCache();
|
|
quoteBar.AddData(new QuoteBar
|
|
{
|
|
Ask = new Bar(12, 13, 14, 15),
|
|
Bid = new Bar(16, 17, 18, 19),
|
|
LastAskSize = 20,
|
|
LastBidSize = 21,
|
|
Value = 22,
|
|
EndTime = ReferenceTime
|
|
});
|
|
|
|
var fundamentals = new SecurityCache();
|
|
fundamentals.AddData(new Fundamental(ReferenceTime, Symbols.AAPL)
|
|
{
|
|
Value = 23
|
|
});
|
|
|
|
return new[]
|
|
{
|
|
new TestCaseData(defaultInstance, SecuritySeedData.None).SetName("Default Instance"),
|
|
new TestCaseData(tradeTick, SecuritySeedData.TradeTick).SetName("Seeded w/ Trade Tick"),
|
|
new TestCaseData(quoteTick, SecuritySeedData.QuoteTick).SetName("Seeded w/ Quote Tick"),
|
|
new TestCaseData(openInterestTick, SecuritySeedData.OpenInterestTick).SetName("Seeded w/ OpenInterest Tick"),
|
|
new TestCaseData(openInterest, SecuritySeedData.OpenInterest).SetName("Seeded w/ OpenInterest"),
|
|
new TestCaseData(tradeBar, SecuritySeedData.TradeBar).SetName("Seeded w/ TradeBar"),
|
|
new TestCaseData(quoteBar, SecuritySeedData.QuoteBar).SetName("Seeded w/ QuoteBar"),
|
|
new TestCaseData(fundamentals, SecuritySeedData.Fundamentals).SetName("Seeded w/ Fundamentals")
|
|
};
|
|
}
|
|
|
|
private IReadOnlyCollection<BaseData> GenerateData(MarketDataType type, int quantity, bool sameTime, DateTime? firstTimeStamp = null)
|
|
{
|
|
var time = firstTimeStamp ?? DateTime.Now;
|
|
var outputTradeBars = new List<BaseData>();
|
|
for (var i = 0; i < quantity; i++)
|
|
{
|
|
var rnd = _rng.Next(minValue: 50, maxValue: 150);
|
|
var ask = new Bar { Close = 1m * rnd, High = 1.2m * rnd, Low = 0.9m * rnd, Open = 1.1m * rnd };
|
|
var bid = new Bar { Close = 0.9m * rnd, High = 1.1m * rnd, Low = 0.8m * rnd, Open = 1m * rnd };
|
|
BaseData data = new TradeBar();
|
|
switch (type)
|
|
{
|
|
case MarketDataType.TradeBar:
|
|
data = new TradeBar
|
|
{
|
|
Close = (ask.Close + bid.Close) / 2,
|
|
Open = (ask.Open + bid.Open) / 2,
|
|
High = (ask.High + bid.High) / 2,
|
|
Low = (ask.Low + bid.Low) / 2,
|
|
Volume = 1,
|
|
DataType = type
|
|
};
|
|
break;
|
|
case MarketDataType.Tick:
|
|
data = new Tick
|
|
{
|
|
AskPrice = ask.Close,
|
|
BidPrice = bid.Close,
|
|
AskSize = 1,
|
|
BidSize = 1
|
|
};
|
|
break;
|
|
case MarketDataType.QuoteBar:
|
|
data = new QuoteBar
|
|
{
|
|
Ask = ask,
|
|
Bid = bid,
|
|
LastAskSize = 1,
|
|
LastBidSize = 1,
|
|
DataType = type,
|
|
Value = (ask.Close + bid.Close) / 2
|
|
};
|
|
break;
|
|
case MarketDataType.Auxiliary:
|
|
case MarketDataType.OptionChain:
|
|
case MarketDataType.FuturesChain:
|
|
case MarketDataType.Base:
|
|
throw new NotImplementedException("Cases not tested yet");
|
|
}
|
|
data.Time = time;
|
|
data.EndTime = time.AddSeconds(value: 1);
|
|
time = sameTime ? time : time.AddSeconds(value: 1);
|
|
outputTradeBars.Add(data);
|
|
}
|
|
return outputTradeBars;
|
|
}
|
|
}
|
|
}
|