Skip to content

Commit

Permalink
Bug fix on Option Indicator calculation (#7999)
Browse files Browse the repository at this point in the history
* Handle expired options

* unit test

* Move Zero Checks to Single Location

---------

Co-authored-by: Alexandre Catarino <[email protected]>
  • Loading branch information
LouisSzeto and AlexCatarino authored May 7, 2024
1 parent 520e604 commit bb7a7f2
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 13 deletions.
19 changes: 10 additions & 9 deletions Indicators/ImpliedVolatility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,17 +309,18 @@ protected override decimal ComputeNextValue(IndicatorDataPoint input)
private decimal TheoreticalPrice(decimal volatility, decimal spotPrice, decimal strikePrice, decimal timeTillExpiry, decimal riskFreeRate,
decimal dividendYield, OptionRight optionType, OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
{
switch (optionModel)
if (timeTillExpiry <= 0m)
{
// Binomial model also follows BSM process (log-normal)
case OptionPricingModelType.BinomialCoxRossRubinstein:
return OptionGreekIndicatorsHelper.CRRTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType);
case OptionPricingModelType.ForwardTree:
return OptionGreekIndicatorsHelper.ForwardTreeTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType);
case OptionPricingModelType.BlackScholes:
default:
return OptionGreekIndicatorsHelper.BlackTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType);
return 0m;
}

return optionModel switch
{
// Binomial model also follows BSM process (log-normal)
OptionPricingModelType.BinomialCoxRossRubinstein => OptionGreekIndicatorsHelper.CRRTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
OptionPricingModelType.ForwardTree => OptionGreekIndicatorsHelper.ForwardTreeTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
_ => OptionGreekIndicatorsHelper.BlackTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
};
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions Indicators/OptionGreekIndicatorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ protected override decimal ComputeNextValue(IndicatorDataPoint input)
RiskFreeRate.Update(time, _riskFreeInterestRateModel.GetInterestRate(time));
DividendYield.Update(time, _dividendYieldModel.GetDividendYield(time));

var timeTillExpiry = Convert.ToDecimal((Expiry - time).TotalDays) / 365m;
_greekValue = CalculateGreek(timeTillExpiry);
var timeTillExpiry = Convert.ToDecimal((Expiry - time).TotalDays / 365);
_greekValue = timeTillExpiry < 0 ? 0 : CalculateGreek(timeTillExpiry);
}

return _greekValue;
Expand Down
4 changes: 2 additions & 2 deletions Indicators/OptionGreekIndicatorsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static decimal BlackTheoreticalPrice(decimal volatility, decimal spotPric
internal static decimal CalculateD1(decimal spotPrice, decimal strikePrice, decimal timeToExpiration, decimal riskFreeRate, decimal dividendYield, decimal volatility)
{
var numerator = DecimalMath(Math.Log, spotPrice / strikePrice) + (riskFreeRate - dividendYield + 0.5m * volatility * volatility) * timeToExpiration;
var denominator = volatility * DecimalMath(Math.Sqrt, timeToExpiration);
var denominator = volatility * DecimalMath(Math.Sqrt, Math.Max(0m, timeToExpiration));
if (denominator == 0m)
{
// return a random variable large enough to produce normal probability density close to 1
Expand All @@ -69,7 +69,7 @@ internal static decimal CalculateD1(decimal spotPrice, decimal strikePrice, deci

internal static decimal CalculateD2(decimal d1, decimal volatility, decimal timeToExpiration)
{
return d1 - volatility * DecimalMath(Math.Sqrt, timeToExpiration);
return d1 - volatility * DecimalMath(Math.Sqrt, Math.Max(0m, timeToExpiration));
}

// Reference: https://en.wikipedia.org/wiki/Binomial_options_pricing_model#Step_1:_Create_the_binomial_price_tree
Expand Down
14 changes: 14 additions & 0 deletions Tests/Indicators/OptionBaseIndicatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@ protected void RunTestIndicator(Symbol call, Symbol put, OptionIndicatorBase cal
Assert.AreEqual(expected, (double)putIndicator.Current.Value, acceptance);
}

[Test]
public void ZeroGreeksIfExpired()
{
var indicator = CreateIndicator();
var date = new DateTime(2099, 1, 1); // date that the option must be expired already
var price = 500m;
var optionPrice = 10m;

indicator.Update(new IndicatorDataPoint(_symbol, date, optionPrice));
indicator.Update(new IndicatorDataPoint(_underlying, date, price));

Assert.AreEqual(0m, indicator.Current.Value);
}

[Test]
public override void ResetsProperly()
{
Expand Down

0 comments on commit bb7a7f2

Please sign in to comment.