From 164c150f750959f3fbe5a008d1f4e8ace07a99e3 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Wed, 14 Oct 2026 17:10:35 -0400 Subject: [PATCH 1/3] Hide the OrderTicket int conversion from the stubs --- Common/Orders/OrderTicket.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Common/Orders/OrderTicket.cs b/Common/Orders/OrderTicket.cs index 1931335ed913..3d16f5a61985 100644 --- a/Common/Orders/OrderTicket.cs +++ b/Common/Orders/OrderTicket.cs @@ -692,6 +692,7 @@ public override string ToString() /// an error, where it will return the integer value of the from /// the most recent response /// + [StubsIgnore] public static implicit operator int(OrderTicket ticket) { var response = ticket.GetMostRecentOrderResponse(); From fcac3915edf2f2003cc203192d7da7c70f9a80a4 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Wed, 28 Oct 2026 11:51:24 -0400 Subject: [PATCH 2/3] Hide engine-only extension methods from Python instances with EditorBrowsable --- Common/Data/Auxiliary/PriceScalingExtensions.cs | 3 +++ Common/Data/SubscriptionDataConfigExtensions.cs | 6 ++++++ Common/Extensions.cs | 10 ++++++++++ Common/Orders/ContingentOrderExtensions.cs | 7 +++++++ Common/Orders/GroupOrderExtensions.cs | 2 ++ 5 files changed, 28 insertions(+) diff --git a/Common/Data/Auxiliary/PriceScalingExtensions.cs b/Common/Data/Auxiliary/PriceScalingExtensions.cs index 183fe2bba15a..6559393b85af 100644 --- a/Common/Data/Auxiliary/PriceScalingExtensions.cs +++ b/Common/Data/Auxiliary/PriceScalingExtensions.cs @@ -17,6 +17,7 @@ using System; using System.Linq; using System.Collections.Generic; +using System.ComponentModel; namespace QuantConnect.Data.Auxiliary { @@ -80,6 +81,7 @@ public static decimal GetPriceScale( /// Determines the symbol to use to fetch it's factor file /// /// This is useful for futures where the symbol to use is the canonical + [EditorBrowsable(EditorBrowsableState.Never)] public static Symbol GetFactorFileSymbol(this Symbol symbol) { return symbol.SecurityType == SecurityType.Future ? symbol.Canonical : symbol; @@ -88,6 +90,7 @@ public static Symbol GetFactorFileSymbol(this Symbol symbol) /// /// Helper method to return an empty factor file /// + [EditorBrowsable(EditorBrowsableState.Never)] public static IFactorProvider GetEmptyFactorFile(this Symbol symbol) { if (symbol.SecurityType == SecurityType.Future) diff --git a/Common/Data/SubscriptionDataConfigExtensions.cs b/Common/Data/SubscriptionDataConfigExtensions.cs index 2a47c5788f50..2f5f19cbd209 100644 --- a/Common/Data/SubscriptionDataConfigExtensions.cs +++ b/Common/Data/SubscriptionDataConfigExtensions.cs @@ -14,6 +14,7 @@ */ using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using QuantConnect.Util; @@ -114,6 +115,7 @@ public static void SetDataNormalizationMode( /// One of the objectives of this method is to normalize the 'use mapping' /// check and void code duplication and related issues /// True if ticker should be mapped + [EditorBrowsable(EditorBrowsableState.Never)] public static bool TickerShouldBeMapped(this SubscriptionDataConfig config) { // we create an instance of the data type, if it is a custom type @@ -129,6 +131,7 @@ public static bool TickerShouldBeMapped(this SubscriptionDataConfig config) /// check and void code duplication and related issues /// True, is this is a live mode data stream /// True if ticker prices should be scaled + [EditorBrowsable(EditorBrowsableState.Never)] public static bool PricesShouldBeScaled(this SubscriptionDataConfig config, bool liveMode = false) { if (config.IsCustomData || config.Symbol.Value.Contains("UNIVERSE")) @@ -155,6 +158,7 @@ public static bool PricesShouldBeScaled(this SubscriptionDataConfig config, bool /// Different than because prices could be scale and no split and dividends /// really exist, like in the continuous futures case /// True if this configuration requires split and divided handling + [EditorBrowsable(EditorBrowsableState.Never)] public static bool EmitSplitsAndDividends(this SubscriptionDataConfig config) { return !config.IsCustomData && !config.Symbol.Value.Contains("UNIVERSE") && config.SecurityType == SecurityType.Equity; @@ -163,6 +167,7 @@ public static bool EmitSplitsAndDividends(this SubscriptionDataConfig config) /// /// True if this configuration is associated with an asset which can have delisting events /// + [EditorBrowsable(EditorBrowsableState.Never)] public static bool CanBeDelisted(this SubscriptionDataConfig config) { return config.SecurityType.IsOption() || config.SecurityType == SecurityType.Future || config.SecurityType == SecurityType.Equity; @@ -171,6 +176,7 @@ public static bool CanBeDelisted(this SubscriptionDataConfig config) /// /// Initializes a new instance of the type defined in with the symbol properly set /// + [EditorBrowsable(EditorBrowsableState.Never)] public static BaseData GetBaseDataInstance(this SubscriptionDataConfig config) { var instance = config.Type.GetBaseDataInstance(); diff --git a/Common/Extensions.cs b/Common/Extensions.cs index e8405ee334c6..d3f0a7bde027 100644 --- a/Common/Extensions.cs +++ b/Common/Extensions.cs @@ -13,6 +13,7 @@ * limitations under the License. */ +using System.ComponentModel; using System; using System.Collections; using System.Collections.Concurrent; @@ -514,6 +515,7 @@ public static void ProtobufSerialize(this IBaseData baseData, Stream stream) /// Extension method to get security price is 0 messages for users /// /// The value of this method is normalization + [EditorBrowsable(EditorBrowsableState.Never)] public static string GetZeroPriceMessage(this Symbol symbol) { return Messages.Extensions.ZeroPriceForSecurity(symbol); @@ -2810,6 +2812,7 @@ public static string ResolutionToLower(this Resolution resolution) /// The being converted /// The transaction manager, /// + [EditorBrowsable(EditorBrowsableState.Never)] public static OrderTicket ToOrderTicket(this Order order, SecurityTransactionManager transactionManager) { var limitPrice = 0m; @@ -3559,6 +3562,7 @@ public static string RemoveFromStart(this string s, string start) /// Helper method to determine symbol for a live subscription /// /// Useful for continuous futures where we subscribe to the underlying + [EditorBrowsable(EditorBrowsableState.Never)] public static bool TryGetLiveSubscriptionSymbol(this Symbol symbol, out Symbol mapped) { mapped = null; @@ -3599,6 +3603,7 @@ public static DateTime GetDelistingDate(this Symbol symbol, MapFile mapFile = nu /// /// Helper method to determine if a given symbol is of custom data /// + [EditorBrowsable(EditorBrowsableState.Never)] public static bool IsCustomDataType(this Symbol symbol) { return symbol.SecurityType == SecurityType.Base @@ -4049,6 +4054,7 @@ public static OrderDirection GetOrderDirection(decimal quantity) /// /// Helper method to process an algorithms security changes, will add and remove securities according to them /// + [EditorBrowsable(EditorBrowsableState.Never)] public static void ProcessSecurityChanges(this IAlgorithm algorithm, SecurityChanges securityChanges) { foreach (var security in securityChanges.AddedSecurities) @@ -4082,6 +4088,7 @@ public static void MakeTradable(this Security security) /// /// Helper method to set an algorithm runtime exception in a normalized fashion /// + [EditorBrowsable(EditorBrowsableState.Never)] public static void SetRuntimeError(this IAlgorithm algorithm, Exception exception, string context) { Log.Error(exception, $"Extensions.SetRuntimeError(): {Messages.Extensions.RuntimeError(algorithm, context)}"); @@ -4216,6 +4223,7 @@ private static IEnumerable CreateFutureChain(this IAlgorithm algorithm /// The found or added security instance /// Callback to invoke in case of unsupported security type /// True if the security was found or added + [EditorBrowsable(EditorBrowsableState.Never)] public static bool GetOrAddUnrequestedSecurity(this IAlgorithm algorithm, Symbol symbol, out Security security, Action> onError = null) { @@ -4340,6 +4348,7 @@ public static SubscriptionDataConfig ToSubscriptionDataConfig(this Data.HistoryR /// dependency with the FF enumerators requiring that they receive aux data to properly handle delistings. /// Otherwise we would have issues with delisted symbols continuing to fill forward after expiry/delisting. /// Reference PR #5485 and related issues for more. + [EditorBrowsable(EditorBrowsableState.Never)] public static bool ShouldEmitData(this SubscriptionDataConfig config, BaseData data, bool isUniverse = false) { // For now we are only filtering Auxiliary data; so if its another type just return true or if it's a margin interest rate which we want to emit always @@ -4494,6 +4503,7 @@ public static int GetListHashCode(this IReadOnlyList list) /// /// Type to check /// True if it needs to be mapped + [EditorBrowsable(EditorBrowsableState.Never)] public static bool RequiresMapping(this Symbol symbol) { switch (symbol.SecurityType) diff --git a/Common/Orders/ContingentOrderExtensions.cs b/Common/Orders/ContingentOrderExtensions.cs index cb0a408220d5..5dc3852fe43d 100644 --- a/Common/Orders/ContingentOrderExtensions.cs +++ b/Common/Orders/ContingentOrderExtensions.cs @@ -17,6 +17,7 @@ using System.Linq; using QuantConnect.Logging; using System.Collections.Generic; +using System.ComponentModel; namespace QuantConnect.Orders { @@ -113,6 +114,7 @@ public static bool IsSameGroupOrder(this Order order, Order other) /// List of orders in the set, sorted by id /// False if any of the orders in the set is not yet found in the order provider. True otherwise /// If the target order is not a contingent order, the resulting list will contain that single order alone + [EditorBrowsable(EditorBrowsableState.Never)] public static bool TryGetContingentOrders(this Order order, Func orderProvider, out List orders) { var contingency = order.Contingency; @@ -166,6 +168,7 @@ public static bool TryGetContingentOrders(this Order order, Func ord /// Target order, which can be any of the orders in the set /// Order provider to use to access the existing orders /// The existing orders of the set, including the given one, sorted by id + [EditorBrowsable(EditorBrowsableState.Never)] public static List GetExistingContingentOrders(this Order order, Func orderProvider) { var contingency = order.Contingency; @@ -196,6 +199,7 @@ public static List GetExistingContingentOrders(this Order order, Func /// The parent order /// The orders in the set + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable GetContingentChildren(this Order order, IEnumerable contingentOrders) { var parent = order.GetContingencyLink(ContingencyRole.Parent); @@ -211,6 +215,7 @@ public static IEnumerable GetContingentChildren(this Order order, IEnumer /// /// The child order /// The orders in the set + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable GetContingentParents(this Order order, IEnumerable contingentOrders) { var child = order.GetContingencyLink(ContingencyRole.Child); @@ -227,6 +232,7 @@ public static IEnumerable GetContingentParents(this Order order, IEnumera /// /// The member order /// The orders in the set + [EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable GetContingentSiblings(this Order order, IEnumerable contingentOrders) { if (order.GetSiblingLink() == null) @@ -241,6 +247,7 @@ public static IEnumerable GetContingentSiblings(this Order order, IEnumer /// /// The parent order /// The orders in the set + [EditorBrowsable(EditorBrowsableState.Never)] public static List GetContingentDescendants(this Order order, IReadOnlyCollection contingentOrders) { var result = new List(); diff --git a/Common/Orders/GroupOrderExtensions.cs b/Common/Orders/GroupOrderExtensions.cs index 21848d8c62b1..2df9454fdedf 100644 --- a/Common/Orders/GroupOrderExtensions.cs +++ b/Common/Orders/GroupOrderExtensions.cs @@ -18,6 +18,7 @@ using QuantConnect.Logging; using QuantConnect.Securities; using System.Collections.Generic; +using System.ComponentModel; namespace QuantConnect.Orders { @@ -34,6 +35,7 @@ public static class GroupOrderExtensions /// List of orders in the combo /// False if any of the orders in the combo is not yet found in the order provider. True otherwise /// If the target order is not a combo order, the resulting list will contain that single order alone + [EditorBrowsable(EditorBrowsableState.Never)] public static bool TryGetGroupOrders(this Order order, Func orderProvider, out List orders) { orders = new List { order }; From 8dc12d2de3b341794ff02c405f9830c4af421f26 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Thu, 29 Oct 2026 12:03:11 -0400 Subject: [PATCH 3/3] Test that hidden extension methods are not bound to Python instances --- Tests/Common/Util/ExtensionsTests.cs | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Tests/Common/Util/ExtensionsTests.cs b/Tests/Common/Util/ExtensionsTests.cs index 19bad6fcfbe1..fd3d57bf1ebf 100644 --- a/Tests/Common/Util/ExtensionsTests.cs +++ b/Tests/Common/Util/ExtensionsTests.cs @@ -116,6 +116,43 @@ public void DeserializeObjectList(string input, int count) } } + [Test] + public void EditorBrowsableNeverExtensionMethodsAreNotBoundToPythonInstances() + { + var order = new MarketOrder(Symbols.SPY, 100, DateTime.UtcNow); + var config = new SubscriptionDataConfig(typeof(TradeBar), Symbols.SPY, Resolution.Minute, TimeZones.NewYork, + TimeZones.NewYork, true, true, false); + + using (Py.GIL()) + { + var module = PyModule.FromString("testModule", + @" +from AlgorithmImports import * + +def check(symbol: Symbol, order: Order, config: SubscriptionDataConfig): + hidden = (hasattr(symbol, 'requires_mapping'), hasattr(order, 'try_get_contingent_orders'), hasattr(config, 'ticker_should_be_mapped')) + visible = (hasattr(order, 'is_contingent'), order.is_contingent(), OrderStatus.FILLED.is_open()) + static_forms = (Extensions.requires_mapping(symbol), SubscriptionDataConfigExtensions.ticker_should_be_mapped(config)) + return hidden, visible, static_forms +"); + + dynamic check = module.GetAttr("check"); + var result = check(Symbols.SPY, order, config); + + // hidden extensions are not bound to the instance + Assert.IsFalse((bool)result[0][0]); + Assert.IsFalse((bool)result[0][1]); + Assert.IsFalse((bool)result[0][2]); + // the other extensions are + Assert.IsTrue((bool)result[1][0]); + Assert.IsFalse((bool)result[1][1]); + Assert.IsFalse((bool)result[1][2]); + // the static forms still work + Assert.AreEqual(Symbols.SPY.RequiresMapping(), (bool)result[2][0]); + Assert.AreEqual(config.TickerShouldBeMapped(), (bool)result[2][1]); + } + } + [TestCase(true)] [TestCase(false)] public void ConvertPythonSymbolEnumerableSingle(bool useSymbol)