From a8592e08b569246c59ecf5d84321f2c34a50a1f8 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 13 Dec 2024 18:23:31 -0400 Subject: [PATCH] Greeks calculation on expiration date (#8465) * Add helper method to calculate options expiration date time This allows to compute accurate time till expiry for greek indicators to be able to calculate on the actual expiration date before market close * Update tolerance in greek indicators tests * Minor fix * Modify helper method to calculate settlement time instead of expiration time * Cache option expiration date time * Minor changes * Minor changes --- ...tureOptionIndicatorsRegressionAlgorithm.cs | 2 +- ...ndexOptionIndicatorsRegressionAlgorithm.cs | 2 +- .../OptionIndicatorsRegressionAlgorithm.cs | 2 +- Common/Securities/Option/OptionSymbol.cs | 49 ++++++++++++++++--- Indicators/OptionGreekIndicatorBase.cs | 1 - Indicators/OptionIndicatorBase.cs | 14 +++++- Tests/Algorithm/AlgorithmIndicatorsTests.cs | 4 +- .../Securities/Options/OptionSymbolTests.cs | 26 ++++++++++ Tests/Indicators/DeltaTests.cs | 29 ++++++++++- Tests/Indicators/GammaTests.cs | 35 +++++++++++++ Tests/Indicators/ImpliedVolatilityTests.cs | 35 +++++++++++-- Tests/Indicators/RhoTests.cs | 29 ++++++++++- Tests/Indicators/ThetaTests.cs | 29 ++++++++++- Tests/Indicators/VegaTests.cs | 29 ++++++++++- 14 files changed, 262 insertions(+), 24 deletions(-) diff --git a/Algorithm.CSharp/FutureOptionIndicatorsRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionIndicatorsRegressionAlgorithm.cs index 96a22e915009..8a4e9674f079 100644 --- a/Algorithm.CSharp/FutureOptionIndicatorsRegressionAlgorithm.cs +++ b/Algorithm.CSharp/FutureOptionIndicatorsRegressionAlgorithm.cs @@ -22,7 +22,7 @@ namespace QuantConnect.Algorithm.CSharp { public class FutureOptionIndicatorsRegressionAlgorithm : OptionIndicatorsRegressionAlgorithm { - protected override string ExpectedGreeks { get; set; } = "Implied Volatility: 0.14008,Delta: 0.63466,Gamma: 0.00209,Vega: 5.61442,Theta: -0.48254,Rho: 0.03098"; + protected override string ExpectedGreeks { get; set; } = "Implied Volatility: 0.13941,Delta: 0.63509,Gamma: 0.00209,Vega: 5.64129,Theta: -0.47731,Rho: 0.03145"; public override void Initialize() { diff --git a/Algorithm.CSharp/IndexOptionIndicatorsRegressionAlgorithm.cs b/Algorithm.CSharp/IndexOptionIndicatorsRegressionAlgorithm.cs index 2d52e6a448eb..eaa6bd1f91f7 100644 --- a/Algorithm.CSharp/IndexOptionIndicatorsRegressionAlgorithm.cs +++ b/Algorithm.CSharp/IndexOptionIndicatorsRegressionAlgorithm.cs @@ -20,7 +20,7 @@ namespace QuantConnect.Algorithm.CSharp { public class IndexOptionIndicatorsRegressionAlgorithm : OptionIndicatorsRegressionAlgorithm { - protected override string ExpectedGreeks { get; set; } = "Implied Volatility: 0.17702,Delta: 0.19195,Gamma: 0.00247,Vega: 1.69043,Theta: -1.41571,Rho: 0.01686"; + protected override string ExpectedGreeks { get; set; } = "Implied Volatility: 0.17406,Delta: 0.19196,Gamma: 0.00247,Vega: 1.72195,Theta: -1.3689,Rho: 0.01744"; public override void Initialize() { diff --git a/Algorithm.CSharp/OptionIndicatorsRegressionAlgorithm.cs b/Algorithm.CSharp/OptionIndicatorsRegressionAlgorithm.cs index 6b5e0f68bb23..09aae25c7d8c 100644 --- a/Algorithm.CSharp/OptionIndicatorsRegressionAlgorithm.cs +++ b/Algorithm.CSharp/OptionIndicatorsRegressionAlgorithm.cs @@ -32,7 +32,7 @@ public class OptionIndicatorsRegressionAlgorithm : QCAlgorithm, IRegressionAlgor private Theta _theta; private Rho _rho; - protected virtual string ExpectedGreeks { get; set; } = "Implied Volatility: 0.45252,Delta: -0.0092,Gamma: 0.00036,Vega: 0.03562,Theta: -0.0387,Rho: 0.00045"; + protected virtual string ExpectedGreeks { get; set; } = "Implied Volatility: 0.44529,Delta: -0.00921,Gamma: 0.00036,Vega: 0.03636,Theta: -0.03747,Rho: 0.00047"; public override void Initialize() { diff --git a/Common/Securities/Option/OptionSymbol.cs b/Common/Securities/Option/OptionSymbol.cs index 6842a52ff9ec..9d2ac51404de 100644 --- a/Common/Securities/Option/OptionSymbol.cs +++ b/Common/Securities/Option/OptionSymbol.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using QuantConnect.Securities.Future; using QuantConnect.Securities.IndexOption; @@ -120,6 +121,27 @@ public static DateTime GetLastDayOfTrading(Symbol symbol) return symbolDateTime.AddDays(daysBefore).Date; } + /// + /// Returns the settlement date time of the option contract. + /// + /// The option contract symbol + /// The settlement date time + public static DateTime GetSettlementDateTime(Symbol symbol) + { + if (!TryGetExpirationDateTime(symbol, out var expiryTime, out var exchangeHours)) + { + throw new ArgumentException($"The symbol {symbol} is not an option type"); + } + + // Standard index options are AM-settled, which means they settle on market open of the expiration date + if (expiryTime.Date == symbol.ID.Date.Date && symbol.SecurityType == SecurityType.IndexOption && IsStandard(symbol)) + { + expiryTime = exchangeHours.GetNextMarketOpen(expiryTime.Date, false); + } + + return expiryTime; + } + /// /// Returns true if the option contract is expired at the specified time /// @@ -127,24 +149,35 @@ public static DateTime GetLastDayOfTrading(Symbol symbol) /// The current time (UTC) /// True if the option contract is expired at the specified time, false otherwise public static bool IsOptionContractExpired(Symbol symbol, DateTime currentTimeUtc) + { + if (TryGetExpirationDateTime(symbol, out var expiryTime, out var exchangeHours)) + { + var currentTime = currentTimeUtc.ConvertFromUtc(exchangeHours.TimeZone); + return currentTime >= expiryTime; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryGetExpirationDateTime(Symbol symbol, out DateTime expiryTime, out SecurityExchangeHours exchangeHours) { if (!symbol.SecurityType.IsOption()) { + expiryTime = default; + exchangeHours = null; return false; } - var exchangeHours = MarketHoursDatabase.FromDataFolder() - .GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType); - - var currentTime = currentTimeUtc.ConvertFromUtc(exchangeHours.TimeZone); + exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType); - // Ideally we can calculate expiry on the date of the symbol ID, but if that exchange is not open on that day we + // Ideally we can calculate expiry on the date of the symbol ID, but if that exchange is not open on that day we // will consider expired on the last trading day close before this; Example in AddOptionContractExpiresRegressionAlgorithm - var expiryDay = exchangeHours.IsDateOpen(symbol.ID.Date) + var lastTradingDay = exchangeHours.IsDateOpen(symbol.ID.Date) ? symbol.ID.Date : exchangeHours.GetPreviousTradingDay(symbol.ID.Date); - var expiryTime = exchangeHours.GetNextMarketClose(expiryDay, false); + expiryTime = exchangeHours.GetNextMarketClose(lastTradingDay, false); // Once bug 6189 was solved in ´GetNextMarketClose()´ there was found possible bugs on some futures symbol.ID.Date or delisting/liquidation handle event. // Specifically see 'DelistingFutureOptionRegressionAlgorithm' where Symbol.ID.Date: 4/1/2012 00:00 ExpiryTime: 4/2/2012 16:00 for Milk 3 futures options. @@ -163,7 +196,7 @@ public static bool IsOptionContractExpired(Symbol symbol, DateTime currentTimeUt expiryTime = symbol.ID.Date.AddDays(1).Date; } - return currentTime >= expiryTime; + return true; } } } diff --git a/Indicators/OptionGreekIndicatorBase.cs b/Indicators/OptionGreekIndicatorBase.cs index ae166efa0c23..2d40bd84d7b2 100644 --- a/Indicators/OptionGreekIndicatorBase.cs +++ b/Indicators/OptionGreekIndicatorBase.cs @@ -16,7 +16,6 @@ using System; using Python.Runtime; using QuantConnect.Data; -using QuantConnect.Logging; using QuantConnect.Python; namespace QuantConnect.Indicators diff --git a/Indicators/OptionIndicatorBase.cs b/Indicators/OptionIndicatorBase.cs index b8bef0a9ccdc..7b7ee0d8fa66 100644 --- a/Indicators/OptionIndicatorBase.cs +++ b/Indicators/OptionIndicatorBase.cs @@ -23,6 +23,8 @@ namespace QuantConnect.Indicators /// public abstract class OptionIndicatorBase : IndicatorBase, IIndicatorWarmUpPeriodProvider { + private DateTime _expiry; + /// /// Option's symbol object /// @@ -56,7 +58,17 @@ public abstract class OptionIndicatorBase : IndicatorBase, I /// /// Gets the expiration time of the option /// - public DateTime Expiry => OptionSymbol.ID.Date; + public DateTime Expiry + { + get + { + if (_expiry == default) + { + _expiry = Securities.Option.OptionSymbol.GetSettlementDateTime(OptionSymbol); + } + return _expiry; + } + } /// /// Gets the option right (call/put) of the option diff --git a/Tests/Algorithm/AlgorithmIndicatorsTests.cs b/Tests/Algorithm/AlgorithmIndicatorsTests.cs index 1175b73154cc..ce7a6f3427de 100644 --- a/Tests/Algorithm/AlgorithmIndicatorsTests.cs +++ b/Tests/Algorithm/AlgorithmIndicatorsTests.cs @@ -398,8 +398,8 @@ public void IndicatorUpdatedWithSymbol(string testCase) } Assert.IsTrue(indicator.IsReady); - Assert.AreEqual(0.9942984m, indicator.Current.Value); - Assert.AreEqual(0.3516544m, indicator.ImpliedVolatility.Current.Value); + Assert.AreEqual(0.9942989m, indicator.Current.Value); + Assert.AreEqual(0.3514844m, indicator.ImpliedVolatility.Current.Value); Assert.AreEqual(390, indicatorValues.Count); var lastData = indicatorValues.Current.Last(); diff --git a/Tests/Common/Securities/Options/OptionSymbolTests.cs b/Tests/Common/Securities/Options/OptionSymbolTests.cs index f3df9446505a..f87317d42b2d 100644 --- a/Tests/Common/Securities/Options/OptionSymbolTests.cs +++ b/Tests/Common/Securities/Options/OptionSymbolTests.cs @@ -14,6 +14,7 @@ */ using System; +using System.Collections.Generic; using NUnit.Framework; using QuantConnect.Securities.Option; @@ -69,5 +70,30 @@ public void IsOptionContractExpiredReturnsFalseIfTimeOfDayDiffer() Assert.IsFalse(OptionSymbol.IsOptionContractExpired(symbol, new DateTime(2022, 03, 11))); } + + private static IEnumerable ExpirationDateTimeTestCases() + { + var equityOption = Symbols.SPY_C_192_Feb19_2016; + yield return new TestCaseData(equityOption, new DateTime(2016, 02, 19, 16, 0, 0)); + + // Expires on a Saturday, so the expiration date time should be the Friday before + equityOption = Symbols.CreateOptionSymbol("SPY", OptionRight.Call, 192m, new DateTime(2016, 02, 20)); + yield return new TestCaseData(equityOption, new DateTime(2016, 02, 19, 16, 0, 0)); + + var pmSettledIndexOption = Symbol.CreateOption(Symbols.SPX, "SPXW", Market.USA, OptionStyle.European, + OptionRight.Call, 200m, new DateTime(2016, 02, 12)); + yield return new TestCaseData(pmSettledIndexOption, new DateTime(2016, 02, 12, 15, 15, 0)); + + var amSettledIndexOption = Symbol.CreateOption(Symbols.SPX, "SPX", Market.USA, OptionStyle.European, + OptionRight.Call, 200m, new DateTime(2016, 02, 18)); + yield return new TestCaseData(amSettledIndexOption, new DateTime(2016, 02, 18, 8, 30, 0)); + } + + [TestCaseSource(nameof(ExpirationDateTimeTestCases))] + public void CalculatesSettlementDateTime(Symbol symbol, DateTime expectedSettlementDateTime) + { + var settlementDateTime = OptionSymbol.GetSettlementDateTime(symbol); + Assert.AreEqual(expectedSettlementDateTime, settlementDateTime); + } } } diff --git a/Tests/Indicators/DeltaTests.cs b/Tests/Indicators/DeltaTests.cs index a12fa1c31247..5a1bbb84e6fb 100644 --- a/Tests/Indicators/DeltaTests.cs +++ b/Tests/Indicators/DeltaTests.cs @@ -17,6 +17,7 @@ using QuantConnect.Algorithm; using QuantConnect.Data; using QuantConnect.Indicators; +using System; using System.IO; using System.Linq; @@ -124,7 +125,33 @@ public void ComparesAgainstExternalData2(decimal price, decimal spotPrice, Optio indicator.Update(optionDataPoint); indicator.Update(spotDataPoint); - Assert.AreEqual(refDelta, (double)indicator.Current.Value, 0.0005d); + Assert.AreEqual(refDelta, (double)indicator.Current.Value, 0.0017d); + } + + [TestCase(0.5, 470.0, OptionRight.Put, 0)] + [TestCase(0.5, 470.0, OptionRight.Put, 5)] + [TestCase(0.5, 470.0, OptionRight.Put, 10)] + [TestCase(0.5, 470.0, OptionRight.Put, 15)] + [TestCase(15, 450.0, OptionRight.Call, 0)] + [TestCase(15, 450.0, OptionRight.Call, 5)] + [TestCase(15, 450.0, OptionRight.Call, 10)] + [TestCase(0.5, 450.0, OptionRight.Call, 15)] + public void CanComputeOnExpirationDate(decimal price, decimal spotPrice, OptionRight right, int hoursAfterExpiryDate) + { + var expiration = new DateTime(2024, 12, 6); + var symbol = Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, right, 450m, expiration); + var indicator = new Delta(symbol, 0.0403m, 0.0m, + optionModel: OptionPricingModelType.BinomialCoxRossRubinstein, ivModel: OptionPricingModelType.BlackScholes); + + var currentTime = expiration.AddHours(hoursAfterExpiryDate); + + var optionDataPoint = new IndicatorDataPoint(symbol, currentTime, price); + var spotDataPoint = new IndicatorDataPoint(symbol.Underlying, currentTime, spotPrice); + + Assert.IsFalse(indicator.Update(optionDataPoint)); + Assert.IsTrue(indicator.Update(spotDataPoint)); + + Assert.AreNotEqual(0, indicator.Current.Value); } } } diff --git a/Tests/Indicators/GammaTests.cs b/Tests/Indicators/GammaTests.cs index f85c2b3aa4cb..b0b889203405 100644 --- a/Tests/Indicators/GammaTests.cs +++ b/Tests/Indicators/GammaTests.cs @@ -17,6 +17,7 @@ using QuantConnect.Algorithm; using QuantConnect.Data; using QuantConnect.Indicators; +using System; using System.IO; using System.Linq; @@ -126,5 +127,39 @@ public void ComparesAgainstExternalData2(decimal price, decimal spotPrice, Optio Assert.AreEqual(refGamma, (double)indicator.Current.Value, 0.0005d); } + + [TestCase(0.5, 470.0, OptionRight.Put, OptionStyle.American, 0)] + [TestCase(0.5, 470.0, OptionRight.Put, OptionStyle.American, 5)] + [TestCase(0.5, 470.0, OptionRight.Put, OptionStyle.American, 10)] + [TestCase(0.5, 470.0, OptionRight.Put, OptionStyle.American, 15)] // Expires at 16:00 + [TestCase(15.0, 450.0, OptionRight.Call, OptionStyle.American, 0)] + [TestCase(15.0, 450.0, OptionRight.Call, OptionStyle.American, 5)] + [TestCase(15.0, 450.0, OptionRight.Call, OptionStyle.American, 10)] + [TestCase(15.0, 450.0, OptionRight.Call, OptionStyle.American, 15)] + [TestCase(0.5, 470.0, OptionRight.Put, OptionStyle.European, 0)] + [TestCase(0.5, 470.0, OptionRight.Put, OptionStyle.European, 5)] + [TestCase(0.5, 470.0, OptionRight.Put, OptionStyle.European, 10)] + [TestCase(0.5, 470.0, OptionRight.Put, OptionStyle.European, 15)] + [TestCase(0.5, 450.0, OptionRight.Call, OptionStyle.European, 0)] + [TestCase(0.5, 450.0, OptionRight.Call, OptionStyle.European, 5)] + [TestCase(0.5, 450.0, OptionRight.Call, OptionStyle.European, 10)] + [TestCase(0.5, 450.0, OptionRight.Call, OptionStyle.European, 15)] + public void CanComputeOnExpirationDate(decimal price, decimal spotPrice, OptionRight right, OptionStyle style, int hoursAfterExpiryDate) + { + var expiration = new DateTime(2024, 12, 6); + var symbol = Symbol.CreateOption("SPY", Market.USA, style, right, 450m, expiration); + var model = style == OptionStyle.European ? OptionPricingModelType.BlackScholes : OptionPricingModelType.BinomialCoxRossRubinstein; + var indicator = new Gamma(symbol, 0.0403m, 0.0m, optionModel: model, ivModel: OptionPricingModelType.BlackScholes); + + var currentTime = expiration.AddHours(hoursAfterExpiryDate); + + var optionDataPoint = new IndicatorDataPoint(symbol, currentTime, price); + var spotDataPoint = new IndicatorDataPoint(symbol.Underlying, currentTime, spotPrice); + + Assert.IsFalse(indicator.Update(optionDataPoint)); + Assert.IsTrue(indicator.Update(spotDataPoint)); + + Assert.AreNotEqual(0, indicator.Current.Value); + } } } diff --git a/Tests/Indicators/ImpliedVolatilityTests.cs b/Tests/Indicators/ImpliedVolatilityTests.cs index 1686cd76f381..0d1ba48732ad 100644 --- a/Tests/Indicators/ImpliedVolatilityTests.cs +++ b/Tests/Indicators/ImpliedVolatilityTests.cs @@ -106,7 +106,7 @@ public void SetSmoothingFunction(decimal price, decimal mirrorPrice, decimal spo indicator.Update(mirrorOptionDataPoint); indicator.Update(spotDataPoint); - Assert.AreEqual(refIV1, (double)indicator.Current.Value, 0.001d); + Assert.AreEqual(refIV1, (double)indicator.Current.Value, 0.0025d); indicator.SetSmoothingFunction((iv, mirrorIv) => iv); @@ -117,7 +117,7 @@ public void SetSmoothingFunction(decimal price, decimal mirrorPrice, decimal spo indicator.Update(mirrorOptionDataPoint); indicator.Update(spotDataPoint); - Assert.AreEqual(refIV2, (double)indicator.Current.Value, 0.001d); + Assert.AreEqual(refIV2, (double)indicator.Current.Value, 0.0035d); } [TestCase(23.753, 27.651, 450.0, OptionRight.Call, 60, 0.309, 0.309)] @@ -143,7 +143,7 @@ def TestSmoothingFunction(iv: float, mirror_iv: float) -> float: indicator.Update(mirrorOptionDataPoint); indicator.Update(spotDataPoint); - Assert.AreEqual(refIV1, (double)indicator.Current.Value, 0.001d); + Assert.AreEqual(refIV1, (double)indicator.Current.Value, 0.0025d); indicator.SetSmoothingFunction(pythonSmoothingFunction); @@ -154,7 +154,7 @@ def TestSmoothingFunction(iv: float, mirror_iv: float) -> float: indicator.Update(mirrorOptionDataPoint); indicator.Update(spotDataPoint); - Assert.AreEqual(refIV2, (double)indicator.Current.Value, 0.001d); + Assert.AreEqual(refIV2, (double)indicator.Current.Value, 0.0035d); } // Reference values from QuantLib @@ -180,7 +180,32 @@ public void ComparesAgainstExternalData2(decimal price, decimal spotPrice, Optio indicator.Update(optionDataPoint); indicator.Update(spotDataPoint); - Assert.AreEqual(refIV, (double)indicator.Current.Value, 0.001d); + Assert.AreEqual(refIV, (double)indicator.Current.Value, 0.0036d); + } + + [TestCase(0.5, 470.0, OptionRight.Put, 0)] + [TestCase(0.5, 470.0, OptionRight.Put, 5)] + [TestCase(0.5, 470.0, OptionRight.Put, 10)] + [TestCase(0.5, 470.0, OptionRight.Put, 15)] + [TestCase(15, 450.0, OptionRight.Call, 0)] + [TestCase(15, 450.0, OptionRight.Call, 5)] + [TestCase(15, 450.0, OptionRight.Call, 10)] + [TestCase(0.5, 450.0, OptionRight.Call, 15)] + public void CanComputeOnExpirationDate(decimal price, decimal spotPrice, OptionRight right, int hoursAfterExpiryDate) + { + var expiration = new DateTime(2024, 12, 6); + var symbol = Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, right, 450m, expiration); + var indicator = new ImpliedVolatility(symbol, 0.0530m, 0.0153m, optionModel: OptionPricingModelType.BlackScholes); + + var currentTime = expiration.AddHours(hoursAfterExpiryDate); + + var optionDataPoint = new IndicatorDataPoint(symbol, currentTime, price); + var spotDataPoint = new IndicatorDataPoint(symbol.Underlying, currentTime, spotPrice); + + Assert.IsFalse(indicator.Update(optionDataPoint)); + Assert.IsTrue(indicator.Update(spotDataPoint)); + + Assert.AreNotEqual(0, indicator.Current.Value); } [Test] diff --git a/Tests/Indicators/RhoTests.cs b/Tests/Indicators/RhoTests.cs index b6f547bc9daa..a4ef179b549a 100644 --- a/Tests/Indicators/RhoTests.cs +++ b/Tests/Indicators/RhoTests.cs @@ -17,6 +17,7 @@ using QuantConnect.Algorithm; using QuantConnect.Data; using QuantConnect.Indicators; +using System; namespace QuantConnect.Tests.Indicators { @@ -81,7 +82,33 @@ public void ComparesAgainstExternalData2(decimal price, decimal spotPrice, Optio indicator.Update(optionDataPoint); indicator.Update(spotDataPoint); - Assert.AreEqual(refRho, (double)indicator.Current.Value, 0.0011d); + Assert.AreEqual(refRho, (double)indicator.Current.Value, 0.017d); + } + + [TestCase(0.5, 470.0, OptionRight.Put, 0)] + [TestCase(0.5, 470.0, OptionRight.Put, 5)] + [TestCase(0.5, 470.0, OptionRight.Put, 10)] + [TestCase(0.5, 470.0, OptionRight.Put, 15)] + [TestCase(15.0, 450.0, OptionRight.Call, 0)] + [TestCase(15.0, 450.0, OptionRight.Call, 5)] + [TestCase(15.0, 450.0, OptionRight.Call, 10)] + [TestCase(0.5, 450.0, OptionRight.Call, 15)] + public void CanComputeOnExpirationDate(decimal price, decimal spotPrice, OptionRight right, int hoursAfterExpiryDate) + { + var expiration = new DateTime(2024, 12, 6); + var symbol = Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, right, 450m, expiration); + var indicator = new Rho(symbol, 0.053m, 0.0153m, + optionModel: OptionPricingModelType.BinomialCoxRossRubinstein, ivModel: OptionPricingModelType.BlackScholes); + + var currentTime = expiration.AddHours(hoursAfterExpiryDate); + + var optionDataPoint = new IndicatorDataPoint(symbol, currentTime, price); + var spotDataPoint = new IndicatorDataPoint(symbol.Underlying, currentTime, spotPrice); + + Assert.IsFalse(indicator.Update(optionDataPoint)); + Assert.IsTrue(indicator.Update(spotDataPoint)); + + Assert.AreNotEqual(0, indicator.Current.Value); } } } diff --git a/Tests/Indicators/ThetaTests.cs b/Tests/Indicators/ThetaTests.cs index a1619aeddc83..049e5c2d1add 100644 --- a/Tests/Indicators/ThetaTests.cs +++ b/Tests/Indicators/ThetaTests.cs @@ -17,6 +17,7 @@ using QuantConnect.Algorithm; using QuantConnect.Data; using QuantConnect.Indicators; +using System; using System.IO; using System.Linq; @@ -125,7 +126,33 @@ public void ComparesAgainstExternalData2(decimal price, decimal spotPrice, Optio indicator.Update(optionDataPoint); indicator.Update(spotDataPoint); - Assert.AreEqual(refTheta, (double)indicator.Current.Value, 0.00052d); + Assert.AreEqual(refTheta, (double)indicator.Current.Value, 0.0042d); + } + + [TestCase(0.5, 470.0, OptionRight.Put, 0)] + [TestCase(0.5, 470.0, OptionRight.Put, 5)] + [TestCase(0.5, 470.0, OptionRight.Put, 10)] + [TestCase(0.5, 470.0, OptionRight.Put, 15)] + [TestCase(15.0, 450.0, OptionRight.Call, 0)] + [TestCase(15.0, 450.0, OptionRight.Call, 5)] + [TestCase(15.0, 450.0, OptionRight.Call, 10)] + [TestCase(0.5, 450.0, OptionRight.Call, 15)] + public void CanComputeOnExpirationDate(decimal price, decimal spotPrice, OptionRight right, int hoursAfterExpiryDate) + { + var expiration = new DateTime(2024, 12, 6); + var symbol = Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, right, 450m, expiration); + var indicator = new Theta(symbol, 0.0403m, 0.0m, + optionModel: OptionPricingModelType.BinomialCoxRossRubinstein, ivModel: OptionPricingModelType.BlackScholes); + + var currentTime = expiration.AddHours(hoursAfterExpiryDate); + + var optionDataPoint = new IndicatorDataPoint(symbol, currentTime, price); + var spotDataPoint = new IndicatorDataPoint(symbol.Underlying, currentTime, spotPrice); + + Assert.IsFalse(indicator.Update(optionDataPoint)); + Assert.IsTrue(indicator.Update(spotDataPoint)); + + Assert.AreNotEqual(0, indicator.Current.Value); } } } diff --git a/Tests/Indicators/VegaTests.cs b/Tests/Indicators/VegaTests.cs index 9414f35a23da..0d77acbc9a6d 100644 --- a/Tests/Indicators/VegaTests.cs +++ b/Tests/Indicators/VegaTests.cs @@ -17,6 +17,7 @@ using QuantConnect.Algorithm; using QuantConnect.Data; using QuantConnect.Indicators; +using System; using System.IO; using System.Linq; @@ -125,7 +126,33 @@ public void ComparesAgainstExternalData2(decimal price, decimal spotPrice, Optio indicator.Update(optionDataPoint); indicator.Update(spotDataPoint); - Assert.AreEqual(refVega, (double)indicator.Current.Value, 0.0021d); + Assert.AreEqual(refVega, (double)indicator.Current.Value, 0.0072d); + } + + [TestCase(0.5, 470.0, OptionRight.Put, 0)] + [TestCase(0.5, 470.0, OptionRight.Put, 5)] + [TestCase(0.5, 470.0, OptionRight.Put, 10)] + [TestCase(0.5, 470.0, OptionRight.Put, 15)] + [TestCase(15.0, 450.0, OptionRight.Call, 0)] + [TestCase(15.0, 450.0, OptionRight.Call, 5)] + [TestCase(15.0, 450.0, OptionRight.Call, 10)] + [TestCase(0.5, 450.0, OptionRight.Call, 15)] + public void CanComputeOnExpirationDate(decimal price, decimal spotPrice, OptionRight right, int hoursAfterExpiryDate) + { + var expiration = new DateTime(2024, 12, 6); + var symbol = Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, right, 450m, expiration); + var indicator = new Vega(symbol, 0.053m, 0.0153m, + optionModel: OptionPricingModelType.BinomialCoxRossRubinstein, ivModel: OptionPricingModelType.BlackScholes); + + var currentTime = expiration.AddHours(hoursAfterExpiryDate); + + var optionDataPoint = new IndicatorDataPoint(symbol, currentTime, price); + var spotDataPoint = new IndicatorDataPoint(symbol.Underlying, currentTime, spotPrice); + + Assert.IsFalse(indicator.Update(optionDataPoint)); + Assert.IsTrue(indicator.Update(spotDataPoint)); + + Assert.AreNotEqual(0, indicator.Current.Value); } } }