From a7c43199a9e64eee4c45d00adb340996a4681ee0 Mon Sep 17 00:00:00 2001 From: Martin Molinero Date: Mon, 14 Sep 2026 12:55:28 -0300 Subject: [PATCH 1/3] Rename the algorithm brokerage data to deployment details Renames the members added in #9788: IAlgorithm.BrokerageData -> DeploymentDetails, SetBrokerageData -> SetDeploymentDetails, IResultHandler.AddBrokerageData -> AddDeploymentDetail and AlgorithmConfiguration.BrokerageData -> DeploymentDetails. The old name collided with LiveNodePacket.BrokerageData and IBrokerageFactory.BrokerageData, which hold the brokerage credentials and are a different thing. The new name is also neutral for entries shared by a data queue handler or any other component, not just the brokerage. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UAELnEhZMd7X9D6rc9CAmC --- Algorithm/QCAlgorithm.cs | 24 ++++----- .../Python/Wrappers/AlgorithmPythonWrapper.cs | 10 ++-- Common/AlgorithmConfiguration.cs | 10 ++-- Common/Interfaces/IAlgorithm.cs | 10 ++-- Engine/Engine.cs | 4 +- Engine/Results/BaseResultsHandler.cs | 26 ++++----- Engine/Results/IResultHandler.cs | 12 ++--- Tests/Common/AlgorithmConfigurationTests.cs | 28 +++++----- Tests/Engine/AlgorithmManagerTests.cs | 4 +- .../Results/LiveTradingResultHandlerTests.cs | 54 +++++++++---------- 10 files changed, 91 insertions(+), 91 deletions(-) diff --git a/Algorithm/QCAlgorithm.cs b/Algorithm/QCAlgorithm.cs index d495edb1dca4..37883f9e704e 100644 --- a/Algorithm/QCAlgorithm.cs +++ b/Algorithm/QCAlgorithm.cs @@ -152,7 +152,7 @@ public partial class QCAlgorithm : MarshalByRefObject, IAlgorithm private TimeSpan? _warmupTimeSpan; private int? _warmupBarCount; private Dictionary _parameters = new Dictionary(); - private bool _brokerageDataSet; + private bool _deploymentDetailsSet; private SecurityDefinitionSymbolResolver _securityDefinitionSymbolResolver; private SecurityDefinitionSymbolResolver SecurityDefinitionSymbolResolver @@ -750,11 +750,11 @@ public ConcurrentQueue ErrorMessages public ObjectStore ObjectStore { get; private set; } /// - /// Gets a read-only view of the brokerage data shared by the brokerage, data queue handler or any other component, + /// Gets a read-only view of the deployment details shared by the brokerage, data queue handler or any other component, /// for example account information. Usually empty when not running in live mode /// [DocumentationAttribute(LiveTrading)] - public ReadOnlyExtendedDictionary BrokerageData { get; private set; } = new(); + public ReadOnlyExtendedDictionary DeploymentDetails { get; private set; } = new(); /// /// The current statistics for the running algorithm. @@ -927,22 +927,22 @@ public ReadOnlyExtendedDictionary GetParameters() } /// - /// Sets the brokerage data read-only view. Can only be set once, it's shared by the engine + /// Sets the deployment details read-only view. Can only be set once, it's shared by the engine /// - /// The brokerage data + /// The deployment details [DocumentationAttribute(LiveTrading)] - public void SetBrokerageData(ReadOnlyExtendedDictionary brokerageData) + public void SetDeploymentDetails(ReadOnlyExtendedDictionary deploymentDetails) { - if (brokerageData == null) + if (deploymentDetails == null) { - throw new ArgumentNullException(nameof(brokerageData)); + throw new ArgumentNullException(nameof(deploymentDetails)); } - if (_brokerageDataSet && !ReferenceEquals(BrokerageData, brokerageData)) + if (_deploymentDetailsSet && !ReferenceEquals(DeploymentDetails, deploymentDetails)) { - throw new InvalidOperationException("QCAlgorithm.SetBrokerageData(): the brokerage data has already been set, it can only be set once"); + throw new InvalidOperationException("QCAlgorithm.SetDeploymentDetails(): the deployment details have already been set, they can only be set once"); } - BrokerageData = brokerageData; - _brokerageDataSet = true; + DeploymentDetails = deploymentDetails; + _deploymentDetailsSet = true; } /// diff --git a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs index cf47abc0b929..5315c8a59ca3 100644 --- a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs +++ b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs @@ -427,10 +427,10 @@ public Exception RunTimeError public ObjectStore ObjectStore => _baseAlgorithm.ObjectStore; /// - /// Gets a read-only view of the brokerage data shared by the brokerage, data queue handler or any other component, + /// Gets a read-only view of the deployment details shared by the brokerage, data queue handler or any other component, /// for example account information. Usually empty when not running in live mode /// - public ReadOnlyExtendedDictionary BrokerageData => _baseAlgorithm.BrokerageData; + public ReadOnlyExtendedDictionary DeploymentDetails => _baseAlgorithm.DeploymentDetails; /// /// Returns the current Slice object @@ -1171,10 +1171,10 @@ public void SetFinishedWarmingUp() public void SetParameters(Dictionary parameters) => _baseAlgorithm.SetParameters(parameters); /// - /// Sets the brokerage data read-only view + /// Sets the deployment details read-only view /// - /// The brokerage data - public void SetBrokerageData(ReadOnlyExtendedDictionary brokerageData) => _baseAlgorithm.SetBrokerageData(brokerageData); + /// The deployment details + public void SetDeploymentDetails(ReadOnlyExtendedDictionary deploymentDetails) => _baseAlgorithm.SetDeploymentDetails(deploymentDetails); /// /// Tries to convert a PyObject into a C# object diff --git a/Common/AlgorithmConfiguration.cs b/Common/AlgorithmConfiguration.cs index 2be4b733438f..4d330d00a5a3 100644 --- a/Common/AlgorithmConfiguration.cs +++ b/Common/AlgorithmConfiguration.cs @@ -63,10 +63,10 @@ public class AlgorithmConfiguration public IReadOnlyDictionary Parameters { get; set; } /// - /// The brokerage data used by the live algorithm, if any + /// The deployment details of the live algorithm, if any /// [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public IReadOnlyDictionary BrokerageData { get; set; } + public IReadOnlyDictionary DeploymentDetails { get; set; } /// /// Backtest maximum end date @@ -102,10 +102,10 @@ public class AlgorithmConfiguration public AlgorithmConfiguration(string name, ISet tags, string accountCurrency, BrokerageName brokerageName, AccountType accountType, IReadOnlyDictionary parameters, DateTime startDate, DateTime endDate, DateTime? outOfSampleMaxEndDate, int outOfSampleDays = 0, int tradingDaysPerYear = 0, - IReadOnlyDictionary brokerageData = null) + IReadOnlyDictionary deploymentDetails = null) { Name = name; - BrokerageData = brokerageData; + DeploymentDetails = deploymentDetails; Tags = tags; OutOfSampleMaxEndDate = outOfSampleMaxEndDate; TradingDaysPerYear = tradingDaysPerYear; @@ -149,7 +149,7 @@ public static AlgorithmConfiguration Create(IAlgorithm algorithm, BacktestNodePa // use value = 252 like default for backwards compatibility algorithm?.Settings?.TradingDaysPerYear ?? 252, // only included when set, live mode. We take a snapshot since the algorithm's instance can be updated later on - algorithm.BrokerageData?.Count > 0 ? new Dictionary(algorithm.BrokerageData) : null); + algorithm.DeploymentDetails?.Count > 0 ? new Dictionary(algorithm.DeploymentDetails) : null); } } } diff --git a/Common/Interfaces/IAlgorithm.cs b/Common/Interfaces/IAlgorithm.cs index b40311b1bed8..d388dcdbfd0c 100644 --- a/Common/Interfaces/IAlgorithm.cs +++ b/Common/Interfaces/IAlgorithm.cs @@ -407,10 +407,10 @@ InsightManager Insights ObjectStore ObjectStore { get; } /// - /// Gets a read-only view of the brokerage data shared by the brokerage, data queue handler or any other component, + /// Gets a read-only view of the deployment details shared by the brokerage, data queue handler or any other component, /// for example account information. Usually empty when not running in live mode /// - ReadOnlyExtendedDictionary BrokerageData { get; } + ReadOnlyExtendedDictionary DeploymentDetails { get; } /// /// Returns the current Slice object @@ -481,10 +481,10 @@ InsightManager Insights void SetParameters(Dictionary parameters); /// - /// Sets the brokerage data read-only view + /// Sets the deployment details read-only view /// - /// The brokerage data - void SetBrokerageData(ReadOnlyExtendedDictionary brokerageData); + /// The deployment details + void SetDeploymentDetails(ReadOnlyExtendedDictionary deploymentDetails); /// /// Determines if the Symbol is shortable at the brokerage diff --git a/Engine/Engine.cs b/Engine/Engine.cs index 089b260205db..61deaa74c56f 100644 --- a/Engine/Engine.cs +++ b/Engine/Engine.cs @@ -126,8 +126,8 @@ public void Run(AlgorithmNodePacket job, AlgorithmManager manager, string assemb algorithm.ProjectId = job.ProjectId; - // share the brokerage data with the algorithm right away so it's available during initialization - algorithm.SetBrokerageData(AlgorithmHandlers.Results.BrokerageData); + // share the deployment details with the algorithm right away so it's available during initialization + algorithm.SetDeploymentDetails(AlgorithmHandlers.Results.DeploymentDetails); // Set algorithm in ILeanManager SystemHandlers.LeanManager.SetAlgorithm(algorithm); diff --git a/Engine/Results/BaseResultsHandler.cs b/Engine/Results/BaseResultsHandler.cs index 7c0a73f3f7ee..59cebaab00db 100644 --- a/Engine/Results/BaseResultsHandler.cs +++ b/Engine/Results/BaseResultsHandler.cs @@ -250,14 +250,14 @@ protected Bar CurrentAlgorithmEquity protected Dictionary State { get; set; } /// - /// Brokerage data shared with the user and the algorithm, see + /// Deployment details shared with the user and the algorithm, see /// - private readonly Dictionary _brokerageData = new(); + private readonly Dictionary _deploymentDetails = new(); /// - /// Read only view of the brokerage data, see . Shared with the algorithm + /// Read only view of the deployment details, see . Shared with the algorithm /// - public ReadOnlyExtendedDictionary BrokerageData { get; } + public ReadOnlyExtendedDictionary DeploymentDetails { get; } /// /// The handler responsible for communicating messages to listeners @@ -338,7 +338,7 @@ protected BaseResultsHandler() Messages = new ConcurrentQueue(); RuntimeStatistics = new Dictionary(); // same instance, so any entries added later are visible through the view - BrokerageData = new ReadOnlyExtendedDictionary(_brokerageData, copy: false); + DeploymentDetails = new ReadOnlyExtendedDictionary(_deploymentDetails, copy: false); StartTime = DateTime.UtcNow; CompileId = ""; AlgorithmId = ""; @@ -557,32 +557,32 @@ public virtual void SetAlgorithm(IAlgorithm algorithm, decimal startingPortfolio } /// - /// Adds or updates a brokerage data entry. Key value pairs the brokerage, data queue handler or any other component + /// Adds or updates a deployment detail entry. Key value pairs the brokerage, data queue handler or any other component /// wants to share with the user, through the results, and the algorithm, for example account information. /// Sensitive data, like credentials, should never be added /// - /// The brokerage data key - /// The brokerage data value - public virtual void AddBrokerageData(string key, string value) + /// The deployment detail key + /// The deployment detail value + public virtual void AddDeploymentDetail(string key, string value) { if (string.IsNullOrEmpty(key)) { return; } - lock (_brokerageData) + lock (_deploymentDetails) { - _brokerageData[key] = value ?? string.Empty; + _deploymentDetails[key] = value ?? string.Empty; } } /// - /// Creates the algorithm configuration to include in the results, taking a snapshot of the current brokerage data + /// Creates the algorithm configuration to include in the results, taking a snapshot of the current deployment details /// /// The associated backtest node packet if any /// A new instance protected AlgorithmConfiguration CreateAlgorithmConfiguration(BacktestNodePacket backtestNodePacket = null) { - lock (_brokerageData) + lock (_deploymentDetails) { return AlgorithmConfiguration.Create(Algorithm, backtestNodePacket); } diff --git a/Engine/Results/IResultHandler.cs b/Engine/Results/IResultHandler.cs index 59c28af4d1ca..a3a1ef8291db 100644 --- a/Engine/Results/IResultHandler.cs +++ b/Engine/Results/IResultHandler.cs @@ -143,18 +143,18 @@ bool IsActive void RuntimeStatistic(string key, string value); /// - /// Adds or updates a brokerage data entry. Key value pairs the brokerage, data queue handler or any other component + /// Adds or updates a deployment detail entry. Key value pairs the brokerage, data queue handler or any other component /// wants to share with the user, through the results, and the algorithm, for example account information. /// Sensitive data, like credentials, should never be added /// - /// The brokerage data key - /// The brokerage data value - void AddBrokerageData(string key, string value); + /// The deployment detail key + /// The deployment detail value + void AddDeploymentDetail(string key, string value); /// - /// Read only view of the brokerage data, see . Shared with the algorithm + /// Read only view of the deployment details, see . Shared with the algorithm /// - ReadOnlyExtendedDictionary BrokerageData { get; } + ReadOnlyExtendedDictionary DeploymentDetails { get; } /// /// Send a new order event. diff --git a/Tests/Common/AlgorithmConfigurationTests.cs b/Tests/Common/AlgorithmConfigurationTests.cs index 45215aaf32fe..8a6b2b8c4ca1 100644 --- a/Tests/Common/AlgorithmConfigurationTests.cs +++ b/Tests/Common/AlgorithmConfigurationTests.cs @@ -109,37 +109,37 @@ public void JsonRoundtrip(bool backwardsCompatible) } [Test] - public void BrokerageDataIsOnlyIncludedWhenSet() + public void DeploymentDetailsAreOnlyIncludedWhenSet() { var algorithm = new QCAlgorithm(); // not set, e.g. backtesting var algorithmConfiguration = AlgorithmConfiguration.Create(algorithm, null); - Assert.IsNull(algorithmConfiguration.BrokerageData); + Assert.IsNull(algorithmConfiguration.DeploymentDetails); var serialized = JsonConvert.SerializeObject(algorithmConfiguration); - Assert.IsFalse(serialized.Contains("BrokerageData", StringComparison.InvariantCultureIgnoreCase)); + Assert.IsFalse(serialized.Contains("DeploymentDetails", StringComparison.InvariantCultureIgnoreCase)); // set, e.g. live trading - var brokerageData = new Dictionary { { "some-key", "some value" }, { "some-other-key", "another value" } }; - algorithm.SetBrokerageData(new ReadOnlyExtendedDictionary(brokerageData, copy: false)); + var deploymentDetails = new Dictionary { { "some-key", "some value" }, { "some-other-key", "another value" } }; + algorithm.SetDeploymentDetails(new ReadOnlyExtendedDictionary(deploymentDetails, copy: false)); algorithmConfiguration = AlgorithmConfiguration.Create(algorithm, null); - CollectionAssert.AreEquivalent(brokerageData, algorithmConfiguration.BrokerageData); + CollectionAssert.AreEquivalent(deploymentDetails, algorithmConfiguration.DeploymentDetails); // the configuration holds a snapshot, later changes are reflected by the algorithm but not by the existing configuration - brokerageData.Remove("some-other-key"); - brokerageData["some-key"] = ""; - Assert.AreEqual(2, algorithmConfiguration.BrokerageData.Count); - Assert.AreEqual("some value", algorithmConfiguration.BrokerageData["some-key"]); - CollectionAssert.AreEquivalent(brokerageData, algorithm.BrokerageData); + deploymentDetails.Remove("some-other-key"); + deploymentDetails["some-key"] = ""; + Assert.AreEqual(2, algorithmConfiguration.DeploymentDetails.Count); + Assert.AreEqual("some value", algorithmConfiguration.DeploymentDetails["some-key"]); + CollectionAssert.AreEquivalent(deploymentDetails, algorithm.DeploymentDetails); algorithmConfiguration = AlgorithmConfiguration.Create(algorithm, null); - CollectionAssert.AreEquivalent(brokerageData, algorithmConfiguration.BrokerageData); + CollectionAssert.AreEquivalent(deploymentDetails, algorithmConfiguration.DeploymentDetails); serialized = JsonConvert.SerializeObject(algorithmConfiguration); - Assert.IsTrue(serialized.Contains("\"BrokerageData\":{\"some-key\":\"\"}", StringComparison.InvariantCulture)); + Assert.IsTrue(serialized.Contains("\"DeploymentDetails\":{\"some-key\":\"\"}", StringComparison.InvariantCulture)); var deserialized = JsonConvert.DeserializeObject(serialized); - CollectionAssert.AreEquivalent(brokerageData, deserialized.BrokerageData); + CollectionAssert.AreEquivalent(deploymentDetails, deserialized.DeploymentDetails); } private static TestCaseData[] AlgorithmConfigurationTestCases => new[] diff --git a/Tests/Engine/AlgorithmManagerTests.cs b/Tests/Engine/AlgorithmManagerTests.cs index 7ecf3fe7b42d..9e9272b903f3 100644 --- a/Tests/Engine/AlgorithmManagerTests.cs +++ b/Tests/Engine/AlgorithmManagerTests.cs @@ -236,11 +236,11 @@ public void SendStatusUpdate(AlgorithmStatus status, string message = "") { } - public void AddBrokerageData(string key, string value) + public void AddDeploymentDetail(string key, string value) { } - public ReadOnlyExtendedDictionary BrokerageData { get; } = new(); + public ReadOnlyExtendedDictionary DeploymentDetails { get; } = new(); public void RuntimeStatistic(string key, string value) { diff --git a/Tests/Engine/Results/LiveTradingResultHandlerTests.cs b/Tests/Engine/Results/LiveTradingResultHandlerTests.cs index d590475a86e1..4a090915f46e 100644 --- a/Tests/Engine/Results/LiveTradingResultHandlerTests.cs +++ b/Tests/Engine/Results/LiveTradingResultHandlerTests.cs @@ -526,14 +526,14 @@ public void StoredResultsCarryAlgorithmConfigurationFromTheStart() resultHandler.Initialize(new(job, messaging, api, transactionHandler, null)); // the engine shares the view right after creating the algorithm - algorithm.SetBrokerageData(resultHandler.BrokerageData); + algorithm.SetDeploymentDetails(resultHandler.DeploymentDetails); // e.g. the brokerage or data queue handler, which are created before the algorithm is set - resultHandler.AddBrokerageData("some-key", "some value"); + resultHandler.AddDeploymentDetail("some-key", "some value"); resultHandler.SetAlgorithm(algorithm, 100000); algorithm.SetLocked(); - var expectedBrokerageData = new Dictionary { { "some-key", "some value" } }; - CollectionAssert.AreEquivalent(expectedBrokerageData, algorithm.BrokerageData); + var expectedDeploymentDetails = new Dictionary { { "some-key", "some value" } }; + CollectionAssert.AreEquivalent(expectedDeploymentDetails, algorithm.DeploymentDetails); // the first update pass stores the status file and the complete results right away, no final result required var expected = new[] { $"{deployId}.json", $"{deployId}-{DateTime.UtcNow:yyyy-MM-dd}_minute.json" }; @@ -544,7 +544,7 @@ public void StoredResultsCarryAlgorithmConfigurationFromTheStart() foreach (var result in resultHandler.GetStoredResults(name)) { Assert.IsNotNull(result.AlgorithmConfiguration, $"'{name}' is missing the algorithm configuration"); - CollectionAssert.AreEquivalent(expectedBrokerageData, result.AlgorithmConfiguration.BrokerageData); + CollectionAssert.AreEquivalent(expectedDeploymentDetails, result.AlgorithmConfiguration.DeploymentDetails); Assert.AreEqual("10", result.AlgorithmConfiguration.Parameters["ema-fast"]); } } @@ -556,7 +556,7 @@ public void StoredResultsCarryAlgorithmConfigurationFromTheStart() } [Test] - public void BrokerageDataIsSharedWithTheAlgorithmAndTheResults() + public void DeploymentDetailsAreSharedWithTheAlgorithmAndTheResults() { using var api = new Api.Api(); using var messaging = new QuantConnect.Messaging.Messaging(); @@ -566,37 +566,37 @@ public void BrokerageDataIsSharedWithTheAlgorithmAndTheResults() var algorithm = new AlgorithmStub(); algorithm.SetFinishedWarmingUp(); - Assert.IsEmpty(algorithm.BrokerageData); - Assert.IsEmpty(resultHandler.BrokerageData); + Assert.IsEmpty(algorithm.DeploymentDetails); + Assert.IsEmpty(resultHandler.DeploymentDetails); // the engine shares the view right after creating the algorithm, so it's available during initialization - algorithm.SetBrokerageData(resultHandler.BrokerageData); - resultHandler.AddBrokerageData("account", "123"); - Assert.AreEqual("123", algorithm.BrokerageData["account"]); - Assert.AreEqual("123", resultHandler.BrokerageData["account"]); + algorithm.SetDeploymentDetails(resultHandler.DeploymentDetails); + resultHandler.AddDeploymentDetail("account", "123"); + Assert.AreEqual("123", algorithm.DeploymentDetails["account"]); + Assert.AreEqual("123", resultHandler.DeploymentDetails["account"]); resultHandler.SetAlgorithm(algorithm, 100000); - Assert.AreSame(resultHandler.BrokerageData, algorithm.BrokerageData); + Assert.AreSame(resultHandler.DeploymentDetails, algorithm.DeploymentDetails); // it's only set once by the engine: the same instance is fine, a different one is not - Assert.DoesNotThrow(() => algorithm.SetBrokerageData(resultHandler.BrokerageData)); - Assert.Throws(() => algorithm.SetBrokerageData(new ReadOnlyExtendedDictionary())); - Assert.AreSame(resultHandler.BrokerageData, algorithm.BrokerageData); + Assert.DoesNotThrow(() => algorithm.SetDeploymentDetails(resultHandler.DeploymentDetails)); + Assert.Throws(() => algorithm.SetDeploymentDetails(new ReadOnlyExtendedDictionary())); + Assert.AreSame(resultHandler.DeploymentDetails, algorithm.DeploymentDetails); - resultHandler.AddBrokerageData("environment", "paper"); - Assert.AreEqual("paper", algorithm.BrokerageData["environment"]); + resultHandler.AddDeploymentDetail("environment", "paper"); + Assert.AreEqual("paper", algorithm.DeploymentDetails["environment"]); // entries are updated in place, empty keys are ignored and null values are stored as empty - resultHandler.AddBrokerageData("account", "456"); - resultHandler.AddBrokerageData("", "ignored"); - resultHandler.AddBrokerageData(null, "ignored"); - resultHandler.AddBrokerageData("empty", null); - CollectionAssert.AreEquivalent(new Dictionary { { "account", "456" }, { "environment", "paper" }, { "empty", "" } }, algorithm.BrokerageData); + resultHandler.AddDeploymentDetail("account", "456"); + resultHandler.AddDeploymentDetail("", "ignored"); + resultHandler.AddDeploymentDetail(null, "ignored"); + resultHandler.AddDeploymentDetail("empty", null); + CollectionAssert.AreEquivalent(new Dictionary { { "account", "456" }, { "environment", "paper" }, { "empty", "" } }, algorithm.DeploymentDetails); // read only for the algorithm - Assert.Throws(() => algorithm.BrokerageData.Add("new-key", "new value")); - Assert.Throws(() => algorithm.BrokerageData.Remove("account")); - Assert.Throws(() => algorithm.BrokerageData["account"] = "new value"); + Assert.Throws(() => algorithm.DeploymentDetails.Add("new-key", "new value")); + Assert.Throws(() => algorithm.DeploymentDetails.Remove("account")); + Assert.Throws(() => algorithm.DeploymentDetails["account"] = "new value"); // the final result is stored on exit resultHandler.Exit(); @@ -604,7 +604,7 @@ public void BrokerageDataIsSharedWithTheAlgorithmAndTheResults() Assert.IsNotEmpty(stored); foreach (var result in stored) { - CollectionAssert.AreEquivalent(algorithm.BrokerageData, result.AlgorithmConfiguration.BrokerageData); + CollectionAssert.AreEquivalent(algorithm.DeploymentDetails, result.AlgorithmConfiguration.DeploymentDetails); } } From b6d52390a7b13e74cb6c4e174d5b697c049a618c Mon Sep 17 00:00:00 2001 From: Martin Molinero Date: Mon, 14 Sep 2026 13:31:19 -0300 Subject: [PATCH 2/3] Add DeploymentDetailsHelper Single entry point for brokerages, data queue handlers and any other component to share a deployment detail, resolving the result handler from the Composer so callers don't have to. Never throws and logs once if no result handler is found. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UAELnEhZMd7X9D6rc9CAmC --- Engine/Results/DeploymentDetailsHelper.cs | 61 +++++++++++++++ .../Results/DeploymentDetailsHelperTests.cs | 74 +++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 Engine/Results/DeploymentDetailsHelper.cs create mode 100644 Tests/Engine/Results/DeploymentDetailsHelperTests.cs diff --git a/Engine/Results/DeploymentDetailsHelper.cs b/Engine/Results/DeploymentDetailsHelper.cs new file mode 100644 index 000000000000..51089337aaf5 --- /dev/null +++ b/Engine/Results/DeploymentDetailsHelper.cs @@ -0,0 +1,61 @@ +/* + * 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.Threading; +using QuantConnect.Util; +using QuantConnect.Logging; + +namespace QuantConnect.Lean.Engine.Results +{ + /// + /// Helper to share deployment details with the user and the algorithm, see + /// + public static class DeploymentDetailsHelper + { + private static int _missingResultHandlerLogged; + + /// + /// Adds or updates a deployment detail entry on the result handler loaded in the , if any. + /// Key value pairs the brokerage, data queue handler or any other component wants to share with the user, + /// through the results, and the algorithm, for example account information. + /// Sensitive data, like credentials, should never be added + /// + /// Will never throw, callers are not expected to handle any failure sharing a deployment detail + /// The deployment detail key + /// The deployment detail value + public static void Add(string key, string value) + { + try + { + var resultHandler = Composer.Instance.GetPart(); + if (resultHandler == null) + { + // we only log this once, else we would spam for every entry + if (Interlocked.Exchange(ref _missingResultHandlerLogged, 1) == 0) + { + Log.Error($"DeploymentDetailsHelper.Add(): no result handler was found, deployment details will be ignored"); + } + return; + } + resultHandler.AddDeploymentDetail(key, value); + } + catch (Exception exception) + { + Log.Error(exception, $"Failed to add deployment detail '{key}'"); + } + } + } +} diff --git a/Tests/Engine/Results/DeploymentDetailsHelperTests.cs b/Tests/Engine/Results/DeploymentDetailsHelperTests.cs new file mode 100644 index 000000000000..cf795b84aa6e --- /dev/null +++ b/Tests/Engine/Results/DeploymentDetailsHelperTests.cs @@ -0,0 +1,74 @@ +/* + * 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 Moq; +using NUnit.Framework; +using QuantConnect.Util; +using QuantConnect.Lean.Engine.Results; + +namespace QuantConnect.Tests.Engine.Results +{ + [TestFixture] + public class DeploymentDetailsHelperTests + { + [SetUp] + public void SetUp() + { + Composer.Instance.Reset(); + } + + [TearDown] + public void TearDown() + { + Composer.Instance.Reset(); + } + + [Test] + public void AddsToTheComposerResultHandler() + { + var resultHandler = new TestResultHandler(); + Composer.Instance.AddPart(resultHandler); + + DeploymentDetailsHelper.Add("account", "123"); + DeploymentDetailsHelper.Add("environment", "paper"); + DeploymentDetailsHelper.Add("account", "456"); + + Assert.AreEqual(2, resultHandler.DeploymentDetails.Count); + Assert.AreEqual("456", resultHandler.DeploymentDetails["account"]); + Assert.AreEqual("paper", resultHandler.DeploymentDetails["environment"]); + } + + [Test] + public void IgnoredWithoutResultHandler() + { + Assert.IsNull(Composer.Instance.GetPart()); + + Assert.DoesNotThrow(() => DeploymentDetailsHelper.Add("account", "123")); + Assert.DoesNotThrow(() => DeploymentDetailsHelper.Add("environment", "paper")); + } + + [Test] + public void DoesNotThrowOnResultHandlerFailure() + { + var resultHandler = new Mock(); + resultHandler.Setup(x => x.AddDeploymentDetail(It.IsAny(), It.IsAny())) + .Throws(new Exception("Some failure")); + Composer.Instance.AddPart(resultHandler.Object); + + Assert.DoesNotThrow(() => DeploymentDetailsHelper.Add("account", "123")); + } + } +} From 623d7bbde0ec61c8cdfba697840f30ec55f7650a Mon Sep 17 00:00:00 2001 From: Martin Molinero Date: Mon, 14 Sep 2026 14:11:24 -0300 Subject: [PATCH 3/3] Don't reset the composer in the deployment details helper tests Resetting dropped the parts registered by TestGlobals, like the factor file provider, which nothing puts back, breaking later tests that resolve them from the composer, IndicatorBasedOptionPriceModelTests through DividendYieldProvider. The test now explicitly uses the result handler the composer resolves, adding one only if there is none. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UAELnEhZMd7X9D6rc9CAmC --- .../Results/DeploymentDetailsHelperTests.cs | 48 ++++--------------- 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/Tests/Engine/Results/DeploymentDetailsHelperTests.cs b/Tests/Engine/Results/DeploymentDetailsHelperTests.cs index cf795b84aa6e..31483bb52d81 100644 --- a/Tests/Engine/Results/DeploymentDetailsHelperTests.cs +++ b/Tests/Engine/Results/DeploymentDetailsHelperTests.cs @@ -13,8 +13,6 @@ * limitations under the License. */ -using System; -using Moq; using NUnit.Framework; using QuantConnect.Util; using QuantConnect.Lean.Engine.Results; @@ -24,51 +22,25 @@ namespace QuantConnect.Tests.Engine.Results [TestFixture] public class DeploymentDetailsHelperTests { - [SetUp] - public void SetUp() - { - Composer.Instance.Reset(); - } - - [TearDown] - public void TearDown() - { - Composer.Instance.Reset(); - } - [Test] - public void AddsToTheComposerResultHandler() + public void AddsToTheResultHandlerInTheComposer() { - var resultHandler = new TestResultHandler(); - Composer.Instance.AddPart(resultHandler); + // we explicitly use the result handler the composer resolves, adding one if there is none, + // instead of resetting the composer which would drop the parts other tests rely on + var resultHandler = Composer.Instance.GetPart(); + if (resultHandler == null) + { + resultHandler = new TestResultHandler(); + Composer.Instance.AddPart(resultHandler); + } DeploymentDetailsHelper.Add("account", "123"); DeploymentDetailsHelper.Add("environment", "paper"); + // updates in place DeploymentDetailsHelper.Add("account", "456"); - Assert.AreEqual(2, resultHandler.DeploymentDetails.Count); Assert.AreEqual("456", resultHandler.DeploymentDetails["account"]); Assert.AreEqual("paper", resultHandler.DeploymentDetails["environment"]); } - - [Test] - public void IgnoredWithoutResultHandler() - { - Assert.IsNull(Composer.Instance.GetPart()); - - Assert.DoesNotThrow(() => DeploymentDetailsHelper.Add("account", "123")); - Assert.DoesNotThrow(() => DeploymentDetailsHelper.Add("environment", "paper")); - } - - [Test] - public void DoesNotThrowOnResultHandlerFailure() - { - var resultHandler = new Mock(); - resultHandler.Setup(x => x.AddDeploymentDetail(It.IsAny(), It.IsAny())) - .Throws(new Exception("Some failure")); - Composer.Instance.AddPart(resultHandler.Object); - - Assert.DoesNotThrow(() => DeploymentDetailsHelper.Add("account", "123")); - } } }