From ae599a524b64a05d35c136c4543cffcc33388a0a Mon Sep 17 00:00:00 2001 From: Nemo Date: Sat, 23 May 2026 11:37:01 +0800 Subject: [PATCH] Feature: add ReconnectBrokerage() to QCAlgorithm for self-healing data feeds Exposes a ReconnectBrokerage() method on IAlgorithm/QCAlgorithm that allows algorithms running in live trading to trigger a brokerage Disconnect/Connect cycle when they detect data quality issues (e.g. IBKR gateway silently freezing with volume=0 bars). The reconnect action is injected by the engine so the algorithm layer stays decoupled from the concrete brokerage type. Closes #9496 --- Algorithm/QCAlgorithm.cs | 29 +++++++ .../Python/Wrappers/AlgorithmPythonWrapper.cs | 6 ++ Common/Interfaces/IAlgorithm.cs | 13 ++++ Engine/Engine.cs | 7 ++ .../AlgorithmReconnectBrokerageTests.cs | 75 +++++++++++++++++++ 5 files changed, 130 insertions(+) create mode 100644 Tests/Algorithm/AlgorithmReconnectBrokerageTests.cs diff --git a/Algorithm/QCAlgorithm.cs b/Algorithm/QCAlgorithm.cs index 28f1c352f359..e747691adf03 100644 --- a/Algorithm/QCAlgorithm.cs +++ b/Algorithm/QCAlgorithm.cs @@ -121,6 +121,8 @@ public partial class QCAlgorithm : MarshalByRefObject, IAlgorithm private IStatisticsService _statisticsService; private IBrokerageModel _brokerageModel; + private Action _brokerageReconnectAction; + private bool _sentBroadcastCommandsDisabled; private readonly HashSet _oneTimeCommandErrors = new(); private readonly Dictionary> _registeredCommands = new(StringComparer.InvariantCultureIgnoreCase); @@ -1424,6 +1426,33 @@ public void SetBrokerageMessageHandler(IBrokerageMessageHandler handler) BrokerageMessageHandler = handler ?? throw new ArgumentNullException(nameof(handler)); } + /// + /// Sets the action used to reconnect the brokerage. Intended for internal use by the Lean engine only. + /// + /// The action that performs the disconnect/connect cycle + [DocumentationAttribute(LiveTrading)] + public void SetBrokerageReconnectAction(Action reconnectAction) + { + _brokerageReconnectAction = reconnectAction ?? throw new ArgumentNullException(nameof(reconnectAction)); + } + + /// + /// Triggers a brokerage disconnect/reconnect cycle. Use this when the algorithm detects + /// a data quality issue (e.g. a frozen market data feed) and needs to self-heal without + /// human intervention. Only meaningful in live trading; no-op in backtesting. + /// + [DocumentationAttribute(LiveTrading)] + public void ReconnectBrokerage() + { + if (_brokerageReconnectAction == null) + { + Debug("ReconnectBrokerage(): no reconnect action registered, ignoring."); + return; + } + Log("ReconnectBrokerage(): triggering brokerage reconnect."); + _brokerageReconnectAction(); + } + /// /// Sets the risk free interest rate model to be used in the algorithm /// diff --git a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs index 642f1a1918d0..25bc935e0335 100644 --- a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs +++ b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs @@ -1032,6 +1032,12 @@ public void OnWarmupFinished() /// The message handler to use public void SetBrokerageMessageHandler(IBrokerageMessageHandler handler) => _baseAlgorithm.SetBrokerageMessageHandler(handler); + /// + public void SetBrokerageReconnectAction(Action reconnectAction) => _baseAlgorithm.SetBrokerageReconnectAction(reconnectAction); + + /// + public void ReconnectBrokerage() => _baseAlgorithm.ReconnectBrokerage(); + /// /// Sets the brokerage model used to resolve transaction models, settlement models, /// and brokerage specified ordering behaviors. diff --git a/Common/Interfaces/IAlgorithm.cs b/Common/Interfaces/IAlgorithm.cs index a4221bd440e5..b2863f9b6be8 100644 --- a/Common/Interfaces/IAlgorithm.cs +++ b/Common/Interfaces/IAlgorithm.cs @@ -830,6 +830,19 @@ Security AddSecurity(Symbol symbol, Resolution? resolution = null, bool? fillFor /// The message handler to use void SetBrokerageMessageHandler(IBrokerageMessageHandler handler); + /// + /// Sets the action used to reconnect the brokerage. Intended for internal use by the Lean engine only. + /// + /// The action that performs the disconnect/connect cycle + void SetBrokerageReconnectAction(Action reconnectAction); + + /// + /// Triggers a brokerage disconnect/reconnect cycle. Use this when the algorithm detects + /// a data quality issue (e.g. a frozen market data feed) and needs to self-heal without + /// human intervention. Only meaningful in live trading; no-op in backtesting. + /// + void ReconnectBrokerage(); + /// /// Set the historical data provider /// diff --git a/Engine/Engine.cs b/Engine/Engine.cs index 6de6c7237dbf..f10b4248ccf9 100644 --- a/Engine/Engine.cs +++ b/Engine/Engine.cs @@ -228,6 +228,13 @@ public void Run(AlgorithmNodePacket job, AlgorithmManager manager, string assemb // initialize the default brokerage message handler algorithm.BrokerageMessageHandler = factory.CreateBrokerageMessageHandler(algorithm, job, SystemHandlers.Api); + // allow the algorithm to trigger a brokerage reconnect (e.g. when it detects a frozen data feed) + algorithm.SetBrokerageReconnectAction(() => + { + brokerage.Disconnect(); + brokerage.Connect(); + }); + var brokerageDataQueueHandlers = Composer.Instance.GetParts().OfType() // In backtesting, brokerages can be used as data downloaders (BrokerageDataDownloader) // and are added to the composer as IBrokerage diff --git a/Tests/Algorithm/AlgorithmReconnectBrokerageTests.cs b/Tests/Algorithm/AlgorithmReconnectBrokerageTests.cs new file mode 100644 index 000000000000..d22789a24f99 --- /dev/null +++ b/Tests/Algorithm/AlgorithmReconnectBrokerageTests.cs @@ -0,0 +1,75 @@ +/* + * 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.Algorithm; +using QuantConnect.Tests.Engine.DataFeeds; + +namespace QuantConnect.Tests.Algorithm +{ + [TestFixture] + public class AlgorithmReconnectBrokerageTests + { + private QCAlgorithm _algo; + + [SetUp] + public void Setup() + { + _algo = new QCAlgorithm(); + _algo.SubscriptionManager.SetDataManager(new DataManagerStub(_algo)); + } + + [Test] + public void ReconnectBrokerage_InvokesRegisteredAction() + { + var reconnectCalled = false; + _algo.SetBrokerageReconnectAction(() => reconnectCalled = true); + + _algo.ReconnectBrokerage(); + + Assert.IsTrue(reconnectCalled); + } + + [Test] + public void ReconnectBrokerage_NoOp_WhenNoActionRegistered() + { + // should not throw when no action has been set + Assert.DoesNotThrow(() => _algo.ReconnectBrokerage()); + } + + [Test] + public void SetBrokerageReconnectAction_ThrowsOnNull() + { + Assert.Throws(() => _algo.SetBrokerageReconnectAction(null)); + } + + [Test] + public void ReconnectBrokerage_InvokesDisconnectThenConnect() + { + var callOrder = new System.Collections.Generic.List(); + _algo.SetBrokerageReconnectAction(() => + { + callOrder.Add("disconnect"); + callOrder.Add("connect"); + }); + + _algo.ReconnectBrokerage(); + + Assert.AreEqual(new[] { "disconnect", "connect" }, callOrder); + } + } +}