Skip to content

Commit

Permalink
Option Delta Indicator (#7704)
Browse files Browse the repository at this point in the history
* Add base indicator for greeks

* Add option delta indicator

* Add helper method

* Add unit tests

* Address peer review

* Fix minor bugs

* Change OptionDelta into Delta

* rename helper method
  • Loading branch information
LouisSzeto authored Jan 29, 2024
1 parent 00e1cae commit 3a10afe
Show file tree
Hide file tree
Showing 11 changed files with 971 additions and 278 deletions.
42 changes: 42 additions & 0 deletions Algorithm/QCAlgorithm.Indicators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,48 @@ public DonchianChannel DCH(Symbol symbol, int period, Resolution? resolution = n
return DCH(symbol, period, period, resolution, selector);
}

/// <summary>
/// Creates a new Delta indicator for the symbol The indicator will be automatically
/// updated on the symbol's subscription resolution
/// </summary>
/// <param name="symbol">The option symbol whose values we want as an indicator</param>
/// <param name="riskFreeRate">The risk free rate</param>
/// <param name="optionModel">The option pricing model used to estimate Delta</param>
/// <param name="ivModel">The option pricing model used to estimate IV</param>
/// <param name="resolution">The desired resolution of the data</param>
/// <returns>A new Delta indicator for the specified symbol</returns>
[DocumentationAttribute(Indicators)]
public Delta D(Symbol symbol, decimal? riskFreeRate = null, OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes,
OptionPricingModelType? ivModel = null, Resolution? resolution = null)
{
var name = CreateIndicatorName(symbol, $"Delta({riskFreeRate},{optionModel},{ivModel})", resolution);
IRiskFreeInterestRateModel riskFreeRateModel = riskFreeRate.HasValue
? new ConstantRiskFreeRateInterestRateModel(riskFreeRate.Value)
// Make it a function so it's lazily evaluated: SetRiskFreeInterestRateModel can be called after this method
: new FuncRiskFreeRateInterestRateModel((datetime) => RiskFreeInterestRateModel.GetInterestRate(datetime));
var delta = new Delta(name, symbol, riskFreeRateModel, optionModel, ivModel);
RegisterIndicator(symbol, delta, ResolveConsolidator(symbol, resolution));
RegisterIndicator(symbol.Underlying, delta, ResolveConsolidator(symbol, resolution));
return delta;
}

/// <summary>
/// Creates a new Delta indicator for the symbol The indicator will be automatically
/// updated on the symbol's subscription resolution
/// </summary>
/// <param name="symbol">The option symbol whose values we want as an indicator</param>
/// <param name="riskFreeRate">The risk free rate</param>
/// <param name="optionModel">The option pricing model used to estimate Delta</param>
/// <param name="ivModel">The option pricing model used to estimate IV</param>
/// <param name="resolution">The desired resolution of the data</param>
/// <returns>A new Delta indicator for the specified symbol</returns>
[DocumentationAttribute(Indicators)]
public Delta Δ(Symbol symbol, decimal? riskFreeRate = null, OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes,
OptionPricingModelType? ivModel = null, Resolution? resolution = null)
{
return D(symbol, riskFreeRate, optionModel, ivModel, resolution);
}

/// <summary>
/// Creates a new DoubleExponentialMovingAverage indicator.
/// </summary>
Expand Down
157 changes: 157 additions & 0 deletions Indicators/Delta.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* 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 MathNet.Numerics.Distributions;
using Python.Runtime;
using QuantConnect.Data;

namespace QuantConnect.Indicators
{
/// <summary>
/// Option Delta indicator that calculate the delta of an option
/// </summary>
/// <remarks>sensitivity of option price relative to $1 of underlying change</remarks>
public class Delta : OptionGreeksIndicatorBase
{
/// <summary>
/// Initializes a new instance of the Delta class
/// </summary>
/// <param name="name">The name of this indicator</param>
/// <param name="option">The option to be tracked</param>
/// <param name="riskFreeRateModel">Risk-free rate model</param>
/// <param name="optionModel">The option pricing model used to estimate Delta</param>
/// <param name="ivModel">The option pricing model used to estimate IV</param>
public Delta(string name, Symbol option, IRiskFreeInterestRateModel riskFreeRateModel,
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, OptionPricingModelType? ivModel = null)
: base(name, option, riskFreeRateModel, optionModel: optionModel, ivModel: ivModel)
{
}

/// <summary>
/// Initializes a new instance of the Delta class
/// </summary>
/// <param name="option">The option to be tracked</param>
/// <param name="riskFreeRateModel">Risk-free rate model</param>
/// <param name="optionModel">The option pricing model used to estimate Delta</param>
/// <param name="ivModel">The option pricing model used to estimate IV</param>
public Delta(Symbol option, IRiskFreeInterestRateModel riskFreeRateModel,
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, OptionPricingModelType? ivModel = null)
: this($"Delta({optionModel})", option, riskFreeRateModel, optionModel, ivModel)
{
}

/// <summary>
/// Initializes a new instance of the Delta class
/// </summary>
/// <param name="name">The name of this indicator</param>
/// <param name="option">The option to be tracked</param>
/// <param name="riskFreeRateModel">Risk-free rate model</param>
/// <param name="optionModel">The option pricing model used to estimate Delta</param>
/// <param name="ivModel">The option pricing model used to estimate IV</param>
public Delta(string name, Symbol option, PyObject riskFreeRateModel,
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, OptionPricingModelType? ivModel = null)
: base(name, option, riskFreeRateModel, optionModel: optionModel, ivModel: ivModel)
{
}

/// <summary>
/// Initializes a new instance of the Delta class
/// </summary>
/// <param name="option">The option to be tracked</param>
/// <param name="riskFreeRateModel">Risk-free rate model</param>
/// <param name="optionModel">The option pricing model used to estimate Delta</param>
/// <param name="ivModel">The option pricing model used to estimate IV</param>
public Delta(Symbol option, PyObject riskFreeRateModel, OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes,
OptionPricingModelType? ivModel = null)
: this($"Delta({optionModel})", option, riskFreeRateModel, optionModel, ivModel)
{
}

/// <summary>
/// Initializes a new instance of the Delta class
/// </summary>
/// <param name="option">The option to be tracked</param>am>
/// <param name="riskFreeRate">Risk-free rate, as a constant</param>
/// <param name="optionModel">The option pricing model used to estimate Delta</param>
/// <param name="ivModel">The option pricing model used to estimate IV</param>
public Delta(string name, Symbol option, decimal riskFreeRate = 0.05m,
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, OptionPricingModelType? ivModel = null)
: base(name, option, riskFreeRate, optionModel: optionModel, ivModel: ivModel)
{
}

/// <summary>
/// Initializes a new instance of the Delta class
/// </summary>
/// <param name="option">The option to be tracked</param>
/// <param name="riskFreeRate">Risk-free rate, as a constant</param>
/// <param name="optionModel">The option pricing model used to estimate Delta</param>
/// <param name="ivModel">The option pricing model used to estimate IV</param>
public Delta(Symbol option, decimal riskFreeRate = 0.05m, OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes,
OptionPricingModelType? ivModel = null)
: this($"Delta({optionModel})", option, riskFreeRate, optionModel, ivModel)
{
}

// Calculate the theoretical option price
private decimal TheoreticalDelta(decimal spotPrice, decimal timeToExpiration, decimal volatility,
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
{
var math = OptionGreekIndicatorsHelper.DecimalMath;

switch (optionModel)
{
case OptionPricingModelType.BinomialCoxRossRubinstein:
var upFactor = math(Math.Exp, volatility * math(Math.Sqrt, timeToExpiration / OptionGreekIndicatorsHelper.Steps));
if (upFactor == 1)
{
// provide a small step to estimate delta
upFactor = 1.00001m;
}

var sU = spotPrice * upFactor;
var sD = spotPrice * 1m / upFactor;

var fU = OptionGreekIndicatorsHelper.CRRTheoreticalPrice(volatility, sU, Strike, timeToExpiration, RiskFreeRate, Right);
var fD = OptionGreekIndicatorsHelper.CRRTheoreticalPrice(volatility, sD, Strike, timeToExpiration, RiskFreeRate, Right);

return (fU - fD) / (sU - sD);

case OptionPricingModelType.BlackScholes:
default:
var norm = new Normal();
var d1 = OptionGreekIndicatorsHelper.CalculateD1(spotPrice, Strike, timeToExpiration, RiskFreeRate, volatility);

if (Right == OptionRight.Call)
{
return math(norm.CumulativeDistribution, d1);
}

return -math(norm.CumulativeDistribution, -d1);
}
}

// Calculate the Delta of the option
protected override decimal CalculateGreek(DateTime time)
{
var spotPrice = UnderlyingPrice.Current.Value;
var timeToExpiration = Convert.ToDecimal((Expiry - time).TotalDays) / 365m;
var volatility = ImpliedVolatility.Current.Value;

return TheoreticalDelta(spotPrice, timeToExpiration, volatility, _optionModel);
}
}
}
79 changes: 5 additions & 74 deletions Indicators/ImpliedVolatility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,60 +26,17 @@ namespace QuantConnect.Indicators
/// <summary>
/// Implied Volatility indicator that calculate the IV of an option using Black-Scholes Model
/// </summary>
public class ImpliedVolatility : IndicatorBase<IndicatorDataPoint>, IIndicatorWarmUpPeriodProvider
public class ImpliedVolatility : OptionIndicatorBase
{
private readonly Symbol _optionSymbol;
private readonly Symbol _underlyingSymbol;
private BaseDataConsolidator _consolidator;
private RateOfChange _roc;
private decimal _impliedVolatility;
private OptionPricingModelType _optionModel;

/// <summary>
/// Risk-free rate model
/// </summary>
private readonly IRiskFreeInterestRateModel _riskFreeInterestRateModel;

/// <summary>
/// Gets the expiration time of the option
/// </summary>
public DateTime Expiry => _optionSymbol.ID.Date;

/// <summary>
/// Gets the option right (call/put) of the option
/// </summary>
public OptionRight Right => _optionSymbol.ID.OptionRight;

/// <summary>
/// Gets the strike price of the option
/// </summary>
public decimal Strike => _optionSymbol.ID.StrikePrice;

/// <summary>
/// Gets the option style (European/American) of the option
/// </summary>
public OptionStyle Style => _optionSymbol.ID.OptionStyle;

/// <summary>
/// Risk Free Rate
/// </summary>
public Identity RiskFreeRate { get; set; }

/// <summary>
/// Gets the historical volatility of the underlying
/// </summary>
public IndicatorBase<IndicatorDataPoint> HistoricalVolatility { get; }

/// <summary>
/// Gets the option price level
/// </summary>
public IndicatorBase<IndicatorDataPoint> Price { get; }

/// <summary>
/// Gets the underlying's price level
/// </summary>
public IndicatorBase<IndicatorDataPoint> UnderlyingPrice { get; }

/// <summary>
/// Initializes a new instance of the ImpliedVolatility class
/// </summary>
Expand All @@ -90,37 +47,21 @@ public class ImpliedVolatility : IndicatorBase<IndicatorDataPoint>, IIndicatorWa
/// <param name="optionModel">The option pricing model used to estimate IV</param>
public ImpliedVolatility(string name, Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, int period = 252,
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
: base(name)
: base(name, option, riskFreeRateModel, period, optionModel)
{
var sid = option.ID;
if (!sid.SecurityType.IsOption())
{
throw new ArgumentException("ImpliedVolatility only support SecurityType.Option.");
}

_optionSymbol = option;
_underlyingSymbol = option.Underlying;
_roc = new(1);
_riskFreeInterestRateModel = riskFreeRateModel;
_optionModel = optionModel;

RiskFreeRate = new Identity(name + "_RiskFreeRate");
HistoricalVolatility = IndicatorExtensions.Times(
IndicatorExtensions.Of(
new StandardDeviation(period),
_roc
),
Convert.ToDecimal(Math.Sqrt(252))
);
Price = new Identity(name + "_Close");
UnderlyingPrice = new Identity(name + "_UnderlyingClose");


_consolidator = new(TimeSpan.FromDays(1));
_consolidator.DataConsolidated += (_, bar) => {
_roc.Update(bar.EndTime, bar.Price);
};

WarmUpPeriod = period;
}

/// <summary>
Expand Down Expand Up @@ -159,8 +100,7 @@ public ImpliedVolatility(string name, Symbol option, PyObject riskFreeRateModel,
/// <param name="optionModel">The option pricing model used to estimate IV</param>
public ImpliedVolatility(Symbol option, PyObject riskFreeRateModel, int period = 252,
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
: this($"IV({option.Value},{period},{optionModel})", option,
RiskFreeInterestRateModelPythonWrapper.FromPyObject(riskFreeRateModel), period, optionModel)
: this($"IV({option.Value},{period},{optionModel})", option, riskFreeRateModel, period, optionModel)
{
}

Expand All @@ -187,8 +127,7 @@ public ImpliedVolatility(string name, Symbol option, decimal riskFreeRate = 0.05
/// <param name="optionModel">The option pricing model used to estimate IV</param>
public ImpliedVolatility(Symbol option, decimal riskFreeRate = 0.05m, int period = 252,
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
: this($"IV({option.Value},{period},{riskFreeRate},{optionModel})", option,
new ConstantRiskFreeRateInterestRateModel(riskFreeRate), period, optionModel)
: this($"IV({option.Value},{period},{riskFreeRate},{optionModel})", option, riskFreeRate, period, optionModel)
{
}

Expand All @@ -197,11 +136,6 @@ public ImpliedVolatility(Symbol option, decimal riskFreeRate = 0.05m, int period
/// </summary>
public override bool IsReady => HistoricalVolatility.Samples >= 2 && Price.Current.Time == UnderlyingPrice.Current.Time;

/// <summary>
/// Required period, in data points, for the indicator to be ready and fully initialized.
/// </summary>
public int WarmUpPeriod { get; }

/// <summary>
/// Computes the next value
/// </summary>
Expand Down Expand Up @@ -281,10 +215,7 @@ public override void Reset()
};

_roc.Reset();
RiskFreeRate.Reset();
HistoricalVolatility.Reset();
Price.Reset();
UnderlyingPrice.Reset();
base.Reset();
}
}
Expand Down
Loading

0 comments on commit 3a10afe

Please sign in to comment.