chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Api;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
[TestFixture]
|
||||
public class AccountTests
|
||||
{
|
||||
[TestCase("{\"organizationId\":\"organizationID\",\"creditBalance\":111.56,\"card\":{\"brand\":\"visa\",\"expiration\":\"12\\/27\",\"last4\":\"0001\"},\"success\":true}", true)]
|
||||
[TestCase("{\"organizationId\":\"organizationID\",\"creditBalance\":111.56,\"card\":null,\"success\":true}", false)]
|
||||
public void Deserialize(string response, bool hasCard)
|
||||
{
|
||||
var account = JsonConvert.DeserializeObject<Account>(response);
|
||||
|
||||
Assert.AreEqual("organizationID", account.OrganizationId);
|
||||
Assert.AreEqual(111.56m, account.CreditBalance);
|
||||
|
||||
if (hasCard)
|
||||
{
|
||||
Assert.AreEqual(1, account.Card.LastFourDigits);
|
||||
Assert.AreEqual("visa", account.Card.Brand);
|
||||
Assert.AreEqual(new DateTime(2027, 12, 1), account.Card.Expiration);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(account.Card);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.Api;
|
||||
using QuantConnect.Configuration;
|
||||
using QuantConnect.Logging;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
/// <summary>
|
||||
/// Base test class for Api tests, provides the setup needed for all api tests
|
||||
/// </summary>
|
||||
public class ApiTestBase
|
||||
{
|
||||
internal int TestAccount;
|
||||
internal string TestToken;
|
||||
internal string TestOrganization;
|
||||
internal string DataFolder;
|
||||
internal Api.Api ApiClient;
|
||||
|
||||
protected Project TestProject { get; private set; }
|
||||
protected Backtest TestBacktest { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Run once before any RestApiTests
|
||||
/// </summary>
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
ReloadConfiguration();
|
||||
|
||||
TestAccount = Globals.UserId;
|
||||
TestToken = Globals.UserToken;
|
||||
TestOrganization = Globals.OrganizationID;
|
||||
DataFolder = Globals.DataFolder;
|
||||
|
||||
ApiClient = new Api.Api();
|
||||
ApiClient.Initialize(TestAccount, TestToken, DataFolder);
|
||||
|
||||
// Let's create a project and backtest that can be used for general testing
|
||||
CreateTestProjectAndBacktest();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
DeleteTestProjectAndBacktest();
|
||||
}
|
||||
|
||||
private void CreateTestProjectAndBacktest()
|
||||
{
|
||||
// Create a new project and backtest that can be used for testing
|
||||
Log.Debug("ApiTestBase.Setup(): Creating test project and backtest");
|
||||
var createProjectResult = ApiClient.CreateProject($"TestProject{DateTime.UtcNow.ToStringInvariant("yyyyMMddHHmmssfff")}",
|
||||
Language.CSharp, TestOrganization);
|
||||
if (!createProjectResult.Success)
|
||||
{
|
||||
Assert.Warn("Could not create test project, tests using it will fail.");
|
||||
return;
|
||||
}
|
||||
TestProject = createProjectResult.Projects[0];
|
||||
|
||||
// Create a new compile for the project
|
||||
Log.Debug("ApiTestBase.Setup(): Creating test compile");
|
||||
var compile = ApiClient.CreateCompile(TestProject.ProjectId);
|
||||
if (!compile.Success)
|
||||
{
|
||||
Assert.Warn("Could not create compile for the test project, tests using it will fail.");
|
||||
return;
|
||||
}
|
||||
Log.Debug("ApiTestBase.Setup(): Waiting for test compile to complete");
|
||||
compile = WaitForCompilerResponse(ApiClient, TestProject.ProjectId, compile.CompileId);
|
||||
if (!compile.Success)
|
||||
{
|
||||
Assert.Warn("Could not create compile for the test project, tests using it will fail.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a backtest
|
||||
Log.Debug("ApiTestBase.Setup(): Creating test backtest");
|
||||
var backtestName = $"{DateTime.UtcNow.ToStringInvariant("yyyy-MM-dd HH-mm-ss")} API Backtest";
|
||||
var backtest = ApiClient.CreateBacktest(TestProject.ProjectId, compile.CompileId, backtestName);
|
||||
if (!backtest.Success)
|
||||
{
|
||||
Assert.Warn("Could not create backtest for the test project, tests using it will fail.");
|
||||
return;
|
||||
}
|
||||
Log.Debug("ApiTestBase.Setup(): Waiting for test backtest to complete");
|
||||
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.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Debug("ApiTestBase.Setup(): Test project and backtest created successfully");
|
||||
}
|
||||
|
||||
private void DeleteTestProjectAndBacktest()
|
||||
{
|
||||
if (TestBacktest != null && TestBacktest.Success)
|
||||
{
|
||||
Log.Debug("ApiTestBase.TearDown(): Deleting test backtest");
|
||||
ApiClient.DeleteBacktest(TestProject.ProjectId, TestBacktest.BacktestId);
|
||||
}
|
||||
|
||||
if (TestProject != null)
|
||||
{
|
||||
Log.Debug("ApiTestBase.TearDown(): Deleting test project");
|
||||
ApiClient.DeleteProject(TestProject.ProjectId);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsValidJson(string jsonString)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (JsonDocument doc = JsonDocument.Parse(jsonString))
|
||||
{
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for the compiler to respond to a specified compile request
|
||||
/// </summary>
|
||||
/// <param name="projectId">Id of the project</param>
|
||||
/// <param name="compileId">Id of the compilation of the project</param>
|
||||
/// <returns></returns>
|
||||
public static Compile WaitForCompilerResponse(Api.Api apiClient, int projectId, string compileId, int seconds = 60)
|
||||
{
|
||||
var compile = new Compile();
|
||||
var finish = DateTime.UtcNow.AddSeconds(seconds);
|
||||
do
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
compile = apiClient.ReadCompile(projectId, compileId);
|
||||
} while (compile.State == CompileState.InQueue && DateTime.UtcNow < finish);
|
||||
|
||||
return compile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for the backtest to complete
|
||||
/// </summary>
|
||||
/// <param name="projectId">Project id to scan</param>
|
||||
/// <param name="backtestId">Backtest id previously started</param>
|
||||
/// <returns>Completed backtest object</returns>
|
||||
public static Backtest WaitForBacktestCompletion(Api.Api apiClient, int projectId, string backtestId, int secondsTimeout = 60, bool returnFailedBacktest = false)
|
||||
{
|
||||
Backtest backtest;
|
||||
var finish = DateTime.UtcNow.AddSeconds(secondsTimeout);
|
||||
do
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
backtest = apiClient.ReadBacktest(projectId, backtestId);
|
||||
if (backtest == null)
|
||||
{
|
||||
// api failed, let's retry
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(backtest.Error) || backtest.HasInitializeError)
|
||||
{
|
||||
if (!returnFailedBacktest)
|
||||
{
|
||||
Assert.Fail($"Backtest {projectId}/{backtestId} failed: {backtest.Error}. Stacktrace: {backtest.Stacktrace}. Api errors: {string.Join(",", backtest.Errors)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
return backtest;
|
||||
}
|
||||
}
|
||||
} while (((backtest == null || (backtest.Success && backtest.Progress < 1)) && DateTime.UtcNow < finish));
|
||||
|
||||
return backtest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reload configuration, making sure environment variables are loaded into the config
|
||||
/// </summary>
|
||||
internal static void ReloadConfiguration()
|
||||
{
|
||||
// nunit 3 sets the current folder to a temp folder we need it to be the test bin output folder
|
||||
var dir = TestContext.CurrentContext.TestDirectory;
|
||||
Environment.CurrentDirectory = dir;
|
||||
Directory.SetCurrentDirectory(dir);
|
||||
// reload config from current path
|
||||
Config.Reset();
|
||||
|
||||
var environment = Environment.GetEnvironmentVariables();
|
||||
foreach (DictionaryEntry entry in environment)
|
||||
{
|
||||
var envKey = entry.Key.ToString();
|
||||
var value = entry.Value.ToString();
|
||||
|
||||
if (envKey.StartsWith("QC_", StringComparison.InvariantCulture))
|
||||
{
|
||||
var key = envKey.Substring(3).Replace("_", "-", StringComparison.InvariantCulture).ToLowerInvariant();
|
||||
Log.Trace($"TestSetup(): Updating config setting '{key}' from environment var '{envKey}'");
|
||||
Config.Set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// resets the version among other things
|
||||
Globals.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Api;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
/// <summary>
|
||||
/// API Object tests
|
||||
/// Tests APIs ability to connect to Web API
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class ApiTest : ApiTestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Test successfully authenticating with the ApiConnection using valid credentials.
|
||||
/// </summary>
|
||||
[Test, Explicit("Requires configured api access")]
|
||||
public void ApiConnectionWillAuthenticate_ValidCredentials_Successfully()
|
||||
{
|
||||
using var connection = new ApiConnection(TestAccount, TestToken);
|
||||
Assert.IsTrue(connection.Connected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test successfully authenticating with the API using valid credentials.
|
||||
/// </summary>
|
||||
[Test, Explicit("Requires configured api access")]
|
||||
public void ApiWillAuthenticate_ValidCredentials_Successfully()
|
||||
{
|
||||
using var api = new Api.Api();
|
||||
api.Initialize(TestAccount, TestToken, DataFolder);
|
||||
Assert.IsTrue(api.Connected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that the ApiConnection will reject invalid credentials
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ApiConnectionWillAuthenticate_InvalidCredentials_Unsuccessfully()
|
||||
{
|
||||
using var connection = new ApiConnection(TestAccount, "");
|
||||
Assert.IsFalse(connection.Connected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that the Api will reject invalid credentials
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ApiWillAuthenticate_InvalidCredentials_Unsuccessfully()
|
||||
{
|
||||
using var api = new Api.Api();
|
||||
api.Initialize(TestAccount, "", DataFolder);
|
||||
Assert.IsFalse(api.Connected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NullDataFolder()
|
||||
{
|
||||
using var api = new Api.Api();
|
||||
Assert.DoesNotThrow(() => api.Initialize(TestAccount, "", null));
|
||||
}
|
||||
|
||||
[TestCase("C:\\Data", "forex/oanda/daily/eurusd.zip")]
|
||||
[TestCase("C:\\Data\\", "forex/oanda/daily/eurusd.zip")]
|
||||
[TestCase("C:/Data/", "forex/oanda/daily/eurusd.zip")]
|
||||
[TestCase("C:/Data", "forex/oanda/daily/eurusd.zip")]
|
||||
[TestCase("C:/Data", "forex\\oanda\\daily\\eurusd.zip")]
|
||||
[TestCase("C:/Data/", "forex\\oanda\\daily\\eurusd.zip")]
|
||||
[TestCase("C:\\Data\\", "forex\\oanda\\daily\\eurusd.zip")]
|
||||
[TestCase("C:\\Data", "forex\\oanda\\daily\\eurusd.zip")]
|
||||
public void FormattingPathForDataRequestsAreCorrect(string dataFolder, string dataToDownload)
|
||||
{
|
||||
var path = Path.Combine(dataFolder, dataToDownload);
|
||||
|
||||
var result = Api.Api.FormatPathForDataRequest(path, dataFolder);
|
||||
Assert.AreEqual(dataToDownload.Replace("\\", "/", StringComparison.InvariantCulture), result);
|
||||
}
|
||||
|
||||
[TestCase("Authorization", "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request, SignedHeaders=host;range;x-amz-date,Signature=EXAMPLE_SIGNATURE")]
|
||||
[TestCase("Custom-Header", "Custom header value")]
|
||||
public void DownloadBytesAllowsUserDefinedHeaders(string headerKey, string headerValue)
|
||||
{
|
||||
using var api = new Api.Api();
|
||||
|
||||
var headers = new List<KeyValuePair<string, string>>() { new(headerKey, headerValue) };
|
||||
Assert.DoesNotThrow(() => api.Download("https://www.dropbox.com/s/ggt6blmib54q36e/CAPE.csv?dl=1", headers, "", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.Web;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Api;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
[TestFixture]
|
||||
public class AuthenticationTests
|
||||
{
|
||||
[Test, Explicit("Requires api creds")]
|
||||
public void Link()
|
||||
{
|
||||
var link = Authentication.Link("authenticate");
|
||||
|
||||
var response = link.DownloadData();
|
||||
|
||||
Assert.IsNotNull(response);
|
||||
|
||||
var jobject = JObject.Parse(response);
|
||||
Assert.IsTrue(jobject["success"].ToObject<bool>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PopulateQueryString()
|
||||
{
|
||||
var payload = new { SomeArray = new[] { 1, 2, 3 }, Symbol = "SPY", Parameters = new Dictionary<string, int>() { { "Quantity", 10 } } };
|
||||
|
||||
var queryString = HttpUtility.ParseQueryString(string.Empty);
|
||||
Authentication.PopulateQueryString(queryString, new[] { new KeyValuePair<string, object>("command", payload) });
|
||||
|
||||
Assert.AreEqual("command%5bSomeArray%5d%5b0%5d=1&command%5bSomeArray%5d%5b1%5d=2&command%5bSomeArray%5d%5b2%5d=3&command%5bSymbol%5d=SPY&command%5bParameters%5d%5bQuantity%5d=10", queryString.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using QuantConnect.Api;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
[TestFixture, Explicit("Requires configured api access, a live node to run on, and brokerage configurations.")]
|
||||
public class CommandTests
|
||||
{
|
||||
private Api.Api _apiClient;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
ApiTestBase.ReloadConfiguration();
|
||||
|
||||
_apiClient = new Api.Api();
|
||||
_apiClient.Initialize(Globals.UserId, Globals.UserToken, Globals.DataFolder);
|
||||
}
|
||||
|
||||
[TestCase("MyCommand")]
|
||||
[TestCase("MyCommand2")]
|
||||
[TestCase("MyCommand3")]
|
||||
[TestCase("")]
|
||||
public void LiveCommand(string commandType)
|
||||
{
|
||||
var command = new Dictionary<string, object>
|
||||
{
|
||||
{ "quantity", 0.1 },
|
||||
{ "target", "BTCUSD" },
|
||||
{ "$type", commandType }
|
||||
};
|
||||
|
||||
var projectId = RunLiveAlgorithm();
|
||||
try
|
||||
{
|
||||
// allow algo to be deployed and prices to be set so we can trade
|
||||
Thread.Sleep(TimeSpan.FromSeconds(10));
|
||||
var result = _apiClient.CreateLiveCommand(projectId, command);
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_apiClient.StopLiveAlgorithm(projectId);
|
||||
_apiClient.DeleteProject(projectId);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("MyCommand")]
|
||||
[TestCase("MyCommand2")]
|
||||
[TestCase("MyCommand3")]
|
||||
[TestCase("")]
|
||||
[TestCase("", true)]
|
||||
public void BroadcastCommand(string commandType, bool excludeProject = false)
|
||||
{
|
||||
var command = new Dictionary<string, object>
|
||||
{
|
||||
{ "quantity", 0.1 },
|
||||
{ "target", "BTCUSD" },
|
||||
{ "$type", commandType }
|
||||
};
|
||||
|
||||
var projectId = RunLiveAlgorithm();
|
||||
try
|
||||
{
|
||||
// allow algo to be deployed and prices to be set so we can trade
|
||||
Thread.Sleep(TimeSpan.FromSeconds(10));
|
||||
// Our project will not receive the broadcast, unless we pass null value, but we can still use it to send a command
|
||||
var result = _apiClient.BroadcastLiveCommand(Globals.OrganizationID, excludeProject ? projectId : null, command);
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_apiClient.StopLiveAlgorithm(projectId);
|
||||
_apiClient.DeleteProject(projectId);
|
||||
}
|
||||
}
|
||||
|
||||
private int RunLiveAlgorithm()
|
||||
{
|
||||
var settings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "QuantConnectBrokerage" },
|
||||
{ "environment", "paper" },
|
||||
{ "user", "" },
|
||||
{ "password", "" },
|
||||
{ "account", "" }
|
||||
};
|
||||
|
||||
var file = new ProjectFile
|
||||
{
|
||||
Name = "Main.cs",
|
||||
Code = @"from AlgorithmImports import *
|
||||
|
||||
class MyCommand():
|
||||
quantity = 0
|
||||
target = ''
|
||||
def run(self, algo: QCAlgorithm) -> bool | None:
|
||||
self.execute_order(algo)
|
||||
def execute_order(self, algo):
|
||||
algo.order(self.target, self.quantity)
|
||||
|
||||
class MyCommand2():
|
||||
quantity = 0
|
||||
target = ''
|
||||
def run(self, algo: QCAlgorithm) -> bool | None:
|
||||
algo.order(self.target, self.quantity)
|
||||
return True
|
||||
|
||||
class MyCommand3():
|
||||
quantity = 0
|
||||
target = ''
|
||||
def run(self, algo: QCAlgorithm) -> bool | None:
|
||||
algo.order(self.target, self.quantity)
|
||||
return False
|
||||
|
||||
class DeterminedSkyBlueGorilla(QCAlgorithm):
|
||||
def initialize(self):
|
||||
self.set_start_date(2023, 3, 17)
|
||||
self.add_crypto(""BTCUSD"", Resolution.SECOND)
|
||||
self.add_command(MyCommand)
|
||||
self.add_command(MyCommand2)
|
||||
self.add_command(MyCommand3)
|
||||
|
||||
def on_command(self, data):
|
||||
self.order(data.target, data.quantity)"
|
||||
};
|
||||
|
||||
// Run the live algorithm
|
||||
return LiveTradingTests.RunLiveAlgorithm(_apiClient, settings, file, stopLiveAlgos: false, language: Language.Python);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Api;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
/// <summary>
|
||||
/// API Data endpoint tests
|
||||
/// </summary>
|
||||
[TestFixture, Explicit("Requires configured api access, and also makes calls to data endpoints which are charging transactions")]
|
||||
public class DataTests : ApiTestBase
|
||||
{
|
||||
private DataPricesList _pricesCache;
|
||||
private static object[] validForexDataTestCases =
|
||||
{
|
||||
new object[] { "EURUSD", Market.Oanda, new DateTime(2013,10,07), Resolution.Daily, TickType.Quote },
|
||||
new object[] { "EURUSD", Market.Oanda, new DateTime(2013,10,07), Resolution.Minute, TickType.Quote }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Test downloading data
|
||||
/// </summary>
|
||||
[TestCase("forex/oanda/minute/eurusd/20131011_quote.zip")]
|
||||
[TestCase("forex/oanda/daily/eurusd.zip")]
|
||||
public void DataDownloadedAndSaved(string fileToDownload)
|
||||
{
|
||||
var path = Path.Combine(DataFolder, fileToDownload);
|
||||
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
|
||||
var downloadedData = ApiClient.DownloadData(path, TestOrganization);
|
||||
|
||||
Assert.IsTrue(downloadedData);
|
||||
Assert.IsTrue(File.Exists(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test attempting to fetch invalid data links
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void InvalidDataLinks()
|
||||
{
|
||||
var fakePath = Path.Combine(DataFolder, "forex/oanda/minute/eurusd/19891011_quote.zip");
|
||||
var nonExistentData = ApiClient.DownloadData(fakePath, TestOrganization);
|
||||
|
||||
Assert.IsFalse(nonExistentData);
|
||||
Assert.IsFalse(File.Exists(fakePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test getting links to forex data for Oanda
|
||||
/// </summary>
|
||||
[TestCaseSource(nameof(validForexDataTestCases))]
|
||||
public void ValidForexDataLinks(string ticker, string market, DateTime date, Resolution resolution, TickType tickType)
|
||||
{
|
||||
var path = LeanData.GenerateRelativeZipFilePath(
|
||||
new Symbol(SecurityIdentifier.GenerateForex(ticker, market), ticker),
|
||||
date, resolution, tickType);
|
||||
var dataLink = ApiClient.ReadDataLink(path, TestOrganization);
|
||||
var stringRepresentation = dataLink.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
|
||||
Assert.IsTrue(dataLink.Success);
|
||||
Assert.IsFalse(dataLink.Link.IsNullOrEmpty());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test getting price for file
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
[TestCase("forex/oanda/daily/eurusd.zip")]
|
||||
[TestCase("crypto/coinbase/daily/btcusd_quote.zip")]
|
||||
public void GetPrices(string filePath)
|
||||
{
|
||||
if (_pricesCache == null)
|
||||
{
|
||||
_pricesCache = ApiClient.ReadDataPrices(TestOrganization);
|
||||
var stringRepresentation = _pricesCache.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
}
|
||||
|
||||
// Make sure we actually have these prices for the test to work
|
||||
Assert.IsTrue(_pricesCache.Success);
|
||||
|
||||
// Get the price
|
||||
int price = _pricesCache.GetPrice(filePath);
|
||||
Assert.AreNotEqual(price, -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test regex implementation for DataPriceList price matching
|
||||
/// </summary>
|
||||
/// <param name="dataFile"></param>
|
||||
/// <param name="matchingRegex"></param>
|
||||
[TestCase("forex/oanda/daily/eurusd.zip", "/^(cfd|forex)\\/oanda\\/(daily|hour)\\/[^\\/]+.zip$/m")]
|
||||
[TestCase("forex/oanda/daily/eurusd.zip", "/^(cfd|forex)\\/oanda\\/(daily|hour)\\/[^\\/]+.zip$")]
|
||||
[TestCase("forex/oanda/daily/eurusd.zip", "^(cfd|forex)\\/oanda\\/(daily|hour)\\/[^\\/]+.zip$/")]
|
||||
[TestCase("forex/oanda/daily/eurusd.zip", "^(cfd|forex)\\/oanda\\/(daily|hour)\\/[^\\/]+.zip$")]
|
||||
public void DataPriceRegex(string dataFile, string matchingRegex)
|
||||
{
|
||||
var setPrice = 10;
|
||||
var dataList = new DataPricesList
|
||||
{
|
||||
Prices = new List<PriceEntry>() { new PriceEntry() { Price = setPrice, RawRegEx = matchingRegex } }
|
||||
};
|
||||
|
||||
int price = dataList.GetPrice(dataFile);
|
||||
Assert.AreEqual(setPrice, price);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test getting available data listings for directories
|
||||
/// </summary>
|
||||
/// <param name="directory"></param>
|
||||
[TestCase("alternative/sec/aapl/")]
|
||||
[TestCase("cfd/oanda/daily/")]
|
||||
[TestCase("crypto/coinbase/minute/btcusd/")]
|
||||
[TestCase("equity/usa/shortable/")]
|
||||
[TestCase("forex/oanda/minute/eurusd/")]
|
||||
[TestCase("forex\\oanda\\minute\\eurusd\\")] //Windows path case
|
||||
[TestCase("future/cbot/minute/zs")]
|
||||
[TestCase("futureoption/comex/minute/og")]
|
||||
[TestCase("index/usa/minute/spx")]
|
||||
[TestCase("indexoption/usa/minute/spx")]
|
||||
[TestCase("option/usa/minute/aapl")]
|
||||
public void GetDataListings(string directory)
|
||||
{
|
||||
var dataList = ApiClient.ReadDataDirectory(directory);
|
||||
var stringRepresentation = dataList.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(dataList.Success);
|
||||
Assert.IsTrue(dataList.AvailableData.Count > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,894 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Api;
|
||||
using System.Threading;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Brokerages;
|
||||
using QuantConnect.Configuration;
|
||||
using System.Collections.Generic;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Interfaces;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
/// <summary>
|
||||
/// API Live endpoint tests
|
||||
/// </summary>
|
||||
[TestFixture, Explicit("Requires configured api access, a live node to run on, and brokerage configurations.")]
|
||||
public class LiveTradingTests : ApiTestBase
|
||||
{
|
||||
private const bool StopLiveAlgos = true;
|
||||
private readonly Dictionary<string, object> _defaultSettings = new()
|
||||
{
|
||||
{ "id", "QuantConnectBrokerage" },
|
||||
{ "environment", "paper" },
|
||||
{ "user", "" },
|
||||
{ "password", "" },
|
||||
{ "account", "" }
|
||||
};
|
||||
private readonly ProjectFile _defaultFile = new ProjectFile
|
||||
{
|
||||
Name = "Main.cs",
|
||||
Code = File.ReadAllText("../../../Algorithm.CSharp/BasicTemplateAlgorithm.cs")
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Live paper trading via Interactive Brokers
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LiveIBTest()
|
||||
{
|
||||
var user = Config.Get("ib-user-name");
|
||||
var password = Config.Get("ib-password");
|
||||
var account = Config.Get("ib-account");
|
||||
var environment = account.Substring(0, 2) == "DU" ? "paper" : "live";
|
||||
var ib_weekly_restart_utc_time = Config.Get("ib-weekly-restart-utc-time");
|
||||
|
||||
// Create default algorithm settings
|
||||
var settings = new Dictionary<string, object>() {
|
||||
{ "id", "InteractiveBrokersBrokerage" },
|
||||
{ "ib-trading-mode", environment },
|
||||
{ "ib-user-name", user },
|
||||
{ "ib-password", password },
|
||||
{ "ib-account", account },
|
||||
{ "ib-weekly-restart-utc-time", ib_weekly_restart_utc_time},
|
||||
{ "holdings", new List<Holding>() },
|
||||
{ "cash", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "currency" , "USD"},
|
||||
{ "amount", 100000}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var quantConnectDataProvider = new Dictionary<string, object>
|
||||
{
|
||||
{ "id", "QuantConnectBrokerage" },
|
||||
};
|
||||
|
||||
var dataProviders = new Dictionary<string, object>
|
||||
{
|
||||
{ "QuantConnectBrokerage", quantConnectDataProvider },
|
||||
{ "InteractiveBrokersBrokerage", settings }
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(settings, _defaultFile, StopLiveAlgos, dataProviders);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PolygonTest()
|
||||
{
|
||||
var apiKey = Config.Get("polygon-api-key");
|
||||
var polygonDataProvider = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "Polygon" },
|
||||
{ "polygon-api-key", apiKey },
|
||||
};
|
||||
|
||||
var dataProviders = new Dictionary<string, object>
|
||||
{
|
||||
{ "Polygon", polygonDataProvider },
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(_defaultSettings, _defaultFile, StopLiveAlgos, dataProviders);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BinanceTest()
|
||||
{
|
||||
var apiSecret = Config.Get("binance-api-secret");
|
||||
var apiKey = Config.Get("binance-api-key");
|
||||
var apiUrl = Config.Get("binance-api-url");
|
||||
var websocketUrl = Config.Get("binance-websocket-url");
|
||||
var binanceSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "BinanceBrokerage" },
|
||||
{ "binance-use-testnet", "paper" },
|
||||
{ "binance-exchange-name", "Binance" },
|
||||
{ "binance-api-secret", apiSecret },
|
||||
{ "binance-api-key", apiKey },
|
||||
{ "binance-api-url", apiUrl },
|
||||
{ "binance-websocket-url", websocketUrl },
|
||||
{ "holdings", new List<Holding>() },
|
||||
{ "cash", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "currency" , "USD"},
|
||||
{ "amount", 100000}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var dataProviders = new Dictionary<string, object>
|
||||
{
|
||||
{ "BinanceBrokerage", binanceSettings },
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(binanceSettings, _defaultFile, StopLiveAlgos, dataProviders);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BinanceUSTest()
|
||||
{
|
||||
var apiSecret = Config.Get("binanceus-api-secret");
|
||||
var apiKey = Config.Get("binanceus-api-key");
|
||||
var apiUrl = Config.Get("binanceus-api-url");
|
||||
var websocketUrl = Config.Get("binanceus-websocket-url");
|
||||
var binanceUSSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "BinanceBrokerage" },
|
||||
{ "binance-exchange-name", "BinanceUS" },
|
||||
{ "binanceus-api-secret", apiSecret },
|
||||
{ "binanceus-api-key", apiKey },
|
||||
{ "binanceus-api-url", apiUrl },
|
||||
{ "binanceus-websocket-url", websocketUrl },
|
||||
{ "holdings", new List<Holding>() },
|
||||
{ "cash", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "currency" , "USD"},
|
||||
{ "amount", 100000}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var dataProviders = new Dictionary<string, object>
|
||||
{
|
||||
{ "BinanceBrokerage", binanceUSSettings },
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(binanceUSSettings, _defaultFile, StopLiveAlgos, dataProviders);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BinanceFuturesUSDMTest()
|
||||
{
|
||||
var apiSecret = Config.Get("binance-api-secret");
|
||||
var apiKey = Config.Get("binance-api-key");
|
||||
var apiUrl = Config.Get("binance-fapi-url");
|
||||
var websocketUrl = Config.Get("binance-fwebsocket-url");
|
||||
var binanceSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "BinanceBrokerage" },
|
||||
{ "binance-exchange-name", "Binance-USDM-Futures" },
|
||||
{ "binance-api-secret", apiSecret },
|
||||
{ "binance-api-key", apiKey },
|
||||
{ "binance-fapi-url", apiUrl },
|
||||
{ "binance-fwebsocket-url", websocketUrl },
|
||||
{ "holdings", new List<Holding>() },
|
||||
{ "cash", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "currency" , "USD"},
|
||||
{ "amount", 100000}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var dataProviders = new Dictionary<string, object>
|
||||
{
|
||||
{ "BinanceBrokerage", binanceSettings },
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(binanceSettings, _defaultFile, StopLiveAlgos, dataProviders);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BinanceFuturesCOINTest()
|
||||
{
|
||||
var apiSecret = Config.Get("binance-api-secret");
|
||||
var apiKey = Config.Get("binance-api-key");
|
||||
var apiUrl = Config.Get("binance-dapi-url");
|
||||
var websocketUrl = Config.Get("binance-dwebsocket-url");
|
||||
var binanceSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "BinanceBrokerage" },
|
||||
{ "binance-exchange-name", "Binance-COIN-Futures" },
|
||||
{ "binance-api-secret", apiSecret },
|
||||
{ "binance-api-key", apiKey },
|
||||
{ "binance-dapi-url", apiUrl },
|
||||
{ "binance-dwebsocket-url", websocketUrl },
|
||||
{ "holdings", new List<Holding>() },
|
||||
{ "cash", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "currency" , "USD"},
|
||||
{ "amount", 100000}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var dataProviders = new Dictionary<string, object>
|
||||
{
|
||||
{ "BinanceBrokerage", binanceSettings },
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(binanceSettings, _defaultFile, StopLiveAlgos, dataProviders);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Live paper trading via FXCM
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LiveFXCMTest()
|
||||
{
|
||||
var user = Config.Get("fxcm-user-name");
|
||||
var password = Config.Get("fxcm-password");
|
||||
var account = Config.Get("fxcm-account-id");
|
||||
|
||||
// Create default algorithm settings
|
||||
var settings = new Dictionary<string, object>() {
|
||||
{ "id", "FxcmBrokerage" },
|
||||
{ "environment", "paper" },
|
||||
{ "user", user },
|
||||
{ "password", password },
|
||||
{ "account", account }
|
||||
};
|
||||
|
||||
var file = new ProjectFile
|
||||
{
|
||||
Name = "Main.cs",
|
||||
Code = File.ReadAllText("../../../Algorithm.CSharp/BasicTemplateForexAlgorithm.cs")
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(settings, file, StopLiveAlgos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Live paper trading via Oanda
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LiveOandaTest()
|
||||
{
|
||||
var token = Config.Get("oanda-access-token");
|
||||
var account = Config.Get("oanda-account-id");
|
||||
var environment = Config.Get("oanda-environment");
|
||||
|
||||
// Create default algorithm settings
|
||||
var oandaSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "OandaBrokerage" },
|
||||
{ "oanda-access-token", token },
|
||||
{ "oanda-account-id", account },
|
||||
{ "oanda-environment", environment }
|
||||
};
|
||||
|
||||
var dataProvider = new Dictionary<string, object>
|
||||
{
|
||||
{ "OandaBrokerage", oandaSettings }
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(_defaultSettings, _defaultFile, StopLiveAlgos, dataProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Live paper trading via Tradier
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LiveTradierTest()
|
||||
{
|
||||
var refreshToken = Config.Get("tradier-refresh-token");
|
||||
var account = Config.Get("tradier-account-id");
|
||||
var accessToken = Config.Get("tradier-access-token");
|
||||
var dateIssued = Config.Get("tradier-issued-at");
|
||||
|
||||
// Create default algorithm settings
|
||||
var tradierSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "TradierBrokerage" },
|
||||
{ "tradier-account-id", account },
|
||||
{ "tradier-access-token", accessToken },
|
||||
{ "tradier-environment", "paper" },
|
||||
{ "holdings", new List<Holding>() },
|
||||
{ "cash", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "currency" , "USD"},
|
||||
{ "amount", 100000}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var dataProvider = new Dictionary<string, object>()
|
||||
{
|
||||
{ "TradierBrokerage", tradierSettings}
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(tradierSettings, _defaultFile, StopLiveAlgos, dataProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Live trading via Bitfinex
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LiveBitfinexTest()
|
||||
{
|
||||
var key = Config.Get("bitfinex-api-key");
|
||||
var secretKey = Config.Get("bitfinex-api-secret");
|
||||
|
||||
// Create default algorithm settings
|
||||
var bitfinexSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "BitfinexBrokerage" },
|
||||
{ "bitfinex-api-secret", secretKey },
|
||||
{ "bitfinex-api-key", key },
|
||||
{ "holdings", new List<Holding>() },
|
||||
{ "cash", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "currency" , "USD"},
|
||||
{ "amount", 100000}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var dataProvider = new Dictionary<string, object>()
|
||||
{
|
||||
{ "BitfinexBrokerage", bitfinexSettings }
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(bitfinexSettings, _defaultFile, StopLiveAlgos, dataProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Live trading via Coinbase
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LiveCoinbaseTest()
|
||||
{
|
||||
var key = Config.Get("coinbase-api-key");
|
||||
var secretKey = Config.Get("coinbase-api-secret");
|
||||
var apiUrl = Config.Get("coinbase-rest-api");
|
||||
var wsUrl = Config.Get("coinbase-url");
|
||||
|
||||
// Create default algorithm settings
|
||||
var coinbaseSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "CoinbaseBrokerage" },
|
||||
{ "coinbase-api-key", key },
|
||||
{ "coinbase-api-secret", secretKey },
|
||||
{ "coinbase-rest-api", apiUrl },
|
||||
{ "coinbase-url", wsUrl },
|
||||
{ "holdings", new List<Holding>() },
|
||||
{ "cash", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "currency" , "USD"},
|
||||
{ "amount", 100000}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var dataProvider = new Dictionary<string, object>
|
||||
{
|
||||
{ "CoinbaseBrokerage", coinbaseSettings }
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(coinbaseSettings, _defaultFile, StopLiveAlgos, dataProvider);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void KrakenTest()
|
||||
{
|
||||
var krakenSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "KrakenBrokerage" },
|
||||
{ "kraken-api-key", Config.Get("kraken-api-key") },
|
||||
{ "kraken-api-secret", Config.Get("kraken-api-secret") },
|
||||
{ "kraken-verification-tier", Config.Get("kraken-verification-tier") },
|
||||
{ "holdings", new List<Holding>() },
|
||||
{ "cash", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "currency" , "USD"},
|
||||
{ "amount", 100000}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var dataProvider = new Dictionary<string, object>
|
||||
{
|
||||
{ "KrakenBrokerage", krakenSettings }
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(krakenSettings, _defaultFile, StopLiveAlgos, dataProvider);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BybitTest()
|
||||
{
|
||||
var bybitSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "BybitBrokerage" },
|
||||
{ "bybit-api-key", Config.Get("bybit-api-key") },
|
||||
{ "bybit-api-secret", Config.Get("bybit-api-secret") },
|
||||
{ "bybit-api-url", Config.Get("bybit-api-url") },
|
||||
{ "bybit-websocket-url", Config.Get("bybit-websocket-url") },
|
||||
{ "bybit-use-testnet", "paper" },
|
||||
{ "bybit-vip-level", "VIP0" },
|
||||
{ "holdings", new List<Holding>() },
|
||||
{ "cash", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "currency" , "USD"},
|
||||
{ "amount", 100000}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var dataProvider = new Dictionary<string, object>
|
||||
{
|
||||
{ "BybitBrokerage", bybitSettings }
|
||||
};
|
||||
|
||||
RunLiveAlgorithm(bybitSettings, _defaultFile, StopLiveAlgos, dataProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test creating the settings object that provide the necessary parameters for each broker
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LiveAlgorithmSettings_CanBeCreated_Successfully()
|
||||
{
|
||||
var user = "";
|
||||
var password = "";
|
||||
var environment = "paper";
|
||||
var account = "";
|
||||
var key = "";
|
||||
var secretKey = "";
|
||||
|
||||
// Oanda Custom Variables
|
||||
var accessToken = "";
|
||||
|
||||
// Tradier Custom Variables
|
||||
var dateIssued = "";
|
||||
var refreshToken = "";
|
||||
|
||||
// Create and test settings for each brokerage
|
||||
foreach (BrokerageName brokerageName in Enum.GetValues(typeof(BrokerageName)))
|
||||
{
|
||||
Dictionary<string, string> settings = null;
|
||||
|
||||
switch (brokerageName)
|
||||
{
|
||||
case BrokerageName.Default:
|
||||
user = Config.Get("default-username");
|
||||
password = Config.Get("default-password");
|
||||
settings = new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", BrokerageName.Default.ToString() },
|
||||
{ "environment", environment },
|
||||
{ "user", user },
|
||||
{ "password", password },
|
||||
{ "account", account }
|
||||
};
|
||||
|
||||
Assert.IsTrue(settings["id"] == BrokerageName.Default.ToString());
|
||||
break;
|
||||
case BrokerageName.FxcmBrokerage:
|
||||
user = Config.Get("fxcm-user-name");
|
||||
password = Config.Get("fxcm-password");
|
||||
settings = new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", BrokerageName.FxcmBrokerage.ToString() },
|
||||
{ "environment", environment },
|
||||
{ "user", user },
|
||||
{ "password", password },
|
||||
{ "account", account }
|
||||
};
|
||||
|
||||
Assert.IsTrue(settings["id"] == BrokerageName.FxcmBrokerage.ToString());
|
||||
break;
|
||||
case BrokerageName.InteractiveBrokersBrokerage:
|
||||
user = Config.Get("ib-user-name");
|
||||
password = Config.Get("ib-password");
|
||||
account = Config.Get("ib-account");
|
||||
settings = new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", BrokerageName.InteractiveBrokersBrokerage.ToString() },
|
||||
{ "environment", account.Substring(0, 2) == "DU" ? "paper" : "live" },
|
||||
{ "user", user },
|
||||
{ "password", password },
|
||||
{ "acount", account }
|
||||
};
|
||||
|
||||
Assert.IsTrue(settings["id"] == BrokerageName.InteractiveBrokersBrokerage.ToString());
|
||||
break;
|
||||
case BrokerageName.OandaBrokerage:
|
||||
accessToken = Config.Get("oanda-access-token");
|
||||
account = Config.Get("oanda-account-id");
|
||||
|
||||
settings = new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", BrokerageName.OandaBrokerage.ToStringInvariant() },
|
||||
{ "user", "" },
|
||||
{ "password", "" },
|
||||
{ "environment", environment },
|
||||
{ "account", account },
|
||||
{ "accessToken", accessToken },
|
||||
{ "dateIssued", "1" }
|
||||
};
|
||||
Assert.IsTrue(settings["id"] == BrokerageName.OandaBrokerage.ToString());
|
||||
break;
|
||||
case BrokerageName.TradierBrokerage:
|
||||
dateIssued = Config.Get("tradier-issued-at");
|
||||
refreshToken = Config.Get("tradier-refresh-token");
|
||||
account = Config.Get("tradier-account-id");
|
||||
|
||||
settings = new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", BrokerageName.TradierBrokerage.ToString() },
|
||||
{ "user", "" },
|
||||
{ "password", "" },
|
||||
{ "environment", "live" },
|
||||
{ "accessToken", accessToken },
|
||||
{ "dateIssued", dateIssued },
|
||||
{ "refreshToken", refreshToken },
|
||||
{ "lifetime", "86399" },
|
||||
{ "account", account }
|
||||
};
|
||||
break;
|
||||
case BrokerageName.Bitfinex:
|
||||
key = Config.Get("bitfinex-api-key");
|
||||
secretKey = Config.Get("bitfinex-api-secret");
|
||||
|
||||
settings = new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", "BitfinexBrokerage" },
|
||||
{ "user", "" },
|
||||
{ "password", "" },
|
||||
{ "environment", "live" },
|
||||
{ "key", key },
|
||||
{ "secret", secretKey },
|
||||
};
|
||||
break;
|
||||
case BrokerageName.GDAX:
|
||||
case BrokerageName.Coinbase:
|
||||
key = Config.Get("coinbase-api-key");
|
||||
secretKey = Config.Get("coinbase-api-secret");
|
||||
var apiUrl = Config.Get("coinbase-rest-api");
|
||||
var wsUrl = Config.Get("coinbase-url");
|
||||
|
||||
settings = new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", "CoinbaseBrokerage"},
|
||||
{ "user", "" },
|
||||
{ "password", "" },
|
||||
{ "environment", "live" },
|
||||
{ "key", key },
|
||||
{ "secret", secretKey },
|
||||
{ "apiUrl", (new Uri(apiUrl)).AbsoluteUri },
|
||||
{ "wsUrl", (new Uri(wsUrl)).AbsoluteUri }
|
||||
};
|
||||
break;
|
||||
case BrokerageName.AlphaStreams:
|
||||
// No live algorithm settings
|
||||
settings = new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", "" },
|
||||
{ "user", "" },
|
||||
{ "password", "" },
|
||||
{ "account", "" }
|
||||
};
|
||||
break;
|
||||
case BrokerageName.Binance:
|
||||
// No live algorithm settings
|
||||
settings = new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", "" },
|
||||
{ "user", "" },
|
||||
{ "password", "" },
|
||||
{ "account", "" }
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Settings have not been implemented for this brokerage: {brokerageName}");
|
||||
}
|
||||
|
||||
// Tests common to all brokerage configuration classes
|
||||
Assert.IsTrue(settings != null);
|
||||
Assert.IsTrue(settings["password"] == password);
|
||||
Assert.IsTrue(settings["user"] == user);
|
||||
|
||||
// Oanda specific settings
|
||||
if (brokerageName == BrokerageName.OandaBrokerage)
|
||||
{
|
||||
var oandaSetting = settings;
|
||||
Assert.IsTrue(oandaSetting["accessToken"] == accessToken);
|
||||
}
|
||||
|
||||
// Tradier specific settings
|
||||
if (brokerageName == BrokerageName.TradierBrokerage)
|
||||
{
|
||||
var tradierLiveAlogrithmSettings = settings;
|
||||
|
||||
Assert.IsTrue(tradierLiveAlogrithmSettings["dateIssued"] == dateIssued);
|
||||
Assert.IsTrue(tradierLiveAlogrithmSettings["refreshToken"] == refreshToken);
|
||||
Assert.IsTrue(settings["environment"] == "life");
|
||||
}
|
||||
|
||||
// reset variables
|
||||
user = "";
|
||||
password = "";
|
||||
environment = "paper";
|
||||
account = "";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reading live algorithm tests
|
||||
/// - Get a list of live algorithms
|
||||
/// - Get logs for the first algorithm returned
|
||||
/// Will there always be a live algorithm for the test user?
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LiveAlgorithmsAndLiveLogs_CanBeRead_Successfully()
|
||||
{
|
||||
// Read all currently running algorithms
|
||||
var liveAlgorithms = ApiClient.ListLiveAlgorithms(AlgorithmStatus.Running);
|
||||
var stringRepresentation = liveAlgorithms.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
|
||||
Assert.IsTrue(liveAlgorithms.Success);
|
||||
// There has to be at least one running algorithm
|
||||
Assert.IsTrue(liveAlgorithms.Algorithms.Any());
|
||||
|
||||
// Read the logs of the first live algorithm
|
||||
var firstLiveAlgo = liveAlgorithms.Algorithms[0];
|
||||
var liveLogs = ApiClient.ReadLiveLogs(firstLiveAlgo.ProjectId, firstLiveAlgo.DeployId, 0, 20);
|
||||
stringRepresentation = liveLogs.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
|
||||
Assert.IsTrue(liveLogs.Success);
|
||||
Assert.IsTrue(liveLogs.Logs.Any());
|
||||
Assert.IsTrue(liveLogs.Length >= 0);
|
||||
Assert.IsTrue(liveLogs.DeploymentOffset >= 0);
|
||||
|
||||
Assert.Throws<ArgumentException>(() => ApiClient.ReadLiveLogs(firstLiveAlgo.ProjectId, firstLiveAlgo.DeployId, 0, 251));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the algorithm with the given settings and file
|
||||
/// </summary>
|
||||
/// <param name="settings">Settings for Lean</param>
|
||||
/// <param name="file">File to run</param>
|
||||
/// <param name="stopLiveAlgos">If true the algorithm will be stopped at the end of the method.
|
||||
/// Otherwise, it will keep running</param>
|
||||
/// <param name="dataProviders">Dictionary with the data providers and their corresponding credentials</param>
|
||||
/// <returns>The id of the project created with the algorithm in</returns>
|
||||
private int RunLiveAlgorithm(Dictionary<string, object> settings, ProjectFile file, bool stopLiveAlgos, Dictionary<string, object> dataProviders = null)
|
||||
{
|
||||
return RunLiveAlgorithm(ApiClient, settings, file, stopLiveAlgos, dataProviders);
|
||||
}
|
||||
|
||||
internal static int RunLiveAlgorithm(Api.Api apiClient, Dictionary<string, object> settings, ProjectFile file, bool stopLiveAlgos,
|
||||
Dictionary<string, object> dataProviders = null, Language language = Language.CSharp)
|
||||
{
|
||||
// Create a new project
|
||||
var project = apiClient.CreateProject($"Test project - {DateTime.Now.ToStringInvariant()}", language, Globals.OrganizationID);
|
||||
var projectId = project.Projects.First().ProjectId;
|
||||
|
||||
// Update Project Files
|
||||
var updateProjectFileContent = apiClient.UpdateProjectFileContent(projectId, language == Language.CSharp ? "Main.cs" : "main.py", file.Code);
|
||||
Assert.IsTrue(updateProjectFileContent.Success);
|
||||
|
||||
// Create compile
|
||||
var compile = apiClient.CreateCompile(projectId);
|
||||
Assert.IsTrue(compile.Success);
|
||||
|
||||
// Wait at max 30 seconds for project to compile
|
||||
var compileCheck = WaitForCompilerResponse(apiClient, projectId, compile.CompileId, 30);
|
||||
Assert.IsTrue(compileCheck.Success);
|
||||
Assert.IsTrue(compileCheck.State == CompileState.BuildSuccess);
|
||||
|
||||
// Get a live node to launch the algorithm on
|
||||
var nodesResponse = apiClient.ReadProjectNodes(projectId);
|
||||
Assert.IsTrue(nodesResponse.Success);
|
||||
var freeNode = nodesResponse.Nodes.LiveNodes.Where(x => x.Busy == false);
|
||||
Assert.IsNotEmpty(freeNode, "No free Live Nodes found");
|
||||
|
||||
// Create live default algorithm
|
||||
var createLiveAlgorithm = apiClient.CreateLiveAlgorithm(projectId, compile.CompileId, freeNode.FirstOrDefault().Id, settings, dataProviders: dataProviders);
|
||||
Assert.IsTrue(createLiveAlgorithm.Success);
|
||||
|
||||
if (stopLiveAlgos)
|
||||
{
|
||||
// Liquidate live algorithm; will also stop algorithm
|
||||
var liquidateLive = apiClient.LiquidateLiveAlgorithm(projectId);
|
||||
Assert.IsTrue(liquidateLive.Success);
|
||||
|
||||
// Delete the project
|
||||
var deleteProject = apiClient.DeleteProject(projectId);
|
||||
Assert.IsTrue(deleteProject.Success);
|
||||
}
|
||||
|
||||
return projectId;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadLiveOrders()
|
||||
{
|
||||
// Create default algorithm settings
|
||||
var settings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "QuantConnectBrokerage" },
|
||||
{ "environment", "paper" },
|
||||
{ "user", "" },
|
||||
{ "password", "" },
|
||||
{ "account", "" }
|
||||
};
|
||||
|
||||
var file = new ProjectFile
|
||||
{
|
||||
Name = "Main.cs",
|
||||
Code = File.ReadAllText("../../../Algorithm.CSharp/BasicTemplateAlgorithm.cs")
|
||||
};
|
||||
|
||||
// Run the live algorithm
|
||||
var projectId = RunLiveAlgorithm(settings, file, false);
|
||||
|
||||
// Wait to receive the orders
|
||||
var readLiveOrders = WaitForReadLiveOrdersResponse(projectId, 60 * 5);
|
||||
Assert.IsTrue(readLiveOrders.Any());
|
||||
Assert.AreEqual(Symbols.SPY, readLiveOrders.First().Symbol);
|
||||
|
||||
// Liquidate live algorithm; will also stop algorithm
|
||||
var liquidateLive = ApiClient.LiquidateLiveAlgorithm(projectId);
|
||||
Assert.IsTrue(liquidateLive.Success);
|
||||
|
||||
// Delete the project
|
||||
var deleteProject = ApiClient.DeleteProject(projectId);
|
||||
Assert.IsTrue(deleteProject.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RunLiveAlgorithmsFromPython()
|
||||
{
|
||||
var file = new ProjectFile
|
||||
{
|
||||
Name = "Main.cs",
|
||||
Code = File.ReadAllText("../../../Algorithm.CSharp/BasicTemplateAlgorithm.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
|
||||
var compile = ApiClient.CreateCompile(projectId);
|
||||
Assert.IsTrue(compile.Success);
|
||||
|
||||
// Wait at max 30 seconds for project to compile
|
||||
var compileCheck = WaitForCompilerResponse(ApiClient, projectId, compile.CompileId, 30);
|
||||
Assert.IsTrue(compileCheck.Success);
|
||||
Assert.IsTrue(compileCheck.State == CompileState.BuildSuccess);
|
||||
|
||||
// Get a live node to launch the algorithm on
|
||||
var nodesResponse = ApiClient.ReadProjectNodes(projectId);
|
||||
Assert.IsTrue(nodesResponse.Success);
|
||||
var freeNode = nodesResponse.Nodes.LiveNodes.Where(x => x.Busy == false);
|
||||
Assert.IsNotEmpty(freeNode, "No free Live Nodes found");
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
|
||||
dynamic pythonCall = PyModule.FromString("testModule",
|
||||
@"
|
||||
from AlgorithmImports import *
|
||||
|
||||
def CreateLiveAlgorithmFromPython(apiClient, projectId, compileId, nodeId):
|
||||
user = Config.Get('ib-user-name')
|
||||
password = Config.Get('ib-password')
|
||||
account = Config.Get('ib-account')
|
||||
environment = 'paper'
|
||||
if account[:2] == 'DU':
|
||||
environment = 'live'
|
||||
ib_weekly_restart_utc_time = '22:00:00'
|
||||
settings = {'id':'InteractiveBrokersBrokerage', 'environment': environment, 'ib-user-name': user, 'ib-password': password, 'ib-account': account, 'ib-weekly-restart-utc-time': ib_weekly_restart_utc_time, 'holdings':[], 'cash': [{'currency' : 'USD', 'amount' : 100000}]}
|
||||
dataProviders = {'QuantConnectBrokerage':{'id':'QuantConnectBrokerage'}}
|
||||
apiClient.CreateLiveAlgorithm(projectId, compileId, nodeId, settings, dataProviders = dataProviders)
|
||||
");
|
||||
var createLiveAlgorithmModule = pythonCall.GetAttr("CreateLiveAlgorithmFromPython");
|
||||
var createLiveAlgorithm = createLiveAlgorithmModule(ApiClient, projectId, compile.CompileId, freeNode.FirstOrDefault().Id);
|
||||
Assert.IsTrue(createLiveAlgorithm.Success);
|
||||
|
||||
// Liquidate live algorithm; will also stop algorithm
|
||||
var liquidateLive = ApiClient.LiquidateLiveAlgorithm(projectId);
|
||||
Assert.IsTrue(liquidateLive.Success);
|
||||
|
||||
// Delete the project
|
||||
var deleteProject = ApiClient.DeleteProject(projectId);
|
||||
Assert.IsTrue(deleteProject.Success);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait to receive at least one order
|
||||
/// </summary>
|
||||
/// <param name="projectId">Id of the project</param>
|
||||
/// <param name="seconds">Seconds to allow for receive an order</param>
|
||||
/// <returns></returns>
|
||||
private List<ApiOrderResponse> WaitForReadLiveOrdersResponse(int projectId, int seconds)
|
||||
{
|
||||
var readLiveOrders = new List<ApiOrderResponse>();
|
||||
var finish = DateTime.UtcNow.AddSeconds(seconds);
|
||||
while (DateTime.UtcNow < finish && !readLiveOrders.Any())
|
||||
{
|
||||
Thread.Sleep(10000);
|
||||
readLiveOrders = ApiClient.ReadLiveOrders(projectId);
|
||||
}
|
||||
return readLiveOrders;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using QuantConnect.Api;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
[TestFixture, Explicit("Requires configured api access and available backtest node to run on"), Parallelizable(ParallelScope.Fixtures)]
|
||||
public class ObjectStoreTests : ApiTestBase
|
||||
{
|
||||
private const string _ciTestFolder = "/CI_TEST";
|
||||
private const string _key = _ciTestFolder + "/Ricardo";
|
||||
private readonly byte[] _data = new byte[3] { 1, 2, 3 };
|
||||
|
||||
private static readonly string[] _keysToSetUp = new[]
|
||||
{
|
||||
"/filename.zip",
|
||||
"/mm_test.csv",
|
||||
"/model",
|
||||
"/trades_test.json",
|
||||
"/profile_results.json",
|
||||
"/CustomData/placeholder.txt",
|
||||
"/log.txt",
|
||||
"/l1_model.p",
|
||||
"/latency_1_False.txt",
|
||||
"/portfolio-targets2.csv",
|
||||
"/Regressor",
|
||||
"/example_data_2.zip"
|
||||
};
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void SetUpObjectStoreFiles()
|
||||
{
|
||||
foreach (var key in _keysToSetUp)
|
||||
{
|
||||
var fullKey = _ciTestFolder + key;
|
||||
if (!ApiClient.GetObjectStoreProperties(TestOrganization, fullKey).Success)
|
||||
{
|
||||
ApiClient.SetObjectStore(TestOrganization, fullKey, _data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetObjectStoreWorksAsExpectedTestCases))]
|
||||
public void GetObjectStoreWorksAsExpected(string testName, List<string> keys, bool isSuccessExpected)
|
||||
{
|
||||
var path = Directory.GetCurrentDirectory() + "/StoreObjectFolder/";
|
||||
var result = ApiClient.GetObjectStore(TestOrganization, keys, path);
|
||||
if (isSuccessExpected)
|
||||
{
|
||||
Assert.IsTrue(result);
|
||||
DirectoryAssert.Exists(path);
|
||||
Assert.IsTrue(keys.Where(x => File.Exists(path + x) || Directory.Exists(path + x)).Any()); // For some test cases, just one of the keys is present in the Object Store.
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(_ciTestFolder + "/filename.zip", true)]
|
||||
[TestCase(_ciTestFolder + "/orats_2024-02-32.json", false)]
|
||||
[TestCase(_ciTestFolder + "/mrm8488", false)]
|
||||
[TestCase(_ciTestFolder + "/mm_test.csv", true)]
|
||||
[TestCase(_ciTestFolder + "/model", true)]
|
||||
[TestCase(_ciTestFolder + "/trades_test.json", true)]
|
||||
public void GetObjectStorePropertiesWorksAsExpected(string key, bool isSuccessExpected)
|
||||
{
|
||||
var result = ApiClient.GetObjectStoreProperties(TestOrganization, key);
|
||||
var stringRepresentation = result.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
if (isSuccessExpected)
|
||||
{
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsFalse(result.Success);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetObjectStoreWorksAsExpected()
|
||||
{
|
||||
var result = ApiClient.DeleteObjectStore(TestOrganization, _key);
|
||||
Assert.IsFalse(result.Success);
|
||||
|
||||
result = ApiClient.SetObjectStore(TestOrganization, _key, _data);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
result = ApiClient.DeleteObjectStore(TestOrganization, _key);
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeleteObjectStoreWorksAsExpected()
|
||||
{
|
||||
var result = ApiClient.SetObjectStore(TestOrganization, _key + "/test1.txt", _data);
|
||||
var result2 = ApiClient.SetObjectStore(TestOrganization, _key + "/test2.txt", _data);
|
||||
Assert.IsTrue(result.Success);
|
||||
var objectsBefore = ApiClient.ListObjectStore(TestOrganization, _key);
|
||||
var stringRepresentation = objectsBefore.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
var numberOfObjectsBefore = objectsBefore.Objects.Count;
|
||||
var totalSizeBefore = objectsBefore.Objects.Select(o => o.Size).Sum();
|
||||
|
||||
result = ApiClient.DeleteObjectStore(TestOrganization, _key + "/test1.txt");
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
ListObjectStoreResponse objectsAfter;
|
||||
var time = DateTime.UtcNow;
|
||||
do
|
||||
{
|
||||
objectsAfter = ApiClient.ListObjectStore(TestOrganization, _key);
|
||||
} while (objectsAfter.Objects.Count == numberOfObjectsBefore && DateTime.UtcNow < time.AddMinutes(10));
|
||||
|
||||
var totalSizeAfter = objectsAfter.Objects.Select(o => o.Size).Sum();
|
||||
Assert.IsTrue(totalSizeAfter < totalSizeBefore);
|
||||
|
||||
result = ApiClient.DeleteObjectStore(TestOrganization, _key);
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ListObjectStoreWorksAsExpected()
|
||||
{
|
||||
var path = "/";
|
||||
|
||||
var result = ApiClient.ListObjectStore(TestOrganization, path);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.IsNotEmpty(result.Objects);
|
||||
Assert.AreEqual(path, result.Path);
|
||||
}
|
||||
|
||||
private static object[] GetObjectStoreWorksAsExpectedTestCases =
|
||||
{
|
||||
new object[] { "Two keys present", new List<string> { _ciTestFolder + "/trades_test.json", _ciTestFolder + "/profile_results.json" }, true},
|
||||
new object[] { "No key is given", new List<string> {}, false},
|
||||
new object[] { "One key is present and the other one not", new List<string> { _ciTestFolder + "/trades_test.json", _ciTestFolder + "/orats_2024-02-32.json" }, true},
|
||||
new object[] { "The key is not present", new List<string> { _ciTestFolder + "/orats_2024-02-32.json" }, false},
|
||||
new object[] { "The type of the object store file is directory", new List<string> { _ciTestFolder + "/CustomData" }, true},
|
||||
new object[] { "The type of the object store file is text/plain", new List<string> { _ciTestFolder + "/log.txt" }, true},
|
||||
new object[] { "The type of the object store file is application/octet-stream", new List<string> { _ciTestFolder + "/model" }, true},
|
||||
new object[] { "The type of the object store file is P", new List<string> { _ciTestFolder + "/l1_model.p" }, true},
|
||||
new object[] { "Heavy object store files", new List<string> {
|
||||
_ciTestFolder + "/latency_1_False.txt",
|
||||
_ciTestFolder + "/portfolio-targets2.csv",
|
||||
_ciTestFolder + "/Regressor",
|
||||
_ciTestFolder + "/example_data_2.zip"
|
||||
}, true}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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 Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Api;
|
||||
using QuantConnect.Optimizer.Parameters;
|
||||
using QuantConnect.Statistics;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
[TestFixture]
|
||||
public class OptimizationBacktestJsonConverterTests
|
||||
{
|
||||
private const string _validSerialization = "{\"name\":\"ImABacktestName\",\"id\":\"backtestId\",\"progress\":0.0,\"exitCode\":0," +
|
||||
"\"startDate\":\"2023-01-01T00:00:00Z\",\"endDate\":\"2024-01-01T00:00:00Z\",\"outOfSampleMaxEndDate\":\"2024-01-01T00:00:00Z\",\"outOfSampleDays\":10,\"statistics\":{\"0\":0.374,\"1\":0.217,\"2\":0.047,\"3\":-4.51,\"4\":2.86,\"5\":-0.664,\"6\":52.602,\"7\":17.800,\"8\":6300000.00,\"9\":0.196,\"10\":1.571,\"11\":27.0,\"12\":123.888,\"13\":77.188,\"14\":0.63,\"15\":1.707,\"16\":1390.49,\"17\":180.0,\"18\":0.233,\"19\":-0.558,\"20\":73.0,\"21\":0.1,\"22\":100000.0,\"23\":200000.0,\"24\":3.0}," +
|
||||
"\"parameterSet\":{\"pinocho\":\"19\",\"pepe\":\"-1\"},\"equity\":[[1,1.0,1.0,1.0,1.0],[2,2.0,2.0,2.0,2.0],[3,3.0,3.0,3.0,3.0]]}";
|
||||
private const string _oldValidSerialization = "{\"name\":\"ImABacktestName\",\"id\":\"backtestId\",\"progress\":0.0,\"exitCode\":0," +
|
||||
"\"statistics\":{\"0\":0.374,\"1\":0.217,\"2\":0.047,\"3\":-4.51,\"4\":2.86,\"5\":-0.664,\"6\":52.602,\"7\":17.800,\"8\":6300000.00,\"9\":0.196,\"10\":1.571,\"11\":27.0,\"12\":123.888,\"13\":77.188,\"14\":0.63,\"15\":1.707,\"16\":1390.49,\"17\":180.0,\"18\":0.233,\"19\":-0.558,\"20\":73.0,\"21\":0.1,\"22\":100000.0,\"23\":200000.0,\"24\":3.0}," +
|
||||
"\"parameterSet\":{\"pinocho\":\"19\",\"pepe\":\"-1\"},\"equity\":[[1,1.0,1.0,1.0,1.0],[2,2.0,2.0,2.0,2.0],[3,3.0,3.0,3.0,3.0]]}";
|
||||
private const string _oldValid2Serialization = "{\"name\":\"ImABacktestName\",\"id\":\"backtestId\",\"progress\":0.0,\"exitCode\":0," +
|
||||
"\"statistics\":{\"0\":0.374,\"1\":0.217,\"2\":0.047,\"3\":-4.51,\"4\":2.86,\"5\":-0.664,\"6\":52.602,\"7\":17.800,\"8\":6300000.00,\"9\":0.196,\"10\":1.571,\"11\":27.0,\"12\":123.888,\"13\":77.188,\"14\":0.63,\"15\":1.707,\"16\":1390.49,\"17\":180.0,\"18\":0.233,\"19\":-0.558,\"20\":73.0,\"21\":0.1,\"22\":100000.0,\"23\":200000.0,\"24\":3.0}," +
|
||||
"\"parameterSet\":{\"pinocho\":\"19\",\"pepe\":\"-1\"},\"equity\":[[1,1.0],[2,2.0],[3,3.0]]}";
|
||||
private const string _oldValid3Serialization = "{\"name\":\"ImABacktestName\",\"id\":\"backtestId\",\"progress\":0.0,\"exitCode\":0," +
|
||||
"\"statistics\":{\"0\":0.374,\"1\":0.217,\"2\":0.047,\"3\":-4.51,\"4\":2.86,\"5\":-0.664,\"6\":52.602,\"7\":17.800,\"8\":6300000.00,\"9\":0.196,\"10\":1.571,\"11\":27.0,\"12\":123.888,\"13\":77.188,\"14\":0.63,\"15\":1.707,\"16\":1390.49,\"17\":180.0,\"18\":0.233,\"19\":-0.558,\"20\":73.0}," +
|
||||
"\"parameterSet\":{\"pinocho\":\"19\",\"pepe\":\"-1\"},\"equity\":[[1,1.0],[2,2.0],[3,3.0]]}";
|
||||
|
||||
private const string _validSerializationWithCustomStats = "{\"name\":\"ImABacktestName\",\"id\":\"backtestId\",\"progress\":0.0,\"exitCode\":0," +
|
||||
"\"startDate\":\"2023-01-01T00:00:00Z\",\"endDate\":\"2024-01-01T00:00:00Z\",\"outOfSampleMaxEndDate\":\"2024-01-01T00:00:00Z\",\"outOfSampleDays\":10,\"statistics\":{\"0\":0.374,\"1\":0.217,\"2\":0.047,\"3\":-4.51,\"4\":2.86,\"5\":-0.664,\"6\":52.602,\"7\":17.800,\"8\":6300000.00,\"9\":0.196,\"10\":1.571,\"11\":27.0,\"12\":123.888,\"13\":77.188,\"14\":0.63,\"15\":1.707,\"16\":1390.49,\"17\":180.0,\"18\":0.233,\"19\":-0.558,\"20\":73.0,\"21\":0.1,\"22\":100000.0,\"23\":200000.0,\"24\":3.0,\"customstat2\":5.4321,\"customstat1\":1.2345}," +
|
||||
"\"parameterSet\":{\"pinocho\":\"19\",\"pepe\":\"-1\"},\"equity\":[[1,1.0,1.0,1.0,1.0],[2,2.0,2.0,2.0,2.0],[3,3.0,3.0,3.0,3.0]]}";
|
||||
|
||||
private const string _validOldStatsDeserialization = "{\"name\":\"ImABacktestName\",\"id\":\"backtestId\",\"progress\":0.0,\"exitCode\":0," +
|
||||
"\"startDate\":\"2023-01-01T00:00:00Z\",\"endDate\":\"2024-01-01T00:00:00Z\",\"outOfSampleMaxEndDate\":\"2024-01-01T00:00:00Z\",\"outOfSampleDays\":10,\"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\":{\"pinocho\":\"19\",\"pepe\":\"-1\"},\"equity\":[[1,1.0,1.0,1.0,1.0],[2,2.0,2.0,2.0,2.0],[3,3.0,3.0,3.0,3.0]]}";
|
||||
private const string _validOldStatsDeserialization2 = "{\"name\":\"ImABacktestName\",\"id\":\"backtestId\",\"progress\":0.0,\"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\":{\"pinocho\":\"19\",\"pepe\":\"-1\"},\"equity\":[[1,1.0,1.0,1.0,1.0],[2,2.0,2.0,2.0,2.0],[3,3.0,3.0,3.0,3.0]]}";
|
||||
private const string _validOldStatsDeserialization3 = "{\"name\":\"ImABacktestName\",\"id\":\"backtestId\",\"progress\":0.0,\"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\":{\"pinocho\":\"19\",\"pepe\":\"-1\"},\"equity\":[[1,1.0],[2,2.0],[3,3.0]]}";
|
||||
private const string _validOldStatsDeserializationWithLessStats = "{\"name\":\"ImABacktestName\",\"id\":\"backtestId\",\"progress\":0.0,\"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]," +
|
||||
"\"parameterSet\":{\"pinocho\":\"19\",\"pepe\":\"-1\"},\"equity\":[[1,1.0],[2,2.0],[3,3.0]]}";
|
||||
|
||||
[Test]
|
||||
public void SerializationNulls()
|
||||
{
|
||||
var optimizationBacktest = new OptimizationBacktest(null, null, null);
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(optimizationBacktest);
|
||||
Assert.AreEqual("{}", serialized);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(0)]
|
||||
public void Serialization(int version)
|
||||
{
|
||||
var optimizationBacktest = new OptimizationBacktest(new ParameterSet(18,
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
{ "pinocho", "19" },
|
||||
{ "pepe", "-1" }
|
||||
}), "backtestId", "ImABacktestName");
|
||||
|
||||
optimizationBacktest.Statistics = new Dictionary<string, string>
|
||||
{
|
||||
{ "Total Orders", "180" },
|
||||
{ "Average Win", "2.86%" },
|
||||
{ "Average Loss", "-4.51%" },
|
||||
{ "Compounding Annual Return", "52.602%" },
|
||||
{ "Drawdown", "17.800%" },
|
||||
{ "Expectancy", "0.196" },
|
||||
{ "Start Equity", "100000" },
|
||||
{ "End Equity", "200000" },
|
||||
{ "Net Profit", "123.888%" },
|
||||
{ "Sharpe Ratio", "1.707" },
|
||||
{ "Sortino Ratio", "0.1" },
|
||||
{ "Probabilistic Sharpe Ratio", "77.188%" },
|
||||
{ "Loss Rate", "27%" },
|
||||
{ "Win Rate", "73%" },
|
||||
{ "Profit-Loss Ratio", "0.63" },
|
||||
{ "Alpha", "0.374" },
|
||||
{ "Beta", "-0.664" },
|
||||
{ "Annual Standard Deviation", "0.217" },
|
||||
{ "Annual Variance", "0.047" },
|
||||
{ "Information Ratio", "1.571" },
|
||||
{ "Tracking Error", "0.233" },
|
||||
{ "Treynor Ratio", "-0.558" },
|
||||
{ "Total Fees", "$1390.49" },
|
||||
{ "Estimated Strategy Capacity", "ZRX6300000.00" },
|
||||
{ "Drawdown Recovery", "3" }
|
||||
};
|
||||
|
||||
optimizationBacktest.Equity = new CandlestickSeries
|
||||
{
|
||||
Values = new List<ISeriesPoint> { new Candlestick(1, 1, 1, 1, 1), new Candlestick(2, 2, 2, 2, 2), new Candlestick(3, 3, 3, 3, 3) }
|
||||
};
|
||||
if (version > 0)
|
||||
{
|
||||
optimizationBacktest.StartDate = new DateTime(2023, 01, 01);
|
||||
optimizationBacktest.EndDate = new DateTime(2024, 01, 01);
|
||||
optimizationBacktest.OutOfSampleMaxEndDate = new DateTime(2024, 01, 01);
|
||||
optimizationBacktest.OutOfSampleDays = 10;
|
||||
}
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(optimizationBacktest);
|
||||
|
||||
var expected = _validSerialization;
|
||||
if (version == 0)
|
||||
{
|
||||
expected = _oldValidSerialization;
|
||||
}
|
||||
Assert.AreEqual(expected, serialized);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SerializationWithCustomStatistics()
|
||||
{
|
||||
var optimizationBacktest = new OptimizationBacktest(new ParameterSet(18,
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
{ "pinocho", "19" },
|
||||
{ "pepe", "-1" }
|
||||
}), "backtestId", "ImABacktestName");
|
||||
|
||||
optimizationBacktest.Statistics = new Dictionary<string, string>
|
||||
{
|
||||
{ "customstat2", "5.4321" },
|
||||
{ "customstat1", "1.2345" },
|
||||
{ "Total Orders", "180" },
|
||||
{ "Average Win", "2.86%" },
|
||||
{ "Average Loss", "-4.51%" },
|
||||
{ "Compounding Annual Return", "52.602%" },
|
||||
{ "Drawdown", "17.800%" },
|
||||
{ "Expectancy", "0.196" },
|
||||
{ "Start Equity", "100000" },
|
||||
{ "End Equity", "200000" },
|
||||
{ "Net Profit", "123.888%" },
|
||||
{ "Sharpe Ratio", "1.707" },
|
||||
{ "Sortino Ratio", "0.1" },
|
||||
{ "Probabilistic Sharpe Ratio", "77.188%" },
|
||||
{ "Loss Rate", "27%" },
|
||||
{ "Win Rate", "73%" },
|
||||
{ "Profit-Loss Ratio", "0.63" },
|
||||
{ "Alpha", "0.374" },
|
||||
{ "Beta", "-0.664" },
|
||||
{ "Annual Standard Deviation", "0.217" },
|
||||
{ "Annual Variance", "0.047" },
|
||||
{ "Information Ratio", "1.571" },
|
||||
{ "Tracking Error", "0.233" },
|
||||
{ "Treynor Ratio", "-0.558" },
|
||||
{ "Total Fees", "$1390.49" },
|
||||
{ "Estimated Strategy Capacity", "ZRX6300000.00" },
|
||||
{ "Drawdown Recovery", "3" }
|
||||
};
|
||||
|
||||
optimizationBacktest.Equity = new CandlestickSeries
|
||||
{
|
||||
Values = new List<ISeriesPoint> { new Candlestick(1, 1, 1, 1, 1), new Candlestick(2, 2, 2, 2, 2), new Candlestick(3, 3, 3, 3, 3) }
|
||||
};
|
||||
optimizationBacktest.StartDate = new DateTime(2023, 01, 01);
|
||||
optimizationBacktest.EndDate = new DateTime(2024, 01, 01);
|
||||
optimizationBacktest.OutOfSampleMaxEndDate = new DateTime(2024, 01, 01);
|
||||
optimizationBacktest.OutOfSampleDays = 10;
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(optimizationBacktest);
|
||||
|
||||
Assert.AreEqual(_validSerializationWithCustomStats, serialized);
|
||||
}
|
||||
|
||||
[TestCase(_validSerialization, false, 25)]
|
||||
[TestCase(_oldValidSerialization, false, 25)]
|
||||
[TestCase(_oldValid2Serialization, false, 25)]
|
||||
// This case has only 21 stats because Sortino Ratio, Start Equity, End Equity and Drawdown Recovery were not supported
|
||||
[TestCase(_oldValid3Serialization, false, 21)]
|
||||
[TestCase(_validOldStatsDeserialization, false, 21)]
|
||||
[TestCase(_validOldStatsDeserialization2, false, 21)]
|
||||
[TestCase(_validOldStatsDeserialization3, false, 21)]
|
||||
[TestCase(_validOldStatsDeserializationWithLessStats, false, 15)]
|
||||
[TestCase(_validSerializationWithCustomStats, true, 25)]
|
||||
public void Deserialization(string serialization, bool hasCustomStats, int expectedLeanStats)
|
||||
{
|
||||
var deserialized = JsonConvert.DeserializeObject<OptimizationBacktest>(serialization);
|
||||
Assert.IsNotNull(deserialized);
|
||||
Assert.AreEqual("ImABacktestName", deserialized.Name);
|
||||
Assert.AreEqual("backtestId", deserialized.BacktestId);
|
||||
Assert.AreEqual(0.0m, deserialized.Progress);
|
||||
Assert.AreEqual(0, deserialized.ExitCode);
|
||||
Assert.AreEqual(-1, deserialized.ParameterSet.Id);
|
||||
Assert.IsTrue(deserialized.ParameterSet.Value.Count == 2);
|
||||
Assert.IsTrue(deserialized.ParameterSet.Value["pinocho"] == "19");
|
||||
Assert.IsTrue(deserialized.ParameterSet.Value["pepe"] == "-1");
|
||||
Assert.IsTrue(deserialized.Equity.Values.Count == 3);
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
var expected = i + 1;
|
||||
Assert.IsTrue(((Candlestick)deserialized.Equity.Values[i]).LongTime == expected);
|
||||
Assert.IsTrue(((Candlestick)deserialized.Equity.Values[i]).Open == expected);
|
||||
Assert.IsTrue(((Candlestick)deserialized.Equity.Values[i]).High == expected);
|
||||
Assert.IsTrue(((Candlestick)deserialized.Equity.Values[i]).Low == expected);
|
||||
Assert.IsTrue(((Candlestick)deserialized.Equity.Values[i]).Close == expected);
|
||||
}
|
||||
Assert.AreEqual("77.188", deserialized.Statistics[PerformanceMetrics.ProbabilisticSharpeRatio]);
|
||||
|
||||
if (!hasCustomStats)
|
||||
{
|
||||
Assert.AreEqual(expectedLeanStats, deserialized.Statistics.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There are 2 custom stats
|
||||
Assert.AreEqual(expectedLeanStats + 2, deserialized.Statistics.Count);
|
||||
|
||||
Assert.AreEqual("1.2345", deserialized.Statistics["customstat1"]);
|
||||
Assert.AreEqual("5.4321", deserialized.Statistics["customstat2"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests API account and optimizations endpoints
|
||||
/// </summary>
|
||||
[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<Optimization>(_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<OptimizationStepParameter>();
|
||||
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<StaticOptimizationParameter>();
|
||||
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<Maximization>(deserialized.Criterion.Extremum);
|
||||
Assert.IsNull(deserialized.Criterion.TargetValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EstimateDeserialization()
|
||||
{
|
||||
var deserialized = JsonConvert.DeserializeObject<Estimate>(_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<OptimizationParameter>
|
||||
{
|
||||
new OptimizationStepParameter("ema-fast", 20, 50, 1, 1) // Replace params with valid optimization parameter data for test project
|
||||
},
|
||||
constraints: new List<Constraint>
|
||||
{
|
||||
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<OptimizationParameter>
|
||||
{
|
||||
new OptimizationStepParameter("ema-fast", 20, 50, 1, 1) // Replace params with valid optimization parameter data for test project
|
||||
},
|
||||
constraints: new List<Constraint>
|
||||
{
|
||||
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<OptimizationStatus>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 System;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests API account and organizations endpoints
|
||||
/// </summary>
|
||||
[TestFixture, Explicit("Requires configured api access")]
|
||||
public class OrganizationTests : ApiTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ReadAccount()
|
||||
{
|
||||
var account = ApiClient.ReadAccount();
|
||||
var stringRepresentation = account.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
|
||||
Assert.IsTrue(account.Success);
|
||||
Assert.IsNotEmpty(account.OrganizationId);
|
||||
Assert.IsNotNull(account.Card);
|
||||
Assert.AreNotEqual(default(DateTime), account.Card.Expiration);
|
||||
Assert.IsNotEmpty(account.Card.Brand);
|
||||
Assert.AreNotEqual(0, account.Card.LastFourDigits);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadOrganization()
|
||||
{
|
||||
var organization = ApiClient.ReadOrganization(TestOrganization);
|
||||
var stringRepresentation = organization.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
|
||||
Assert.AreNotEqual(default(DateTime), organization.DataAgreement.Signed);
|
||||
Assert.AreNotEqual(0, organization.DataAgreement.EpochSignedTime);
|
||||
Assert.AreNotEqual(0, organization.Credit.Balance);
|
||||
Assert.AreNotEqual(0, organization.Products.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Optimizer.Parameters;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
[TestFixture]
|
||||
public class ParameterSetJsonConverterTests
|
||||
{
|
||||
private const string ValidSerialization = "{\"parameterSet\":{\"pinocho\":\"19\",\"pepe\":\"-1\"}}";
|
||||
|
||||
[Test]
|
||||
public void SerializationNulls()
|
||||
{
|
||||
var parameterSet = new ParameterSet(0, null);
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(parameterSet);
|
||||
Assert.AreEqual("{}", serialized);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Serialization()
|
||||
{
|
||||
var parameterSet = new ParameterSet(18,
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
{ "pinocho", "19" },
|
||||
{ "pepe", "-1" }
|
||||
});
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(parameterSet);
|
||||
|
||||
Assert.AreEqual(ValidSerialization, serialized);
|
||||
}
|
||||
|
||||
[TestCase("{}", 0)]
|
||||
[TestCase("[]", 0)]
|
||||
[TestCase(ValidSerialization, 2)]
|
||||
public void Deserialization(string validSerialization, int count)
|
||||
{
|
||||
var deserialized = JsonConvert.DeserializeObject<ParameterSet>(validSerialization);
|
||||
Assert.IsNotNull(deserialized);
|
||||
Assert.AreEqual(-1, deserialized.Id);
|
||||
Assert.AreEqual(count, deserialized.Value.Count);
|
||||
if (count != 0)
|
||||
{
|
||||
Assert.IsTrue(deserialized.Value["pinocho"] == "19");
|
||||
Assert.IsTrue(deserialized.Value["pepe"] == "-1");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,851 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Web;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Api;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Optimizer.Parameters;
|
||||
using QuantConnect.Util;
|
||||
using QuantConnect.Optimizer;
|
||||
using QuantConnect.Optimizer.Objectives;
|
||||
using System.Threading;
|
||||
|
||||
namespace QuantConnect.Tests.API
|
||||
{
|
||||
/// <summary>
|
||||
/// API Project endpoints, includes some Backtest endpoints testing as well
|
||||
/// </summary>
|
||||
[TestFixture, Explicit("Requires configured api access and available backtest node to run on"), Parallelizable(ParallelScope.Fixtures)]
|
||||
public class ProjectTests : ApiTestBase
|
||||
{
|
||||
private readonly Dictionary<string, object> _defaultSettings = new Dictionary<string, object>()
|
||||
{
|
||||
{ "id", "QuantConnectBrokerage" },
|
||||
{ "environment", "paper" },
|
||||
{ "cash", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "currency" , "USD"},
|
||||
{ "amount", 300000}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "holdings", new List<Dictionary<object, object>>()
|
||||
{
|
||||
{new Dictionary<object, object>
|
||||
{
|
||||
{ "symbolId" , Symbols.AAPL.ID.ToString()},
|
||||
{ "symbol", Symbols.AAPL.Value},
|
||||
{ "quantity", 1 },
|
||||
{ "averagePrice", 1}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void ReadProject()
|
||||
{
|
||||
var readProject = ApiClient.ReadProject(TestProject.ProjectId);
|
||||
Assert.IsTrue(readProject.Success);
|
||||
Assert.AreEqual(1, readProject.Projects.Count);
|
||||
|
||||
var project = readProject.Projects[0];
|
||||
Assert.AreNotEqual(0, project.OwnerId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test creating and deleting projects with the Api
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Projects_CanBeCreatedAndDeleted_Successfully()
|
||||
{
|
||||
var name = $"TestProject{GetTimestamp()}";
|
||||
|
||||
//Test create a new project successfully
|
||||
var project = ApiClient.CreateProject(name, Language.CSharp, TestOrganization);
|
||||
var stringRepresentation = project.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(project.Success);
|
||||
Assert.Greater(project.Projects.First().ProjectId, 0);
|
||||
Assert.AreEqual(name, project.Projects.First().Name);
|
||||
|
||||
// Delete the project
|
||||
var deleteProject = ApiClient.DeleteProject(project.Projects.First().ProjectId);
|
||||
Assert.IsTrue(deleteProject.Success);
|
||||
|
||||
// Make sure the project is really deleted
|
||||
// The API soft deletes projects (moves them to "Recycle Bin/..."), so exclude those
|
||||
var projectList = ApiClient.ListProjects();
|
||||
Assert.IsFalse(projectList.Projects
|
||||
.Where(p => !p.Name.StartsWith("Recycle Bin/", StringComparison.Ordinal))
|
||||
.Any(p => p.ProjectId == project.Projects.First().ProjectId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test updating the files associated with a project
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CRUD_ProjectFiles_Successfully()
|
||||
{
|
||||
var fakeFile = new ProjectFile
|
||||
{
|
||||
Name = "Hello.cs",
|
||||
Code = HttpUtility.HtmlEncode("Hello, world!")
|
||||
};
|
||||
|
||||
var realFile = new ProjectFile
|
||||
{
|
||||
Name = "main.cs",
|
||||
Code = HttpUtility.HtmlEncode(File.ReadAllText("../../../Algorithm.CSharp/BasicTemplateAlgorithm.cs"))
|
||||
};
|
||||
|
||||
var secondRealFile = new ProjectFile()
|
||||
{
|
||||
Name = "algorithm.cs",
|
||||
Code = HttpUtility.HtmlEncode(File.ReadAllText("../../../Algorithm.CSharp/BubbleAlgorithm.cs"))
|
||||
};
|
||||
|
||||
// Add random file
|
||||
var randomAdd = ApiClient.AddProjectFile(TestProject.ProjectId, fakeFile.Name, fakeFile.Code);
|
||||
var stringRepresentation = randomAdd.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(randomAdd.Success);
|
||||
// Update names of file
|
||||
var updatedName = ApiClient.UpdateProjectFileName(TestProject.ProjectId, fakeFile.Name, realFile.Name);
|
||||
Assert.IsTrue(updatedName.Success);
|
||||
|
||||
// Replace content of file
|
||||
var updateContents = ApiClient.UpdateProjectFileContent(TestProject.ProjectId, realFile.Name, realFile.Code);
|
||||
Assert.IsTrue(updateContents.Success);
|
||||
|
||||
// Read single file
|
||||
var readFile = ApiClient.ReadProjectFile(TestProject.ProjectId, realFile.Name);
|
||||
stringRepresentation = readFile.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(readFile.Success);
|
||||
Assert.IsTrue(readFile.Files.First().Code == realFile.Code);
|
||||
Assert.IsTrue(readFile.Files.First().Name == realFile.Name);
|
||||
|
||||
// Add a second file
|
||||
var secondFile = ApiClient.AddProjectFile(TestProject.ProjectId, secondRealFile.Name, secondRealFile.Code);
|
||||
Assert.IsTrue(secondFile.Success);
|
||||
|
||||
// Read multiple files
|
||||
var readFiles = ApiClient.ReadProjectFiles(TestProject.ProjectId);
|
||||
Assert.IsTrue(readFiles.Success);
|
||||
Assert.IsTrue(readFiles.Files.Count == 4); // 2 Added + 2 Automatic (Research.ipynb & Main.cs)
|
||||
|
||||
// Delete the second file
|
||||
var deleteFile = ApiClient.DeleteProjectFile(TestProject.ProjectId, secondRealFile.Name);
|
||||
Assert.IsTrue(deleteFile.Success);
|
||||
|
||||
// Read files
|
||||
var readFilesAgain = ApiClient.ReadProjectFiles(TestProject.ProjectId);
|
||||
Assert.IsTrue(readFilesAgain.Success);
|
||||
Assert.IsTrue(readFilesAgain.Files.Count == 3);
|
||||
Assert.IsTrue(readFilesAgain.Files.Any(x => x.Name == realFile.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test updating the nodes associated with a project
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RU_ProjectNodes_Successfully()
|
||||
{
|
||||
// Read the nodes
|
||||
var nodesResponse = ApiClient.ReadProjectNodes(TestProject.ProjectId);
|
||||
var stringRepresentation = nodesResponse.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(nodesResponse.Success);
|
||||
Assert.Greater(nodesResponse.Nodes.BacktestNodes.Count, 0);
|
||||
|
||||
// Save reference node
|
||||
var node = nodesResponse.Nodes.BacktestNodes.First();
|
||||
var nodeId = node.Id;
|
||||
var active = node.Active;
|
||||
|
||||
// If the node is active, deactivate it. Otherwise, set active to true
|
||||
var nodes = node.Active ? Array.Empty<string>() : new[] { nodeId };
|
||||
|
||||
// Update the nodes
|
||||
nodesResponse = ApiClient.UpdateProjectNodes(TestProject.ProjectId, nodes);
|
||||
Assert.IsTrue(nodesResponse.Success);
|
||||
|
||||
// Node has a new active state
|
||||
node = nodesResponse.Nodes.BacktestNodes.First(x => x.Id == nodeId);
|
||||
Assert.AreNotEqual(active, node.Active);
|
||||
|
||||
// Set it back to previous state
|
||||
nodes = node.Active ? Array.Empty<string>() : new[] { nodeId };
|
||||
|
||||
nodesResponse = ApiClient.UpdateProjectNodes(TestProject.ProjectId, nodes);
|
||||
Assert.IsTrue(nodesResponse.Success);
|
||||
|
||||
// Node has a new active state
|
||||
node = nodesResponse.Nodes.BacktestNodes.First(x => x.Id == nodeId);
|
||||
Assert.AreEqual(active, node.Active);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test creating, compiling and backtesting a C# project via the Api
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CSharpProject_CreatedCompiledAndBacktested_Successully()
|
||||
{
|
||||
var language = Language.CSharp;
|
||||
var code = File.ReadAllText("../../../Algorithm.CSharp/BasicTemplateAlgorithm.cs");
|
||||
var algorithmName = "Main.cs";
|
||||
var projectName = $"{GetTimestamp()} Test {TestAccount} Lang {language}";
|
||||
|
||||
Perform_CreateCompileBackTest_Tests(projectName, language, algorithmName, code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test creating, compiling and backtesting a Python project via the Api
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void PythonProject_CreatedCompiledAndBacktested_Successully()
|
||||
{
|
||||
var language = Language.Python;
|
||||
var code = File.ReadAllText("../../../Algorithm.Python/BasicTemplateAlgorithm.py");
|
||||
var algorithmName = "main.py";
|
||||
|
||||
var projectName = $"{GetTimestamp()} Test {TestAccount} Lang {language}";
|
||||
|
||||
Perform_CreateCompileBackTest_Tests(projectName, language, algorithmName, code);
|
||||
}
|
||||
|
||||
private void Perform_CreateCompileBackTest_Tests(string projectName, Language language, string algorithmName, string code, string expectedStatus = "Completed.")
|
||||
{
|
||||
//Test create a new project successfully
|
||||
var project = ApiClient.CreateProject(projectName, language, TestOrganization);
|
||||
Assert.IsTrue(project.Success);
|
||||
Assert.Greater(project.Projects.First().ProjectId, 0);
|
||||
Assert.AreEqual(projectName, project.Projects.First().Name);
|
||||
|
||||
// Make sure the project just created is now present
|
||||
var projects = ApiClient.ListProjects();
|
||||
Assert.IsTrue(projects.Success);
|
||||
Assert.IsTrue(projects.Projects.Any(p => p.ProjectId == project.Projects.First().ProjectId));
|
||||
|
||||
// Test read back the project we just created
|
||||
var readProject = ApiClient.ReadProject(project.Projects.First().ProjectId);
|
||||
Assert.IsTrue(readProject.Success);
|
||||
Assert.AreEqual(projectName, readProject.Projects.First().Name);
|
||||
|
||||
// Test change project file name and content
|
||||
var file = new ProjectFile { Name = algorithmName, Code = code };
|
||||
var updateProjectFileContent = ApiClient.UpdateProjectFileContent(project.Projects.First().ProjectId, file.Name, file.Code);
|
||||
Assert.IsTrue(updateProjectFileContent.Success);
|
||||
|
||||
// Download the project again to validate its got the new file
|
||||
var verifyRead = ApiClient.ReadProject(project.Projects.First().ProjectId);
|
||||
Assert.IsTrue(verifyRead.Success);
|
||||
|
||||
// Compile the project we've created
|
||||
var compileCreate = ApiClient.CreateCompile(project.Projects.First().ProjectId);
|
||||
Assert.IsTrue(compileCreate.Success);
|
||||
Assert.AreEqual(CompileState.InQueue, compileCreate.State);
|
||||
|
||||
// Read out the compile
|
||||
var compileSuccess = WaitForCompilerResponse(ApiClient, project.Projects.First().ProjectId, compileCreate.CompileId);
|
||||
Assert.IsTrue(compileSuccess.Success);
|
||||
Assert.AreEqual(CompileState.BuildSuccess, compileSuccess.State);
|
||||
|
||||
// Update the file, create a build error, test we get build error
|
||||
file.Code += "[Jibberish at end of the file to cause a build error]";
|
||||
ApiClient.UpdateProjectFileContent(project.Projects.First().ProjectId, file.Name, file.Code);
|
||||
var compileError = ApiClient.CreateCompile(project.Projects.First().ProjectId);
|
||||
compileError = WaitForCompilerResponse(ApiClient, project.Projects.First().ProjectId, compileError.CompileId);
|
||||
Assert.IsTrue(compileError.Success); // Successfully processed rest request.
|
||||
Assert.AreEqual(CompileState.BuildError, compileError.State); //Resulting in build fail.
|
||||
|
||||
// Using our successful compile; launch a backtest!
|
||||
var backtestName = $"{DateTime.UtcNow.ToStringInvariant("yyyy-MM-dd HH-mm-ss")} API Backtest";
|
||||
var backtest = ApiClient.CreateBacktest(project.Projects.First().ProjectId, compileSuccess.CompileId, backtestName);
|
||||
Assert.IsTrue(backtest.Success);
|
||||
|
||||
// Now read the backtest and wait for it to complete
|
||||
var backtestRead = WaitForBacktestCompletion(ApiClient, project.Projects.First().ProjectId, backtest.BacktestId, secondsTimeout: 600, returnFailedBacktest: true);
|
||||
Assert.IsTrue(backtestRead.Success);
|
||||
|
||||
// Backtest completed, let's wait a second to allow status update
|
||||
backtestRead = ApiClient.ReadBacktest(project.Projects.First().ProjectId, backtestRead.BacktestId);
|
||||
Assert.AreEqual(expectedStatus, backtestRead.Status);
|
||||
|
||||
if (expectedStatus == "Runtime Error")
|
||||
{
|
||||
Assert.IsTrue(backtestRead.Error.Contains("Intentional Failure", StringComparison.InvariantCulture) || backtestRead.HasInitializeError);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(1, backtestRead.Progress);
|
||||
Assert.AreEqual(backtestName, backtestRead.Name);
|
||||
Assert.AreEqual("1", backtestRead.Statistics["Total Orders"]);
|
||||
Assert.Greater(backtestRead.Charts["Benchmark"].Series.Count, 0);
|
||||
|
||||
// In the same way, read the orders returned in the backtest
|
||||
var backtestOrdersRead = ApiClient.ReadBacktestOrders(project.Projects.First().ProjectId, backtest.BacktestId, 0, 1);
|
||||
Assert.IsTrue(backtestOrdersRead.Any());
|
||||
Assert.AreEqual(Symbols.SPY.Value, backtestOrdersRead.First().Symbol.Value);
|
||||
|
||||
// Verify we have the backtest in our project
|
||||
var listBacktests = ApiClient.ListBacktests(project.Projects.First().ProjectId);
|
||||
Assert.IsTrue(listBacktests.Success);
|
||||
Assert.GreaterOrEqual(listBacktests.Backtests.Count, 1);
|
||||
Assert.AreEqual(backtestName, listBacktests.Backtests[0].Name);
|
||||
|
||||
// Update the backtest name and test its been updated
|
||||
backtestName += "-Amendment";
|
||||
var renameBacktest = ApiClient.UpdateBacktest(project.Projects.First().ProjectId, backtest.BacktestId, backtestName);
|
||||
Assert.IsTrue(renameBacktest.Success);
|
||||
backtestRead = ApiClient.ReadBacktest(project.Projects.First().ProjectId, backtest.BacktestId);
|
||||
Assert.AreEqual(backtestName, backtestRead.Name);
|
||||
|
||||
//Update the note and make sure its been updated:
|
||||
var newNote = DateTime.Now.ToStringInvariant("yyyy-MM-dd HH-mm-ss");
|
||||
var noteBacktest = ApiClient.UpdateBacktest(project.Projects.First().ProjectId, backtest.BacktestId, note: newNote);
|
||||
Assert.IsTrue(noteBacktest.Success);
|
||||
backtestRead = ApiClient.ReadBacktest(project.Projects.First().ProjectId, backtest.BacktestId);
|
||||
Assert.AreEqual(newNote, backtestRead.Note);
|
||||
}
|
||||
|
||||
// Delete the backtest we just created
|
||||
var deleteBacktest = ApiClient.DeleteBacktest(project.Projects.First().ProjectId, backtest.BacktestId);
|
||||
Assert.IsTrue(deleteBacktest.Success);
|
||||
|
||||
// Test delete the project we just created
|
||||
var deleteProject = ApiClient.DeleteProject(project.Projects.First().ProjectId);
|
||||
Assert.IsTrue(deleteProject.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadBacktestOrdersReportAndChart()
|
||||
{
|
||||
// Project settings
|
||||
var language = Language.CSharp;
|
||||
var code = File.ReadAllText("../../../Algorithm.CSharp/BasicTemplateAlgorithm.cs");
|
||||
var algorithmName = "Main.cs";
|
||||
var projectName = $"{GetTimestamp()} Test {TestAccount} Lang {language}";
|
||||
|
||||
// Create a default project
|
||||
var projectResult = ApiClient.CreateProject(projectName, language, TestOrganization);
|
||||
Assert.IsTrue(projectResult.Success, $"Error creating project:\n {string.Join("\n ", projectResult.Errors)}");
|
||||
var project = projectResult.Projects.First();
|
||||
|
||||
var file = new ProjectFile { Name = algorithmName, Code = code };
|
||||
var updateProjectFileContent = ApiClient.UpdateProjectFileContent(project.ProjectId, file.Name, file.Code);
|
||||
Assert.IsTrue(updateProjectFileContent.Success,
|
||||
$"Error updating project file:\n {string.Join("\n ", updateProjectFileContent.Errors)}");
|
||||
|
||||
var compileCreate = ApiClient.CreateCompile(project.ProjectId);
|
||||
var compileSuccess = WaitForCompilerResponse(ApiClient, project.ProjectId, compileCreate.CompileId);
|
||||
Assert.IsTrue(compileSuccess.Success, $"Error compiling project:\n {string.Join("\n ", compileSuccess.Errors)}");
|
||||
|
||||
var backtestName = $"ReadBacktestOrders Backtest {GetTimestamp()}";
|
||||
var backtest = ApiClient.CreateBacktest(project.ProjectId, compileSuccess.CompileId, backtestName);
|
||||
|
||||
// Read ongoing backtest
|
||||
var backtestRead = ApiClient.ReadBacktest(project.ProjectId, backtest.BacktestId);
|
||||
Assert.IsTrue(backtestRead.Success);
|
||||
|
||||
// Now wait until the backtest is completed and request the orders again
|
||||
backtestRead = WaitForBacktestCompletion(ApiClient, project.ProjectId, backtest.BacktestId);
|
||||
var backtestOrdersRead = ApiClient.ReadBacktestOrders(project.ProjectId, backtest.BacktestId);
|
||||
string stringRepresentation;
|
||||
foreach (var backtestOrder in backtestOrdersRead)
|
||||
{
|
||||
stringRepresentation = backtestOrder.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
}
|
||||
Assert.IsTrue(backtestOrdersRead.Any());
|
||||
Assert.AreEqual(Symbols.SPY.Value, backtestOrdersRead.First().Symbol.Value);
|
||||
|
||||
var readBacktestReport = ApiClient.ReadBacktestReport(project.ProjectId, backtest.BacktestId);
|
||||
stringRepresentation = readBacktestReport.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(readBacktestReport.Success);
|
||||
Assert.IsFalse(string.IsNullOrEmpty(readBacktestReport.Report));
|
||||
|
||||
var readBacktestChart = ApiClient.ReadBacktestChart(
|
||||
project.ProjectId, "Strategy Equity",
|
||||
new DateTime(2013, 10, 07).Second,
|
||||
new DateTime(2013, 10, 11).Second,
|
||||
1000,
|
||||
backtest.BacktestId);
|
||||
stringRepresentation = readBacktestChart.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(readBacktestChart.Success);
|
||||
Assert.IsNotNull(readBacktestChart.Chart);
|
||||
|
||||
// Delete the backtest we just created
|
||||
var deleteBacktest = ApiClient.DeleteBacktest(project.ProjectId, backtest.BacktestId);
|
||||
Assert.IsTrue(deleteBacktest.Success);
|
||||
|
||||
// Delete the project we just created
|
||||
var deleteProject = ApiClient.DeleteProject(project.ProjectId);
|
||||
Assert.IsTrue(deleteProject.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateBacktestName()
|
||||
{
|
||||
// We will be using the existing TestBacktest for this test
|
||||
var originalName = TestBacktest.Name;
|
||||
var newName = $"{originalName} - Amended - {DateTime.UtcNow.ToStringInvariant("yyyy-MM-dd HH-mm-ss")}";
|
||||
|
||||
// Update the backtest name
|
||||
var updateResult = ApiClient.UpdateBacktest(TestProject.ProjectId, TestBacktest.BacktestId, name: newName);
|
||||
Assert.IsTrue(updateResult.Success, $"Error updating backtest name:\n {string.Join("\n ", updateResult.Errors)}");
|
||||
|
||||
// Read the backtest and verify the name has been updated
|
||||
var readResult = ApiClient.ReadBacktest(TestProject.ProjectId, TestBacktest.BacktestId);
|
||||
Assert.IsTrue(readResult.Success, $"Error reading backtest:\n {string.Join("\n ", readResult.Errors)}");
|
||||
Assert.AreEqual(newName, readResult.Name);
|
||||
|
||||
// Revert the name back to the original
|
||||
updateResult = ApiClient.UpdateBacktest(TestProject.ProjectId, TestBacktest.BacktestId, name: originalName);
|
||||
Assert.IsTrue(updateResult.Success, $"Error updating backtest name:\n {string.Join("\n ", updateResult.Errors)}");
|
||||
|
||||
// Read the backtest and verify the name has been updated
|
||||
readResult = ApiClient.ReadBacktest(TestProject.ProjectId, TestBacktest.BacktestId);
|
||||
Assert.IsTrue(readResult.Success, $"Error reading backtest:\n {string.Join("\n ", readResult.Errors)}");
|
||||
Assert.AreEqual(originalName, readResult.Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadLiveInsightsWorksAsExpected()
|
||||
{
|
||||
var quantConnectDataProvider = new Dictionary<string, object>
|
||||
{
|
||||
{ "id", "QuantConnectBrokerage" },
|
||||
};
|
||||
|
||||
var dataProviders = new Dictionary<string, object>
|
||||
{
|
||||
{ "QuantConnectBrokerage", quantConnectDataProvider }
|
||||
};
|
||||
|
||||
GetProjectAndCompileIdToReadInsights(out var projectId, out var compileId);
|
||||
|
||||
// Get a live node to launch the algorithm on
|
||||
var nodesResponse = ApiClient.ReadProjectNodes(projectId);
|
||||
Assert.IsTrue(nodesResponse.Success);
|
||||
var freeNode = nodesResponse.Nodes.LiveNodes.Where(x => x.Busy == false);
|
||||
Assert.IsNotEmpty(freeNode, "No free Live Nodes found");
|
||||
|
||||
try
|
||||
{
|
||||
// Create live default algorithm
|
||||
var createLiveAlgorithm = ApiClient.CreateLiveAlgorithm(projectId, compileId, freeNode.FirstOrDefault().Id, _defaultSettings, dataProviders: dataProviders);
|
||||
Assert.IsTrue(createLiveAlgorithm.Success, $"ApiClient.CreateLiveAlgorithm(): Error: {string.Join(",", createLiveAlgorithm.Errors)}");
|
||||
|
||||
// Wait 2 minutes
|
||||
Thread.Sleep(120000);
|
||||
|
||||
// Stop the algorithm
|
||||
var stopLive = ApiClient.StopLiveAlgorithm(projectId);
|
||||
Assert.IsTrue(stopLive.Success, $"ApiClient.StopLiveAlgorithm(): Error: {string.Join(",", stopLive.Errors)}");
|
||||
|
||||
// Try to read the insights from the algorithm
|
||||
var readInsights = ApiClient.ReadLiveInsights(projectId, 0, 5);
|
||||
var finish = DateTime.UtcNow.AddMinutes(2);
|
||||
do
|
||||
{
|
||||
Thread.Sleep(5000);
|
||||
readInsights = ApiClient.ReadLiveInsights(projectId, 0, 5);
|
||||
}
|
||||
while (finish > DateTime.UtcNow && !readInsights.Insights.Any());
|
||||
|
||||
Assert.IsTrue(readInsights.Success, $"ApiClient.ReadLiveInsights(): Error: {string.Join(",", readInsights.Errors)}");
|
||||
Assert.IsNotEmpty(readInsights.Insights);
|
||||
Assert.IsTrue(readInsights.Length >= 0);
|
||||
Assert.Throws<ArgumentException>(() => ApiClient.ReadLiveInsights(projectId, 0, 101));
|
||||
Assert.DoesNotThrow(() => ApiClient.ReadLiveInsights(projectId));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Delete the project in case of an error
|
||||
Assert.IsTrue(ApiClient.DeleteProject(projectId).Success);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// Delete the project
|
||||
var deleteProject = ApiClient.DeleteProject(projectId);
|
||||
Assert.IsTrue(deleteProject.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdatesBacktestTags()
|
||||
{
|
||||
// We will be using the existing TestBacktest for this test
|
||||
var tags = new List<string> { "tag1", "tag2", "tag3" };
|
||||
|
||||
// Add the tags to the backtest
|
||||
var addTagsResult = ApiClient.UpdateBacktestTags(TestProject.ProjectId, TestBacktest.BacktestId, tags);
|
||||
Assert.IsTrue(addTagsResult.Success, $"Error adding tags to backtest:\n {string.Join("\n ", addTagsResult.Errors)}");
|
||||
|
||||
// Read the backtest and verify the tags were added
|
||||
var backtestsResult = ApiClient.ListBacktests(TestProject.ProjectId);
|
||||
var stringRepresentation = backtestsResult.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(backtestsResult.Success, $"Error getting backtests:\n {string.Join("\n ", backtestsResult.Errors)}");
|
||||
Assert.AreEqual(1, backtestsResult.Backtests.Count);
|
||||
CollectionAssert.AreEquivalent(tags, backtestsResult.Backtests[0].Tags);
|
||||
|
||||
// Remove all tags from the backtest
|
||||
var deleteTagsResult = ApiClient.UpdateBacktestTags(TestProject.ProjectId, TestBacktest.BacktestId, new List<string>());
|
||||
Assert.IsTrue(deleteTagsResult.Success, $"Error deleting tags from backtest:\n {string.Join("\n ", deleteTagsResult.Errors)}");
|
||||
|
||||
// Read the backtest and verify the tags were deleted
|
||||
backtestsResult = ApiClient.ListBacktests(TestProject.ProjectId);
|
||||
Assert.IsTrue(backtestsResult.Success, $"Error getting backtests:\n {string.Join("\n ", backtestsResult.Errors)}");
|
||||
Assert.AreEqual(1, backtestsResult.Backtests.Count);
|
||||
Assert.AreEqual(0, backtestsResult.Backtests[0].Tags.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadBacktestInsightsWorksAsExpected()
|
||||
{
|
||||
GetProjectAndCompileIdToReadInsights(out var projectId, out var compileId);
|
||||
try
|
||||
{
|
||||
// Create backtest
|
||||
var backtestName = $"ReadBacktestOrders Backtest {GetTimestamp()}";
|
||||
var backtest = ApiClient.CreateBacktest(projectId, compileId, backtestName);
|
||||
var stringRepresentation = backtest.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
|
||||
// Try to read the insights from the algorithm
|
||||
var readInsights = ApiClient.ReadBacktestInsights(projectId, backtest.BacktestId, 0, 5);
|
||||
stringRepresentation = readInsights.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
var finish = DateTime.UtcNow.AddMinutes(2);
|
||||
do
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
readInsights = ApiClient.ReadBacktestInsights(projectId, backtest.BacktestId, 0, 5);
|
||||
}
|
||||
while (finish > DateTime.UtcNow && !readInsights.Insights.Any());
|
||||
|
||||
Assert.IsTrue(readInsights.Success, $"ApiClient.ReadBacktestInsights(): Error: {string.Join(",", readInsights.Errors)}");
|
||||
Assert.IsNotEmpty(readInsights.Insights);
|
||||
Assert.IsTrue(readInsights.Length >= 0);
|
||||
Assert.Throws<ArgumentException>(() => ApiClient.ReadBacktestInsights(projectId, backtest.BacktestId, 0, 101));
|
||||
Assert.DoesNotThrow(() => ApiClient.ReadBacktestInsights(projectId, backtest.BacktestId));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Delete the project in case of an error
|
||||
Assert.IsTrue(ApiClient.DeleteProject(projectId).Success);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// Delete the project
|
||||
var deleteProject = ApiClient.DeleteProject(projectId);
|
||||
Assert.IsTrue(deleteProject.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreatesLiveAlgorithm()
|
||||
{
|
||||
var quantConnectDataProvider = new Dictionary<string, object>
|
||||
{
|
||||
{ "id", "QuantConnectBrokerage" },
|
||||
};
|
||||
|
||||
var dataProviders = new Dictionary<string, object>
|
||||
{
|
||||
{ "QuantConnectBrokerage", quantConnectDataProvider }
|
||||
};
|
||||
|
||||
var file = new ProjectFile
|
||||
{
|
||||
Name = "Main.cs",
|
||||
Code = File.ReadAllText("../../../Algorithm.CSharp/BasicTemplateAlgorithm.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
|
||||
var compile = ApiClient.CreateCompile(projectId);
|
||||
var stringRepresentation = compile.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
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);
|
||||
|
||||
// Get a live node to launch the algorithm on
|
||||
var nodesResponse = ApiClient.ReadProjectNodes(projectId);
|
||||
Assert.IsTrue(nodesResponse.Success);
|
||||
var freeNode = nodesResponse.Nodes.LiveNodes.Where(x => x.Busy == false);
|
||||
Assert.IsNotEmpty(freeNode, "No free Live Nodes found");
|
||||
|
||||
try
|
||||
{
|
||||
// Create live default algorithm
|
||||
var createLiveAlgorithm = ApiClient.CreateLiveAlgorithm(projectId, compile.CompileId, freeNode.FirstOrDefault().Id, _defaultSettings, dataProviders: dataProviders);
|
||||
stringRepresentation = createLiveAlgorithm.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(createLiveAlgorithm.Success, $"ApiClient.CreateLiveAlgorithm(): Error: {string.Join(",", createLiveAlgorithm.Errors)}");
|
||||
|
||||
// Read live algorithm
|
||||
var readLiveAlgorithm = ApiClient.ReadLiveAlgorithm(projectId, createLiveAlgorithm.DeployId);
|
||||
stringRepresentation = readLiveAlgorithm.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(readLiveAlgorithm.Success, $"ApiClient.ReadLiveAlgorithm(): Error: {string.Join(",", readLiveAlgorithm.Errors)}");
|
||||
|
||||
// Stop the algorithm
|
||||
var stopLive = ApiClient.StopLiveAlgorithm(projectId);
|
||||
Assert.IsTrue(stopLive.Success, $"ApiClient.StopLiveAlgorithm(): Error: {string.Join(",", stopLive.Errors)}");
|
||||
|
||||
var readChart = ApiClient.ReadLiveChart(projectId, "Strategy Equity", new DateTime(2013, 10, 07).Second, new DateTime(2013, 10, 11).Second, 1000);
|
||||
Assert.IsTrue(readChart.Success, $"ApiClient.ReadLiveChart(): Error: {string.Join(",", readChart.Errors)}");
|
||||
Assert.IsNotNull(readChart.Chart);
|
||||
|
||||
var readLivePortfolio = ApiClient.ReadLivePortfolio(projectId);
|
||||
stringRepresentation = readLivePortfolio.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(readLivePortfolio.Success, $"ApiClient.ReadLivePortfolio(): Error: {string.Join(",", readLivePortfolio.Errors)}");
|
||||
Assert.IsNotNull(readLivePortfolio.Portfolio, "Portfolio was null!");
|
||||
Assert.IsNotNull(readLivePortfolio.Portfolio.Cash, "Portfolio.Cash was null!");
|
||||
Assert.IsNotNull(readLivePortfolio.Portfolio.Holdings, "Portfolio Holdings was null!");
|
||||
|
||||
var readLiveLogs = ApiClient.ReadLiveLogs(projectId, createLiveAlgorithm.DeployId, 0, 20);
|
||||
Assert.IsTrue(readLiveLogs.Success, $"ApiClient.ReadLiveLogs(): Error: {string.Join(",", readLiveLogs.Errors)}");
|
||||
Assert.IsNotNull(readLiveLogs.Logs, "Logs was null!");
|
||||
Assert.IsTrue(readLiveLogs.Length >= 0, "The length of the logs was negative!");
|
||||
Assert.IsTrue(readLiveLogs.DeploymentOffset >= 0, "The deploymentOffset");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Delete the project in case of an error
|
||||
Assert.IsTrue(ApiClient.DeleteProject(projectId).Success);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// Delete the project
|
||||
var deleteProject = ApiClient.DeleteProject(projectId);
|
||||
Assert.IsTrue(deleteProject.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadVersionsWorksAsExpected()
|
||||
{
|
||||
var result = ApiClient.ReadLeanVersions();
|
||||
var stringRepresentation = result.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.IsNotEmpty(result.Versions);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreatesOptimization()
|
||||
{
|
||||
var file = new ProjectFile
|
||||
{
|
||||
Name = "Main.cs",
|
||||
Code = File.ReadAllText("../../../Algorithm.CSharp/ParameterizedAlgorithm.cs")
|
||||
};
|
||||
|
||||
|
||||
// Create a new project
|
||||
var project = ApiClient.CreateProject($"Test project optimization - {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
|
||||
var 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);
|
||||
|
||||
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<OptimizationParameter>
|
||||
{
|
||||
new OptimizationStepParameter("ema-fast", 50, 150, 1, 1) // Replace params with valid optimization parameter data for test project
|
||||
},
|
||||
constraints: new List<Constraint>
|
||||
{
|
||||
new Constraint("TotalPerformance.PortfolioStatistics.SharpeRatio", ComparisonOperatorTypes.GreaterOrEqual, 1)
|
||||
},
|
||||
estimatedCost: 0.06m,
|
||||
nodeType: OptimizationNodes.O2_8,
|
||||
parallelNodes: 12
|
||||
);
|
||||
var stringRepresentation = optimization.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
|
||||
var finish = DateTime.UtcNow.AddMinutes(5);
|
||||
var readOptimization = ApiClient.ReadOptimization(optimization.OptimizationId);
|
||||
do
|
||||
{
|
||||
Thread.Sleep(5000);
|
||||
readOptimization = ApiClient.ReadOptimization(optimization.OptimizationId);
|
||||
}
|
||||
while (finish > DateTime.UtcNow && readOptimization.Status != OptimizationStatus.Completed);
|
||||
stringRepresentation = readOptimization.ToString();
|
||||
Assert.IsTrue(ApiTestBase.IsValidJson(stringRepresentation));
|
||||
|
||||
Assert.IsNotNull(optimization);
|
||||
Assert.IsNotEmpty(optimization.OptimizationId);
|
||||
Assert.AreNotEqual(default(DateTime), optimization.Created);
|
||||
Assert.Positive(optimization.ProjectId);
|
||||
Assert.IsNotEmpty(optimization.Name);
|
||||
Assert.IsInstanceOf<OptimizationStatus>(optimization.Status);
|
||||
Assert.IsNotEmpty(optimization.NodeType);
|
||||
Assert.IsTrue(0 <= optimization.OutOfSampleDays);
|
||||
Assert.AreNotEqual(default(DateTime), optimization.OutOfSampleMaxEndDate);
|
||||
Assert.IsNotNull(optimization.Criterion);
|
||||
|
||||
// Delete the project
|
||||
var deleteProject = ApiClient.DeleteProject(projectId);
|
||||
Assert.IsTrue(deleteProject.Success);
|
||||
}
|
||||
|
||||
private static string GetTimestamp()
|
||||
{
|
||||
return DateTime.UtcNow.ToStringInvariant("yyyyMMddHHmmssfffff");
|
||||
}
|
||||
|
||||
private void GetProjectAndCompileIdToReadInsights(out int projectId, out string compileId)
|
||||
{
|
||||
var file = new ProjectFile
|
||||
{
|
||||
Name = "Main.cs",
|
||||
Code = File.ReadAllText("../../../Algorithm.CSharp/BasicTemplateCryptoFrameworkAlgorithm.cs")
|
||||
};
|
||||
|
||||
// Create a new project
|
||||
var project = ApiClient.CreateProject($"Test project insight - {DateTime.Now.ToStringInvariant()}", Language.CSharp, TestOrganization);
|
||||
projectId = project.Projects.First().ProjectId;
|
||||
|
||||
// Update Project Files
|
||||
var updateProjectFileContent = ApiClient.UpdateProjectFileContent(projectId, "Main.cs", file.Code);
|
||||
Assert.IsTrue(updateProjectFileContent.Success);
|
||||
|
||||
// Create compile
|
||||
var compile = ApiClient.CreateCompile(projectId);
|
||||
Assert.IsTrue(compile.Success);
|
||||
compileId = compile.CompileId;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test creating, compiling and backtesting a failure C# project via the Api
|
||||
/// </summary>
|
||||
[TestCase("Constructor")]
|
||||
[TestCase("Initialize")]
|
||||
[TestCase("OnData")]
|
||||
|
||||
public void CSharpProject_CreatedCompiledAndBacktested_Unsuccessully(string section)
|
||||
{
|
||||
var language = Language.CSharp;
|
||||
var code = File.ReadAllText("../../../Algorithm.CSharp/BasicTemplateAlgorithm.cs");
|
||||
if (section == "Constructor")
|
||||
{
|
||||
code = code.Replace("private Symbol _spy = QuantConnect.Symbol.Create(\"SPY\", SecurityType.Equity, Market.USA);",
|
||||
"private Symbol _spy = QuantConnect.Symbol.Create(\"SPY\", SecurityType.Equity, Market.USA);" +
|
||||
"public BasicTemplateAlgorithm(): base() { throw new RegressionTestException(\"Intentional Failure\"); }", StringComparison.InvariantCulture);
|
||||
}
|
||||
else if (section == "Initialize")
|
||||
{
|
||||
code = code.Replace("SetStartDate(2013, 10, 07);", "throw new RegressionTestException($\"Intentional Failure\");", StringComparison.InvariantCulture);
|
||||
}
|
||||
else if (section == "OnData")
|
||||
{
|
||||
code = code.Replace("Debug(\"Purchased Stock\");", "throw new RegressionTestException($\"Intentional Failure\");", StringComparison.InvariantCulture);
|
||||
}
|
||||
var algorithmName = "Main.cs";
|
||||
var projectName = $"{GetTimestamp()} Test {TestAccount} Lang {language}";
|
||||
|
||||
Perform_CreateCompileBackTest_Tests(projectName, language, algorithmName, code, "Runtime Error");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test creating, compiling and backtesting a failure Python project via the Api
|
||||
/// </summary>
|
||||
[TestCase("Constructor")]
|
||||
[TestCase("Initialize")]
|
||||
[TestCase("OnData")]
|
||||
|
||||
public void PythonProject_CreatedCompiledAndBacktested_Unsuccessully(string section)
|
||||
{
|
||||
var language = Language.Python;
|
||||
var code = File.ReadAllText("../../../Algorithm.Python/BasicTemplateAlgorithm.py");
|
||||
if (section == "Constructor")
|
||||
{
|
||||
code = code.Replace("self.set_start_date(2013,10, 7) #Set Start Date",
|
||||
"self.set_start_date(2013,10, 7) #Set Start Date\n" +
|
||||
" def __init__(self):\r\n super().__init__()\r\n raise Exception(\"Intentional Failure\")", StringComparison.InvariantCulture);
|
||||
}
|
||||
else if (section == "Initialize")
|
||||
{
|
||||
code = code.Replace("self.set_start_date(2013,10, 7) #Set Start Date", "raise Exception(\"Intentional Failure\")", StringComparison.InvariantCulture);
|
||||
}
|
||||
else if (section == "OnData")
|
||||
{
|
||||
code = code.Replace("self.set_holdings(\"SPY\", 1)",
|
||||
"self.set_holdings(\"SPY\", 1)\r\n" +
|
||||
" raise Exception(\"Intentional Failure\")", StringComparison.InvariantCulture);
|
||||
}
|
||||
var algorithmName = "main.py";
|
||||
var projectName = $"{GetTimestamp()} Test {TestAccount} Lang {language}";
|
||||
|
||||
Perform_CreateCompileBackTest_Tests(projectName, language, algorithmName, code, "Runtime Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user