From aab3a6fc431d124c743858f0e185eac58f2c6515 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Wed, 1 Jul 2026 14:13:48 -0400 Subject: [PATCH 1/5] Add Price, Value and Close aliases to BaseContract Expose Price, Value and Close on BaseContract as aliases of LastPrice to mimic the BaseData/TradeBar API without inheriting BaseData. Marked with PandasIgnore to avoid duplicating the LastPrice column in chain DataFrames. --- Common/Data/Market/BaseContract.cs | 21 +++++++ .../Data/Market/FuturesContractTests.cs | 20 ++++++ .../Common/Data/Market/OptionContractTests.cs | 61 +++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 Tests/Common/Data/Market/OptionContractTests.cs diff --git a/Common/Data/Market/BaseContract.cs b/Common/Data/Market/BaseContract.cs index 9ae321f0e7d4..19110435d8f0 100644 --- a/Common/Data/Market/BaseContract.cs +++ b/Common/Data/Market/BaseContract.cs @@ -67,6 +67,27 @@ public DateTime Time /// public virtual decimal LastPrice { get; set; } + /// + /// Value representation of this contract, mimicking . + /// Aliases . + /// + [PandasIgnore] + public virtual decimal Value => LastPrice; + + /// + /// Alias of value as price, mimicking . + /// Aliases . + /// + [PandasIgnore] + public virtual decimal Price => LastPrice; + + /// + /// Closing price of this contract, mimicking . + /// Aliases . + /// + [PandasIgnore] + public virtual decimal Close => LastPrice; + /// /// Gets the last volume this contract traded at /// diff --git a/Tests/Common/Data/Market/FuturesContractTests.cs b/Tests/Common/Data/Market/FuturesContractTests.cs index 6a59e71b3bd5..0c8dba318459 100644 --- a/Tests/Common/Data/Market/FuturesContractTests.cs +++ b/Tests/Common/Data/Market/FuturesContractTests.cs @@ -91,6 +91,26 @@ public void TradeBarUpdate() Assert.AreEqual(0, futureContract.OpenInterest); } + [Test] + public void PriceValueAndCloseAliasLastPrice() + { + var futureContract = new FuturesContract(Symbols.Future_CLF19_Jan2019); + + // No data yet, all aliases default to zero + Assert.AreEqual(0, futureContract.LastPrice); + Assert.AreEqual(futureContract.LastPrice, futureContract.Price); + Assert.AreEqual(futureContract.LastPrice, futureContract.Value); + Assert.AreEqual(futureContract.LastPrice, futureContract.Close); + + var tradeBar = new TradeBar(new DateTime(2025, 12, 10), Symbols.Future_CLF19_Jan2019, 1, 2, 3, 4, 5); + futureContract.Update(tradeBar); + + Assert.AreEqual(4, futureContract.LastPrice); + Assert.AreEqual(futureContract.LastPrice, futureContract.Price); + Assert.AreEqual(futureContract.LastPrice, futureContract.Value); + Assert.AreEqual(futureContract.LastPrice, futureContract.Close); + } + [Test] public void OpenInterest() { diff --git a/Tests/Common/Data/Market/OptionContractTests.cs b/Tests/Common/Data/Market/OptionContractTests.cs new file mode 100644 index 000000000000..a92bd1b62eb8 --- /dev/null +++ b/Tests/Common/Data/Market/OptionContractTests.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 NUnit.Framework; +using QuantConnect.Data; +using QuantConnect.Data.Market; +using QuantConnect.Securities; +using QuantConnect.Securities.Option; + +namespace QuantConnect.Tests.Common.Data.Market +{ + [TestFixture] + public class OptionContractTests + { + private static Option CreateOption(Symbol symbol) + { + return new Option( + SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork), + new SubscriptionDataConfig(typeof(TradeBar), symbol, Resolution.Minute, TimeZones.NewYork, TimeZones.NewYork, true, true, true), + new Cash(Currencies.USD, 0, 1m), + new OptionSymbolProperties(SymbolProperties.GetDefault(Currencies.USD)), + ErrorCurrencyConverter.Instance, + RegisteredSecurityDataTypesProvider.Null + ); + } + + [Test] + public void PriceValueAndCloseAliasLastPrice() + { + var symbol = Symbols.SPY_C_192_Feb19_2016; + var contract = new OptionContract(CreateOption(symbol)) { Time = new DateTime(2016, 02, 16) }; + + // No data yet, all aliases default to zero + Assert.AreEqual(0, contract.LastPrice); + Assert.AreEqual(contract.LastPrice, contract.Price); + Assert.AreEqual(contract.LastPrice, contract.Value); + Assert.AreEqual(contract.LastPrice, contract.Close); + + var tradeBar = new TradeBar(new DateTime(2016, 02, 16), symbol, 1, 2, 3, 4, 5); + contract.Update(tradeBar); + + Assert.AreEqual(4, contract.LastPrice); + Assert.AreEqual(contract.LastPrice, contract.Price); + Assert.AreEqual(contract.LastPrice, contract.Value); + Assert.AreEqual(contract.LastPrice, contract.Close); + } + } +} From 340a7bbf2a64a85f6635dc27e2c62ec2d5744740 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Wed, 1 Jul 2026 14:50:26 -0400 Subject: [PATCH 2/5] Keep BaseContract chains DataFrame unchanged for Value alias The pandas converter force-includes the 'Value' member for non-Lean-data types (to preserve the value column of custom data types despite BaseData.Value being PandasIgnore'd). That override made the new BaseContract.Value alias leak into option/future chain DataFrames as a redundant 'value' column. Exclude BaseContract-derived types from the forced inclusion so their PandasIgnore'd aliases are honored and the chain DataFrames stay unchanged. --- Common/Python/PandasData.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Common/Python/PandasData.cs b/Common/Python/PandasData.cs index 9fecd394c7c4..397db63a41da 100644 --- a/Common/Python/PandasData.cs +++ b/Common/Python/PandasData.cs @@ -509,7 +509,10 @@ private List GetTypeMembers(Type type) { if (!_membersCache.TryGetValue(type, out typeMembers)) { - var forcedInclusionMembers = LeanData.IsCommonLeanDataType(type) + // Contracts (e.g. OptionContract, FuturesContract) expose their own representative price members + // (LastPrice, BidPrice, ...) and mark the BaseData-like aliases (Value, Price, Close) with + // PandasIgnore, so we don't want to force the Value member in as we do for custom data types. + var forcedInclusionMembers = LeanData.IsCommonLeanDataType(type) || typeof(BaseContract).IsAssignableFrom(type) ? Array.Empty() : _nonLeanDataTypeForcedMemberNames; typeMembers = GetDataTypeMembers(type, forcedInclusionMembers).ToList(); From 4fbce940838bed9215443b4503f74758f76c8ed1 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Wed, 1 Jul 2026 16:17:01 -0400 Subject: [PATCH 3/5] Isolate OptionContract alias test from shared price-model singleton OptionContract's default option data is the shared static OptionPriceModelResultData.Null singleton, which other tests mutate via Update. Reading LastPrice before setting up the contract's own data made the assertion depend on global test state (failing in CI with a leaked price). Assign a dedicated price model so the contract no longer reads from or writes to the shared singleton. --- Tests/Common/Data/Market/OptionContractTests.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Tests/Common/Data/Market/OptionContractTests.cs b/Tests/Common/Data/Market/OptionContractTests.cs index a92bd1b62eb8..e6dfb7f7532d 100644 --- a/Tests/Common/Data/Market/OptionContractTests.cs +++ b/Tests/Common/Data/Market/OptionContractTests.cs @@ -42,12 +42,9 @@ public void PriceValueAndCloseAliasLastPrice() { var symbol = Symbols.SPY_C_192_Feb19_2016; var contract = new OptionContract(CreateOption(symbol)) { Time = new DateTime(2016, 02, 16) }; - - // No data yet, all aliases default to zero - Assert.AreEqual(0, contract.LastPrice); - Assert.AreEqual(contract.LastPrice, contract.Price); - Assert.AreEqual(contract.LastPrice, contract.Value); - Assert.AreEqual(contract.LastPrice, contract.Close); + // Give the contract its own option data holder so it doesn't read from/write to the + // shared OptionPriceModelResultData.Null singleton, which other tests may have mutated. + contract.SetOptionPriceModel(() => OptionPriceModelResult.None); var tradeBar = new TradeBar(new DateTime(2016, 02, 16), symbol, 1, 2, 3, 4, 5); contract.Update(tradeBar); From 1d07d389298adadf64d634af4365b38db8e35edb Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Wed, 1 Jul 2026 16:23:26 -0400 Subject: [PATCH 4/5] Give each OptionContract its own default option data instead of a shared singleton OptionContract initialized its option data to the shared static OptionPriceModelResultData.Null singleton. Update() mutates that data, so contracts without an explicit price model shared and clobbered each other's trade/quote/open-interest state (a latent bug, and the cause of order-dependent test failures). Each contract now gets its own default OptionPriceModelResultData instance, and the alias test no longer needs to work around the shared state. --- Common/Data/Market/OptionContract.cs | 4 +--- Tests/Common/Data/Market/OptionContractTests.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Common/Data/Market/OptionContract.cs b/Common/Data/Market/OptionContract.cs index 3d44ec15f2c9..6326d1d57bab 100644 --- a/Common/Data/Market/OptionContract.cs +++ b/Common/Data/Market/OptionContract.cs @@ -26,7 +26,7 @@ namespace QuantConnect.Data.Market /// public class OptionContract : BaseContract { - private IOptionData _optionData = OptionPriceModelResultData.Null; + private IOptionData _optionData = new OptionPriceModelResultData(() => OptionPriceModelResult.None); private readonly SymbolProperties _symbolProperties; /// @@ -229,8 +229,6 @@ private interface IOptionData /// private class OptionPriceModelResultData : IOptionData { - public static readonly OptionPriceModelResultData Null = new(() => OptionPriceModelResult.None); - private readonly Lazy _optionPriceModelResult; private TradeBar _tradeBar; private QuoteBar _quoteBar; diff --git a/Tests/Common/Data/Market/OptionContractTests.cs b/Tests/Common/Data/Market/OptionContractTests.cs index e6dfb7f7532d..a92bd1b62eb8 100644 --- a/Tests/Common/Data/Market/OptionContractTests.cs +++ b/Tests/Common/Data/Market/OptionContractTests.cs @@ -42,9 +42,12 @@ public void PriceValueAndCloseAliasLastPrice() { var symbol = Symbols.SPY_C_192_Feb19_2016; var contract = new OptionContract(CreateOption(symbol)) { Time = new DateTime(2016, 02, 16) }; - // Give the contract its own option data holder so it doesn't read from/write to the - // shared OptionPriceModelResultData.Null singleton, which other tests may have mutated. - contract.SetOptionPriceModel(() => OptionPriceModelResult.None); + + // No data yet, all aliases default to zero + Assert.AreEqual(0, contract.LastPrice); + Assert.AreEqual(contract.LastPrice, contract.Price); + Assert.AreEqual(contract.LastPrice, contract.Value); + Assert.AreEqual(contract.LastPrice, contract.Close); var tradeBar = new TradeBar(new DateTime(2016, 02, 16), symbol, 1, 2, 3, 4, 5); contract.Update(tradeBar); From 0feb01c81b4524ada941e6aa13af79dca424cc1a Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Wed, 1 Jul 2026 16:59:20 -0400 Subject: [PATCH 5/5] Restore shared Null option data default and reset it in test SetUp Revert OptionContract back to the shared static OptionPriceModelResultData.Null default. To keep the alias test deterministic, reset that singleton in the fixture's SetUp by updating a throwaway contract with a zero-priced trade bar, and give the tested contract its own price model so its Update doesn't re-pollute the singleton. --- Common/Data/Market/OptionContract.cs | 4 +++- Tests/Common/Data/Market/OptionContractTests.cs | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Common/Data/Market/OptionContract.cs b/Common/Data/Market/OptionContract.cs index 6326d1d57bab..3d44ec15f2c9 100644 --- a/Common/Data/Market/OptionContract.cs +++ b/Common/Data/Market/OptionContract.cs @@ -26,7 +26,7 @@ namespace QuantConnect.Data.Market /// public class OptionContract : BaseContract { - private IOptionData _optionData = new OptionPriceModelResultData(() => OptionPriceModelResult.None); + private IOptionData _optionData = OptionPriceModelResultData.Null; private readonly SymbolProperties _symbolProperties; /// @@ -229,6 +229,8 @@ private interface IOptionData /// private class OptionPriceModelResultData : IOptionData { + public static readonly OptionPriceModelResultData Null = new(() => OptionPriceModelResult.None); + private readonly Lazy _optionPriceModelResult; private TradeBar _tradeBar; private QuoteBar _quoteBar; diff --git a/Tests/Common/Data/Market/OptionContractTests.cs b/Tests/Common/Data/Market/OptionContractTests.cs index a92bd1b62eb8..024821ba548c 100644 --- a/Tests/Common/Data/Market/OptionContractTests.cs +++ b/Tests/Common/Data/Market/OptionContractTests.cs @@ -37,11 +37,23 @@ private static Option CreateOption(Symbol symbol) ); } + [SetUp] + public void ResetSharedOptionData() + { + // Other tests can leave the shared OptionPriceModelResultData.Null singleton holding a + // trade bar, which then leaks into any contract that hasn't set its own price model. + // Reset it by updating a throwaway (singleton-backed) contract with a zero-priced trade bar. + var symbol = Symbols.SPY_C_192_Feb19_2016; + new OptionContract(CreateOption(symbol)) + .Update(new TradeBar(new DateTime(2016, 02, 16), symbol, 0, 0, 0, 0, 0)); + } + [Test] public void PriceValueAndCloseAliasLastPrice() { var symbol = Symbols.SPY_C_192_Feb19_2016; var contract = new OptionContract(CreateOption(symbol)) { Time = new DateTime(2016, 02, 16) }; + contract.SetOptionPriceModel(() => OptionPriceModelResult.None); // No data yet, all aliases default to zero Assert.AreEqual(0, contract.LastPrice);