/** * Copyright (C) 2014 - 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.OpenGammaRuntimeException; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption; import com.opengamma.analytics.financial.model.volatility.smile.function.SABRFormulaData; import com.opengamma.analytics.financial.model.volatility.smile.function.VolatilityFunctionProvider; import com.opengamma.analytics.math.differentiation.ScalarFirstOrderDifferentiator; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.util.ArgumentChecker; /** * Left and right extrapolation for SABR smile interpolation by using the shifted lognormal model */ public class ShiftedLogNormalExtrapolationFunctionProvider extends SmileExtrapolationFunctionSABRProvider { private static final ShiftedLogNormalTailExtrapolationFitter TAIL_FITTER = new ShiftedLogNormalTailExtrapolationFitter(); private static final ScalarFirstOrderDifferentiator DIFFERENTIATOR = new ScalarFirstOrderDifferentiator(); /* * Failure behaviors. See {@link SmileInterpolatorSpline} for detail. */ private static final String s_exception = "Exception"; private static final String s_flat = "Flat"; private static final String s_quiet = "Quiet"; private final String _extrapolatorFailureBehaviour; /** * Default constructor, throwing exception if fitting fails */ public ShiftedLogNormalExtrapolationFunctionProvider() { _extrapolatorFailureBehaviour = s_exception; } /** * Constructor specifying the behavior when fitting fails * @param extrapolatorFailureBehaviour The expected behavior */ public ShiftedLogNormalExtrapolationFunctionProvider(final String extrapolatorFailureBehaviour) { ArgumentChecker.notNull(extrapolatorFailureBehaviour, "extrapolatorFailureBehaviour"); _extrapolatorFailureBehaviour = extrapolatorFailureBehaviour; } @Override public Function1D<Double, Double> getExtrapolationFunction(final SABRFormulaData sabrDataLow, final SABRFormulaData sabrDataHigh, final VolatilityFunctionProvider<SABRFormulaData> volatilityFunction, final double forward, final double expiry, final double cutOffStrikeLow, final double cutOffStrikeHigh) { ArgumentChecker.notNull(sabrDataLow, "sabrDataLow"); ArgumentChecker.notNull(sabrDataHigh, "sabrDataHigh"); ArgumentChecker.notNull(volatilityFunction, "volatilityFunction"); ArgumentChecker.isTrue(0.0 < cutOffStrikeLow, "cutOffStrikeLow should be positive"); final Function1D<Double, Boolean> domain = new Function1D<Double, Boolean>() { @Override public Boolean evaluate(final Double k) { return k >= cutOffStrikeLow && k <= cutOffStrikeHigh; } }; final Function1D<Double, Double> interpFuncLow = new Function1D<Double, Double>() { @Override public Double evaluate(final Double strike) { EuropeanVanillaOption option = new EuropeanVanillaOption(strike, expiry, true); final Function1D<SABRFormulaData, Double> volFunc = volatilityFunction.getVolatilityFunction(option, forward); return volFunc.evaluate(sabrDataLow); } }; final Function1D<Double, Double> interpFuncHigh = new Function1D<Double, Double>() { @Override public Double evaluate(final Double strike) { EuropeanVanillaOption option = new EuropeanVanillaOption(strike, expiry, true); final Function1D<SABRFormulaData, Double> volFunc = volatilityFunction.getVolatilityFunction(option, forward); return volFunc.evaluate(sabrDataHigh); } }; final double[] shiftLnVolLowTail; final double[] shiftLnVolHighTail; final Function1D<Double, Double> dSigmaDxLow = DIFFERENTIATOR.differentiate(interpFuncLow, domain); final Function1D<Double, Double> dSigmaDxHigh = DIFFERENTIATOR.differentiate(interpFuncHigh, domain); if (_extrapolatorFailureBehaviour.equalsIgnoreCase(s_flat)) { shiftLnVolHighTail = TAIL_FITTER.fitVolatilityAndGrad(forward, cutOffStrikeHigh, interpFuncHigh.evaluate(cutOffStrikeHigh), 0.0, expiry); shiftLnVolLowTail = TAIL_FITTER.fitVolatilityAndGrad(forward, cutOffStrikeLow, interpFuncLow.evaluate(cutOffStrikeLow), 0.0, expiry); } else { ArgumentChecker.isTrue(cutOffStrikeLow <= forward, "Cannot do left tail extrapolation when the lowest strike ({}) is greater than the forward ({})", cutOffStrikeLow, forward); ArgumentChecker.isTrue(cutOffStrikeHigh >= forward, "Cannot do right tail extrapolation when the highest strike ({}) is less than the forward ({})", cutOffStrikeHigh, forward); if (_extrapolatorFailureBehaviour.equalsIgnoreCase(s_quiet)) { shiftLnVolHighTail = TAIL_FITTER.fitVolatilityAndGradRecursivelyByReducingSmile(forward, cutOffStrikeHigh, interpFuncHigh.evaluate(cutOffStrikeHigh), dSigmaDxHigh.evaluate(cutOffStrikeHigh), expiry); shiftLnVolLowTail = TAIL_FITTER.fitVolatilityAndGradRecursivelyByReducingSmile(forward, cutOffStrikeLow, interpFuncLow.evaluate(cutOffStrikeLow), dSigmaDxLow.evaluate(cutOffStrikeLow), expiry); } else if (_extrapolatorFailureBehaviour.equalsIgnoreCase(s_exception)) { shiftLnVolHighTail = TAIL_FITTER.fitVolatilityAndGrad(forward, cutOffStrikeHigh, interpFuncHigh.evaluate(cutOffStrikeHigh), dSigmaDxHigh.evaluate(cutOffStrikeHigh), expiry); shiftLnVolLowTail = TAIL_FITTER.fitVolatilityAndGrad(forward, cutOffStrikeLow, interpFuncLow.evaluate(cutOffStrikeLow), dSigmaDxLow.evaluate(cutOffStrikeLow), expiry); } else { throw new OpenGammaRuntimeException( "Unrecognized _extrapolatorFailureBehaviour. Looking for one of Exception, Quiet, or Flat"); } } return new Function1D<Double, Double>() { @Override public Double evaluate(final Double strike) { if (strike < cutOffStrikeLow) { return ShiftedLogNormalTailExtrapolation.impliedVolatility(forward, strike, expiry, shiftLnVolLowTail[0], shiftLnVolLowTail[1]); } if (strike > cutOffStrikeHigh) { return ShiftedLogNormalTailExtrapolation.impliedVolatility(forward, strike, expiry, shiftLnVolHighTail[0], shiftLnVolHighTail[1]); } throw new OpenGammaRuntimeException( "Use smile interpolation method for cutOffStrikeLow <= strike <= cutOffStrikeHigh"); } }; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((_extrapolatorFailureBehaviour == null) ? 0 : _extrapolatorFailureBehaviour.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof ShiftedLogNormalExtrapolationFunctionProvider)) { return false; } ShiftedLogNormalExtrapolationFunctionProvider other = (ShiftedLogNormalExtrapolationFunctionProvider) obj; if (_extrapolatorFailureBehaviour == null) { if (other._extrapolatorFailureBehaviour != null) { return false; } } else if (!_extrapolatorFailureBehaviour.equals(other._extrapolatorFailureBehaviour)) { return false; } return true; } }