/** * 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 -------------------------- }
/** * 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 -------------------------- }
/** 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 -------------------------- }