From a15995011746363ba66741eed84ae827e989f01c Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Tue, 11 Jun 2024 13:50:33 -0400 Subject: [PATCH] Fix: refresh symbol properties using right type for security. (#8087) Fix for live trading SPDB refresh --- Common/Securities/IndexOption/IndexOption.cs | 11 +++ Common/Securities/Option/Option.cs | 11 +++ Common/Securities/Security.cs | 13 +++- Engine/RealTime/LiveTradingRealTimeHandler.cs | 2 +- .../LiveTradingRealTimeHandlerTests.cs | 76 +++++++++++++++++++ 5 files changed, 111 insertions(+), 2 deletions(-) diff --git a/Common/Securities/IndexOption/IndexOption.cs b/Common/Securities/IndexOption/IndexOption.cs index 2b044d7d0b22..341b9f3d6d30 100644 --- a/Common/Securities/IndexOption/IndexOption.cs +++ b/Common/Securities/IndexOption/IndexOption.cs @@ -83,5 +83,16 @@ protected override void UpdateConsumersMarketPrice(BaseData data) base.UpdateConsumersMarketPrice(data); ((IndexOptionSymbolProperties)SymbolProperties).UpdateMarketPrice(data); } + + /// + /// Updates the symbol properties of this security + /// + internal override void UpdateSymbolProperties(SymbolProperties symbolProperties) + { + if (symbolProperties != null) + { + SymbolProperties = new IndexOptionSymbolProperties(symbolProperties); + } + } } } diff --git a/Common/Securities/Option/Option.cs b/Common/Securities/Option/Option.cs index de50e3331d30..fe0a75bd14ed 100644 --- a/Common/Securities/Option/Option.cs +++ b/Common/Securities/Option/Option.cs @@ -668,5 +668,16 @@ private void SetFilterImp(Func unive return result.ApplyTypesFilter(); }); } + + /// + /// Updates the symbol properties of this security + /// + internal override void UpdateSymbolProperties(SymbolProperties symbolProperties) + { + if (symbolProperties != null) + { + SymbolProperties = new OptionSymbolProperties(symbolProperties); + } + } } } diff --git a/Common/Securities/Security.cs b/Common/Securities/Security.cs index 83e6e4d2322d..480c31848d52 100644 --- a/Common/Securities/Security.cs +++ b/Common/Securities/Security.cs @@ -100,7 +100,7 @@ public Cash QuoteCurrency public SymbolProperties SymbolProperties { get; - internal set; + protected set; } /// @@ -1146,5 +1146,16 @@ internal void ApplySplit(Split split) Cache.ApplySplit(split); UpdateMarketPrice(Cache.GetData()); } + + /// + /// Updates the symbol properties of this security + /// + internal virtual void UpdateSymbolProperties(SymbolProperties symbolProperties) + { + if (symbolProperties != null) + { + SymbolProperties = symbolProperties; + } + } } } diff --git a/Engine/RealTime/LiveTradingRealTimeHandler.cs b/Engine/RealTime/LiveTradingRealTimeHandler.cs index 32846e516392..9dc624bbcb87 100644 --- a/Engine/RealTime/LiveTradingRealTimeHandler.cs +++ b/Engine/RealTime/LiveTradingRealTimeHandler.cs @@ -242,7 +242,7 @@ protected virtual void UpdateSymbolProperties(Security security) { var symbolProperties = SymbolPropertiesDatabase.GetSymbolProperties(security.Symbol.ID.Market, security.Symbol, security.Symbol.ID.SecurityType, security.QuoteCurrency.Symbol); - security.SymbolProperties = symbolProperties; + security.UpdateSymbolProperties(symbolProperties); } /// diff --git a/Tests/Engine/RealTime/LiveTradingRealTimeHandlerTests.cs b/Tests/Engine/RealTime/LiveTradingRealTimeHandlerTests.cs index bae0eea6ecfb..b0c9dc37ea5a 100644 --- a/Tests/Engine/RealTime/LiveTradingRealTimeHandlerTests.cs +++ b/Tests/Engine/RealTime/LiveTradingRealTimeHandlerTests.cs @@ -35,6 +35,8 @@ using QuantConnect.Lean.Engine.HistoricalData; using QuantConnect.Lean.Engine.DataFeeds; using QuantConnect.Util; +using QuantConnect.Securities.Option; +using QuantConnect.Securities.IndexOption; namespace QuantConnect.Tests.Engine.RealTime { @@ -202,6 +204,80 @@ public void RefreshesSymbolProperties(string refreshPeriodStr) } } + [TestCase(SecurityType.Equity, typeof(SymbolProperties))] + [TestCase(SecurityType.Forex, typeof(SymbolProperties))] + [TestCase(SecurityType.Future, typeof(SymbolProperties))] + [TestCase(SecurityType.FutureOption, typeof(SymbolProperties))] + [TestCase(SecurityType.Cfd, typeof(SymbolProperties))] + [TestCase(SecurityType.Crypto, typeof(SymbolProperties))] + [TestCase(SecurityType.CryptoFuture, typeof(SymbolProperties))] + [TestCase(SecurityType.Index, typeof(SymbolProperties))] + [TestCase(SecurityType.Option, typeof(OptionSymbolProperties))] + [TestCase(SecurityType.IndexOption, typeof(IndexOptionSymbolProperties))] + public void SecuritySymbolPropertiesTypeIsRespectedAfterRefresh(SecurityType securityType, Type expectedSymbolPropertiesType) + { + using var realTimeHandler = new SPDBTestLiveTradingRealTimeHandler(); + + var timeProvider = realTimeHandler.PublicTimeProvider; + timeProvider.SetCurrentTimeUtc(new DateTime(2023, 5, 30)); + + var algorithm = new AlgorithmStub(); + var refreshPeriod = TimeSpan.FromDays(1); + algorithm.Settings.DatabasesRefreshPeriod = refreshPeriod; + + var symbol = GetSymbol(securityType); + var security = algorithm.AddSecurity(symbol); + + Assert.IsInstanceOf(expectedSymbolPropertiesType, security.SymbolProperties); + + realTimeHandler.Setup(algorithm, + new AlgorithmNodePacket(PacketType.AlgorithmNode), + new BacktestingResultHandler(), + null, + new TestTimeLimitManager()); + realTimeHandler.SpdbRefreshed.Reset(); + realTimeHandler.SecuritySymbolPropertiesUpdated.Reset(); + + algorithm.SetFinishedWarmingUp(); + realTimeHandler.SetTime(timeProvider.GetUtcNow()); + + var previousSymbolProperties = security.SymbolProperties; + + // Refresh the spdb + timeProvider.Advance(refreshPeriod); + Assert.IsTrue(realTimeHandler.SpdbRefreshed.WaitOne(1000)); + Assert.IsTrue(realTimeHandler.SecuritySymbolPropertiesUpdated.WaitOne(1000)); + + // Access the symbol properties again + // The instance must have been changed + Assert.AreNotSame(security.SymbolProperties, previousSymbolProperties); + Assert.IsInstanceOf(expectedSymbolPropertiesType, security.SymbolProperties); + } + + private static Symbol GetSymbol(SecurityType securityType) + { + return securityType switch + { + SecurityType.Equity => Symbols.SPY, + SecurityType.Forex => Symbols.USDJPY, + SecurityType.Future => Symbols.Future_ESZ18_Dec2018, + SecurityType.FutureOption => Symbol.CreateOption( + Symbols.Future_ESZ18_Dec2018, + Market.CME, + OptionStyle.American, + OptionRight.Call, + 4000m, + new DateTime(2023, 6, 16)), + SecurityType.Cfd => Symbols.DE10YBEUR, + SecurityType.Crypto => Symbols.BTCUSD, + SecurityType.CryptoFuture => Symbol.Create("BTCUSD", securityType, Market.Binance), + SecurityType.Index => Symbols.SPX, + SecurityType.Option => Symbols.SPY_C_192_Feb19_2016, + SecurityType.IndexOption => Symbol.Create("SPX", securityType, Market.USA), + _ => throw new ArgumentOutOfRangeException(nameof(securityType), securityType, null) + }; + } + private class TestTimeLimitManager : IIsolatorLimitResultProvider { public IsolatorLimitResult IsWithinLimit()