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/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(); 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..024821ba548c --- /dev/null +++ b/Tests/Common/Data/Market/OptionContractTests.cs @@ -0,0 +1,73 @@ +/* + * 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 + ); + } + + [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); + 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); + } + } +}