diff --git a/Algorithm.Framework/Selection/FutureUniverseSelectionModel.cs b/Algorithm.Framework/Selection/FutureUniverseSelectionModel.cs index aa8ba1fc2dcd..144039370c81 100644 --- a/Algorithm.Framework/Selection/FutureUniverseSelectionModel.cs +++ b/Algorithm.Framework/Selection/FutureUniverseSelectionModel.cs @@ -103,4 +103,32 @@ protected virtual FutureFilterUniverse Filter(FutureFilterUniverse filter) return filter; } } + + /// + /// Provides an implementation of that subscribes to future chains + /// + public class FuturesUniverseSelectionModel : FutureUniverseSelectionModel + { + /// + /// Creates a new instance of + /// + /// Time interval between universe refreshes + /// Selects symbols from the provided future chain + public FuturesUniverseSelectionModel(TimeSpan refreshInterval, Func> futureChainSymbolSelector) + : base(refreshInterval, futureChainSymbolSelector) + { + } + /// + /// Creates a new instance of + /// + /// Time interval between universe refreshes + /// Selects symbols from the provided future chain + /// Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed + public FuturesUniverseSelectionModel(TimeSpan refreshInterval, + Func> futureChainSymbolSelector, + UniverseSettings universeSettings) + : base(refreshInterval, futureChainSymbolSelector, universeSettings) + { + } + } } diff --git a/Algorithm/QCAlgorithm.cs b/Algorithm/QCAlgorithm.cs index 8e216e9dab5f..a7079e1bee59 100644 --- a/Algorithm/QCAlgorithm.cs +++ b/Algorithm/QCAlgorithm.cs @@ -3362,6 +3362,24 @@ public OptionChains OptionChains(IEnumerable symbols, bool flatten = fal return chains; } + /// + /// Get the futures chain for the specified symbol at the current time () + /// + /// + /// The symbol for which the futures chain is asked for. + /// It can be either the canonical future, a contract or an option symbol. + /// + /// + /// Whether to flatten the resulting data frame. Used from Python when accessing . + /// See + /// + /// The futures chain + [DocumentationAttribute(AddingData)] + public FuturesChain FutureChain(Symbol symbol, bool flatten = false) + { + return FuturesChain(symbol, flatten); + } + /// /// Get the futures chain for the specified symbol at the current time () /// @@ -3381,6 +3399,24 @@ public FuturesChain FuturesChain(Symbol symbol, bool flatten = false) new FuturesChain(GetCanonicalFutureSymbol(symbol), Time.Date); } + /// + /// Get the futures chains for the specified symbols at the current time () + /// + /// + /// The symbols for which the futures chains are asked for. + /// It can be either the canonical future, a contract or an option symbol. + /// + /// + /// Whether to flatten the resulting data frame. Used from Python when accessing . + /// See + /// + /// The futures chains + [DocumentationAttribute(AddingData)] + public FuturesChains FutureChains(IEnumerable symbols, bool flatten = false) + { + return FuturesChains(symbols, flatten); + } + /// /// Get the futures chains for the specified symbols at the current time () /// diff --git a/Common/Exceptions/StackExceptionInterpreter.cs b/Common/Exceptions/StackExceptionInterpreter.cs index 30f5fd04c9b9..04892d06f365 100644 --- a/Common/Exceptions/StackExceptionInterpreter.cs +++ b/Common/Exceptions/StackExceptionInterpreter.cs @@ -116,14 +116,16 @@ public string GetExceptionMessageHeader(Exception exception) public static StackExceptionInterpreter CreateFromAssemblies(IEnumerable assemblies) { var interpreters = - from assembly in assemblies + from assembly in assemblies.Where(x => + (!x.FullName?.StartsWith("System.", StringComparison.InvariantCultureIgnoreCase) ?? false) + && (!x.FullName?.StartsWith("Microsoft.", StringComparison.InvariantCultureIgnoreCase) ?? false)) from type in assembly.GetTypes() // ignore non-public and non-instantiable abstract types where type.IsPublic && !type.IsAbstract // type implements IExceptionInterpreter where typeof(IExceptionInterpreter).IsAssignableFrom(type) // type is not mocked with MOQ library - where type.FullName != null && !type.FullName.StartsWith("Castle.Proxies.ObjectProxy") + where type.FullName != null && !type.FullName.StartsWith("Castle.Proxies.ObjectProxy", StringComparison.InvariantCultureIgnoreCase) // type has default parameterless ctor where type.GetConstructor(new Type[0]) != null // provide guarantee of deterministic ordering diff --git a/Tests/AlgorithmRunner.cs b/Tests/AlgorithmRunner.cs index 00229da733d3..93e3af09b91c 100644 --- a/Tests/AlgorithmRunner.cs +++ b/Tests/AlgorithmRunner.cs @@ -111,7 +111,11 @@ public static AlgorithmRunnerResults RunLocalBacktest( var initialLogHandler = Log.LogHandler; var initialDebugEnabled = Log.DebuggingEnabled; - var newLogHandlers = new List() { MaintainLogHandlerAttribute.LogHandler }; + var newLogHandlers = new List(); + if (MaintainLogHandlerAttribute.LogHandler != null) + { + newLogHandlers.Add(MaintainLogHandlerAttribute.LogHandler); + } // Use our current test LogHandler and a FileLogHandler if (!reducedDiskSize) { @@ -190,30 +194,7 @@ public static AlgorithmRunnerResults RunLocalBacktest( } } - if (algorithmManager?.State != expectedFinalStatus) - { - Assert.Fail($"Algorithm state should be {expectedFinalStatus} and is: {algorithmManager?.State}"); - } - - foreach (var expectedStat in expectedStatistics) - { - string result; - Assert.IsTrue(statistics.TryGetValue(expectedStat.Key, out result), "Missing key: " + expectedStat.Key); - - // normalize -0 & 0, they are the same thing - var expected = expectedStat.Value; - if (expected == "-0") - { - expected = "0"; - } - - if (result == "-0") - { - result = "0"; - } - - Assert.AreEqual(expected, result, "Failed on " + expectedStat.Key); - } + AssertAlgorithmState(expectedFinalStatus, algorithmManager?.State, expectedStatistics, statistics); if (!reducedDiskSize) { @@ -272,5 +253,40 @@ public override IEnumerable GetHistory(IEnumerable reques public class TestWorkerThread : WorkerThread { } + + public static void AssertAlgorithmState(AlgorithmStatus expectedFinalStatus, AlgorithmStatus? actualState, + IDictionary expectedStatistics, IDictionary statistics) + { + if (actualState != expectedFinalStatus) + { + Assert.Fail($"Algorithm state should be {expectedFinalStatus} and is: {actualState}"); + } + + foreach (var expectedStat in expectedStatistics) + { + if (statistics == null) + { + Assert.Fail("Algorithm statistics are null"); + break; + } + + string result; + Assert.IsTrue(statistics.TryGetValue(expectedStat.Key, out result), "Missing key: " + expectedStat.Key); + + // normalize -0 & 0, they are the same thing + var expected = expectedStat.Value; + if (expected == "-0") + { + expected = "0"; + } + + if (result == "-0") + { + result = "0"; + } + + Assert.AreEqual(expected, result, "Failed on " + expectedStat.Key); + } + } } } diff --git a/Tests/Api/ApiTestBase.cs b/Tests/Api/ApiTestBase.cs index b90d2cfecab1..218f276eeae3 100644 --- a/Tests/Api/ApiTestBase.cs +++ b/Tests/Api/ApiTestBase.cs @@ -104,7 +104,7 @@ private void CreateTestProjectAndBacktest() return; } Log.Debug("ApiTestBase.Setup(): Waiting for test backtest to complete"); - TestBacktest = WaitForBacktestCompletion(TestProject.ProjectId, backtest.BacktestId); + TestBacktest = WaitForBacktestCompletion(ApiClient, TestProject.ProjectId, backtest.BacktestId); if (!TestBacktest.Success) { Assert.Warn("Could not create backtest for the test project, tests using it will fail."); @@ -151,7 +151,7 @@ public static bool IsValidJson(string jsonString) /// Id of the project /// Id of the compilation of the project /// - protected static Compile WaitForCompilerResponse(Api.Api apiClient, int projectId, string compileId, int seconds = 60) + public static Compile WaitForCompilerResponse(Api.Api apiClient, int projectId, string compileId, int seconds = 60) { var compile = new Compile(); var finish = DateTime.UtcNow.AddSeconds(seconds); @@ -159,7 +159,7 @@ protected static Compile WaitForCompilerResponse(Api.Api apiClient, int projectI { Thread.Sleep(100); compile = apiClient.ReadCompile(projectId, compileId); - } while (compile.State != CompileState.BuildSuccess && DateTime.UtcNow < finish); + } while (compile.State == CompileState.InQueue && DateTime.UtcNow < finish); return compile; } @@ -170,14 +170,18 @@ protected static Compile WaitForCompilerResponse(Api.Api apiClient, int projectI /// Project id to scan /// Backtest id previously started /// Completed backtest object - protected Backtest WaitForBacktestCompletion(int projectId, string backtestId) + public static Backtest WaitForBacktestCompletion(Api.Api apiClient, int projectId, string backtestId, int secondsTimeout = 60) { Backtest backtest; - var finish = DateTime.UtcNow.AddSeconds(60); + var finish = DateTime.UtcNow.AddSeconds(secondsTimeout); do { Thread.Sleep(1000); - backtest = ApiClient.ReadBacktest(projectId, backtestId); + backtest = apiClient.ReadBacktest(projectId, backtestId); + if (backtest != null && (!string.IsNullOrEmpty(backtest.Error) || backtest.HasInitializeError)) + { + Assert.Fail($"Backtest {projectId}/{backtestId} failed: {backtest.Error}. Stacktrace: {backtest.Stacktrace}. Api errors: {string.Join(",", backtest.Errors)}"); + } } while (backtest.Success && backtest.Progress < 1 && DateTime.UtcNow < finish); return backtest; diff --git a/Tests/Api/OptimizationTests.cs b/Tests/Api/OptimizationTests.cs index 70f53cde84fa..3a1fb4ef5a99 100644 --- a/Tests/Api/OptimizationTests.cs +++ b/Tests/Api/OptimizationTests.cs @@ -230,7 +230,7 @@ private int GetProjectCompiledAndWithBacktest(out Compile compile) var backtest = ApiClient.CreateBacktest(projectId, compile.CompileId, backtestName); // Now wait until the backtest is completed and request the orders again - var backtestReady = WaitForBacktestCompletion(projectId, backtest.BacktestId); + var backtestReady = WaitForBacktestCompletion(ApiClient, projectId, backtest.BacktestId); Assert.IsTrue(backtestReady.Success); return projectId; diff --git a/Tests/Api/ProjectTests.cs b/Tests/Api/ProjectTests.cs index d8c704398f9c..6e9dd01fec63 100644 --- a/Tests/Api/ProjectTests.cs +++ b/Tests/Api/ProjectTests.cs @@ -283,7 +283,7 @@ private void Perform_CreateCompileBackTest_Tests(string projectName, Language la Assert.IsTrue(backtest.Success); // Now read the backtest and wait for it to complete - var backtestRead = WaitForBacktestCompletion(project.Projects.First().ProjectId, backtest.BacktestId); + var backtestRead = WaitForBacktestCompletion(ApiClient, project.Projects.First().ProjectId, backtest.BacktestId); Assert.IsTrue(backtestRead.Success); Assert.AreEqual(1, backtestRead.Progress); Assert.AreEqual(backtestName, backtestRead.Name); @@ -355,7 +355,7 @@ public void ReadBacktestOrdersReportAndChart() Assert.IsTrue(backtestRead.Success); // Now wait until the backtest is completed and request the orders again - backtestRead = WaitForBacktestCompletion(project.ProjectId, backtest.BacktestId); + backtestRead = WaitForBacktestCompletion(ApiClient, project.ProjectId, backtest.BacktestId); var backtestOrdersRead = ApiClient.ReadBacktestOrders(project.ProjectId, backtest.BacktestId); string stringRepresentation; foreach(var backtestOrder in backtestOrdersRead) @@ -684,7 +684,7 @@ public void CreatesOptimization() var backtest = ApiClient.CreateBacktest(projectId, compile.CompileId, backtestName); // Now wait until the backtest is completed and request the orders again - var backtestReady = WaitForBacktestCompletion(projectId, backtest.BacktestId); + var backtestReady = WaitForBacktestCompletion(ApiClient, projectId, backtest.BacktestId); Assert.IsTrue(backtestReady.Success); var optimization = ApiClient.CreateOptimization( diff --git a/Tests/Optimizer/Models.cs b/Tests/Optimizer/Models.cs index ab68284dc92b..a0f03b0833de 100644 --- a/Tests/Optimizer/Models.cs +++ b/Tests/Optimizer/Models.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -19,7 +19,7 @@ namespace QuantConnect.Tests.Optimizer { - public class BacktestResult + internal class BacktestResult { private static JsonSerializerSettings _jsonSettings = new JsonSerializerSettings { Culture = CultureInfo.InvariantCulture, NullValueHandling = NullValueHandling.Ignore}; public Statistics Statistics { get; set; } diff --git a/Tests/RegressionTestMessageHandler.cs b/Tests/RegressionTestMessageHandler.cs index 83e22a1ccf84..4623d596bb9b 100644 --- a/Tests/RegressionTestMessageHandler.cs +++ b/Tests/RegressionTestMessageHandler.cs @@ -64,7 +64,7 @@ public override void Send(Packet packet) { if (_updateRegressionStatistics && _job.Language == Language.CSharp) { - UpdateRegressionStatisticsInSourceFile(result); + UpdateRegressionStatisticsInSourceFile(result.Results.Statistics, _algorithmManager, "../../../Algorithm.CSharp", $"{_job.AlgorithmId}.cs"); } } break; @@ -74,9 +74,9 @@ public override void Send(Packet packet) } } - private void UpdateRegressionStatisticsInSourceFile(BacktestResultPacket result) + public static void UpdateRegressionStatisticsInSourceFile(IDictionary statistics, AlgorithmManager algorithmManager, string folder, string fileToUpdate) { - var algorithmSource = Directory.EnumerateFiles("../../../Algorithm.CSharp", $"{_job.AlgorithmId}.cs", SearchOption.AllDirectories).Single(); + var algorithmSource = Directory.EnumerateFiles(folder, fileToUpdate, SearchOption.AllDirectories).Single(); var file = File.ReadAllLines(algorithmSource).ToList().GetEnumerator(); var lines = new List(); while (file.MoveNext()) @@ -90,7 +90,7 @@ private void UpdateRegressionStatisticsInSourceFile(BacktestResultPacket result) if (line.Contains("Dictionary ExpectedStatistics => new Dictionary") || line.Contains("Dictionary ExpectedStatistics => new()")) { - if (!result.Results.Statistics.Any() || line.EndsWith("();")) + if (!statistics.Any() || line.EndsWith("();")) { lines.Add(line); continue; @@ -99,7 +99,7 @@ private void UpdateRegressionStatisticsInSourceFile(BacktestResultPacket result) lines.Add(line); lines.Add(" {"); - foreach (var pair in result.Results.Statistics) + foreach (var pair in statistics) { lines.Add($" {{\"{pair.Key}\", \"{pair.Value}\"}},"); } @@ -127,7 +127,7 @@ private void UpdateRegressionStatisticsInSourceFile(BacktestResultPacket result) } else { - lines.Add(GetDataPointLine(line, _algorithmManager?.DataPoints.ToString())); + lines.Add(GetDataPointLine(line, algorithmManager?.DataPoints.ToString())); } } else if (line.Contains($"int AlgorithmHistoryDataPoints =>")) @@ -138,12 +138,12 @@ private void UpdateRegressionStatisticsInSourceFile(BacktestResultPacket result) } else { - lines.Add(GetDataPointLine(line, _algorithmManager?.AlgorithmHistoryDataPoints.ToString())); + lines.Add(GetDataPointLine(line, algorithmManager?.AlgorithmHistoryDataPoints.ToString())); } } else if (line.Contains($"AlgorithmStatus AlgorithmStatus =>")) { - lines.Add(GetAlgorithmStatusLine(line, _algorithmManager?.State.ToString())); + lines.Add(GetAlgorithmStatusLine(line, algorithmManager?.State.ToString())); } else { diff --git a/Tests/RegressionTests.cs b/Tests/RegressionTests.cs index 59803f419500..f03537dec8e9 100644 --- a/Tests/RegressionTests.cs +++ b/Tests/RegressionTests.cs @@ -27,7 +27,7 @@ namespace QuantConnect.Tests [TestFixture, Category("TravisExclude"), Category("RegressionTests")] public class RegressionTests { - [Test, TestCaseSource(nameof(GetRegressionTestParameters))] + [Test, TestCaseSource(nameof(GetLocalRegressionTestParameters))] public void AlgorithmStatisticsRegression(AlgorithmStatisticsTestParameters parameters) { // ensure we start with a fresh config every time when running multiple tests @@ -78,7 +78,17 @@ public void AlgorithmStatisticsRegression(AlgorithmStatisticsTestParameters para } } - private static TestCaseData[] GetRegressionTestParameters() + public static TestCaseData[] GetLocalRegressionTestParameters() + { + return GetRegressionTestParameters(canRunLocally: true, + (instance, language) => new AlgorithmStatisticsTestParameters(instance.GetType().Name, instance.ExpectedStatistics, language, + instance.AlgorithmStatus, instance.DataPoints, instance.AlgorithmHistoryDataPoints)); + } + + public static TestCaseData[] GetRegressionTestParameters(bool canRunLocally, Func factory) + where T : IRegressionAlgorithmDefinition + where K : AlgorithmStatisticsTestParameters + where J : class { TestGlobals.Initialize(); @@ -91,14 +101,14 @@ private static TestCaseData[] GetRegressionTestParameters() // find all regression algorithms in Algorithm.CSharp return ( - from type in typeof(BasicTemplateAlgorithm).Assembly.GetTypes() - where typeof(IRegressionAlgorithmDefinition).IsAssignableFrom(type) + from type in typeof(J).Assembly.GetTypes() + where typeof(T).IsAssignableFrom(type) where !type.IsAbstract // non-abstract where type.GetConstructor(Array.Empty()) != null // has default ctor - let instance = (IRegressionAlgorithmDefinition)Activator.CreateInstance(type) - where instance.CanRunLocally // open source has data to run this algorithm + let instance = (T)Activator.CreateInstance(type) + where instance.CanRunLocally == canRunLocally // open source has data to run this algorithm from language in instance.Languages.Where(languages.Contains) - select new AlgorithmStatisticsTestParameters(type.Name, instance.ExpectedStatistics, language, instance.AlgorithmStatus, instance.DataPoints, instance.AlgorithmHistoryDataPoints) + select factory(instance, language) ) .OrderBy(x => x.Language).ThenBy(x => x.Algorithm) // generate test cases from test parameters diff --git a/mypy.ini b/mypy.ini index d69ef0b693be..9ee13709de1b 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,4 +1,4 @@ # Global options: [mypy] -disable_error_code = var-annotated,has-type \ No newline at end of file +disable_error_code = var-annotated,has-type,union-attr \ No newline at end of file