/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation; import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository; import com.opengamma.analytics.math.MathException; /** * A shifted log normal model is one where the forward and volatility in the Black model are adjusted to produce plausible prices for far OTM options. * Since there are two free parameters, two prices, or a price and gradient, can be matched - the prices (as viewed by implied volatility) then have sensible extrapolated * values (i.e. stays positive and finite). <p> * <b>Note</b> this is not a full smile model and should never be used for values crossing ATM (there is a step in implied volatility ATM) <p> * Do not confuse with displaced diffusion which has dynamics $\frac{df}{f+\alpha}=\sigma_{\alpha} dW$ and is priced by the Black formula with the replacement * $f\rightarrow f+\alpha \quad k\rightarrow k+\alpha \quad \sigma \rightarrow \sigma_{\alpha}$ */ public class ShiftedLogNormalTailExtrapolation { /** * The price of an option under a shifted log normal model * @param forward The (actual) forward value of the underlying * @param strike The option strike * @param timeToExpiry time-to-expiry * @param isCall true for a call option * @param mu The shift in the distribution such that the effective forward = f*exp(mu) * @param theta The volatility of the distribution * @return The price */ public static double price(final double forward, final double strike, final double timeToExpiry, final boolean isCall, final double mu, final double theta) { return BlackFormulaRepository.price(forward * Math.exp(mu), strike, timeToExpiry, theta, isCall); } /** * The Black implied volatility under a shifted log normal model * @param forward The (actual) forward value of the underlying * @param strike The option strike * @param timeToExpiry time-to-expiry * @param mu The shift in the distribution such that the effective forward = f*exp(mu) * @param theta The volatility of the distribution * @return The implied volatility */ public static double impliedVolatility(final double forward, final double strike, final double timeToExpiry, final double mu, final double theta) { boolean isCall = strike >= forward; if (strike == 0) { return theta; } if (mu == 0) { return theta; } double p = price(forward, strike, timeToExpiry, isCall, mu, theta); if (p <= 1e-100) { //TODO this is an arbitrary choice to switch to the approximation here double c = Math.log(strike / forward); double a = timeToExpiry / 2; double b = (-c + mu - theta * theta * timeToExpiry / 2) / theta; double arg = b * b - 4 * a * c; if (arg < 0) { throw new MathException("cannot solve for sigma"); } double root = Math.sqrt(arg); double volGuess = isCall ? (-b - root) / 2 / a : (-b + root) / 2 / a; return volGuess; } if (!isCall && p >= strike) { double c = Math.log(strike / forward); double a = timeToExpiry / 2; double b = (c + mu - theta * theta * timeToExpiry / 2) / theta; double arg = b * b - 4 * a * c; if (arg < 0) { throw new MathException("cannot solve for sigma"); } double root = Math.sqrt(arg); double volGuess = isCall ? (-b - root) / 2 / a : (-b + root) / 2 / a; return volGuess; } return BlackFormulaRepository.impliedVolatility(p, forward, strike, timeToExpiry, isCall); } /** * The dual delta (i.e. sensitivity of the price to a change in the strike) of an option under a shifted log normal model * @param forward The (actual) forward value of the underlying * @param strike The option strike * @param timeToExpiry time-to-expiry * @param isCall true for a call option * @param mu The shift in the distribution such that the effective forward = f*exp(mu) * @param theta The volatility of the distribution * @return The dual delta */ public static double dualDelta(final double forward, final double strike, final double timeToExpiry, final boolean isCall, final double mu, final double theta) { return BlackFormulaRepository.dualDelta(forward * Math.exp(mu), strike, timeToExpiry, theta, isCall); } /** * The gradient of the smile under a shifted log normal model * @param forward The (actual) forward value of the underlying * @param strike The option strike * @param timeToExpiry time-to-expiry * @param mu The shift in the distribution such that the effective forward = f*exp(mu) * @param theta The volatility of the distribution * @return The smile gradient */ public static double dVdK(final double forward, final double strike, final double timeToExpiry, final double mu, final double theta) { if (mu == 0.0) { return 0.0; } boolean isCall = strike >= forward; double vol = impliedVolatility(forward, strike, timeToExpiry, mu, theta); double dd = dualDelta(forward, strike, timeToExpiry, isCall, mu, theta); double blackDD = BlackFormulaRepository.dualDelta(forward, strike, timeToExpiry, vol, isCall); double blackVega = BlackFormulaRepository.vega(forward, strike, timeToExpiry, vol); return (dd - blackDD) / blackVega; } protected static double dVdK(final double forward, final double strike, final double timeToExpiry, final double mu, final double theta, final double vol) { if (mu == 0.0) { return 0.0; } boolean isCall = strike >= forward; double dd = dualDelta(forward, strike, timeToExpiry, isCall, mu, theta); double blackDD = BlackFormulaRepository.dualDelta(forward, strike, timeToExpiry, vol, isCall); double blackVega = BlackFormulaRepository.vega(forward, strike, timeToExpiry, vol); return (dd - blackDD) / blackVega; } }