/* * 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 QuantConnect.Data; using QuantConnect.Python; namespace QuantConnect.Indicators { /// /// To provide a base class for option indicator /// public abstract class OptionIndicatorBase : MultiSymbolIndicator { private DateTime _expiry; /// /// Option's symbol object /// [PandasIgnore] public Symbol OptionSymbol { get; init; } /// /// Mirror option symbol (by option right), for implied volatility /// protected Symbol _oppositeOptionSymbol { get; private set; } /// /// Underlying security's symbol object /// protected Symbol _underlyingSymbol => OptionSymbol.Underlying; /// /// Option pricing model used to calculate indicator /// protected OptionPricingModelType _optionModel { get; set; } /// /// Risk-free rate model /// protected IRiskFreeInterestRateModel _riskFreeInterestRateModel { get; init; } /// /// Dividend yield model, for continuous dividend yield /// protected IDividendYieldModel _dividendYieldModel { get; init; } /// /// Gets the expiration time of the option /// [PandasIgnore] public DateTime Expiry { get { if (_expiry == default) { _expiry = Securities.Option.OptionSymbol.GetSettlementDateTime(OptionSymbol); } return _expiry; } } /// /// Gets the option right (call/put) of the option /// [PandasIgnore] public OptionRight Right => OptionSymbol.ID.OptionRight; /// /// Gets the strike price of the option /// [PandasIgnore] public decimal Strike => OptionSymbol.ID.StrikePrice; /// /// Gets the option style (European/American) of the option /// [PandasIgnore] public OptionStyle Style => OptionSymbol.ID.OptionStyle; /// /// Risk Free Rate /// [PandasIgnore] public Identity RiskFreeRate { get; set; } /// /// Dividend Yield /// [PandasIgnore] public Identity DividendYield { get; set; } /// /// Gets the option price level /// public IndicatorBase Price { get; } /// /// Gets the mirror option price level, for implied volatility /// public IndicatorBase OppositePrice { get; private set; } /// /// Gets the underlying's price level /// public IndicatorBase UnderlyingPrice { get; } /// /// Flag if mirror option is implemented for parity type calculation /// [PandasIgnore] public bool UseMirrorContract => _oppositeOptionSymbol != null; /// /// Initializes a new instance of the OptionIndicatorBase class /// /// The name of this indicator /// The option to be tracked /// Risk-free rate model /// Dividend yield model /// The mirror option for parity calculation /// The lookback period of volatility /// The option pricing model used to estimate the Greek/IV protected OptionIndicatorBase(string name, Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, IDividendYieldModel dividendYieldModel, Symbol mirrorOption = null, OptionPricingModelType? optionModel = null, int period = 1) : base(name, mirrorOption == null ? [option, option.Underlying] : [option, option.Underlying, mirrorOption], period) { var sid = option.ID; if (!sid.SecurityType.IsOption()) { throw new ArgumentException("OptionIndicatorBase only support SecurityType.Option."); } OptionSymbol = option; _riskFreeInterestRateModel = riskFreeRateModel; _dividendYieldModel = dividendYieldModel; _optionModel = optionModel ?? GetOptionModel(optionModel, sid.OptionStyle); RiskFreeRate = new Identity(name + "_RiskFreeRate"); DividendYield = new Identity(name + "_DividendYield"); Price = new Identity(name + "_Close"); UnderlyingPrice = new Identity(name + "_UnderlyingClose"); DataBySymbol[OptionSymbol].NewInput += (sender, input) => Price.Update(input); DataBySymbol[_underlyingSymbol].NewInput += (sender, input) => UnderlyingPrice.Update(input); if (mirrorOption != null) { _oppositeOptionSymbol = mirrorOption; OppositePrice = new Identity(Name + "_OppositeClose"); DataBySymbol[_oppositeOptionSymbol].NewInput += (sender, input) => OppositePrice.Update(input); } } /// /// Computes the next value of this indicator from the given state. /// This will round the result to 7 decimal places. /// /// The input given to the indicator /// A new value for this indicator protected override decimal ComputeNextValue(IBaseData input) { return Math.Round(base.ComputeNextValue(input), 7); } /// /// Resets this indicator and all sub-indicators /// public override void Reset() { RiskFreeRate.Reset(); DividendYield.Reset(); Price.Reset(); UnderlyingPrice.Reset(); base.Reset(); if (UseMirrorContract) { OppositePrice.Reset(); } } /// /// Gets the option pricing model based on the option style, if not specified /// /// The optional option pricing model, which will be returned if not null /// The option style /// The option pricing model based on the option style, if not specified public static OptionPricingModelType GetOptionModel(OptionPricingModelType? optionModel, OptionStyle optionStyle) { if (optionModel.HasValue) { return optionModel.Value; } // Default values depend on the option style return optionStyle switch { OptionStyle.European => OptionPricingModelType.BlackScholes, OptionStyle.American => OptionPricingModelType.ForwardTree, _ => throw new ArgumentOutOfRangeException(nameof(optionStyle), optionStyle, null) }; } } }