From 20a048dfe11d7b8bf5cea9563d9435df843500e4 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Fri, 20 Mar 2026 12:54:12 -0500 Subject: [PATCH 01/10] Fix snake_case error messages for Python algorithms --- .../Python/Wrappers/AlgorithmPythonWrapper.cs | 2 ++ .../Messages.Algorithm.Framework.Portfolio.cs | 4 +-- Common/Messages/Messages.Brokerages.cs | 2 +- Common/Messages/Messages.Orders.cs | 6 ++-- Common/Messages/Messages.QuantConnect.cs | 29 +++++++++++++-- Common/Messages/Messages.Securities.cs | 35 +++++++++++++------ Common/Securities/SecurityPortfolioManager.cs | 4 +-- .../Securities/SecurityTransactionManager.cs | 4 +-- Common/Securities/SymbolProperties.cs | 6 ++-- Tests/Common/Orders/OrderTicketTests.cs | 7 +++- 10 files changed, 72 insertions(+), 27 deletions(-) diff --git a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs index 6af94c1b877b..62270336f42c 100644 --- a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs +++ b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs @@ -177,6 +177,8 @@ public AlgorithmPythonWrapper(string moduleName) { throw new Exception("Please ensure that one class inherits from QCAlgorithm."); } + + Messages.SetAlgorithmLanguage(Language.Python); } } catch (Exception e) diff --git a/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs b/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs index 18e71e6036dd..0ce90f8050af 100644 --- a/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs +++ b/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs @@ -39,8 +39,8 @@ public static class PortfolioTarget public static string InvalidTargetPercent(IAlgorithm algorithm, decimal percent) { return Invariant($@"The portfolio target percent: { - percent}, does not comply with the current 'Algorithm.Settings' 'MaxAbsolutePortfolioTargetPercentage': { - algorithm.Settings.MaxAbsolutePortfolioTargetPercentage} or 'MinAbsolutePortfolioTargetPercentage': { + percent}, does not comply with the current 'Algorithm.Settings' '{FormatCode("MaxAbsolutePortfolioTargetPercentage")}': { + algorithm.Settings.MaxAbsolutePortfolioTargetPercentage} or '{FormatCode("MinAbsolutePortfolioTargetPercentage")}': { algorithm.Settings.MinAbsolutePortfolioTargetPercentage}. Skipping"); } diff --git a/Common/Messages/Messages.Brokerages.cs b/Common/Messages/Messages.Brokerages.cs index b1095cacbef5..5e7c58618950 100644 --- a/Common/Messages/Messages.Brokerages.cs +++ b/Common/Messages/Messages.Brokerages.cs @@ -411,7 +411,7 @@ public static class FxcmBrokerageModel [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string InvalidOrderQuantityForLotSize(Securities.Security security) { - return Invariant($"The order quantity must be a multiple of LotSize: [{security.SymbolProperties.LotSize}]."); + return Invariant($"The order quantity must be a multiple of {FormatCode("LotSize")}: [{security.SymbolProperties.LotSize}]."); } /// diff --git a/Common/Messages/Messages.Orders.cs b/Common/Messages/Messages.Orders.cs index 47033d5192eb..cba77cc7d09f 100644 --- a/Common/Messages/Messages.Orders.cs +++ b/Common/Messages/Messages.Orders.cs @@ -354,7 +354,7 @@ public static string ZeroQuantity(Orders.OrderRequest request) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string MissingSecurity(Orders.SubmitOrderRequest request) { - return Invariant($"You haven't requested {request.Symbol} data. Add this with AddSecurity() in the Initialize() Method."); + return Invariant($"You haven't requested {request.Symbol} data. Add this with {FormatCode("AddSecurity")}() in the {FormatCode("Initialize")}() method."); } /// @@ -365,8 +365,8 @@ public static string MissingSecurity(Orders.SubmitOrderRequest request) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string WarmingUp(Orders.OrderRequest request) { - return Invariant($@"This operation is not allowed in Initialize or during warm up: OrderRequest.{ - request.OrderRequestType}. Please move this code to the OnWarmupFinished() method."); + return Invariant($@"This operation is not allowed in {FormatCode("Initialize")} or during warm up: OrderRequest.{ + FormatCode(request.OrderRequestType.ToString())}. Please move this code to the {FormatCode("OnWarmupFinished")}() method."); } } diff --git a/Common/Messages/Messages.QuantConnect.cs b/Common/Messages/Messages.QuantConnect.cs index 05743b63cd4e..9a5889cc0f58 100644 --- a/Common/Messages/Messages.QuantConnect.cs +++ b/Common/Messages/Messages.QuantConnect.cs @@ -30,6 +30,29 @@ namespace QuantConnect /// public static partial class Messages { + private static Language _algorithmLanguage = Language.CSharp; + + /// + /// Sets the algorithm language used to format code identifiers in error messages. + /// + public static void SetAlgorithmLanguage(Language language) + { + _algorithmLanguage = language; + } + + /// + /// Returns the code identifier formatted for the current algorithm language. + /// For Python, converts PascalCase/camelCase to snake_case. + /// + private static string FormatCode(string code) + { + return _algorithmLanguage switch + { + Language.Python => code.ToSnakeCase(), + _ => code + }; + } + /// /// Provides user-facing messages for the class and its consumers or related classes /// @@ -187,8 +210,8 @@ public static string RemoveInvalidOperation(ExtendedDictionary @@ -282,7 +305,7 @@ public static string DownloadDataFailed(string url) public static string ZeroPriceForSecurity(QuantConnect.Symbol symbol) { return $"{symbol}: The security does not have an accurate price as it has not yet received a bar of data. " + - "Before placing a trade (or using SetHoldings) warm up your algorithm with SetWarmup, or use slice.Contains(symbol) " + + $"Before placing a trade (or using {FormatCode("SetHoldings")}) warm up your algorithm with {FormatCode("SetWarmup")}, or use slice.Contains(symbol) " + "to confirm the Slice object has price before using the data. Data does not necessarily all arrive at the same " + "time so your algorithm should confirm the data is ready before using it. In live trading this can mean you do " + "not have an active subscription to the asset class you're trying to trade. If using custom data make sure you've " + diff --git a/Common/Messages/Messages.Securities.cs b/Common/Messages/Messages.Securities.cs index 79542d7dd204..dc54c3ead3a7 100644 --- a/Common/Messages/Messages.Securities.cs +++ b/Common/Messages/Messages.Securities.cs @@ -110,7 +110,7 @@ public static string TargetOrderMarginNotAboveMinimum(decimal absDifferenceOfMar public static string TargetOrderMarginNotAboveMinimum() { return "Warning: Portfolio rebalance result ignored as it resulted in a single share trade recommendation which can generate high fees." + - " To disable minimum order size checks please set Settings.MinimumOrderMarginPortfolioPercentage = 0."; + $" To disable minimum order size checks please set {FormatCode("Settings")}.{FormatCode("MinimumOrderMarginPortfolioPercentage")} = 0."; } /// @@ -886,15 +886,19 @@ public static class SecurityPortfolioManager /// Returns a string message saying the AccountCurrency cannot be changed after adding a Security and that the method /// SetAccountCurrency() should be moved before AddSecurity() /// - public static string CannotChangeAccountCurrencyAfterAddingSecurity = - "Cannot change AccountCurrency after adding a Security. Please move SetAccountCurrency() before AddSecurity()."; + public static string CannotChangeAccountCurrencyAfterAddingSecurity() + { + return $"Cannot change AccountCurrency after adding a Security. Please move {FormatCode("SetAccountCurrency")}() before {FormatCode("AddSecurity")}()."; + } /// /// Returns a string message saying the AccountCurrency cannot be changed after setting cash and that the method /// SetAccountCurrency() should be moved before SetCash() /// - public static string CannotChangeAccountCurrencyAfterSettingCash = - "Cannot change AccountCurrency after setting cash. Please move SetAccountCurrency() before SetCash()."; + public static string CannotChangeAccountCurrencyAfterSettingCash() + { + return $"Cannot change AccountCurrency after setting cash. Please move {FormatCode("SetAccountCurrency")}() before {FormatCode("SetCash")}()."; + } /// /// Returns a string message saying the AccountCurrency has already been set and that the new value for this property @@ -958,8 +962,10 @@ public static class SecurityTransactionManager /// /// Returns a string message saying CancelOpenOrders operation is not allowed in Initialize or during warm up /// - public static string CancelOpenOrdersNotAllowedOnInitializeOrWarmUp = - "This operation is not allowed in Initialize or during warm up: CancelOpenOrders. Please move this code to the OnWarmupFinished() method."; + public static string CancelOpenOrdersNotAllowedOnInitializeOrWarmUp() + { + return $"This operation is not allowed in {FormatCode("Initialize")} or during warm up: {FormatCode("CancelOpenOrders")}. Please move this code to the {FormatCode("OnWarmupFinished")}() method."; + } /// /// Returns a string message saying the order was canceled by the CancelOpenOrders() at the given time @@ -997,17 +1003,26 @@ public static class SymbolProperties /// /// String message saying the SymbolProperties LotSize can not be less than or equal to 0 /// - public static string InvalidLotSize = "SymbolProperties LotSize can not be less than or equal to 0"; + public static string InvalidLotSize() + { + return $"{FormatCode("SymbolProperties")} {FormatCode("LotSize")} can not be less than or equal to 0"; + } /// /// String message saying the SymbolProperties PriceMagnifier can not be less than or equal to 0 /// - public static string InvalidPriceMagnifier = "SymbolProprties PriceMagnifier can not be less than or equal to 0"; + public static string InvalidPriceMagnifier() + { + return $"{FormatCode("SymbolProperties")} {FormatCode("PriceMagnifier")} can not be less than or equal to 0"; + } /// /// String message saying the SymbolProperties StrikeMultiplier can not be less than or equal to 0 /// - public static string InvalidStrikeMultiplier = "SymbolProperties StrikeMultiplier can not be less than or equal to 0"; + public static string InvalidStrikeMultiplier() + { + return $"{FormatCode("SymbolProperties")} {FormatCode("StrikeMultiplier")} can not be less than or equal to 0"; + } /// /// Parses a given SymbolProperties object into a string message diff --git a/Common/Securities/SecurityPortfolioManager.cs b/Common/Securities/SecurityPortfolioManager.cs index 2cbd6fee5b19..cda52d0f5d4d 100644 --- a/Common/Securities/SecurityPortfolioManager.cs +++ b/Common/Securities/SecurityPortfolioManager.cs @@ -642,13 +642,13 @@ public void SetAccountCurrency(string accountCurrency, decimal? startingCash = n if (Securities.Count > 0) { throw new InvalidOperationException("SecurityPortfolioManager.SetAccountCurrency(): " + - Messages.SecurityPortfolioManager.CannotChangeAccountCurrencyAfterAddingSecurity); + Messages.SecurityPortfolioManager.CannotChangeAccountCurrencyAfterAddingSecurity()); } if (_setCashWasCalled) { throw new InvalidOperationException("SecurityPortfolioManager.SetAccountCurrency(): " + - Messages.SecurityPortfolioManager.CannotChangeAccountCurrencyAfterSettingCash); + Messages.SecurityPortfolioManager.CannotChangeAccountCurrencyAfterSettingCash()); } Log.Trace("SecurityPortfolioManager.SetAccountCurrency(): " + diff --git a/Common/Securities/SecurityTransactionManager.cs b/Common/Securities/SecurityTransactionManager.cs index 4917cd2048f9..6c19823fafc2 100644 --- a/Common/Securities/SecurityTransactionManager.cs +++ b/Common/Securities/SecurityTransactionManager.cs @@ -251,7 +251,7 @@ public List CancelOpenOrders() { if (_algorithm != null && _algorithm.IsWarmingUp) { - throw new InvalidOperationException(Messages.SecurityTransactionManager.CancelOpenOrdersNotAllowedOnInitializeOrWarmUp); + throw new InvalidOperationException(Messages.SecurityTransactionManager.CancelOpenOrdersNotAllowedOnInitializeOrWarmUp()); } var cancelledOrders = new List(); @@ -273,7 +273,7 @@ public List CancelOpenOrders(Symbol symbol, string tag = null) { if (_algorithm != null && _algorithm.IsWarmingUp) { - throw new InvalidOperationException(Messages.SecurityTransactionManager.CancelOpenOrdersNotAllowedOnInitializeOrWarmUp); + throw new InvalidOperationException(Messages.SecurityTransactionManager.CancelOpenOrdersNotAllowedOnInitializeOrWarmUp()); } var cancelledOrders = new List(); diff --git a/Common/Securities/SymbolProperties.cs b/Common/Securities/SymbolProperties.cs index 9c3db7e38b65..d17642b4f27a 100644 --- a/Common/Securities/SymbolProperties.cs +++ b/Common/Securities/SymbolProperties.cs @@ -171,7 +171,7 @@ public SymbolPropertiesHolder(string description, string quoteCurrency, decimal if (LotSize <= 0) { - throw new ArgumentException(Messages.SymbolProperties.InvalidLotSize); + throw new ArgumentException(Messages.SymbolProperties.InvalidLotSize()); } MarketTicker = marketTicker; @@ -180,13 +180,13 @@ public SymbolPropertiesHolder(string description, string quoteCurrency, decimal PriceMagnifier = priceMagnifier; if (PriceMagnifier <= 0) { - throw new ArgumentException(Messages.SymbolProperties.InvalidPriceMagnifier); + throw new ArgumentException(Messages.SymbolProperties.InvalidPriceMagnifier()); } StrikeMultiplier = strikeMultiplier; if (strikeMultiplier <= 0) { - throw new ArgumentException(Messages.SymbolProperties.InvalidStrikeMultiplier); + throw new ArgumentException(Messages.SymbolProperties.InvalidStrikeMultiplier()); } } } diff --git a/Tests/Common/Orders/OrderTicketTests.cs b/Tests/Common/Orders/OrderTicketTests.cs index 86a07f11486a..e8c604e42a19 100644 --- a/Tests/Common/Orders/OrderTicketTests.cs +++ b/Tests/Common/Orders/OrderTicketTests.cs @@ -29,7 +29,7 @@ public class OrderTicketTests [Test] public void TestInvalidUpdateOrderId() { - var updateFields = new UpdateOrderFields { Quantity = 99, Tag = "Pepe", StopPrice = 77 , LimitPrice = 55 }; + var updateFields = new UpdateOrderFields { Quantity = 99, Tag = "Pepe", StopPrice = 77, LimitPrice = 55 }; var updateRequest = new UpdateOrderRequest(_requestTime, 11, updateFields); var ticket = OrderTicket.InvalidUpdateOrderId(null, updateRequest); Assert.AreEqual(11, ticket.OrderId); @@ -101,6 +101,11 @@ public void TestInvalidWarmingUp() Assert.AreEqual(1000, ticket.SubmitRequest.Quantity); Assert.AreEqual("Pepe", ticket.SubmitRequest.Tag); Assert.AreEqual("This operation is not allowed in Initialize or during warm up: OrderRequest.Submit. Please move this code to the OnWarmupFinished() method.", ticket.SubmitRequest.Response.ErrorMessage); + // Verify Python snake_case formatting + Messages.SetAlgorithmLanguage(Language.Python); + var pythonResponse = OrderResponse.WarmingUp(orderRequest); + Assert.AreEqual("This operation is not allowed in initialize or during warm up: OrderRequest.submit. Please move this code to the on_warmup_finished() method.", pythonResponse.ErrorMessage); + Messages.SetAlgorithmLanguage(Language.CSharp); } [TestCase(8, 0, true, Description = "8 AM - valid submission")] From 83b8be4012be4effe247907361162e9ea82a8190 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Fri, 20 Mar 2026 16:16:15 -0500 Subject: [PATCH 02/10] Minor fix --- Tests/Common/Orders/OrderTicketTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/Common/Orders/OrderTicketTests.cs b/Tests/Common/Orders/OrderTicketTests.cs index e8c604e42a19..e15963cad5f0 100644 --- a/Tests/Common/Orders/OrderTicketTests.cs +++ b/Tests/Common/Orders/OrderTicketTests.cs @@ -26,6 +26,12 @@ public class OrderTicketTests { private DateTime _requestTime = new DateTime(2022, 08, 25, 15, 0, 0); + [TearDown] + public void TearDown() + { + Messages.SetAlgorithmLanguage(Language.CSharp); + } + [Test] public void TestInvalidUpdateOrderId() { @@ -105,7 +111,6 @@ public void TestInvalidWarmingUp() Messages.SetAlgorithmLanguage(Language.Python); var pythonResponse = OrderResponse.WarmingUp(orderRequest); Assert.AreEqual("This operation is not allowed in initialize or during warm up: OrderRequest.submit. Please move this code to the on_warmup_finished() method.", pythonResponse.ErrorMessage); - Messages.SetAlgorithmLanguage(Language.CSharp); } [TestCase(8, 0, true, Description = "8 AM - valid submission")] From 0a97e52248024a2a6f0774fd676044ae78125d58 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Fri, 20 Mar 2026 16:35:59 -0500 Subject: [PATCH 03/10] improve FormatCode robustness and fix test state isolation --- Common/Messages/Messages.Orders.cs | 2 +- Common/Messages/Messages.QuantConnect.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Common/Messages/Messages.Orders.cs b/Common/Messages/Messages.Orders.cs index cba77cc7d09f..749d51416bd3 100644 --- a/Common/Messages/Messages.Orders.cs +++ b/Common/Messages/Messages.Orders.cs @@ -366,7 +366,7 @@ public static string MissingSecurity(Orders.SubmitOrderRequest request) public static string WarmingUp(Orders.OrderRequest request) { return Invariant($@"This operation is not allowed in {FormatCode("Initialize")} or during warm up: OrderRequest.{ - FormatCode(request.OrderRequestType.ToString())}. Please move this code to the {FormatCode("OnWarmupFinished")}() method."); + FormatCode(request.OrderRequestType)}. Please move this code to the {FormatCode("OnWarmupFinished")}() method."); } } diff --git a/Common/Messages/Messages.QuantConnect.cs b/Common/Messages/Messages.QuantConnect.cs index 9a5889cc0f58..9f23c863a9dd 100644 --- a/Common/Messages/Messages.QuantConnect.cs +++ b/Common/Messages/Messages.QuantConnect.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using Python.Runtime; @@ -53,6 +54,11 @@ private static string FormatCode(string code) }; } + private static string FormatCode(T value) where T : Enum + { + return string.Join(", ", value.ToString().Split(", ").Select(FormatCode)); + } + /// /// Provides user-facing messages for the class and its consumers or related classes /// From 06981834283b034c362addae4a0a76dacfbc71d4 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Mon, 23 Mar 2026 15:28:24 -0500 Subject: [PATCH 04/10] migrate QCAlgorithm inline messages to Messages classes --- Algorithm/QCAlgorithm.History.cs | 2 +- Algorithm/QCAlgorithm.cs | 19 ++-- .../Python/Wrappers/AlgorithmPythonWrapper.cs | 4 +- Common/Messages/Messages.Algorithm.cs | 100 ++++++++++++++++++ 4 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 Common/Messages/Messages.Algorithm.cs diff --git a/Algorithm/QCAlgorithm.History.cs b/Algorithm/QCAlgorithm.History.cs index 41caa59528cc..077b4cbda32f 100644 --- a/Algorithm/QCAlgorithm.History.cs +++ b/Algorithm/QCAlgorithm.History.cs @@ -1474,7 +1474,7 @@ private void SetWarmup(int? barCount, TimeSpan? timeSpan, Resolution? resolution { if (_locked) { - throw new InvalidOperationException("QCAlgorithm.SetWarmup(): This method cannot be used after algorithm initialized"); + throw new InvalidOperationException(Messages.QCAlgorithm.SetWarmupAlreadyInitialized()); } _warmupTimeSpan = timeSpan; diff --git a/Algorithm/QCAlgorithm.cs b/Algorithm/QCAlgorithm.cs index 28ae1f6e0802..28f1c352f359 100644 --- a/Algorithm/QCAlgorithm.cs +++ b/Algorithm/QCAlgorithm.cs @@ -1315,7 +1315,7 @@ public void SetTimeZone(DateTimeZone timeZone) { if (_locked) { - throw new InvalidOperationException("Algorithm.SetTimeZone(): Cannot change time zone after algorithm running."); + throw new InvalidOperationException(Messages.QCAlgorithm.SetTimeZoneAlreadyRunning()); } if (timeZone == null) throw new ArgumentNullException(nameof(timeZone)); @@ -1450,7 +1450,7 @@ public void SetBenchmark(SecurityType securityType, string symbol) { if (_locked) { - throw new InvalidOperationException("Algorithm.SetBenchmark(): Cannot change Benchmark after algorithm initialized."); + throw new InvalidOperationException(Messages.QCAlgorithm.SetBenchmarkAlreadyInitialized()); } var market = GetMarket(null, symbol, securityType, defaultMarket: Market.USA); @@ -1503,7 +1503,7 @@ public void SetBenchmark(Symbol symbol) { if (_locked) { - throw new InvalidOperationException("Algorithm.SetBenchmark(): Cannot change Benchmark after algorithm initialized."); + throw new InvalidOperationException(Messages.QCAlgorithm.SetBenchmarkAlreadyInitialized()); } // Create our security benchmark @@ -1522,7 +1522,7 @@ public void SetBenchmark(Func benchmark) { if (_locked) { - throw new InvalidOperationException("Algorithm.SetBenchmark(): Cannot change Benchmark after algorithm initialized."); + throw new InvalidOperationException(Messages.QCAlgorithm.SetBenchmarkAlreadyInitialized()); } Benchmark = new FuncBenchmark(benchmark); @@ -1599,8 +1599,7 @@ public void SetAccountCurrency(string accountCurrency, decimal? startingCash = n { if (_locked) { - throw new InvalidOperationException("Algorithm.SetAccountCurrency(): " + - "Cannot change AccountCurrency after algorithm initialized."); + throw new InvalidOperationException(Messages.QCAlgorithm.SetAccountCurrencyAlreadyInitialized()); } if (startingCash == null) @@ -1653,7 +1652,7 @@ public void SetCash(decimal startingCash) } else { - throw new InvalidOperationException("Algorithm.SetCash(): Cannot change cash available after algorithm initialized."); + throw new InvalidOperationException(Messages.QCAlgorithm.SetCashAlreadyInitialized()); } } @@ -1672,7 +1671,7 @@ public void SetCash(string symbol, decimal startingCash, decimal conversionRate } else { - throw new InvalidOperationException("Algorithm.SetCash(): Cannot change cash available after algorithm initialized."); + throw new InvalidOperationException(Messages.QCAlgorithm.SetCashAlreadyInitialized()); } } @@ -1778,7 +1777,7 @@ public void SetStartDate(DateTime start) } else { - throw new InvalidOperationException("Algorithm.SetStartDate(): Cannot change start date after algorithm initialized."); + throw new InvalidOperationException(Messages.QCAlgorithm.SetStartDateAlreadyInitialized()); } } @@ -1797,7 +1796,7 @@ public void SetEndDate(DateTime end) //1. Check not locked already: if (_locked) { - throw new InvalidOperationException("Algorithm.SetEndDate(): Cannot change end date after algorithm initialized."); + throw new InvalidOperationException(Messages.QCAlgorithm.SetEndDateAlreadyInitialized()); } //Validate: diff --git a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs index 62270336f42c..5ca48bfb1dd8 100644 --- a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs +++ b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs @@ -903,7 +903,7 @@ public void OnMarginCall(List requests) // If the method does not return or returns a non-iterable PyObject, throw an exception if (result == null || !result.IsIterable()) { - throw new Exception("OnMarginCall must return a non-empty list of SubmitOrderRequest"); + throw new Exception(Messages.AlgorithmPythonWrapper.OnMarginCallMustReturnNonEmptyList()); } requests.Clear(); @@ -921,7 +921,7 @@ public void OnMarginCall(List requests) // If the PyObject is an empty list or its items are not SubmitOrderRequest objects, throw an exception if (requests.Count == 0) { - throw new Exception("OnMarginCall must return a non-empty list of SubmitOrderRequest"); + throw new Exception(Messages.AlgorithmPythonWrapper.OnMarginCallMustReturnNonEmptyList()); } } } diff --git a/Common/Messages/Messages.Algorithm.cs b/Common/Messages/Messages.Algorithm.cs new file mode 100644 index 000000000000..14563dd893a0 --- /dev/null +++ b/Common/Messages/Messages.Algorithm.cs @@ -0,0 +1,100 @@ +/* + * 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. +*/ + +namespace QuantConnect +{ + /// + /// Provides user-facing message construction methods and static messages for the namespace + /// + public static partial class Messages + { + /// + /// Provides user-facing messages for the class and its consumers or related classes + /// + public static class QCAlgorithm + { + /// + /// Returns a string message saying the time zone cannot be changed after the algorithm is running + /// + public static string SetTimeZoneAlreadyRunning() + { + return $"Algorithm.{FormatCode("SetTimeZone")}(): Cannot change time zone after algorithm running."; + } + + /// + /// Returns a string message saying the benchmark cannot be changed after the algorithm is initialized + /// + public static string SetBenchmarkAlreadyInitialized() + { + return $"Algorithm.{FormatCode("SetBenchmark")}(): Cannot change Benchmark after algorithm initialized."; + } + + /// + /// Returns a string message saying the account currency cannot be changed after the algorithm is initialized + /// + public static string SetAccountCurrencyAlreadyInitialized() + { + return $"Algorithm.{FormatCode("SetAccountCurrency")}(): Cannot change AccountCurrency after algorithm initialized."; + } + + /// + /// Returns a string message saying the cash cannot be changed after the algorithm is initialized + /// + public static string SetCashAlreadyInitialized() + { + return $"Algorithm.{FormatCode("SetCash")}(): Cannot change cash available after algorithm initialized."; + } + + /// + /// Returns a string message saying the start date cannot be changed after the algorithm is initialized + /// + public static string SetStartDateAlreadyInitialized() + { + return $"Algorithm.{FormatCode("SetStartDate")}(): Cannot change start date after algorithm initialized."; + } + + /// + /// Returns a string message saying the end date cannot be changed after the algorithm is initialized + /// + public static string SetEndDateAlreadyInitialized() + { + return $"Algorithm.{FormatCode("SetEndDate")}(): Cannot change end date after algorithm initialized."; + } + + /// + /// Returns a string message saying SetWarmup cannot be used after the algorithm is initialized + /// + public static string SetWarmupAlreadyInitialized() + { + return $"QCAlgorithm.{FormatCode("SetWarmup")}(): This method cannot be used after algorithm initialized"; + } + } + + /// + /// Provides user-facing messages for the class + /// and its consumers or related classes + /// + public static class AlgorithmPythonWrapper + { + /// + /// Returns a string message saying OnMarginCall must return a non-empty list of SubmitOrderRequest + /// + public static string OnMarginCallMustReturnNonEmptyList() + { + return $"{FormatCode("OnMarginCall")} must return a non-empty list of SubmitOrderRequest"; + } + } + } +} From bdb7f4a67dd3442fdab7d3facf3d3f1c6aed0cad Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Mon, 23 Mar 2026 16:53:40 -0500 Subject: [PATCH 05/10] Solve review comments --- .../Messages.Algorithm.Framework.Portfolio.cs | 2 +- Common/Messages/Messages.Algorithm.cs | 22 ++++++++++++++----- Common/Messages/Messages.QuantConnect.cs | 4 ++-- Common/Messages/Messages.Securities.cs | 2 +- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs b/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs index 0ce90f8050af..6eb3f850ae16 100644 --- a/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs +++ b/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs @@ -39,7 +39,7 @@ public static class PortfolioTarget public static string InvalidTargetPercent(IAlgorithm algorithm, decimal percent) { return Invariant($@"The portfolio target percent: { - percent}, does not comply with the current 'Algorithm.Settings' '{FormatCode("MaxAbsolutePortfolioTargetPercentage")}': { + percent}, does not comply with the current 'QCAlgorithm.{FormatCode("Settings")}' '{FormatCode("MaxAbsolutePortfolioTargetPercentage")}': { algorithm.Settings.MaxAbsolutePortfolioTargetPercentage} or '{FormatCode("MinAbsolutePortfolioTargetPercentage")}': { algorithm.Settings.MinAbsolutePortfolioTargetPercentage}. Skipping"); } diff --git a/Common/Messages/Messages.Algorithm.cs b/Common/Messages/Messages.Algorithm.cs index 14563dd893a0..ef7f2d19613a 100644 --- a/Common/Messages/Messages.Algorithm.cs +++ b/Common/Messages/Messages.Algorithm.cs @@ -13,6 +13,8 @@ * limitations under the License. */ +using System.Runtime.CompilerServices; + namespace QuantConnect { /// @@ -28,54 +30,61 @@ public static class QCAlgorithm /// /// Returns a string message saying the time zone cannot be changed after the algorithm is running /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SetTimeZoneAlreadyRunning() { - return $"Algorithm.{FormatCode("SetTimeZone")}(): Cannot change time zone after algorithm running."; + return $"QCAlgorithm.{FormatCode("SetTimeZone")}(): Cannot change time zone after algorithm running."; } /// /// Returns a string message saying the benchmark cannot be changed after the algorithm is initialized /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SetBenchmarkAlreadyInitialized() { - return $"Algorithm.{FormatCode("SetBenchmark")}(): Cannot change Benchmark after algorithm initialized."; + return $"QCAlgorithm.{FormatCode("SetBenchmark")}(): Cannot change Benchmark after algorithm initialized."; } /// /// Returns a string message saying the account currency cannot be changed after the algorithm is initialized /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SetAccountCurrencyAlreadyInitialized() { - return $"Algorithm.{FormatCode("SetAccountCurrency")}(): Cannot change AccountCurrency after algorithm initialized."; + return $"QCAlgorithm.{FormatCode("SetAccountCurrency")}(): Cannot change AccountCurrency after algorithm initialized."; } /// /// Returns a string message saying the cash cannot be changed after the algorithm is initialized /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SetCashAlreadyInitialized() { - return $"Algorithm.{FormatCode("SetCash")}(): Cannot change cash available after algorithm initialized."; + return $"QCAlgorithm.{FormatCode("SetCash")}(): Cannot change cash available after algorithm initialized."; } /// /// Returns a string message saying the start date cannot be changed after the algorithm is initialized /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SetStartDateAlreadyInitialized() { - return $"Algorithm.{FormatCode("SetStartDate")}(): Cannot change start date after algorithm initialized."; + return $"QCAlgorithm.{FormatCode("SetStartDate")}(): Cannot change start date after algorithm initialized."; } /// /// Returns a string message saying the end date cannot be changed after the algorithm is initialized /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SetEndDateAlreadyInitialized() { - return $"Algorithm.{FormatCode("SetEndDate")}(): Cannot change end date after algorithm initialized."; + return $"QCAlgorithm.{FormatCode("SetEndDate")}(): Cannot change end date after algorithm initialized."; } /// /// Returns a string message saying SetWarmup cannot be used after the algorithm is initialized /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SetWarmupAlreadyInitialized() { return $"QCAlgorithm.{FormatCode("SetWarmup")}(): This method cannot be used after algorithm initialized"; @@ -91,6 +100,7 @@ public static class AlgorithmPythonWrapper /// /// Returns a string message saying OnMarginCall must return a non-empty list of SubmitOrderRequest /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string OnMarginCallMustReturnNonEmptyList() { return $"{FormatCode("OnMarginCall")} must return a non-empty list of SubmitOrderRequest"; diff --git a/Common/Messages/Messages.QuantConnect.cs b/Common/Messages/Messages.QuantConnect.cs index 9f23c863a9dd..7565ccff0d5b 100644 --- a/Common/Messages/Messages.QuantConnect.cs +++ b/Common/Messages/Messages.QuantConnect.cs @@ -311,7 +311,7 @@ public static string DownloadDataFailed(string url) public static string ZeroPriceForSecurity(QuantConnect.Symbol symbol) { return $"{symbol}: The security does not have an accurate price as it has not yet received a bar of data. " + - $"Before placing a trade (or using {FormatCode("SetHoldings")}) warm up your algorithm with {FormatCode("SetWarmup")}, or use slice.Contains(symbol) " + + $"Before placing a trade (or using {FormatCode("SetHoldings")}) warm up your algorithm with {FormatCode("SetWarmup")}, or use slice.{FormatCode("Contains")}(symbol) " + "to confirm the Slice object has price before using the data. Data does not necessarily all arrive at the same " + "time so your algorithm should confirm the data is ready before using it. In live trading this can mean you do " + "not have an active subscription to the asset class you're trying to trade. If using custom data make sure you've " + @@ -753,7 +753,7 @@ public static string ErrorParsingSecurityIdentifier(string value, Exception exce [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string MarketNotFound(string market) { - return $@"The specified market wasn't found in the markets lookup. Requested: {market}. You can add markets by calling QuantConnect.Market.Add(string,int)"; + return $@"The specified market wasn't found in the markets lookup. Requested: {market}. You can add markets by calling QuantConnect.Market.{FormatCode("Add")}(string,int)"; } } diff --git a/Common/Messages/Messages.Securities.cs b/Common/Messages/Messages.Securities.cs index dc54c3ead3a7..aa7b85ef2a9e 100644 --- a/Common/Messages/Messages.Securities.cs +++ b/Common/Messages/Messages.Securities.cs @@ -973,7 +973,7 @@ public static string CancelOpenOrdersNotAllowedOnInitializeOrWarmUp() [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string OrderCanceledByCancelOpenOrders(DateTime time) { - return Invariant($"Canceled by CancelOpenOrders() at {time:o}"); + return Invariant($"Canceled by {FormatCode("CancelOpenOrders")}() at {time:o}"); } /// From 00dde239deef773f699334b96f75ee40942d31ec Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Mon, 23 Mar 2026 16:59:23 -0500 Subject: [PATCH 06/10] Minor fix --- Common/Messages/Messages.QuantConnect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/Messages/Messages.QuantConnect.cs b/Common/Messages/Messages.QuantConnect.cs index 7565ccff0d5b..bb707074ad2a 100644 --- a/Common/Messages/Messages.QuantConnect.cs +++ b/Common/Messages/Messages.QuantConnect.cs @@ -753,7 +753,7 @@ public static string ErrorParsingSecurityIdentifier(string value, Exception exce [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string MarketNotFound(string market) { - return $@"The specified market wasn't found in the markets lookup. Requested: {market}. You can add markets by calling QuantConnect.Market.{FormatCode("Add")}(string,int)"; + return $@"The specified market wasn't found in the markets lookup. Requested: {market}. You can add markets by calling QuantConnect.Market.Add(string,int)"; } } From 3957ca9ee0019ca7cd175cb76a4b77a2c473b32c Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Mon, 23 Mar 2026 17:47:04 -0500 Subject: [PATCH 07/10] Fix broken regression tests --- Common/Messages/Messages.Securities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/Messages/Messages.Securities.cs b/Common/Messages/Messages.Securities.cs index aa7b85ef2a9e..dc54c3ead3a7 100644 --- a/Common/Messages/Messages.Securities.cs +++ b/Common/Messages/Messages.Securities.cs @@ -973,7 +973,7 @@ public static string CancelOpenOrdersNotAllowedOnInitializeOrWarmUp() [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string OrderCanceledByCancelOpenOrders(DateTime time) { - return Invariant($"Canceled by {FormatCode("CancelOpenOrders")}() at {time:o}"); + return Invariant($"Canceled by CancelOpenOrders() at {time:o}"); } /// From fcf033b61503ceb7c1e9dac0ab96f6e7888cd529 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Tue, 24 Mar 2026 09:20:37 -0500 Subject: [PATCH 08/10] Minor fix --- Common/Messages/Messages.Securities.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Common/Messages/Messages.Securities.cs b/Common/Messages/Messages.Securities.cs index dc54c3ead3a7..f262d186f502 100644 --- a/Common/Messages/Messages.Securities.cs +++ b/Common/Messages/Messages.Securities.cs @@ -886,6 +886,7 @@ public static class SecurityPortfolioManager /// Returns a string message saying the AccountCurrency cannot be changed after adding a Security and that the method /// SetAccountCurrency() should be moved before AddSecurity() /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string CannotChangeAccountCurrencyAfterAddingSecurity() { return $"Cannot change AccountCurrency after adding a Security. Please move {FormatCode("SetAccountCurrency")}() before {FormatCode("AddSecurity")}()."; @@ -895,6 +896,7 @@ public static string CannotChangeAccountCurrencyAfterAddingSecurity() /// Returns a string message saying the AccountCurrency cannot be changed after setting cash and that the method /// SetAccountCurrency() should be moved before SetCash() /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string CannotChangeAccountCurrencyAfterSettingCash() { return $"Cannot change AccountCurrency after setting cash. Please move {FormatCode("SetAccountCurrency")}() before {FormatCode("SetCash")}()."; @@ -962,6 +964,7 @@ public static class SecurityTransactionManager /// /// Returns a string message saying CancelOpenOrders operation is not allowed in Initialize or during warm up /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string CancelOpenOrdersNotAllowedOnInitializeOrWarmUp() { return $"This operation is not allowed in {FormatCode("Initialize")} or during warm up: {FormatCode("CancelOpenOrders")}. Please move this code to the {FormatCode("OnWarmupFinished")}() method."; @@ -1003,6 +1006,7 @@ public static class SymbolProperties /// /// String message saying the SymbolProperties LotSize can not be less than or equal to 0 /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string InvalidLotSize() { return $"{FormatCode("SymbolProperties")} {FormatCode("LotSize")} can not be less than or equal to 0"; @@ -1011,6 +1015,7 @@ public static string InvalidLotSize() /// /// String message saying the SymbolProperties PriceMagnifier can not be less than or equal to 0 /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string InvalidPriceMagnifier() { return $"{FormatCode("SymbolProperties")} {FormatCode("PriceMagnifier")} can not be less than or equal to 0"; @@ -1019,6 +1024,7 @@ public static string InvalidPriceMagnifier() /// /// String message saying the SymbolProperties StrikeMultiplier can not be less than or equal to 0 /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string InvalidStrikeMultiplier() { return $"{FormatCode("SymbolProperties")} {FormatCode("StrikeMultiplier")} can not be less than or equal to 0"; From b3dd2c233cebe49721436addcc31c16f20e25d06 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Tue, 24 Mar 2026 09:40:33 -0500 Subject: [PATCH 09/10] Minor fix --- Common/Messages/Messages.QuantConnect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/Messages/Messages.QuantConnect.cs b/Common/Messages/Messages.QuantConnect.cs index bb707074ad2a..7565ccff0d5b 100644 --- a/Common/Messages/Messages.QuantConnect.cs +++ b/Common/Messages/Messages.QuantConnect.cs @@ -753,7 +753,7 @@ public static string ErrorParsingSecurityIdentifier(string value, Exception exce [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string MarketNotFound(string market) { - return $@"The specified market wasn't found in the markets lookup. Requested: {market}. You can add markets by calling QuantConnect.Market.Add(string,int)"; + return $@"The specified market wasn't found in the markets lookup. Requested: {market}. You can add markets by calling QuantConnect.Market.{FormatCode("Add")}(string,int)"; } } From 1787a4915c56403a6ce23601ce7fe7bb36624da2 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Fri, 17 Apr 2026 16:22:00 -0500 Subject: [PATCH 10/10] Add FormatCodeRoot helper for Python self-referenced properties --- .../Messages/Messages.Algorithm.Framework.Portfolio.cs | 4 ++-- Common/Messages/Messages.QuantConnect.cs | 9 +++++++++ Common/Messages/Messages.Securities.cs | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs b/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs index 6eb3f850ae16..8c40abb2af5c 100644 --- a/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs +++ b/Common/Messages/Messages.Algorithm.Framework.Portfolio.cs @@ -39,8 +39,8 @@ public static class PortfolioTarget public static string InvalidTargetPercent(IAlgorithm algorithm, decimal percent) { return Invariant($@"The portfolio target percent: { - percent}, does not comply with the current 'QCAlgorithm.{FormatCode("Settings")}' '{FormatCode("MaxAbsolutePortfolioTargetPercentage")}': { - algorithm.Settings.MaxAbsolutePortfolioTargetPercentage} or '{FormatCode("MinAbsolutePortfolioTargetPercentage")}': { + percent}, does not comply with the current '{FormatCodeRoot("Settings")}.{FormatCode("MaxAbsolutePortfolioTargetPercentage")}': { + algorithm.Settings.MaxAbsolutePortfolioTargetPercentage} or '{FormatCodeRoot("Settings")}.{FormatCode("MinAbsolutePortfolioTargetPercentage")}': { algorithm.Settings.MinAbsolutePortfolioTargetPercentage}. Skipping"); } diff --git a/Common/Messages/Messages.QuantConnect.cs b/Common/Messages/Messages.QuantConnect.cs index 7565ccff0d5b..3efc58cd6c28 100644 --- a/Common/Messages/Messages.QuantConnect.cs +++ b/Common/Messages/Messages.QuantConnect.cs @@ -54,6 +54,15 @@ private static string FormatCode(string code) }; } + private static string FormatCodeRoot(string code) + { + return _algorithmLanguage switch + { + Language.Python => "self." + code.ToSnakeCase(), + _ => code + }; + } + private static string FormatCode(T value) where T : Enum { return string.Join(", ", value.ToString().Split(", ").Select(FormatCode)); diff --git a/Common/Messages/Messages.Securities.cs b/Common/Messages/Messages.Securities.cs index f262d186f502..1f030e24b599 100644 --- a/Common/Messages/Messages.Securities.cs +++ b/Common/Messages/Messages.Securities.cs @@ -110,7 +110,7 @@ public static string TargetOrderMarginNotAboveMinimum(decimal absDifferenceOfMar public static string TargetOrderMarginNotAboveMinimum() { return "Warning: Portfolio rebalance result ignored as it resulted in a single share trade recommendation which can generate high fees." + - $" To disable minimum order size checks please set {FormatCode("Settings")}.{FormatCode("MinimumOrderMarginPortfolioPercentage")} = 0."; + $" To disable minimum order size checks please set {FormatCodeRoot("Settings")}.{FormatCode("MinimumOrderMarginPortfolioPercentage")} = 0."; } ///