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); } } }