/**
 * Surface Stochastic Volatility Inspired (SSVI) formula.
 *
 * <p>Reference: Gatheral, Jim and Jacquier, Antoine. Arbitrage-free SVI volatility surfaces.
 * arXiv:1204.0646v4, 2013. Section 4.
 */
@BeanDefinition(style = "light")
public final class SsviVolatilityFunction extends VolatilityFunctionProvider<SsviFormulaData>
    implements ImmutableBean, Serializable {

  /** Default implementation. */
  public static final SsviVolatilityFunction DEFAULT = new SsviVolatilityFunction();

  /**
   * SSVI volatility description diverge for theta -> 0. Lower bound for which time to expiry is
   * accepted.
   */
  public static final double MIN_TIME_TO_EXPIRY = 1.0E-3;

  // -------------------------------------------------------------------------
  @Override
  public double volatility(
      double forward, double strike, double timeToExpiry, SsviFormulaData data) {
    ArgChecker.isTrue(
        timeToExpiry > MIN_TIME_TO_EXPIRY,
        "time to expiry must not be zero to be able to compute volatility");
    double volatilityAtm = data.getSigma();
    double rho = data.getRho();
    double eta = data.getEta();
    double theta = volatilityAtm * volatilityAtm * timeToExpiry;
    double phi = eta / Math.sqrt(theta);
    double k = Math.log(strike / forward);
    double w =
        0.5
            * theta
            * (1.0d + rho * phi * k + Math.sqrt(1.0d + 2 * rho * phi * k + phi * k * phi * k));
    return Math.sqrt(w / timeToExpiry);
  }

  /**
   * Computes the implied volatility in the SSVI formula and its derivatives.
   *
   * <p>The derivatives are stored in an array with:
   *
   * <ul>
   *   <li>[0] derivative with respect to the forward
   *   <li>[1] derivative with respect to the strike
   *   <li>[2] derivative with respect to the time to expiry
   *   <li>[3] derivative with respect to the sigma (ATM volatility)
   *   <li>[4] derivative with respect to the rho
   *   <li>[5] derivative with respect to the eta
   * </ul>
   *
   * @param forward the forward value of the underlying
   * @param strike the strike value of the option
   * @param timeToExpiry the time to expiry of the option
   * @param data the SSVI data
   * @return the volatility and associated derivatives
   */
  @Override
  public ValueDerivatives volatilityAdjoint(
      double forward, double strike, double timeToExpiry, SsviFormulaData data) {
    ArgChecker.isTrue(
        timeToExpiry > MIN_TIME_TO_EXPIRY,
        "time to expiry must not be zero to be able to compute volatility");
    double volatilityAtm = data.getSigma();
    double rho = data.getRho();
    double eta = data.getEta();
    double theta = volatilityAtm * volatilityAtm * timeToExpiry;
    double stheta = Math.sqrt(theta);
    double phi = eta / stheta;
    double k = Math.log(strike / forward);
    double s = Math.sqrt(1.0d + 2 * rho * phi * k + phi * k * phi * k);
    double w = 0.5 * theta * (1.0d + rho * phi * k + s);
    double volatility = Math.sqrt(w / timeToExpiry);
    // Backward sweep.
    double[] derivatives = new double[6]; // 6 inputs
    double volatilityBar = 1.0;
    double wBar = 0.5 * volatility / w * volatilityBar;
    derivatives[2] += -0.5 * volatility / timeToExpiry * volatilityBar;
    double thetaBar = w / theta * wBar;
    derivatives[4] += 0.5 * theta * phi * k * wBar;
    double phiBar = 0.5 * theta * rho * k * wBar;
    double kBar = 0.5 * theta * rho * phi * wBar;
    double sBar = 0.5 * theta * wBar;
    derivatives[4] += phi * k / s * sBar;
    phiBar += (rho * k + phi * k * k) / s * sBar;
    kBar += (rho * phi + phi * phi * k) / s * sBar;
    derivatives[1] += 1.0d / strike * kBar;
    derivatives[0] += -1.0d / forward * kBar;
    derivatives[5] += phiBar / stheta;
    double sthetaBar = -eta / (stheta * stheta) * phiBar;
    thetaBar += 0.5 / stheta * sthetaBar;
    derivatives[3] += 2 * volatilityAtm * timeToExpiry * thetaBar;
    derivatives[2] += volatilityAtm * volatilityAtm * thetaBar;
    return ValueDerivatives.of(volatility, DoubleArray.ofUnsafe(derivatives));
  }

  @Override
  public double volatilityAdjoint2(
      double forward,
      double strike,
      double timeToExpiry,
      SsviFormulaData data,
      double[] volatilityD,
      double[][] volatilityD2) {
    throw new UnsupportedOperationException("Not implemented");
  }

  // ------------------------- AUTOGENERATED START -------------------------
  /// CLOVER:OFF
  /** The meta-bean for {@code SsviVolatilityFunction}. */
  private static MetaBean META_BEAN = LightMetaBean.of(SsviVolatilityFunction.class);

  /**
   * The meta-bean for {@code SsviVolatilityFunction}.
   *
   * @return the meta-bean, not null
   */
  public static MetaBean meta() {
    return META_BEAN;
  }

  static {
    JodaBeanUtils.registerMetaBean(META_BEAN);
  }

  /** The serialization version id. */
  private static final long serialVersionUID = 1L;

  private SsviVolatilityFunction() {}

  @Override
  public MetaBean metaBean() {
    return META_BEAN;
  }

  @Override
  public <R> Property<R> property(String propertyName) {
    return metaBean().<R>metaProperty(propertyName).createProperty(this);
  }

  @Override
  public Set<String> propertyNames() {
    return metaBean().metaPropertyMap().keySet();
  }

  // -----------------------------------------------------------------------
  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (obj != null && obj.getClass() == this.getClass()) {
      return true;
    }
    return false;
  }

  @Override
  public int hashCode() {
    int hash = getClass().hashCode();
    return hash;
  }

  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder(32);
    buf.append("SsviVolatilityFunction{");
    buf.append('}');
    return buf.toString();
  }

  /// CLOVER:ON
  // -------------------------- AUTOGENERATED END --------------------------
}
Beispiel #2
0
/**
 * Mock address JavaBean, used for testing.
 *
 * @author Stephen Colebourne
 */
@BeanDefinition(style = "light")
public final class Light implements ImmutableBean, Serializable {

  /** The number. */
  @PropertyDefinition private final int number;
  /** The number. */
  @PropertyDefinition private final boolean flag;
  /** The street. */
  @PropertyDefinition(validate = "notNull", get = "field")
  private final String street;
  /** The town. */
  @PropertyDefinition(get = "optionalGuava")
  private final String town;
  /** The city. */
  @PropertyDefinition(validate = "notNull")
  private final String city;
  /** The owner. */
  @PropertyDefinition(validate = "notNull")
  private final ImmPerson owner;
  /** The list. */
  @PropertyDefinition(validate = "notNull")
  private final ImmutableList<String> list;
  /** The currency. */
  @PropertyDefinition(get = "optionalGuava")
  private final Currency currency;

  // -----------------------------------------------------------------------
  // manual getter with a different name
  public String getStreetName() {
    return street;
  }

  // ------------------------- AUTOGENERATED START -------------------------
  /// CLOVER:OFF
  /** The meta-bean for {@code Light}. */
  private static MetaBean META_BEAN = LightMetaBean.of(Light.class);

  /**
   * The meta-bean for {@code Light}.
   *
   * @return the meta-bean, not null
   */
  public static MetaBean meta() {
    return META_BEAN;
  }

  static {
    JodaBeanUtils.registerMetaBean(META_BEAN);
  }

  /** The serialization version id. */
  private static final long serialVersionUID = 1L;

  private Light(
      int number,
      boolean flag,
      String street,
      String town,
      String city,
      ImmPerson owner,
      List<String> list,
      Currency currency) {
    JodaBeanUtils.notNull(street, "street");
    JodaBeanUtils.notNull(city, "city");
    JodaBeanUtils.notNull(owner, "owner");
    JodaBeanUtils.notNull(list, "list");
    this.number = number;
    this.flag = flag;
    this.street = street;
    this.town = town;
    this.city = city;
    this.owner = owner;
    this.list = ImmutableList.copyOf(list);
    this.currency = currency;
  }

  @Override
  public MetaBean metaBean() {
    return META_BEAN;
  }

  @Override
  public <R> Property<R> property(String propertyName) {
    return metaBean().<R>metaProperty(propertyName).createProperty(this);
  }

  @Override
  public Set<String> propertyNames() {
    return metaBean().metaPropertyMap().keySet();
  }

  // -----------------------------------------------------------------------
  /**
   * Gets the number.
   *
   * @return the value of the property
   */
  public int getNumber() {
    return number;
  }

  // -----------------------------------------------------------------------
  /**
   * Gets the number.
   *
   * @return the value of the property
   */
  public boolean isFlag() {
    return flag;
  }

  // -----------------------------------------------------------------------
  /**
   * Gets the town.
   *
   * @return the optional value of the property, not null
   */
  public Optional<String> getTown() {
    return Optional.fromNullable(town);
  }

  // -----------------------------------------------------------------------
  /**
   * Gets the city.
   *
   * @return the value of the property, not null
   */
  public String getCity() {
    return city;
  }

  // -----------------------------------------------------------------------
  /**
   * Gets the owner.
   *
   * @return the value of the property, not null
   */
  public ImmPerson getOwner() {
    return owner;
  }

  // -----------------------------------------------------------------------
  /**
   * Gets the list.
   *
   * @return the value of the property, not null
   */
  public ImmutableList<String> getList() {
    return list;
  }

  // -----------------------------------------------------------------------
  /**
   * Gets the currency.
   *
   * @return the optional value of the property, not null
   */
  public Optional<Currency> getCurrency() {
    return Optional.fromNullable(currency);
  }

  // -----------------------------------------------------------------------
  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (obj != null && obj.getClass() == this.getClass()) {
      Light other = (Light) obj;
      return (number == other.number)
          && (flag == other.flag)
          && JodaBeanUtils.equal(street, other.street)
          && JodaBeanUtils.equal(town, other.town)
          && JodaBeanUtils.equal(city, other.city)
          && JodaBeanUtils.equal(owner, other.owner)
          && JodaBeanUtils.equal(list, other.list)
          && JodaBeanUtils.equal(currency, other.currency);
    }
    return false;
  }

  @Override
  public int hashCode() {
    int hash = getClass().hashCode();
    hash = hash * 31 + JodaBeanUtils.hashCode(number);
    hash = hash * 31 + JodaBeanUtils.hashCode(flag);
    hash = hash * 31 + JodaBeanUtils.hashCode(street);
    hash = hash * 31 + JodaBeanUtils.hashCode(town);
    hash = hash * 31 + JodaBeanUtils.hashCode(city);
    hash = hash * 31 + JodaBeanUtils.hashCode(owner);
    hash = hash * 31 + JodaBeanUtils.hashCode(list);
    hash = hash * 31 + JodaBeanUtils.hashCode(currency);
    return hash;
  }

  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder(288);
    buf.append("Light{");
    buf.append("number").append('=').append(number).append(',').append(' ');
    buf.append("flag").append('=').append(flag).append(',').append(' ');
    buf.append("street").append('=').append(street).append(',').append(' ');
    buf.append("town").append('=').append(town).append(',').append(' ');
    buf.append("city").append('=').append(city).append(',').append(' ');
    buf.append("owner").append('=').append(owner).append(',').append(' ');
    buf.append("list").append('=').append(list).append(',').append(' ');
    buf.append("currency").append('=').append(JodaBeanUtils.toString(currency));
    buf.append('}');
    return buf.toString();
  }

  /// CLOVER:ON
  // -------------------------- AUTOGENERATED END --------------------------
}
/**
 * A one-dimensional interpolator. The interpolation is linear on x y^2. The interpolator is used
 * for interpolation on integrated variance for options. All values of y must be positive.
 */
@BeanDefinition(style = "light", constructorScope = "public")
public final class TimeSquareInterpolator1D extends Interpolator1D
    implements CurveInterpolator, ImmutableBean, Serializable {

  /* Level below which the value is consider to be 0. */
  private static final double EPS = 1.0E-10;

  /** Interpolator name. */
  private static final String NAME = "TimeSquare";

  // -------------------------------------------------------------------------
  @Override
  public Double interpolate(Interpolator1DDataBundle data, Double value) {
    JodaBeanUtils.notNull(value, "value");
    JodaBeanUtils.notNull(data, "data");
    ArgChecker.isTrue(value > 0, "Value should be stricly positive");
    InterpolationBoundedValues boundedValues = data.getBoundedValues(value);
    double x1 = boundedValues.getLowerBoundKey();
    double y1 = boundedValues.getLowerBoundValue();
    if (boundedValues.getLowerBoundIndex() == data.size() - 1) {
      return y1;
    }
    double x2 = boundedValues.getHigherBoundKey();
    double y2 = boundedValues.getHigherBoundValue();
    double w = (x2 - value) / (x2 - x1);
    double xy21 = x1 * y1 * y1;
    double xy22 = x2 * y2 * y2;
    double xy2 = w * xy21 + (1 - w) * xy22;
    return Math.sqrt(xy2 / value);
  }

  @Override
  public double firstDerivative(Interpolator1DDataBundle data, Double value) {
    JodaBeanUtils.notNull(value, "value");
    JodaBeanUtils.notNull(data, "data");
    ArgChecker.isTrue(value > 0, "Value should be stricly positive");
    int lowerIndex = data.getLowerBoundIndex(value);
    int index;
    if (lowerIndex == data.size() - 1) {
      index = data.size() - 2;
    } else {
      index = lowerIndex;
    }
    double x1 = data.getKeys()[index];
    double y1 = data.getValues()[index];
    double x2 = data.getKeys()[index + 1];
    double y2 = data.getValues()[index + 1];
    if ((y1 < EPS) || (y2 < EPS)) {
      throw new UnsupportedOperationException(
          "node sensitivity not implemented when one node is 0 value");
    }
    double w = (x2 - value) / (x2 - x1);
    double xy21 = x1 * y1 * y1;
    double xy22 = x2 * y2 * y2;
    double xy2 = w * xy21 + (1 - w) * xy22;
    return 0.5
        * (-Math.sqrt(xy2 / value) + (-xy21 + xy22) / (x2 - x1) / Math.sqrt(xy2 / value))
        / value;
  }

  @Override
  public double[] getNodeSensitivitiesForValue(Interpolator1DDataBundle data, Double value) {
    ArgChecker.notNull(value, "Value to be interpolated must not be null");
    ArgChecker.notNull(data, "Data bundle must not be null");
    int n = data.size();
    double[] resultSensitivity = new double[n];
    InterpolationBoundedValues boundedValues = data.getBoundedValues(value);
    double x1 = boundedValues.getLowerBoundKey();
    double y1 = boundedValues.getLowerBoundValue();
    int index = boundedValues.getLowerBoundIndex();
    if (index == n - 1) {
      resultSensitivity[n - 1] = 1.0;
      return resultSensitivity;
    }
    double x2 = boundedValues.getHigherBoundKey();
    double y2 = boundedValues.getHigherBoundValue();
    if ((y1 < EPS) || (y2 < EPS)) {
      throw new UnsupportedOperationException(
          "node sensitivity not implemented when one node is 0 value");
    }
    double w = (x2 - value) / (x2 - x1);
    double xy21 = x1 * y1 * y1;
    double xy22 = x2 * y2 * y2;
    double xy2 = w * xy21 + (1 - w) * xy22;
    double resultValue = Math.sqrt(xy2 / value);
    double resultValueBar = 1.0;
    double xy2Bar = 0.5 / resultValue / value * resultValueBar;
    double xy21Bar = w * xy2Bar;
    double xy22Bar = (1 - w) * xy2Bar;
    double y2Bar = 2 * x2 * y2 * xy22Bar;
    double y1Bar = 2 * x1 * y1 * xy21Bar;
    resultSensitivity[index] = y1Bar;
    resultSensitivity[index + 1] = y2Bar;
    return resultSensitivity;
  }

  @Override
  public Interpolator1DDataBundle getDataBundle(double[] x, double[] y) {
    ArgChecker.notNull(y, "y");
    int nY = y.length;
    for (int i = 0; i < nY; ++i) {
      ArgChecker.isTrue(y[i] >= 0.0, "All values in y must be positive");
    }
    return new ArrayInterpolator1DDataBundle(x, y);
  }

  @Override
  public Interpolator1DDataBundle getDataBundleFromSortedArrays(double[] x, double[] y) {
    ArgChecker.notNull(y, "y");
    int nY = y.length;
    for (int i = 0; i < nY; ++i) {
      ArgChecker.isTrue(y[i] >= 0.0, "All values in y must be positive");
    }
    return new ArrayInterpolator1DDataBundle(x, y, true);
  }

  @Override
  public String getName() {
    return NAME;
  }
  // ------------------------- AUTOGENERATED START -------------------------
  /// CLOVER:OFF
  /** The meta-bean for {@code TimeSquareInterpolator1D}. */
  private static MetaBean META_BEAN = LightMetaBean.of(TimeSquareInterpolator1D.class);

  /**
   * The meta-bean for {@code TimeSquareInterpolator1D}.
   *
   * @return the meta-bean, not null
   */
  public static MetaBean meta() {
    return META_BEAN;
  }

  /** The serialization version id. */
  private static final long serialVersionUID = 1L;

  /** Creates an instance. */
  public TimeSquareInterpolator1D() {}

  @Override
  public MetaBean metaBean() {
    return META_BEAN;
  }

  @Override
  public <R> Property<R> property(String propertyName) {
    return metaBean().<R>metaProperty(propertyName).createProperty(this);
  }

  @Override
  public Set<String> propertyNames() {
    return metaBean().metaPropertyMap().keySet();
  }

  // -----------------------------------------------------------------------
  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (obj != null && obj.getClass() == this.getClass()) {
      return true;
    }
    return false;
  }

  @Override
  public int hashCode() {
    int hash = getClass().hashCode();
    return hash;
  }

  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder(32);
    buf.append("TimeSquareInterpolator1D{");
    buf.append('}');
    return buf.toString();
  }

  /// CLOVER:ON
  // -------------------------- AUTOGENERATED END --------------------------
}
Beispiel #4
0
/** Dummy trade. Based on a FRA. */
@BeanDefinition(style = "light")
public final class DummyFraTrade implements Trade, ImmutableBean, Serializable {

  @PropertyDefinition(validate = "notNull")
  private final LocalDate date;

  @PropertyDefinition private final double fixedRate;

  public static DummyFraTrade of(LocalDate date, double fixedRate) {
    return new DummyFraTrade(date, fixedRate);
  }

  // ------------------------- AUTOGENERATED START -------------------------
  /// CLOVER:OFF
  /** The meta-bean for {@code DummyFraTrade}. */
  private static MetaBean META_BEAN = LightMetaBean.of(DummyFraTrade.class);

  /**
   * The meta-bean for {@code DummyFraTrade}.
   *
   * @return the meta-bean, not null
   */
  public static MetaBean meta() {
    return META_BEAN;
  }

  static {
    JodaBeanUtils.registerMetaBean(META_BEAN);
  }

  /** The serialization version id. */
  private static final long serialVersionUID = 1L;

  private DummyFraTrade(LocalDate date, double fixedRate) {
    JodaBeanUtils.notNull(date, "date");
    this.date = date;
    this.fixedRate = fixedRate;
  }

  @Override
  public MetaBean metaBean() {
    return META_BEAN;
  }

  @Override
  public <R> Property<R> property(String propertyName) {
    return metaBean().<R>metaProperty(propertyName).createProperty(this);
  }

  @Override
  public Set<String> propertyNames() {
    return metaBean().metaPropertyMap().keySet();
  }

  // -----------------------------------------------------------------------
  /**
   * Gets the date.
   *
   * @return the value of the property, not null
   */
  public LocalDate getDate() {
    return date;
  }

  // -----------------------------------------------------------------------
  /**
   * Gets the fixedRate.
   *
   * @return the value of the property
   */
  public double getFixedRate() {
    return fixedRate;
  }

  // -----------------------------------------------------------------------
  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (obj != null && obj.getClass() == this.getClass()) {
      DummyFraTrade other = (DummyFraTrade) obj;
      return JodaBeanUtils.equal(date, other.date)
          && JodaBeanUtils.equal(fixedRate, other.fixedRate);
    }
    return false;
  }

  @Override
  public int hashCode() {
    int hash = getClass().hashCode();
    hash = hash * 31 + JodaBeanUtils.hashCode(date);
    hash = hash * 31 + JodaBeanUtils.hashCode(fixedRate);
    return hash;
  }

  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder(96);
    buf.append("DummyFraTrade{");
    buf.append("date").append('=').append(date).append(',').append(' ');
    buf.append("fixedRate").append('=').append(JodaBeanUtils.toString(fixedRate));
    buf.append('}');
    return buf.toString();
  }

  /// CLOVER:ON
  // -------------------------- AUTOGENERATED END --------------------------
}
/**
 * Extrapolator that does no extrapolation itself and delegates to the interpolator for all
 * operations.
 *
 * <p>This reproduces the old behaviour in {@link CombinedInterpolatorExtrapolator} when the
 * extrapolators were null. This extrapolator is used in place of a null extrapolator which allows
 * the extrapolators to be non-null and makes for simpler and cleaner code where the extrapolators
 * are used.
 */
@BeanDefinition(style = "light", constructorScope = "public")
public final class InterpolatorExtrapolator
    implements CurveExtrapolator, Extrapolator1D, ImmutableBean, Serializable {

  /** The interpolator name. */
  public static final String NAME = "Interpolator";

  // -------------------------------------------------------------------------
  @Override
  public String getName() {
    return NAME;
  }

  @Override
  public Double extrapolate(
      Interpolator1DDataBundle data, Double value, Interpolator1D interpolator) {
    JodaBeanUtils.notNull(data, "data");
    return interpolator.interpolate(data, value);
  }

  @Override
  public double firstDerivative(
      Interpolator1DDataBundle data, Double value, Interpolator1D interpolator) {
    JodaBeanUtils.notNull(data, "data");
    return interpolator.firstDerivative(data, value);
  }

  @Override
  public double[] getNodeSensitivitiesForValue(
      Interpolator1DDataBundle data, Double value, Interpolator1D interpolator) {
    JodaBeanUtils.notNull(data, "data");
    return interpolator.getNodeSensitivitiesForValue(data, value);
  }

  // ------------------------- AUTOGENERATED START -------------------------
  /// CLOVER:OFF
  /** The meta-bean for {@code InterpolatorExtrapolator}. */
  private static MetaBean META_BEAN = LightMetaBean.of(InterpolatorExtrapolator.class);

  /**
   * The meta-bean for {@code InterpolatorExtrapolator}.
   *
   * @return the meta-bean, not null
   */
  public static MetaBean meta() {
    return META_BEAN;
  }

  /** The serialization version id. */
  private static final long serialVersionUID = 1L;

  /** Creates an instance. */
  public InterpolatorExtrapolator() {}

  @Override
  public MetaBean metaBean() {
    return META_BEAN;
  }

  @Override
  public <R> Property<R> property(String propertyName) {
    return metaBean().<R>metaProperty(propertyName).createProperty(this);
  }

  @Override
  public Set<String> propertyNames() {
    return metaBean().metaPropertyMap().keySet();
  }

  // -----------------------------------------------------------------------
  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (obj != null && obj.getClass() == this.getClass()) {
      return true;
    }
    return false;
  }

  @Override
  public int hashCode() {
    int hash = getClass().hashCode();
    return hash;
  }

  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder(32);
    buf.append("InterpolatorExtrapolator{");
    buf.append('}');
    return buf.toString();
  }

  /// CLOVER:ON
  // -------------------------- AUTOGENERATED END --------------------------
}
/**
 * The Hagan SABR volatility function provider.
 *
 * <p>This class provides the functions of volatility and its sensitivity to the SABR model
 * parameters based on the original Hagan SABR formula.
 *
 * <p>Reference: Hagan, P.; Kumar, D.; Lesniewski, A. & Woodward, D. "Managing smile risk", Wilmott
 * Magazine, 2002, September, 84-108
 *
 * <p>OpenGamma documentation: SABR Implementation, OpenGamma documentation n. 33, April 2016.
 */
@BeanDefinition(style = "light")
public final class SabrHaganVolatilityFunctionProvider
    extends VolatilityFunctionProvider<SabrFormulaData>
    implements SabrVolatilityFormula, ImmutableBean, Serializable {

  /** Default implementation. */
  public static final SabrHaganVolatilityFunctionProvider DEFAULT =
      new SabrHaganVolatilityFunctionProvider();

  /* internal parameters */
  private static final double CUTOFF_MONEYNESS = 1e-12;
  private static final double SMALL_Z = 1e-6;
  private static final double LARGE_NEG_Z = -1e6;
  private static final double LARGE_POS_Z = 1e8;
  private static final double BETA_EPS = 1e-8;
  private static final double RHO_EPS = 1e-5;
  private static final double RHO_EPS_NEGATIVE = 1e-8;
  private static final double ATM_EPS = 1e-7;
  private static final double MIN_VOL =
      1e-6; // Minimal volatility, to avoid negative volatility for extreme parameters

  // -------------------------------------------------------------------------
  @Override
  public double volatility(
      double forward, double strike, double timeToExpiry, SabrFormulaData data) {
    ArgChecker.notNull(data, "data");
    double alpha = data.getAlpha();
    double beta = data.getBeta();
    double rho = data.getRho();
    double nu = data.getNu();
    return volatility(forward, strike, timeToExpiry, alpha, beta, rho, nu);
  }

  @Override
  public double volatility(
      double forward,
      double strike,
      double timeToExpiry,
      double alpha,
      double beta,
      double rho,
      double nu) {

    ArgChecker.isTrue(forward > 0.0, "forward must be greater than zero");
    ArgChecker.isTrue(strike >= 0.0, "strike must be greater than zero");
    ArgChecker.isTrue(timeToExpiry >= 0.0, "timeToExpiry must be greater than zero");

    if (alpha == 0.0) {
      return 0.0;
    }
    double cutoff = forward * CUTOFF_MONEYNESS;
    double k;
    if (strike < cutoff) {
      Logger s_logger = LoggerFactory.getLogger(SabrHaganVolatilityFunctionProvider.class);
      s_logger.info(
          "Given strike of {} is less than cutoff at {}, therefore the strike is taken as {}",
          new Object[] {strike, cutoff, cutoff});
      k = cutoff;
    } else {
      k = strike;
    }
    double vol, z, zOverChi;
    double beta1 = 1 - beta;
    if (DoubleMath.fuzzyEquals(forward, k, ATM_EPS)) {
      double f1 = Math.pow(forward, beta1);
      vol =
          alpha
              * (1
                  + timeToExpiry
                      * (beta1 * beta1 * alpha * alpha / 24 / f1 / f1
                          + rho * alpha * beta * nu / 4 / f1
                          + nu * nu * (2 - 3 * rho * rho) / 24))
              / f1;
    } else {
      if (DoubleMath.fuzzyEquals(beta, 0, BETA_EPS)) {
        double ln = Math.log(forward / k);
        z = nu * Math.sqrt(forward * k) * ln / alpha;
        zOverChi = getZOverChi(rho, z);
        vol =
            alpha
                * ln
                * zOverChi
                * (1
                    + timeToExpiry
                        * (alpha * alpha / forward / k + nu * nu * (2 - 3 * rho * rho))
                        / 24)
                / (forward - k);
      } else if (DoubleMath.fuzzyEquals(beta, 1, BETA_EPS)) {
        double ln = Math.log(forward / k);
        z = nu * ln / alpha;
        zOverChi = getZOverChi(rho, z);
        vol =
            alpha
                * zOverChi
                * (1 + timeToExpiry * (rho * alpha * nu / 4 + nu * nu * (2 - 3 * rho * rho) / 24));
      } else {
        double ln = Math.log(forward / k);
        double f1 = Math.pow(forward * k, beta1);
        double f1Sqrt = Math.sqrt(f1);
        double lnBetaSq = Math.pow(beta1 * ln, 2);
        z = nu * f1Sqrt * ln / alpha;
        zOverChi = getZOverChi(rho, z);
        double first = alpha / (f1Sqrt * (1 + lnBetaSq / 24 + lnBetaSq * lnBetaSq / 1920));
        double second = zOverChi;
        double third =
            1
                + timeToExpiry
                    * (beta1 * beta1 * alpha * alpha / 24 / f1
                        + rho * nu * beta * alpha / 4 / f1Sqrt
                        + nu * nu * (2 - 3 * rho * rho) / 24);
        vol = first * second * third;
      }
    }
    // There is nothing to prevent the nu * nu * (2 - 3 * rho * rho) / 24 to be large negative, and
    // hence the volatility negative
    return Math.max(MIN_VOL, vol);
  }

  /**
   * Computes the implied volatility in the SABR model and its derivatives.
   *
   * <p>The derivatives are stored in an array with:
   *
   * <ul>
   *   <li>[0] derivative with respect to the forward
   *   <li>[1] derivative with respect to the strike
   *   <li>[2] derivative with respect to the alpha
   *   <li>[3] derivative with respect to the beta
   *   <li>[4] derivative with respect to the rho
   *   <li>[5] derivative with respect to the nu
   * </ul>
   *
   * @param forward the forward value of the underlying
   * @param strike the strike value of the option
   * @param timeToExpiry the time to expiry of the option
   * @param data the SABR data
   * @return the volatility and associated derivatives
   */
  @Override
  public ValueDerivatives volatilityAdjoint(
      double forward, double strike, double timeToExpiry, SabrFormulaData data) {
    ArgChecker.notNull(data, "data");
    double alpha = data.getAlpha();
    double beta = data.getBeta();
    double rho = data.getRho();
    double nu = data.getNu();
    return volatilityAdjoint(forward, strike, timeToExpiry, alpha, beta, rho, nu);
  }

  /**
   * Computes the implied volatility in the SABR model and its derivatives.
   *
   * <p>The derivatives are stored in an array with:
   *
   * <ul>
   *   <li>[0] derivative with respect to the forward
   *   <li>[1] derivative with respect to the strike
   *   <li>[2] derivative with respect to the alpha
   *   <li>[3] derivative with respect to the beta
   *   <li>[4] derivative with respect to the rho
   *   <li>[5] derivative with respect to the nu
   * </ul>
   *
   * @param forward the forward value of the underlying
   * @param strike the strike value of the option
   * @param timeToExpiry the time to expiry of the option
   * @param alpha the SABR alpha value
   * @param beta the SABR beta value
   * @param rho the SABR rho value
   * @param nu the SABR nu value
   * @return the volatility and associated derivatives
   */
  @Override
  public ValueDerivatives volatilityAdjoint(
      double forward,
      double strike,
      double timeToExpiry,
      double alpha,
      double beta,
      double rho,
      double nu) {

    ArgChecker.isTrue(forward > 0.0, "forward must be greater than zero");
    ArgChecker.isTrue(strike >= 0.0, "strike must be greater than zero");
    ArgChecker.isTrue(timeToExpiry >= 0.0, "timeToExpiry must be greater than zero");
    double cutoff = forward * CUTOFF_MONEYNESS;
    double k = strike;
    if (k < cutoff) {
      Logger s_logger = LoggerFactory.getLogger(SabrHaganVolatilityFunctionProvider.class);
      s_logger.info(
          "Given strike of {} is less than cutoff at {}, therefore the strike is taken as {}",
          new Object[] {k, cutoff, cutoff});
      k = cutoff;
    }
    double betaStar = 1 - beta;
    double rhoStar = 1.0 - rho;

    if (alpha == 0.0) {
      double alphaBar;
      if (DoubleMath.fuzzyEquals(forward, k, ATM_EPS)) { // TODO should this is relative
        alphaBar =
            (1 + (2 - 3 * rho * rho) * nu * nu / 24 * timeToExpiry) / Math.pow(forward, betaStar);
      } else {
        // for non-atm options the alpha sensitivity at alpha = 0 is infinite. Returning this will
        // most likely break calibrations,
        // so we return an arbitrary large number
        alphaBar = 1e7;
      }
      return ValueDerivatives.of(0d, DoubleArray.of(0, 0, alphaBar, 0, 0, 0));
    }

    // Implementation note: Forward sweep.
    double sfK = Math.pow(forward * k, betaStar / 2);
    double lnrfK = Math.log(forward / k);
    double z = nu / alpha * sfK * lnrfK;
    double rzxz;
    double xz = 0;
    if (DoubleMath.fuzzyEquals(z, 0.0, SMALL_Z)) {
      rzxz = 1.0 - 0.5 * z * rho; // small z expansion to z^2 terms
    } else {
      if (DoubleMath.fuzzyEquals(rhoStar, 0.0, RHO_EPS)) {
        if (z < 1.0) {
          xz = -Math.log(1.0d - z);
          rzxz = z / xz;
        } else {
          throw new IllegalArgumentException("can't handle z>=1, rho=1");
        }
      } else {
        double arg;
        if (z < LARGE_NEG_Z) {
          arg =
              (rho * rho - 1)
                  / 2
                  / z; // get rounding errors due to fine balanced cancellation for very large
                       // negative z
        } else if (z > LARGE_POS_Z) {
          arg = 2 * (z - rho);
        } else {
          arg = (Math.sqrt(1 - 2 * rho * z + z * z) + z - rho);
        }
        if (arg
            <= 0.0) { // Mathematically this cannot be less than zero, but you know what computers
                      // are like.
          rzxz = 0.0;
        } else {
          xz = Math.log(arg / (1 - rho));
          rzxz = z / xz;
        }
      }
    }
    double sf1 =
        sfK
            * (1
                + betaStar * betaStar / 24 * (lnrfK * lnrfK)
                + Math.pow(betaStar, 4) / 1920 * Math.pow(lnrfK, 4));
    double sf2 =
        (1
            + (Math.pow(betaStar * alpha / sfK, 2) / 24
                    + (rho * beta * nu * alpha) / (4 * sfK)
                    + (2 - 3 * rho * rho) * nu * nu / 24)
                * timeToExpiry);
    double volatility = Math.max(MIN_VOL, alpha / sf1 * rzxz * sf2);

    // Implementation note: Backward sweep.
    double vBar = 1;
    double sf2Bar = alpha / sf1 * rzxz * vBar;
    double sf1Bar = -alpha / (sf1 * sf1) * rzxz * sf2 * vBar;
    double rzxzBar = alpha / sf1 * sf2 * vBar;
    double zBar;
    double xzBar = 0.0;
    if (DoubleMath.fuzzyEquals(z, 0.0, SMALL_Z)) {
      zBar = -rho / 2 * rzxzBar;
    } else {
      if (DoubleMath.fuzzyEquals(rhoStar, 0.0, RHO_EPS)) {
        if (z < 1.0) {
          xzBar = -z / (xz * xz) * rzxzBar;
          zBar = 1.0d / xz * rzxzBar + 1.0d / (1.0d - z) * xzBar;
        } else {
          throw new IllegalArgumentException("can't handle z>=1, rho=1");
        }
      } else {
        if (z < LARGE_NEG_Z) {
          zBar = 1 / xz * rzxzBar + xzBar / (xz * xz) * rzxzBar;
        } else if (z > LARGE_POS_Z) {
          zBar = 1 / xz * rzxzBar - xzBar / (xz * xz) * rzxzBar;
        } else {
          xzBar = -z / (xz * xz) * rzxzBar;
          zBar =
              1 / xz * rzxzBar
                  + 1
                      / ((Math.sqrt(1 - 2 * rho * z + z * z) + z - rho))
                      * (0.5 * Math.pow(1 - 2 * rho * z + z * z, -0.5) * (-2 * rho + 2 * z) + 1)
                      * xzBar;
        }
      }
    }

    double lnrfKBar =
        sfK
                * (betaStar * betaStar / 12 * lnrfK
                    + Math.pow(betaStar, 4) / 1920 * 4 * Math.pow(lnrfK, 3))
                * sf1Bar
            + nu / alpha * sfK * zBar;
    double sfKBar =
        nu / alpha * lnrfK * zBar
            + sf1 / sfK * sf1Bar
            - (Math.pow(betaStar * alpha, 2) / Math.pow(sfK, 3) / 12
                    + (rho * beta * nu * alpha) / 4 / (sfK * sfK))
                * timeToExpiry
                * sf2Bar;
    double strikeBar = -1 / k * lnrfKBar + betaStar * sfK / (2 * k) * sfKBar;
    double forwardBar = 1 / forward * lnrfKBar + betaStar * sfK / (2 * forward) * sfKBar;
    double nuBar =
        1 / alpha * sfK * lnrfK * zBar
            + ((rho * beta * alpha) / (4 * sfK) + (2 - 3 * rho * rho) * nu / 12)
                * timeToExpiry
                * sf2Bar;

    double rhoBar;
    if (Math.abs(forward - k) < ATM_EPS) {
      rhoBar = -z / 2 * rzxzBar;
    } else {
      if (DoubleMath.fuzzyEquals(rhoStar, 0.0, RHO_EPS)) {
        if (z >= 1) {
          if (rhoStar == 0.0) {
            rhoBar =
                Double
                    .NEGATIVE_INFINITY; // the derivative at rho = 1 is infinite  - this sets it to
                                        // some arbitrary large number
          } else {
            rhoBar = xzBar * (1.0 / rhoStar + (0.5 - z) / (z - 1.0) / (z - 1.0));
          }
        } else {
          rhoBar =
              (0.5 * Math.pow(z / (1 - z), 2)
                      + 0.25 * (z - 4.0) * Math.pow(z / (1.0 - z), 3) / (1.0 - z) * rhoStar)
                  * xzBar;
        }
      } else {
        rhoBar =
            (1
                        / (Math.sqrt(1 - 2 * rho * z + z * z) + z - rho)
                        * (-Math.pow(1 - 2 * rho * z + z * z, -0.5) * z - 1)
                    + 1 / rhoStar)
                * xzBar;
      }
    }
    rhoBar += ((beta * nu * alpha) / (4 * sfK) - rho * nu * nu / 4) * timeToExpiry * sf2Bar;

    double alphaBar =
        -nu / (alpha * alpha) * sfK * lnrfK * zBar
            + ((betaStar * alpha / sfK) * (betaStar / sfK) / 12 + (rho * beta * nu) / (4 * sfK))
                * timeToExpiry
                * sf2Bar
            + 1 / sf1 * rzxz * sf2 * vBar;
    double betaBar =
        -0.5 * Math.log(forward * k) * sfK * sfKBar
            - sfK
                * (betaStar / 12 * (lnrfK * lnrfK)
                    + Math.pow(betaStar, 3) / 480 * Math.pow(lnrfK, 4))
                * sf1Bar
            + (-betaStar * alpha * alpha / sfK / sfK / 12 + rho * nu * alpha / 4 / sfK)
                * timeToExpiry
                * sf2Bar;

    return ValueDerivatives.of(
        volatility, DoubleArray.of(forwardBar, strikeBar, alphaBar, betaBar, rhoBar, nuBar));
  }

  /**
   * Computes the first and second order derivatives of the Black implied volatility in the SABR
   * model.
   *
   * <p>The first derivative values will be stored in the input array {@code volatilityD} The array
   * contains, [0] Derivative w.r.t the forward, [1] the derivative w.r.t the strike, [2] the
   * derivative w.r.t. to alpha, [3] the derivative w.r.t. to beta, [4] the derivative w.r.t. to
   * rho, and [5] the derivative w.r.t. to nu. Thus the length of the array should be 6.
   *
   * <p>The second derivative values will be stored in the input array {@code volatilityD2}. Only
   * the second order derivative with respect to the forward and strike are implemented. The array
   * contains [0][0] forward-forward; [0][1] forward-strike; [1][1] strike-strike. Thus the size
   * should be 2 x 2.
   *
   * <p>Around ATM, a first order expansion is used to due to some 0/0-type indetermination. The
   * second order derivative produced is poor around ATM.
   *
   * @param forward the forward value of the underlying
   * @param strike the strike value of the option
   * @param timeToExpiry the time to expiry of the option
   * @param data the SABR data.
   * @param volatilityD the array used to return the first order derivative
   * @param volatilityD2 the array of array used to return the second order derivative
   * @return the Black implied volatility
   */
  @Override
  public double volatilityAdjoint2(
      double forward,
      double strike,
      double timeToExpiry,
      SabrFormulaData data,
      double[] volatilityD,
      double[][] volatilityD2) {
    double k = Math.max(strike, 0.000001);
    double alpha = data.getAlpha();
    double beta = data.getBeta();
    double rho = data.getRho();
    double nu = data.getNu();
    // Forward
    double h0 = (1 - beta) / 2;
    double h1 = forward * k;
    double h1h0 = Math.pow(h1, h0);
    double h12 = h1h0 * h1h0;
    double h2 = Math.log(forward / k);
    double h22 = h2 * h2;
    double h23 = h22 * h2;
    double h24 = h23 * h2;
    double f1 = h1h0 * (1 + h0 * h0 / 6.0 * (h22 + h0 * h0 / 20.0 * h24));
    double f2 = nu / alpha * h1h0 * h2;
    double f3 =
        h0 * h0 / 6.0 * alpha * alpha / h12
            + rho * beta * nu * alpha / 4.0 / h1h0
            + (2 - 3 * rho * rho) / 24.0 * nu * nu;
    double sqrtf2 = Math.sqrt(1 - 2 * rho * f2 + f2 * f2);
    double f2x = 0.0;
    double x = 0.0, xp = 0, xpp = 0;
    if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z)) {
      f2x = 1.0 - 0.5 * f2 * rho; // small f2 expansion to f2^2 terms
    } else {
      if (DoubleMath.fuzzyEquals(rho, 1.0, RHO_EPS)) {
        x =
            f2 < 1.0
                ? -Math.log(1.0 - f2) - 0.5 * Math.pow(f2 / (f2 - 1.0), 2) * (1.0 - rho)
                : Math.log(2.0 * f2 - 2.0) - Math.log(1.0 - rho);
      } else {
        x = Math.log((sqrtf2 + f2 - rho) / (1 - rho));
      }
      xp = 1. / sqrtf2;
      xpp = (rho - f2) / Math.pow(sqrtf2, 3.0);
      f2x = f2 / x;
    }
    double sigma = Math.max(MIN_VOL, alpha / f1 * f2x * (1 + f3 * timeToExpiry));
    // First level
    double h0Dbeta = -0.5;
    double sigmaDf1 = -sigma / f1;
    double sigmaDf2 = 0;
    if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z)) {
      sigmaDf2 = alpha / f1 * (1 + f3 * timeToExpiry) * -0.5 * rho;
    } else {
      sigmaDf2 = alpha / f1 * (1 + f3 * timeToExpiry) * (1.0 / x - f2 * xp / (x * x));
    }
    double sigmaDf3 = alpha / f1 * f2x * timeToExpiry;
    double sigmaDf4 = f2x / f1 * (1 + f3 * timeToExpiry);
    double sigmaDx = -alpha / f1 * f2 / (x * x) * (1 + f3 * timeToExpiry);
    double[][] sigmaD2ff = new double[3][3];
    sigmaD2ff[0][0] = -sigmaDf1 / f1 + sigma / (f1 * f1); // OK
    sigmaD2ff[0][1] = -sigmaDf2 / f1;
    sigmaD2ff[0][2] = -sigmaDf3 / f1;
    if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z)) {
      sigmaD2ff[1][2] = alpha / f1 * -0.5 * rho * timeToExpiry;
    } else {
      sigmaD2ff[1][1] =
          alpha
              / f1
              * (1 + f3 * timeToExpiry)
              * (-2 * xp / (x * x) - f2 * xpp / (x * x) + 2 * f2 * xp * xp / (x * x * x));
      sigmaD2ff[1][2] = alpha / f1 * timeToExpiry * (1.0 / x - f2 * xp / (x * x));
    }
    sigmaD2ff[2][2] = 0.0;
    //      double sigma = alpha / f1 * f2x * (1 + f3 * theta);
    // Second level
    double[] f1Dh = new double[3];
    double[] f2Dh = new double[3];
    double[] f3Dh = new double[3];
    f1Dh[0] = h1h0 * (h0 * (h22 / 3.0 + h0 * h0 / 40.0 * h24)) + Math.log(h1) * f1;
    f1Dh[1] = h0 * f1 / h1;
    f1Dh[2] = h1h0 * (h0 * h0 / 6.0 * (2.0 * h2 + h0 * h0 / 5.0 * h23));
    f2Dh[0] = Math.log(h1) * f2;
    f2Dh[1] = h0 * f2 / h1;
    f2Dh[2] = nu / alpha * h1h0;
    f3Dh[0] =
        h0 / 3.0 * alpha * alpha / h12
            - 2 * h0 * h0 / 6.0 * alpha * alpha / h12 * Math.log(h1)
            - rho * beta * nu * alpha / 4.0 / h1h0 * Math.log(h1);
    f3Dh[1] =
        -2 * h0 * h0 / 6.0 * alpha * alpha / h12 * h0 / h1
            - rho * beta * nu * alpha / 4.0 / h1h0 * h0 / h1;
    f3Dh[2] = 0.0;
    double[] f1Dp = new double[4]; // Derivative to sabr parameters
    double[] f2Dp = new double[4];
    double[] f3Dp = new double[4];
    double[] f4Dp = new double[4];
    f1Dp[0] = 0.0;
    f1Dp[1] = f1Dh[0] * h0Dbeta;
    f1Dp[2] = 0.0;
    f1Dp[3] = 0.0;
    f2Dp[0] = -f2 / alpha;
    f2Dp[1] = f2Dh[0] * h0Dbeta;
    f2Dp[2] = 0.0;
    f2Dp[3] = h1h0 * h2 / alpha;
    f3Dp[0] = h0 * h0 / 3.0 * alpha / h12 + rho * beta * nu / 4.0 / h1h0;
    f3Dp[1] = rho * nu * alpha / 4.0 / h1h0 + f3Dh[0] * h0Dbeta;
    f3Dp[2] = beta * nu * alpha / 4.0 / h1h0 - rho / 4.0 * nu * nu;
    f3Dp[3] = rho * beta * alpha / 4.0 / h1h0 + (2 - 3 * rho * rho) / 12.0 * nu;
    f4Dp[0] = 1.0;
    f4Dp[1] = 0.0;
    f4Dp[2] = 0.0;
    f4Dp[3] = 0.0;
    double sigmaDh1 = sigmaDf1 * f1Dh[1] + sigmaDf2 * f2Dh[1] + sigmaDf3 * f3Dh[1];
    double sigmaDh2 = sigmaDf1 * f1Dh[2] + sigmaDf2 * f2Dh[2] + sigmaDf3 * f3Dh[2];
    double[][] f1D2hh = new double[2][2]; // No h0
    double[][] f2D2hh = new double[2][2];
    double[][] f3D2hh = new double[2][2];
    f1D2hh[0][0] = h0 * (h0 - 1) * f1 / (h1 * h1);
    f1D2hh[0][1] = h0 * h1h0 / h1 * h0 * h0 / 6.0 * (2.0 * h2 + 4.0 * h0 * h0 / 20.0 * h23);
    f1D2hh[1][1] = h1h0 * (h0 * h0 / 6.0 * (2.0 + 12.0 * h0 * h0 / 20.0 * h2));
    f2D2hh[0][0] = h0 * (h0 - 1) * f2 / (h1 * h1);
    f2D2hh[0][1] = nu / alpha * h0 * h1h0 / h1;
    f2D2hh[1][1] = 0.0;
    f3D2hh[0][0] =
        2 * h0 * (2 * h0 + 1) * h0 * h0 / 6.0 * alpha * alpha / (h12 * h1 * h1)
            + h0 * (h0 + 1) * rho * beta * nu * alpha / 4.0 / (h1h0 * h1 * h1);
    f3D2hh[0][1] = 0.0;
    f3D2hh[1][1] = 0.0;
    double[][] sigmaD2hh = new double[2][2]; // No h0
    for (int loopx = 0; loopx < 2; loopx++) {
      for (int loopy = loopx; loopy < 2; loopy++) {
        sigmaD2hh[loopx][loopy] =
            (sigmaD2ff[0][0] * f1Dh[loopy + 1]
                        + sigmaD2ff[0][1] * f2Dh[loopy + 1]
                        + sigmaD2ff[0][2] * f3Dh[loopy + 1])
                    * f1Dh[loopx + 1]
                + sigmaDf1 * f1D2hh[loopx][loopy]
                + (sigmaD2ff[0][1] * f1Dh[loopy + 1]
                        + sigmaD2ff[1][1] * f2Dh[loopy + 1]
                        + sigmaD2ff[1][2] * f3Dh[loopy + 1])
                    * f2Dh[loopx + 1]
                + sigmaDf2 * f2D2hh[loopx][loopy]
                + (sigmaD2ff[0][2] * f1Dh[loopy + 1]
                        + sigmaD2ff[1][2] * f2Dh[loopy + 1]
                        + sigmaD2ff[2][2] * f3Dh[loopy + 1])
                    * f3Dh[loopx + 1]
                + sigmaDf3 * f3D2hh[loopx][loopy];
      }
    }
    // Third level
    double h1Df = k;
    double h1Dk = forward;
    double h1D2ff = 0.0;
    double h1D2kf = 1.0;
    double h1D2kk = 0.0;
    double h2Df = 1.0 / forward;
    double h2Dk = -1.0 / k;
    double h2D2ff = -1 / (forward * forward);
    double h2D2fk = 0.0;
    double h2D2kk = 1.0 / (k * k);
    volatilityD[0] = sigmaDh1 * h1Df + sigmaDh2 * h2Df;
    volatilityD[1] = sigmaDh1 * h1Dk + sigmaDh2 * h2Dk;
    volatilityD[2] =
        sigmaDf1 * f1Dp[0] + sigmaDf2 * f2Dp[0] + sigmaDf3 * f3Dp[0] + sigmaDf4 * f4Dp[0];
    volatilityD[3] =
        sigmaDf1 * f1Dp[1] + sigmaDf2 * f2Dp[1] + sigmaDf3 * f3Dp[1] + sigmaDf4 * f4Dp[1];
    if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z)) {
      volatilityD[4] = -0.5 * f2 + sigmaDf3 * f3Dp[2];
    } else {
      double xDr;
      if (DoubleMath.fuzzyEquals(rho, 1.0, RHO_EPS)) {
        xDr =
            f2 > 1.0
                ? 1.0 / (1.0 - rho) + (0.5 - f2) / (f2 - 1.0) / (f2 - 1.0)
                : 0.5 * Math.pow(f2 / (1.0 - f2), 2.0)
                    + 0.25 * (f2 - 4.0) * Math.pow(f2 / (f2 - 1.0), 3) / (f2 - 1.0) * (1.0 - rho);
        if (Doubles.isFinite(xDr)) {
          volatilityD[4] =
              sigmaDf1 * f1Dp[2] + sigmaDx * xDr + sigmaDf3 * f3Dp[2] + sigmaDf4 * f4Dp[2];
        } else {
          volatilityD[4] = Double.NEGATIVE_INFINITY;
        }
      } else {
        xDr = (-f2 / sqrtf2 - 1 + (sqrtf2 + f2 - rho) / (1 - rho)) / (sqrtf2 + f2 - rho);
        volatilityD[4] =
            sigmaDf1 * f1Dp[2] + sigmaDx * xDr + sigmaDf3 * f3Dp[2] + sigmaDf4 * f4Dp[2];
      }
    }
    volatilityD[5] =
        sigmaDf1 * f1Dp[3] + sigmaDf2 * f2Dp[3] + sigmaDf3 * f3Dp[3] + sigmaDf4 * f4Dp[3];
    volatilityD2[0][0] =
        (sigmaD2hh[0][0] * h1Df + sigmaD2hh[0][1] * h2Df) * h1Df
            + sigmaDh1 * h1D2ff
            + (sigmaD2hh[0][1] * h1Df + sigmaD2hh[1][1] * h2Df) * h2Df
            + sigmaDh2 * h2D2ff;
    volatilityD2[0][1] =
        (sigmaD2hh[0][0] * h1Dk + sigmaD2hh[0][1] * h2Dk) * h1Df
            + sigmaDh1 * h1D2kf
            + (sigmaD2hh[0][1] * h1Dk + sigmaD2hh[1][1] * h2Dk) * h2Df
            + sigmaDh2 * h2D2fk;
    volatilityD2[1][0] = volatilityD2[0][1];
    volatilityD2[1][1] =
        (sigmaD2hh[0][0] * h1Dk + sigmaD2hh[0][1] * h2Dk) * h1Dk
            + sigmaDh1 * h1D2kk
            + (sigmaD2hh[0][1] * h1Dk + sigmaD2hh[1][1] * h2Dk) * h2Dk
            + sigmaDh2 * h2D2kk;
    return sigma;
  }

  private double getZOverChi(double rho, double z) {

    // Implementation comment: To avoid numerical instability (0/0) around ATM the first order
    // approximation is used.
    if (DoubleMath.fuzzyEquals(z, 0.0, SMALL_Z)) {
      return 1.0 - rho * z / 2.0;
    }

    double rhoStar = 1 - rho;
    if (DoubleMath.fuzzyEquals(rhoStar, 0.0, RHO_EPS)) {
      if (z < 1.0) {
        return -z / Math.log(1.0d - z);
      } else {
        throw new IllegalArgumentException("can't handle z>=1, rho=1");
      }
    }

    double rhoHat = 1 + rho;
    if (DoubleMath.fuzzyEquals(rhoHat, 0.0, RHO_EPS_NEGATIVE)) {
      if (z > -1) {
        return z / Math.log(1 + z);
      } else if (z < -1) {
        if (rhoHat == 0) {
          return 0.0;
        }
        double chi = Math.log(rhoHat) - Math.log(-(1 + z) / rhoStar);
        return z / chi;
      } else {
        return 0.0;
      }
    }

    double arg;
    if (z < LARGE_NEG_Z) {
      arg =
          (rho * rho - 1)
              / 2
              / z; // get rounding errors due to fine balanced cancellation for very large negative
                   // z
    } else if (z > LARGE_POS_Z) {
      arg = 2 * (z - rho);
    } else {
      arg = (Math.sqrt(1 - 2 * rho * z + z * z) + z - rho);
      // Mathematically this cannot be less than zero, but you know what computers are like.
      if (arg <= 0.0) {
        return 0.0;
      }
    }

    double chi = Math.log(arg) - Math.log(rhoStar);
    return z / chi;
  }

  @Override
  public int hashCode() {
    return toString().hashCode();
  }

  @Override
  public boolean equals(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)";
  }

  // ------------------------- AUTOGENERATED START -------------------------
  /// CLOVER:OFF
  /** The meta-bean for {@code SabrHaganVolatilityFunctionProvider}. */
  private static MetaBean META_BEAN = LightMetaBean.of(SabrHaganVolatilityFunctionProvider.class);

  /**
   * The meta-bean for {@code SabrHaganVolatilityFunctionProvider}.
   *
   * @return the meta-bean, not null
   */
  public static MetaBean meta() {
    return META_BEAN;
  }

  static {
    JodaBeanUtils.registerMetaBean(META_BEAN);
  }

  /** The serialization version id. */
  private static final long serialVersionUID = 1L;

  private SabrHaganVolatilityFunctionProvider() {}

  @Override
  public MetaBean metaBean() {
    return META_BEAN;
  }

  @Override
  public <R> Property<R> property(String propertyName) {
    return metaBean().<R>metaProperty(propertyName).createProperty(this);
  }

  @Override
  public Set<String> propertyNames() {
    return metaBean().metaPropertyMap().keySet();
  }

  // -----------------------------------------------------------------------
  /// CLOVER:ON
  // -------------------------- AUTOGENERATED END --------------------------
}