diff --git a/Common/Api/LiveAlgorithmResults.cs b/Common/Api/LiveAlgorithmResults.cs index 49d09914b790..6270f47a0422 100644 --- a/Common/Api/LiveAlgorithmResults.cs +++ b/Common/Api/LiveAlgorithmResults.cs @@ -103,6 +103,13 @@ public class LiveAlgorithmResults : RestResponse /// [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public IDictionary Charts { get; set; } + + /// + /// Deployment details shared by the brokerage, data queue handler or any other component, + /// for example account information. Null when the deployment reported none + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public IDictionary DeploymentDetails { get; set; } } /// diff --git a/Common/Api/LiveAlgorithmResultsJsonConverter.cs b/Common/Api/LiveAlgorithmResultsJsonConverter.cs index bdd6c884c224..be0d8e0746f1 100644 --- a/Common/Api/LiveAlgorithmResultsJsonConverter.cs +++ b/Common/Api/LiveAlgorithmResultsJsonConverter.cs @@ -125,25 +125,26 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist liveAlgoResults.Charts = chartDictionary; liveAlgoResults.Files = projectFiles; - liveAlgoResults.RuntimeStatistics = DeserializeStatistics(jObject, "runtimeStatistics", "RuntimeStatistics"); - liveAlgoResults.ServerStatistics = DeserializeStatistics(jObject, "serverStatistics", "ServerStatistics"); + liveAlgoResults.RuntimeStatistics = DeserializeDictionary(jObject, "runtimeStatistics", "RuntimeStatistics"); + liveAlgoResults.ServerStatistics = DeserializeDictionary(jObject, "serverStatistics", "ServerStatistics"); + liveAlgoResults.DeploymentDetails = DeserializeDictionary(jObject, "deploymentDetails", "DeploymentDetails"); return liveAlgoResults; } /// - /// Deserializes a statistics dictionary, if the given json holds one. Older deployments were + /// Deserializes a string dictionary, if the given json holds one. Older deployments were /// run before some of them were reported, so they are all optional /// - private static IDictionary DeserializeStatistics(JObject jObject, string name, string alternativeName) + private static IDictionary DeserializeDictionary(JObject jObject, string name, string alternativeName) { - var statistics = jObject[name] ?? jObject[alternativeName]; - if (statistics == null || statistics.Type != JTokenType.Object) + var value = jObject[name] ?? jObject[alternativeName]; + if (value == null || value.Type != JTokenType.Object) { return null; } - return statistics.ToObject>(); + return value.ToObject>(); } } } diff --git a/Tests/Api/LiveAlgorithmResultsJsonConverterTests.cs b/Tests/Api/LiveAlgorithmResultsJsonConverterTests.cs new file mode 100644 index 000000000000..03f5b72b1ea1 --- /dev/null +++ b/Tests/Api/LiveAlgorithmResultsJsonConverterTests.cs @@ -0,0 +1,82 @@ +/* + * 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.Collections.Generic; +using Newtonsoft.Json; +using NUnit.Framework; +using QuantConnect.Api; + +namespace QuantConnect.Tests.API +{ + [TestFixture] + public class LiveAlgorithmResultsJsonConverterTests + { + [TestCase("deploymentDetails")] + [TestCase("DeploymentDetails")] + public void DeserializesDeploymentDetails(string name) + { + var result = Deserialize($@"""{name}"": {{ ""Account"": ""U1234567"", ""environment"": ""paper"" }},"); + + CollectionAssert.AreEquivalent( + new Dictionary { { "Account", "U1234567" }, { "environment", "paper" } }, + result.DeploymentDetails); + } + + [Test] + public void DeploymentDetailsAreOptional() + { + // deployments running an older Lean, or whose brokerage shares none, report nothing + var result = Deserialize(); + + Assert.IsNull(result.DeploymentDetails); + // the rest is still deserialized + Assert.AreEqual("DeployId", result.DeployId); + Assert.AreEqual("Running", result.Status); + CollectionAssert.AreEquivalent(new Dictionary { { "Unrealized", "0" } }, result.RuntimeStatistics); + } + + [Test] + public void ServerStatisticsAreStillDeserialized() + { + var result = Deserialize(@"""serverStatistics"": { ""CPU Usage"": ""1%"" },"); + + CollectionAssert.AreEquivalent(new Dictionary { { "CPU Usage", "1%" } }, result.ServerStatistics); + } + + private static LiveAlgorithmResults Deserialize(string extraFields = "") + { + var json = $@"{{ + ""success"": true, + ""message"": """", + ""status"": ""Running"", + ""deployId"": ""DeployId"", + ""cloneId"": 1, + ""launched"": ""2026-09-14T00:00:00Z"", + ""stopped"": null, + ""brokerage"": ""Paper Trading"", + ""securityTypes"": ""Equity"", + ""projectName"": ""ProjectName"", + ""datacenter"": ""Datacenter"", + ""public"": false, + ""files"": [], + ""charts"": {{}}, + {extraFields} + ""runtimeStatistics"": {{ ""Unrealized"": ""0"" }} + }}"; + + return JsonConvert.DeserializeObject(json, new LiveAlgorithmResultsJsonConverter()); + } + } +}