chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
+47
View File
@@ -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);
}
}
}
}
+233
View File
@@ -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();
}
}
}
+106
View File
@@ -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, "", ""));
}
}
}
+51
View File
@@ -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());
}
}
}
+150
View File
@@ -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);
}
}
}
+154
View File
@@ -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);
}
}
}
+894
View File
@@ -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;
}
}
}
+170
View File
@@ -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"]);
}
}
}
}
+304
View File
@@ -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);
}
}
}
+55
View File
@@ -0,0 +1,55 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using 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");
}
}
}
}
+851
View File
@@ -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");
}
}
}