868 lines
38 KiB
C#
868 lines
38 KiB
C#
/*
|
|
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
|
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
using NUnit.Framework;
|
|
using Python.Runtime;
|
|
using QuantConnect.Securities;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using QuantConnect.Research;
|
|
using QuantConnect.Logging;
|
|
using QuantConnect.Data.Fundamental;
|
|
using System.Data;
|
|
using QuantConnect.Securities.Future;
|
|
using QuantConnect.Data;
|
|
using NodaTime;
|
|
using QuantConnect.Interfaces;
|
|
using QuantConnect.Data.UniverseSelection;
|
|
|
|
namespace QuantConnect.Tests.Research
|
|
{
|
|
[TestFixture]
|
|
public class QuantBookHistoryTests
|
|
{
|
|
private ILogHandler _logHandler;
|
|
dynamic _module;
|
|
|
|
[OneTimeSetUp]
|
|
public void Setup()
|
|
{
|
|
// Store initial handler
|
|
_logHandler = Log.LogHandler;
|
|
|
|
SymbolCache.Clear();
|
|
MarketHoursDatabase.Reset();
|
|
|
|
using (Py.GIL())
|
|
{
|
|
_module = Py.Import("Test_QuantBookHistory");
|
|
}
|
|
}
|
|
|
|
[OneTimeTearDown]
|
|
public void OneTimeTearDown()
|
|
{
|
|
// Reset to initial handler
|
|
Log.LogHandler = _logHandler;
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(2013, 10, 11, SecurityType.Equity, "SPY")]
|
|
[TestCase(2014, 5, 9, SecurityType.Forex, "EURUSD")]
|
|
[TestCase(2016, 10, 9, SecurityType.Crypto, "BTCUSD")]
|
|
public void SecurityQuantBookHistoryTests(int year, int month, int day, SecurityType securityType, string symbol)
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var startDate = new DateTime(year, month, day);
|
|
var securityTestHistory = _module.SecurityHistoryTest(startDate, securityType, symbol);
|
|
|
|
// Get the last 10 candles
|
|
var periodHistory = securityTestHistory.test_period_overload(10);
|
|
var count = (periodHistory.shape[0] as PyObject).AsManagedObject(typeof(int));
|
|
Assert.AreEqual(10, count);
|
|
|
|
// Get the one day of data
|
|
var timedeltaHistory = securityTestHistory.test_period_overload(TimeSpan.FromDays(1));
|
|
var firstIndex = (DateTime)(timedeltaHistory.index.values[0] as PyObject).AsManagedObject(typeof(DateTime));
|
|
Assert.GreaterOrEqual(startDate.AddDays(-1).Date, firstIndex.Date);
|
|
|
|
// Get the one day of data, ending one day before start date
|
|
var startEndHistory = securityTestHistory.test_daterange_overload(startDate.AddDays(-1));
|
|
firstIndex = (DateTime)(startEndHistory.index.values[0] as PyObject).AsManagedObject(typeof(DateTime));
|
|
Assert.GreaterOrEqual(startDate.AddDays(-2).Date, firstIndex.Date);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(2014, 5, 9, "Nifty", "NIFTY")]
|
|
public void CustomDataQuantBookHistoryTests(int year, int month, int day, string customDataType, string symbol)
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var startDate = new DateTime(year, month, day);
|
|
var securityTestHistory = _module.CustomDataHistoryTest(startDate, customDataType, symbol);
|
|
|
|
// Get the last 5 candles
|
|
var periodHistory = securityTestHistory.test_period_overload(5);
|
|
var count = (periodHistory.shape[0] as PyObject).AsManagedObject(typeof(int));
|
|
Assert.AreEqual(5, count);
|
|
|
|
// Get the one day of data
|
|
var timedeltaHistory = securityTestHistory.test_period_overload(TimeSpan.FromDays(8));
|
|
var firstIndex =
|
|
(DateTime)(timedeltaHistory.index.values[0] as PyObject).AsManagedObject(typeof(DateTime));
|
|
Assert.AreEqual(startDate.AddDays(-7), firstIndex);
|
|
|
|
// Get the one day of data, ending one day before start date
|
|
var startEndHistory = securityTestHistory.test_daterange_overload(startDate.AddDays(-2));
|
|
firstIndex = (DateTime)(startEndHistory.index.values[0] as PyObject).AsManagedObject(typeof(DateTime));
|
|
Assert.AreEqual(startDate.AddDays(-2).Date, firstIndex.Date);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void MultipleSecuritiesQuantBookHistoryTests()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var startDate = new DateTime(2014, 5, 9);
|
|
var securityTestHistory = _module.MultipleSecuritiesHistoryTest(startDate, null, null);
|
|
|
|
// Get the last 5 candles
|
|
var periodHistory = securityTestHistory.test_period_overload(5);
|
|
|
|
// Note there is no data for BTCUSD at 2014
|
|
|
|
//symbol EURUSD SPY
|
|
//time
|
|
//2014-05-02 16:00:00 NaN 164.219446
|
|
//2014-05-04 20:00:00 1.387185 NaN
|
|
//2014-05-05 16:00:00 NaN 164.551273
|
|
//2014-05-05 20:00:00 1.387480 NaN
|
|
//2014-05-06 16:00:00 NaN 163.127909
|
|
//2014-05-06 20:00:00 1.392925 NaN
|
|
//2014-05-07 16:00:00 NaN 164.070997
|
|
//2014-05-07 20:00:00 1.391070 NaN
|
|
//2014-05-08 16:00:00 NaN 163.905083
|
|
//2014-05-08 20:00:00 1.384265 NaN
|
|
Log.Trace(periodHistory.ToString());
|
|
|
|
var count = (periodHistory.shape[0] as PyObject).AsManagedObject(typeof(int));
|
|
Assert.AreEqual(10, count);
|
|
|
|
// Get the one day of data
|
|
var timedeltaHistory = securityTestHistory.test_period_overload(TimeSpan.FromDays(8));
|
|
var firstIndex = (DateTime)(timedeltaHistory.index.values[0] as PyObject).AsManagedObject(typeof(DateTime));
|
|
|
|
// EURUSD exchange time zone is NY but data is UTC so we have a 4 hour difference with algo TZ which is NY
|
|
Assert.AreEqual(startDate.AddDays(-8).AddHours(16), firstIndex);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void CanonicalOptionQuantBookHistory()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var symbol = "TWX";
|
|
var startDate = new DateTime(2014, 6, 6);
|
|
var securityTestHistory = _module.OptionHistoryTest(startDate, SecurityType.Option, symbol);
|
|
|
|
// Get the one day of data, ending on start date
|
|
var startEndHistory = securityTestHistory.test_daterange_overload(startDate);
|
|
Log.Trace(startEndHistory.ToString());
|
|
var firstIndex = (DateTime)(startEndHistory.index.values[0][4] as PyObject).AsManagedObject(typeof(DateTime));
|
|
Assert.GreaterOrEqual(startDate.AddDays(-1).Date, firstIndex.Date);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void CanonicalOptionIntradayQuantBookHistory()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var symbol = "TWX";
|
|
var currentDate = new DateTime(2014, 6, 6, 18, 0, 0);
|
|
var securityTestHistory = _module.OptionHistoryTest(new DateTime(2014, 6, 7), SecurityType.Option, symbol);
|
|
|
|
var startEndHistory = securityTestHistory.test_daterange_overload(currentDate, new DateTime(2014, 6, 6, 10, 0, 0));
|
|
Log.Trace(startEndHistory.ToString());
|
|
Assert.IsFalse((bool)startEndHistory.empty);
|
|
}
|
|
}
|
|
|
|
private static TestCaseData[] CanonicalOptionIntradayHistoryTestCases
|
|
{
|
|
get
|
|
{
|
|
var twx = Symbol.Create("TWX", SecurityType.Equity, Market.USA);
|
|
var twxOption = Symbol.CreateCanonicalOption(twx);
|
|
|
|
var spx = Symbol.Create("SPX", SecurityType.Index, Market.USA);
|
|
var spxwOption = Symbol.CreateCanonicalOption(spx, Market.USA, null);
|
|
|
|
return
|
|
[
|
|
new TestCaseData(twxOption, new DateTime(2014, 06, 05), (DateTime?)null, Resolution.Minute),
|
|
new TestCaseData(twxOption, new DateTime(2014, 06, 05), new DateTime(2014, 06, 05), Resolution.Minute),
|
|
new TestCaseData(twxOption, new DateTime(2014, 06, 05), new DateTime(2014, 06, 06), Resolution.Minute),
|
|
new TestCaseData(twxOption, new DateTime(2014, 06, 05, 0, 0, 0), new DateTime(2014, 06, 05, 15, 0, 0), Resolution.Minute),
|
|
new TestCaseData(twxOption, new DateTime(2014, 06, 05, 10, 0, 0), new DateTime(2014, 06, 05, 15, 0, 0), Resolution.Minute),
|
|
new TestCaseData(twxOption, new DateTime(2014, 06, 05, 10, 0, 0), new DateTime(2014, 06, 06), Resolution.Minute),
|
|
new TestCaseData(twxOption, new DateTime(2014, 06, 05, 10, 0, 0), new DateTime(2014, 06, 06, 10, 0, 0), Resolution.Minute),
|
|
new TestCaseData(twxOption, new DateTime(2014, 06, 05, 10, 0, 0), new DateTime(2014, 06, 06, 15, 0, 0), Resolution.Minute),
|
|
|
|
new TestCaseData(spxwOption, new DateTime(2021, 01, 04), (DateTime?)null, Resolution.Hour),
|
|
new TestCaseData(spxwOption, new DateTime(2021, 01, 04), new DateTime(2021, 01, 04), Resolution.Hour),
|
|
new TestCaseData(spxwOption, new DateTime(2021, 01, 04), new DateTime(2021, 01, 05), Resolution.Hour),
|
|
new TestCaseData(spxwOption, new DateTime(2021, 01, 04, 10, 0, 0), new DateTime(2021, 01, 04, 15, 0, 0), Resolution.Hour),
|
|
new TestCaseData(spxwOption, new DateTime(2021, 01, 04, 10, 0, 0), new DateTime(2021, 01, 05, 15, 0, 0), Resolution.Hour),
|
|
new TestCaseData(spxwOption, new DateTime(2021, 01, 14, 10, 0, 0), new DateTime(2021, 01, 14, 15, 0, 0), Resolution.Hour),
|
|
];
|
|
}
|
|
}
|
|
|
|
[TestCaseSource(nameof(CanonicalOptionIntradayHistoryTestCases))]
|
|
public void CanonicalOptionIntradayQuantBookHistoryWithIntradayRange(Symbol canonicalOption, DateTime start, DateTime? end, Resolution resolution)
|
|
{
|
|
var quantBook = new QuantBook();
|
|
var historyProvider = new TestHistoryProvider(quantBook.HistoryProvider);
|
|
quantBook.SetHistoryProvider(historyProvider);
|
|
quantBook.SetStartDate((end ?? start).Date.AddDays(1));
|
|
|
|
var option = quantBook.AddSecurity(canonicalOption);
|
|
var history = quantBook.OptionHistory(canonicalOption, start, end, resolution);
|
|
|
|
Assert.Greater(history.Count, 0);
|
|
|
|
var symbolsInHistory = history.SelectMany(slice => slice.AllData.Select(x => x.Symbol)).Distinct().ToList();
|
|
Assert.Greater(symbolsInHistory.Count, 1);
|
|
|
|
var underlying = symbolsInHistory.Where(x => x == canonicalOption.Underlying).ToList();
|
|
Assert.AreEqual(1, underlying.Count);
|
|
|
|
var contractsSymbols = symbolsInHistory.Where(x => x.SecurityType == canonicalOption.SecurityType).ToList();
|
|
Assert.Greater(contractsSymbols.Count, 1);
|
|
|
|
var expectedDates = new HashSet<DateTime> { start.Date };
|
|
if (end.HasValue && end.Value > end.Value.Date)
|
|
{
|
|
expectedDates.Add(end.Value.Date);
|
|
}
|
|
|
|
var dataDates = history.SelectMany(slice => slice.AllData.Where(x => contractsSymbols.Contains(x.Symbol)).Select(x => x.EndTime.Date)).ToHashSet();
|
|
CollectionAssert.AreEqual(expectedDates, dataDates);
|
|
|
|
// OptionUniverse must have been requested for all dates in the range
|
|
foreach (var date in Time.EachTradeableDay(option, start.Date, (end ?? start).Date))
|
|
{
|
|
Assert.AreEqual(1, historyProvider.HistoryRequests.Count(request => request.DataType == typeof(OptionUniverse) && request.EndTimeLocal == date));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void OptionContractQuantBookHistory()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var symbol = Symbol.CreateOption("TWX", Market.USA, OptionStyle.American, OptionRight.Call, 70, new DateTime(2015, 01, 17));
|
|
var startDate = new DateTime(2014, 6, 6);
|
|
var securityTestHistory = _module.OptionContractHistoryTest(startDate, SecurityType.Option, symbol);
|
|
|
|
// Get the one day of data, ending on start date
|
|
var startEndHistory = securityTestHistory.test_daterange_overload(startDate);
|
|
Log.Trace(startEndHistory.ToString());
|
|
var firstIndex = (DateTime)(startEndHistory.index.values[0][4] as PyObject).AsManagedObject(typeof(DateTime));
|
|
Assert.GreaterOrEqual(startDate.AddDays(-1).Date, firstIndex.Date);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void OptionIndexWeekly()
|
|
{
|
|
var qb = new QuantBook();
|
|
var spxw = qb.AddIndexOption(Symbols.SPX, "SPXW");
|
|
spxw.SetFilter(u => u.Strikes(0, 1)
|
|
// single week ahead since there are many SPXW contracts and we want to preserve performance
|
|
.Expiration(0, 7)
|
|
.IncludeWeeklys());
|
|
|
|
var startTime = new DateTime(2021, 1, 4);
|
|
|
|
var historyByOptionSymbol = qb.GetOptionHistory(spxw.Symbol, startTime);
|
|
var historyByUnderlyingSymbol = qb.GetOptionHistory(Symbols.SPX, "SPXW", startTime);
|
|
|
|
List<DateTime> expiry;
|
|
List<DateTime> byUnderlyingExpiry;
|
|
|
|
historyByOptionSymbol.GetExpiryDates().TryConvert(out expiry);
|
|
historyByUnderlyingSymbol.GetExpiryDates().TryConvert(out byUnderlyingExpiry);
|
|
|
|
List<decimal> strikes;
|
|
List<decimal> byUnderlyingStrikes;
|
|
|
|
historyByOptionSymbol.GetStrikes().TryConvert(out strikes);
|
|
historyByUnderlyingSymbol.GetStrikes().TryConvert(out byUnderlyingStrikes);
|
|
|
|
Assert.IsTrue(expiry.Count > 0);
|
|
Assert.IsTrue(expiry.SequenceEqual(byUnderlyingExpiry));
|
|
|
|
Assert.IsTrue(strikes.Count > 0);
|
|
Assert.IsTrue(strikes.SequenceEqual(byUnderlyingStrikes));
|
|
}
|
|
|
|
[Test]
|
|
public void OptionUnderlyingSymbolQuantBookHistory()
|
|
{
|
|
var qb = new QuantBook();
|
|
var twx = qb.AddEquity("TWX");
|
|
var twxOptions = qb.AddOption("TWX");
|
|
|
|
var historyByOptionSymbol = qb.GetOptionHistory(twxOptions.Symbol, new DateTime(2014, 6, 5), new DateTime(2014, 6, 6));
|
|
var historyByEquitySymbol = qb.GetOptionHistory(twx.Symbol, new DateTime(2014, 6, 5), new DateTime(2014, 6, 6));
|
|
|
|
List<DateTime> expiry;
|
|
List<DateTime> byUnderlyingExpiry;
|
|
|
|
historyByOptionSymbol.GetExpiryDates().TryConvert(out expiry);
|
|
historyByEquitySymbol.GetExpiryDates().TryConvert(out byUnderlyingExpiry);
|
|
|
|
List<decimal> strikes;
|
|
List<decimal> byUnderlyingStrikes;
|
|
|
|
historyByOptionSymbol.GetStrikes().TryConvert(out strikes);
|
|
historyByEquitySymbol.GetStrikes().TryConvert(out byUnderlyingStrikes);
|
|
|
|
Assert.IsTrue(expiry.Count > 0);
|
|
Assert.IsTrue(expiry.SequenceEqual(byUnderlyingExpiry));
|
|
|
|
Assert.IsTrue(strikes.Count > 0);
|
|
Assert.IsTrue(strikes.SequenceEqual(byUnderlyingStrikes));
|
|
}
|
|
|
|
[TestCase(182, 2)]
|
|
[TestCase(120, 1)]
|
|
public void CanonicalFutureQuantBookHistory(int maxFilter, int numberOfFutureContracts)
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var symbol = Futures.Indices.SP500EMini;
|
|
var startDate = new DateTime(2013, 10, 11);
|
|
var securityTestHistory = _module.FutureHistoryTest(startDate, SecurityType.Future, symbol);
|
|
|
|
// Get the one day of data, ending on start date
|
|
var startEndHistory = securityTestHistory.test_daterange_overload(startDate, startDate.AddDays(-1), maxFilter);
|
|
|
|
Log.Trace(startEndHistory.index.levels[1].size.ToString());
|
|
Assert.AreEqual(numberOfFutureContracts, (int)startEndHistory.index.levels[1].size);
|
|
|
|
Log.Trace(startEndHistory.ToString());
|
|
var firstIndex = (DateTime)(startEndHistory.index.values[0][2] as PyObject).AsManagedObject(typeof(DateTime));
|
|
Assert.GreaterOrEqual(startDate.AddDays(-1).Date, firstIndex.Date);
|
|
}
|
|
}
|
|
|
|
[TestCase(182, 2)]
|
|
[TestCase(120, 1)]
|
|
public void CanonicalFutureIntradayQuantBookHistory(int maxFilter, int numberOfFutureContracts)
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var symbol = Futures.Indices.SP500EMini;
|
|
var currentDate = new DateTime(2013, 10, 11, 18, 0, 0);
|
|
var securityTestHistory = _module.FutureHistoryTest(new DateTime(2013, 10, 12), SecurityType.Future, symbol);
|
|
|
|
var startEndHistory = securityTestHistory.test_daterange_overload(currentDate, new DateTime(2013, 10, 11, 10, 0, 0), maxFilter);
|
|
|
|
Log.Trace(startEndHistory.index.levels[1].size.ToString());
|
|
Assert.AreEqual(numberOfFutureContracts, (int)startEndHistory.index.levels[1].size);
|
|
|
|
Log.Trace(startEndHistory.ToString());
|
|
Assert.IsFalse((bool)startEndHistory.empty);
|
|
}
|
|
}
|
|
|
|
private static TestCaseData[] CanonicalFutureIntradayHistoryTestCases
|
|
{
|
|
get
|
|
{
|
|
var es = Symbol.Create(Futures.Indices.SP500EMini, SecurityType.Future, Market.CME);
|
|
return
|
|
[
|
|
new TestCaseData(es, new DateTime(2013, 10, 10), (DateTime?)null),
|
|
new TestCaseData(es, new DateTime(2013, 10, 10), new DateTime(2013, 10, 10)),
|
|
new TestCaseData(es, new DateTime(2013, 10, 10), new DateTime(2013, 10, 11)),
|
|
new TestCaseData(es, new DateTime(2013, 10, 10, 0, 0, 0), new DateTime(2013, 10, 10, 15, 0, 0)),
|
|
new TestCaseData(es, new DateTime(2013, 10, 10, 10, 0, 0), new DateTime(2013, 10, 10, 15, 0, 0)),
|
|
new TestCaseData(es, new DateTime(2013, 10, 10, 10, 0, 0), new DateTime(2013, 10, 11)),
|
|
new TestCaseData(es, new DateTime(2013, 10, 10, 10, 0, 0), new DateTime(2013, 10, 11, 10, 0, 0)),
|
|
new TestCaseData(es, new DateTime(2013, 10, 10, 10, 0, 0), new DateTime(2013, 10, 11, 15, 0, 0))
|
|
];
|
|
}
|
|
}
|
|
|
|
[TestCaseSource(nameof(CanonicalFutureIntradayHistoryTestCases))]
|
|
public void CanonicalFutureIntradayQuantBookHistoryWithIntradayRange(Symbol canonicalFuture, DateTime start, DateTime? end)
|
|
{
|
|
var quantBook = new QuantBook();
|
|
var historyProvider = new TestHistoryProvider(quantBook.HistoryProvider);
|
|
quantBook.SetHistoryProvider(historyProvider);
|
|
quantBook.SetStartDate((end ?? start).Date.AddDays(1));
|
|
var future = quantBook.AddSecurity(canonicalFuture) as Future;
|
|
future.SetFilter(universe => universe);
|
|
|
|
var history = quantBook.FutureHistory(canonicalFuture, start, end, Resolution.Minute);
|
|
Assert.Greater(history.Count, 0);
|
|
|
|
var symbolsInHistory = history.SelectMany(slice => slice.AllData.Select(x => x.Symbol)).Distinct().ToList();
|
|
Assert.Greater(symbolsInHistory.Count, 1);
|
|
|
|
var expectedDates = new HashSet<DateTime> { start.Date };
|
|
if (end.HasValue && end.Value > end.Value.Date)
|
|
{
|
|
expectedDates.Add(end.Value.Date);
|
|
}
|
|
|
|
var dataDates = history.SelectMany(slice => slice.AllData.Select(x => x.EndTime.Date)).ToHashSet();
|
|
CollectionAssert.AreEqual(expectedDates, dataDates);
|
|
|
|
// FutureUniverse must have been requested for all dates in the range
|
|
foreach (var date in Time.EachTradeableDay(future, start.Date, (end ?? start).Date))
|
|
{
|
|
Assert.AreEqual(1, historyProvider.HistoryRequests.Count(request => request.DataType == typeof(FutureUniverse) && request.EndTimeLocal == date));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void FutureContractQuantBookHistory()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var symbol = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2014, 12, 19));
|
|
var startDate = new DateTime(2013, 10, 11);
|
|
var securityTestHistory = _module.FutureContractHistoryTest(startDate, SecurityType.Future, symbol);
|
|
|
|
// Get the one day of data, ending on start date
|
|
var startEndHistory = securityTestHistory.test_daterange_overload(startDate);
|
|
Log.Trace(startEndHistory.ToString());
|
|
var firstIndex = (DateTime)(startEndHistory.index.values[0][2] as PyObject).AsManagedObject(typeof(DateTime));
|
|
Assert.GreaterOrEqual(startDate.AddDays(-1).Date, firstIndex.Date);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void FuturesOptionsWithFutureContract()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var qb = new QuantBook();
|
|
var expiry = new DateTime(2020, 3, 20);
|
|
var future = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, expiry);
|
|
var start = new DateTime(2020, 1, 5);
|
|
var end = new DateTime(2020, 1, 6);
|
|
var history = qb.GetOptionHistory(future, start, end, Resolution.Minute, extendedMarketHours: true);
|
|
dynamic df = history.GetAllData();
|
|
|
|
Assert.IsNotNull(df);
|
|
Assert.IsFalse((bool)df.empty.AsManagedObject(typeof(bool)));
|
|
Assert.Greater((int)df.__len__().AsManagedObject(typeof(int)), 360);
|
|
Assert.AreEqual(5, (int)df.index.levels.__len__().AsManagedObject(typeof(int)));
|
|
Assert.IsTrue((bool)df.index.levels[0].__contains__(expiry.ToStringInvariant("yyyy-MM-dd")).AsManagedObject(typeof(bool)));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void FuturesOptionsWithFutureOptionContract()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var qb = new QuantBook();
|
|
var expiry = new DateTime(2020, 3, 20);
|
|
var future = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, expiry);
|
|
var futureOption = Symbol.CreateOption(
|
|
future,
|
|
future.ID.Market,
|
|
OptionStyle.American,
|
|
OptionRight.Call,
|
|
3300m,
|
|
expiry);
|
|
|
|
var start = new DateTime(2020, 1, 5);
|
|
var end = new DateTime(2020, 1, 6);
|
|
var history = qb.GetOptionHistory(futureOption, start, end, Resolution.Minute, extendedMarketHours: true);
|
|
dynamic df = history.GetAllData();
|
|
|
|
Assert.IsNotNull(df);
|
|
Assert.IsFalse((bool)df.empty.AsManagedObject(typeof(bool)));
|
|
Assert.AreEqual(360, (int)df.__len__().AsManagedObject(typeof(int)));
|
|
Assert.AreEqual(5, (int)df.index.levels.__len__().AsManagedObject(typeof(int)));
|
|
Assert.IsTrue((bool)df.index.levels[0].__contains__(expiry.ToStringInvariant("yyyy-MM-dd")).AsManagedObject(typeof(bool)));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void CanoicalFutureCrashesGetOptionHistory()
|
|
{
|
|
var qb = new QuantBook();
|
|
var future = Symbol.Create(Futures.Indices.SP500EMini, SecurityType.Future, Market.CME);
|
|
|
|
Assert.Throws<ArgumentException>(() =>
|
|
{
|
|
qb.GetOptionHistory(future, default(DateTime), DateTime.MaxValue, Resolution.Minute);
|
|
});
|
|
}
|
|
|
|
[TestCase(true, true, 1920)]
|
|
[TestCase(true, false, 780)]
|
|
[TestCase(false, true, 898)]
|
|
[TestCase(false, false, 390)]
|
|
public void OptionHistorySpecifyingFillForwardAndExtendedMarket(bool fillForward, bool extendedMarket, int expectedCount)
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var qb = new QuantBook();
|
|
var start = new DateTime(2013, 10, 11);
|
|
var end = new DateTime(2013, 10, 15);
|
|
|
|
var spy = qb.AddEquity("SPY");
|
|
dynamic history = qb.GetOptionHistory(spy.Symbol, start, end, Resolution.Minute, fillForward, extendedMarket).GetAllData();
|
|
var historyCount = (history.shape[0] as PyObject).As<int>();
|
|
|
|
Assert.AreEqual(expectedCount, historyCount);
|
|
}
|
|
}
|
|
|
|
[TestCase(true, true, 8640)]
|
|
[TestCase(true, false, 2700)]
|
|
[TestCase(false, true, 8279)]
|
|
[TestCase(false, false, 2699)]
|
|
public void FutureHistorySpecifyingFillForwardAndExtendedMarket(bool fillForward, bool extendedMarket, int expectedCount)
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var qb = new QuantBook();
|
|
var start = new DateTime(2013, 10, 6);
|
|
var end = new DateTime(2013, 10, 15);
|
|
|
|
var future = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2013, 12, 20));
|
|
dynamic history = qb.GetFutureHistory(future, start, end, Resolution.Minute, fillForward, extendedMarket).GetAllData();
|
|
var historyCount = (history.shape[0] as PyObject).As<int>();
|
|
|
|
Assert.AreEqual(expectedCount, historyCount);
|
|
}
|
|
}
|
|
|
|
[TestCase(Language.CSharp)]
|
|
[TestCase(Language.Python)]
|
|
public void OptionHistoryObjectIsIterable(Language language)
|
|
{
|
|
var qb = new QuantBook();
|
|
var start = new DateTime(2013, 10, 11);
|
|
var end = new DateTime(2013, 10, 15);
|
|
|
|
var spy = qb.AddEquity("SPY");
|
|
var history = qb.GetOptionHistory(spy.Symbol, start, end, Resolution.Minute);
|
|
|
|
Assert.DoesNotThrow(() =>
|
|
{
|
|
if (language == Language.CSharp)
|
|
{
|
|
Assert.AreEqual(780, history.Count);
|
|
}
|
|
else
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var testModule = PyModule.FromString("testModule",
|
|
@"
|
|
def getOptionHistory(qb, symbol, start, end, resolution):
|
|
return qb.GetOptionHistory(symbol, start, end, resolution)
|
|
|
|
def getHistoryCount(history):
|
|
return len(list(history))
|
|
");
|
|
|
|
dynamic getOptionHistory = testModule.GetAttr("getOptionHistory");
|
|
dynamic getHistoryCount = testModule.GetAttr("getHistoryCount");
|
|
var pyHistory = getOptionHistory(qb, spy.Symbol, start, end, Resolution.Minute);
|
|
Assert.AreEqual(780, getHistoryCount(pyHistory).AsManagedObject(typeof(int)));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
[TestCase(Language.CSharp)]
|
|
[TestCase(Language.Python)]
|
|
public void FutureHistoryObjectIsIterable(Language language)
|
|
{
|
|
var qb = new QuantBook();
|
|
var start = new DateTime(2013, 10, 6);
|
|
var end = new DateTime(2013, 10, 15);
|
|
|
|
var futureSymbol = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2013, 12, 20));
|
|
var history = qb.GetFutureHistory(futureSymbol, start, end, Resolution.Minute);
|
|
|
|
Assert.DoesNotThrow(() =>
|
|
{
|
|
if (language == Language.CSharp)
|
|
{
|
|
Assert.AreEqual(2700, history.Count);
|
|
}
|
|
else
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var testModule = PyModule.FromString("testModule",
|
|
@"
|
|
def getFutureHistory(qb, symbol, start, end, resolution):
|
|
return qb.GetFutureHistory(symbol, start, end, resolution)
|
|
|
|
def getHistoryCount(history):
|
|
return len(list(history))
|
|
");
|
|
|
|
dynamic getFutureHistory = testModule.GetAttr("getFutureHistory");
|
|
dynamic getHistoryCount = testModule.GetAttr("getHistoryCount");
|
|
var pyHistory = getFutureHistory(qb, futureSymbol, start, end, Resolution.Minute);
|
|
Assert.AreEqual(2700, getHistoryCount(pyHistory).AsManagedObject(typeof(int)));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
[TestCase(Language.CSharp)]
|
|
[TestCase(Language.Python)]
|
|
public void GetOptionContractsWithFrontMonthFilter(Language language)
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
Assert.DoesNotThrow(() =>
|
|
{
|
|
if (language == Language.CSharp)
|
|
{
|
|
var qb = new QuantBook();
|
|
var start = new DateTime(2015, 12, 24);
|
|
var end = new DateTime(2015, 12, 24);
|
|
|
|
var goog = qb.AddEquity("GOOG");
|
|
var option = qb.AddOption(goog.Symbol);
|
|
option.SetFilter(universe => universe.Strikes(-5, 5).FrontMonth());
|
|
|
|
var history = qb.GetOptionHistory(goog.Symbol, start, end, Resolution.Minute, fillForward: false, extendedMarketHours: false);
|
|
dynamic data = history.GetAllData();
|
|
var labels = data.axes[0].names;
|
|
Assert.AreEqual("expiry", (labels[0] as PyObject).As<string>());
|
|
}
|
|
else
|
|
{
|
|
var testModule = PyModule.FromString("testModule",
|
|
@"
|
|
from AlgorithmImports import *
|
|
def getAllData():
|
|
qb = QuantBook()
|
|
underlying_symbol = qb.AddEquity(""GOOG"").Symbol
|
|
option = qb.AddOption(underlying_symbol)
|
|
option.SetFilter(lambda option_filter_universe: option_filter_universe.Strikes(-5, 5).FrontMonth())
|
|
option_history = qb.OptionHistory(underlying_symbol, datetime(2015, 12, 24), datetime(2015, 12, 24), Resolution.Minute, fillForward=False, extendedMarketHours=False)
|
|
data = option_history.GetAllData()
|
|
return data.axes[0].names[0]");
|
|
|
|
dynamic getAllData = testModule.GetAttr("getAllData");
|
|
var data = getAllData();
|
|
Assert.AreEqual("expiry", data.AsManagedObject(typeof(string)));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void HistoryDataDoesnNotReturnDataLabelWithBaseDataCollectionTypes()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var testModule = PyModule.FromString("testModule",
|
|
@"
|
|
from AlgorithmImports import *
|
|
|
|
def getHistory():
|
|
qb = QuantBook()
|
|
symbol = qb.AddEquity(""AAPL"", Resolution.Daily).symbol
|
|
dataset_symbol = qb.AddData(FundamentalUniverse, symbol).symbol
|
|
history = qb.History(dataset_symbol, datetime(2014, 3, 1), datetime(2014, 4, 1), Resolution.Daily)
|
|
return history
|
|
");
|
|
dynamic getHistory = testModule.GetAttr("getHistory");
|
|
var pyHistory = getHistory() as PyObject;
|
|
var isHistoryEmpty = pyHistory.GetAttr("empty").GetAndDispose<bool?>();
|
|
Assert.IsFalse(isHistoryEmpty);
|
|
Assert.IsFalse(pyHistory.HasAttr("data"));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void HistoryDataDoesWorksCorrecltyWithoutAddingTheCustomDataInPython()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var testModule = PyModule.FromString("testModule",
|
|
@"
|
|
from AlgorithmImports import *
|
|
|
|
def getHistory():
|
|
qb = QuantBook()
|
|
symbol = qb.AddEquity(""AAPL"", Resolution.Daily).symbol
|
|
dataset_symbol = Symbol.CreateBase(FundamentalUniverse, symbol, symbol.ID.Market)
|
|
history = qb.History(dataset_symbol, datetime(2014, 3, 1), datetime(2014, 4, 1), Resolution.Daily)
|
|
return history
|
|
");
|
|
dynamic getHistory = testModule.GetAttr("getHistory");
|
|
var pyHistory = getHistory() as PyObject;
|
|
var isHistoryEmpty = pyHistory.GetAttr("empty").GetAndDispose<bool?>();
|
|
Assert.IsFalse(isHistoryEmpty);
|
|
Assert.IsFalse(pyHistory.HasAttr("data"));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void HistoryDataDoesWorksCorrectlyWithCustomDataInPython()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var testModule = PyModule.FromString("testModule",
|
|
@"
|
|
from AlgorithmImports import *
|
|
|
|
from datetime import datetime
|
|
|
|
from AlgorithmImports import *
|
|
|
|
def getHistory():
|
|
qb = QuantBook()
|
|
qb.add_data(
|
|
type=TestTradeBar,
|
|
ticker='TEST1',
|
|
properties=SymbolProperties(
|
|
description='TEST1',
|
|
quoteCurrency='USD',
|
|
contractMultiplier=1,
|
|
minimumPriceVariation=0.01,
|
|
lotSize=1,
|
|
marketTicker='TEST1',
|
|
),
|
|
exchange_hours=SecurityExchangeHours.always_open(TimeZones.NEW_YORK),
|
|
resolution=Resolution.MINUTE,
|
|
fill_forward=True,
|
|
leverage=1,
|
|
)
|
|
history = qb.history(qb.securities.keys(), datetime(2024, 8, 2), datetime(2024, 8, 3))
|
|
return history
|
|
|
|
class TestTradeBar(TradeBar):
|
|
def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live_mode: bool) -> SubscriptionDataSource:
|
|
return SubscriptionDataSource(source='../../TestData/test.csv',
|
|
transportMedium=SubscriptionTransportMedium.LOCAL_FILE,
|
|
format=FileFormat.CSV)
|
|
|
|
def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live_mode: bool) -> BaseData:
|
|
if not line[0].isdigit():
|
|
return None
|
|
data = line.split(',')
|
|
bar_time = datetime.utcfromtimestamp(int(data[0]))
|
|
|
|
open = float(data[1])
|
|
high = float(data[2])
|
|
low = float(data[3])
|
|
close = float(data[4])
|
|
volume = int(float(data[7]))
|
|
return TradeBar(bar_time, config.symbol, open, high, low, close, volume)
|
|
");
|
|
dynamic getHistory = testModule.GetAttr("getHistory");
|
|
var pyHistory = getHistory() as PyObject;
|
|
var isHistoryEmpty = pyHistory.GetAttr("empty").GetAndDispose<bool?>();
|
|
Assert.IsFalse(isHistoryEmpty);
|
|
Assert.IsFalse(pyHistory.HasAttr("data"));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void HistoryDataWorksCorrecltyWithoutAddingTheCustomDataInCSharp()
|
|
{
|
|
var qb = new QuantBook();
|
|
var symbol = qb.AddEquity("AAPL", Resolution.Daily).Symbol;
|
|
var datasetSymbol = Symbol.CreateBase(typeof(FundamentalUniverse), symbol, symbol.ID.Market);
|
|
MarketHoursDatabase.Reset();
|
|
Assert.DoesNotThrow(() => qb.History(datasetSymbol, new DateTime(2014, 3, 1), new DateTime(2014, 4, 1), Resolution.Daily).ToList());
|
|
}
|
|
|
|
[Test]
|
|
public void HistoryDataDoesnNotReturnDataLabelWithBaseDataCollectionTypesAndPeriods()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var testModule = PyModule.FromString("testModule",
|
|
@"
|
|
from AlgorithmImports import *
|
|
|
|
def get_history():
|
|
qb = QuantBook()
|
|
qb.set_start_date(2014, 4, 8)
|
|
symbol = qb.add_equity(""AAPL"", Resolution.DAILY).symbol
|
|
dataset_symbol = qb.add_data(FundamentalUniverse, symbol).symbol
|
|
history = qb.history(dataset_symbol, 20, Resolution.DAILY)
|
|
return history
|
|
");
|
|
dynamic getHistory = testModule.GetAttr("get_history");
|
|
var pyHistory = getHistory() as PyObject;
|
|
var isHistoryEmpty = pyHistory.GetAttr("empty").GetAndDispose<bool?>();
|
|
Assert.IsFalse(isHistoryEmpty);
|
|
Assert.IsFalse(pyHistory.HasAttr("data"));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void IndicatorHistoryDoesNotReturnPeriodColumn()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var testModule = PyModule.FromString("testModule",
|
|
@"
|
|
from AlgorithmImports import *
|
|
|
|
def get_indicator_history():
|
|
qb = QuantBook()
|
|
qb.set_start_date(2014, 4, 8)
|
|
symbol = qb.add_equity(""AAPL"", Resolution.DAILY).symbol
|
|
history = qb.indicator(ValueAtRisk(252, 0.95), symbol, 365, Resolution.DAILY)
|
|
return history
|
|
");
|
|
dynamic getHistory = testModule.GetAttr("get_indicator_history");
|
|
var pyHistory = getHistory() as PyObject;
|
|
var columns = pyHistory.GetAttr("columns")
|
|
.InvokeMethod("tolist")
|
|
.AsManagedObject(typeof(List<string>)) as List<string>;
|
|
Assert.IsFalse(columns.Contains("period"));
|
|
Assert.AreEqual(1, columns.Count);
|
|
}
|
|
}
|
|
|
|
private class TestHistoryProvider : HistoryProviderBase
|
|
{
|
|
private IHistoryProvider _provider;
|
|
|
|
public List<HistoryRequest> HistoryRequests { get; } = new();
|
|
|
|
public override int DataPointCount => _provider.DataPointCount;
|
|
|
|
public TestHistoryProvider(IHistoryProvider provider)
|
|
{
|
|
_provider = provider;
|
|
}
|
|
|
|
public override void Initialize(HistoryProviderInitializeParameters parameters)
|
|
{
|
|
}
|
|
|
|
public override IEnumerable<Slice> GetHistory(IEnumerable<HistoryRequest> requests, DateTimeZone sliceTimeZone)
|
|
{
|
|
requests = requests.ToList();
|
|
HistoryRequests.AddRange(requests);
|
|
return _provider.GetHistory(requests, sliceTimeZone);
|
|
}
|
|
}
|
|
}
|
|
}
|