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
+276
View File
@@ -0,0 +1,276 @@
/*
* 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.Linq;
using Python.Runtime;
using NUnit.Framework;
using QuantConnect.Logging;
using QuantConnect.Research;
using QuantConnect.Securities;
using QuantConnect.Interfaces;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Tests.Common.Data.Fundamental;
using QuantConnect.Configuration;
namespace QuantConnect.Tests.Research
{
[TestFixture]
public class QuantBookFundamentalTests
{
private dynamic _module;
private DateTime _startDate;
private DateTime _endDate;
private ILogHandler _logHandler;
private QuantBook _qb;
[OneTimeSetUp]
public void Setup()
{
// Store initial handler
_logHandler = Log.LogHandler;
SymbolCache.Clear();
MarketHoursDatabase.Reset();
Config.Set("fundamental-data-provider", "NullFundamentalDataProvider");
// Using a date that we have data for in the repo
_startDate = new DateTime(2014, 3, 31);
_endDate = new DateTime(2014, 3, 31);
// Our qb instance to test on
_qb = new QuantBook();
using (Py.GIL())
{
_module = Py.Import("Test_QuantBookHistory");
}
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
// Reset to initial handler
Log.LogHandler = _logHandler;
}
[Test]
public void DefaultEndDate()
{
var startDate = DateTime.UtcNow.Date.AddDays(-7);
// Expected end date should be either today if tradable, or last tradable day
var aapl = _qb.AddEquity("AAPL");
var now = DateTime.UtcNow.Date;
var expectedEndDate = aapl.Exchange.Hours.IsDateOpen(now) ? now : aapl.Exchange.Hours.GetPreviousTradingDay(now);
expectedEndDate = expectedEndDate.AddDays(1);
IEnumerable<DataDictionary<dynamic>> data = _qb.GetFundamental("AAPL", "", startDate);
// Check that the last day in the collection is as expected
var lastDay = data.Last();
Assert.AreEqual(expectedEndDate, lastDay.Time);
Assert.AreEqual(expectedEndDate, lastDay[aapl.Symbol].EndTime);
}
[TestCaseSource(nameof(DataTestCases))]
public void PyFundamentalData(dynamic input)
{
using (Py.GIL())
{
var testModule = _module.FundamentalHistoryTest();
FundamentalService.Initialize(TestGlobals.DataProvider, new TestFundamentalDataProvider(), false);
var dataFrame = testModule.getFundamentals(input[0], input[1], _startDate, _endDate);
// Should not be empty
Assert.IsFalse(dataFrame.empty.AsManagedObject(typeof(bool)));
// Get the test row (plus 1 day since data is time-stamped with the base data's end time)
var testRow = dataFrame.loc[_startDate.AddDays(1).ToPython()];
Assert.IsFalse(testRow.empty.AsManagedObject(typeof(bool)));
// Check the length
var count = testRow.__len__().AsManagedObject(typeof(int));
Assert.AreEqual(count, 1);
// Verify the data value
var index = testRow.index[0];
if (input.Length == 4)
{
var fine = testRow.at[index].AsManagedObject(typeof(FineFundamental));
Assert.AreEqual(input[2], input[3](fine));
}
else
{
var value = testRow.at[index].AsManagedObject(input[2].GetType());
Assert.AreEqual(input[2], value);
}
}
}
[TestCaseSource(nameof(DataTestCases))]
public void CSharpFundamentalData(dynamic input)
{
FundamentalService.Initialize(TestGlobals.DataProvider, new TestFundamentalDataProvider(), false);
var data = _qb.GetFundamental(input[0], input[1], _startDate, _endDate);
var currentDate = _startDate;
foreach (var day in data)
{
// plus 1 day since data is time-stamped with the base data's end time
currentDate = currentDate.AddDays(1);
foreach (var value in day.Values)
{
if (input.Length == 4)
{
Assert.AreEqual(input[2], input[3](value));
}
else
{
Assert.AreEqual(input[2], value);
}
Assert.AreEqual(currentDate, day.Time);
}
}
}
[Test]
public void PyReturnNoneTest()
{
using (Py.GIL())
{
var start = new DateTime(2023, 10, 10);
var symbol = Symbol.Create("AIG", SecurityType.Equity, Market.USA);
var testModule = _module.FundamentalHistoryTest();
var data = testModule.getFundamentals(symbol, "ValuationRatios.PERatio", start, start.AddDays(5));
Assert.AreNotEqual(true, (bool)data.empty);
var subdataframe = data.loc[start.AddDays(1).ToPython()];
PyObject result = subdataframe[symbol.ID.ToString()];
Assert.IsNull(result.As<object>());
}
}
[TestCaseSource(nameof(NullRequestTestCases))]
public void PyReturnNullTest(dynamic input)
{
using (Py.GIL())
{
var testModule = _module.FundamentalHistoryTest();
var data = testModule.getFundamentals(input[0], input[1], input[2], input[3]);
Assert.AreEqual(true, (bool)data.empty);
}
}
[TestCaseSource(nameof(NullRequestTestCases))]
public void CSharpReturnNullTest(dynamic input)
{
var data = _qb.GetFundamental(input[0], input[1], input[2], input[3]);
Assert.IsEmpty(data);
}
[TestCaseSource(nameof(FundamentalEndTimeTestCases))]
public void FundamentalDataEndTime(DateTime startDate, DateTime endDate)
{
var originalQBEndDate = _qb.EndDate;
_qb.SetEndDate(endDate);
var security = _qb.AddEquity("AAPL");
var history = _qb.History(Symbols.AAPL, startDate, endDate, Resolution.Daily).ToList();
Assert.IsNotEmpty(history);
var fundamental = (_qb.GetFundamental("AAPL", "", startDate, endDate) as IEnumerable<DataDictionary<dynamic>>).ToList();
var isEndDateOpen = security.Exchange.Hours.IsDateOpen(endDate);
var expectedFundamentalCount = 10;
var expectedHistoryCount = isEndDateOpen ? expectedFundamentalCount - 1 : expectedFundamentalCount;
Assert.AreEqual(expectedFundamentalCount, fundamental.Count);
Assert.AreEqual(expectedHistoryCount, history.Count);
var historyTimes = history.Select(x => x.EndTime.AddHours(+8));// shift 4pm to midnight to match fundamental
var fundamentalTimes = fundamental.Select(x => x.Time).SkipLast(isEndDateOpen ? 1 : 0);
CollectionAssert.AreEqual(historyTimes, fundamentalTimes);
Assert.IsTrue(fundamental.All(x => x.Time == x.Values.Cast<FineFundamental>().Single().EndTime));
_qb.RemoveSecurity(security.Symbol);
_qb.SetEndDate(originalQBEndDate);
}
// Different requests and their expected values
private static readonly object[] DataTestCases =
{
new object[] {new List<string> {"AAPL"}, null, 13.2725m, new Func<FineFundamental, double>(fundamental => fundamental.ValuationRatios.PERatio) },
new object[] {new List<string> {"AAPL"}, "ValuationRatios.PERatio", 13.2725m},
new object[] {Symbol.Create("IBM", SecurityType.Equity, Market.USA), "ValuationRatios.BookValuePerShare", 22.5177},
new object[] {new List<Symbol> {Symbol.Create("AIG", SecurityType.Equity, Market.USA)}, "FinancialStatements.NumberOfShareHolders.Value", 36319}
};
// Different requests that should return null
// Nonexistent data; start date after end date;
private static readonly object[] NullRequestTestCases =
{
new object[] {Symbol.Create("AIG", SecurityType.Equity, Market.USA), "ValuationRatios.PERatio", new DateTime(1972, 4, 1), new DateTime(1972, 4, 1)},
new object[] {Symbol.Create("IBM", SecurityType.Equity, Market.USA), "ValuationRatios.BookValuePerShare", new DateTime(2014, 4, 1), new DateTime(2014, 3, 31)},
};
private static readonly TestCaseData[] FundamentalEndTimeTestCases =
{
// monday,friday
new TestCaseData(new DateTime(2014, 3, 31), new DateTime(2014, 4, 11)),
// monday,saturday
new TestCaseData(new DateTime(2014, 3, 31), new DateTime(2014, 4, 12))
};
private class TestFundamentalDataProvider : IFundamentalDataProvider
{
public T Get<T>(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty name)
{
if (securityIdentifier == SecurityIdentifier.Empty)
{
return default;
}
return Get(time, securityIdentifier, name);
}
private dynamic Get(DateTime time, SecurityIdentifier securityIdentifier, FundamentalProperty enumName)
{
var name = Enum.GetName(enumName);
switch (name)
{
case "ValuationRatios_PERatio":
return 13.2725d;
case "ValuationRatios_BookValuePerShare":
return 22.5177d;
case "FinancialStatements_NumberOfShareHolders_TwelveMonths":
return 36319;
}
return null;
}
public void Initialize(IDataProvider dataProvider, bool liveMode)
{
}
}
}
}
+867
View File
@@ -0,0 +1,867 @@
/*
* 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);
}
}
}
}
+118
View File
@@ -0,0 +1,118 @@
/*
* 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 System;
using QuantConnect.Logging;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Research
{
[TestFixture]
public class QuantBookIndicatorsTests
{
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_QuantBookIndicator");
}
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
// Reset to initial handler
Log.LogHandler = _logHandler;
}
[TestCase(2013, 10, 11, SecurityType.Equity, "SPY")]
[TestCase(2014, 5, 9, SecurityType.Forex, "EURUSD")]
[TestCase(2016, 10, 9, SecurityType.Crypto, "BTCUSD")]
public void QuantBookIndicatorTests(int year, int month, int day, SecurityType securityType, string symbol)
{
using (Py.GIL())
{
var startDate = new DateTime(year, month, day);
var indicatorTest = _module.IndicatorTest(startDate, securityType, symbol);
var endDate = startDate;
startDate = endDate.AddYears(-1);
// Tests a data point indicator
var dfBB = indicatorTest.test_bollinger_bands(symbol, startDate, endDate, Resolution.Daily).DataFrame;
Assert.IsTrue(GetDataFrameLength(dfBB) > 0);
// Tests a bar indicator
var dfATR = indicatorTest.test_average_true_range(symbol, startDate, endDate, Resolution.Daily).DataFrame;
Assert.IsTrue(GetDataFrameLength(dfATR) > 0);
if (securityType == SecurityType.Forex)
{
return;
}
// Tests a trade bar indicator
var dfOBV = indicatorTest.test_on_balance_volume(symbol, startDate, endDate, Resolution.Daily).DataFrame;
Assert.IsTrue(GetDataFrameLength(dfOBV) > 0);
}
}
[TestCase(2013, 10, 11, SecurityType.Equity, "SPY")]
[TestCase(2014, 5, 9, SecurityType.Forex, "EURUSD")]
[TestCase(2016, 10, 9, SecurityType.Crypto, "BTCUSD")]
public void QuantBookIndicatorTests_BackwardsCompatibility(int year, int month, int day, SecurityType securityType, string symbol)
{
using (Py.GIL())
{
var startDate = new DateTime(year, month, day);
var indicatorTest = _module.IndicatorTest(startDate, securityType, symbol);
var endDate = startDate;
startDate = endDate.AddYears(-1);
// Tests a data point indicator
var dfBB = indicatorTest.test_bollinger_bands_backwards_compatibility(symbol, startDate, endDate, Resolution.Daily);
Assert.IsTrue(GetDataFrameLength(dfBB) > 0);
// Tests a bar indicator
var dfATR = indicatorTest.test_average_true_range_backwards_compatibility(symbol, startDate, endDate, Resolution.Daily);
Assert.IsTrue(GetDataFrameLength(dfATR) > 0);
if (securityType == SecurityType.Forex)
{
return;
}
// Tests a trade bar indicator
var dfOBV = indicatorTest.test_on_balance_volume_backwards_compatibility(symbol, startDate, endDate, Resolution.Daily);
Assert.IsTrue(GetDataFrameLength(dfOBV) > 0);
}
}
internal static int GetDataFrameLength(dynamic df) => (int)(df.shape[0] as PyObject).AsManagedObject(typeof(int));
}
}
+911
View File
@@ -0,0 +1,911 @@
/*
* 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.Linq;
using System.Reflection;
using Python.Runtime;
using NUnit.Framework;
using QuantConnect.Research;
using System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Scheduling;
using QuantConnect.Util;
namespace QuantConnect.Tests.Research
{
[TestFixture]
public class QuantBookSelectionTests
{
private QuantBook _qb;
private DateTime _end;
private DateTime _start;
[SetUp]
public void Setup()
{
_qb = new QuantBook();
_end = new DateTime(2014, 4, 22);
_start = new DateTime(2014, 3, 24);
}
[TestCase(Language.CSharp)]
[TestCase(Language.Python, true)]
[TestCase(Language.Python, false)]
public void UniverseSelectionData(Language language, bool flatten = false)
{
if (language == Language.CSharp)
{
var universe = _qb.AddUniverse((IEnumerable<Fundamental> fundamentals) => fundamentals.Select(x => x.Symbol));
var history = _qb.UniverseHistory(universe, _start, _end).ToList();
// we asked for 4 weeks, 5 work days for each week expected
Assert.AreEqual(20, history.Count);
Assert.IsTrue(history.All(x => x.Count() > 7000));
Assert.IsTrue(history.All(x => x.All(fundamental => fundamental.GetType() == typeof(Fundamental))));
}
else
{
using (Py.GIL())
{
var testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
def getUniverseHistory(qb, start, end):
universe = qb.AddUniverse(lambda fundamentals: [ x.Symbol for x in fundamentals ])
" + GetBaseImplementation(expectedCount: 7000, identation: " ", flatten: flatten));
dynamic getUniverse = testModule.GetAttr("getUniverseHistory");
var pyHistory = getUniverse(_qb, _start, _end);
Console.WriteLine((string)pyHistory.to_string());
if (flatten)
{
Assert.AreEqual(20, pyHistory.index.levels[0].__len__().AsManagedObject(typeof(int)));
foreach (var date in pyHistory.index.levels[0])
{
var fundamentalDataCount = pyHistory.loc[date].shape[0].AsManagedObject(typeof(int));
Assert.GreaterOrEqual(fundamentalDataCount, 7000);
}
}
else
{
Assert.AreEqual(20, pyHistory.__len__().AsManagedObject(typeof(int)));
for (var i = 0; i < 20; i++)
{
var index = pyHistory.index[i];
var type = typeof(List<Fundamental>);
var fundamental = (List<Fundamental>)pyHistory.loc[index].AsManagedObject(type);
Assert.GreaterOrEqual(fundamental.Count, 7000);
}
}
}
}
}
[TestCase(Language.CSharp, false)]
[TestCase(Language.Python, false, true)]
[TestCase(Language.Python, false, false)]
[TestCase(Language.CSharp, true)]
[TestCase(Language.Python, true, true)]
[TestCase(Language.Python, true, false)]
public void UniverseSelection(Language language, bool useUniverseUnchanged, bool flatten = false)
{
var selectionState = false;
if (language == Language.CSharp)
{
var universe = _qb.AddUniverse((IEnumerable<Fundamental> fundamentals) =>
{
if (!useUniverseUnchanged || !selectionState)
{
selectionState = true;
return new[] { Symbols.AAPL };
}
// after the first call we will return 'unchanged' if 'useUniverseUnchanged' is true
return Universe.Unchanged;
});
var history = _qb.UniverseHistory(universe, _start, _end).ToList();
// we asked for 4 weeks, 5 work days for each week expected
Assert.AreEqual(20, history.Count);
Assert.IsTrue(history.All(x => x.Count() == 1));
Assert.IsTrue(history.All(x => x.Single().Symbol == Symbols.AAPL));
Assert.IsTrue(history.All(x => x.All(fundamental => fundamental.GetType() == typeof(Fundamental))));
}
else
{
using (Py.GIL())
{
dynamic testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class Test():
def __init__(self, useUniverseUnchanged):
self.useUniverseUnchanged = useUniverseUnchanged
self.state = False
def selection(self, fundamentals):
if not self.useUniverseUnchanged or not self.state:
self.state = True
return [ x.Symbol for x in fundamentals if x.Symbol.Value == ""AAPL"" ]
return Universe.Unchanged
def getUniverseHistory(self, qb, start, end):
universe = qb.add_universe(self.selection)
" + GetBaseImplementation(expectedCount: 1, identation: " ", flatten: flatten)).GetAttr("Test");
var instance = testModule(useUniverseUnchanged);
var pyHistory = instance.getUniverseHistory(_qb, _start, _end);
if (flatten)
{
Assert.AreEqual(20, pyHistory.index.levels[0].__len__().AsManagedObject(typeof(int)));
foreach (var date in pyHistory.index.levels[0])
{
var fundamentalData = pyHistory.loc[date];
var fundamentalDataCount = fundamentalData.shape[0].AsManagedObject(typeof(int));
Assert.GreaterOrEqual(fundamentalDataCount, 1);
Assert.AreEqual(Symbols.AAPL, fundamentalData.index[0].AsManagedObject(typeof(Symbol)));
}
}
else
{
Assert.AreEqual(20, pyHistory.__len__().AsManagedObject(typeof(int)));
for (var i = 0; i < 20; i++)
{
var index = pyHistory.index[i];
var series = pyHistory.loc[index];
var type = typeof(Fundamental[]);
var fundamental = (Fundamental[])series.AsManagedObject(type);
Assert.GreaterOrEqual(fundamental.Length, 1);
Assert.AreEqual(Symbols.AAPL, fundamental[0].Symbol);
}
}
}
}
}
[TestCase(Language.CSharp)]
[TestCase(Language.Python, true)]
[TestCase(Language.Python, false)]
public void UniverseSelectionWithDateRule(Language language, bool flatten = false)
{
if (language == Language.CSharp)
{
var universe = _qb.AddUniverse((IEnumerable<Fundamental> fundamentals) =>
{
return new[] { Symbols.AAPL };
});
var history = _qb.UniverseHistory(universe, _start, _end, _qb.DateRules.WeekEnd()).ToList();
Assert.AreEqual(4, history.Count);
Assert.IsTrue(history.All(x => x.Count() == 1));
Assert.IsTrue(history.All(x => x.Single().Symbol == Symbols.AAPL));
Assert.IsTrue(history.All(x => x.All(fundamental => fundamental.GetType() == typeof(Fundamental))));
}
else
{
using (Py.GIL())
{
dynamic testModule = PyModule.FromString("testModule", @"
from AlgorithmImports import *
class Test():
def selection(self, fundamentals):
return [ x.Symbol for x in fundamentals if x.Symbol.Value == ""AAPL"" ]
def getUniverseHistory(self, qb, start, end, flatten):
universe = qb.add_universe(self.selection)
universeDataPerTime = qb.universe_history(universe, start, end, date_rule = qb.date_rules.week_end(), flatten=flatten)
if flatten:
for date in universeDataPerTime.index.levels[0]:
dateUniverseData = universeDataPerTime.loc[date]
dataPointCount = dateUniverseData.shape[0]
if dataPointCount < 1:
raise ValueError(f'Unexpected historical Fundamentals data count {dataPointCount}! Expected > 0')
else:
for universeDataCollection in universeDataPerTime:
dataPointCount = 0
for fundamental in universeDataCollection:
dataPointCount += 1
if type(fundamental) is not Fundamental:
raise ValueError(f""Unexpected Fundamentals data type {type(fundamental)}! {str(fundamental)}"")
if dataPointCount < 1:
raise ValueError(f""Unexpected historical Fundamentals data count {dataPointCount}! Expected > expectedCount"")
return universeDataPerTime
").GetAttr("Test");
var instance = testModule();
var pyHistory = instance.getUniverseHistory(_qb, _start, _end, flatten);
if (flatten)
{
Assert.AreEqual(4, pyHistory.index.levels[0].__len__().AsManagedObject(typeof(int)));
foreach (var date in pyHistory.index.levels[0])
{
var fundamentalData = pyHistory.loc[date];
var fundamentalDataCount = fundamentalData.shape[0].AsManagedObject(typeof(int));
Assert.GreaterOrEqual(fundamentalDataCount, 1);
Assert.AreEqual(Symbols.AAPL, fundamentalData.index[0].AsManagedObject(typeof(Symbol)));
}
}
else
{
Assert.AreEqual(4, pyHistory.__len__().AsManagedObject(typeof(int)));
for (var i = 0; i < 4; i++)
{
var index = pyHistory.index[i];
var series = pyHistory.loc[index];
var type = typeof(Fundamental[]);
var fundamental = (Fundamental[])series.AsManagedObject(type);
Assert.GreaterOrEqual(fundamental.Length, 1);
Assert.AreEqual(Symbols.AAPL, fundamental[0].Symbol);
}
}
}
}
}
[TestCase(Language.CSharp)]
[TestCase(Language.Python, true)]
[TestCase(Language.Python, false)]
public void UniverseSelectionEtf(Language language, bool flatten = false)
{
_start = new DateTime(2020, 12, 1);
_end = new DateTime(2021, 1, 31);
var selectionState = false;
if (language == Language.CSharp)
{
var spy = Symbol.Create("SPY", SecurityType.Equity, Market.USA);
var universe = _qb.Universe.ETF(spy, _qb.UniverseSettings, (IEnumerable<ETFConstituentUniverse> etfConstituents) =>
{
if (!selectionState)
{
selectionState = true;
return new[] { Symbols.AAPL };
}
// after the first call we will return 'unchanged' if 'useUniverseUnchanged' is true
return Universe.Unchanged;
});
var history = _qb.UniverseHistory(universe, _start, _end).ToList();
// we asked for 2 weeks, 5 work days for each week expected
Assert.AreEqual(41, history.Count);
Assert.IsTrue(history.All(x => x.Count() == 1));
Assert.IsTrue(history.All(x => x.Single().Symbol == Symbols.AAPL));
Assert.IsTrue(history.All(x => x.All(fundamental => fundamental.GetType() == typeof(ETFConstituentUniverse))));
}
else
{
using (Py.GIL())
{
dynamic testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class Test():
def __init__(self):
self.state = False
def selection(self, etfConstituents):
if not self.state:
self.state = True
return [ x.Symbol for x in etfConstituents if x.Symbol.Value == ""AAPL"" ]
return Universe.Unchanged
def getUniverseHistory(self, qb, start, end, flatten):
universe = qb.add_universe(qb.universe.etf(""SPY"", Market.USA, qb.universe_settings, self.selection))
universeDataPerTime = qb.universe_history(universe, start, end, flatten=flatten)
if flatten:
for date in universeDataPerTime.index.levels[0]:
dateUniverseData = universeDataPerTime.loc[date]
dataPointCount = dateUniverseData.shape[0]
if dataPointCount < 1:
raise ValueError(f""Unexpected historical Fundamentals data count {dataPointCount}! Expected > 0"")
else:
for universeDataCollection in universeDataPerTime:
dataPointCount = 0
for etfConstituent in universeDataCollection:
dataPointCount += 1
if type(etfConstituent) is not ETFConstituentUniverse:
raise ValueError(f""Unexpected data type {type(etfConstituent)}! {str(ETFConstituentUniverse)}"")
if dataPointCount < 1:
raise ValueError(f""Unexpected historical Fundamentals data count {dataPointCount}! Expected > expectedCount"")
return universeDataPerTime
").GetAttr("Test");
var instance = testModule();
var pyHistory = instance.getUniverseHistory(_qb, _start, _end, flatten);
if (flatten)
{
Assert.AreEqual(41, pyHistory.index.levels[0].__len__().AsManagedObject(typeof(int)));
foreach (var date in pyHistory.index.levels[0])
{
var fundamentalData = pyHistory.loc[date];
var fundamentalDataCount = fundamentalData.shape[0].AsManagedObject(typeof(int));
Assert.GreaterOrEqual(fundamentalDataCount, 1);
Assert.AreEqual(Symbols.AAPL, fundamentalData.index[0].AsManagedObject(typeof(Symbol)));
}
}
else
{
Assert.AreEqual(41, pyHistory.__len__().AsManagedObject(typeof(int)));
for (var i = 0; i < 41; i++)
{
var index = pyHistory.index[i];
var series = pyHistory.loc[index];
var type = typeof(ETFConstituentUniverse[]);
var etfConstituent = (ETFConstituentUniverse[])series.AsManagedObject(type);
Assert.GreaterOrEqual(etfConstituent.Length, 1);
Assert.AreEqual(Symbols.AAPL, etfConstituent[0].Symbol);
}
}
}
}
}
[TestCase(Language.CSharp)]
[TestCase(Language.Python, true)]
[TestCase(Language.Python, false)]
public void UniverseSelectionData_BackwardsCompatibility(Language language, bool flatten = false)
{
if (language == Language.CSharp)
{
var history = _qb.UniverseHistory<Fundamentals, Fundamental>(_start, _end).ToList();
// we asked for 4 weeks, 5 work days for each week expected
Assert.AreEqual(20, history.Count);
Assert.IsTrue(history.All(x => x.Count() > 7000));
Assert.IsTrue(history.All(x => x.All(fundamental => fundamental.GetType() == typeof(Fundamental))));
}
else
{
using (Py.GIL())
{
var testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
def getUniverseHistory(qb, start, end, flatten):
return qb.universe_history(Fundamentals, start, end, flatten=flatten)
");
dynamic getUniverse = testModule.GetAttr("getUniverseHistory");
var pyHistory = getUniverse(_qb, _start, _end, flatten);
if (flatten)
{
Assert.AreEqual(20, pyHistory.index.levels[0].__len__().AsManagedObject(typeof(int)));
foreach (var date in pyHistory.index.levels[0])
{
var fundamentalDataCount = pyHistory.loc[date].shape[0].AsManagedObject(typeof(int));
Assert.GreaterOrEqual(fundamentalDataCount, 7000);
}
}
else
{
Assert.AreEqual(20, pyHistory.__len__().AsManagedObject(typeof(int)));
for (var i = 0; i < 20; i++)
{
var index = pyHistory.index[i];
var type = typeof(List<Fundamental>);
var fundamental = (List<Fundamental>)pyHistory.loc[index].AsManagedObject(type);
Assert.GreaterOrEqual(fundamental.Count, 7000);
}
}
}
}
}
[TestCase(Language.CSharp, false)]
[TestCase(Language.Python, false, true)]
[TestCase(Language.Python, false, false)]
[TestCase(Language.CSharp, true)]
[TestCase(Language.Python, true, true)]
[TestCase(Language.Python, true, false)]
public void UniverseSelection_BackwardsCompatibility(Language language, bool useUniverseUnchanged, bool flatten = false)
{
var selectionState = false;
if (language == Language.CSharp)
{
var history = _qb.UniverseHistory<Fundamentals, Fundamental>(_start, _end, (fundamental) =>
{
if (!useUniverseUnchanged || !selectionState)
{
selectionState = true;
return new[] { Symbols.AAPL };
}
// after the first call we will return 'unchanged' if 'useUniverseUnchanged' is true
return Universe.Unchanged;
}).ToList();
// we asked for 4 weeks, 5 work days for each week expected
Assert.AreEqual(20, history.Count);
Assert.IsTrue(history.All(x => x.Count() == 1));
Assert.IsTrue(history.All(x => x.Single().Symbol == Symbols.AAPL));
Assert.IsTrue(history.All(x => x.All(fundamental => fundamental.GetType() == typeof(Fundamental))));
}
else
{
using (Py.GIL())
{
dynamic testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class Test():
def __init__(self, useUniverseUnchanged):
self.useUniverseUnchanged = useUniverseUnchanged
self.state = False
def selection(self, fundamentals):
if not self.useUniverseUnchanged or not self.state:
self.state = True
return [ x.Symbol for x in fundamentals if x.Symbol.Value == ""AAPL"" ]
return Universe.Unchanged
def getUniverseHistory(self, qb, start, end, flatten):
return qb.universe_history(Fundamentals, start, end, self.selection, flatten=flatten)
").GetAttr("Test");
var instance = testModule(useUniverseUnchanged);
var pyHistory = instance.getUniverseHistory(_qb, _start, _end, flatten);
if (flatten)
{
Assert.AreEqual(20, pyHistory.index.levels[0].__len__().AsManagedObject(typeof(int)));
foreach (var date in pyHistory.index.levels[0])
{
var fundamentalData = pyHistory.loc[date];
var fundamentalDataCount = fundamentalData.shape[0].AsManagedObject(typeof(int));
Assert.GreaterOrEqual(fundamentalDataCount, 1);
Assert.AreEqual(Symbols.AAPL, fundamentalData.index[0].AsManagedObject(typeof(Symbol)));
}
}
else
{
Assert.AreEqual(20, pyHistory.__len__().AsManagedObject(typeof(int)));
for (var i = 0; i < 20; i++)
{
var index = pyHistory.index[i];
var series = pyHistory.loc[index];
var type = typeof(Fundamental[]);
var fundamental = (Fundamental[])series.AsManagedObject(type);
Assert.GreaterOrEqual(fundamental.Length, 1);
Assert.AreEqual(Symbols.AAPL, fundamental[0].Symbol);
}
}
}
}
}
[TestCase(Language.CSharp)]
[TestCase(Language.Python, true)]
[TestCase(Language.Python, false)]
public void GenericUniverseSelectionIsCompatibleWithDateRule(Language language, bool flatten = false)
{
if (language == Language.CSharp)
{
var history = _qb.UniverseHistory<Fundamentals, Fundamental>(_start, _end, (fundamental) =>
{
return new[] { Symbols.AAPL };
}, _qb.DateRules.WeekEnd()).ToList();
// we asked for 4 weeks, 5 work days for each week expected
Assert.AreEqual(4, history.Count);
Assert.IsTrue(history.All(x => x.Count() == 1));
Assert.IsTrue(history.All(x => x.Single().Symbol == Symbols.AAPL));
Assert.IsTrue(history.All(x => x.All(fundamental => fundamental.GetType() == typeof(Fundamental))));
}
else
{
using (Py.GIL())
{
dynamic testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class Test():
def selection(self, fundamentals):
return [ x.Symbol for x in fundamentals if x.Symbol.Value == ""AAPL"" ]
def getUniverseHistory(self, qb, start, end, flatten):
return qb.universe_history(Fundamentals, start, end, self.selection, date_rule = qb.date_rules.week_end(), flatten=flatten)
").GetAttr("Test");
var instance = testModule();
var pyHistory = instance.getUniverseHistory(_qb, _start, _end, flatten);
if (flatten)
{
Assert.AreEqual(4, pyHistory.index.levels[0].__len__().AsManagedObject(typeof(int)));
foreach (var date in pyHistory.index.levels[0])
{
var fundamentalData = pyHistory.loc[date];
var fundamentalDataCount = fundamentalData.shape[0].AsManagedObject(typeof(int));
Assert.GreaterOrEqual(fundamentalDataCount, 1);
Assert.AreEqual(Symbols.AAPL, fundamentalData.index[0].AsManagedObject(typeof(Symbol)));
}
Assert.AreEqual(4, pyHistory.__len__().AsManagedObject(typeof(int)));
}
else
{
Assert.AreEqual(4, pyHistory.__len__().AsManagedObject(typeof(int)));
for (var i = 0; i < 4; i++)
{
var index = pyHistory.index[i];
var series = pyHistory.loc[index];
var type = typeof(Fundamental[]);
var fundamental = (Fundamental[])series.AsManagedObject(type);
Assert.GreaterOrEqual(fundamental.Length, 1);
Assert.AreEqual(Symbols.AAPL, fundamental[0].Symbol);
}
}
}
}
}
[Test]
public void MonthlyEndGenericUniverseSelectionWorksAsExpected()
{
var history = _qb.UniverseHistory<Fundamentals, Fundamental>(
new DateTime(2014, 3, 24),
new DateTime(2014, 4, 7),
(fundamental) =>
{
return new[] { Symbols.AAPL };
},
_qb.DateRules.MonthEnd(Symbols.AAPL)).ToList();
var lastDayOfMonth = history.Select(x => x.First()).Select(x => x.EndTime).First();
Assert.IsNotNull(lastDayOfMonth);
Assert.AreEqual(new DateTime(2014, 3, 29), lastDayOfMonth);
}
[Test]
public void MonthlyStartGenericUniverseSelectionWorksAsExpected()
{
var history = _qb.UniverseHistory<Fundamentals, Fundamental>(
new DateTime(2014, 3, 24),
new DateTime(2014, 4, 7),
(fundamental) =>
{
return new[] { Symbols.AAPL };
},
_qb.DateRules.MonthStart(Symbols.AAPL)).ToList();
var firstDayOfMonth = history.Select(x => x.First()).Select(x => x.EndTime).First();
Assert.IsNotNull(firstDayOfMonth);
Assert.AreEqual(new DateTime(2014, 4, 1), firstDayOfMonth);
}
[Test]
public void MonthlyEndSelectionWorksAsExpected()
{
var universe = _qb.AddUniverse((IEnumerable<Fundamental> fundamentals) =>
{
return new[] { Symbols.AAPL };
});
var history = _qb.UniverseHistory(universe, new DateTime(2014, 3, 15), new DateTime(2014, 4, 7), _qb.DateRules.MonthEnd(Symbols.AAPL)).ToList();
var lastDayOfMonth = history.Select(x => x.First()).Select(x => x.EndTime).First();
Assert.IsNotNull(lastDayOfMonth);
Assert.AreEqual(new DateTime(2014, 3, 29), lastDayOfMonth);
}
[Test]
public void MonthlyStartSelectionWorksAsExpected()
{
var universe = _qb.AddUniverse((IEnumerable<Fundamental> fundamentals) =>
{
return new[] { Symbols.AAPL };
});
var history = _qb.UniverseHistory(universe, new DateTime(2014, 3, 15), new DateTime(2014, 4, 7), _qb.DateRules.MonthStart(Symbols.AAPL)).ToList();
var firstDayOfMonth = history.Select(x => x.First()).Select(x => x.EndTime).First();
Assert.IsNotNull(firstDayOfMonth);
Assert.AreEqual(new DateTime(2014, 4, 1), firstDayOfMonth);
}
[Test]
public void WeekendGenericUniverseSelectionWorksAsExpected()
{
var history = _qb.UniverseHistory<Fundamentals, Fundamental>(
new DateTime(2014, 3, 24),
new DateTime(2014, 4, 7),
(fundamental) =>
{
return new[] { Symbols.AAPL };
},
_qb.DateRules.Every(DayOfWeek.Wednesday)).ToList();
var dates = history.Select(x => x.First()).Select(x => x.EndTime).ToList();
Assert.IsNotNull(dates);
Assert.IsTrue(dates.All(x => x.DayOfWeek == DayOfWeek.Wednesday));
}
[Test]
public void PythonMonthlyStartGenericUniverseSelectionWorksAsExpected([Values] bool flatten)
{
using (Py.GIL())
{
dynamic testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class Test():
def selection(self, fundamentals):
return [ x.Symbol for x in fundamentals if x.Symbol.Value == ""AAPL"" ]
def getUniverseHistory(self, qb, start, end, symbol, flatten):
return qb.universe_history(Fundamentals, start, end, self.selection, date_rule = qb.date_rules.month_start(symbol), flatten=flatten)
").GetAttr("Test");
var instance = testModule();
var pyHistory = instance.getUniverseHistory(_qb, new DateTime(2014, 3, 24), new DateTime(2014, 4, 7), Symbols.AAPL, flatten);
Assert.AreEqual(1, pyHistory.__len__().AsManagedObject(typeof(int)));
var firstDayOfTheMonth = pyHistory.index[0][flatten ? 0 : 1].AsManagedObject(typeof(DateTime));
Assert.AreEqual(new DateTime(2014, 4, 1), firstDayOfTheMonth);
}
}
[Test]
public void PythonMonthlyEndGenericUniverseSelectionWorksAsExpected([Values] bool flatten)
{
using (Py.GIL())
{
dynamic testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class Test():
def selection(self, fundamentals):
return [ x.Symbol for x in fundamentals if x.Symbol.Value == ""AAPL"" ]
def getUniverseHistory(self, qb, start, end, symbol, flatten):
return qb.universe_history(Fundamentals, start, end, self.selection, date_rule = qb.date_rules.month_end(symbol), flatten=flatten)
").GetAttr("Test");
var instance = testModule();
var pyHistory = instance.getUniverseHistory(_qb, new DateTime(2014, 3, 24), new DateTime(2014, 4, 7), Symbols.AAPL, flatten);
Assert.AreEqual(1, pyHistory.__len__().AsManagedObject(typeof(int)));
var firstDayOfTheMonth = (pyHistory.index[0][flatten ? 0 : 1]).AsManagedObject(typeof(DateTime));
Assert.AreEqual(new DateTime(2014, 3, 29), firstDayOfTheMonth);
}
}
[Test]
public void PythonDailyGenericUniverseSelectionWorksAsExpected([Values] bool flatten)
{
using (Py.GIL())
{
dynamic testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
class Test():
def selection(self, fundamentals):
return [ x.Symbol for x in fundamentals if x.Symbol.Value == ""AAPL"" ]
def getUniverseHistory(self, qb, start, end, symbol, flatten):
return qb.universe_history(Fundamentals, start, end, self.selection, date_rule = qb.date_rules.every_day(), flatten=flatten)
").GetAttr("Test");
var instance = testModule();
var pyHistory = instance.getUniverseHistory(_qb, new DateTime(2014, 3, 24), new DateTime(2014, 4, 7), Symbols.AAPL, flatten);
if (flatten)
{
Assert.AreEqual(10, pyHistory.index.levels[0].__len__().AsManagedObject(typeof(int)));
foreach (var date in pyHistory.index.levels[0])
{
var fundamentalData = pyHistory.loc[date];
var fundamentalDataCount = fundamentalData.shape[0].AsManagedObject(typeof(int));
Assert.GreaterOrEqual(fundamentalDataCount, 1);
Assert.AreEqual(Symbols.AAPL, fundamentalData.index[0].AsManagedObject(typeof(Symbol)));
}
}
else
{
Assert.AreEqual(10, pyHistory.__len__().AsManagedObject(typeof(int)));
for (var i = 0; i < 10; i++)
{
var index = pyHistory.index[i];
var series = pyHistory.loc[index];
var type = typeof(Fundamental[]);
var fundamental = (Fundamental[])series.AsManagedObject(type);
Assert.GreaterOrEqual(fundamental.Length, 1);
Assert.AreEqual(Symbols.AAPL, fundamental[0].Symbol);
}
}
}
}
[Test]
public void PythonWeekendGenericUniverseSelectionWorksAsExpected([Values] bool flatten)
{
using (Py.GIL())
{
dynamic testModule = PyModule.FromString("testModule",
@"
from AlgorithmImports import *
from datetime import datetime
class Test():
def selection(self, fundamentals):
return [ x.Symbol for x in fundamentals if x.Symbol.Value == ""AAPL"" ]
def getUniverseHistory(self, qb, start, end, symbol, flatten):
return qb.universe_history(Fundamentals, start, end, self.selection,
date_rule=qb.date_rules.on(datetime(2014, 3, 30), datetime(2014, 3, 31), datetime(2014, 4, 1)),
flatten=flatten)
").GetAttr("Test");
var instance = testModule();
var pyHistory = instance.getUniverseHistory(_qb, new DateTime(2014, 3, 24), new DateTime(2014, 4, 7), Symbols.AAPL, flatten);
if (flatten)
{
Assert.AreEqual(2, pyHistory.index.levels[0].__len__().AsManagedObject(typeof(int)));
foreach (var date in pyHistory.index.levels[0])
{
var fundamentalData = pyHistory.loc[date];
var fundamentalDataCount = fundamentalData.shape[0].AsManagedObject(typeof(int));
Assert.GreaterOrEqual(fundamentalDataCount, 1);
Assert.AreEqual(Symbols.AAPL, fundamentalData.index[0].AsManagedObject(typeof(Symbol)));
}
}
else
{
Assert.AreEqual(2, pyHistory.__len__().AsManagedObject(typeof(int)));
for (var i = 0; i < 2; i++)
{
var index = pyHistory.index[i];
var series = pyHistory.loc[index];
var type = typeof(Fundamental[]);
var fundamental = (Fundamental[])series.AsManagedObject(type);
Assert.GreaterOrEqual(fundamental.Length, 1);
Assert.AreEqual(Symbols.AAPL, fundamental[0].Symbol);
}
}
}
}
[Test]
public void FilterUniverseDataFallsBackToCurrencyPairMatchingWhenSecurityTypesDiffer()
{
var eurBase = new Symbol(SecurityIdentifier.GenerateBase(typeof(Fundamental), "EUR", Market.USA), "EUR");
var gbpBase = new Symbol(SecurityIdentifier.GenerateBase(typeof(Fundamental), "GBP", Market.USA), "GBP");
var eurUsd = Symbol.Create("EURUSD", SecurityType.Forex, Market.Oanda);
// selection returns Forex — security type differs from Base data symbols
var filteredSymbols = new HashSet<Symbol> { eurUsd };
var data = new List<BaseData> { new Tick { Symbol = eurBase }, new Tick { Symbol = gbpBase } };
var filterMethod = typeof(QuantBook).GetMethod("FilterUniverseData", BindingFlags.NonPublic | BindingFlags.Static);
var result = (List<BaseData>)filterMethod.Invoke(null, new object[] { data, filteredSymbols, (SecurityType?)eurUsd.SecurityType });
// EUR matches EURUSD base currency, GBP matches neither
Assert.AreEqual(1, result.Count);
Assert.AreEqual(eurBase, result[0].Symbol);
}
[Test]
public void PerformSelectionDoesNotSkipDataPointWhenPreviousDataPointIsYielded()
{
var historyDataPoints = new List<BaseDataCollection>()
{
new BaseDataCollection(new DateTime(2024, 10, 14), Symbols.AAPL),
new BaseDataCollection(new DateTime(2024, 10, 15), Symbols.AAPL),
new BaseDataCollection(new DateTime(2024, 10, 17), Symbols.AAPL),
new BaseDataCollection(new DateTime(2024, 10, 22), Symbols.AAPL),
};
var dateRule = _qb.DateRules.On(new DateTime(2024, 10, 14), new DateTime(2024, 10, 16), new DateTime(2024, 10, 18));
var selectedDates = QuantBookTestClass.PerformSelection(historyDataPoints, new DateTime(2024, 10, 14), new DateTime(2024, 10, 22), dateRule).Select(x => x.EndTime).ToList();
for (int index = 0; index < 3; index++)
{
Assert.AreEqual(historyDataPoints[index].EndTime, selectedDates[index]);
}
}
private static string GetBaseImplementation(int expectedCount, string identation, bool flatten = true)
{
if (flatten)
{
return @"
{identation}universe_data_df = qb.universe_history(universe, start, end, flatten=True)
{identation}for date in universe_data_df.index.levels[0]:
{identation} dateUniverseData = universe_data_df.loc[date]
{identation} dataPointCount = dateUniverseData.shape[0]
{identation} if dataPointCount < expectedCount:
{identation} raise ValueError(f""Unexpected historical Fundamentals data count {dataPointCount}! Expected > expectedCount"")
{identation}return universe_data_df
".Replace("expectedCount", expectedCount.ToStringInvariant(), StringComparison.InvariantCulture)
.Replace("{identation}", identation, StringComparison.InvariantCulture);
}
return @"
{identation}universeDataPerTime = qb.universe_history(universe, start, end)
{identation}for universeDataCollection in universeDataPerTime:
{identation} dataPointCount = 0
{identation} for fundamental in universeDataCollection:
{identation} dataPointCount += 1
{identation} if type(fundamental) is not Fundamental:
{identation} raise ValueError(f""Unexpected Fundamentals data type {type(fundamental)}! {str(fundamental)}"")
{identation} if dataPointCount < expectedCount:
{identation} raise ValueError(f""Unexpected historical Fundamentals data count {dataPointCount}! Expected > expectedCount"")
{identation}return universeDataPerTime
".Replace("expectedCount", expectedCount.ToStringInvariant(), StringComparison.InvariantCulture)
.Replace("{identation}", identation, StringComparison.InvariantCulture);
}
private class QuantBookTestClass : QuantBook
{
public static IEnumerable<BaseDataCollection> PerformSelection(IEnumerable<BaseDataCollection> history, DateTime start, DateTime end, IDateRule dateRule)
{
Func<BaseDataCollection, BaseDataCollection> processDataPointFunction = dataPoint => dataPoint;
Func<BaseDataCollection, DateTime> getTime = dataPoint => dataPoint.EndTime.Date;
return PerformSelection<BaseDataCollection, BaseDataCollection>(history, processDataPointFunction, getTime, start, end, dateRule);
}
}
}
}
+55
View File
@@ -0,0 +1,55 @@
/*
* 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.Research;
using QuantConnect.Configuration;
using Newtonsoft.Json.Linq;
namespace QuantConnect.Tests.Research
{
[TestFixture]
public class QuantBookTests
{
[Test]
public void AlgorithmModeIsResearch()
{
var qb = new QuantBook();
Assert.AreEqual(AlgorithmMode.Research, qb.AlgorithmMode);
}
[TestCase(DeploymentTarget.CloudPlatform)]
[TestCase(DeploymentTarget.LocalPlatform)]
[TestCase(null)]
public void SetsDeploymentTarget(DeploymentTarget? deploymentTarget)
{
Config.Reset();
if (deploymentTarget.HasValue)
{
Config.Set("deployment-target", JToken.FromObject(deploymentTarget));
Config.Write();
}
else
{
// The default value for deploymentTarget = DeploymentTarget.LocalPlatform
deploymentTarget = DeploymentTarget.LocalPlatform;
}
var qb = new QuantBook();
Assert.AreEqual(deploymentTarget, qb.DeploymentTarget);
}
}
}
@@ -0,0 +1,103 @@
# 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.
from AlgorithmImports import *
from custom_data import *
class SecurityHistoryTest():
def __init__(self, start_date, security_type, symbol):
self.qb = QuantBook()
self.qb.SetStartDate(start_date)
self.symbol = self.qb.AddSecurity(security_type, symbol).Symbol
self.column = 'close'
def __str__(self):
return "{} on {}".format(self.symbol.ID, self.qb.StartDate)
def test_period_overload(self, period):
history = self.qb.History([self.symbol], period)
return history[self.column].unstack(level=0)
def test_daterange_overload(self, end):
start = end - timedelta(1)
history = self.qb.History([self.symbol], start, end)
return history[self.column].unstack(level=0)
class OptionHistoryTest(SecurityHistoryTest):
def test_daterange_overload(self, end, start = None):
if start is None:
start = end - timedelta(1)
history = self.qb.GetOptionHistory(self.symbol, start, end)
return history.GetAllData()
class FutureHistoryTest(SecurityHistoryTest):
def test_daterange_overload(self, end, start = None, maxFilter = 182):
if start is None:
start = end - timedelta(1)
self.qb.Securities[self.symbol].SetFilter(0, maxFilter) # default is 35 days
history = self.qb.GetFutureHistory(self.symbol, start, end)
return history.GetAllData()
class FutureContractHistoryTest():
def __init__(self, start_date, security_type, symbol):
self.qb = QuantBook()
self.qb.SetStartDate(start_date)
self.symbol = symbol
self.column = 'close'
def test_daterange_overload(self, end):
start = end - timedelta(1)
history = self.qb.GetFutureHistory(self.symbol, start, end)
return history.GetAllData()
class OptionContractHistoryTest(FutureContractHistoryTest):
def test_daterange_overload(self, end):
start = end - timedelta(1)
history = self.qb.GetOptionHistory(self.symbol, start, end)
return history.GetAllData()
class CustomDataHistoryTest(SecurityHistoryTest):
def __init__(self, start_date, security_type, symbol):
self.qb = QuantBook()
self.qb.SetStartDate(start_date)
if security_type == 'Nifty':
type = Nifty
self.column = 'close'
elif security_type == 'CustomPythonData':
type = CustomPythonData
self.column = 'close'
else:
raise
self.symbol = self.qb.AddData(type, symbol, Resolution.Daily).Symbol
class MultipleSecuritiesHistoryTest(SecurityHistoryTest):
def __init__(self, start_date, security_type, symbol):
self.qb = QuantBook()
self.qb.SetStartDate(start_date)
self.qb.AddEquity('SPY', Resolution.Daily)
self.qb.AddForex('EURUSD', Resolution.Daily)
self.qb.AddCrypto('BTCUSD', Resolution.Daily)
def test_period_overload(self, period):
history = self.qb.History(self.qb.Securities.Keys, period)
return history['close'].unstack(level=0)
class FundamentalHistoryTest():
def __init__(self):
self.qb = QuantBook()
def getFundamentals(self, ticker, selector, start, end):
return self.qb.GetFundamental(ticker, selector, start, end)
@@ -0,0 +1,47 @@
# 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.
from AlgorithmImports import *
class IndicatorTest():
def __init__(self, start_date, security_type, symbol):
self.qb = QuantBook()
self.qb.SetStartDate(start_date)
self.symbol = self.qb.AddSecurity(security_type, symbol).Symbol
def __str__(self):
return "{} on {}".format(self.symbol.ID, self.qb.StartDate)
def test_bollinger_bands(self, symbol, start, end, resolution):
ind = BollingerBands(10, 2)
return self.qb.IndicatorHistory(ind, symbol, start, end, resolution)
def test_average_true_range(self, symbol, start, end, resolution):
ind = AverageTrueRange(14)
return self.qb.IndicatorHistory(ind, symbol, start, end, resolution)
def test_on_balance_volume(self, symbol, start, end, resolution):
ind = OnBalanceVolume(symbol)
return self.qb.IndicatorHistory(ind, symbol, start, end, resolution)
def test_bollinger_bands_backwards_compatibility(self, symbol, start, end, resolution):
ind = BollingerBands(10, 2)
return self.qb.Indicator(ind, symbol, start, end, resolution)
def test_average_true_range_backwards_compatibility(self, symbol, start, end, resolution):
ind = AverageTrueRange(14)
return self.qb.Indicator(ind, symbol, start, end, resolution)
def test_on_balance_volume_backwards_compatibility(self, symbol, start, end, resolution):
ind = OnBalanceVolume(symbol)
return self.qb.Indicator(ind, symbol, start, end, resolution)
@@ -0,0 +1,70 @@
# 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.
from AlgorithmImports import *
import decimal
class CustomPythonData(PythonData):
def get_source(self, config, date, is_live):
source = Globals.DataFolder + "/equity/usa/daily/ibm.zip"
return SubscriptionDataSource(source, SubscriptionTransportMedium.LocalFile, FileFormat.Csv)
def reader(self, config, line, date, is_live):
if line == None:
return None
customPythonData = CustomPythonData()
customPythonData.Symbol = config.Symbol
scaleFactor = 1 / 10000
csv = line.split(",")
customPythonData.Time = datetime.strptime(csv[0], '%Y%m%d %H:%M')
customPythonData["Open"] = float(csv[1]) * scaleFactor
customPythonData["High"] = float(csv[2]) * scaleFactor
customPythonData["Low"] = float(csv[3]) * scaleFactor
customPythonData["Close"] = float(csv[4]) * scaleFactor
customPythonData["Volume"] = float(csv[5])
return customPythonData
class Nifty(PythonData):
'''NIFTY Custom Data Class'''
def get_source(self, config, date, is_live_mode):
return SubscriptionDataSource("https://www.dropbox.com/s/rsmg44jr6wexn2h/CNXNIFTY.csv?dl=1", SubscriptionTransportMedium.REMOTE_FILE)
def reader(self, config, line, date, is_live_mode):
if not (line.strip() and line[0].isdigit()): return None
# New Nifty object
index = Nifty()
index.symbol = config.symbol
try:
# Example File Format:
# Date, Open High Low Close Volume Turnover
# 2011-09-13 7792.9 7799.9 7722.65 7748.7 116534670 6107.78
data = line.split(',')
index.time = datetime.strptime(data[0], "%Y-%m-%d")
index.value = decimal.Decimal(data[4])
index["Open"] = float(data[1])
index["High"] = float(data[2])
index["Low"] = float(data[3])
index["Close"] = float(data[4])
except ValueError:
# Do nothing
return None
return index
@@ -0,0 +1,137 @@
/*
* 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 QuantConnect.Interfaces;
namespace QuantConnect.Tests.Research.RegressionTemplates
{
/// <summary>
/// Basic template framework for regression testing of research notebooks
/// </summary>
public class BasicTemplateCustomDataTypeHistoryResearchCSharp : IRegressionResearchDefinition
{
/// <summary>
/// Expected output from the reading the raw notebook file
/// </summary>
/// <remarks>Requires to be implemented last in the file <see cref="ResearchRegressionTests.UpdateResearchRegressionOutputInSourceFile"/>
/// get should start from next line</remarks>
public string ExpectedOutput =>
"{ \"cells\": [ { \"cell_type\": \"markdown\", \"id\": \"c5f3ed6d\", \"metadata\": { \"papermill\": { \"duration\": 0.003499, \"end_t" +
"ime\": \"2023-02-17T23:37:52.736173\", \"exception\": false, \"start_time\": \"2023-02-17T23:37:52.732674\", \"status\": \"completed\" " +
"}, \"tags\": [] }, \"source\": [ \"![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\", \"<hr>\" ] }, { \"cell_type" +
"\": \"markdown\", \"id\": \"2025ecbd\", \"metadata\": { \"papermill\": { \"duration\": 0.002997, \"end_time\": \"2023-02-17T23:37:52.74" +
"1673\", \"exception\": false, \"start_time\": \"2023-02-17T23:37:52.738676\", \"status\": \"completed\" }, \"tags\": [] }, \"sou" +
"rce\": [ \"# Custom data history\" ] }, { \"cell_type\": \"code\", \"execution_count\": 1, \"id\": \"bbc993a0\", \"metadata\": { \"e" +
"xecution\": { \"iopub.execute_input\": \"2023-02-17T23:37:52.761679Z\", \"iopub.status.busy\": \"2023-02-17T23:37:52.753179Z\", \"iopub.st" +
"atus.idle\": \"2023-02-17T23:38:01.186963Z\", \"shell.execute_reply\": \"2023-02-17T23:38:01.173466Z\" }, \"papermill\": { \"duration\":" +
" 8.442283, \"end_time\": \"2023-02-17T23:38:01.187462\", \"exception\": false, \"start_time\": \"2023-02-17T23:37:52.745179\", \"statu" +
"s\": \"completed\" }, \"tags\": [], \"vscode\": { \"languageId\": \"csharp\" } }, \"outputs\": [ { \"data\": { \"text/" +
"html\": [ \"\", \"<div>\", \" <div id='dotnet-interactive-this-cell-122456.Microsoft.DotNet.Interactive.Http.HttpPort' style='dis" +
"play: none'>\", \" The below script needs to be able to find the current output cell; this is an easy method to get it.\", \" </" +
"div>\", \" <script type='text/javascript'>\", \"async function probeAddresses(probingAddresses) {\", \" function timeout(ms, p" +
"romise) {\", \" return new Promise(function (resolve, reject) {\", \" setTimeout(function () {\", \" " +
" reject(new Error('timeout'))\", \" }, ms)\", \" promise.then(resolve, reject)\", \" })\", \" " +
" }\", \"\", \" if (Array.isArray(probingAddresses)) {\", \" for (let i = 0; i < probingAddresses.length; i++) {\", \"" +
"\", \" let rootUrl = probingAddresses[i];\", \"\", \" if (!rootUrl.endsWith('/')) {\", \" " +
" rootUrl = `${rootUrl}/`;\", \" }\", \"\", \" try {\", \" let response = await timeout(10" +
"00, fetch(`${rootUrl}discovery`, {\", \" method: 'POST',\", \" cache: 'no-cache',\", \" " +
" mode: 'cors',\", \" timeout: 1000,\", \" headers: {\", \" " +
"'Content-Type': 'text/plain'\", \" },\", \" body: probingAddresses[i]\", \" }))" +
";\", \"\", \" if (response.status == 200) {\", \" return rootUrl;\", \" }\", " +
" \" }\", \" catch (e) { }\", \" }\", \" }\", \"}\", \"\", \"function loadDotn" +
"etInteractiveApi() {\", \" probeAddresses([\\\"http://172.19.192.1:1000/\\\", \\\"http://192.168.56.1:1000/\\\", \\\"http://192.168.16.104:10" +
"00/\\\", \\\"http://127.0.0.1:1000/\\\"])\", \" .then((root) => {\", \" // use probing to find host url and api resources\"," +
" \" // load interactive helpers and language services\", \" let dotnetInteractiveRequire = require.config({\", \" " +
" context: '122456.Microsoft.DotNet.Interactive.Http.HttpPort',\", \" paths:\", \" {\", \" " +
" 'dotnet-interactive': `${root}resources`\", \" }\", \" }) || require;\", \"\", \" window.dot" +
"netInteractiveRequire = dotnetInteractiveRequire;\", \"\", \" window.configureRequireFromExtension = function(extensionName, ex" +
"tensionCacheBuster) {\", \" let paths = {};\", \" paths[extensionName] = `${root}extensions/${extensionName}" +
"/resources/`;\", \" \", \" let internalRequire = require.config({\", \" context: ex" +
"tensionCacheBuster,\", \" paths: paths,\", \" urlArgs: `cacheBuster=${extensionCacheBuster}`\", " +
" \" }) || require;\", \"\", \" return internalRequire\", \" };\", \" \", " +
" \" dotnetInteractiveRequire([\", \" 'dotnet-interactive/dotnet-interactive'\", \" ],\"," +
" \" function (dotnet) {\", \" dotnet.init(window);\", \" },\", \" " +
" function (error) {\", \" console.log(error);\", \" }\", \" );\", \" })" +
"\", \" .catch(error => {console.log(error);});\", \" }\", \"\", \"// ensure `require` is available globally\", " +
" \"if ((typeof(require) !== typeof(Function)) || (typeof(require.config) !== typeof(Function))) {\", \" let require_script = document.create" +
"Element('script');\", \" require_script.setAttribute('src', 'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js');\", " +
" \" require_script.setAttribute('type', 'text/javascript');\", \" \", \" \", \" require_script.onload = function() {\"" +
", \" loadDotnetInteractiveApi();\", \" };\", \"\", \" document.getElementsByTagName('head')[0].appendChild(requir" +
"e_script);\", \"}\", \"else {\", \" loadDotnetInteractiveApi();\", \"}\", \"\", \" </script>\", \"</di" +
"v>\" ] }, \"metadata\": {}, \"output_type\": \"display_data\" }, { \"name\": \"stdout\", \"output_type\": \"stream\", " +
" \"text\": [ \"Initialize.csx: Loading assemblies from C:\\\\Users\\\\jhona\\\\QuantConnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\" ] } ], " +
" \"source\": [ \"// We need to load assemblies at the start in their own cell\", \"#load \\\"./Initialize.csx\\\"\" ] }, { \"cell_type\":" +
" \"code\", \"execution_count\": 2, \"id\": \"0f8ca7c8\", \"metadata\": { \"execution\": { \"iopub.execute_input\": \"2023-02-17T23:38:01." +
"204969Z\", \"iopub.status.busy\": \"2023-02-17T23:38:01.203966Z\", \"iopub.status.idle\": \"2023-02-17T23:38:01.720374Z\", \"shell.execute" +
"_reply\": \"2023-02-17T23:38:01.718372Z\" }, \"papermill\": { \"duration\": 0.527908, \"end_time\": \"2023-02-17T23:38:01.720374\", " +
"\"exception\": false, \"start_time\": \"2023-02-17T23:38:01.192466\", \"status\": \"completed\" }, \"tags\": [], \"vscode\": { \"" +
"languageId\": \"csharp\" } }, \"outputs\": [ { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20230217 23" +
":38:01.491 TRACE:: Config.GetValue(): debug-mode - Using default value: False\" ] }, { \"name\": \"stdout\", \"output_type\": \"stre" +
"am\", \"text\": [ \"20230217 23:38:01.493 TRACE:: Config.Get(): Configuration key not found. Key: results-destination-folder - Using default " +
"value: \" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20230217 23:38:01.496 TRACE:: Config.Get(" +
"): Configuration key not found. Key: plugin-directory - Using default value: \" ] }, { \"name\": \"stdout\", \"output_type\": \"stre" +
"am\", \"text\": [ \"20230217 23:38:01.501 TRACE:: Config.Get(): Configuration key not found. Key: composer-dll-directory - Using default valu" +
"e: \" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20230217 23:38:01.502 TRACE:: Composer(): Loa" +
"ding Assemblies from C:\\\\Users\\\\jhona\\\\QuantConnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\" ] }, { \"name\": \"stdout\", \"output_type\":" +
" \"stream\", \"text\": [ \"20230217 23:38:01.587 TRAC" +
"E:: Config.Get(): Configuration key not found. Key: version-id - Using default value: \" ] }, { \"name\": \"stdout\", \"output_type\"" +
": \"stream\", \"text\": [ \"20230217 23:38:01.587 TRACE:: Config.Get(): Configuration key not found. Key: cache-location - Using default valu" +
"e: ../../../Data/\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20230217 23:38:01.588 TRACE:: E" +
"ngine.Main(): LEAN ALGORITHMIC TRADING ENGINE v2.5.0.0 Mode: DEBUG (64bit) Host: ABREU\" ] }, { \"name\": \"stdout\", \"output_type\"" +
": \"stream\", \"text\": [ \"20230217 23:38:01.589 TRACE:: Engine.Main(): Started 7:38 PM\" ] }, { \"name\": \"stdout\", \"o" +
"utput_type\": \"stream\", \"text\": [ \"20230217 23:38:01.597 TRACE:: Config.Get(): Configuration key not found. Key: lean-manager-type - Usi" +
"ng default value: LocalLeanManager\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20230217 23:38" +
":01.619 TRACE:: Config.Get(): Configuration key not found. Key: data-permission-manager - Using default value: DataPermissionManager\" ] }, " +
"{ \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20230217 23:38:01.623 TRACE:: Config.Get(): Configuration key not " +
"found. Key: results-destination-folder - Using default value: C:\\\\Users\\\\jhona\\\\QuantConnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\" ] }, {" +
" \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20230217 23:38:01.657 TRACE:: Config.Get(): Configuration key not f" +
"ound. Key: object-store-root - Using default value: ./storage\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text" +
"\": [ \"20230217 23:38:01.668 TRACE:: Config.Get(): Configuration key not found. Key: results-destination-folder - Using default value: C:\\\\Use" +
"rs\\\\jhona\\\\QuantConnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\" ] } ], \"source\": [ \"// Initialize Lean Engine.\", \"#load \\\"./Qua" +
"ntConnect.csx\\\"\", \"\", \"using System.Globalization;\", \"using System.Linq;\", \"using QuantConnect;\", \"using QuantConnect.Data;" +
"\", \"using QuantConnect.Algorithm;\", \"using QuantConnect.Research;\" ] }, { \"cell_type\": \"code\", \"execution_count\": 3, \"id\"" +
": \"83870958\", \"metadata\": { \"execution\": { \"iopub.execute_input\": \"2023-02-17T23:38:01.737374Z\", \"iopub.status.busy\": \"2023-" +
"02-17T23:38:01.735874Z\", \"iopub.status.idle\": \"2023-02-17T23:38:02.793265Z\", \"shell.execute_reply\": \"2023-02-17T23:38:02.792263Z\" " +
"}, \"papermill\": { \"duration\": 1.067389, \"end_time\": \"2023-02-17T23:38:02.793764\", \"exception\": false, \"start_time\": \"2" +
"023-02-17T23:38:01.726375\", \"status\": \"completed\" }, \"tags\": [], \"vscode\": { \"languageId\": \"csharp\" } }, \"output" +
"s\": [], \"source\": [ \"class CustomDataType : DynamicData\", \"{\", \" public decimal Open;\", \" public decimal High;\", \" " +
" public decimal Low;\", \" public decimal Close;\", \"\", \" public override SubscriptionDataSource GetSource(SubscriptionDataConfig " +
"config, DateTime date, bool isLiveMode)\", \" {\", \" var source = \\\"https://www.dl.dropboxusercontent.com/s/d83xvd7mm9fzpk0/path_to" +
"_my_csv_data.csv?dl=0\\\";\", \" return new SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile);\", \" }\", \"\"" +
", \" public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)\", \" {\", \" i" +
"f (string.IsNullOrWhiteSpace(line.Trim()))\", \" {\", \" return null;\", \" }\", \"\", \" try\", \" " +
" {\", \" var csv = line.Split(\\\",\\\");\", \" var data = new CustomDataType()\", \" {\", \" " +
" Symbol = config.Symbol,\", \" Time = DateTime.ParseExact(csv[0], DateFormat.DB, CultureInfo.InvariantCulture).AddHours(20)" +
",\", \" Value = csv[4].ToDecimal(),\", \" Open = csv[1].ToDecimal(),\", \" High = csv[2].ToDecim" +
"al(),\", \" Low = csv[3].ToDecimal(),\", \" Close = csv[4].ToDecimal()\", \" };\", \"\", \" " +
" return data;\", \" }\", \" catch\", \" {\", \" return null;\", \" }\", \" }\", " +
" \"}\" ] }, { \"cell_type\": \"code\", \"execution_count\": 4, \"id\": \"ed9ea0a3\", \"metadata\": { \"execution\": { \"iopub.execu" +
"te_input\": \"2023-02-17T23:38:02.815268Z\", \"iopub.status.busy\": \"2023-02-17T23:38:02.814265Z\", \"iopub.status.idle\": \"2023-02-17T23:38" +
":10.436355Z\", \"shell.execute_reply\": \"2023-02-17T23:38:10.435324Z\" }, \"papermill\": { \"duration\": 7.633591, \"end_time\": \"" +
"2023-02-17T23:38:10.436355\", \"exception\": false, \"start_time\": \"2023-02-17T23:38:02.802764\", \"status\": \"completed\" }, \"t" +
"ags\": [], \"vscode\": { \"languageId\": \"csharp\" } }, \"outputs\": [ { \"name\": \"stdout\", \"output_type\": \"stream\", " +
" \"text\": [ \"PythonEngine.Initialize(): clr GetManifestResourceStream...\" ] } ], \"source\": [ \"var qb = new QuantBook();\"," +
" \"var symbol = qb.AddData<CustomDataType>(\\\"CustomDataType\\\", Resolution.Hour).Symbol;\", \"\", \"var start = new DateTime(2017, 8, 20);" +
"\", \"var end = start.AddHours(48);\", \"var history = qb.History<CustomDataType>(symbol, start, end, Resolution.Hour).ToList();\", \"\", " +
"\"if (history.Count == 0)\", \"{\", \" throw new Exception(\\\"No history data returned\\\");\", \"}\" ] } ], \"metadata\": { \"kernel" +
"spec\": { \"display_name\": \".NET (C#)\", \"language\": \"C#\", \"name\": \".net-csharp\" }, \"language_info\": { \"file_extension\": \".cs" +
"\", \"mimetype\": \"text/x-csharp\", \"name\": \"C#\", \"pygments_lexer\": \"csharp\", \"version\": \"10.0\" }, \"papermill\": { \"default" +
"_parameters\": {}, \"duration\": 25.341314, \"end_time\": \"2023-02-17T23:38:13.053129\", \"environment_variables\": {}, \"exception\": null, " +
" \"input_path\": \"C:\\\\Users\\\\jhona\\\\QuantConnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\\\\Research\\\\RegressionTemplates\\\\BasicTemplateCustomDat" +
"aTypeHistoryResearchCSharp.ipynb\", \"output_path\": \"C:\\\\Users\\\\jhona\\\\QuantConnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\\\\Research\\\\Regressi" +
"onTemplates\\\\BasicTemplateCustomDataTypeHistoryResearchCSharp-output.ipynb\", \"parameters\": {}, \"start_time\": \"2023-02-17T23:37:47.711815\"" +
", \"version\": \"2.4.0\" } }, \"nbformat\": 4, \"nbformat_minor\": 5}";
}
}
@@ -0,0 +1,58 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [ "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\n", "<hr>" ]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [ "# Custom data history" ]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": { "vscode": { "languageId": "csharp" } },
"outputs": [],
"source": [ "// We need to load assemblies at the start in their own cell\n", "#load \"./Initialize.csx\"" ]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": { "vscode": { "languageId": "csharp" } },
"outputs": [],
"source": [ "// Initialize Lean Engine.\n", "#load \"./QuantConnect.csx\"\n", "\n", "using System.Globalization;\n", "using System.Linq;\n", "using QuantConnect;\n", "using QuantConnect.Data;\n", "using QuantConnect.Algorithm;\n", "using QuantConnect.Research;" ]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": { "vscode": { "languageId": "csharp" } },
"outputs": [],
"source": [ "class CustomDataType : DynamicData\n", "{\n", " public decimal Open;\n", " public decimal High;\n", " public decimal Low;\n", " public decimal Close;\n", "\n", " public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)\n", " {\n", " var source = \"https://www.dl.dropboxusercontent.com/s/d83xvd7mm9fzpk0/path_to_my_csv_data.csv?dl=0\";\n", " return new SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile);\n", " }\n", "\n", " public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)\n", " {\n", " if (string.IsNullOrWhiteSpace(line.Trim()))\n", " {\n", " return null;\n", " }\n", "\n", " try\n", " {\n", " var csv = line.Split(\",\");\n", " var data = new CustomDataType()\n", " {\n", " Symbol = config.Symbol,\n", " Time = DateTime.ParseExact(csv[0], DateFormat.DB, CultureInfo.InvariantCulture).AddHours(20),\n", " Value = csv[4].ToDecimal(),\n", " Open = csv[1].ToDecimal(),\n", " High = csv[2].ToDecimal(),\n", " Low = csv[3].ToDecimal(),\n", " Close = csv[4].ToDecimal()\n", " };\n", "\n", " return data;\n", " }\n", " catch\n", " {\n", " return null;\n", " }\n", " }\n", "}" ]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": { "vscode": { "languageId": "csharp" } },
"outputs": [],
"source": [ "var qb = new QuantBook();\n", "var symbol = qb.AddData<CustomDataType>(\"CustomDataType\", Resolution.Hour).Symbol;\n", "\n", "var start = new DateTime(2017, 8, 20);\n", "var end = start.AddHours(48);\n", "var history = qb.History<CustomDataType>(symbol, start, end, Resolution.Hour).ToList();\n", "\n", "if (history.Count == 0)\n", "{\n", " throw new Exception(\"No history data returned\");\n", "}" ]
}
],
"metadata": {
"kernelspec": {
"display_name": ".NET (C#)",
"language": "C#",
"name": ".net-csharp"
},
"language_info": {
"file_extension": ".cs",
"mimetype": "text/x-csharp",
"name": "C#",
"pygments_lexer": "csharp",
"version": "9.0"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,81 @@
/*
* 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 QuantConnect.Interfaces;
namespace QuantConnect.Tests.Research.RegressionTemplates
{
/// <summary>
/// Basic template framework for regression testing of research notebooks
/// </summary>
public class BasicTemplateCustomDataTypeHistoryResearchPython : IRegressionResearchDefinition
{
/// <summary>
/// Expected output from the reading the raw notebook file
/// </summary>
/// <remarks>Requires to be implemented last in the file <see cref="ResearchRegressionTests.UpdateResearchRegressionOutputInSourceFile"/>
/// get should start from next line</remarks>
public string ExpectedOutput =>
"{ \"cells\": [ { \"cell_type\": \"markdown\", \"id\": \"d0ea3064\", \"metadata\": { \"papermill\": { \"duration\": 0.003996, \"end_t" +
"ime\": \"2023-02-17T21:33:10.586532\", \"exception\": false, \"start_time\": \"2023-02-17T21:33:10.582536\", \"status\": \"completed\" " +
"}, \"tags\": [] }, \"source\": [ \"![QuantConnect Logo](https://cdn.quantconnect.com/web/i/qc_notebook_logo_rev0.png)\", \"## Welcome to " +
"The QuantConnect Research Page\", \"#### Refer to this page for documentation https://www.quantconnect.com/docs/research/overview#\", \"#### Con" +
"tribute to this template file https://github.com/QuantConnect/Lean/blob/master/Research/BasicQuantBookTemplate.ipynb\" ] }, { \"cell_type\": \"m" +
"arkdown\", \"id\": \"e3e3d0c7\", \"metadata\": { \"papermill\": { \"duration\": 0.003965, \"end_time\": \"2023-02-17T21:33:10.593525\"," +
" \"exception\": false, \"start_time\": \"2023-02-17T21:33:10.589560\", \"status\": \"completed\" }, \"tags\": [] }, \"source\": " +
"[ \"## QuantBook Basics\", \"\", \"### Start QuantBook\", \"- Add the references and imports\", \"- Create a QuantBook instance\" ] " +
"}, { \"cell_type\": \"code\", \"execution_count\": 1, \"id\": \"d0e8fcfc\", \"metadata\": { \"execution\": { \"iopub.execute_input\": " +
"\"2023-02-17T21:33:10.602549Z\", \"iopub.status.busy\": \"2023-02-17T21:33:10.601530Z\", \"iopub.status.idle\": \"2023-02-17T21:33:10.616522Z\"" +
", \"shell.execute_reply\": \"2023-02-17T21:33:10.614535Z\" }, \"papermill\": { \"duration\": 0.021984, \"end_time\": \"2023-02-17T21" +
":33:10.618527\", \"exception\": false, \"start_time\": \"2023-02-17T21:33:10.596543\", \"status\": \"completed\" }, \"tags\": [] }" +
", \"outputs\": [], \"source\": [ \"import warnings\", \"warnings.filterwarnings(\\\"ignore\\\")\" ] }, { \"cell_type\": \"code\", \"" +
"execution_count\": 2, \"id\": \"a845a7ca\", \"metadata\": { \"execution\": { \"iopub.execute_input\": \"2023-02-17T21:33:10.627549Z\", " +
"\"iopub.status.busy\": \"2023-02-17T21:33:10.627549Z\", \"iopub.status.idle\": \"2023-02-17T21:33:15.142098Z\", \"shell.execute_reply\": \"202" +
"3-02-17T21:33:15.140599Z\" }, \"papermill\": { \"duration\": 4.526574, \"end_time\": \"2023-02-17T21:33:15.149104\", \"exception\": " +
"false, \"start_time\": \"2023-02-17T21:33:10.622530\", \"status\": \"completed\" }, \"tags\": [] }, \"outputs\": [], \"source\": [" +
" \"# Load in our startup script, required to set runtime for PythonNet\", \"%run ./start.py\" ] }, { \"cell_type\": \"code\", \"executio" +
"n_count\": 3, \"id\": \"9b0cd81c\", \"metadata\": { \"execution\": { \"iopub.execute_input\": \"2023-02-17T21:33:15.162102Z\", \"iopub." +
"status.busy\": \"2023-02-17T21:33:15.160599Z\", \"iopub.status.idle\": \"2023-02-17T21:33:15.266621Z\", \"shell.execute_reply\": \"2023-02-17T" +
"21:33:15.263102Z\" }, \"papermill\": { \"duration\": 0.117533, \"end_time\": \"2023-02-17T21:33:15.270138\", \"exception\": false, " +
" \"start_time\": \"2023-02-17T21:33:15.152605\", \"status\": \"completed\" }, \"tags\": [] }, \"outputs\": [], \"source\": [ \"cl" +
"ass CustomDataType(PythonData):\", \"\", \" def GetSource(self, config: SubscriptionDataConfig, date: datetime, isLive: bool) -> Subscription" +
"DataSource:\", \" source = \\\"https://www.dl.dropboxusercontent.com/s/d83xvd7mm9fzpk0/path_to_my_csv_data.csv?dl=0\\\"\", \" retu" +
"rn SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile)\", \"\", \" def Reader(self, config: SubscriptionDataConfig, line: " +
"str, date: datetime, isLive: bool) -> BaseData:\", \" if not (line.strip()):\", \" return None\", \"\", \" data =" +
" line.split(',')\", \" obj_data = CustomDataType()\", \" obj_data.Symbol = config.Symbol\", \"\", \" try:\", \" " +
" obj_data.Time = datetime.strptime(data[0], '%Y-%m-%d %H:%M:%S') + timedelta(hours=20)\", \" obj_data[\\\"open\\\"] = float(data" +
"[1])\", \" obj_data[\\\"high\\\"] = float(data[2])\", \" obj_data[\\\"low\\\"] = float(data[3])\", \" obj_da" +
"ta[\\\"close\\\"] = float(data[4])\", \" obj_data.Value = obj_data[\\\"close\\\"]\", \"\", \" # property for asserting " +
"the correct data is fetched\", \" obj_data[\\\"some_property\\\"] = \\\"some property value\\\"\", \" except ValueError:\", " +
" \" return None\", \"\", \" return obj_data\", \"\", \" def __str__ (self):\", \" return f\\\"Time: {self.T" +
"ime}, Value: {self.Value}, SomeProperty: {self['some_property']}, Open: {self['open']}, High: {self['high']}, Low: {self['low']}, Close: {self['close'" +
"]}\\\"\" ] }, { \"cell_type\": \"code\", \"execution_count\": 4, \"id\": \"e21f2b04\", \"metadata\": { \"execution\": { \"iopub.exe" +
"cute_input\": \"2023-02-17T21:33:15.282600Z\", \"iopub.status.busy\": \"2023-02-17T21:33:15.281102Z\", \"iopub.status.idle\": \"2023-02-17T21:" +
"33:17.198103Z\", \"shell.execute_reply\": \"2023-02-17T21:33:17.196601Z\" }, \"papermill\": { \"duration\": 1.926991, \"end_time\": " +
"\"2023-02-17T21:33:17.200100\", \"exception\": false, \"start_time\": \"2023-02-17T21:33:15.273109\", \"status\": \"completed\" }, \"" +
"tags\": [] }, \"outputs\": [], \"source\": [ \"# Create an instance\", \"qb = QuantBook()\", \"symbol = qb.AddData(CustomDataType, \\\"" +
"CustomDataType\\\", Resolution.Hour).Symbol\", \"\", \"startDate = datetime(2017, 8, 20)\", \"endDate = startDate + timedelta(hours=48)\", " +
" \"history = list(qb.History[CustomDataType](symbol, startDate, endDate, Resolution.Hour))\", \"\", \"if len(history) == 0:\", \" raise Ex" +
"ception(\\\"No history data returned\\\")\" ] } ], \"metadata\": { \"kernelspec\": { \"display_name\": \"Python 3 (ipykernel)\", \"language\":" +
" \"python\", \"name\": \"python3\" }, \"language_info\": { \"codemirror_mode\": { \"name\": \"ipython\", \"version\": 3 }, \"file_exte" +
"nsion\": \".py\", \"mimetype\": \"text/x-python\", \"name\": \"python\", \"nbconvert_exporter\": \"python\", \"pygments_lexer\": \"ipython3\"," +
" \"version\": \"3.8.10\" }, \"papermill\": { \"default_parameters\": {}, \"duration\": 13.405899, \"end_time\": \"2023-02-17T21:33:20.058754" +
"\", \"environment_variables\": {}, \"exception\": null, \"input_path\": \"C:\\\\Users\\\\jhona\\\\QuantConnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\\\\R" +
"esearch\\\\RegressionTemplates\\\\BasicTemplateCustomDataTypeHistoryResearchPython.ipynb\", \"output_path\": \"C:\\\\Users\\\\jhona\\\\QuantConnect\\\\L" +
"ean\\\\Tests\\\\bin\\\\Debug\\\\Research\\\\RegressionTemplates\\\\BasicTemplateCustomDataTypeHistoryResearchPython-output.ipynb\", \"parameters\": " +
"{}, \"start_time\": \"2023-02-17T21:33:06.652855\", \"version\": \"2.4.0\" }, \"vscode\": { \"interpreter\": { \"hash\": \"9650cb4e16cdd4a8" +
"e8e2d128bf38d875813998db22a3c986335f89e0cb4d7bb2\" } } }, \"nbformat\": 4, \"nbformat_minor\": 5}";
}
}
@@ -0,0 +1,128 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![QuantConnect Logo](https://cdn.quantconnect.com/web/i/qc_notebook_logo_rev0.png)\n",
"## Welcome to The QuantConnect Research Page\n",
"#### Refer to this page for documentation https://www.quantconnect.com/docs/research/overview#\n",
"#### Contribute to this template file https://github.com/QuantConnect/Lean/blob/master/Research/BasicQuantBookTemplate.ipynb"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## QuantBook Basics\n",
"\n",
"### Start QuantBook\n",
"- Add the references and imports\n",
"- Create a QuantBook instance"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import warnings\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Load in our startup script, required to set runtime for PythonNet\n",
"%run ./start.py"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class CustomDataType(PythonData):\n",
"\n",
" def GetSource(self, config: SubscriptionDataConfig, date: datetime, isLive: bool) -> SubscriptionDataSource:\n",
" source = \"https://www.dl.dropboxusercontent.com/s/d83xvd7mm9fzpk0/path_to_my_csv_data.csv?dl=0\"\n",
" return SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile)\n",
"\n",
" def Reader(self, config: SubscriptionDataConfig, line: str, date: datetime, isLive: bool) -> BaseData:\n",
" if not (line.strip()):\n",
" return None\n",
"\n",
" data = line.split(',')\n",
" obj_data = CustomDataType()\n",
" obj_data.Symbol = config.Symbol\n",
"\n",
" try:\n",
" obj_data.Time = datetime.strptime(data[0], '%Y-%m-%d %H:%M:%S') + timedelta(hours=20)\n",
" obj_data[\"open\"] = float(data[1])\n",
" obj_data[\"high\"] = float(data[2])\n",
" obj_data[\"low\"] = float(data[3])\n",
" obj_data[\"close\"] = float(data[4])\n",
" obj_data.Value = obj_data[\"close\"]\n",
"\n",
" # property for asserting the correct data is fetched\n",
" obj_data[\"some_property\"] = \"some property value\"\n",
" except ValueError:\n",
" return None\n",
"\n",
" return obj_data\n",
"\n",
" def __str__ (self):\n",
" return f\"Time: {self.Time}, Value: {self.Value}, SomeProperty: {self['some_property']}, Open: {self['open']}, High: {self['high']}, Low: {self['low']}, Close: {self['close']}\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create an instance\n",
"qb = QuantBook()\n",
"symbol = qb.AddData(CustomDataType, \"CustomDataType\", Resolution.Hour).Symbol\n",
"\n",
"startDate = datetime(2017, 8, 20)\n",
"endDate = startDate + timedelta(hours=48)\n",
"history = list(qb.History[CustomDataType](symbol, startDate, endDate, Resolution.Hour))\n",
"\n",
"if len(history) == 0:\n",
" raise Exception(\"No history data returned\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.10"
},
"vscode": {
"interpreter": {
"hash": "9650cb4e16cdd4a8e8e2d128bf38d875813998db22a3c986335f89e0cb4d7bb2"
}
}
},
"nbformat": 4,
"nbformat_minor": 4
}
@@ -0,0 +1,143 @@
/*
* 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 QuantConnect.Interfaces;
namespace QuantConnect.Tests.Research.RegressionTemplates
{
/// <summary>
/// Basic template framework for regression testing of research notebooks
/// </summary>
public class BasicTemplateResearchCSharp : IRegressionResearchDefinition
{
/// <summary>
/// Expected output from the reading the raw notebook file
/// </summary>
/// <remarks>Requires to be implemented last in the file <see cref="ResearchRegressionTests.UpdateResearchRegressionOutputInSourceFile"/>
/// get should start from next line</remarks>
public string ExpectedOutput =>
"{ \"cells\": [ { \"cell_type\": \"markdown\", \"id\": \"a4652bd4\", \"metadata\": { \"papermill\": { \"duration\": 0.005036, \"end_t" +
"ime\": \"2022-03-02T20:44:55.508287\", \"exception\": false, \"start_time\": \"2022-03-02T20:44:55.503251\", \"status\": \"completed\" " +
"}, \"tags\": [] }, \"source\": [ \"![QuantConnect Logo](https://cdn.quantconnect.com/web/i/qc_notebook_logo_rev0.png)\", \"## Welcome to " +
"The QuantConnect Research Page\", \"#### Refer to this page for documentation https://www.quantconnect.com/docs/research/overview#\", \"#### Con" +
"tribute to this template file https://github.com/QuantConnect/Lean/blob/master/Research/BasicQuantBookTemplate.ipynb\" ] }, { \"cell_type\": \"m" +
"arkdown\", \"id\": \"acb5aec0\", \"metadata\": { \"papermill\": { \"duration\": 0.002024, \"end_time\": \"2022-03-02T20:44:55.513310\"," +
" \"exception\": false, \"start_time\": \"2022-03-02T20:44:55.511286\", \"status\": \"completed\" }, \"tags\": [] }, \"source\": " +
"[ \"## QuantBook Basics\", \"\", \"### Start QuantBook\", \"- Add the references and imports\", \"- Create a QuantBook instance\" ] " +
"}, { \"cell_type\": \"code\", \"execution_count\": 1, \"id\": \"39de525f\", \"metadata\": { \"execution\": { \"iopub.execute_input\": " +
"\"2022-03-02T20:44:55.529252Z\", \"iopub.status.busy\": \"2022-03-02T20:44:55.522252Z\", \"iopub.status.idle\": \"2022-03-02T20:44:57.058250Z\"" +
", \"shell.execute_reply\": \"2022-03-02T20:44:57.052865Z\" }, \"papermill\": { \"duration\": 1.542006, \"end_time\": \"2022-03-02T20" +
":44:57.058250\", \"exception\": false, \"start_time\": \"2022-03-02T20:44:55.516244\", \"status\": \"completed\" }, \"tags\": [] }" +
", \"outputs\": [ { \"data\": { \"text/html\": [ \"\", \"<div>\", \" <div id='dotnet-interactive-this-cell-4972.Micr" +
"osoft.DotNet.Interactive.Http.HttpPort' style='display: none'>\", \" The below script needs to be able to find the current output cell; t" +
"his is an easy method to get it.\", \" </div>\", \" <script type='text/javascript'>\", \"async function probeAddresses(probing" +
"Addresses) {\", \" function timeout(ms, promise) {\", \" return new Promise(function (resolve, reject) {\", \" " +
"setTimeout(function () {\", \" reject(new Error('timeout'))\", \" }, ms)\", \" promise.then(res" +
"olve, reject)\", \" })\", \" }\", \"\", \" if (Array.isArray(probingAddresses)) {\", \" for (let i =" +
" 0; i < probingAddresses.length; i++) {\", \"\", \" let rootUrl = probingAddresses[i];\", \"\", \" if (!" +
"rootUrl.endsWith('/')) {\", \" rootUrl = `${rootUrl}/`;\", \" }\", \"\", \" try {\", " +
" \" let response = await timeout(1000, fetch(`${rootUrl}discovery`, {\", \" method: 'POST',\", \" " +
" cache: 'no-cache',\", \" mode: 'cors',\", \" timeout: 1000,\", \" " +
" headers: {\", \" 'Content-Type': 'text/plain'\", \" },\", \" body:" +
" probingAddresses[i]\", \" }));\", \"\", \" if (response.status == 200) {\", \" " +
" return rootUrl;\", \" }\", \" }\", \" catch (e) { }\", \" }\", \" }\"," +
" \"}\", \"\", \"function loadDotnetInteractiveApi() {\", \" probeAddresses([\\\"http://192.168.29.151:1000/\\\", \\\"http:/" +
"/127.0.0.1:1000/\\\"])\", \" .then((root) => {\", \" // use probing to find host url and api resources\", \" //" +
" load interactive helpers and language services\", \" let dotnetInteractiveRequire = require.config({\", \" context: '4972.M" +
"icrosoft.DotNet.Interactive.Http.HttpPort',\", \" paths:\", \" {\", \" 'dotnet-interactive'" +
": `${root}resources`\", \" }\", \" }) || require;\", \"\", \" window.dotnetInteractiveRequire" +
" = dotnetInteractiveRequire;\", \"\", \" window.configureRequireFromExtension = function(extensionName, extensionCacheBuster) {" +
"\", \" let paths = {};\", \" paths[extensionName] = `${root}extensions/${extensionName}/resources/`;\", " +
" \" \", \" let internalRequire = require.config({\", \" context: extensionCacheBuster,\"" +
", \" paths: paths,\", \" urlArgs: `cacheBuster=${extensionCacheBuster}`\", \" " +
" }) || require;\", \"\", \" return internalRequire\", \" };\", \" \", \" d" +
"otnetInteractiveRequire([\", \" 'dotnet-interactive/dotnet-interactive'\", \" ],\", \" " +
" function (dotnet) {\", \" dotnet.init(window);\", \" },\", \" function (error) " +
"{\", \" console.log(error);\", \" }\", \" );\", \" })\", \" ." +
"catch(error => {console.log(error);});\", \" }\", \"\", \"// ensure `require` is available globally\", \"if ((typeof(requir" +
"e) !== typeof(Function)) || (typeof(require.config) !== typeof(Function))) {\", \" let require_script = document.createElement('script');\"," +
" \" require_script.setAttribute('src', 'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js');\", \" require_scri" +
"pt.setAttribute('type', 'text/javascript');\", \" \", \" \", \" require_script.onload = function() {\", \" loa" +
"dDotnetInteractiveApi();\", \" };\", \"\", \" document.getElementsByTagName('head')[0].appendChild(require_script);\", \"" +
"}\", \"else {\", \" loadDotnetInteractiveApi();\", \"}\", \"\", \" </script>\", \"</div>\" ] }, " +
" \"metadata\": {}, \"output_type\": \"display_data\" }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"" +
"Initialize.csx: Loading assemblies from D:\\\\quantconnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\" ] } ], \"source\": [ \"// QuantBook C# Res" +
"earch Environment\", \"// For more information see https://www.quantconnect.com/docs/research/overview\", \"#load \\\"./Initialize.csx\\\"\" ]" +
" }, { \"cell_type\": \"code\", \"execution_count\": 2, \"id\": \"ceb0015a\", \"metadata\": { \"execution\": { \"iopub.execute_input\"" +
": \"2022-03-02T20:44:57.066259Z\", \"iopub.status.busy\": \"2022-03-02T20:44:57.066259Z\", \"iopub.status.idle\": \"2022-03-02T20:44:57.188021" +
"Z\", \"shell.execute_reply\": \"2022-03-02T20:44:57.187022Z\" }, \"papermill\": { \"duration\": 0.126763, \"end_time\": \"2022-03-02" +
"T20:44:57.188021\", \"exception\": false, \"start_time\": \"2022-03-02T20:44:57.061258\", \"status\": \"completed\" }, \"tags\": [] " +
" }, \"outputs\": [ { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20220302 20:44:57.140 TRACE:: Config.GetV" +
"alue(): debug-mode - Using default value: False\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"2" +
"0220302 20:44:57.141 TRACE:: Config.Get(): Configuration key not found. Key: results-destination-folder - Using default value: \" ] }, { " +
" \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20220302 20:44:57.141 TRACE:: Config.Get(): Configuration key not found" +
". Key: plugin-directory - Using default value: \" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"2" +
"0220302 20:44:57.142 TRACE:: Config.Get(): Configuration key not found. Key: composer-dll-directory - Using default value: \" ] }, { \"n" +
"ame\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20220302 20:44:57.142 TRACE:: Composer(): Loading Assemblies from D:\\\\qua" +
"ntconnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\" ] }, { \"" +
"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20220302 20:44:57.168 TRACE:: Config.Get(): Configuration key not found. K" +
"ey: version-id - Using default value: \" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20220302 2" +
"0:44:57.169 TRACE:: Config.Get(): Configuration key not found. Key: cache-location - Using default value: ../../../Data/\" ] }, { \"name" +
"\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20220302 20:44:57.169 TRACE:: Engine.Main(): LEAN ALGORITHMIC TRADING ENGINE v" +
"2.5.0.0 Mode: DEBUG (64bit) Host: P3561-70DPBL3\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"2" +
"0220302 20:44:57.171 TRACE:: Engine.Main(): Started 2:14 AM\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\"" +
": [ \"20220302 20:44:57.173 TRACE:: Config.Get(): Configuration key not found. Key: lean-manager-type - Using default value: LocalLeanManager\" " +
" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"20220302 20:44:57.179 TRACE:: Config.Get(): Configur" +
"ation key not found. Key: data-permission-manager - Using default value: DataPermissionManager\" ] }, { \"name\": \"stdout\", \"outp" +
"ut_type\": \"stream\", \"text\": [ \"20220302 20:44:57.180 TRACE:: Config.Get(): Configuration key not found. Key: results-destination-folder" +
" - Using default value: D:\\\\quantconnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream" +
"\", \"text\": [ \"20220302 20:44:57.185 TRACE:: Config.Get(): Configuration key not found. Key: object-store-root - Using default value: ./st" +
"orage\" ] } ], \"source\": [ \"#load \\\"./QuantConnect.csx\\\"\", \"\", \"using QuantConnect;\", \"using QuantConnect.Data;\"," +
" \"using QuantConnect.Algorithm;\", \"using QuantConnect.Research;\" ] }, { \"cell_type\": \"code\", \"execution_count\": 3, \"id\": \"" +
"e81ef855\", \"metadata\": { \"execution\": { \"iopub.execute_input\": \"2022-03-02T20:44:57.200095Z\", \"iopub.status.busy\": \"2022-03-0" +
"2T20:44:57.199026Z\", \"iopub.status.idle\": \"2022-03-02T20:44:58.393456Z\", \"shell.execute_reply\": \"2022-03-02T20:44:58.392457Z\" }, " +
" \"papermill\": { \"duration\": 1.200436, \"end_time\": \"2022-03-02T20:44:58.393456\", \"exception\": false, \"start_time\": \"2022-" +
"03-02T20:44:57.193020\", \"status\": \"completed\" }, \"tags\": [] }, \"outputs\": [ { \"name\": \"stdout\", \"output_type\":" +
" \"stream\", \"text\": [ \"PythonEngine.Initialize(): Runtime.Initialize()...\" ] }, { \"name\": \"stdout\", \"output_type\"" +
": \"stream\", \"text\": [ \"Runtime.Initialize(): Py_Initialize...\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream" +
"\", \"text\": [ \"Runtime.Initialize(): PyEval_InitThreads...\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", " +
" \"text\": [ \"Runtime.Initialize(): Initialize types...\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"te" +
"xt\": [ \"Runtime.Initialize(): Initialize types end.\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\":" +
" [ \"Runtime.Initialize(): AssemblyManager.Initialize()...\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"te" +
"xt\": [ \"Runtime.Initialize(): AssemblyManager.UpdatePath()...\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", " +
" \"text\": [ \"PythonEngine.Initialize(): GetCLRModule()...\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"t" +
"ext\": [ \"PythonEngine.Initialize(): clr GetManifestResourceStream...\" ] } ], \"source\": [ \"var qb = new QuantBook();\", \"v" +
"ar spy = qb.AddEquity(\\\"SPY\\\");\" ] }, { \"cell_type\": \"code\", \"execution_count\": 4, \"id\": \"06cd5f47\", \"metadata\": { \"e" +
"xecution\": { \"iopub.execute_input\": \"2022-03-02T20:44:58.408454Z\", \"iopub.status.busy\": \"2022-03-02T20:44:58.408454Z\", \"iopub.st" +
"atus.idle\": \"2022-03-02T20:44:58.449457Z\", \"shell.execute_reply\": \"2022-03-02T20:44:58.449457Z\" }, \"papermill\": { \"duration\":" +
" 0.048992, \"end_time\": \"2022-03-02T20:44:58.449457\", \"exception\": false, \"start_time\": \"2022-03-02T20:44:58.400465\", \"statu" +
"s\": \"completed\" }, \"tags\": [] }, \"outputs\": [], \"source\": [ \"var startDate = new DateTime(2021,1,1);\", \"var endDate = ne" +
"w DateTime(2021,12,31);\", \"var history = qb.History(qb.Securities.Keys, startDate, endDate, Resolution.Daily);\" ] }, { \"cell_type\": \"co" +
"de\", \"execution_count\": 5, \"id\": \"37b045b2\", \"metadata\": { \"execution\": { \"iopub.execute_input\": \"2022-03-02T20:44:58.46348" +
"5Z\", \"iopub.status.busy\": \"2022-03-02T20:44:58.463485Z\", \"iopub.status.idle\": \"2022-03-02T20:44:58.611572Z\", \"shell.execute_repl" +
"y\": \"2022-03-02T20:44:58.611572Z\" }, \"papermill\": { \"duration\": 0.156117, \"end_time\": \"2022-03-02T20:44:58.611572\", \"exc" +
"eption\": false, \"start_time\": \"2022-03-02T20:44:58.455455\", \"status\": \"completed\" }, \"tags\": [] }, \"outputs\": [ { " +
" \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"SPY: O: 374.075 H: 374.2245 L: 363.6392 C: 367.5863 V: 94685703\" " +
"] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"SPY: O: 366.8487 H: 371.2642 L: 366.8487 C: 370.118 V: " +
"54790079\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"SPY: O: 368.4634 H: 375.7495 L: 367.9152" +
" C: 372.3307 V: 93254510\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"SPY: O: 374.9521 H: 378." +
"65 L: 374.693 C: 377.8626 V: 62474792\" ] }, { \"name\": \"stdout\", \"output_type\": \"stream\", \"text\": [ \"SPY: O: 379" +
".3876 H: 380.2448 L: 375.8791 C: 380.0156 V: 63370827\" ] } ], \"source\": [ \"foreach(var slice in history.Take(5)) {\", \" Conso" +
"le.WriteLine(slice.Bars[spy.Symbol].ToString());\", \"}\" ] }, { \"cell_type\": \"code\", \"execution_count\": null, \"id\": \"bd2ab8d7\"" +
", \"metadata\": { \"papermill\": { \"duration\": 0.006983, \"end_time\": \"2022-03-02T20:44:58.626572\", \"exception\": false, \"" +
"start_time\": \"2022-03-02T20:44:58.619589\", \"status\": \"completed\" }, \"tags\": [] }, \"outputs\": [], \"source\": [] } ], \"met" +
"adata\": { \"kernelspec\": { \"display_name\": \".NET (C#)\", \"language\": \"C#\", \"name\": \".net-csharp\" }, \"language_info\": { \"fil" +
"e_extension\": \".cs\", \"mimetype\": \"text/x-csharp\", \"name\": \"C#\", \"pygments_lexer\": \"csharp\", \"version\": \"9.0\" }, \"papermi" +
"ll\": { \"default_parameters\": {}, \"duration\": 7.291395, \"end_time\": \"2022-03-02T20:45:01.252292\", \"environment_variables\": {}, \"e" +
"xception\": null, \"input_path\": \"D:\\\\quantconnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\\\\BasicTemplateResearchCSharp.ipynb\", \"output_path\": \"" +
"D:\\\\quantconnect\\\\Lean\\\\Tests\\\\bin\\\\Debug\\\\BasicTemplateResearchCSharp-output.ipynb\", \"parameters\": {}, \"start_time\": \"2022-03-0" +
"2T20:44:53.960897\", \"version\": \"2.3.4\" } }, \"nbformat\": 4, \"nbformat_minor\": 5}";
}
}
@@ -0,0 +1,105 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![QuantConnect Logo](https://cdn.quantconnect.com/web/i/qc_notebook_logo_rev0.png)\n",
"## Welcome to The QuantConnect Research Page\n",
"#### Refer to this page for documentation https://www.quantconnect.com/docs/research/overview#\n",
"#### Contribute to this template file https://github.com/QuantConnect/Lean/blob/master/Research/BasicQuantBookTemplate.ipynb"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## QuantBook Basics\n",
"\n",
"### Start QuantBook\n",
"- Add the references and imports\n",
"- Create a QuantBook instance"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"// QuantBook C# Research Environment\n",
"// For more information see https://www.quantconnect.com/docs/research/overview\n",
"#load \"./Initialize.csx\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#load \"./QuantConnect.csx\"\n",
"\n",
"using QuantConnect;\n",
"using QuantConnect.Data;\n",
"using QuantConnect.Algorithm;\n",
"using QuantConnect.Research;"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"var qb = new QuantBook();\n",
"var spy = qb.AddEquity(\"SPY\");"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"var startDate = new DateTime(2021,1,1);\n",
"var endDate = new DateTime(2021,12,31);\n",
"var history = qb.History(qb.Securities.Keys, startDate, endDate, Resolution.Daily);"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"foreach(var slice in history.Take(5)) {\n",
" Console.WriteLine(slice.Bars[spy.Symbol].ToString());\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".NET (C#)",
"language": "C#",
"name": ".net-csharp"
},
"language_info": {
"file_extension": ".cs",
"mimetype": "text/x-csharp",
"name": "C#",
"pygments_lexer": "csharp",
"version": "9.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
@@ -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 QuantConnect.Interfaces;
namespace QuantConnect.Tests.Research.RegressionTemplates
{
/// <summary>
/// Basic template framework for regression testing of research notebooks
/// </summary>
public class BasicTemplateResearchPython : IRegressionResearchDefinition
{
/// <summary>
/// Expected output from the reading the raw notebook file
/// </summary>
/// <remarks>Requires to be implemented last in the file <see cref="ResearchRegressionTests.UpdateResearchRegressionOutputInSourceFile"/>
/// get should start from next line</remarks>
public string ExpectedOutput =>
"{ \"cells\": [ { \"cell_type\": \"markdown\", \"id\": \"f5416762\", \"metadata\": { \"papermill\": { \"duration\": 0.001898, \"end_t" +
"ime\": \"2024-06-06T22:15:49.572477\", \"exception\": false, \"start_time\": \"2024-06-06T22:15:49.570579\", \"status\": \"completed\" " +
"}, \"tags\": [] }, \"source\": [ \"![QuantConnect Logo](https://cdn.quantconnect.com/web/i/qc_notebook_logo_rev0.png)\", \"## Welcome to " +
"The QuantConnect Research Page\", \"#### Refer to this page for documentation https://www.quantconnect.com/docs/research/overview#\", \"#### Con" +
"tribute to this template file https://github.com/QuantConnect/Lean/blob/master/Research/BasicQuantBookTemplate.ipynb\" ] }, { \"cell_type\": \"m" +
"arkdown\", \"id\": \"44ed65de\", \"metadata\": { \"papermill\": { \"duration\": 0.001, \"end_time\": \"2024-06-06T22:15:49.574475\", " +
" \"exception\": false, \"start_time\": \"2024-06-06T22:15:49.573475\", \"status\": \"completed\" }, \"tags\": [] }, \"source\": [ " +
" \"## QuantBook Basics\", \"\", \"### Start QuantBook\", \"- Add the references and imports\", \"- Create a QuantBook instance\" ] }, " +
" { \"cell_type\": \"code\", \"execution_count\": 1, \"id\": \"677a4f25\", \"metadata\": { \"execution\": { \"iopub.execute_input\": \"2" +
"024-06-06T22:15:49.577476Z\", \"iopub.status.busy\": \"2024-06-06T22:15:49.577476Z\", \"iopub.status.idle\": \"2024-06-06T22:15:49.581464Z\", " +
" \"shell.execute_reply\": \"2024-06-06T22:15:49.581464Z\" }, \"papermill\": { \"duration\": 0.006902, \"end_time\": \"2024-06-06T22:1" +
"5:49.582478\", \"exception\": false, \"start_time\": \"2024-06-06T22:15:49.575576\", \"status\": \"completed\" }, \"tags\": [] }, " +
" \"outputs\": [], \"source\": [ \"import warnings\", \"warnings.filterwarnings(\\\"ignore\\\")\" ] }, { \"cell_type\": \"code\", \"ex" +
"ecution_count\": 2, \"id\": \"e752d505\", \"metadata\": { \"execution\": { \"iopub.execute_input\": \"2024-06-06T22:15:49.584480Z\", \"" +
"iopub.status.busy\": \"2024-06-06T22:15:49.584480Z\", \"iopub.status.idle\": \"2024-06-06T22:15:51.028418Z\", \"shell.execute_reply\": \"2024-" +
"06-06T22:15:51.028418Z\" }, \"papermill\": { \"duration\": 1.445955, \"end_time\": \"2024-06-06T22:15:51.029433\", \"exception\": fa" +
"lse, \"start_time\": \"2024-06-06T22:15:49.583478\", \"status\": \"completed\" }, \"tags\": [] }, \"outputs\": [], \"source\": [ " +
" \"# Load in our startup script, required to set runtime for PythonNet\", \"%run ./start.py\" ] }, { \"cell_type\": \"code\", \"execution_" +
"count\": 3, \"id\": \"08d48a2d\", \"metadata\": { \"execution\": { \"iopub.execute_input\": \"2024-06-06T22:15:51.032433Z\", \"iopub.st" +
"atus.busy\": \"2024-06-06T22:15:51.032433Z\", \"iopub.status.idle\": \"2024-06-06T22:15:51.344980Z\", \"shell.execute_reply\": \"2024-06-06T22" +
":15:51.344980Z\" }, \"papermill\": { \"duration\": 0.315568, \"end_time\": \"2024-06-06T22:15:51.345999\", \"exception\": false, " +
" \"start_time\": \"2024-06-06T22:15:51.030431\", \"status\": \"completed\" }, \"tags\": [] }, \"outputs\": [], \"source\": [ \"# Cr" +
"eate an instance\", \"qb = QuantBook()\", \"\", \"# Select asset data\", \"spy = qb.AddEquity(\\\"SPY\\\")\" ] }, { \"cell_type\": \"" +
"markdown\", \"id\": \"07f707ad\", \"metadata\": { \"papermill\": { \"duration\": 0.000998, \"end_time\": \"2024-06-06T22:15:51.348997\"" +
", \"exception\": false, \"start_time\": \"2024-06-06T22:15:51.347999\", \"status\": \"completed\" }, \"tags\": [] }, \"source\":" +
" [ \"### Historical Data Requests\", \"\", \"We can use the QuantConnect API to make Historical Data Requests. The data will be presented as " +
"multi-index pandas.DataFrame where the first index is the Symbol.\", \"\", \"For more information, please follow the [link](https://www.quantcon" +
"nect.com/docs#Historical-Data-Historical-Data-Requests).\" ] }, { \"cell_type\": \"code\", \"execution_count\": 4, \"id\": \"ef440f9e\", \"" +
"metadata\": { \"execution\": { \"iopub.execute_input\": \"2024-06-06T22:15:51.352999Z\", \"iopub.status.busy\": \"2024-06-06T22:15:51.35299" +
"9Z\", \"iopub.status.idle\": \"2024-06-06T22:15:51.356860Z\", \"shell.execute_reply\": \"2024-06-06T22:15:51.356860Z\" }, \"papermill\":" +
" { \"duration\": 0.00689, \"end_time\": \"2024-06-06T22:15:51.357885\", \"exception\": false, \"start_time\": \"2024-06-06T22:15:51.35" +
"0995\", \"status\": \"completed\" }, \"tags\": [] }, \"outputs\": [], \"source\": [ \"startDate = DateTime(2021,1,1)\", \"endDat" +
"e = DateTime(2021,12,31)\" ] }, { \"cell_type\": \"code\", \"execution_count\": 5, \"id\": \"33a5fd5d\", \"metadata\": { \"execution\":" +
" { \"iopub.execute_input\": \"2024-06-06T22:15:51.361875Z\", \"iopub.status.busy\": \"2024-06-06T22:15:51.360883Z\", \"iopub.status.idle\"" +
": \"2024-06-06T22:15:51.453871Z\", \"shell.execute_reply\": \"2024-06-06T22:15:51.453871Z\" }, \"papermill\": { \"duration\": 0.095009, " +
" \"end_time\": \"2024-06-06T22:15:51.454884\", \"exception\": false, \"start_time\": \"2024-06-06T22:15:51.359875\", \"status\": \"comp" +
"leted\" }, \"scrolled\": true, \"tags\": [] }, \"outputs\": [], \"source\": [ \"# Gets historical data from the subscribed assets, t" +
"he last 360 datapoints with daily resolution\", \"h1 = qb.History(qb.Securities.Keys, startDate, endDate, Resolution.Daily)\", \"\", \"if h1." +
"shape[0] < 1:\", \" raise Exception(\\\"History request resulted in no data\\\")\" ] }, { \"cell_type\": \"markdown\", \"id\": \"e8f9c90" +
"d\", \"metadata\": { \"papermill\": { \"duration\": 0.000996, \"end_time\": \"2024-06-06T22:15:51.457883\", \"exception\": false, " +
" \"start_time\": \"2024-06-06T22:15:51.456887\", \"status\": \"completed\" }, \"tags\": [] }, \"source\": [ \"### Indicators\", \"" +
"\", \"We can easily get the indicator of a given symbol with QuantBook. \", \"\", \"For all indicators, please checkout QuantConnect Indicato" +
"rs [Reference Table](https://www.quantconnect.com/docs#Indicators-Reference-Table)\" ] }, { \"cell_type\": \"code\", \"execution_count\": 6, " +
" \"id\": \"dcc7e1f0\", \"metadata\": { \"execution\": { \"iopub.execute_input\": \"2024-06-06T22:15:51.461887Z\", \"iopub.status.busy\": " +
"\"2024-06-06T22:15:51.461887Z\", \"iopub.status.idle\": \"2024-06-06T22:15:51.502741Z\", \"shell.execute_reply\": \"2024-06-06T22:15:51.502741" +
"Z\" }, \"papermill\": { \"duration\": 0.044872, \"end_time\": \"2024-06-06T22:15:51.503757\", \"exception\": false, \"start_time" +
"\": \"2024-06-06T22:15:51.458885\", \"status\": \"completed\" }, \"tags\": [] }, \"outputs\": [], \"source\": [ \"# Example with BB" +
", it is a datapoint indicator\", \"# Define the indicator\", \"bb = BollingerBands(30, 2)\", \"\", \"# Gets historical data of indicator\"" +
", \"bbdf = qb.IndicatorHistory(bb, \\\"SPY\\\", startDate, endDate, Resolution.Daily).data_frame\", \"\", \"# drop undesired fields\", \"bbdf = b" +
"bdf.drop('standarddeviation', axis=1)\", \"\", \"if bbdf.shape[0] < 1:\", \" raise Exception(\\\"Bollinger Bands resulted in no data\\\")\"" +
" ] }, { \"cell_type\": \"code\", \"execution_count\": null, \"id\": \"3095c061\", \"metadata\": { \"papermill\": { \"duration\": 0." +
"001003, \"end_time\": \"2024-06-06T22:15:51.506759\", \"exception\": false, \"start_time\": \"2024-06-06T22:15:51.505756\", \"status\"" +
": \"completed\" }, \"tags\": [] }, \"outputs\": [], \"source\": [] } ], \"metadata\": { \"kernelspec\": { \"display_name\": \"Python 3" +
" (ipykernel)\", \"language\": \"python\", \"name\": \"python3\" }, \"language_info\": { \"codemirror_mode\": { \"name\": \"ipython\", \"" +
"version\": 3 }, \"file_extension\": \".py\", \"mimetype\": \"text/x-python\", \"name\": \"python\", \"nbconvert_exporter\": \"python\", \"" +
"pygments_lexer\": \"ipython3\", \"version\": \"3.11.7\" }, \"papermill\": { \"default_parameters\": {}, \"duration\": 6.254067, \"end_time\"" +
": \"2024-06-06T22:15:54.143637\", \"environment_variables\": {}, \"exception\": null, \"input_path\": \"D:\\\\QuantConnect\\\\MyLean\\\\Lean\\\\T" +
"ests\\\\bin\\\\Debug\\\\Research\\\\RegressionTemplates\\\\BasicTemplateResearchPython.ipynb\", \"output_path\": \"D:\\\\QuantConnect\\\\MyLean\\\\L" +
"ean\\\\Tests\\\\bin\\\\Debug\\\\Research\\\\RegressionTemplates\\\\BasicTemplateResearchPython-output.ipynb\", \"parameters\": {}, \"start_time\":" +
" \"2024-06-06T22:15:47.889570\", \"version\": \"2.4.0\" } }, \"nbformat\": 4, \"nbformat_minor\": 5}";
}
}
@@ -0,0 +1,153 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![QuantConnect Logo](https://cdn.quantconnect.com/web/i/qc_notebook_logo_rev0.png)\n",
"## Welcome to The QuantConnect Research Page\n",
"#### Refer to this page for documentation https://www.quantconnect.com/docs/research/overview#\n",
"#### Contribute to this template file https://github.com/QuantConnect/Lean/blob/master/Research/BasicQuantBookTemplate.ipynb"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## QuantBook Basics\n",
"\n",
"### Start QuantBook\n",
"- Add the references and imports\n",
"- Create a QuantBook instance"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import warnings\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Load in our startup script, required to set runtime for PythonNet\n",
"%run ./start.py"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create an instance\n",
"qb = QuantBook()\n",
"\n",
"# Select asset data\n",
"spy = qb.AddEquity(\"SPY\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Historical Data Requests\n",
"\n",
"We can use the QuantConnect API to make Historical Data Requests. The data will be presented as multi-index pandas.DataFrame where the first index is the Symbol.\n",
"\n",
"For more information, please follow the [link](https://www.quantconnect.com/docs#Historical-Data-Historical-Data-Requests)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"startDate = DateTime(2021,1,1)\n",
"endDate = DateTime(2021,12,31)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"# Gets historical data from the subscribed assets, the last 360 datapoints with daily resolution\n",
"h1 = qb.History(qb.Securities.Keys, startDate, endDate, Resolution.Daily)\n",
"\n",
"if h1.shape[0] < 1:\n",
" raise Exception(\"History request resulted in no data\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Indicators\n",
"\n",
"We can easily get the indicator of a given symbol with QuantBook. \n",
"\n",
"For all indicators, please checkout QuantConnect Indicators [Reference Table](https://www.quantconnect.com/docs#Indicators-Reference-Table)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Example with BB, it is a datapoint indicator\n",
"# Define the indicator\n",
"bb = BollingerBands(30, 2)\n",
"\n",
"# Gets historical data of indicator\n",
"bbdf = qb.IndicatorHistory(bb, \"SPY\", startDate, endDate, Resolution.Daily).data_frame\n",
"\n",
"# drop undesired fields\n",
"bbdf = bbdf.drop('standarddeviation', axis=1)\n",
"\n",
"if bbdf.shape[0] < 1:\n",
" raise Exception(\"Bollinger Bands resulted in no data\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+33
View File
@@ -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 NUnit.Framework;
namespace QuantConnect.Tests.Research
{
[TestFixture, Category("ResearchRegressionTests")]
public class StartTests
{
[Test]
public void RunStartFromPython()
{
TestProcess.RunPythonProcess("start.py", out var process);
Assert.AreEqual(0, process.ExitCode);
process.Dispose();
}
}
}