Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Common/Api/LiveAlgorithmResults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ public class LiveAlgorithmResults : RestResponse
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, Chart> Charts { get; set; }

/// <summary>
/// Deployment details shared by the brokerage, data queue handler or any other component,
/// for example account information. Null when the deployment reported none
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IDictionary<string, string> DeploymentDetails { get; set; }
}

/// <summary>
Expand Down
15 changes: 8 additions & 7 deletions Common/Api/LiveAlgorithmResultsJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/// <summary>
/// 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
/// </summary>
private static IDictionary<string, string> DeserializeStatistics(JObject jObject, string name, string alternativeName)
private static IDictionary<string, string> 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<Dictionary<string, string>>();
return value.ToObject<Dictionary<string, string>>();
}
}
}
82 changes: 82 additions & 0 deletions Tests/Api/LiveAlgorithmResultsJsonConverterTests.cs
Original file line number Diff line number Diff line change
@@ -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<string, string> { { "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<string, string> { { "Unrealized", "0" } }, result.RuntimeStatistics);
}

[Test]
public void ServerStatisticsAreStillDeserialized()
{
var result = Deserialize(@"""serverStatistics"": { ""CPU Usage"": ""1%"" },");

CollectionAssert.AreEquivalent(new Dictionary<string, string> { { "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<LiveAlgorithmResults>(json, new LiveAlgorithmResultsJsonConverter());
}
}
}
Loading