/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.volatility.smile.function; import org.apache.commons.lang.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.util.CompareUtils; /** * This is the form given in Obloj, Fine-Tune Your Smile (2008), and supposedly corresponds to that given in Hagan, Managing Smile Risk (2002). However it differs from Hagan * {@link SABRBerestyckiVolatilityFunction} */ public class SABRHaganAlternativeVolatilityFunction extends VolatilityFunctionProvider<SABRFormulaData> { private static final Logger s_logger = LoggerFactory.getLogger(SABRHaganAlternativeVolatilityFunction.class); private static final double CUTOFF_MONEYNESS = 1e-6; private static final double EPS = 1e-15; @Override public Function1D<SABRFormulaData, Double> getVolatilityFunction(final EuropeanVanillaOption option, final double forward) { Validate.notNull(option, "option"); final double strike = option.getStrike(); final double t = option.getTimeToExpiry(); return new Function1D<SABRFormulaData, Double>() { @SuppressWarnings("synthetic-access") @Override public final Double evaluate(final SABRFormulaData data) { Validate.notNull(data, "data"); final double alpha = data.getAlpha(); final double beta = data.getBeta(); final double rho = data.getRho(); final double nu = data.getNu(); final double cutoff = forward * CUTOFF_MONEYNESS; final double k; if (strike < cutoff) { s_logger.info("Given strike of " + strike + " is less than cutoff at " + cutoff + ", therefore the strike is taken as " + cutoff); k = cutoff; } else { k = strike; } double i0; final double beta1 = 1 - beta; if (CompareUtils.closeEquals(forward, k, EPS)) { i0 = alpha / Math.pow(k, beta1); } else { final double x = Math.log(forward / k); if (CompareUtils.closeEquals(nu, 0, EPS)) { if (CompareUtils.closeEquals(beta, 1.0, EPS)) { return alpha; // this is just log-normal } i0 = x * alpha * beta1 / (Math.pow(forward, beta1) - Math.pow(k, beta1)); } else { double z, zeta; if (beta == 1.0) { z = nu * x / alpha; zeta = z; } else { z = nu * (Math.pow(forward, beta1) - Math.pow(k, beta1)) / alpha / beta1; zeta = nu * (forward - k) / alpha / Math.pow(forward * k, beta / 2); } final double temp = (Math.sqrt(1 + zeta * (zeta - 2 * rho)) + zeta - rho) / (1 - rho); i0 = nu * x * zeta / z / Math.log(temp); } } final double f1sqrt = Math.pow(forward * k, beta1 / 2); final double i1 = beta1 * beta1 * alpha * alpha / 24 / f1sqrt / f1sqrt + rho * alpha * beta * nu / 4 / f1sqrt + nu * nu * (2 - 3 * rho * rho) / 24; return i0 * (1 + i1 * t); } }; } @Override public int hashCode() { return toString().hashCode(); } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (getClass() != obj.getClass()) { return false; } return true; } @Override public String toString() { return "SABR (Hagan alternative)"; } }