Skip to content

Commit 35d9b36

Browse files
committed
Address 2nd review
1 parent a7d5b9c commit 35d9b36

File tree

5 files changed

+257
-184
lines changed

5 files changed

+257
-184
lines changed

Algorithm/QCAlgorithm.Indicators.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -858,10 +858,14 @@ public Identity Identity(Symbol symbol, TimeSpan resolution, Func<IBaseData, dec
858858
/// <param name="resolution">The desired resolution of the data</param>
859859
/// <returns>A new ImpliedVolatility indicator for the specified symbol</returns>
860860
[DocumentationAttribute(Indicators)]
861-
public ImpliedVolatility IV(Symbol symbol, decimal riskFreeRate = 0.05m, int period = 252, OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, Resolution? resolution = null)
861+
public ImpliedVolatility IV(Symbol symbol, decimal? riskFreeRate = null, int period = 252, OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, Resolution? resolution = null)
862862
{
863863
var name = CreateIndicatorName(symbol, $"IV({riskFreeRate},{period},{optionModel})", resolution);
864-
var iv = new ImpliedVolatility(name, symbol, riskFreeRate, period, optionModel);
864+
IRiskFreeInterestRateModel riskFreeRateModel = riskFreeRate.HasValue
865+
? new ConstantRiskFreeRateInterestRateModel(riskFreeRate.Value)
866+
// Make it a function so it's lazily evaluated: SetRiskFreeInterestRateModel can be called after this method
867+
: new FuncRiskFreeRateInterestRateModel((datetime) => RiskFreeInterestRateModel.GetInterestRate(datetime));
868+
var iv = new ImpliedVolatility(name, symbol, riskFreeRateModel, period, optionModel);
865869
RegisterIndicator(symbol, iv, ResolveConsolidator(symbol, resolution));
866870
RegisterIndicator(symbol.Underlying, iv, ResolveConsolidator(symbol, resolution));
867871
return iv;

Indicators/ImpliedVolatility.cs

Lines changed: 100 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@
1515

1616
using System;
1717
using MathNet.Numerics.RootFinding;
18+
using Python.Runtime;
19+
using QuantConnect.Data;
1820
using QuantConnect.Data.Consolidators;
19-
using QuantConnect.Data.Market;
2021
using QuantConnect.Logging;
22+
using QuantConnect.Python;
2123

2224
namespace QuantConnect.Indicators
2325
{
2426
/// <summary>
2527
/// Implied Volatility indicator that calculate the IV of an option using Black-Scholes Model
2628
/// </summary>
27-
public class ImpliedVolatility : BarIndicator, IIndicatorWarmUpPeriodProvider
29+
public class ImpliedVolatility : IndicatorBase<IndicatorDataPoint>, IIndicatorWarmUpPeriodProvider
2830
{
2931
private readonly Symbol _optionSymbol;
3032
private readonly Symbol _underlyingSymbol;
@@ -34,29 +36,34 @@ public class ImpliedVolatility : BarIndicator, IIndicatorWarmUpPeriodProvider
3436
private OptionPricingModelType _optionModel;
3537

3638
/// <summary>
37-
/// Gets the expiration time of the option
39+
/// Risk-free rate model
3840
/// </summary>
39-
public DateTime Expiry { get; }
41+
private readonly IRiskFreeInterestRateModel _riskFreeInterestRateModel;
4042

4143
/// <summary>
42-
/// Gets the option right (call/put) of the option
44+
/// Gets the expiration time of the option
4345
/// </summary>
44-
public OptionRight Right { get; }
46+
public DateTime Expiry => _optionSymbol.ID.Date;
4547

4648
/// <summary>
47-
/// Risk Free Rate
49+
/// Gets the option right (call/put) of the option
4850
/// </summary>
49-
public decimal RiskFreeRate { get; set; }
51+
public OptionRight Right => _optionSymbol.ID.OptionRight;
5052

5153
/// <summary>
5254
/// Gets the strike price of the option
5355
/// </summary>
54-
public decimal Strike { get; }
56+
public decimal Strike => _optionSymbol.ID.StrikePrice;
5557

5658
/// <summary>
5759
/// Gets the option style (European/American) of the option
5860
/// </summary>
59-
public OptionStyle Style { get; }
61+
public OptionStyle Style => _optionSymbol.ID.OptionStyle;
62+
63+
/// <summary>
64+
/// Risk Free Rate
65+
/// </summary>
66+
public Identity RiskFreeRate { get; set; }
6067

6168
/// <summary>
6269
/// Gets the historical volatility of the underlying
@@ -73,28 +80,15 @@ public class ImpliedVolatility : BarIndicator, IIndicatorWarmUpPeriodProvider
7380
/// </summary>
7481
public IndicatorBase<IndicatorDataPoint> UnderlyingPrice { get; }
7582

76-
/// <summary>
77-
/// Initializes a new instance of the ImpliedVolatility class
78-
/// </summary>
79-
/// <param name="option">The option to be tracked</param>am>
80-
/// <param name="riskFreeRate">The risk free rate</param>
81-
/// <param name="period">The lookback period of historical volatility</param>
82-
/// <param name="optionModel">The option pricing model used to estimate IV</param>
83-
public ImpliedVolatility(Symbol option, decimal riskFreeRate = 0.05m, int period = 252,
84-
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
85-
: this($"IV({option.Value},{riskFreeRate},{period},{optionModel})", option, riskFreeRate, period, optionModel)
86-
{
87-
}
88-
8983
/// <summary>
9084
/// Initializes a new instance of the ImpliedVolatility class
9185
/// </summary>
9286
/// <param name="name">The name of this indicator</param>
9387
/// <param name="option">The option to be tracked</param>
94-
/// <param name="riskFreeRate">The risk free rate</param>
88+
/// <param name="riskFreeRateModel">Risk-free rate model</param>
9589
/// <param name="period">The lookback period of historical volatility</param>
9690
/// <param name="optionModel">The option pricing model used to estimate IV</param>
97-
public ImpliedVolatility(string name, Symbol option, decimal riskFreeRate = 0.05m, int period = 252,
91+
public ImpliedVolatility(string name, Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, int period = 252,
9892
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
9993
: base(name)
10094
{
@@ -107,14 +101,10 @@ public ImpliedVolatility(string name, Symbol option, decimal riskFreeRate = 0.05
107101
_optionSymbol = option;
108102
_underlyingSymbol = option.Underlying;
109103
_roc = new(1);
104+
_riskFreeInterestRateModel = riskFreeRateModel;
110105
_optionModel = optionModel;
111-
112-
Strike = sid.StrikePrice;
113-
Expiry = sid.Date;
114-
Right = sid.OptionRight;
115-
Style = sid.OptionStyle;
116-
RiskFreeRate = riskFreeRate;
117106

107+
RiskFreeRate = new Identity(name + "_RiskFreeRate");
118108
HistoricalVolatility = IndicatorExtensions.Times(
119109
IndicatorExtensions.Of(
120110
new StandardDeviation(period),
@@ -133,6 +123,75 @@ public ImpliedVolatility(string name, Symbol option, decimal riskFreeRate = 0.05
133123
WarmUpPeriod = period;
134124
}
135125

126+
/// <summary>
127+
/// Initializes a new instance of the ImpliedVolatility class
128+
/// </summary>
129+
/// <param name="option">The option to be tracked</param>
130+
/// <param name="riskFreeRateModel">Risk-free rate model</param>
131+
/// <param name="period">The lookback period of historical volatility</param>
132+
/// <param name="optionModel">The option pricing model used to estimate IV</param>
133+
public ImpliedVolatility(Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, int period = 252,
134+
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
135+
: this($"IV({option.Value},{period},{optionModel})", option, riskFreeRateModel, period, optionModel)
136+
{
137+
}
138+
139+
/// <summary>
140+
/// Initializes a new instance of the ImpliedVolatility class
141+
/// </summary>
142+
/// <param name="name">The name of this indicator</param>
143+
/// <param name="option">The option to be tracked</param>
144+
/// <param name="riskFreeRateModel">Risk-free rate model</param>
145+
/// <param name="period">The lookback period of historical volatility</param>
146+
/// <param name="optionModel">The option pricing model used to estimate IV</param>
147+
public ImpliedVolatility(string name, Symbol option, PyObject riskFreeRateModel, int period = 252,
148+
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
149+
: this(name, option, RiskFreeInterestRateModelPythonWrapper.FromPyObject(riskFreeRateModel), period, optionModel)
150+
{
151+
}
152+
153+
/// <summary>
154+
/// Initializes a new instance of the ImpliedVolatility class
155+
/// </summary>
156+
/// <param name="option">The option to be tracked</param>
157+
/// <param name="riskFreeRateModel">Risk-free rate model</param>
158+
/// <param name="period">The lookback period of historical volatility</param>
159+
/// <param name="optionModel">The option pricing model used to estimate IV</param>
160+
public ImpliedVolatility(Symbol option, PyObject riskFreeRateModel, int period = 252,
161+
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
162+
: this($"IV({option.Value},{period},{optionModel})", option,
163+
RiskFreeInterestRateModelPythonWrapper.FromPyObject(riskFreeRateModel), period, optionModel)
164+
{
165+
}
166+
167+
/// <summary>
168+
/// Initializes a new instance of the ImpliedVolatility class
169+
/// </summary>
170+
/// <param name="name">The name of this indicator</param>
171+
/// <param name="option">The option to be tracked</param>
172+
/// <param name="riskFreeRate">Risk-free rate, as a constant</param>
173+
/// <param name="period">The lookback period of historical volatility</param>
174+
/// <param name="optionModel">The option pricing model used to estimate IV</param>
175+
public ImpliedVolatility(string name, Symbol option, decimal riskFreeRate = 0.05m, int period = 252,
176+
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
177+
: this(name, option, new ConstantRiskFreeRateInterestRateModel(riskFreeRate), period, optionModel)
178+
{
179+
}
180+
181+
/// <summary>
182+
/// Initializes a new instance of the ImpliedVolatility class
183+
/// </summary>
184+
/// <param name="option">The option to be tracked</param>
185+
/// <param name="riskFreeRate">Risk-free rate, as a constant</param>
186+
/// <param name="period">The lookback period of historical volatility</param>
187+
/// <param name="optionModel">The option pricing model used to estimate IV</param>
188+
public ImpliedVolatility(Symbol option, decimal riskFreeRate = 0.05m, int period = 252,
189+
OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
190+
: this($"IV({option.Value},{period},{riskFreeRate},{optionModel})", option,
191+
new ConstantRiskFreeRateInterestRateModel(riskFreeRate), period, optionModel)
192+
{
193+
}
194+
136195
/// <summary>
137196
/// Gets a flag indicating when this indicator is ready and fully initialized
138197
/// </summary>
@@ -148,17 +207,19 @@ public ImpliedVolatility(string name, Symbol option, decimal riskFreeRate = 0.05
148207
/// </summary>
149208
/// <param name="input">The input given to the indicator</param>
150209
/// <returns>The input is returned unmodified.</returns>
151-
protected override decimal ComputeNextValue(IBaseDataBar input)
210+
protected override decimal ComputeNextValue(IndicatorDataPoint input)
152211
{
212+
RiskFreeRate.Update(input.EndTime, _riskFreeInterestRateModel.GetInterestRate(input.EndTime));
213+
153214
var inputSymbol = input.Symbol;
154215
if (inputSymbol == _optionSymbol)
155216
{
156-
Price.Update(input.EndTime, input.Close);
217+
Price.Update(input.EndTime, input.Price);
157218
}
158219
else if (inputSymbol == _underlyingSymbol)
159220
{
160221
_consolidator.Update(input);
161-
UnderlyingPrice.Update(input.EndTime, input.Close);
222+
UnderlyingPrice.Update(input.EndTime, input.Price);
162223
}
163224
else
164225
{
@@ -194,14 +255,15 @@ private decimal CalculateIV(DateTime time)
194255
var spotPrice = UnderlyingPrice.Current.Value;
195256
var timeToExpiration = Convert.ToDecimal((Expiry - time).TotalDays) / 365m;
196257

197-
Func<double, double> f = (vol) => (double)(TheoreticalPrice(Convert.ToDecimal(vol), spotPrice, Strike, timeToExpiration, RiskFreeRate, Right, _optionModel) - price);
258+
Func<double, double> f = (vol) =>
259+
(double)(TheoreticalPrice(Convert.ToDecimal(vol), spotPrice, Strike, timeToExpiration, RiskFreeRate.Current.Value, Right, _optionModel) - price);
198260
try
199261
{
200262
return Convert.ToDecimal(Brent.FindRoot(f, 0.01d, 1.0d, 1e-5d, 20));
201263
}
202264
catch
203265
{
204-
Log.Error("Fail to converge, returning 0.");
266+
Log.Error("ImpliedVolatility.CalculateIV(): Fail to converge, returning 0.");
205267
return 0m;
206268
}
207269
}
@@ -211,12 +273,14 @@ private decimal CalculateIV(DateTime time)
211273
/// </summary>
212274
public override void Reset()
213275
{
276+
_consolidator.Dispose();
214277
_consolidator = new(TimeSpan.FromDays(1));
215278
_consolidator.DataConsolidated += (_, bar) => {
216279
_roc.Update(bar.EndTime, bar.Price);
217280
};
218281

219282
_roc.Reset();
283+
RiskFreeRate.Reset();
220284
HistoricalVolatility.Reset();
221285
Price.Reset();
222286
UnderlyingPrice.Reset();

Indicators/OptionPricingModelType.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ public enum OptionPricingModelType
2323
/// <summary>
2424
/// Vanilla Black Scholes Model
2525
/// </summary>
26+
/// <remarks>Preferred on calculating greeks for European options, and IV for all options</remarks>
2627
BlackScholes,
2728
/// <summary>
2829
/// The Cox-Ross-Rubinstein binomial tree model (CRR model)
2930
/// </summary>
31+
/// <remarks>Preferred on calculating greeks for American options</remarks>
3032
BinomialCoxRossRubinstein
3133
}
3234
}

Tests/Algorithm/AlgorithmIndicatorsTests.cs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@
1414
*/
1515

1616
using System;
17-
using System.Collections.Generic;
18-
using System.Diagnostics;
19-
using System.Linq;
20-
using System.Linq.Expressions;
2117

2218
using Moq;
2319
using NUnit.Framework;
@@ -26,7 +22,6 @@
2622
using QuantConnect.Data;
2723
using QuantConnect.Data.Market;
2824
using QuantConnect.Indicators;
29-
using QuantConnect.Interfaces;
3025
using QuantConnect.Lean.Engine.DataFeeds;
3126
using QuantConnect.Lean.Engine.HistoricalData;
3227
using QuantConnect.Tests.Engine.DataFeeds;
@@ -37,6 +32,7 @@ namespace QuantConnect.Tests.Algorithm
3732
public class AlgorithmIndicatorsTests
3833
{
3934
private QCAlgorithm _algorithm;
35+
private Symbol _option;
4036

4137
[SetUp]
4238
public void Setup()
@@ -52,6 +48,7 @@ public void Setup()
5248

5349
_algorithm.SetDateTime(new DateTime(2013, 10, 11, 15, 0, 0));
5450
_algorithm.AddEquity("SPY");
51+
_option = _algorithm.AddOption("SPY").Symbol;
5552
_algorithm.EnableAutomaticIndicatorWarmUp = true;
5653
}
5754

@@ -93,5 +90,34 @@ public void SharpeRatioIndicatorUsesAlgorithmsRiskFreeRateModelSetAfterIndicator
9390
// Our interest rate provider should have been called once
9491
interestRateProviderMock.Verify(x => x.GetInterestRate(reference), Times.Once);
9592
}
93+
94+
[Test]
95+
public void IVIndicatorUsesAlgorithmsRiskFreeRateModelSetAfterIndicatorRegistration()
96+
{
97+
// Register indicator
98+
var sharpeRatio = _algorithm.IV(_option);
99+
100+
// Setup risk free rate model
101+
var interestRateProviderMock = new Mock<IRiskFreeInterestRateModel>();
102+
var reference = new DateTime(2023, 11, 21, 10, 0, 0);
103+
interestRateProviderMock.Setup(x => x.GetInterestRate(reference)).Verifiable();
104+
105+
// Update indicator
106+
sharpeRatio.Update(new IndicatorDataPoint(_option, reference, 30m));
107+
sharpeRatio.Update(new IndicatorDataPoint(Symbols.SPY, reference, 300m));
108+
109+
// Our interest rate provider shouldn't have been called yet since it's hasn't been set to the algorithm
110+
interestRateProviderMock.Verify(x => x.GetInterestRate(reference), Times.Never);
111+
112+
// Set the interest rate provider to the algorithm
113+
_algorithm.SetRiskFreeInterestRateModel(interestRateProviderMock.Object);
114+
115+
// Update indicator
116+
sharpeRatio.Update(new IndicatorDataPoint(_option, reference, 30m));
117+
sharpeRatio.Update(new IndicatorDataPoint(Symbols.SPY, reference, 300m));
118+
119+
// Our interest rate provider should have been called once by each update
120+
interestRateProviderMock.Verify(x => x.GetInterestRate(reference), Times.Exactly(2));
121+
}
96122
}
97123
}

0 commit comments

Comments
 (0)