From f291b557c5e4bd4129e24e2d81b42047ea8b24fc Mon Sep 17 00:00:00 2001 From: Martin Molinero Date: Tue, 15 Sep 2026 09:35:35 -0300 Subject: [PATCH] Surface the deployment details in the live read api client LiveAlgorithmResults now carries the deploymentDetails the live/read endpoint reports, deserialized like the runtime and server statistics next to it. The field is optional: deployments running an older Lean, or whose brokerage and data queue handlers share none, report nothing and it stays null. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N792RJ2EcZBDXhbe3KRLLq --- Common/Api/LiveAlgorithmResults.cs | 7 ++ .../Api/LiveAlgorithmResultsJsonConverter.cs | 15 ++-- .../LiveAlgorithmResultsJsonConverterTests.cs | 82 +++++++++++++++++++ 3 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 Tests/Api/LiveAlgorithmResultsJsonConverterTests.cs 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()); + } + } +}