/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using NUnit.Framework; using QuantConnect.Api; using QuantConnect.Optimizer; using QuantConnect.Optimizer.Objectives; using QuantConnect.Optimizer.Parameters; using QuantConnect.Statistics; using QuantConnect.Util; using System.IO; namespace QuantConnect.Tests.API { /// /// Tests API account and optimizations endpoints /// [TestFixture, Explicit("Requires configured api access")] public class OptimizationTests : ApiTestBase { private string _validSerialization = "{\"optimizationId\":\"myOptimizationId\",\"name\":\"myOptimizationName\",\"runtimeStatistics\":{\"Completed\":\"1\"},"+ "\"constraints\":[{\"target\":\"TotalPerformance.PortfolioStatistics.SharpeRatio\",\"operator\":\"GreaterOrEqual\",\"targetValue\":1}],"+ "\"parameters\":[{\"name\":\"myParamName\",\"min\":2,\"max\":4,\"step\":1}, {\"name\":\"myStaticParamName\",\"value\":4}],\"nodeType\":\"O2-8\",\"parallelNodes\":12,\"projectId\":1234567,\"status\":\"completed\"," + "\"backtests\":{\"myBacktestKey\":{\"name\":\"myBacktestName\",\"id\":\"myBacktestId\",\"progress\":1,\"exitCode\":0,"+ "\"statistics\":[0.374,0.217,0.047,-4.51,2.86,-0.664,52.602,17.800,6300000.00,0.196,1.571,27.0,123.888,77.188,0.63,1.707,1390.49,180.0,0.233,-0.558,73.0]," + "\"parameterSet\":{\"myParamName\":\"2\"},\"equity\":[]}},\"strategy\":\"QuantConnect.Optimizer.Strategies.GridSearchOptimizationStrategy\"," + "\"requested\":\"2021-12-16 00:51:58\",\"criterion\":{\"extremum\":\"max\",\"target\":\"TotalPerformance.PortfolioStatistics.SharpeRatio\",\"targetValue\":null}}"; private string _validEstimateSerialization = "{\"estimateId\":\"myEstimateId\",\"time\":26,\"balance\":500}"; [Test] public void Deserialization() { var deserialized = JsonConvert.DeserializeObject(_validSerialization); Assert.IsNotNull(deserialized); Assert.AreEqual("myOptimizationId", deserialized.OptimizationId); Assert.AreEqual("myOptimizationName", deserialized.Name); Assert.IsTrue(deserialized.RuntimeStatistics.Count == 1); Assert.IsTrue(deserialized.RuntimeStatistics["Completed"] == "1"); Assert.IsTrue(deserialized.Constraints.Count == 1); Assert.AreEqual("['TotalPerformance'].['PortfolioStatistics'].['SharpeRatio']", deserialized.Constraints[0].Target); Assert.IsTrue(deserialized.Constraints[0].Operator == ComparisonOperatorTypes.GreaterOrEqual); Assert.IsTrue(deserialized.Constraints[0].TargetValue == 1); Assert.IsTrue(deserialized.Parameters.Count == 2); var stepParam = deserialized.Parameters.First().ConvertInvariant(); Assert.IsTrue(stepParam.Name == "myParamName"); Assert.IsTrue(stepParam.MinValue == 2); Assert.IsTrue(stepParam.MaxValue == 4); Assert.IsTrue(stepParam.Step == 1); var staticParam = deserialized.Parameters.ElementAt(1).ConvertInvariant(); Assert.IsTrue(staticParam.Name == "myStaticParamName"); Assert.IsTrue(staticParam.Value == "4"); Assert.AreEqual(OptimizationNodes.O2_8, deserialized.NodeType); Assert.AreEqual(12, deserialized.ParallelNodes); Assert.AreEqual(1234567, deserialized.ProjectId); Assert.AreEqual(OptimizationStatus.Completed, deserialized.Status); Assert.IsTrue(deserialized.Backtests.Count == 1); Assert.IsTrue(deserialized.Backtests["myBacktestKey"].BacktestId == "myBacktestId"); Assert.IsTrue(deserialized.Backtests["myBacktestKey"].Name == "myBacktestName"); Assert.IsTrue(deserialized.Backtests["myBacktestKey"].ParameterSet.Value["myParamName"] == "2"); Assert.IsTrue(deserialized.Backtests["myBacktestKey"].Statistics[PerformanceMetrics.ProbabilisticSharpeRatio] == "77.188"); Assert.AreEqual("QuantConnect.Optimizer.Strategies.GridSearchOptimizationStrategy", deserialized.Strategy); Assert.AreEqual(new DateTime(2021, 12, 16, 00, 51, 58), deserialized.Requested); Assert.AreEqual("['TotalPerformance'].['PortfolioStatistics'].['SharpeRatio']", deserialized.Criterion.Target); Assert.IsInstanceOf(deserialized.Criterion.Extremum); Assert.IsNull(deserialized.Criterion.TargetValue); } [Test] public void EstimateDeserialization() { var deserialized = JsonConvert.DeserializeObject(_validEstimateSerialization); Assert.AreEqual("myEstimateId", deserialized.EstimateId); Assert.AreEqual(26, deserialized.Time); Assert.AreEqual(500, deserialized.Balance); } [Test] public void EstimateOptimization() { var projectId = GetProjectCompiledAndWithBacktest(out var compile); var estimate = ApiClient.EstimateOptimization( projectId: projectId, name: "My Testable Optimization", target: "TotalPerformance.PortfolioStatistics.SharpeRatio", targetTo: "max", targetValue: null, strategy: "QuantConnect.Optimizer.Strategies.GridSearchOptimizationStrategy", compileId: compile.CompileId, parameters: new HashSet { new OptimizationStepParameter("ema-fast", 20, 50, 1, 1) // Replace params with valid optimization parameter data for test project }, constraints: new List { new Constraint("TotalPerformance.PortfolioStatistics.SharpeRatio", ComparisonOperatorTypes.GreaterOrEqual, 1) } ); var stringRepresentation = estimate.ToString(); Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation)); Assert.IsNotNull(estimate); Assert.IsNotEmpty(estimate.EstimateId); Assert.GreaterOrEqual(estimate.Time, 0); Assert.GreaterOrEqual(estimate.Balance, 0); // Delete the project var deleteProject = ApiClient.DeleteProject(projectId); Assert.IsTrue(deleteProject.Success); } [Test] public void CreateOptimization() { var optimization = GetOptimization(out var projectId); TestBaseOptimization(optimization); // Delete the project var deleteProject = ApiClient.DeleteProject(projectId); Assert.IsTrue(deleteProject.Success); } [Test] public void ListOptimizations() { GetOptimization(out var projectId); var optimizations = ApiClient.ListOptimizations(projectId); Assert.IsNotNull(optimizations); Assert.IsTrue(optimizations.Any()); TestBaseOptimization(optimizations.First()); // Delete the project var deleteProject = ApiClient.DeleteProject(projectId); Assert.IsTrue(deleteProject.Success); } [Test] public void ReadOptimization() { var optimization = GetOptimization(out var projectId); var readOptimization = ApiClient.ReadOptimization(optimization.OptimizationId); TestBaseOptimization(readOptimization); // Delete the project var deleteProject = ApiClient.DeleteProject(projectId); Assert.IsTrue(deleteProject.Success); } [Test] public void AbortOptimization() { var optimization = GetOptimization(out var projectId); var response = ApiClient.AbortOptimization(optimization.OptimizationId); Assert.IsTrue(response.Success); // Delete the project var deleteProject = ApiClient.DeleteProject(projectId); Assert.IsTrue(deleteProject.Success); } [Test] public void UpdateOptimization() { var optimization = GetOptimization(out var projectId); var response = ApiClient.UpdateOptimization(optimization.OptimizationId, "Alert Yellow Submarine"); Assert.IsTrue(response.Success); // Delete the project var deleteProject = ApiClient.DeleteProject(projectId); Assert.IsTrue(deleteProject.Success); } [Test] public void DeleteOptimization() { var optimization = GetOptimization(out var projectId); var response = ApiClient.DeleteOptimization(optimization.OptimizationId); Assert.IsTrue(response.Success); // Delete the project var deleteProject = ApiClient.DeleteProject(projectId); Assert.IsTrue(deleteProject.Success); } private int GetProjectCompiledAndWithBacktest(out Compile compile) { var file = new ProjectFile { Name = "Main.cs", Code = File.ReadAllText("../../../Algorithm.CSharp/ParameterizedAlgorithm.cs") }; // Create a new project var project = ApiClient.CreateProject($"Test project - {DateTime.Now.ToStringInvariant()}", Language.CSharp, TestOrganization); var projectId = project.Projects.First().ProjectId; // Update Project Files var updateProjectFileContent = ApiClient.UpdateProjectFileContent(projectId, "Main.cs", file.Code); Assert.IsTrue(updateProjectFileContent.Success); // Create compile compile = ApiClient.CreateCompile(projectId); Assert.IsTrue(compile.Success); // Wait at max 30 seconds for project to compile var compileCheck = WaitForCompilerResponse(ApiClient, projectId, compile.CompileId); Assert.IsTrue(compileCheck.Success); Assert.IsTrue(compileCheck.State == CompileState.BuildSuccess); var backtestName = $"Estimate optimization Backtest"; var backtest = ApiClient.CreateBacktest(projectId, compile.CompileId, backtestName); // Now wait until the backtest is completed and request the orders again var backtestReady = WaitForBacktestCompletion(ApiClient, projectId, backtest.BacktestId); Assert.IsTrue(backtestReady.Success); return projectId; } private BaseOptimization GetOptimization(out int projectId) { projectId = GetProjectCompiledAndWithBacktest(out var compile); var optimization = ApiClient.CreateOptimization( projectId: projectId, name: "My Testable Optimization", target: "TotalPerformance.PortfolioStatistics.SharpeRatio", targetTo: "max", targetValue: null, strategy: "QuantConnect.Optimizer.Strategies.GridSearchOptimizationStrategy", compileId: compile.CompileId, parameters: new HashSet { new OptimizationStepParameter("ema-fast", 20, 50, 1, 1) // Replace params with valid optimization parameter data for test project }, constraints: new List { new Constraint("TotalPerformance.PortfolioStatistics.SharpeRatio", ComparisonOperatorTypes.GreaterOrEqual, 1) }, estimatedCost: 0.06m, nodeType: OptimizationNodes.O2_8, parallelNodes: 12 ); return optimization; } private void TestBaseOptimization(BaseOptimization optimization) { Assert.IsNotNull(optimization); Assert.IsNotEmpty(optimization.OptimizationId); Assert.Positive(optimization.ProjectId); Assert.IsNotEmpty(optimization.Name); Assert.IsInstanceOf(optimization.Status); Assert.IsNotEmpty(optimization.NodeType); Assert.IsTrue(0 <= optimization.OutOfSampleDays); Assert.AreNotEqual(default(DateTime), optimization.OutOfSampleMaxEndDate); Assert.IsNotNull(optimization.Criterion); foreach (var item in optimization.Parameters) { Assert.IsFalse(string.IsNullOrEmpty(item.Name)); } if (optimization is OptimizationSummary) { Assert.AreNotEqual(default(DateTime), (optimization as OptimizationSummary).Created); } else if (optimization is Optimization) { TestOptimization(optimization as Optimization); } } private void TestOptimization(Optimization optimization) { Assert.AreNotEqual(default(string), optimization.OptimizationTarget); Assert.IsNotNull(optimization.GridLayout); Assert.IsNotNull(optimization.RuntimeStatistics); Assert.IsNotNull(optimization.Constraints); Assert.IsTrue(0 <= optimization.ParallelNodes); Assert.IsNotNull(optimization.Backtests); Assert.AreNotEqual(default(string), optimization.Strategy); Assert.AreNotEqual(default(DateTime), optimization.Requested); } } }