23 using System.Collections.Generic;
27 using PricingEngineFunc = Func<GeneralizedBlackScholesProcess, IPricingEngine>;
28 using PricingEngineFuncEx = Func<Symbol, GeneralizedBlackScholesProcess, IPricingEngine>;
43 private readonly PricingEngineFuncEx _pricingEngineFunc;
75 : this((option, process) => pricingEngineFunc(process), underlyingVolEstimator, riskFreeRateEstimator, dividendYieldEstimator, allowedOptionStyles)
91 _pricingEngineFunc = pricingEngineFunc;
92 _underlyingVolEstimator = underlyingVolEstimator ?? _defaultUnderlyingVolEstimator;
93 _riskFreeRateEstimator = riskFreeRateEstimator ?? _defaultRiskFreeRateEstimator;
94 _dividendYieldEstimator = dividendYieldEstimator ?? _defaultDividendYieldEstimator;
112 throw new ArgumentException($
"{contract.Symbol.ID.OptionStyle} style options are not supported by option price model '{this.GetType().Name}'");
118 if (contract.
Time.Date > contract.
Expiry.Date)
122 Log.
Debug($
"QLOptionPriceModel.Evaluate(). Expired {contract.Symbol}. Time > Expiry: {contract.Time.Date} > {contract.Expiry.Date}");
127 var dayCounter =
new Actual365Fixed();
132 var maturity = dayCounter.yearFraction(contract.
Time.Date, maturityDate);
137 Log.
Debug($
"QLOptionPriceModel.Evaluate(). negative time ({maturity}) given for {contract.Symbol}. Time: {contract.Time.Date}. Maturity {maturityDate}");
143 var optionSecurity = (
Option)security;
144 var premium = (double)optionSecurity.
Price;
145 var spot = (
double)optionSecurity.Underlying.Price;
147 if (spot <= 0d || premium <= 0d)
151 Log.
Debug($
"QLOptionPriceModel.Evaluate(). Non-positive prices for {contract.Symbol}. Premium: {premium}. Underlying price {spot}");
157 var calendar =
new UnitedStates();
159 var underlyingQuoteValue =
new SimpleQuote(spot);
161 var dividendYieldValue =
new SimpleQuote(_dividendYieldEstimator.Estimate(security, slice, contract));
162 var dividendYield =
new Handle<YieldTermStructure>(
new FlatForward(0, calendar, dividendYieldValue, dayCounter));
164 var riskFreeRateValue =
new SimpleQuote((
double)_riskFreeRateEstimator.Estimate(security, slice, contract));
165 var riskFreeRate =
new Handle<YieldTermStructure>(
new FlatForward(0, calendar, riskFreeRateValue, dayCounter));
168 var dividendDiscount = dividendYield.link.discount(maturity);
169 var riskFreeDiscount = riskFreeRate.link.discount(maturity);
170 var forwardPrice = spot * dividendDiscount / riskFreeDiscount;
173 var initialGuess = Math.Sqrt(2 * Math.PI / maturity) * premium / spot;
175 var underlyingVolEstimate = _underlyingVolEstimator.Estimate(security, slice, contract);
178 if (!_underlyingVolEstimator.IsReady)
180 underlyingVolEstimate = initialGuess;
183 var underlyingVolValue =
new SimpleQuote(underlyingVolEstimate);
184 var underlyingVol =
new Handle<BlackVolTermStructure>(
new BlackConstantVol(0, calendar,
new Handle<Quote>(underlyingVolValue), dayCounter));
187 var stochasticProcess =
new BlackScholesMertonProcess(
new Handle<Quote>(underlyingQuoteValue), dividendYield, riskFreeRate, underlyingVol);
188 var payoff =
new PlainVanillaPayoff(contract.
Right ==
OptionRight.Call ? QLNet.Option.Type.Call : QLNet.Option.Type.Put, (
double)contract.
Strike);
192 new VanillaOption(payoff,
new AmericanExercise(settlementDate, maturityDate)) :
193 new VanillaOption(payoff,
new EuropeanExercise(maturityDate));
196 option.setPricingEngine(_pricingEngineFunc(contract.
Symbol, stochasticProcess));
199 var evaluationDate = contract.
Time.Date;
200 SetEvaluationDate(evaluationDate);
203 var npv = EvaluateOption(option);
205 BlackCalculator blackCalculator =
null;
211 SetEvaluationDate(evaluationDate);
212 impliedVol = option.impliedVolatility(premium, stochasticProcess);
217 impliedVol =
ImpliedVolatilityEstimation(premium, initialGuess, maturity, riskFreeDiscount, forwardPrice, payoff, out blackCalculator);
220 var referenceDate = underlyingVol.link.referenceDate();
221 Log.
Debug($
"QLOptionPriceModel.Evaluate(). Cannot calculate Implied Volatility for {contract.Symbol}. Implied volatility from Newton-Raphson optimization: {impliedVol}. Premium: {premium}. Underlying price: {spot}. Initial guess volatility: {initialGuess}. Maturity: {maturity}. Risk Free: {riskFreeDiscount}. Forward price: {forwardPrice}. Data time: {evaluationDate}. Reference date: {referenceDate}. {e.Message} {e.StackTrace}");
230 underlyingVolValue.setValue(impliedVol);
233 decimal tryGetGreekOrReevaluate(Func<double> greek, Func<BlackCalculator, double> black)
236 var isApproximation =
false;
237 Exception exception =
null;
241 SetEvaluationDate(evaluationDate);
244 catch (Exception err)
253 if (blackCalculator ==
null)
258 var vol = underlyingVol.link.blackVol(maturityDate, (
double)contract.
Strike);
259 blackCalculator = CreateBlackCalculator(forwardPrice, riskFreeDiscount, vol, payoff);
262 isApproximation =
true;
263 result = black(blackCalculator);
266 if (result.IsNaNOrInfinity())
270 var referenceDate = underlyingVol.link.referenceDate();
271 Log.
Debug($
"QLOptionPriceModel.Evaluate(). NaN or Infinity greek for {contract.Symbol}. Premium: {premium}. Underlying price: {spot}. Initial guess volatility: {initialGuess}. Maturity: {maturity}. Risk Free: {riskFreeDiscount}. Forward price: {forwardPrice}. Implied Volatility: {impliedVol}. Is Approximation? {isApproximation}. Data time: {evaluationDate}. Reference date: {referenceDate}. {exception?.Message} {exception?.StackTrace}");
277 var value = result.SafeDecimalCast();
281 var referenceDate = underlyingVol.link.referenceDate();
282 Log.
Debug($
"QLOptionPriceModel.Evaluate(). Zero-value greek for {contract.Symbol}. Premium: {premium}. Underlying price: {spot}. Initial guess volatility: {initialGuess}. Maturity: {maturity}. Risk Free: {riskFreeDiscount}. Forward price: {forwardPrice}. Implied Volatility: {impliedVol}. Is Approximation? {isApproximation}. Data time: {evaluationDate}. Reference date: {referenceDate}. {exception?.Message} {exception?.StackTrace}");
291 () => impliedVol.IsNaNOrInfinity() ? 0m : impliedVol.SafeDecimalCast(),
292 () =>
new ModeledGreeks(() => tryGetGreekOrReevaluate(() => option.delta(), (black) => black.delta(spot)),
293 () => tryGetGreekOrReevaluate(() => option.gamma(), (black) => black.gamma(spot)),
294 () => tryGetGreekOrReevaluate(() => option.vega(), (black) => black.vega(maturity)) / 100,
295 () => tryGetGreekOrReevaluate(() => option.theta(), (black) => black.theta(spot, maturity)),
296 () => tryGetGreekOrReevaluate(() => option.rho(), (black) => black.rho(maturity)) / 100,
297 () => tryGetGreekOrReevaluate(() => option.elasticity(), (black) => black.elasticity(spot))));
299 catch (Exception err)
301 Log.
Debug($
"QLOptionPriceModel.Evaluate() error: {err.Message} {(Log.DebuggingEnabled ? err.StackTrace : string.Empty)} for {contract.Symbol}");
311 private static decimal EvaluateOption(VanillaOption option)
315 var npv = option.NPV();
317 if (
double.IsNaN(npv) ||
318 double.IsInfinity(npv))
322 return Math.Max(0, npv).SafeDecimalCast();
324 catch (Exception err)
326 Log.
Debug($
"QLOptionPriceModel.EvaluateOption() error: {err.Message}");
347 double forwardPrice, PlainVanillaPayoff payoff, out BlackCalculator black)
350 const double tolerance = 1e-3d;
351 const double lowerBound = 1e-7d;
352 const double upperBound = 4d;
354 var error =
double.MaxValue;
355 var impliedVolEstimate = initialGuess;
358 black = CreateBlackCalculator(forwardPrice, riskFreeDiscount, initialGuess, payoff);
360 while (error > tolerance && iterRemain > 0)
362 var oldImpliedVol = impliedVolEstimate;
365 black = CreateBlackCalculator(forwardPrice, riskFreeDiscount, oldImpliedVol, payoff);
366 impliedVolEstimate -= (black.value() - price) / black.vega(timeTillExpiry);
368 if (impliedVolEstimate < lowerBound)
370 impliedVolEstimate = lowerBound;
372 else if (impliedVolEstimate > upperBound)
374 impliedVolEstimate = upperBound;
377 error = Math.Abs(impliedVolEstimate - oldImpliedVol) / impliedVolEstimate;
385 Log.
Debug(
"QLOptionPriceModel.ImpliedVolatilityEstimation() error: Implied Volatility approxiation did not converge, returning 0.");
390 return impliedVolEstimate;
398 private BlackCalculator CreateBlackCalculator(
double forwardPrice,
double riskFreeDiscount,
double stdDev, PlainVanillaPayoff payoff)
400 return new BlackCalculator(payoff, forwardPrice, stdDev, riskFreeDiscount);
405 var forwardDate = date.AddDays(days);
419 private void SetEvaluationDate(DateTime evaluationDate)
421 if (Settings.evaluationDate().ToDateTime() != evaluationDate)
423 Settings.setEvaluationDate(evaluationDate);