chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
+577
View File
@@ -0,0 +1,577 @@
/*
* 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.IO;
using System.Linq;
using System.Text;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Securities;
using QuantConnect.ToolBox;
using QuantConnect.Util;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.ToolBox
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class LeanDataReaderTests
{
string _dataDirectory = "../../../Data/";
DateTime _fromDate = new DateTime(2013, 10, 7);
DateTime _toDate = new DateTime(2013, 10, 11);
[Test, Parallelizable(ParallelScope.Self)]
public void LoadsEquity_Daily_SingleEntryZip()
{
var dataPath = LeanData.GenerateZipFilePath(Globals.DataFolder, Symbols.AAPL, DateTime.UtcNow, Resolution.Daily, TickType.Trade);
var leanDataReader = new LeanDataReader(dataPath);
var data = leanDataReader.Parse().ToList();
Assert.AreEqual(5849, data.Count);
Assert.IsTrue(data.All(baseData => baseData.Symbol == Symbols.AAPL && baseData is TradeBar));
}
#region futures
[Test, Parallelizable(ParallelScope.Self)]
public void ReadsEntireZipFileEntries_OpenInterest()
{
var baseFuture = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, SecurityIdentifier.DefaultDate);
var filePath = LeanData.GenerateZipFilePath(Globals.DataFolder, baseFuture, new DateTime(2013, 10, 06), Resolution.Minute, TickType.OpenInterest);
var leanDataReader = new LeanDataReader(filePath);
var data = leanDataReader.Parse()
.ToList()
.GroupBy(baseData => baseData.Symbol)
.Select(grp => grp.ToList())
.OrderBy(list => list[0].Symbol)
.ToList();
Assert.AreEqual(5, data.Count);
Assert.IsTrue(data.All(kvp => kvp.Count == 1));
foreach (var dataForSymbol in data)
{
Assert.IsTrue(dataForSymbol[0] is OpenInterest);
Assert.IsFalse(dataForSymbol[0].Symbol.IsCanonical());
Assert.AreEqual(Futures.Indices.SP500EMini, dataForSymbol[0].Symbol.ID.Symbol);
Assert.AreNotEqual(0, dataForSymbol[0]);
}
}
[Test, Parallelizable(ParallelScope.Self)]
public void ReadsEntireZipFileEntries_Trade()
{
var baseFuture = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, SecurityIdentifier.DefaultDate);
var filePath = LeanData.GenerateZipFilePath(Globals.DataFolder, baseFuture, new DateTime(2013, 10, 06), Resolution.Minute, TickType.Trade);
var leanDataReader = new LeanDataReader(filePath);
var data = leanDataReader.Parse()
.ToList()
.GroupBy(baseData => baseData.Symbol)
.Select(grp => grp.ToList())
.OrderBy(list => list[0].Symbol)
.ToList();
Assert.AreEqual(2, data.Count);
foreach (var dataForSymbol in data)
{
Assert.IsTrue(dataForSymbol[0] is TradeBar);
Assert.IsFalse(dataForSymbol[0].Symbol.IsCanonical());
Assert.AreEqual(Futures.Indices.SP500EMini, dataForSymbol[0].Symbol.ID.Symbol);
}
Assert.AreEqual(118, data[0].Count);
Assert.AreEqual(10, data[1].Count);
}
[Test, Parallelizable(ParallelScope.Self)]
public void ReadsEntireZipFileEntries_Quote()
{
var baseFuture = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, SecurityIdentifier.DefaultDate);
var filePath = LeanData.GenerateZipFilePath(Globals.DataFolder, baseFuture, new DateTime(2013, 10, 06), Resolution.Minute, TickType.Quote);
var leanDataReader = new LeanDataReader(filePath);
var data = leanDataReader.Parse()
.ToList()
.GroupBy(baseData => baseData.Symbol)
.Select(grp => grp.ToList())
.OrderBy(list => list[0].Symbol)
.ToList();
Assert.AreEqual(5, data.Count);
foreach (var dataForSymbol in data)
{
Assert.IsTrue(dataForSymbol[0] is QuoteBar);
Assert.IsFalse(dataForSymbol[0].Symbol.IsCanonical());
Assert.AreEqual(Futures.Indices.SP500EMini, dataForSymbol[0].Symbol.ID.Symbol);
}
Assert.AreEqual(10, data[0].Count);
Assert.AreEqual(13, data[1].Count);
Assert.AreEqual(52, data[2].Count);
Assert.AreEqual(155, data[3].Count);
Assert.AreEqual(100, data[4].Count);
}
[Test]
public void ReadFutureChainData()
{
var canonicalFutures = new Dictionary<Symbol, string>()
{
{ Symbol.Create(Futures.Indices.SP500EMini, SecurityType.Future, Market.CME),
"ES20Z13|ES21H14|ES20M14|ES19U14|ES19Z14" },
{Symbol.Create(Futures.Metals.Gold, SecurityType.Future, Market.COMEX),
"GC29V13|GC26X13|GC27Z13|GC26G14|GC28J14|GC26M14|GC27Q14|GC29V14|GC29Z14|GC25G15|GC28J15|GC26M15|GC27Q15|GC29Z15|GC28M16|GC28Z16|GC28M17|GC27Z17|GC27M18|GC27Z18|GC26M19"},
};
var tickTypes = new[] { TickType.Trade, TickType.Quote, TickType.OpenInterest };
var resolutions = new[] { Resolution.Minute };
foreach (var canonical in canonicalFutures)
{
foreach (var res in resolutions)
{
foreach (var tickType in tickTypes)
{
var futures = LoadFutureChain(canonical.Key, _fromDate, tickType, res);
string chain = string.Join("|", futures.Select(f => f.Value));
if (tickType == TickType.Quote) //only quotes have the full chain!
Assert.AreEqual(canonical.Value, chain);
foreach (var future in futures)
{
string csv = LoadFutureData(future, tickType, res);
Assert.IsTrue(!string.IsNullOrEmpty(csv));
}
}
}
}
}
private List<Symbol> LoadFutureChain(Symbol baseFuture, DateTime date, TickType tickType, Resolution res)
{
var filePath = LeanData.GenerateZipFilePath(_dataDirectory, baseFuture, date, res, tickType);
//load future chain first
var config = new SubscriptionDataConfig(typeof(ZipEntryNameData), baseFuture, res,
TimeZones.NewYork, TimeZones.NewYork, false, false, false, false, tickType);
var factory = new ZipEntryNameSubscriptionDataSourceReader(TestGlobals.DataCacheProvider, config, date, false);
var result = factory.Read(new SubscriptionDataSource(filePath, SubscriptionTransportMedium.LocalFile, FileFormat.ZipEntryName))
.Select(s => s.Symbol).ToList();
return result;
}
private string LoadFutureData(Symbol future, TickType tickType, Resolution res)
{
var dataType = LeanData.GetDataType(res, tickType);
var config = new SubscriptionDataConfig(dataType, future, res,
TimeZones.NewYork, TimeZones.NewYork, false, false, false, false, tickType);
var date = _fromDate;
var sb = new StringBuilder();
while (date <= _toDate)
{
var leanDataReader = new LeanDataReader(config, future, res, date, _dataDirectory);
foreach (var bar in leanDataReader.Parse())
{
//write base data type back to string
sb.AppendLine(LeanData.GenerateLine(bar, SecurityType.Future, res));
}
date = date.AddDays(1);
}
var csv = sb.ToString();
return csv;
}
[Test]
public void GenerateDailyAndHourlyFutureDataFromMinutes()
{
var tickTypes = new[] { TickType.Trade, TickType.Quote, TickType.OpenInterest };
var futures = new[] { Symbol.Create(Futures.Indices.SP500EMini, SecurityType.Future, Market.CME),
Symbol.Create(Futures.Metals.Gold, SecurityType.Future, Market.COMEX)};
var resolutions = new[] { Resolution.Hour, Resolution.Daily };
foreach (var future in futures)
foreach (var res in resolutions)
foreach (var tickType in tickTypes)
ConvertMinuteFuturesData(future, tickType, res);
}
private void ConvertMinuteFuturesData(Symbol canonical, TickType tickType, Resolution outputResolution, Resolution inputResolution = Resolution.Minute)
{
var timeSpans = new Dictionary<Resolution, TimeSpan>()
{
{ Resolution.Daily, TimeSpan.FromHours(24)},
{ Resolution.Hour, TimeSpan.FromHours(1)},
};
var timeSpan = timeSpans[outputResolution];
var tickTypeConsolidatorMap = new Dictionary<TickType, Func<IDataConsolidator>>()
{
{TickType.Quote, () => new QuoteBarConsolidator(timeSpan)},
{TickType.OpenInterest, ()=> new OpenInterestConsolidator(timeSpan)},
{TickType.Trade, ()=> new TradeBarConsolidator(timeSpan) }
};
var consolidators = new Dictionary<string, IDataConsolidator>();
var configs = new Dictionary<string, SubscriptionDataConfig>();
var outputFiles = new Dictionary<string, StringBuilder>();
var futures = new Dictionary<string, Symbol>();
var date = _fromDate;
while (date <= _toDate)
{
var futureChain = LoadFutureChain(canonical, date, tickType, inputResolution);
foreach (var future in futureChain)
{
if (!futures.ContainsKey(future.Value))
{
futures[future.Value] = future;
var config = new SubscriptionDataConfig(LeanData.GetDataType(outputResolution, tickType),
future, inputResolution, TimeZones.NewYork, TimeZones.NewYork,
false, false, false, false, tickType);
configs[future.Value] = config;
consolidators[future.Value] = tickTypeConsolidatorMap[tickType].Invoke();
var sb = new StringBuilder();
outputFiles[future.Value] = sb;
consolidators[future.Value].DataConsolidated += (sender, bar) =>
{
sb.Append(LeanData.GenerateLine(bar, SecurityType.Future, outputResolution) + Environment.NewLine);
};
}
var leanDataReader = new LeanDataReader(configs[future.Value], future, inputResolution, date, _dataDirectory);
var consolidator = consolidators[future.Value];
foreach (var bar in leanDataReader.Parse())
{
consolidator.Update(bar);
}
}
date = date.AddDays(1);
}
//write all results
foreach (var consolidator in consolidators.Values)
consolidator.Scan(date);
var zip = LeanData.GenerateRelativeZipFilePath(canonical, _fromDate, outputResolution, tickType);
var zipPath = Path.Combine(_dataDirectory, zip);
var fi = new FileInfo(zipPath);
if (!fi.Directory.Exists)
fi.Directory.Create();
foreach (var future in futures.Values)
{
var zipEntry = LeanData.GenerateZipEntryName(future, _fromDate, outputResolution, tickType);
var sb = outputFiles[future.Value];
//Uncomment to write zip files
//QuantConnect.Compression.ZipCreateAppendData(zipPath, zipEntry, sb.ToString());
Assert.IsTrue(sb.Length > 0);
}
}
#endregion
[Test, TestCaseSource(nameof(OptionAndFuturesCases))]
public void ReadLeanFutureAndOptionDataFromFilePath(string composedFilePath, Symbol symbol, int rowsInfile, double sumValue)
{
// Act
var ldr = new LeanDataReader(composedFilePath);
var data = ldr.Parse().ToList();
// Assert
Assert.True(symbol.Equals(data.First().Symbol));
Assert.AreEqual(rowsInfile, data.Count);
Assert.AreEqual(sumValue, data.Sum(c => c.Value));
}
public static object[] OptionAndFuturesCases =
{
new object[]
{
"../../../Data/future/cme/minute/es/20131008_quote.zip#20131008_es_minute_quote_201312.csv",
LeanData
.ReadSymbolFromZipEntry(Symbol.Create(Futures.Indices.SP500EMini, SecurityType.Future, Market.CME),
Resolution.Minute, "20131008_es_minute_quote_201312.csv"),
1411,
2346061.875
},
new object[]
{
"../../../Data/future/comex/minute/gc/20131010_trade.zip#20131010_gc_minute_trade_201312.csv",
LeanData.ReadSymbolFromZipEntry(Symbol.Create(Futures.Metals.Gold, SecurityType.Future, Market.COMEX),
Resolution.Minute, "20131010_gc_minute_trade_201312.csv"),
1379,
1791800.9
},
new object[]
{
"../../../Data/future/comex/tick/gc/20131009_quote.zip#20131009_gc_tick_quote_201406.csv",
LeanData.ReadSymbolFromZipEntry(Symbol.Create(Futures.Metals.Gold, SecurityType.Future, Market.COMEX),
Resolution.Tick, "20131009_gc_tick_quote_201406.csv"),
197839,
259245064.8
},
new object[]
{
"../../../Data/future/comex/tick/gc/20131009_trade.zip#20131009_gc_tick_trade_201312.csv",
LeanData.ReadSymbolFromZipEntry(Symbol.Create(Futures.Metals.Gold, SecurityType.Future, Market.COMEX),
Resolution.Tick, "20131009_gc_tick_trade_201312.csv"),
64712,
84596673.8
},
new object[]
{
"../../../Data/future/cme/minute/es/20131010_openinterest.zip#20131010_es_minute_openinterest_201312.csv",
LeanData
.ReadSymbolFromZipEntry(Symbol.Create(Futures.Indices.SP500EMini, SecurityType.Future, Market.CME),
Resolution.Minute, "20131010_es_minute_openinterest_201312.csv"),
3,
8119169
},
new object[]
{
"../../../Data/future/comex/tick/gc/20131009_openinterest.zip#20131009_gc_tick_openinterest_201310.csv",
LeanData.ReadSymbolFromZipEntry(Symbol.Create(Futures.Metals.Gold, SecurityType.Future, Market.COMEX),
Resolution.Tick, "20131009_gc_tick_openinterest_201310.csv"),
4,
1312
},
new object[]
{
"../../../Data/option/usa/minute/aapl/20140606_quote_american.zip#20140606_aapl_minute_quote_american_put_7500000_20141018.csv",
LeanData.ReadSymbolFromZipEntry(Symbol.Create("AAPL", SecurityType.Option, Market.USA),
Resolution.Minute,
"20140606_aapl_minute_quote_american_put_7500000_20141018.csv"),
391,
44210.7
},
new object[]
{
"../../../Data/option/usa/minute/aapl/20140606_trade_american.zip#20140606_aapl_minute_trade_american_call_6475000_20140606.csv",
LeanData.ReadSymbolFromZipEntry(Symbol.Create("AAPL", SecurityType.Option, Market.USA),
Resolution.Minute,
"20140606_aapl_minute_trade_american_call_6475000_20140606.csv"),
374,
745.35
},
new object[]
{
"../../../Data/option/usa/minute/goog/20151224_openinterest_american.zip#20151224_goog_minute_openinterest_american_call_3000000_20160115.csv",
LeanData.ReadSymbolFromZipEntry(Symbol.Create("GOOG", SecurityType.Option, Market.USA),
Resolution.Minute,
"20151224_goog_minute_openinterest_american_call_3000000_20160115.csv"),
1,
38
},
new object[]
{
"../../../Data/option/usa/daily/aapl_2014_openinterest_american.zip#aapl_openinterest_american_call_1950000_20150117.csv",
LeanData.ReadSymbolFromZipEntry(Symbol.Create("AAPL", SecurityType.Option, Market.USA),
Resolution.Daily,
"aapl_openinterest_american_call_1950000_20150117.csv"),
2,
824
},
new object[]
{
"../../../Data/option/usa/daily/aapl_2014_trade_american.zip#aapl_trade_american_call_5400000_20141018.csv",
LeanData.ReadSymbolFromZipEntry(Symbol.Create("AAPL", SecurityType.Option, Market.USA),
Resolution.Daily,
"aapl_trade_american_call_5400000_20141018.csv"),
1,
109.9
},
new object[]
{
"../../../Data/option/usa/daily/aapl_2014_quote_american.zip#aapl_quote_american_call_307100_20150117.csv",
LeanData.ReadSymbolFromZipEntry(Symbol.Create("AAPL", SecurityType.Option, Market.USA),
Resolution.Daily,
"aapl_quote_american_call_307100_20150117.csv"),
1,
63.3
}
};
[Test, TestCaseSource(nameof(SpotMarketCases))]
public void ReadLeanSpotMarketsSecuritiesDataFromFilePath(string securityType, string market, string resolution, string ticker, string fileName, int rowsInfile, double sumValue)
{
// Arrange
var filepath = GenerateFilepathForTesting(_dataDirectory, securityType, market, resolution, ticker, fileName);
SecurityType securityTypeEnum;
Enum.TryParse(securityType, true, out securityTypeEnum);
var symbol = Symbol.Create(ticker, securityTypeEnum, market);
// Act
var ldr = new LeanDataReader(filepath);
var data = ldr.Parse().ToList();
// Assert
Assert.True(symbol.Equals(data.First().Symbol));
Assert.AreEqual(rowsInfile, data.Count);
Assert.AreEqual(sumValue, data.Sum(c => c.Value));
}
public static object[] SpotMarketCases =
{
//TODO: generate Low resolution sample data for equities
new object[] {"equity", "usa", "daily", "aig", "aig.zip", 5849, 340770.5801},
new object[] {"equity", "usa", "minute", "aapl", "20140605_trade.zip", 686, 443184.58},
new object[] {"equity", "usa", "minute", "ibm", "20131010_quote.zip", 584, 107061.125},
new object[] {"equity", "usa", "second", "ibm", "20131010_trade.zip", 5060, 929385.34},
new object[] {"equity", "usa", "tick", "bac", "20131011_trade.zip", 112177, 1591680.73},
new object[] {"forex", "oanda", "minute", "eurusd", "20140502_quote.zip", 1222, 1693.578875},
new object[] {"forex", "oanda", "second", "nzdusd", "20140514_quote.zip", 18061, 15638.724575},
new object[] {"forex", "oanda", "tick", "eurusd", "20140507_quote.zip", 41367, 57598.54664},
new object[] {"cfd", "oanda", "hour", "xauusd", "xauusd.zip", 76499, 90453133.772 },
new object[] {"crypto", "coinbase", "second", "btcusd", "20161008_trade.zip", 3453, 2137057.57},
new object[] {"crypto", "coinbase", "minute", "ethusd", "20170903_trade.zip", 1440, 510470.66},
new object[] {"crypto", "coinbase", "daily", "btcusd", "btcusd_trade.zip", 1318, 3725052.03},
};
private static IEnumerable<TestCaseData> MinuteZipEntryFileNames
{
get
{
yield return new TestCaseData(
Symbol.CreateCanonicalOption(Symbol.CreateFuture(Futures.Grains.Corn, Market.CBOT, new(2025, 12, 12))),
"20251103_ozc_minute_trade_american_call_41000_20251121.csv",
4.1m,
OptionRight.Call,
new DateTime(2025, 11, 21));
yield return new TestCaseData(
Symbol.CreateCanonicalOption(Symbol.CreateFuture(Futures.Currencies.JPY, Market.CME, new(2025, 12, 15))),
"20251103_jpu_minute_trade_american_call_62.50000_20260109.csv",
0.00625m,
OptionRight.Call,
new DateTime(2026, 01, 09));
yield return new TestCaseData(
Symbol.CreateCanonicalOption(Symbol.CreateFuture(Futures.Currencies.AUD, Market.CME, new(2025, 12, 15))),
"20251103_adu_minute_openinterest_american_put_6350_20251205.csv",
0.635m,
OptionRight.Put,
new DateTime(2025, 12, 05));
yield return new TestCaseData(
Symbol.CreateCanonicalOption(Symbol.CreateFuture(Futures.Currencies.AUD, Market.CME, new(2025, 12, 15))),
"20251103_adu_minute_quote_american_call_8400_20260306.csv",
0.84m,
OptionRight.Call,
new DateTime(2026, 03, 06));
yield return new TestCaseData(
Symbol.CreateCanonicalOption(Symbol.CreateFuture(Futures.Currencies.NZD, Market.CME, new(2025, 12, 15))),
"20251103_6n_minute_quote_american_call_5600_20260403.csv",
0.56m,
OptionRight.Call,
new DateTime(2026, 04, 03));
yield return new TestCaseData(
Symbol.CreateCanonicalOption(Symbol.CreateFuture(Futures.Meats.LiveCattle, Market.CME, new(2025, 12, 31))),
"20251103_le_minute_quote_american_call_21800_20251205.csv",
2.18m,
OptionRight.Call,
new DateTime(2025, 12, 05));
}
}
[TestCaseSource(nameof(MinuteZipEntryFileNames))]
public void ReadSymbolFromZipEntryShouldParseFileNameWithFloatingNumber(Symbol rootSymbol, string fileNameCsv,
decimal expectedStrike, OptionRight expectedOptionRight, DateTime expectedExpiry)
{
var actualSymbol = LeanData.ReadSymbolFromZipEntry(rootSymbol, Resolution.Minute, fileNameCsv);
Assert.AreEqual(expectedStrike, actualSymbol.ID.StrikePrice);
Assert.AreEqual(expectedOptionRight, actualSymbol.ID.OptionRight);
Assert.AreEqual(expectedExpiry, actualSymbol.ID.Date);
}
public static string GenerateFilepathForTesting(string dataDirectory, string securityType, string market, string resolution, string ticker,
string fileName)
{
string filepath;
if (resolution == "daily" || resolution == "hour")
{
filepath = Path.Combine(dataDirectory, securityType, market, resolution, fileName);
}
else
{
filepath = Path.Combine(dataDirectory, securityType, market, resolution, ticker, fileName);
}
return filepath;
}
private class ZipEntryNameData : BaseData
{
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
var symbol = LeanData.ReadSymbolFromZipEntry(config.Symbol, config.Resolution, line);
return new ZipEntryNameData { Time = date, Symbol = symbol };
}
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType);
return new SubscriptionDataSource(source, SubscriptionTransportMedium.LocalFile, FileFormat.ZipEntryName);
}
}
}
}
+564
View File
@@ -0,0 +1,564 @@
/*
* 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.IO;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Util;
using QuantConnect.Securities;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using NodaTime;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.HistoricalData;
using QuantConnect.Tests.Algorithm;
using QuantConnect.ToolBox;
using System.Globalization;
namespace QuantConnect.Tests.ToolBox
{
[TestFixture]
public class LeanDataWriterTests
{
private readonly string _dataDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
private Symbol _forex;
private Symbol _cfd;
private Symbol _equity;
private Symbol _crypto;
private DateTime _date;
[OneTimeSetUp]
public void Setup()
{
_forex = Symbol.Create("EURUSD", SecurityType.Forex, Market.FXCM);
_cfd = Symbol.Create("BCOUSD", SecurityType.Cfd, Market.Oanda);
_equity = Symbol.Create("spy", SecurityType.Equity, Market.USA);
_date = Parse.DateTime("3/16/2017 12:00:00 PM");
_crypto = Symbol.Create("BTCUSD", SecurityType.Crypto, Market.GDAX);
}
private List<Tick> GetTicks(Symbol sym)
{
return new List<Tick>()
{
new Tick(Parse.DateTime("3/16/2017 12:00:00 PM"), sym, 1.0m, 2.0m),
new Tick(Parse.DateTime("3/16/2017 12:00:01 PM"), sym, 3.0m, 4.0m),
new Tick(Parse.DateTime("3/16/2017 12:00:02 PM"), sym, 5.0m, 6.0m),
};
}
private List<QuoteBar> GetQuoteBars(Symbol sym)
{
return new List<QuoteBar>()
{
new QuoteBar(Parse.DateTime("3/16/2017 12:00:00 PM"), sym, new Bar(1m, 2m, 3m, 4m), 1, new Bar(5m, 6m, 7m, 8m), 2),
new QuoteBar(Parse.DateTime("3/16/2017 12:00:01 PM"), sym, new Bar(11m, 21m, 31m, 41m), 3, new Bar(51m, 61m, 71m, 81m), 4),
new QuoteBar(Parse.DateTime("3/16/2017 12:00:02 PM"), sym, new Bar(10m, 20m, 30m, 40m), 5, new Bar(50m, 60m, 70m, 80m), 6),
};
}
[Test]
public void LeanDataWriter_MultipleDays()
{
var leanDataWriter = new LeanDataWriter(Resolution.Second, _forex, _dataDirectory, TickType.Quote);
var sourceData = new List<QuoteBar>
{
new (Parse.DateTime("3/16/2021 12:00:00 PM"), _forex, new Bar(1m, 2m, 3m, 4m), 1, new Bar(5m, 6m, 7m, 8m), 2)
};
for (var i = 1; i < 100; i++)
{
sourceData.Add(new QuoteBar(sourceData.Last().Time.AddDays(1),
_forex,
new Bar(1m, 2m, 3m, 4m),
1, new Bar(5m, 6m, 7m, 8m),
2));
}
leanDataWriter.Write(sourceData);
foreach (var bar in sourceData)
{
var filePath = LeanData.GenerateZipFilePath(_dataDirectory, _forex, bar.Time, Resolution.Second, TickType.Quote);
Assert.IsTrue(File.Exists(filePath));
Assert.IsFalse(File.Exists(filePath + ".tmp"));
var data = QuantConnect.Compression.Unzip(filePath).Single();
Assert.AreEqual(1, data.Value.Count);
Assert.IsTrue(data.Key.Contains(bar.Time.ToStringInvariant(DateFormat.EightCharacter)), $"Key {data.Key} BarTime: {bar.Time}");
}
}
[TestCase(true)]
[TestCase(false)]
public void Mapping(bool mapSymbol)
{
LeanDataWriter.MapFileProvider = new Lazy<IMapFileProvider>(TestGlobals.MapFileProvider);
// asset got mapped on 20080929 to SPWRA
var symbol = Symbol.Create("SPWR", SecurityType.Equity, Market.USA);
var leanDataWriter = new LeanDataWriter(Resolution.Daily, symbol, _dataDirectory, TickType.Trade, mapSymbol: mapSymbol);
var sourceData = new List<TradeBar>
{
new (new DateTime(2008, 9, 29), symbol, 10, 11, 12, 13, 2),
new (new DateTime(2008, 9, 30), symbol, 10, 11, 12, 13, 2),
};
leanDataWriter.Write(sourceData);
for (int i = 0; i < sourceData.Count; i++)
{
var bar = sourceData[i];
var expectedTicker = (i == 0 || !mapSymbol) ? "SPWR" : "SPWRA";
symbol = symbol.UpdateMappedSymbol(expectedTicker);
var filePath = LeanData.GenerateZipFilePath(_dataDirectory, symbol, bar.Time, Resolution.Daily, TickType.Trade);
Assert.IsTrue(File.Exists(filePath));
Assert.IsFalse(File.Exists(filePath + ".tmp"));
var data = QuantConnect.Compression.Unzip(filePath).Single();
Assert.AreEqual(!mapSymbol ? 2 : 1, data.Value.Count);
Assert.AreEqual($"{expectedTicker}.csv".ToLower(), data.Key, $"Key {data.Key} BarTime: {bar.Time}");
Assert.IsTrue(data.Value.Any(point => point.StartsWith(bar.Time.ToStringInvariant(DateFormat.TwelveCharacter), StringComparison.Ordinal)), $"Key {data.Key} BarTime: {bar.Time}");
}
}
[Test]
public void LeanDataWriter_CanWriteForex()
{
var filePath = LeanData.GenerateZipFilePath(_dataDirectory, _forex, _date, Resolution.Second, TickType.Quote);
var leanDataWriter = new LeanDataWriter(Resolution.Second, _forex, _dataDirectory, TickType.Quote);
leanDataWriter.Write(GetQuoteBars(_forex));
Assert.IsTrue(File.Exists(filePath));
Assert.IsFalse(File.Exists(filePath + ".tmp"));
var data = QuantConnect.Compression.Unzip(filePath);
Assert.AreEqual(data.First().Value.Count, 3);
}
[TestCase(SecurityType.FutureOption, Resolution.Second)]
[TestCase(SecurityType.Future, Resolution.Second)]
[TestCase(SecurityType.Option, Resolution.Second)]
[TestCase(SecurityType.Option, Resolution.Daily)]
[TestCase(SecurityType.Future, Resolution.Daily)]
[TestCase(SecurityType.FutureOption, Resolution.Daily)]
public void LeanDataWriter_CanWriteZipWithMultipleContracts(SecurityType securityType, Resolution resolution)
{
Symbol contract1;
Symbol contract2;
if (securityType == SecurityType.Future)
{
contract1 = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2020, 02, 01));
contract2 = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2020, 03, 01));
}
else if (securityType == SecurityType.Option)
{
contract1 = Symbol.CreateOption("AAPL", Market.USA, OptionStyle.American, OptionRight.Call, 1, new DateTime(2020, 02, 01));
contract2 = Symbol.CreateOption("AAPL", Market.USA, OptionStyle.American, OptionRight.Call, 1, new DateTime(2020, 03, 01));
}
else if (securityType == SecurityType.FutureOption)
{
var underlying = Symbols.ES_Future_Chain;
contract1 = Symbol.CreateOption(underlying, Market.CME, OptionStyle.American, OptionRight.Call, 1, new DateTime(2020, 02, 01));
contract2 = Symbol.CreateOption(underlying, Market.CME, OptionStyle.American, OptionRight.Call, 1, new DateTime(2020, 03, 01));
}
else
{
throw new NotImplementedException($"{securityType} not implemented!");
}
var filePath1 = LeanData.GenerateZipFilePath(_dataDirectory, contract1, _date, resolution, TickType.Quote);
var leanDataWriter1 = new LeanDataWriter(resolution, contract1, _dataDirectory, TickType.Quote);
leanDataWriter1.Write(GetQuoteBars(contract1));
var filePath2 = LeanData.GenerateZipFilePath(_dataDirectory, contract2, _date, resolution, TickType.Quote);
var leanDataWriter2 = new LeanDataWriter(resolution, contract2, _dataDirectory, TickType.Quote);
leanDataWriter2.Write(GetQuoteBars(contract2));
Assert.AreEqual(filePath1, filePath2);
Assert.IsTrue(File.Exists(filePath1));
Assert.IsFalse(File.Exists(filePath1 + ".tmp"));
var data = QuantConnect.Compression.Unzip(filePath1).ToDictionary(x => x.Key, x => x.Value.ToList());
Assert.AreEqual(2, data.Count);
Assert.That(data.Values, Has.All.Count.EqualTo(3));
}
[Test]
public void LeanDataWriter_CanWriteCfd()
{
var filePath = LeanData.GenerateZipFilePath(_dataDirectory, _cfd, _date, Resolution.Minute, TickType.Quote);
var leanDataWriter = new LeanDataWriter(Resolution.Minute, _cfd, _dataDirectory, TickType.Quote);
leanDataWriter.Write(GetQuoteBars(_cfd));
Assert.IsTrue(File.Exists(filePath));
Assert.IsFalse(File.Exists(filePath + ".tmp"));
var data = QuantConnect.Compression.Unzip(filePath);
Assert.AreEqual(data.First().Value.Count, 3);
}
[Test]
public void LeanDataWriter_CanWriteEquity()
{
var filePath = LeanData.GenerateZipFilePath(_dataDirectory, _equity, _date, Resolution.Tick, TickType.Trade);
var leanDataWriter = new LeanDataWriter(Resolution.Tick, _equity, _dataDirectory);
leanDataWriter.Write(GetTicks(_equity));
Assert.IsTrue(File.Exists(filePath));
Assert.IsFalse(File.Exists(filePath + ".tmp"));
var data = QuantConnect.Compression.Unzip(filePath);
Assert.AreEqual(data.First().Value.Count, 3);
}
[Test]
public void LeanDataWriter_CanSupportUtf8Chars()
{
var symbol = Symbol.Create("币安人生usdt", SecurityType.CryptoFuture, Market.Binance);
var filePath = LeanData.GenerateZipFilePath(_dataDirectory, symbol, _date, Resolution.Tick, TickType.Trade);
var leanDataWriter = new LeanDataWriter(Resolution.Tick, symbol, _dataDirectory);
leanDataWriter.Write(GetTicks(symbol));
Assert.IsTrue(File.Exists(filePath));
Assert.IsFalse(File.Exists(filePath + ".tmp"));
var data = QuantConnect.Compression.Unzip(filePath);
var entry = data.First();
Assert.AreEqual(entry.Key, "20170316_币安人生usdt_tick_trade_perp.csv");
Assert.AreEqual(entry.Value.Count, 3);
}
[TestCase("CON")]
[TestCase("PRN")]
[TestCase("AUX")]
[TestCase("NUL")]
[TestCase("COM0")]
[TestCase("COM1")]
[TestCase("COM2")]
[TestCase("COM3")]
[TestCase("COM4")]
[TestCase("COM5")]
[TestCase("COM6")]
[TestCase("COM7")]
[TestCase("COM8")]
[TestCase("COM9")]
[TestCase("LPT0")]
[TestCase("LPT1")]
[TestCase("LPT2")]
[TestCase("LPT3")]
[TestCase("LPT4")]
[TestCase("LPT5")]
[TestCase("LPT6")]
[TestCase("LPT7")]
[TestCase("LPT8")]
[TestCase("LPT9")]
[Platform("Win", Reason = "The paths in these testcases are only forbidden in Windows OS")]
public void LeanDataWriterHandlesWindowsInvalidNames(string ticker)
{
var symbol = Symbol.Create(ticker, SecurityType.Equity, Market.USA);
var filePath = FileExtension.ToNormalizedPath(LeanData.GenerateZipFilePath(_dataDirectory, symbol, _date, Resolution.Tick, TickType.Trade));
var leanDataWriter = new LeanDataWriter(Resolution.Tick, symbol, _dataDirectory);
leanDataWriter.Write(GetTicks(symbol));
Assert.IsTrue(File.Exists(filePath));
Assert.IsFalse(File.Exists(filePath + ".tmp"));
var data = QuantConnect.Compression.Unzip(filePath);
Assert.AreEqual(data.First().Value.Count, 3);
}
[TestCase(null, Resolution.Daily)]
[TestCase(null, Resolution.Second)]
[TestCase(WritePolicy.Merge, Resolution.Second)]
[TestCase(WritePolicy.Merge, Resolution.Daily)]
[TestCase(WritePolicy.Append, Resolution.Second)]
[TestCase(WritePolicy.Overwrite, Resolution.Second)]
public void RespectsWritePolicy(WritePolicy? writePolicy, Resolution resolution)
{
var filePath = LeanData.GenerateZipFilePath(_dataDirectory, _crypto, _date, resolution, TickType.Quote);
if (File.Exists(filePath))
{
File.Delete(filePath);
}
var loopCount = 3;
var dataPointsPerLoop = 2;
for (var i = 0; i < loopCount; i++)
{
var leanDataWriter = new LeanDataWriter(resolution, _crypto, _dataDirectory, TickType.Quote, writePolicy: writePolicy);
var quoteBar = new QuoteBar(Parse.DateTime("3/16/2017 12:00:00 PM").AddHours(i), _crypto, new Bar(1m, 2m, 3m, 4m), 1,
new Bar(5m, 6m, 7m, 8m), 2);
// same quote twice! it has the same time, so it will be dropped when merging
leanDataWriter.Write(Enumerable.Repeat(quoteBar, dataPointsPerLoop));
Assert.IsTrue(File.Exists(filePath));
Assert.IsFalse(File.Exists(filePath + ".tmp"));
}
var data = QuantConnect.Compression.Unzip(filePath).First().Value;
switch (writePolicy)
{
case WritePolicy.Overwrite:
Assert.AreEqual(dataPointsPerLoop, data.Count);
break;
case WritePolicy.Merge:
Assert.AreEqual(loopCount, data.Count);
if (resolution < Resolution.Hour)
{
var previousMs = 0;
Assert.IsTrue(data.All(x =>
{
var milliseconds = int.Parse(x.Split(',')[0], NumberStyles.Number, CultureInfo.InvariantCulture);
var result = previousMs < milliseconds;
previousMs = milliseconds;
return result;
}));
}
break;
case WritePolicy.Append:
Assert.AreEqual(dataPointsPerLoop * loopCount, data.Count);
break;
case null:
if (resolution >= Resolution.Hour)
{
// will merge by default
Assert.AreEqual(loopCount, data.Count);
}
else
{
// overwrite
Assert.AreEqual(dataPointsPerLoop, data.Count);
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(writePolicy), writePolicy, null);
}
}
[Test]
public void LeanDataWriter_CanWriteCrypto()
{
var filePath = LeanData.GenerateZipFilePath(_dataDirectory, _crypto, _date, Resolution.Second, TickType.Quote);
var leanDataWriter = new LeanDataWriter(Resolution.Second, _crypto, _dataDirectory, TickType.Quote);
leanDataWriter.Write(GetQuoteBars(_crypto));
Assert.IsTrue(File.Exists(filePath));
Assert.IsFalse(File.Exists(filePath + ".tmp"));
var data = QuantConnect.Compression.Unzip(filePath);
Assert.AreEqual(data.First().Value.Count, 3);
}
[TestCase(SecurityType.Equity, TickType.Quote, Resolution.Minute)]
[TestCase(SecurityType.Equity, TickType.Trade, Resolution.Daily)]
[TestCase(SecurityType.Equity, TickType.Trade, Resolution.Hour)]
[TestCase(SecurityType.Equity, TickType.Trade, Resolution.Minute)]
[TestCase(SecurityType.Crypto, TickType.Quote, Resolution.Minute)]
[TestCase(SecurityType.Crypto, TickType.Trade, Resolution.Daily)]
[TestCase(SecurityType.Crypto, TickType.Trade, Resolution.Minute)]
[TestCase(SecurityType.Option, TickType.Quote, Resolution.Minute)]
[TestCase(SecurityType.Option, TickType.Trade, Resolution.Minute)]
public void CanDownloadAndSave(SecurityType securityType, TickType tickType, Resolution resolution)
{
var symbol = Symbols.GetBySecurityType(securityType);
var startTimeUtc = GetRepoDataDates(securityType, resolution);
// Override for this case because symbol from Symbols does not have data included
if (securityType == SecurityType.Option)
{
symbol = Symbols.CreateOptionSymbol("GOOG", OptionRight.Call, 770, new DateTime(2015, 12, 24));
startTimeUtc = new DateTime(2015, 12, 23);
}
// EndTime based on start, only do 1 day for anything less than hour because we compare datafiles below
// and minute and finer resolutions store by day
var endTimeUtc = startTimeUtc + TimeSpan.FromDays(resolution >= Resolution.Hour ? 15 : 1);
// Create our writer and LocalHistory brokerage to "download" from
var writer = new LeanDataWriter(_dataDirectory, resolution, securityType, tickType);
var brokerage = new LocalHistoryBrokerage();
var symbols = new List<Symbol>() {symbol};
// "Download" and write to file
writer.DownloadAndSave(brokerage, symbols, startTimeUtc, endTimeUtc);
// Verify the file exists where we expect
var filePath = LeanData.GenerateZipFilePath(_dataDirectory, symbol, startTimeUtc, resolution, tickType);
Assert.IsTrue(File.Exists(filePath));
// Read the file and data
var reader = new LeanDataReader(filePath);
var dataFromFile = reader.Parse().ToList();
// Ensure its not empty and it is actually for this symbol
Assert.IsNotEmpty(dataFromFile);
Assert.IsTrue(dataFromFile.All(x => x.Symbol == symbol));
// Get history directly ourselves and compare with the data in the file
var history = GetHistory(brokerage, resolution, securityType, symbol, tickType, startTimeUtc, endTimeUtc);
CollectionAssert.AreEqual(history.Select(x => x.Time), dataFromFile.Select(x => x.Time));
brokerage.Dispose();
}
/// <summary>
/// Helper to get history for tests from a brokerage implementation
/// </summary>
/// <returns>List of data points from history request</returns>
private List<BaseData> GetHistory(IBrokerage brokerage, Resolution resolution, SecurityType securityType, Symbol symbol, TickType tickType, DateTime startTimeUtc, DateTime endTimeUtc)
{
var dataType = LeanData.GetDataType(resolution, tickType);
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
var ticker = symbol.ID.Symbol;
var market = symbol.ID.Market;
var canonicalSymbol = Symbol.Create(ticker, securityType, market);
var exchangeHours = marketHoursDatabase.GetExchangeHours(canonicalSymbol.ID.Market, canonicalSymbol, securityType);
var dataTimeZone = marketHoursDatabase.GetDataTimeZone(canonicalSymbol.ID.Market, canonicalSymbol, securityType);
var historyRequest = new HistoryRequest(
startTimeUtc,
endTimeUtc,
dataType,
symbol,
resolution,
exchangeHours,
dataTimeZone,
resolution,
true,
false,
DataNormalizationMode.Raw,
tickType
);
return brokerage.GetHistory(historyRequest)
.Select(
x =>
{
// Convert to date timezone before we write it
x.Time = x.Time.ConvertTo(exchangeHours.TimeZone, dataTimeZone);
return x;
})
.ToList();
}
/// <summary>
/// Test helper method to get dates for data we have in the repo
/// Could possibly be refactored and used in Tests.Symbols in a similar way
/// </summary>
/// <returns>Start time where some data included in the repo exists</returns>
private static DateTime GetRepoDataDates(SecurityType securityType, Resolution resolution)
{
// Because I intend to use this with GetBySecurityType here are the symbols we expect
// case SecurityType.Equity: return SPY;
// case SecurityType.Option: return SPY_C_192_Feb19_2016;
// case SecurityType.Forex: return EURUSD;
// case SecurityType.Future: return Future_CLF19_Jan2019;
// case SecurityType.Cfd: return XAGUSD;
// case SecurityType.Crypto: return BTCUSD;
// case SecurityType.Index: return SPX;
switch (securityType)
{
case SecurityType.Equity: // SPY; Daily/Hourly/Minute/Second/Tick
return new DateTime(2013, 10, 7);
case SecurityType.Crypto: // Coinbase (deprecated: GDAX) BTCUSD Daily/Minute/Second
if (resolution == Resolution.Hour || resolution == Resolution.Tick)
{
throw new ArgumentException($"GDAX BTC Crypto does not have data for this resolution {resolution}");
}
return new DateTime(2017, 9, 3);
case SecurityType.Option: // No Data for the default symbol...
return DateTime.MinValue;
default:
throw new NotImplementedException("This has only implemented a few security types (Equity/Crypto/Option)");
}
}
/// <summary>
/// Fake brokerage that just uses Local Disk Data to do history requests
/// </summary>
internal class LocalHistoryBrokerage : NullBrokerage
{
private readonly IHistoryProvider _historyProvider;
public LocalHistoryBrokerage()
{
var mapFileProvider = TestGlobals.MapFileProvider;
var dataProvider = TestGlobals.DataProvider;
var factorFileProvider = TestGlobals.FactorFileProvider;
var dataPermissionManager = new DataPermissionManager();
mapFileProvider.Initialize(dataProvider);
factorFileProvider.Initialize(mapFileProvider, dataProvider);
_historyProvider = new SubscriptionDataReaderHistoryProvider();
_historyProvider.Initialize(
new HistoryProviderInitializeParameters(
null,
null,
dataProvider,
TestGlobals.DataCacheProvider,
mapFileProvider,
factorFileProvider,
null,
true,
dataPermissionManager,
null,
new AlgorithmSettings()
)
);
}
public override IEnumerable<BaseData> GetHistory(HistoryRequest request)
{
var requests = new List<HistoryRequest> {request};
var slices = _historyProvider.GetHistory(requests, DateTimeZone.Utc);
// Grab all the bar values for this
switch (request.TickType)
{
case TickType.Quote:
return slices.SelectMany(x => x.QuoteBars.Values);
case TickType.Trade:
return slices.SelectMany(x => x.Bars.Values);
default:
throw new NotImplementedException("Only support Trade & Quote bars");
}
}
}
}
}
+60
View File
@@ -0,0 +1,60 @@
/*
* 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 NUnit.Framework;
using System;
using System.Globalization;
using System.IO;
using System.Linq;
namespace QuantConnect.Tests.ToolBox
{
[TestFixture]
public class PsychSignalDataTests
{
[Test, Ignore("This test requires raw PsychSignal data")]
public void FileHourMatchesDataTimeRealRawData()
{
var rawPath = Path.Combine("raw", "alternative", "psychsignal");
foreach (var file in Directory.GetFiles(rawPath, "*.csv", SearchOption.TopDirectoryOnly).ToList())
{
var fileSplit = file.Split('_');
var hour = fileSplit[fileSplit.Length - 4].ConvertInvariant<int>();
// Read one line of the file, and compare the hour of the day to the hour on the file
var line = File.ReadLines(file).Last().Split(',');
// SOURCE[0],SYMBOL[1],TIMESTAMP_UTC[2],BULLISH_INTENSITY[3],BEARISH_INTENSITY[4],BULL_MINUS_BEAR[5],BULL_SCORED_MESSAGES[6],BEAR_SCORED_MESSAGES[7],BULL_BEAR_MSG_RATIO[8],TOTAL_SCANNED_MESSAGES[9]
var date = Parse.DateTime(line[2]).ToUniversalTime();
Assert.AreEqual(hour, date.Hour);
}
}
[Test]
public void FileHourMatchesFakeDataTime()
{
var line = File.ReadLines(Path.Combine("TestData", "00010101_05_example_psychsignal_testdata.csv")).Last().Split(',');
var hour = 5;
var date = Parse.DateTime(line[2]).ToUniversalTime();
Assert.AreEqual(hour, date.Hour);
}
}
}
@@ -0,0 +1,92 @@
/*
* 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 Moq;
using NUnit.Framework;
using QuantConnect.Securities;
using QuantConnect.ToolBox.RandomDataGenerator;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
{
[TestFixture]
public class BaseSymbolGeneratorTests
{
[Test]
[TestCase(2, 5)]
[TestCase(3, 3)]
[TestCase(1, 4)]
public void NextUpperCaseString_CreatesString_WithinSpecifiedMinMaxLength(int min, int max)
{
var symbolGenerator = new Mock<BaseSymbolGenerator>(Mock.Of<RandomDataGeneratorSettings>(), new RandomValueGenerator()).Object;
var str = symbolGenerator.NextUpperCaseString(min, max);
Assert.LessOrEqual(min, str.Length);
Assert.GreaterOrEqual(max, str.Length);
}
[Test]
public void NextUpperCaseString_CreatesUpperCaseString()
{
var symbolGenerator = new Mock<BaseSymbolGenerator>(Mock.Of<RandomDataGeneratorSettings>(), new RandomValueGenerator()).Object;
var str = symbolGenerator.NextUpperCaseString(10, 10);
Assert.IsTrue(str.All(char.IsUpper));
}
[Test]
[TestCase(SecurityType.Option)]
[TestCase(SecurityType.Future)]
public void ThrowsArgumentException_ForDerivativeSymbols(SecurityType securityType)
{
var symbolGenerator = new Mock<BaseSymbolGenerator>(Mock.Of<RandomDataGeneratorSettings>(), new RandomValueGenerator()).Object;
Assert.Throws<ArgumentException>(() =>
symbolGenerator.NextSymbol(securityType, Market.USA)
);
}
[Test]
public void ThrowIsSettingsAreNull()
{
Assert.Throws<ArgumentNullException>(() =>
{
BaseSymbolGenerator.Create(null, Mock.Of<IRandomValueGenerator>());
});
}
[Test]
public void ThrowIsRundomValueGeneratorIsNull()
{
Assert.Throws<ArgumentNullException>(() =>
{
BaseSymbolGenerator.Create(new RandomDataGeneratorSettings(), null);
});
}
internal static IEnumerable<Symbol> GenerateAsset(BaseSymbolGenerator instance)
{
var generateAsset = typeof(BaseSymbolGenerator).GetMethod("GenerateAsset", BindingFlags.NonPublic | BindingFlags.Instance);
return (IEnumerable<Symbol>)generateAsset.Invoke(instance, new[] { (object)null });
}
internal static IEnumerable<Symbol> GenerateAssetWithTicker(BaseSymbolGenerator instance, string ticker)
{
var generateAsset = typeof(BaseSymbolGenerator).GetMethod("GenerateAsset", BindingFlags.NonPublic | BindingFlags.Instance);
return (IEnumerable<Symbol>)generateAsset.Invoke(instance, new object[] { ticker });
}
}
}
@@ -0,0 +1,172 @@
/*
* 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 Moq;
using NUnit.Framework;
using QuantConnect.Securities;
using QuantConnect.ToolBox.RandomDataGenerator;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
{
[TestFixture]
public class DefaultSymbolGeneratorTests
{
private const int Seed = 123456789;
private static readonly IRandomValueGenerator _randomValueGenerator = new RandomValueGenerator(Seed);
private BaseSymbolGenerator _symbolGenerator;
private DateTime _minExpiry = new(2000, 01, 01);
private DateTime _maxExpiry = new(2001, 01, 01);
[SetUp]
public void Setup()
{
// initialize using a seed for deterministic tests
_symbolGenerator = new DefaultSymbolGenerator(
new RandomDataGeneratorSettings()
{
Market = Market.CME,
Start = _minExpiry,
End = _maxExpiry
},
_randomValueGenerator);
}
[Test]
[TestCase(SecurityType.Equity)]
[TestCase(SecurityType.Index)]
public void ReturnsDefaultSymbolGeneratorInstance(SecurityType securityType)
{
Assert.IsInstanceOf<DefaultSymbolGenerator>(BaseSymbolGenerator.Create(
new RandomDataGeneratorSettings() { SecurityType = securityType },
Mock.Of<IRandomValueGenerator>()
));
}
[Test]
[TestCase(SecurityType.Equity, Market.USA, true)]
[TestCase(SecurityType.Cfd, Market.FXCM, false)]
[TestCase(SecurityType.Cfd, Market.Oanda, false)]
[TestCase(SecurityType.Forex, Market.FXCM, false)]
[TestCase(SecurityType.Forex, Market.Oanda, false)]
[TestCase(SecurityType.Crypto, Market.GDAX, false)]
[TestCase(SecurityType.Crypto, Market.Bitfinex, false)]
public void GetAvailableSymbolCount(SecurityType securityType, string market, bool expectInfinity)
{
var expected = expectInfinity
? int.MaxValue
: SymbolPropertiesDatabase.FromDataFolder().GetSymbolPropertiesList(market, securityType).Count();
var symbolGenerator = new DefaultSymbolGenerator(new RandomDataGeneratorSettings
{
SecurityType = securityType,
Market = market
}, Mock.Of<RandomValueGenerator>());
Assert.AreEqual(expected, symbolGenerator.GetAvailableSymbolCount());
}
[Test]
[TestCase(SecurityType.Equity, Market.USA)]
[TestCase(SecurityType.Cfd, Market.FXCM)]
[TestCase(SecurityType.Cfd, Market.Oanda)]
[TestCase(SecurityType.Forex, Market.FXCM)]
[TestCase(SecurityType.Forex, Market.Oanda)]
[TestCase(SecurityType.Crypto, Market.GDAX)]
[TestCase(SecurityType.Crypto, Market.Bitfinex)]
public void NextSymbol_CreatesSymbol_WithRequestedSecurityTypeAndMarket(SecurityType securityType, string market)
{
var symbolGenerator = new DefaultSymbolGenerator(new RandomDataGeneratorSettings
{
SecurityType = securityType,
Market = market
}, _randomValueGenerator);
var symbols = BaseSymbolGeneratorTests.GenerateAsset(symbolGenerator).ToList().ToList();
Assert.AreEqual(1, symbols.Count);
var symbol = symbols.First();
Assert.AreEqual(securityType, symbol.SecurityType);
Assert.AreEqual(market, symbol.ID.Market);
}
[Test]
[TestCase(SecurityType.Equity, Market.USA)]
[TestCase(SecurityType.Cfd, Market.FXCM)]
[TestCase(SecurityType.Cfd, Market.Oanda)]
[TestCase(SecurityType.Forex, Market.FXCM)]
[TestCase(SecurityType.Forex, Market.Oanda)]
[TestCase(SecurityType.Crypto, Market.GDAX)]
[TestCase(SecurityType.Crypto, Market.Bitfinex)]
public void NextSymbol_CreatesSymbol_WithEntryInSymbolPropertiesDatabase(SecurityType securityType, string market)
{
var symbolGenerator = new DefaultSymbolGenerator(new RandomDataGeneratorSettings
{
SecurityType = securityType,
Market = market
}, _randomValueGenerator);
var symbols = BaseSymbolGeneratorTests.GenerateAsset(symbolGenerator).ToList().ToList();
Assert.AreEqual(1, symbols.Count);
var symbol = symbols.First();
var db = SymbolPropertiesDatabase.FromDataFolder();
if (db.ContainsKey(market, SecurityDatabaseKey.Wildcard, securityType))
{
// there is a wildcard entry, so no need to check whether there is a specific entry for the symbol
Assert.Pass();
}
else
{
// there is no wildcard entry, so there should be a specific entry for the symbol instead
Assert.IsTrue(db.ContainsKey(market, symbol, securityType));
}
}
[Test]
[TestCase(SecurityType.Cfd, Market.FXCM)]
[TestCase(SecurityType.Cfd, Market.Oanda)]
[TestCase(SecurityType.Forex, Market.FXCM)]
[TestCase(SecurityType.Forex, Market.Oanda)]
[TestCase(SecurityType.Crypto, Market.GDAX)]
[TestCase(SecurityType.Crypto, Market.Bitfinex)]
public void NextSymbol_ThrowsNoTickersAvailableException_WhenAllSymbolsGenerated(SecurityType securityType, string market)
{
var db = SymbolPropertiesDatabase.FromDataFolder();
var symbolCount = db.GetSymbolPropertiesList(market, securityType).Count();
var symbolGenerator = new DefaultSymbolGenerator(new RandomDataGeneratorSettings
{
SecurityType = securityType,
Market = market
}, _randomValueGenerator);
for (var i = 0; i < symbolCount; i++)
{
var symbols = BaseSymbolGeneratorTests.GenerateAsset(symbolGenerator).ToList();
Assert.AreEqual(1, symbols.Count);
}
Assert.Throws<NoTickersAvailableException>(() =>
BaseSymbolGeneratorTests.GenerateAsset(symbolGenerator).ToList()
);
}
}
}
@@ -0,0 +1,74 @@
/*
* 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 NUnit.Framework;
using QuantConnect.ToolBox.RandomDataGenerator;
using System;
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
{
[TestFixture]
public class DividendSplitMapGeneratorTests
{
[TestCase(240, 0.9857119009006162)]
[TestCase(120, 0.9716279515771061)]
[TestCase(60, 0.9440608762859234)]
[TestCase(12, 0.7498942093324559)]
[TestCase(6, 0.5623413251903491)]
[TestCase(3, 0.31622776601683794)]
[TestCase(1, 0.03162277660168379)]
public void GetsExpectedLowerBound(int months, double expectedLowerBound)
{
var lowerBound = (double)DividendSplitMapGenerator.GetLowerBoundForPreviousSplitFactor(months);
Assert.AreEqual(expectedLowerBound, lowerBound, delta: 0.0000000000001);
Assert.IsTrue(Math.Pow(lowerBound, lowerBound * 2) >= 0.0009);
}
[TestCase(240)]
[TestCase(120)]
[TestCase(60)]
[TestCase(12)]
[TestCase(6)]
[TestCase(3)]
[TestCase(1)]
public void GetsValidNextPreviousSplitFactor(int months)
{
var lowerBound = DividendSplitMapGenerator.GetLowerBoundForPreviousSplitFactor(months);
var upperBound = 1;
var nextPreviousSplitFactor = DividendSplitMapGenerator.GetNextPreviousSplitFactor(new Random(), lowerBound, upperBound);
Assert.IsTrue(lowerBound <= nextPreviousSplitFactor && nextPreviousSplitFactor <= upperBound);
Assert.IsTrue(0.001 <= Math.Pow((double)nextPreviousSplitFactor, 2 * (double)months) && Math.Pow((double)nextPreviousSplitFactor, 2 * (double)months) <= 1);
}
[TestCase(240)]
[TestCase(120)]
[TestCase(60)]
[TestCase(12)]
[TestCase(6)]
[TestCase(3)]
[TestCase(1)]
public void PriceScaledBySplitFactorIsBounded(int months)
{
var maxPossiblePrice = 1000000m;
var minPossiblePrice = 0.0001m;
var lowerBound = DividendSplitMapGenerator.GetLowerBoundForPreviousSplitFactor(months);
var upperBound = 1;
var nextPreviousSplitFactor = DividendSplitMapGenerator.GetNextPreviousSplitFactor(new Random(), lowerBound, upperBound);
var finalSplitFactor = (decimal)Math.Pow((double)nextPreviousSplitFactor, 2 * (double)months);
Assert.IsTrue(0.0001m <= (maxPossiblePrice / finalSplitFactor) && (maxPossiblePrice / finalSplitFactor) <= 1000000000m, (maxPossiblePrice / finalSplitFactor).ToString());
Assert.IsTrue(0.0001m <= (minPossiblePrice / finalSplitFactor) && (minPossiblePrice / finalSplitFactor) <= 1000000000m, (minPossiblePrice / finalSplitFactor).ToString());
}
}
}
@@ -0,0 +1,201 @@
/*
* 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 Moq;
using NUnit.Framework;
using QuantConnect.Securities;
using QuantConnect.Securities.Future;
using QuantConnect.ToolBox.RandomDataGenerator;
using System;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
{
[TestFixture]
public class FutureSymbolGeneratorTests
{
private const int Seed = 123456789;
private static readonly IRandomValueGenerator _randomValueGenerator = new RandomValueGenerator(Seed);
private BaseSymbolGenerator _symbolGenerator;
private DateTime _minExpiry = new(2000, 01, 01);
private DateTime _maxExpiry = new(2001, 01, 01);
[SetUp]
public void Setup()
{
// initialize using a seed for deterministic tests
_symbolGenerator = new FutureSymbolGenerator(
new RandomDataGeneratorSettings()
{
Market = Market.CME,
Start = _minExpiry,
End = _maxExpiry
},
_randomValueGenerator);
}
[Test]
[TestCase(SecurityType.Future)]
public void ReturnsFutureSymbolGeneratorInstance(SecurityType securityType)
{
Assert.IsInstanceOf<FutureSymbolGenerator>(BaseSymbolGenerator.Create(
new RandomDataGeneratorSettings { SecurityType = securityType },
Mock.Of<IRandomValueGenerator>()
));
}
[Test]
public void GetAvailableSymbolCount()
{
Assert.AreEqual(int.MaxValue, _symbolGenerator.GetAvailableSymbolCount());
}
[Test]
public void GeneratesFutureSymbolWithCorrectExpiryDate()
{
var startDate = new DateTime(2020, 01, 01);
var endDate = new DateTime(2020, 01, 31);
var futureSymbolGenerator = new FutureSymbolGenerator(
new RandomDataGeneratorSettings()
{
Market = Market.NYMEX,
Start = startDate,
End = endDate
},
_randomValueGenerator);
// Generate a future symbol using a specific ticker "NG"
var symbols = BaseSymbolGeneratorTests.GenerateAssetWithTicker(futureSymbolGenerator, "NG").ToList();
Assert.AreEqual(1, symbols.Count);
var symbol = symbols.First();
var expiry = symbol.ID.Date;
bool hasExpiryFunction = FuturesExpiryFunctions.FuturesExpiryDictionary.TryGetValue(symbol.Canonical, out var expiryFuncWithTicker);
Assert.IsTrue(hasExpiryFunction);
// Add one month to simulate how the expiry function takes the first day of the next month and subtracts 3 business days
Assert.IsTrue(expiryFuncWithTicker(expiry.AddMonths(1)).Equals(expiry));
Assert.AreEqual(expiry, new DateTime(2020, 01, 29));
Assert.Greater(expiry, startDate);
Assert.LessOrEqual(expiry, endDate);
// Generate a future symbol without specifying ticker
symbols = BaseSymbolGeneratorTests.GenerateAsset(futureSymbolGenerator).ToList();
Assert.AreEqual(1, symbols.Count);
symbol = symbols.First();
expiry = symbol.ID.Date;
// Ensure the expiry falls within the configured start and end range
Assert.Greater(expiry, startDate);
Assert.LessOrEqual(expiry, endDate);
}
[Test]
public void GeneratesFutureSymbolWithCorrectExpiryDateOverWiderDateRange()
{
var startDate = new DateTime(2020, 01, 01);
var endDate = new DateTime(2024, 01, 31);
var futureSymbolGenerator = new FutureSymbolGenerator(
new RandomDataGeneratorSettings()
{
Market = Market.NYMEX,
Start = startDate,
End = endDate
},
_randomValueGenerator);
var expiries = new HashSet<DateTime>();
for (int i = 0; i < 500; i++)
{
// Generate a future symbol using a specific ticker "NG"
var symbols = BaseSymbolGeneratorTests.GenerateAssetWithTicker(futureSymbolGenerator, "NG").ToList();
Assert.AreEqual(1, symbols.Count);
var symbol = symbols.First();
var expiry = symbol.ID.Date;
bool hasExpiryFunction = FuturesExpiryFunctions.FuturesExpiryDictionary.TryGetValue(symbol.Canonical, out var expiryFuncWithTicker);
Assert.IsTrue(hasExpiryFunction);
Assert.IsTrue(expiryFuncWithTicker(expiry.AddMonths(1)).Equals(expiry));
expiries.Add(expiry);
}
Assert.Greater(expiries.Count, 1);
}
[TestCase("TEST")]
[TestCase("NG")]
public void StopsExecutionAfterReturningSingleSymbolWhenNoExpiryFunctionOrValidExpiry(string ticker)
{
// Define a small date range that does not contain any valid expiries
var startDate = new DateTime(2020, 01, 15);
var endDate = new DateTime(2020, 01, 17);
var futureSymbolGenerator = new FutureSymbolGenerator(
new RandomDataGeneratorSettings()
{
Market = Market.NYMEX,
Start = startDate,
End = endDate
},
_randomValueGenerator);
// Generate a future symbol using a specific ticker
var symbols = BaseSymbolGeneratorTests.GenerateAssetWithTicker(futureSymbolGenerator, ticker);
var enumerator = symbols.GetEnumerator();
// At least one symbol should be produced
Assert.IsTrue(enumerator.MoveNext());
// No additional symbol should be generated
Assert.IsFalse(enumerator.MoveNext());
}
[Test]
public void NextFuture_CreatesSymbol_WithFutureSecurityTypeAndRequestedMarket()
{
var symbols = BaseSymbolGeneratorTests.GenerateAsset(_symbolGenerator).ToList();
Assert.AreEqual(1, symbols.Count);
var symbol = symbols.First();
Assert.AreEqual(Market.CME, symbol.ID.Market);
Assert.AreEqual(SecurityType.Future, symbol.SecurityType);
}
[Test]
public void NextFuture_CreatesSymbol_WithFutureWithValidFridayExpiry()
{
var symbols = BaseSymbolGeneratorTests.GenerateAsset(_symbolGenerator).ToList();
Assert.AreEqual(1, symbols.Count);
var symbol = symbols.First();
var expiry = symbol.ID.Date;
Assert.Greater(expiry, _minExpiry);
Assert.LessOrEqual(expiry, _maxExpiry);
}
[Test]
public void NextFuture_CreatesSymbol_WithEntryInSymbolPropertiesDatabase()
{
var symbols = BaseSymbolGeneratorTests.GenerateAsset(_symbolGenerator).ToList();
Assert.AreEqual(1, symbols.Count);
var symbol = symbols.First();
var db = SymbolPropertiesDatabase.FromDataFolder();
Assert.IsTrue(db.ContainsKey(Market.CME, symbol, SecurityType.Future));
}
}
}
@@ -0,0 +1,133 @@
/*
* 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 Moq;
using NUnit.Framework;
using QuantConnect.ToolBox.RandomDataGenerator;
using System;
using QLNet;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
using Cash = QuantConnect.Securities.Cash;
using Option = QuantConnect.Securities.Option.Option;
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
{
[TestFixture]
public class OptionPriceModelPriceGeneratorTests
{
private Security _underlying;
private Option _option;
public OptionPriceModelPriceGeneratorTests()
{
_underlying = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new SubscriptionDataConfig(
typeof(TradeBar),
Symbols.SPY,
Resolution.Minute,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
false
),
new Cash("USD", 0, 1m),
SymbolProperties.GetDefault("USD"),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
var optionSymbol = Symbol.CreateOption(
_underlying.Symbol,
_underlying.Symbol.ID.Market,
_underlying.Symbol.SecurityType.DefaultOptionStyle(),
OptionRight.Call,
20,
new DateTime(2022, 1, 1));
_option = new Option(
optionSymbol,
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new Cash("USD", 0, 1m),
new OptionSymbolProperties(_underlying.SymbolProperties),
new CashBook(),
new RegisteredSecurityDataTypesProvider(),
new OptionCache(),
_underlying);
}
[Test]
public void ThrowsIfSecurityIsNull()
{
Assert.Throws<ArgumentNullException>(() =>
{
_ = new OptionPriceModelPriceGenerator(null);
});
}
[Test]
public void ThrowsIfSecurityIsNotOption()
{
Assert.Throws<ArgumentException>(() =>
{
_ = new OptionPriceModelPriceGenerator(_underlying);
});
}
[Test]
public void ReturnsNewPrice()
{
var priceModelMock = new Mock<IOptionPriceModel>();
priceModelMock
.Setup(s => s.Evaluate(It.IsAny<OptionPriceModelParameters>()))
.Returns(new OptionPriceModelResult(1000, NullGreeks.Instance));
_option.PriceModel = priceModelMock.Object;
var randomPriceGenerator = new OptionPriceModelPriceGenerator(_option);
Assert.AreEqual(1000, randomPriceGenerator.NextValue(50, new DateTime(2020, 1, 1)));
}
[Test]
public void WarmedUpIfNotQLOptionPriceModel()
{
_option.PriceModel = Mock.Of<IOptionPriceModel>();
var blackScholesModel = new OptionPriceModelPriceGenerator(_option);
Assert.True(blackScholesModel.WarmedUp);
}
[Test]
[TestCase(true)]
[TestCase(false)]
public void WarmedUpSameQLOptionPriceModel(bool warmUp)
{
var volatilityModel = new Mock<IQLUnderlyingVolatilityEstimator>();
volatilityModel.SetupGet(s => s.IsReady).Returns(warmUp);
_option.PriceModel = new QLOptionPriceModel(process => new AnalyticEuropeanEngine(process),
volatilityModel.Object,
null,
null);
var blackScholesModel = new OptionPriceModelPriceGenerator(_option);
Assert.AreEqual(warmUp, blackScholesModel.WarmedUp);
}
}
}
@@ -0,0 +1,174 @@
/*
* 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 Moq;
using NUnit.Framework;
using QuantConnect.ToolBox.RandomDataGenerator;
using System;
using System.Linq;
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
{
[TestFixture]
public class OptionSymbolGeneratorTests
{
private const int Seed = 123456789;
private static readonly IRandomValueGenerator _randomValueGenerator = new RandomValueGenerator(Seed);
private BaseSymbolGenerator _symbolGenerator;
private DateTime _minExpiry = new(2000, 01, 01);
private DateTime _maxExpiry = new(2001, 01, 01);
private decimal _underlyingPrice = 100m;
private decimal _maximumStrikePriceDeviation = 50m;
[SetUp]
public void Setup()
{
// initialize using a seed for deterministic tests
_symbolGenerator = new OptionSymbolGenerator(
new RandomDataGeneratorSettings()
{
Market = Market.USA,
Start = _minExpiry,
End = _maxExpiry
},
_randomValueGenerator,
_underlyingPrice,
_maximumStrikePriceDeviation);
}
[Test]
[TestCase(SecurityType.Option)]
public void ReturnsFutureSymbolGeneratorInstance(SecurityType securityType)
{
Assert.IsInstanceOf<OptionSymbolGenerator>(BaseSymbolGenerator.Create(
new RandomDataGeneratorSettings { SecurityType = securityType },
Mock.Of<IRandomValueGenerator>()
));
}
[Test]
public void GetAvailableSymbolCount()
{
Assert.AreEqual(int.MaxValue,
new OptionSymbolGenerator(Mock.Of<RandomDataGeneratorSettings>(), Mock.Of<RandomValueGenerator>(), 100m,
75m).GetAvailableSymbolCount());
}
[Test]
public void NextOptionSymbol_CreatesOptionSymbol_WithCorrectSecurityTypeAndEquityUnderlying()
{
var symbols = BaseSymbolGeneratorTests.GenerateAsset(_symbolGenerator).ToList();
Assert.AreEqual(3, symbols.Count);
var underlying = symbols[0];
var option = symbols[1];
Assert.AreEqual(SecurityType.Option, option.SecurityType);
Assert.AreEqual(OptionRight.Put, symbols[1].ID.OptionRight);
Assert.AreEqual(SecurityType.Option, symbols[2].SecurityType);
Assert.AreEqual(OptionRight.Call, symbols[2].ID.OptionRight);
var underlyingOrigin = option.Underlying;
Assert.AreEqual(underlying.Value, underlyingOrigin.Value);
Assert.AreEqual(Market.USA, underlying.ID.Market);
Assert.AreEqual(SecurityType.Equity, underlying.SecurityType);
}
[Test]
public void NextOptionSymbol_CreatesOptionSymbol_WithinSpecifiedExpiration_OnFriday()
{
var symbols = BaseSymbolGeneratorTests.GenerateAsset(_symbolGenerator).ToList();
Assert.AreEqual(3, symbols.Count);
foreach (var option in new[] { symbols[1], symbols[2] })
{
var expiration = option.ID.Date;
Assert.LessOrEqual(_minExpiry, expiration);
Assert.GreaterOrEqual(_maxExpiry, expiration);
}
}
[Test]
public void NextOptionSymbol_CreatesOptionSymbol_WithRequestedMarket()
{
for (int i = 0; i < 50; i++)
{
var price = _randomValueGenerator.NextPrice(SecurityType.Equity, Market.USA, 100m, 100m);
var symbolGenerator = new OptionSymbolGenerator(
new RandomDataGeneratorSettings()
{
Market = Market.USA,
Start = _minExpiry,
End = _maxExpiry
},
_randomValueGenerator,
price,
50m);
var symbols = BaseSymbolGeneratorTests.GenerateAsset(_symbolGenerator).ToList();
Assert.AreEqual(3, symbols.Count);
var option = symbols[1];
Assert.AreEqual(Market.USA, option.ID.Market);
}
}
[Test]
public void NextOptionSymbol_CreatesOptionSymbol_WithinSpecifiedStrikePriceDeviation()
{
var symbols = BaseSymbolGeneratorTests.GenerateAsset(_symbolGenerator).ToList();
Assert.AreEqual(3, symbols.Count);
foreach (var option in new []{ symbols[1], symbols[2] })
{
var strikePrice = option.ID.StrikePrice;
var maximumDeviation = _underlyingPrice * (_maximumStrikePriceDeviation / 100m);
Assert.LessOrEqual(_underlyingPrice - maximumDeviation, strikePrice);
Assert.GreaterOrEqual(_underlyingPrice + maximumDeviation, strikePrice);
}
}
[Test]
[TestCase("2021, 6, 2 00:00:00", "2021, 6, 4 00:00:00")]
[TestCase("2021, 6, 2 00:00:00", "2021, 6, 5 00:00:00")]
[TestCase("2021, 6, 2 00:00:00", "2021, 7, 2 00:00:00")]
[TestCase("2021, 6, 2 00:00:00", "2021, 6, 10 00:00:00")]
[TestCase("2021, 6, 2 00:00:00", "2021, 6, 11 00:00:00")]
[TestCase("2021, 6, 2 00:00:00", "2021, 8, 2 00:00:00")]
[TestCase("2021, 6, 2 00:00:00", "2021, 6, 15 00:00:00")]
public void OptionSymbolGeneratorCreatesOptionSymbol_WithExpirationDateAtLeastThreeDaysAfterMinExpiryDate(DateTime minExpiry, DateTime maxExpiry)
{
var symbolGenerator = new OptionSymbolGenerator(
new RandomDataGeneratorSettings()
{
Market = Market.USA,
Start = minExpiry,
End = maxExpiry
},
new RandomValueGenerator(),
_underlyingPrice,
_maximumStrikePriceDeviation);
var symbols = BaseSymbolGeneratorTests.GenerateAsset(symbolGenerator).ToList();
Assert.AreEqual(3, symbols.Count);
foreach (var option in new[] { symbols[1], symbols[2] })
{
var expiration = option.ID.Date;
Assert.LessOrEqual(minExpiry.AddDays(3), expiration);
}
}
}
}
@@ -0,0 +1,298 @@
/*
* 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 QuantConnect.Securities;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.ToolBox.RandomDataGenerator;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
using QuantConnect.Configuration;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Interfaces;
using QuantConnect.Securities.Option;
using QuantConnect.Util;
using static QuantConnect.ToolBox.RandomDataGenerator.RandomDataGenerator;
using QuantConnect.Algorithm;
using System.Linq;
using System.IO;
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
{
[TestFixture]
public class RandomDataGeneratorTests
{
[Test]
[TestCase("2020, 1, 1 00:00:00", "2020, 1, 1 00:00:00", "2020, 1, 1 00:00:00")]
[TestCase("2020, 1, 1 00:00:00", "2020, 2, 1 00:00:00", "2020, 1, 16 12:00:00")] // (31 days / 2) = 15.5 = 16 Rounds up to 12 pm
[TestCase("2020, 1, 1 00:00:00", "2020, 3, 1 00:00:00", "2020, 1, 31 00:00:00")] // (60 days / 2) = 30
[TestCase("2020, 1, 1 00:00:00", "2020, 6, 1 00:00:00", "2020, 3, 17 00:00:00")] // (152 days / 2) = 76
public void NextRandomGeneratedData(DateTime start, DateTime end, DateTime expectedMidPoint)
{
var randomValueGenerator = new RandomValueGenerator();
var midPoint = QuantConnect.ToolBox.RandomDataGenerator.RandomDataGenerator.GetDateMidpoint(start, end);
var delistDate = QuantConnect.ToolBox.RandomDataGenerator.RandomDataGenerator.GetDelistingDate(start, end, randomValueGenerator);
// midPoint and expectedMidPoint must be the same
Assert.AreEqual(expectedMidPoint, midPoint);
// start must be less than or equal to end
Assert.LessOrEqual(start, end);
// delistDate must be less than or equal to end
Assert.LessOrEqual(delistDate, end);
Assert.GreaterOrEqual(delistDate, midPoint);
}
[TestCase("20220101", "20230101")]
public void RandomGeneratorProducesValuesBoundedForEquitiesWhenSplit(string start, string end)
{
var settings = RandomDataGeneratorSettings.FromCommandLineArguments(
start,
end,
"1",
"usa",
"Equity",
"Minute",
"Dense",
"true",
"1",
null,
"5.0",
"30.0",
"100.0",
"60.0",
"30.0",
"BaroneAdesiWhaleyApproximationEngine",
"Daily",
"1",
new List<string>(),
100
);
var securityManager = new SecurityManager(new TimeKeeper(settings.Start, new[] { TimeZones.Utc }));
var securityService = GetSecurityService(settings, securityManager);
securityManager.SetSecurityService(securityService);
var security = securityManager.CreateSecurity(Symbols.AAPL, new List<SubscriptionDataConfig>(), underlying: null);
var randomValueGenerator = new RandomValueGenerator();
var tickGenerator = new TickGenerator(settings, new TickType[1] { TickType.Trade }, security, randomValueGenerator).GenerateTicks().GetEnumerator();
using var sync = new SynchronizingBaseDataEnumerator(tickGenerator);
var tickHistory = new List<Tick>();
while (sync.MoveNext())
{
var dataPoint = sync.Current;
tickHistory.Add(dataPoint as Tick);
}
var dividendsSplitsMaps = new DividendSplitMapGenerator(
Symbols.AAPL,
settings,
randomValueGenerator,
BaseSymbolGenerator.Create(settings, randomValueGenerator),
new Random(),
GetDelistingDate(settings.Start, settings.End, randomValueGenerator),
false);
dividendsSplitsMaps.GenerateSplitsDividends(tickHistory);
Assert.IsTrue(0.099m <= dividendsSplitsMaps.FinalSplitFactor && dividendsSplitsMaps.FinalSplitFactor <= 1.5m);
foreach (var tick in tickHistory)
{
tick.Value = tick.Value / dividendsSplitsMaps.FinalSplitFactor;
Assert.IsTrue(0.001m <= tick.Value && tick.Value <= 1000000000, $"The tick value was {tick.Value} but should have been bounded by 0.001 and 1 000 000 000");
}
}
[TestCase(Resolution.Tick, 3.0, 33)]
[TestCase(Resolution.Second, 3.0, 33)]
[TestCase(Resolution.Minute, 3.0, 33)]
[TestCase(Resolution.Hour, 3.0, 33)]
[TestCase(Resolution.Daily, 3.0, 33)]
[TestCase(Resolution.Minute, 5.0, 20)]
[TestCase(Resolution.Minute, 10.0, 10)]
public void GetProgressAsPercentageShouldLogWhenProgressExceedsThreshold(Resolution resolution, double thresholdPercent, int expectedLogCount)
{
TimeSpan step = resolution switch
{
Resolution.Tick => TimeSpan.FromTicks(1),
Resolution.Second => TimeSpan.FromSeconds(1),
Resolution.Minute => TimeSpan.FromMinutes(1),
Resolution.Hour => TimeSpan.FromHours(1),
Resolution.Daily => TimeSpan.FromDays(1),
_ => throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null)
};
var start = new DateTime(2024, 1, 1, 0, 0, 0);
var end = start.AddTicks(step.Ticks * 100);
var current = start;
var logs = new List<double>();
var lastLoggedProgress = 0.0;
while (current <= end)
{
var progress = RandomDataGeneratorHelper.GetProgressAsPercentage(start, end, current);
if (progress - lastLoggedProgress >= thresholdPercent)
{
logs.Add(progress);
lastLoggedProgress = progress;
}
current = current.Add(step);
}
Assert.AreEqual(expectedLogCount, logs.Count);
Assert.IsTrue(logs.All(p => p >= 0 && p <= 100));
}
[Test]
public void RandomDataGeneratorCompletesSuccessfully()
{
var tempFolder = Path.Combine(Path.GetTempPath(), $"LeanTest_{Guid.NewGuid()}");
var originalDataFolder = Config.Get("data-folder");
try
{
Directory.CreateDirectory(tempFolder);
Config.Set("data-folder", tempFolder);
Globals.Reset();
var hourPath = Path.Combine(tempFolder, "equity", "usa", "hour");
var dailyPath = Path.Combine(tempFolder, "equity", "usa", "daily");
var factorFilesPath = Path.Combine(tempFolder, "equity", "usa", "factor_files");
var mapFilesPath = Path.Combine(tempFolder, "equity", "usa", "map_files");
// Create the required folders
Directory.CreateDirectory(hourPath);
Directory.CreateDirectory(dailyPath);
Directory.CreateDirectory(factorFilesPath);
Directory.CreateDirectory(mapFilesPath);
var settings = new RandomDataGeneratorSettings
{
Start = new DateTime(2024, 1, 1, 9, 30, 0),
End = new DateTime(2024, 1, 2, 16, 0, 0),
SymbolCount = 1,
Market = "usa",
SecurityType = SecurityType.Equity,
Resolution = Resolution.Hour,
DataDensity = DataDensity.Dense,
IncludeCoarse = false,
QuoteTradeRatio = 1.0,
RandomSeed = 123456,
HasDividendsPercentage = 0,
HasSplitsPercentage = 0,
HasIpoPercentage = 0,
HasRenamePercentage = 0,
Tickers = new List<string>() { "AAPL" }
};
var generator = GetGenerator(settings);
Assert.DoesNotThrow(() => generator.Run());
var allFiles = Directory.GetFiles(tempFolder, "*", SearchOption.AllDirectories);
Assert.Greater(allFiles.Length, 0);
var hourFiles = Directory.GetFiles(hourPath, "*.zip");
Assert.Greater(hourFiles.Length, 0);
}
finally
{
Config.Set("data-folder", originalDataFolder);
Globals.Reset();
Directory.Delete(tempFolder, true);
}
}
private static QuantConnect.ToolBox.RandomDataGenerator.RandomDataGenerator GetGenerator(RandomDataGeneratorSettings settings)
{
var securityManager = new SecurityManager(new TimeKeeper(settings.Start, new[] { TimeZones.Utc }));
var securityService = new SecurityService(
new CashBook(),
MarketHoursDatabase.FromDataFolder(),
SymbolPropertiesDatabase.FromDataFolder(),
new SecurityInitializerProvider(new FuncSecurityInitializer(security =>
{
// init price
security.SetMarketPrice(new Tick(settings.Start, security.Symbol, 100, 100));
security.SetMarketPrice(new OpenInterest(settings.Start, security.Symbol, 10000));
// from settings
security.VolatilityModel = new StandardDeviationOfReturnsVolatilityModel(settings.VolatilityModelResolution);
// from settings
if (security is Option option)
{
option.PriceModel = OptionPriceModels.QuantLib.Create(settings.OptionPriceEngineName,
_interestRateProvider.GetRiskFreeRate(settings.Start, settings.End));
}
})),
RegisteredSecurityDataTypesProvider.Null,
new SecurityCacheProvider(
new SecurityPortfolioManager(securityManager, new SecurityTransactionManager(null, securityManager), new AlgorithmSettings())),
new MapFilePrimaryExchangeProvider(Composer.Instance.GetExportedValueByTypeName<IMapFileProvider>(Config.Get("map-file-provider", "LocalDiskMapFileProvider")))
);
securityManager.SetSecurityService(securityService);
var generator = new QuantConnect.ToolBox.RandomDataGenerator.RandomDataGenerator();
generator.Init(settings, securityManager);
return generator;
}
private static readonly IRiskFreeInterestRateModel _interestRateProvider = new InterestRateProvider();
private static SecurityService GetSecurityService(RandomDataGeneratorSettings settings, SecurityManager securityManager)
{
var algorithm = new QCAlgorithm();
algorithm.Securities = securityManager;
var securityService = new SecurityService(
new CashBook(),
MarketHoursDatabase.FromDataFolder(),
SymbolPropertiesDatabase.FromDataFolder(),
new SecurityInitializerProvider(new FuncSecurityInitializer(security =>
{
// init price
security.SetMarketPrice(new Tick(settings.Start, security.Symbol, 100, 100));
security.SetMarketPrice(new OpenInterest(settings.Start, security.Symbol, 10000));
// from settings
security.VolatilityModel = new StandardDeviationOfReturnsVolatilityModel(settings.VolatilityModelResolution);
// from settings
if (security is Option option)
{
option.PriceModel = OptionPriceModels.QuantLib.Create(settings.OptionPriceEngineName,
_interestRateProvider.GetRiskFreeRate(settings.Start, settings.End));
}
})),
RegisteredSecurityDataTypesProvider.Null,
new SecurityCacheProvider(
new SecurityPortfolioManager(securityManager, new SecurityTransactionManager(null, securityManager), new AlgorithmSettings())),
new MapFilePrimaryExchangeProvider(Composer.Instance.GetExportedValueByTypeName<IMapFileProvider>(Config.Get("map-file-provider", "LocalDiskMapFileProvider"))),
algorithm: algorithm);
securityManager.SetSecurityService(securityService);
return securityService;
}
}
}
@@ -0,0 +1,83 @@
/*
* 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 Moq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Securities;
using QuantConnect.ToolBox.RandomDataGenerator;
using System;
using System.Linq;
using QuantConnect.Util;
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
{
[TestFixture]
public class RandomPriceGeneratorTests
{
private Security _security;
public RandomPriceGeneratorTests()
{
_security = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new SubscriptionDataConfig(
typeof(TradeBar),
Symbols.SPY,
Resolution.Minute,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
false
),
new Cash("USD", 0, 1m),
SymbolProperties.GetDefault("USD"),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
}
[Test]
public void ReturnsSameAsReference()
{
var randomMock = new Mock<IRandomValueGenerator>();
randomMock.Setup(s => s.NextPrice(It.IsAny<SecurityType>(), It.IsNotNull<string>(), It.IsAny<decimal>(),
It.IsAny<decimal>()))
.Returns(50);
var randomPriceGenerator = new RandomPriceGenerator(_security, randomMock.Object);
_security.SetMarketPrice(new Tick(DateTime.UtcNow, Symbols.SPY, 10, 100));
var actual = randomPriceGenerator.NextValue(1, DateTime.MinValue);
randomMock.Verify(s => s.NextPrice(It.IsAny<SecurityType>(), It.IsNotNull<string>(), 55, 1), Times.Once);
Assert.AreEqual(50, actual);
}
[Test]
public void AlwaysReady()
{
var priceGenerator = new RandomPriceGenerator(_security, Mock.Of<IRandomValueGenerator>());
Assert.True(priceGenerator.WarmedUp);
}
[Test]
public void ComposeRandomDataGenerator()
{
Assert.NotNull(Composer.Instance.GetExportedValueByTypeName<QuantConnect.ToolBox.RandomDataGenerator.RandomDataGenerator>("QuantConnect.ToolBox.RandomDataGenerator.RandomDataGenerator"));
}
}
}
@@ -0,0 +1,144 @@
/*
* 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 NUnit.Framework;
using QuantConnect.ToolBox.RandomDataGenerator;
using System;
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
{
[TestFixture]
public class RandomValueGeneratorTests
{
private const int Seed = 123456789;
private RandomValueGenerator randomValueGenerator;
[SetUp]
public void Setup()
{
// initialize using a seed for deterministic tests
randomValueGenerator = new RandomValueGenerator(Seed);
}
[Test]
public void NextDateTime_CreatesDateTime_WithinSpecifiedMinMax()
{
var min = new DateTime(2000, 01, 01);
var max = new DateTime(2001, 01, 01);
var dateTime = randomValueGenerator.NextDate(min, max, dayOfWeek: null);
Assert.LessOrEqual(min, dateTime);
Assert.GreaterOrEqual(max, dateTime);
}
[Test]
[TestCase(DayOfWeek.Sunday)]
[TestCase(DayOfWeek.Monday)]
[TestCase(DayOfWeek.Tuesday)]
[TestCase(DayOfWeek.Wednesday)]
[TestCase(DayOfWeek.Thursday)]
[TestCase(DayOfWeek.Friday)]
[TestCase(DayOfWeek.Saturday)]
public void NextDateTime_CreatesDateTime_OnSpecifiedDayOfWeek(DayOfWeek dayOfWeek)
{
var min = new DateTime(2000, 01, 01);
var max = new DateTime(2001, 01, 01);
var dateTime = randomValueGenerator.NextDate(min, max, dayOfWeek);
Assert.AreEqual(dayOfWeek, dateTime.DayOfWeek);
}
[Test]
public void NextDateTime_ThrowsArgumentException_WhenMaxIsLessThanMin()
{
var min = new DateTime(2000, 01, 01);
var max = min.AddDays(-1);
Assert.Throws<ArgumentException>(() =>
randomValueGenerator.NextDate(min, max, dayOfWeek: null)
);
}
[Test]
public void NextDateTime_ThrowsArgumentException_WhenRangeIsTooSmallToProduceDateTimeOnRequestedDayOfWeek()
{
var min = new DateTime(2019, 01, 15);
var max = new DateTime(2019, 01, 20);
Assert.Throws<ArgumentException>(() =>
// no monday between these dates, so impossible to fulfill request
randomValueGenerator.NextDate(min, max, DayOfWeek.Monday)
);
}
[Test]
public void NextPrice_ReturnsZeroForOptionsZeroReferencePrice()
{
var price = randomValueGenerator.NextPrice(SecurityType.Option, Market.USA, 0m, 1m);
Assert.AreEqual(0m, price);
}
[Test]
public void NextPrice_ThrowsIfReferencePriceIsInvalid([Values] SecurityType securityType)
{
// Negative reference price
Assert.Throws<ArgumentException>(() =>
randomValueGenerator.NextPrice(securityType, null, -1m, 1m)
);
// Zero reference price
if (securityType != SecurityType.Option)
{
Assert.Throws<ArgumentException>(() =>
randomValueGenerator.NextPrice(securityType, null, 0m, 1m)
);
}
}
[TestCase(0)]
[TestCase(-1)]
public void NextPrice_ThrowsIfMaximumPercentDeviationIsInvalid(decimal maximumPercentDeviation)
{
Assert.Throws<ArgumentException>(() =>
randomValueGenerator.NextPrice(SecurityType.Equity, null, 100m, maximumPercentDeviation)
);
}
[Test]
public void NextPrice_ReturnsSamePriceIfFailsToGetAValidOne()
{
// Default min price variation for crypto is 0.01
var maximumPercentDeviation = 0.45m;
var referencePrice = 0.1m; // too close to the minimum price variation
var price = randomValueGenerator.NextPrice(SecurityType.Crypto, Market.GDAX, referencePrice, maximumPercentDeviation);
Assert.AreEqual(referencePrice, price);
}
[Test]
public void NextPrice_PricesIsUpdatedEvenIfMaxPercentageDeviationIsLessThanMinPriceVariation()
{
// Default min price variation for crypto is 0.01
var maximumPercentDeviation = 0.45m;
var referencePrice = 2m;
// The maximum price variation is 0.45% of 2, which is 0.009, less than the minimum price variation of 0.01.
// The generated price will be rounded back to 2m, but this should be properly handled.
var price = randomValueGenerator.NextPrice(SecurityType.Crypto, Market.GDAX, referencePrice, maximumPercentDeviation);
Assert.AreNotEqual(referencePrice, price);
}
}
}
@@ -0,0 +1,33 @@
/*
* 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 Moq;
using NUnit.Framework;
using QuantConnect.Securities;
using QuantConnect.ToolBox.RandomDataGenerator;
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
{
[TestFixture]
public class SecurityInitializerProviderTests
{
[Test]
public void NotNull()
{
var securityInitializerProvider = new SecurityInitializerProvider(Mock.Of<ISecurityInitializer>());
Assert.NotNull(securityInitializerProvider.SecurityInitializer);
}
}
}
@@ -0,0 +1,235 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Securities;
using QuantConnect.ToolBox.RandomDataGenerator;
namespace QuantConnect.Tests.ToolBox.RandomDataGenerator
{
[TestFixture]
public class TickGeneratorTests
{
private Symbol _symbol = Symbol.Create("AAPL", SecurityType.Equity, Market.USA);
private Security _security;
private ITickGenerator _tickGenerator;
[SetUp]
public void Setup()
{
var start = new DateTime(2020, 1, 6);
var end = new DateTime(2020, 1, 10);
// initialize using a seed for deterministic tests
_symbol = Symbol.Create("AAPL", SecurityType.Equity, Market.USA);
_security = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new SubscriptionDataConfig(typeof(TradeBar),
_symbol,
Resolution.Minute,
TimeZones.NewYork,
TimeZones.NewYork,
true, true, false),
new Cash(Currencies.USD, 0, 0),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
_security.SetMarketPrice(new Tick(start, _security.Symbol, 100, 100));
_security.SetMarketPrice(new OpenInterest(start, _security.Symbol, 10000));
_tickGenerator = new TickGenerator(
new RandomDataGeneratorSettings()
{
Start = start,
End = end
},
new TickType[3] { TickType.Trade, TickType.Quote, TickType.OpenInterest },
_security,
new RandomValueGenerator());
}
[Test]
public void NextTick_CreatesTradeTick_WithPriceAndQuantity()
{
var dateTime = new DateTime(2000, 01, 01);
var tick = _tickGenerator.NextTick(dateTime, TickType.Trade, 1m);
Assert.AreEqual(_symbol, tick.Symbol);
Assert.AreEqual(dateTime, tick.Time);
Assert.AreEqual(TickType.Trade, tick.TickType);
Assert.LessOrEqual(99m, tick.Value);
Assert.GreaterOrEqual(101m, tick.Value);
Assert.Greater(tick.Quantity, 0);
Assert.LessOrEqual(tick.Quantity, 1500);
}
[Test]
public void NextTick_CreatesQuoteTick_WithCommonValues()
{
var dateTime = new DateTime(2000, 01, 01);
var tick = _tickGenerator.NextTick(dateTime, TickType.Quote, 1m);
Assert.AreEqual(_symbol, tick.Symbol);
Assert.AreEqual(dateTime, tick.Time);
Assert.AreEqual(TickType.Quote, tick.TickType);
Assert.GreaterOrEqual(tick.Value, 99m);
Assert.LessOrEqual(tick.Value, 101m);
}
[Test]
public void NextTick_CreatesQuoteTick_WithBidData()
{
var dateTime = new DateTime(2000, 01, 01);
var tick = _tickGenerator.NextTick(dateTime, TickType.Quote, 1m);
Assert.Greater(tick.BidSize, 0);
Assert.LessOrEqual(tick.BidSize, 1500);
Assert.GreaterOrEqual(tick.BidPrice, 98.9m);
Assert.LessOrEqual(tick.BidPrice, 100.9m);
Assert.GreaterOrEqual(tick.Value, tick.BidPrice);
}
[Test]
public void NextTick_CreatesQuoteTick_WithAskData()
{
var dateTime = new DateTime(2000, 01, 01);
var tick = _tickGenerator.NextTick(dateTime, TickType.Quote, 1m);
Assert.GreaterOrEqual(tick.AskSize, 0);
Assert.LessOrEqual(tick.AskSize, 1500);
Assert.GreaterOrEqual(tick.AskPrice, 99.1m);
Assert.LessOrEqual(tick.AskPrice, 101.1m);
Assert.LessOrEqual(tick.Value, tick.AskPrice);
}
[Test]
public void NextTick_CreatesOpenInterestTick()
{
var dateTime = new DateTime(2000, 01, 01);
var tick = _tickGenerator.NextTick(dateTime, TickType.OpenInterest, 10m);
Assert.AreEqual(dateTime, tick.Time);
Assert.AreEqual(TickType.OpenInterest, tick.TickType);
Assert.AreEqual(typeof(OpenInterest), tick.GetType());
Assert.AreEqual(_symbol, tick.Symbol);
Assert.GreaterOrEqual(tick.Quantity, 9000);
Assert.LessOrEqual(tick.Quantity, 11000);
Assert.AreEqual(tick.Value, tick.Quantity);
}
[Test]
[TestCase(Resolution.Tick, DataDensity.Dense)]
[TestCase(Resolution.Second, DataDensity.Dense)]
[TestCase(Resolution.Minute, DataDensity.Dense)]
[TestCase(Resolution.Hour, DataDensity.Dense)]
[TestCase(Resolution.Daily, DataDensity.Dense)]
[TestCase(Resolution.Tick, DataDensity.Sparse)]
[TestCase(Resolution.Second, DataDensity.Sparse)]
[TestCase(Resolution.Minute, DataDensity.Sparse)]
[TestCase(Resolution.Hour, DataDensity.Sparse)]
[TestCase(Resolution.Daily, DataDensity.Sparse)]
[TestCase(Resolution.Tick, DataDensity.VerySparse)]
[TestCase(Resolution.Second, DataDensity.VerySparse)]
[TestCase(Resolution.Minute, DataDensity.VerySparse)]
[TestCase(Resolution.Hour, DataDensity.VerySparse)]
[TestCase(Resolution.Daily, DataDensity.VerySparse)]
public void NextTickTime_CreatesTimes(Resolution resolution, DataDensity density)
{
var count = 100;
var deltaSum = TimeSpan.Zero;
var previous = new DateTime(2019, 01, 14, 9, 30, 0);
var increment = resolution.ToTimeSpan();
if (increment == TimeSpan.Zero)
{
increment = TimeSpan.FromMilliseconds(500);
}
var marketHours = MarketHoursDatabase.FromDataFolder()
.GetExchangeHours(_symbol.ID.Market, _symbol, _symbol.SecurityType);
for (int i = 0; i < count; i++)
{
var next = _tickGenerator.NextTickTime(previous, resolution, density);
var barStart = next.Subtract(increment);
Assert.Less(previous, next);
Assert.IsTrue(marketHours.IsOpen(barStart, next, false));
var delta = next - previous;
deltaSum += delta;
previous = next;
}
var avgDelta = TimeSpan.FromTicks(deltaSum.Ticks / count);
switch (density)
{
case DataDensity.Dense:
// more frequent than once an increment
Assert.Less(avgDelta, increment);
break;
case DataDensity.Sparse:
// less frequent that once an increment
Assert.Greater(avgDelta, increment);
break;
case DataDensity.VerySparse:
// less frequent than one every 10 increments
Assert.Greater(avgDelta, TimeSpan.FromTicks(increment.Ticks * 10));
break;
default:
throw new ArgumentOutOfRangeException(nameof(density), density, null);
}
}
[Test]
public void HistoryIsNotEmpty()
{
var history = _tickGenerator.GenerateTicks().ToList();
Assert.IsNotEmpty(history);
Assert.That(history.Select(s => s.Symbol), Is.All.EqualTo(_symbol));
}
[Test]
public void HistoryIsBetweenStartAndEndDate()
{
var start = new DateTime(2020, 1, 6);
var end = new DateTime(2020, 1, 10);
var history = _tickGenerator.GenerateTicks().ToList();
Assert.IsNotEmpty(history);
Assert.That(history.All(s => start <= s.Time && s.Time <= end));
}
[Test]
public void HistoryGeneratesOpenInterestDataAsExpected()
{
var start = new DateTime(2020, 1, 6);
var end = new DateTime(2020, 1, 10);
var history = _tickGenerator.GenerateTicks().ToList();
Assert.IsNotEmpty(history);
Assert.AreEqual(3, history.Where(s => s.TickType == TickType.OpenInterest).Count());
}
}
}
+53
View File
@@ -0,0 +1,53 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Interfaces;
using System;
using System.Linq;
using QuantConnect.Configuration;
namespace QuantConnect.Tests.ToolBox
{
[TestFixture]
public class ToolBoxTests
{
[Test]
public void ComposeDataQueueHandlerInstances()
{
var type = typeof(IDataQueueHandler);
var types = AppDomain.CurrentDomain.Load("QuantConnect.ToolBox")
.GetTypes()
.Where(p => type.IsAssignableFrom(p) && p.IsClass && !p.IsAbstract)
.ToList();
Assert.Zero(types.Count);
}
[TestCase("--app=RDG --tickers=AAPL --resolution=Daily --from-date=20200820-00:00:00 --to-date=20200830-00:00:00", 1)]
[TestCase("--app=RDG --resolution=Daily --from-date=20200820-00:00:00 --to-date=20200830-00:00:00", 0)]
[TestCase("--app=RDG --tickers=AAPL,SPY,TSLA --resolution=Daily --from-date=20200820-00:00:00 --to-date=20200830-00:00:00", 3)]
[TestCase("--app=RDG --tickers=ES --security-type=Future --resolution=Minute --destination-dir=/Lean/Data", 1)]
public void CanParseTickersCorrectly(string args, int expectedTcikerCount)
{
var options = ToolboxArgumentParser.ParseArguments(args.Split(' '));
var tickers = ToolboxArgumentParser.GetTickers(options);
Assert.AreEqual(expectedTcikerCount, tickers.Count);
}
}
}