@Test(enabled = true) /** Tests approximation error. "enabled = false" for the standard testing. */ public void errorAnalysis() { double bp1 = 10000; double errorLimit = 5.0E-1; // 0.5 bp ParRateCalculator prc = ParRateCalculator.getInstance(); double forward = prc.visit(SWAP_PAYER, CURVES); double[] strikeRel = new double[] {-0.0250, -0.0150, -0.0050, 0.0, 0.0050, 0.0150, 0.0250}; double[] pvPayerApproximation = new double[strikeRel.length]; double[] pvPayerIntegration = new double[strikeRel.length]; double[] pvReceiverApproximation = new double[strikeRel.length]; double[] pvReceiverIntegration = new double[strikeRel.length]; for (int loopstrike = 0; loopstrike < strikeRel.length; loopstrike++) { SwapFixedIborDefinition swapStrikePayerDefinition = SwapFixedIborDefinition.from( SETTLEMENT_DATE, CMS_INDEX, bp1, forward + strikeRel[loopstrike], FIXED_IS_PAYER); SwaptionCashFixedIborDefinition swaptionStrikePayerDefinition = SwaptionCashFixedIborDefinition.from(EXPIRY_DATE, swapStrikePayerDefinition, IS_LONG); SwaptionCashFixedIbor swaptionStrikePayer = swaptionStrikePayerDefinition.toDerivative(REFERENCE_DATE, CURVES_NAME); pvPayerApproximation[loopstrike] = METHOD_HW_APPROXIMATION.presentValue(swaptionStrikePayer, BUNDLE_HW).getAmount(); pvPayerIntegration[loopstrike] = METHOD_HW_INTEGRATION.presentValue(swaptionStrikePayer, BUNDLE_HW).getAmount(); assertEquals( "Swaption cash - Hull-White - present value - explicit/numerical integration", pvPayerApproximation[loopstrike], pvPayerIntegration[loopstrike], errorLimit); SwapFixedIborDefinition swapStrikeReceiverDefinition = SwapFixedIborDefinition.from( SETTLEMENT_DATE, CMS_INDEX, bp1, forward + strikeRel[loopstrike], !FIXED_IS_PAYER); SwaptionCashFixedIborDefinition swaptionStrikeReceiverDefinition = SwaptionCashFixedIborDefinition.from(EXPIRY_DATE, swapStrikeReceiverDefinition, IS_LONG); SwaptionCashFixedIbor swaptionStrikeReceiver = swaptionStrikeReceiverDefinition.toDerivative(REFERENCE_DATE, CURVES_NAME); pvReceiverApproximation[loopstrike] = METHOD_HW_APPROXIMATION.presentValue(swaptionStrikeReceiver, BUNDLE_HW).getAmount(); pvReceiverIntegration[loopstrike] = METHOD_HW_INTEGRATION.presentValue(swaptionStrikeReceiver, BUNDLE_HW).getAmount(); assertEquals( "Swaption cash - Hull-White - present value - explicit/numerical integration", pvReceiverApproximation[loopstrike], pvReceiverIntegration[loopstrike], errorLimit); } }
/** * Class used to compute the price of a CMS cap/floor by swaption replication on a SABR formula with * extrapolation. Reference: Hagan, P. S. (2003). Convexity conundrums: Pricing CMS swaps, caps, and * floors. Wilmott Magazine, March, pages 38--44. OpenGamma implementation note: Replication pricing * for linear and TEC format CMS, Version 1.2, March 2011. OpenGamma implementation note for the * extrapolation: Smile extrapolation, version 1.2, May 2011. * * @deprecated Use classes descended from {@link CapFloorCMSSABRReplicationAbstractMethod} */ @Deprecated public class CapFloorCMSSABRExtrapolationRightReplicationMethod extends CapFloorCMSSABRReplicationAbstractMethod { /** The cut-off strike. The smile is extrapolated above that level. */ private final double _cutOffStrike; /** The tail thickness parameter. */ private final double _mu; /** The par rate calculator. */ private static final ParRateCalculator PRC = ParRateCalculator.getInstance(); /** The par rate sensitivity calculator. */ private static final ParRateCurveSensitivityCalculator PRSC = ParRateCurveSensitivityCalculator.getInstance(); /** * Default constructor of the CMS cap/floor replication method. The default integration interval * is 1.00 (100%). * * @param cutOffStrike The cut-off strike. * @param mu The tail thickness parameter. */ public CapFloorCMSSABRExtrapolationRightReplicationMethod( final double cutOffStrike, final double mu) { super(1.0); _mu = mu; _cutOffStrike = cutOffStrike; } /** * Default constructor of the CMS cap/floor replication method. The default integration interval * is 1.00 (100%). * * @param cutOffStrike The cut-off strike. * @param mu The tail thickness parameter. * @param integrationInterval Integration range. */ public CapFloorCMSSABRExtrapolationRightReplicationMethod( final double cutOffStrike, final double mu, final double integrationInterval) { super(integrationInterval); _mu = mu; _cutOffStrike = cutOffStrike; } /** * Compute the present value of a CMS cap/floor by replication in SABR framework with * extrapolation on the right. * * @param cmsCapFloor The CMS cap/floor. * @param sabrData The SABR data bundle. * @return The present value. */ @Override public CurrencyAmount presentValue( final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) { Validate.notNull(cmsCapFloor); Validate.notNull(sabrData); final SABRInterestRateParameters sabrParameter = sabrData.getSABRParameter(); final SwapFixedCoupon<? extends Payment> underlyingSwap = cmsCapFloor.getUnderlyingSwap(); final double forward = underlyingSwap.accept(PRC, sabrData); final double discountFactorTp = sabrData .getCurve(underlyingSwap.getFixedLeg().getNthPayment(0).getFundingCurveName()) .getDiscountFactor(cmsCapFloor.getPaymentTime()); final double maturity = underlyingSwap .getFixedLeg() .getNthPayment(underlyingSwap.getFixedLeg().getNumberOfPayments() - 1) .getPaymentTime() - cmsCapFloor.getSettlementTime(); final DoublesPair expiryMaturity = new DoublesPair(cmsCapFloor.getFixingTime(), maturity); final double alpha = sabrParameter.getAlpha(expiryMaturity); final double beta = sabrParameter.getBeta(expiryMaturity); final double rho = sabrParameter.getRho(expiryMaturity); final double nu = sabrParameter.getNu(expiryMaturity); final SABRFormulaData sabrPoint = new SABRFormulaData(alpha, beta, rho, nu); final CMSIntegrant integrant = new CMSIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu); final double strike = cmsCapFloor.getStrike(); final double factor = discountFactorTp / integrant.h(forward) * integrant.g(forward); final double strikePart = factor * integrant.k(strike) * integrant.bs(strike); final double absoluteTolerance = 1.0 / (factor * Math.abs(cmsCapFloor.getNotional()) * cmsCapFloor.getPaymentYearFraction()); final double relativeTolerance = 1E-10; final RungeKuttaIntegrator1D integrator = new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration()); double integralPart; try { if (cmsCapFloor.isCap()) { integralPart = discountFactorTp * integrator.integrate(integrant, strike, strike + getIntegrationInterval()); } else { integralPart = discountFactorTp * integrator.integrate(integrant, 0.0, strike); } } catch (final Exception e) { throw new RuntimeException(e); } final double priceCMS = (strikePart + integralPart) * cmsCapFloor.getNotional() * cmsCapFloor.getPaymentYearFraction(); return CurrencyAmount.of(cmsCapFloor.getCurrency(), priceCMS); } @Override public CurrencyAmount presentValue( final InstrumentDerivative instrument, final YieldCurveBundle curves) { Validate.isTrue(instrument instanceof CapFloorCMS, "CMS cap/floor"); Validate.isTrue( curves instanceof SABRInterestRateDataBundle, "Bundle should contain SABR data"); return presentValue((CapFloorCMS) instrument, (SABRInterestRateDataBundle) curves); } /** * Computes the present value sensitivity to the yield curves of a CMS cap/floor by replication in * the SABR framework with extrapolation on the right. * * @param cmsCapFloor The CMS cap/floor. * @param sabrData The SABR data bundle. The SABR function need to be the Hagan function. * @return The present value sensitivity to curves. */ @Override public InterestRateCurveSensitivity presentValueCurveSensitivity( final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) { Validate.notNull(cmsCapFloor); Validate.notNull(sabrData); final SABRInterestRateParameters sabrParameter = sabrData.getSABRParameter(); final SwapFixedCoupon<? extends Payment> underlyingSwap = cmsCapFloor.getUnderlyingSwap(); final double forward = underlyingSwap.accept(PRC, sabrData); final double discountFactor = sabrData .getCurve(underlyingSwap.getFixedLeg().getNthPayment(0).getFundingCurveName()) .getDiscountFactor(cmsCapFloor.getPaymentTime()); final double strike = cmsCapFloor.getStrike(); final double maturity = underlyingSwap .getFixedLeg() .getNthPayment(underlyingSwap.getFixedLeg().getNumberOfPayments() - 1) .getPaymentTime() - cmsCapFloor.getSettlementTime(); final DoublesPair expiryMaturity = new DoublesPair(cmsCapFloor.getFixingTime(), maturity); final double alpha = sabrParameter.getAlpha(expiryMaturity); final double beta = sabrParameter.getBeta(expiryMaturity); final double rho = sabrParameter.getRho(expiryMaturity); final double nu = sabrParameter.getNu(expiryMaturity); final SABRFormulaData sabrPoint = new SABRFormulaData(alpha, beta, rho, nu); // Common final CMSIntegrant integrantPrice = new CMSIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu); final CMSDeltaIntegrant integrantDelta = new CMSDeltaIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu); final double factor = discountFactor / integrantDelta.h(forward) * integrantDelta.g(forward); final double absoluteTolerance = 1.0 / (factor * Math.abs(cmsCapFloor.getNotional()) * cmsCapFloor.getPaymentYearFraction()); final double relativeTolerance = 1E-10; final RungeKuttaIntegrator1D integrator = new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration()); // Price final double[] bs = integrantDelta.bsbsp(strike); @SuppressWarnings("synthetic-access") final double[] n = integrantDelta.nnp(forward); final double strikePartPrice = discountFactor * integrantDelta.k(strike) * n[0] * bs[0]; double integralPartPrice; try { if (cmsCapFloor.isCap()) { integralPartPrice = discountFactor * integrator.integrate(integrantPrice, strike, strike + getIntegrationInterval()); } else { integralPartPrice = discountFactor * integrator.integrate(integrantPrice, 0.0, strike); } } catch (final Exception e) { throw new RuntimeException(e); } final double price = (strikePartPrice + integralPartPrice) * cmsCapFloor.getNotional() * cmsCapFloor.getPaymentYearFraction(); // Delta final double strikePart = discountFactor * integrantDelta.k(strike) * (n[1] * bs[0] + n[0] * bs[1]); double integralPart; try { if (cmsCapFloor.isCap()) { integralPart = discountFactor * integrator.integrate(integrantDelta, strike, strike + getIntegrationInterval()); } else { integralPart = discountFactor * integrator.integrate(integrantDelta, 0.0, strike); } } catch (final Exception e) { throw new RuntimeException(e); } final double deltaS0 = (strikePart + integralPart) * cmsCapFloor.getNotional() * cmsCapFloor.getPaymentYearFraction(); final double deltaPD = price / discountFactor; final double sensiDF = -cmsCapFloor.getPaymentTime() * discountFactor * deltaPD; final List<DoublesPair> list = new ArrayList<>(); list.add(new DoublesPair(cmsCapFloor.getPaymentTime(), sensiDF)); final Map<String, List<DoublesPair>> resultMap = new HashMap<>(); resultMap.put( cmsCapFloor.getUnderlyingSwap().getFixedLeg().getNthPayment(0).getFundingCurveName(), list); InterestRateCurveSensitivity result = new InterestRateCurveSensitivity(resultMap); final InterestRateCurveSensitivity forwardDr = new InterestRateCurveSensitivity(cmsCapFloor.getUnderlyingSwap().accept(PRSC, sabrData)); result = result.plus(forwardDr.multipliedBy(deltaS0)); return result; } /** * Computes the present value sensitivity to the SABR parameters of a CMS cap/floor by replication * in SABR framework with extrapolation on the right. * * @param cmsCapFloor The CMS cap/floor. * @param sabrData The SABR data bundle. The SABR function need to be the Hagan function. * @return The present value sensitivity to SABR parameters. */ @Override public PresentValueSABRSensitivityDataBundle presentValueSABRSensitivity( final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) { final SABRInterestRateParameters sabrParameter = sabrData.getSABRParameter(); final SwapFixedCoupon<? extends Payment> underlyingSwap = cmsCapFloor.getUnderlyingSwap(); final double forward = underlyingSwap.accept(PRC, sabrData); final double discountFactorTp = sabrData .getCurve(underlyingSwap.getFixedLeg().getNthPayment(0).getFundingCurveName()) .getDiscountFactor(cmsCapFloor.getPaymentTime()); final double strike = cmsCapFloor.getStrike(); final double maturity = underlyingSwap .getFixedLeg() .getNthPayment(underlyingSwap.getFixedLeg().getNumberOfPayments() - 1) .getPaymentTime() - cmsCapFloor.getSettlementTime(); final DoublesPair expiryMaturity = new DoublesPair(cmsCapFloor.getFixingTime(), maturity); final double alpha = sabrParameter.getAlpha(expiryMaturity); final double beta = sabrParameter.getBeta(expiryMaturity); final double rho = sabrParameter.getRho(expiryMaturity); final double nu = sabrParameter.getNu(expiryMaturity); final SABRFormulaData sabrPoint = new SABRFormulaData(alpha, beta, rho, nu); final CMSVegaIntegrant integrantVega = new CMSVegaIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu); final double factor = discountFactorTp / integrantVega.h(forward) * integrantVega.g(forward); final SABRExtrapolationRightFunction sabrExtrapolation = new SABRExtrapolationRightFunction( forward, sabrPoint, _cutOffStrike, cmsCapFloor.getFixingTime(), _mu); final EuropeanVanillaOption option = new EuropeanVanillaOption(strike, cmsCapFloor.getFixingTime(), cmsCapFloor.isCap()); final double factor2 = factor * integrantVega.k(strike); final double[] strikePartPrice = new double[4]; sabrExtrapolation.priceAdjointSABR(option, strikePartPrice); for (int loopvega = 0; loopvega < 4; loopvega++) { strikePartPrice[loopvega] *= factor2; } final double absoluteTolerance = 1.0 / (factor * Math.abs(cmsCapFloor.getNotional()) * cmsCapFloor.getPaymentYearFraction()); final double relativeTolerance = 1E-3; final RungeKuttaIntegrator1D integrator = new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration()); final double[] integralPart = new double[4]; final double[] totalSensi = new double[4]; for (int loopparameter = 0; loopparameter < 4; loopparameter++) { integrantVega.setParameterIndex(loopparameter); try { if (cmsCapFloor.isCap()) { integralPart[loopparameter] = discountFactorTp * integrator.integrate(integrantVega, strike, strike + getIntegrationInterval()); } else { integralPart[loopparameter] = discountFactorTp * integrator.integrate(integrantVega, 0.0, strike); } } catch (final Exception e) { throw new RuntimeException(e); } totalSensi[loopparameter] = (strikePartPrice[loopparameter] + integralPart[loopparameter]) * cmsCapFloor.getNotional() * cmsCapFloor.getPaymentYearFraction(); } final PresentValueSABRSensitivityDataBundle sensi = new PresentValueSABRSensitivityDataBundle(); sensi.addAlpha(expiryMaturity, totalSensi[0]); sensi.addBeta(expiryMaturity, totalSensi[1]); sensi.addRho(expiryMaturity, totalSensi[2]); sensi.addNu(expiryMaturity, totalSensi[3]); return sensi; } /** * Computes the present value sensitivity to the strike of a CMS cap/floor by replication in SABR * framework with extrapolation on the right. * * @param cmsCapFloor The CMS cap/floor. * @param sabrData The SABR data bundle. The SABR function need to be the Hagan function. * @return The present value sensitivity to strike. */ @Override public double presentValueStrikeSensitivity( final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) { final SABRInterestRateParameters sabrParameter = sabrData.getSABRParameter(); final SwapFixedCoupon<? extends Payment> underlyingSwap = cmsCapFloor.getUnderlyingSwap(); final double forward = underlyingSwap.accept(PRC, sabrData); final double discountFactor = sabrData .getCurve(underlyingSwap.getFixedLeg().getNthPayment(0).getFundingCurveName()) .getDiscountFactor(cmsCapFloor.getPaymentTime()); final double strike = cmsCapFloor.getStrike(); final double maturity = underlyingSwap .getFixedLeg() .getNthPayment(underlyingSwap.getFixedLeg().getNumberOfPayments() - 1) .getPaymentTime() - cmsCapFloor.getSettlementTime(); final DoublesPair expiryMaturity = new DoublesPair(cmsCapFloor.getFixingTime(), maturity); final double alpha = sabrParameter.getAlpha(expiryMaturity); final double beta = sabrParameter.getBeta(expiryMaturity); final double rho = sabrParameter.getRho(expiryMaturity); final double nu = sabrParameter.getNu(expiryMaturity); final SABRFormulaData sabrPoint = new SABRFormulaData(alpha, beta, rho, nu); final CMSStrikeIntegrant integrant = new CMSStrikeIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu); final double factor = discountFactor * integrant.g(forward) / integrant.h(forward); final double absoluteTolerance = 1.0E-9; final double relativeTolerance = 1.0E-5; final RungeKuttaIntegrator1D integrator = new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration()); final SABRExtrapolationRightFunction sabrExtrapolation = new SABRExtrapolationRightFunction( forward, sabrPoint, _cutOffStrike, cmsCapFloor.getFixingTime(), _mu); final EuropeanVanillaOption option = new EuropeanVanillaOption(strike, cmsCapFloor.getFixingTime(), cmsCapFloor.isCap()); final double[] kpkpp = integrant.kpkpp(strike); double firstPart; double thirdPart; if (cmsCapFloor.isCap()) { firstPart = -kpkpp[0] * integrant.bs(strike); thirdPart = integrator.integrate(integrant, strike, strike + getIntegrationInterval()); } else { firstPart = 3 * kpkpp[0] * integrant.bs(strike); thirdPart = integrator.integrate(integrant, 0.0, strike); } final double secondPart = integrant.k(strike) * sabrExtrapolation.priceDerivativeStrike(option); return cmsCapFloor.getNotional() * cmsCapFloor.getPaymentYearFraction() * factor * (firstPart + secondPart + thirdPart); } /** Inner class to implement the integration used in price replication. */ private class CMSIntegrant extends Function1D<Double, Double> { protected static final double EPS = 1E-10; private final int _nbFixedPeriod; private final int _nbFixedPaymentYear; private final double _tau; private final double _delta; private final double _eta; private final double _timeToExpiry; private final double _strike; private final double _forward; private final double _factor; private final SABRExtrapolationRightFunction _sabrExtrapolation; private final boolean _isCall; /** * Gets the _nbFixedPeriod field. * * @return the _nbFixedPeriod */ public int getNbFixedPeriod() { return _nbFixedPeriod; } /** * Gets the _nbFixedPaymentYear field. * * @return the _nbFixedPaymentYear */ public int getNbFixedPaymentYear() { return _nbFixedPaymentYear; } /** * Gets the _tau field. * * @return the _tau */ public double getTau() { return _tau; } /** * Gets the _eta field. * * @return the _eta */ public double getEta() { return _eta; } /** * Gets the _timeToExpiry field. * * @return the _timeToExpiry */ public double getTimeToExpiry() { return _timeToExpiry; } /** * Gets the _sabrExtrapolation field. * * @return the _sabrExtrapolation */ public SABRExtrapolationRightFunction getSabrExtrapolation() { return _sabrExtrapolation; } /** * Gets the _isCall field. * * @return the _isCall */ public boolean isCall() { return _isCall; } /** * Gets the _strike field. * * @return the _strike */ public double getStrike() { return _strike; } /** * Constructor. * * @param cmsCap The CMS cap/floor. * @param sabrParameter The SABR parameters. * @param forward The forward. * @param cutOffStrike The cut-off strike. * @param mu The tail thickness parameter. */ public CMSIntegrant( final CapFloorCMS cmsCap, final SABRFormulaData sabrPoint, final double forward, final double cutOffStrike, final double mu) { _nbFixedPeriod = cmsCap.getUnderlyingSwap().getFixedLeg().getPayments().length; _nbFixedPaymentYear = (int) Math.round( 1.0 / cmsCap .getUnderlyingSwap() .getFixedLeg() .getNthPayment(0) .getPaymentYearFraction()); _tau = 1.0 / _nbFixedPaymentYear; _delta = cmsCap.getPaymentTime() - cmsCap.getSettlementTime(); _eta = -_delta; _timeToExpiry = cmsCap.getFixingTime(); _forward = forward; _sabrExtrapolation = new SABRExtrapolationRightFunction(forward, sabrPoint, cutOffStrike, _timeToExpiry, mu); _isCall = cmsCap.isCap(); _strike = cmsCap.getStrike(); _factor = g(_forward) / h(_forward); } @Override public Double evaluate(final Double x) { final double[] kD = kpkpp(x); // Implementation note: kD[0] contains the first derivative of k; kD[1] the second derivative // of k. return _factor * (kD[1] * (x - _strike) + 2.0 * kD[0]) * bs(x); } /** * The approximation of the discount factor as function of the swap rate. * * @param x The swap rate. * @return The discount factor. */ double h(final double x) { return Math.pow(1.0 + _tau * x, _eta); } /** * The cash annuity. * * @param x The swap rate. * @return The annuity. */ double g(final double x) { if (x >= EPS) { final double periodFactor = 1 + x / _nbFixedPaymentYear; final double nPeriodDiscount = Math.pow(periodFactor, -_nbFixedPeriod); return 1.0 / x * (1.0 - nPeriodDiscount); } return ((double) _nbFixedPeriod) / _nbFixedPaymentYear; } /** * The factor used in the strike part and in the integration of the replication. * * @param x The swap rate. * @return The factor. */ double k(final double x) { double g; double h; if (x >= EPS) { final double periodFactor = 1 + x / _nbFixedPaymentYear; final double nPeriodDiscount = Math.pow(periodFactor, -_nbFixedPeriod); g = 1.0 / x * (1.0 - nPeriodDiscount); h = Math.pow(1.0 + _tau * x, _eta); } else { g = ((double) _nbFixedPeriod) / _nbFixedPaymentYear; h = 1.0; } return h / g; } /** * The first and second derivative of the function k. * * @param x The swap rate. * @return The derivative (first element is the first derivative, second element is second * derivative. */ protected double[] kpkpp(final double x) { final double periodFactor = 1 + x / _nbFixedPaymentYear; final double nPeriodDiscount = Math.pow(periodFactor, -_nbFixedPeriod); /** The value of the annuity and its first and second derivative. */ double g, gp, gpp; if (x >= EPS) { g = 1.0 / x * (1.0 - nPeriodDiscount); gp = -g / x + _nbFixedPeriod / x / _nbFixedPaymentYear * nPeriodDiscount / periodFactor; gpp = 2.0 / (x * x) * g - 2.0 * _nbFixedPeriod / (x * x) / _nbFixedPaymentYear * nPeriodDiscount / periodFactor - (_nbFixedPeriod + 1.0) * _nbFixedPeriod / x / (_nbFixedPaymentYear * _nbFixedPaymentYear) * nPeriodDiscount / (periodFactor * periodFactor); } else { // Implementation comment: When x is (almost) 0, useful for CMS swaps which are priced as // CMS cap of strike 0. g = ((double) _nbFixedPeriod) / _nbFixedPaymentYear; gp = -_nbFixedPeriod / 2.0 * (_nbFixedPeriod + 1.0) / (_nbFixedPaymentYear * _nbFixedPaymentYear); gpp = _nbFixedPeriod / 2.0 * (_nbFixedPeriod + 1.0) * (1.0 + (_nbFixedPeriod + 2.0) / 3.0) / (_nbFixedPaymentYear * _nbFixedPaymentYear * _nbFixedPaymentYear); } final double h = Math.pow(1.0 + _tau * x, _eta); final double hp = _eta * _tau * h / periodFactor; final double hpp = (_eta - 1.0) * _tau * hp / periodFactor; final double kp = hp / g - h * gp / (g * g); final double kpp = hpp / g - 2 * hp * gp / (g * g) - h * (gpp / (g * g) - 2 * (gp * gp) / (g * g * g)); return new double[] {kp, kpp}; } /** * The Black-Scholes formula with numeraire 1 as function of the strike. * * @param strike The strike. * @return The Black-Scholes formula. */ double bs(final double strike) { final EuropeanVanillaOption option = new EuropeanVanillaOption(strike, _timeToExpiry, _isCall); return _sabrExtrapolation.price(option); } } private class CMSDeltaIntegrant extends CMSIntegrant { private final double[] _nnp; public CMSDeltaIntegrant( final CapFloorCMS cmsCap, final SABRFormulaData sabrPoint, final double forward, final double cutOffStrike, final double mu) { super(cmsCap, sabrPoint, forward, cutOffStrike, mu); _nnp = nnp(forward); } @Override public Double evaluate(final Double x) { final double[] kD = kpkpp(x); // Implementation note: kD[0] contains the first derivative of k; kD[1] the second derivative // of k. final double[] bs = bsbsp(x); return (kD[1] * (x - getStrike()) + 2.0 * kD[0]) * (_nnp[1] * bs[0] + _nnp[0] * bs[1]); } /** * The Black-Scholes formula and its derivative with respect to the forward. * * @param strike The strike. * @return The Black-Scholes formula and its derivative. */ double[] bsbsp(final double strike) { final double[] result = new double[2]; final EuropeanVanillaOption option = new EuropeanVanillaOption(strike, getTimeToExpiry(), isCall()); result[0] = getSabrExtrapolation().price(option); result[1] = getSabrExtrapolation().priceDerivativeForward(option); return result; } private double[] nnp(final double x) { final double[] result = new double[2]; final double[] ggp = ggp(x); final double[] hhp = hhp(x); result[0] = ggp[0] / hhp[0]; result[1] = ggp[1] / hhp[0] - ggp[0] * hhp[1] / (hhp[0] * hhp[0]); return result; } private double[] ggp(final double x) { final double[] result = new double[2]; if (x >= EPS) { final double periodFactor = 1 + x / getNbFixedPaymentYear(); final double nPeriodDiscount = Math.pow(periodFactor, -getNbFixedPeriod()); result[0] = 1.0 / x * (1.0 - nPeriodDiscount); result[1] = -result[0] / x + getTau() * getNbFixedPeriod() / x * nPeriodDiscount / periodFactor; } else { result[0] = getNbFixedPeriod() * getTau(); result[1] = -getNbFixedPeriod() * (getNbFixedPeriod() + 1.0) * getTau() * getTau() / 2.0; } return result; } private double[] hhp(final double x) { final double[] result = new double[2]; result[0] = Math.pow(1.0 + getTau() * x, getEta()); result[1] = getEta() * getTau() * result[0] / (1 + x * getTau()); return result; } } private class CMSVegaIntegrant extends CMSIntegrant { /** The index of the sensitivity computed. */ private int _parameterIndex; /** * Constructor. * * @param cmsCap The CMS cap/floor. * @param sabrParameter The SABR parameters. * @param forward The forward. * @param cutOffStrike The cut-off strike. * @param mu The tail thickness parameter. */ public CMSVegaIntegrant( final CapFloorCMS cmsCap, final SABRFormulaData sabrPoint, final double forward, final double cutOffStrike, final double mu) { super(cmsCap, sabrPoint, forward, cutOffStrike, mu); } /** * Sets the index of the sensitivity computed. * * @param parameterIndex The index. */ public void setParameterIndex(final int parameterIndex) { this._parameterIndex = parameterIndex; } @SuppressWarnings("synthetic-access") @Override public Double evaluate(final Double x) { final double[] kD = super.kpkpp(x); // Implementation note: kD[0] contains the first derivative of k; kD[1] the second derivative // of k. final EuropeanVanillaOption option = new EuropeanVanillaOption(x, super._timeToExpiry, super._isCall); final double[] priceDerivativeSABR = new double[4]; getSabrExtrapolation().priceAdjointSABR(option, priceDerivativeSABR); return super._factor * (kD[1] * (x - super._strike) + 2.0 * kD[0]) * priceDerivativeSABR[_parameterIndex]; } } private class CMSStrikeIntegrant extends CMSIntegrant { /** * @param cmsCap * @param sabrParameter * @param forward */ public CMSStrikeIntegrant( final CapFloorCMS cmsCap, final SABRFormulaData sabrPoint, final double forward, final double cutOffStrike, final double mu) { super(cmsCap, sabrPoint, forward, cutOffStrike, mu); } @Override public Double evaluate(final Double x) { final double[] kD = super.kpkpp(x); // Implementation note: kD[0] contains the first derivative of k; kD[1] the second derivative // of k. return -kD[1] * bs(x); } } // End CMSStrikeIntegrant }
/** * Test related to the pricing and sensitivity of the Ibor cap/floor with the SABR model and * extrapolation for high strikes. * * @deprecated This class tests deprecated functionality. */ @Deprecated public class CapFloorIborSABRExtrapolationRightMethodTest { // Details private static final Period TENOR = Period.ofMonths(3); private static final int SETTLEMENT_DAYS = 2; private static final Calendar CALENDAR = new MondayToFridayCalendar("A"); private static final DayCount DAY_COUNT_INDEX = DayCountFactory.INSTANCE.getDayCount("Actual/360"); private static final BusinessDayConvention BUSINESS_DAY = BusinessDayConventionFactory.INSTANCE.getBusinessDayConvention("Modified Following"); private static final boolean IS_EOM = true; private static final Currency CUR = Currency.EUR; private static final IborIndex INDEX = new IborIndex(CUR, TENOR, SETTLEMENT_DAYS, DAY_COUNT_INDEX, BUSINESS_DAY, IS_EOM, "Ibor"); private static final ZonedDateTime FIXING_DATE = DateUtils.getUTCDate(2011, 1, 3); private static final double NOTIONAL = 1000000; // 1m private static final double STRIKE = 0.04; private static final double STRIKE_HIGH = 0.09; private static final boolean IS_CAP = true; // Definition description private static final CapFloorIborDefinition CAP_LONG_DEFINITION = CapFloorIborDefinition.from(NOTIONAL, FIXING_DATE, INDEX, STRIKE, IS_CAP, CALENDAR); private static final CapFloorIborDefinition CAP_HIGH_LONG_DEFINITION = CapFloorIborDefinition.from(NOTIONAL, FIXING_DATE, INDEX, STRIKE_HIGH, IS_CAP, CALENDAR); private static final CouponIborDefinition COUPON_IBOR_DEFINITION = CouponIborDefinition.from(NOTIONAL, FIXING_DATE, INDEX, CALENDAR); private static final CouponFixedDefinition COUPON_STRIKE_DEFINITION = new CouponFixedDefinition(COUPON_IBOR_DEFINITION, STRIKE); private static final CouponFixedDefinition COUPON_STRIKE_HIGH_DEFINITION = new CouponFixedDefinition(COUPON_IBOR_DEFINITION, STRIKE_HIGH); private static final CapFloorIborDefinition CAP_SHORT_DEFINITION = CapFloorIborDefinition.from(-NOTIONAL, FIXING_DATE, INDEX, STRIKE, IS_CAP, CALENDAR); private static final CapFloorIborDefinition CAP_HIGH_SHORT_DEFINITION = CapFloorIborDefinition.from(-NOTIONAL, FIXING_DATE, INDEX, STRIKE_HIGH, IS_CAP, CALENDAR); private static final CapFloorIborDefinition FLOOR_SHORT_DEFINITION = CapFloorIborDefinition.from(-NOTIONAL, FIXING_DATE, INDEX, STRIKE, !IS_CAP, CALENDAR); private static final CapFloorIborDefinition FLOOR_HIGH_SHORT_DEFINITION = CapFloorIborDefinition.from(-NOTIONAL, FIXING_DATE, INDEX, STRIKE_HIGH, !IS_CAP, CALENDAR); // Methods and calculator private static final double CUT_OFF_STRIKE = 0.08; private static final double MU = 2.50; private static final CapFloorIborSABRExtrapolationRightMethod METHOD = new CapFloorIborSABRExtrapolationRightMethod(CUT_OFF_STRIKE, MU); private static final ParRateCalculator PRC = ParRateCalculator.getInstance(); private static final PresentValueCalculator PVC = PresentValueCalculator.getInstance(); private static final PresentValueCurveSensitivitySABRExtrapolationCalculator PVSC = new PresentValueCurveSensitivitySABRExtrapolationCalculator(CUT_OFF_STRIKE, MU); // To derivative private static final ZonedDateTime REFERENCE_DATE = DateUtils.getUTCDate(2008, 8, 18); private static final String FUNDING_CURVE_NAME = "Funding"; private static final String FORWARD_CURVE_NAME = "Forward"; private static final String[] CURVES_NAME = {FUNDING_CURVE_NAME, FORWARD_CURVE_NAME}; private static final CapFloorIbor CAP_LONG = (CapFloorIbor) CAP_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME); private static final CapFloorIbor CAP_HIGH_LONG = (CapFloorIbor) CAP_HIGH_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME); private static final CouponIbor COUPON_IBOR = (CouponIbor) COUPON_IBOR_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME); private static final CouponFixed COUPON_STRIKE = COUPON_STRIKE_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME); private static final CouponFixed COUPON_STRIKE_HIGH = COUPON_STRIKE_HIGH_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME); private static final CapFloorIbor CAP_SHORT = (CapFloorIbor) CAP_SHORT_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME); private static final CapFloorIbor CAP_HIGH_SHORT = (CapFloorIbor) CAP_HIGH_SHORT_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME); private static final CapFloorIbor FLOOR_SHORT = (CapFloorIbor) FLOOR_SHORT_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME); private static final CapFloorIbor FLOOR_HIGH_SHORT = (CapFloorIbor) FLOOR_HIGH_SHORT_DEFINITION.toDerivative(REFERENCE_DATE, CURVES_NAME); // Data private static final YieldCurveBundle CURVES = TestsDataSetsSABR.createCurves1(); private static final SABRInterestRateParameters SABR_PARAMETERS = TestsDataSetsSABR.createSABR1(); private static final SABRInterestRateDataBundle SABR_BUNDLE = new SABRInterestRateDataBundle(SABR_PARAMETERS, CURVES); @Test /** Test the present value using the method with the direct formula with extrapolation. */ public void presentValueBelowCutOff() { final CurrencyAmount methodPrice = METHOD.presentValue(CAP_LONG, SABR_BUNDLE); final double df = CURVES.getCurve(FUNDING_CURVE_NAME).getDiscountFactor(CAP_LONG.getPaymentTime()); final double forward = CAP_LONG.accept(PRC, CURVES); final double maturity = CAP_LONG.getFixingPeriodEndTime() - CAP_LONG.getFixingPeriodStartTime(); final DoublesPair expiryMaturity = new DoublesPair(CAP_LONG.getFixingTime(), maturity); final double alpha = SABR_PARAMETERS.getAlpha(expiryMaturity); final double beta = SABR_PARAMETERS.getBeta(expiryMaturity); final double rho = SABR_PARAMETERS.getRho(expiryMaturity); final double nu = SABR_PARAMETERS.getNu(expiryMaturity); final SABRFormulaData sabrParam = new SABRFormulaData(alpha, beta, rho, nu); final SABRExtrapolationRightFunction sabrExtrapolation = new SABRExtrapolationRightFunction( forward, sabrParam, CUT_OFF_STRIKE, CAP_LONG.getFixingTime(), MU); final EuropeanVanillaOption option = new EuropeanVanillaOption(CAP_LONG.getStrike(), CAP_LONG.getFixingTime(), CAP_LONG.isCap()); final double expectedPrice = sabrExtrapolation.price(option) * CAP_LONG.getNotional() * CAP_LONG.getPaymentYearFraction() * df; assertEquals( "Cap/floor: SABR with extrapolation pricing", expectedPrice, methodPrice.getAmount(), 1E-2); } @Test /** Test the present value using the method with the direct formula with extrapolation. */ public void presentValueAboveCutOff() { CurrencyAmount methodPrice = METHOD.presentValue(CAP_HIGH_LONG, SABR_BUNDLE); final double df = CURVES.getCurve(FUNDING_CURVE_NAME).getDiscountFactor(CAP_HIGH_LONG.getPaymentTime()); final double forward = CAP_HIGH_LONG.accept(PRC, CURVES); final double maturity = CAP_HIGH_LONG.getFixingPeriodEndTime() - CAP_LONG.getFixingPeriodStartTime(); final DoublesPair expiryMaturity = new DoublesPair(CAP_HIGH_LONG.getFixingTime(), maturity); final double alpha = SABR_PARAMETERS.getAlpha(expiryMaturity); final double beta = SABR_PARAMETERS.getBeta(expiryMaturity); final double rho = SABR_PARAMETERS.getRho(expiryMaturity); final double nu = SABR_PARAMETERS.getNu(expiryMaturity); final SABRFormulaData sabrParam = new SABRFormulaData(alpha, beta, rho, nu); final SABRExtrapolationRightFunction sabrExtrapolation = new SABRExtrapolationRightFunction( forward, sabrParam, CUT_OFF_STRIKE, CAP_HIGH_LONG.getFixingTime(), MU); final EuropeanVanillaOption option = new EuropeanVanillaOption( CAP_HIGH_LONG.getStrike(), CAP_HIGH_LONG.getFixingTime(), CAP_HIGH_LONG.isCap()); final double expectedPrice = sabrExtrapolation.price(option) * CAP_HIGH_LONG.getNotional() * CAP_HIGH_LONG.getPaymentYearFraction() * df; assertEquals( "Cap/floor: SABR with extrapolation pricing", expectedPrice, methodPrice.getAmount(), 1E-2); methodPrice = METHOD.presentValue(CAP_HIGH_LONG, SABR_BUNDLE); assertEquals( "Cap/floor: SABR with extrapolation pricing", expectedPrice, methodPrice.getAmount(), 1E-2); } @Test /** Test the present value using the method with the direct formula with extrapolation. */ public void presentValueLongShortParityBelowCutOff() { final CurrencyAmount priceLong = METHOD.presentValue(CAP_LONG, SABR_BUNDLE); final CurrencyAmount priceShort = METHOD.presentValue(CAP_SHORT, SABR_BUNDLE); assertEquals( "Cap/floor: SABR with extrapolation pricing: long/short parity", priceLong.getAmount(), -priceShort.getAmount(), 1E-2); } @Test /** Test the present value using the method with the direct formula with extrapolation. */ public void presentValueLongShortParityAboveCutOff() { final CurrencyAmount priceLong = METHOD.presentValue(CAP_HIGH_LONG, SABR_BUNDLE); final CurrencyAmount priceShort = METHOD.presentValue(CAP_HIGH_SHORT, SABR_BUNDLE); assertEquals( "Cap/floor: SABR with extrapolation pricing: long/short parity", priceLong.getAmount(), -priceShort.getAmount(), 1E-2); } @Test /** Test the cap/floor/forward parity below the cut-off strike. */ public void presentValueCapFloorParityBelowCutOff() { final CurrencyAmount priceCap = METHOD.presentValue(CAP_LONG, SABR_BUNDLE); final CurrencyAmount priceFloor = METHOD.presentValue(FLOOR_SHORT, SABR_BUNDLE); final double priceCouponStrike = COUPON_STRIKE.accept(PVC, CURVES); final double priceCouponIbor = COUPON_IBOR.accept(PVC, CURVES); assertEquals( "Cap/floor: SABR with extrapolation pricing: cap/floor parity", priceCouponIbor - priceCouponStrike, priceCap.getAmount() + priceFloor.getAmount(), 1E-2); } @Test /** Test the cap/floor/forward parity above the cut-off strike. */ public void presentValueCapFloorParityAboveCutOff() { final CurrencyAmount priceCap = METHOD.presentValue(CAP_HIGH_LONG, SABR_BUNDLE); final CurrencyAmount priceFloor = METHOD.presentValue(FLOOR_HIGH_SHORT, SABR_BUNDLE); final double priceCouponStrike = COUPON_STRIKE_HIGH.accept(PVC, CURVES); final double priceCouponIbor = COUPON_IBOR.accept(PVC, CURVES); assertEquals( "Cap/floor: SABR with extrapolation pricing: cap/floor parity", priceCouponIbor - priceCouponStrike, priceCap.getAmount() + priceFloor.getAmount(), 1E-2); } @Test /** Test the present value using the method with the direct formula with extrapolation. */ public void presentValueMethodVsCalculator() { final SABRInterestRateDataBundle sabrExtraBundle = new SABRInterestRateDataBundle(SABR_PARAMETERS, CURVES); final CurrencyAmount pvMethod = METHOD.presentValue(CAP_LONG, SABR_BUNDLE); final PresentValueSABRExtrapolationCalculator pvc = new PresentValueSABRExtrapolationCalculator(CUT_OFF_STRIKE, MU); final double pvCalculator = CAP_LONG.accept(pvc, sabrExtraBundle); assertEquals( "Cap/floor: SABR with extrapolation pricing - Method vs Calculator", pvMethod.getAmount(), pvCalculator, 1E-2); } @Test /** * Test the present value rate sensitivity against a finite difference computation; strike below * the cut-off strike. Test sensitivity long/short parity. */ public void testPresentValueSensitivityBelowCutOff() { final YieldCurveBundle curves = TestsDataSetsSABR.createCurves1(); final SABRInterestRateParameters sabrParameter = TestsDataSetsSABR.createSABR1(); final SABRInterestRateDataBundle sabrBundle = new SABRInterestRateDataBundle(sabrParameter, curves); InterestRateCurveSensitivity pvsCapLong = METHOD.presentValueSensitivity(CAP_LONG, sabrBundle); final InterestRateCurveSensitivity pvsCapShort = METHOD.presentValueSensitivity(CAP_SHORT, sabrBundle); // Long/short parity final InterestRateCurveSensitivity pvsCapShort_1 = pvsCapShort.multipliedBy(-1); assertEquals(pvsCapLong.getSensitivities(), pvsCapShort_1.getSensitivities()); // Present value sensitivity comparison with finite difference. final double deltaTolerancePrice = 1.0E-1; // Testing note: Sensitivity is for a movement of 1. 1E+2 = 1 cent for a 1 bp move. final double deltaShift = 1.0E-7; pvsCapLong = pvsCapLong.cleaned(); final String bumpedCurveName = "Bumped Curve"; // 1. Forward curve sensitivity final String[] CurveNameBumpedForward = {FUNDING_CURVE_NAME, bumpedCurveName}; final CapFloorIbor capBumpedForward = (CapFloorIbor) CAP_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CurveNameBumpedForward); final double[] nodeTimesForward = new double[] { capBumpedForward.getFixingPeriodStartTime(), capBumpedForward.getFixingPeriodEndTime() }; final double[] sensiForwardMethod = SensitivityFiniteDifference.curveSensitivity( capBumpedForward, SABR_BUNDLE, FORWARD_CURVE_NAME, bumpedCurveName, nodeTimesForward, deltaShift, METHOD); assertEquals( "Sensitivity finite difference method: number of node", 2, sensiForwardMethod.length); final List<DoublesPair> sensiPvForward = pvsCapLong.getSensitivities().get(FORWARD_CURVE_NAME); for (int loopnode = 0; loopnode < sensiForwardMethod.length; loopnode++) { final DoublesPair pairPv = sensiPvForward.get(loopnode); assertEquals( "Sensitivity cap/floor pv to forward curve: Node " + loopnode, nodeTimesForward[loopnode], pairPv.getFirst(), 1E-8); assertEquals( "Sensitivity finite difference method: node sensitivity", pairPv.second, sensiForwardMethod[loopnode], deltaTolerancePrice); } // 2. Discounting curve sensitivity final String[] CurveNameBumpedDisc = {bumpedCurveName, FORWARD_CURVE_NAME}; final CapFloorIbor capBumpedDisc = (CapFloorIbor) CAP_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CurveNameBumpedDisc); final double[] nodeTimesDisc = new double[] {capBumpedDisc.getPaymentTime()}; final double[] sensiDiscMethod = SensitivityFiniteDifference.curveSensitivity( capBumpedDisc, SABR_BUNDLE, FUNDING_CURVE_NAME, bumpedCurveName, nodeTimesDisc, deltaShift, METHOD); assertEquals("Sensitivity finite difference method: number of node", 1, sensiDiscMethod.length); final List<DoublesPair> sensiPvDisc = pvsCapLong.getSensitivities().get(FUNDING_CURVE_NAME); for (int loopnode = 0; loopnode < sensiDiscMethod.length; loopnode++) { final DoublesPair pairPv = sensiPvDisc.get(loopnode); assertEquals( "Sensitivity cap/floor pv to forward curve: Node " + loopnode, nodeTimesDisc[loopnode], pairPv.getFirst(), 1E-8); assertEquals( "Sensitivity finite difference method: node sensitivity", pairPv.second, sensiDiscMethod[loopnode], deltaTolerancePrice); } } @Test /** * Test the present value rate sensitivity against a finite difference computation; strike above * the cut-off strike. Test sensitivity long/short parity. */ public void testPresentValueSensitivityAboveCutOff() { final YieldCurveBundle curves = TestsDataSetsSABR.createCurves1(); final SABRInterestRateParameters sabrParameter = TestsDataSetsSABR.createSABR1(); final SABRInterestRateDataBundle sabrBundle = new SABRInterestRateDataBundle(sabrParameter, curves); InterestRateCurveSensitivity pvsCapLong = METHOD.presentValueSensitivity(CAP_HIGH_LONG, sabrBundle); final InterestRateCurveSensitivity pvsCapShort = METHOD.presentValueSensitivity(CAP_HIGH_SHORT, sabrBundle); // Long/short parity final InterestRateCurveSensitivity pvsCapShort_1 = pvsCapShort.multipliedBy(-1); assertEquals(pvsCapLong.getSensitivities(), pvsCapShort_1.getSensitivities()); // Present value sensitivity comparison with finite difference. final double deltaTolerancePrice = 1.0E-1; // Testing note: Sensitivity is for a movement of 1. 1E+2 = 1 cent for a 1 bp move. final double deltaShift = 1.0E-7; pvsCapLong = pvsCapLong.cleaned(); final String bumpedCurveName = "Bumped Curve"; // 1. Forward curve sensitivity final String[] CurveNameBumpedForward = {FUNDING_CURVE_NAME, bumpedCurveName}; final CapFloorIbor capBumpedForward = (CapFloorIbor) CAP_HIGH_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CurveNameBumpedForward); final double[] nodeTimesForward = new double[] { capBumpedForward.getFixingPeriodStartTime(), capBumpedForward.getFixingPeriodEndTime() }; final double[] sensiForwardMethod = SensitivityFiniteDifference.curveSensitivity( capBumpedForward, SABR_BUNDLE, FORWARD_CURVE_NAME, bumpedCurveName, nodeTimesForward, deltaShift, METHOD); assertEquals( "Sensitivity finite difference method: number of node", 2, sensiForwardMethod.length); final List<DoublesPair> sensiPvForward = pvsCapLong.getSensitivities().get(FORWARD_CURVE_NAME); for (int loopnode = 0; loopnode < sensiForwardMethod.length; loopnode++) { final DoublesPair pairPv = sensiPvForward.get(loopnode); assertEquals( "Sensitivity cap/floor pv to forward curve: Node " + loopnode, nodeTimesForward[loopnode], pairPv.getFirst(), 1E-8); // assertEquals("Sensitivity finite difference method: node sensitivity: Node " + // loopnode, pairPv.second, sensiForwardMethod[loopnode], deltaTolerancePrice); } // 2. Discounting curve sensitivity final String[] CurveNameBumpedDisc = {bumpedCurveName, FORWARD_CURVE_NAME}; final CapFloorIbor capBumpedDisc = (CapFloorIbor) CAP_HIGH_LONG_DEFINITION.toDerivative(REFERENCE_DATE, CurveNameBumpedDisc); final double[] nodeTimesDisc = new double[] {capBumpedDisc.getPaymentTime()}; final double[] sensiDiscMethod = SensitivityFiniteDifference.curveSensitivity( capBumpedDisc, SABR_BUNDLE, FUNDING_CURVE_NAME, bumpedCurveName, nodeTimesDisc, deltaShift, METHOD); assertEquals("Sensitivity finite difference method: number of node", 1, sensiDiscMethod.length); final List<DoublesPair> sensiPvDisc = pvsCapLong.getSensitivities().get(FUNDING_CURVE_NAME); for (int loopnode = 0; loopnode < sensiDiscMethod.length; loopnode++) { final DoublesPair pairPv = sensiPvDisc.get(loopnode); assertEquals( "Sensitivity cap/floor pv to forward curve: Node " + loopnode, nodeTimesDisc[loopnode], pairPv.getFirst(), 1E-8); assertEquals( "Sensitivity finite difference method: node sensitivity", pairPv.second, sensiDiscMethod[loopnode], deltaTolerancePrice); } } @Test /** Test the present value using the method with the direct formula with extrapolation. */ public void presentValueCurveSensitivityMethodVsCalculator() { final SABRInterestRateDataBundle sabrExtraBundle = new SABRInterestRateDataBundle(SABR_PARAMETERS, CURVES); final InterestRateCurveSensitivity pvsMethod = METHOD.presentValueSensitivity(CAP_HIGH_LONG, SABR_BUNDLE); final InterestRateCurveSensitivity pvsCalculator = new InterestRateCurveSensitivity(CAP_HIGH_LONG.accept(PVSC, sabrExtraBundle)); assertEquals( "Cap/floor: SABR with extrapolation pv curve sensitivity - Method vs Calculator", pvsMethod, pvsCalculator); } @Test /** * Test the present value SABR parameters sensitivity against a finite difference computation; * strike below the cut-off strike. */ public void testPresentValueSABRSensitivityBelowCutOff() { final YieldCurveBundle curves = TestsDataSetsSABR.createCurves1(); final SABRInterestRateParameters sabrParameter = TestsDataSetsSABR.createSABR1(); final SABRInterestRateDataBundle sabrBundle = new SABRInterestRateDataBundle(sabrParameter, curves); final CurrencyAmount pv = METHOD.presentValue(CAP_LONG, sabrBundle); final PresentValueSABRSensitivityDataBundle pvsCapLong = METHOD.presentValueSABRSensitivity(CAP_LONG, sabrBundle); PresentValueSABRSensitivityDataBundle pvsCapShort = METHOD.presentValueSABRSensitivity(CAP_SHORT, sabrBundle); // Long/short parity pvsCapShort = pvsCapShort.multiplyBy(-1.0); assertEquals(pvsCapShort.getAlpha(), pvsCapLong.getAlpha()); // SABR sensitivity vs finite difference final double shift = 0.0001; final double shiftAlpha = 0.00001; final DoublesPair expectedExpiryTenor = new DoublesPair( CAP_LONG.getFixingTime(), CAP_LONG.getFixingPeriodEndTime() - CAP_LONG.getFixingPeriodStartTime()); // Alpha sensitivity vs finite difference computation final SABRInterestRateParameters sabrParameterAlphaBumped = TestsDataSetsSABR.createSABR1AlphaBumped(shiftAlpha); final SABRInterestRateDataBundle sabrBundleAlphaBumped = new SABRInterestRateDataBundle(sabrParameterAlphaBumped, curves); final CurrencyAmount pvLongPayerAlphaBumped = METHOD.presentValue(CAP_LONG, sabrBundleAlphaBumped); final double expectedAlphaSensi = (pvLongPayerAlphaBumped.getAmount() - pv.getAmount()) / shiftAlpha; assertEquals("Number of alpha sensitivity", pvsCapLong.getAlpha().getMap().keySet().size(), 1); assertEquals( "Alpha sensitivity expiry/tenor", pvsCapLong.getAlpha().getMap().keySet().contains(expectedExpiryTenor), true); assertEquals( "Alpha sensitivity value", expectedAlphaSensi, pvsCapLong.getAlpha().getMap().get(expectedExpiryTenor), 2.0E-1); // Rho sensitivity vs finite difference computation final SABRInterestRateParameters sabrParameterRhoBumped = TestsDataSetsSABR.createSABR1RhoBumped(); final SABRInterestRateDataBundle sabrBundleRhoBumped = new SABRInterestRateDataBundle(sabrParameterRhoBumped, curves); final CurrencyAmount pvLongPayerRhoBumped = METHOD.presentValue(CAP_LONG, sabrBundleRhoBumped); final double expectedRhoSensi = (pvLongPayerRhoBumped.getAmount() - pv.getAmount()) / shift; assertEquals("Number of rho sensitivity", pvsCapLong.getRho().getMap().keySet().size(), 1); assertEquals( "Rho sensitivity expiry/tenor", pvsCapLong.getRho().getMap().keySet().contains(expectedExpiryTenor), true); assertEquals( "Rho sensitivity value", pvsCapLong.getRho().getMap().get(expectedExpiryTenor), expectedRhoSensi, 1.0E-2); // Alpha sensitivity vs finite difference computation final SABRInterestRateParameters sabrParameterNuBumped = TestsDataSetsSABR.createSABR1NuBumped(); final SABRInterestRateDataBundle sabrBundleNuBumped = new SABRInterestRateDataBundle(sabrParameterNuBumped, curves); final CurrencyAmount pvLongPayerNuBumped = METHOD.presentValue(CAP_LONG, sabrBundleNuBumped); final double expectedNuSensi = (pvLongPayerNuBumped.getAmount() - pv.getAmount()) / shift; assertEquals("Number of nu sensitivity", pvsCapLong.getNu().getMap().keySet().size(), 1); assertEquals( "Nu sensitivity expiry/tenor", pvsCapLong.getNu().getMap().keySet().contains(expectedExpiryTenor), true); assertEquals( "Nu sensitivity value", pvsCapLong.getNu().getMap().get(expectedExpiryTenor), expectedNuSensi, 3.0E-2); } @Test /** * Test the present value SABR parameters sensitivity against a finite difference computation; * strike above the cut-off strike. */ public void testPresentValueSABRSensitivityAboveCutOff() { final YieldCurveBundle curves = TestsDataSetsSABR.createCurves1(); final SABRInterestRateParameters sabrParameter = TestsDataSetsSABR.createSABR1(); final SABRInterestRateDataBundle sabrBundle = new SABRInterestRateDataBundle(sabrParameter, curves); final CurrencyAmount pv = METHOD.presentValue(CAP_HIGH_LONG, sabrBundle); final PresentValueSABRSensitivityDataBundle pvsCapLong = METHOD.presentValueSABRSensitivity(CAP_HIGH_LONG, sabrBundle); PresentValueSABRSensitivityDataBundle pvsCapShort = METHOD.presentValueSABRSensitivity(CAP_HIGH_SHORT, sabrBundle); // Long/short parity pvsCapShort = pvsCapShort.multiplyBy(-1.0); assertEquals(pvsCapShort.getAlpha(), pvsCapLong.getAlpha()); // SABR sensitivity vs finite difference final double shift = 0.0001; final double shiftAlpha = 0.00001; final DoublesPair expectedExpiryTenor = new DoublesPair( CAP_HIGH_LONG.getFixingTime(), CAP_HIGH_LONG.getFixingPeriodEndTime() - CAP_HIGH_LONG.getFixingPeriodStartTime()); // Alpha sensitivity vs finite difference computation final SABRInterestRateParameters sabrParameterAlphaBumped = TestsDataSetsSABR.createSABR1AlphaBumped(shiftAlpha); final SABRInterestRateDataBundle sabrBundleAlphaBumped = new SABRInterestRateDataBundle(sabrParameterAlphaBumped, curves); final CurrencyAmount pvLongPayerAlphaBumped = METHOD.presentValue(CAP_HIGH_LONG, sabrBundleAlphaBumped); final double expectedAlphaSensi = (pvLongPayerAlphaBumped.getAmount() - pv.getAmount()) / shiftAlpha; assertEquals("Number of alpha sensitivity", pvsCapLong.getAlpha().getMap().keySet().size(), 1); assertEquals( "Alpha sensitivity expiry/tenor", pvsCapLong.getAlpha().getMap().keySet().contains(expectedExpiryTenor), true); assertEquals( "Alpha sensitivity value", expectedAlphaSensi, pvsCapLong.getAlpha().getMap().get(expectedExpiryTenor), 1.0E-0); // Rho sensitivity vs finite difference computation final SABRInterestRateParameters sabrParameterRhoBumped = TestsDataSetsSABR.createSABR1RhoBumped(); final SABRInterestRateDataBundle sabrBundleRhoBumped = new SABRInterestRateDataBundle(sabrParameterRhoBumped, curves); final CurrencyAmount pvLongPayerRhoBumped = METHOD.presentValue(CAP_HIGH_LONG, sabrBundleRhoBumped); final double expectedRhoSensi = (pvLongPayerRhoBumped.getAmount() - pv.getAmount()) / shift; assertEquals("Number of rho sensitivity", pvsCapLong.getRho().getMap().keySet().size(), 1); assertEquals( "Rho sensitivity expiry/tenor", pvsCapLong.getRho().getMap().keySet().contains(expectedExpiryTenor), true); assertEquals( "Rho sensitivity value", pvsCapLong.getRho().getMap().get(expectedExpiryTenor), expectedRhoSensi, 1.0E-1); // Alpha sensitivity vs finite difference computation final SABRInterestRateParameters sabrParameterNuBumped = TestsDataSetsSABR.createSABR1NuBumped(); final SABRInterestRateDataBundle sabrBundleNuBumped = new SABRInterestRateDataBundle(sabrParameterNuBumped, curves); final CurrencyAmount pvLongPayerNuBumped = METHOD.presentValue(CAP_HIGH_LONG, sabrBundleNuBumped); final double expectedNuSensi = (pvLongPayerNuBumped.getAmount() - pv.getAmount()) / shift; assertEquals("Number of nu sensitivity", pvsCapLong.getNu().getMap().keySet().size(), 1); assertEquals( "Nu sensitivity expiry/tenor", pvsCapLong.getNu().getMap().keySet().contains(expectedExpiryTenor), true); assertEquals( "Nu sensitivity value", pvsCapLong.getNu().getMap().get(expectedExpiryTenor), expectedNuSensi, 2.0E-1); } }
/** * Constructs a single yield curve and its Jacobian from an FX-implied yield curve calculation * configuration and a yield curve definition that contains <b>only</b> {@link * StripInstrumentType#CASH} strips. The transformation of the yield curve allows risk to be * displayed with respect to implied deposit rates, not FX forwards. */ public class ImpliedDepositCurveFunction extends AbstractFunction { /** The calculation method property value */ public static final String IMPLIED_DEPOSIT = "ImpliedDeposit"; /** The Cash instrument method */ private static final CashDiscountingMethod METHOD_CASH = CashDiscountingMethod.getInstance(); /** Calculates the par rate */ private static final ParRateCalculator PAR_RATE_CALCULATOR = ParRateCalculator.getInstance(); /** Calculates the sensitivity of the par rate to the curves */ private static final ParRateCurveSensitivityCalculator PAR_RATE_SENSITIVITY_CALCULATOR = ParRateCurveSensitivityCalculator.getInstance(); /** The business day convention used for FX forward dates computation * */ private static final BusinessDayConvention MOD_FOL = BusinessDayConventions.MODIFIED_FOLLOWING; /** The logger */ private static final Logger s_logger = LoggerFactory.getLogger(ImpliedDepositCurveFunction.class); /** The curve name */ private final String _curveCalculationConfig; private ConfigSourceQuery<MultiCurveCalculationConfig> _multiCurveCalculationConfig; private ConfigSourceQuery<YieldCurveDefinition> _yieldCurveDefinition; /** @param curveCalculationConfig The curve name, not null */ public ImpliedDepositCurveFunction(final String curveCalculationConfig) { ArgumentChecker.notNull(curveCalculationConfig, "curve name"); _curveCalculationConfig = curveCalculationConfig; } @Override public void init(final FunctionCompilationContext context) { _multiCurveCalculationConfig = ConfigSourceQuery.init(context, this, MultiCurveCalculationConfig.class); _yieldCurveDefinition = ConfigSourceQuery.init(context, this, YieldCurveDefinition.class); } @Override public CompiledFunctionDefinition compile( final FunctionCompilationContext context, final Instant atInstant) { final MultiCurveCalculationConfig impliedConfiguration = _multiCurveCalculationConfig.get(_curveCalculationConfig); if (impliedConfiguration == null) { throw new OpenGammaRuntimeException( "Multi-curve calculation called " + _curveCalculationConfig + " was null"); } ComputationTarget target = context.getComputationTargetResolver().resolve(impliedConfiguration.getTarget()); if (!(target.getValue() instanceof Currency)) { throw new OpenGammaRuntimeException( "Target of curve calculation configuration was not a currency"); } final Currency impliedCurrency = (Currency) target.getValue(); if (!IMPLIED_DEPOSIT.equals(impliedConfiguration.getCalculationMethod())) { throw new OpenGammaRuntimeException( "Curve calculation method was not " + IMPLIED_DEPOSIT + " for configuration called " + _curveCalculationConfig); } final String[] impliedCurveNames = impliedConfiguration.getYieldCurveNames(); if (impliedCurveNames.length != 1) { throw new OpenGammaRuntimeException( "Can only handle configurations with a single implied curve"); } final LinkedHashMap<String, String[]> originalConfigurationName = impliedConfiguration.getExogenousConfigData(); if (originalConfigurationName == null || originalConfigurationName.size() != 1) { throw new OpenGammaRuntimeException("Need a configuration with one exogenous configuration"); } final Map.Entry<String, String[]> entry = Iterables.getOnlyElement(originalConfigurationName.entrySet()); final String[] originalCurveNames = entry.getValue(); if (originalCurveNames.length != 1) { s_logger.warn("Found more than one exogenous configuration name; using only the first"); } final MultiCurveCalculationConfig originalConfiguration = _multiCurveCalculationConfig.get(entry.getKey()); if (originalConfiguration == null) { throw new OpenGammaRuntimeException( "Multi-curve calculation called " + entry.getKey() + " was null"); } target = context.getComputationTargetResolver().resolve(originalConfiguration.getTarget()); if (!(target.getValue() instanceof Currency)) { throw new OpenGammaRuntimeException( "Target of curve calculation configuration was not a currency"); } final Currency originalCurrency = (Currency) target.getValue(); if (!originalCurrency.equals(impliedCurrency)) { throw new OpenGammaRuntimeException( "Currency targets for configurations " + _curveCalculationConfig + " and " + entry.getKey() + " did not match"); } final YieldCurveDefinition impliedDefinition = _yieldCurveDefinition.get(impliedCurveNames[0] + "_" + impliedCurrency.getCode()); if (impliedDefinition == null) { throw new OpenGammaRuntimeException( "Could not get implied definition called " + impliedCurveNames[0] + "_" + impliedCurrency.getCode()); } final Set<FixedIncomeStrip> strips = impliedDefinition.getStrips(); for (final FixedIncomeStrip strip : strips) { if (strip.getInstrumentType() != StripInstrumentType.CASH) { throw new OpenGammaRuntimeException( "Can only handle yield curve definitions with CASH strips"); } } final ZonedDateTime atZDT = ZonedDateTime.ofInstant(atInstant, ZoneOffset.UTC); return new MyCompiledFunction( atZDT.with(LocalTime.MIDNIGHT), atZDT.plusDays(1).with(LocalTime.MIDNIGHT).minusNanos(1000000), impliedConfiguration, impliedDefinition, originalConfiguration, originalCurveNames[0]); }; private class MyCompiledFunction extends AbstractInvokingCompiledFunction { /** The definition of the implied curve */ private final YieldCurveDefinition _impliedDefinition; /** The implied curve calculation configuration */ private final MultiCurveCalculationConfig _impliedConfiguration; /** The original curve calculation configuration */ private final MultiCurveCalculationConfig _originalConfiguration; /** The implied curve name */ private final String _impliedCurveName; /** The original curve name */ private final String _originalCurveName; /** The currency */ private final Currency _currency; /** The interpolator */ private final String _interpolatorName; /** The left extrapolator */ private final String _leftExtrapolatorName; /** The right extrapolator */ private final String _rightExtrapolatorName; public MyCompiledFunction( final ZonedDateTime earliestInvokation, final ZonedDateTime latestInvokation, final MultiCurveCalculationConfig impliedConfiguration, final YieldCurveDefinition impliedDefinition, final MultiCurveCalculationConfig originalConfiguration, final String originalCurveName) { super(earliestInvokation, latestInvokation); _impliedConfiguration = impliedConfiguration; _impliedDefinition = impliedDefinition; _originalConfiguration = originalConfiguration; _impliedCurveName = impliedDefinition.getName(); _originalCurveName = originalCurveName; _currency = impliedDefinition.getCurrency(); _interpolatorName = impliedDefinition.getInterpolatorName(); _leftExtrapolatorName = impliedDefinition.getLeftExtrapolatorName(); _rightExtrapolatorName = impliedDefinition.getRightExtrapolatorName(); } @Override public Set<ComputedValue> execute( final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues) throws AsynchronousExecution { final Object originalCurveObject = inputs.getValue(YIELD_CURVE); if (originalCurveObject == null) { throw new OpenGammaRuntimeException("Could not get original curve"); } ValueProperties resultCurveProperties = null; String absoluteToleranceName = null; String relativeToleranceName = null; String iterationsName = null; String decompositionName = null; String useFiniteDifferenceName = null; for (final ValueRequirement desiredValue : desiredValues) { if (desiredValue.getValueName().equals(YIELD_CURVE)) { absoluteToleranceName = desiredValue.getConstraint( MultiYieldCurvePropertiesAndDefaults.PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE); relativeToleranceName = desiredValue.getConstraint( MultiYieldCurvePropertiesAndDefaults.PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE); iterationsName = desiredValue.getConstraint( MultiYieldCurvePropertiesAndDefaults.PROPERTY_ROOT_FINDER_MAX_ITERATIONS); decompositionName = desiredValue.getConstraint( MultiYieldCurvePropertiesAndDefaults.PROPERTY_DECOMPOSITION); useFiniteDifferenceName = desiredValue.getConstraint( MultiYieldCurvePropertiesAndDefaults.PROPERTY_USE_FINITE_DIFFERENCE); resultCurveProperties = desiredValue.getConstraints().copy().get(); break; } } if (resultCurveProperties == null) { throw new OpenGammaRuntimeException("Could not get result curve properties"); } final ValueProperties resultJacobianProperties = resultCurveProperties.withoutAny(CURVE); ZonedDateTime valuationDateTime = executionContext .getValuationTime() .atZone(executionContext.getValuationClock().getZone()); final HolidaySource holidaySource = OpenGammaExecutionContext.getHolidaySource(executionContext); final ConventionSource conventionSource = OpenGammaExecutionContext.getConventionSource(executionContext); final Calendar calendar = CalendarUtils.getCalendar(holidaySource, _currency); final DepositConvention convention = conventionSource.getSingle( ExternalId.of(SCHEME_NAME, getConventionName(_currency, DEPOSIT)), DepositConvention.class); final int spotLag = convention.getSettlementDays(); final ExternalId conventionSettlementRegion = convention.getRegionCalendar(); ZonedDateTime spotDate; if (spotLag == 0 && conventionSettlementRegion == null) { spotDate = valuationDateTime; } else { spotDate = ScheduleCalculator.getAdjustedDate(valuationDateTime, spotLag, calendar); ; } final YieldCurveBundle curves = new YieldCurveBundle(); final String fullYieldCurveName = _originalCurveName + "_" + _currency; curves.setCurve(fullYieldCurveName, (YieldAndDiscountCurve) originalCurveObject); final int n = _impliedDefinition.getStrips().size(); final double[] t = new double[n]; final double[] r = new double[n]; int i = 0; final DayCount dayCount = DayCountFactory.INSTANCE.getDayCount( "Act/360"); // TODO: Get the convention from the curve. final String impliedDepositCurveName = _curveCalculationConfig + "_" + _currency.getCode(); final List<InstrumentDerivative> derivatives = new ArrayList<>(); for (final FixedIncomeStrip strip : _impliedDefinition.getStrips()) { final Tenor tenor = strip.getCurveNodePointTime(); final ZonedDateTime paymentDate = ScheduleCalculator.getAdjustedDate( spotDate, tenor.getPeriod(), MOD_FOL, calendar, true); final double startTime = TimeCalculator.getTimeBetween(valuationDateTime, spotDate); final double endTime = TimeCalculator.getTimeBetween(valuationDateTime, paymentDate); final double accrualFactor = dayCount.getDayCountFraction(spotDate, paymentDate, calendar); final Cash cashFXCurve = new Cash(_currency, startTime, endTime, 1, 0, accrualFactor, fullYieldCurveName); final double parRate = METHOD_CASH.parRate(cashFXCurve, curves); final Cash cashDepositCurve = new Cash(_currency, startTime, endTime, 1, 0, accrualFactor, impliedDepositCurveName); derivatives.add(cashDepositCurve); t[i] = endTime; r[i++] = parRate; } final CombinedInterpolatorExtrapolator interpolator = CombinedInterpolatorExtrapolatorFactory.getInterpolator( _interpolatorName, _leftExtrapolatorName, _rightExtrapolatorName); final double absoluteTolerance = Double.parseDouble(absoluteToleranceName); final double relativeTolerance = Double.parseDouble(relativeToleranceName); final int iterations = Integer.parseInt(iterationsName); final Decomposition<?> decomposition = DecompositionFactory.getDecomposition(decompositionName); final boolean useFiniteDifference = Boolean.parseBoolean(useFiniteDifferenceName); final LinkedHashMap<String, double[]> curveNodes = new LinkedHashMap<>(); final LinkedHashMap<String, Interpolator1D> interpolators = new LinkedHashMap<>(); curveNodes.put(impliedDepositCurveName, t); interpolators.put(impliedDepositCurveName, interpolator); final FXMatrix fxMatrix = new FXMatrix(); final YieldCurveBundle knownCurve = new YieldCurveBundle(); final MultipleYieldCurveFinderDataBundle data = new MultipleYieldCurveFinderDataBundle( derivatives, r, knownCurve, curveNodes, interpolators, useFiniteDifference, fxMatrix); final NewtonVectorRootFinder rootFinder = new BroydenVectorRootFinder( absoluteTolerance, relativeTolerance, iterations, decomposition); final Function1D<DoubleMatrix1D, DoubleMatrix1D> curveCalculator = new MultipleYieldCurveFinderFunction(data, PAR_RATE_CALCULATOR); final Function1D<DoubleMatrix1D, DoubleMatrix2D> jacobianCalculator = new MultipleYieldCurveFinderJacobian(data, PAR_RATE_SENSITIVITY_CALCULATOR); final double[] fittedYields = rootFinder.getRoot(curveCalculator, jacobianCalculator, new DoubleMatrix1D(r)).getData(); final DoubleMatrix2D jacobianMatrix = jacobianCalculator.evaluate(new DoubleMatrix1D(fittedYields)); final YieldCurve impliedDepositCurve = new YieldCurve( impliedDepositCurveName, InterpolatedDoublesCurve.from(t, fittedYields, interpolator)); final ValueSpecification curveSpec = new ValueSpecification(YIELD_CURVE, target.toSpecification(), resultCurveProperties); final ValueSpecification jacobianSpec = new ValueSpecification( YIELD_CURVE_JACOBIAN, target.toSpecification(), resultJacobianProperties); return Sets.newHashSet( new ComputedValue(curveSpec, impliedDepositCurve), new ComputedValue(jacobianSpec, jacobianMatrix)); } @Override public ComputationTargetType getTargetType() { return ComputationTargetType.CURRENCY; } @Override public Set<ValueSpecification> getResults( final FunctionCompilationContext compilationContext, final ComputationTarget target) { final ValueProperties curveProperties = getCurveProperties(_impliedCurveName, _impliedConfiguration.getCalculationConfigName()); final ValueProperties jacobianProperties = getJacobianProperties(_impliedConfiguration.getCalculationConfigName()); final ComputationTargetSpecification targetSpec = target.toSpecification(); final ValueSpecification curve = new ValueSpecification(YIELD_CURVE, targetSpec, curveProperties); final ValueSpecification jacobian = new ValueSpecification(YIELD_CURVE_JACOBIAN, targetSpec, jacobianProperties); return Sets.newHashSet(curve, jacobian); } @Override public Set<ValueRequirement> getRequirements( final FunctionCompilationContext compilationContext, final ComputationTarget target, final ValueRequirement desiredValue) { final ValueProperties constraints = desiredValue.getConstraints(); final Set<String> rootFinderAbsoluteTolerance = constraints.getValues(PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE); if (rootFinderAbsoluteTolerance == null || rootFinderAbsoluteTolerance.size() != 1) { return null; } final Set<String> rootFinderRelativeTolerance = constraints.getValues(PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE); if (rootFinderRelativeTolerance == null || rootFinderRelativeTolerance.size() != 1) { return null; } final Set<String> maxIterations = constraints.getValues(PROPERTY_ROOT_FINDER_MAX_ITERATIONS); if (maxIterations == null || maxIterations.size() != 1) { return null; } final Set<String> decomposition = constraints.getValues(PROPERTY_DECOMPOSITION); if (decomposition == null || decomposition.size() != 1) { return null; } final Set<String> useFiniteDifference = constraints.getValues(PROPERTY_USE_FINITE_DIFFERENCE); if (useFiniteDifference == null || useFiniteDifference.size() != 1) { return null; } if (!_originalConfiguration.getTarget().equals(target.toSpecification())) { s_logger.info( "Invalid target, was {} - expected {}", target, _originalConfiguration.getTarget()); return null; } final ValueProperties properties = ValueProperties.builder() .with(CURVE_CALCULATION_METHOD, _originalConfiguration.getCalculationMethod()) .with(CURVE_CALCULATION_CONFIG, _originalConfiguration.getCalculationConfigName()) .with(CURVE, _originalCurveName) .get(); return Collections.singleton( new ValueRequirement(YIELD_CURVE, target.toSpecification(), properties)); } /** * Gets the properties of the implied yield curve. * * @param curveName The implied curve name * @return The properties */ private ValueProperties getCurveProperties( final String curveName, final String curveCalculationConfig) { return createValueProperties() .with(CURVE_CALCULATION_METHOD, IMPLIED_DEPOSIT) .with(CURVE, curveName) .with(CURVE_CALCULATION_CONFIG, curveCalculationConfig) .withAny(PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE) .withAny(PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE) .withAny(PROPERTY_ROOT_FINDER_MAX_ITERATIONS) .withAny(PROPERTY_DECOMPOSITION) .withAny(PROPERTY_USE_FINITE_DIFFERENCE) .with(InterpolatedDataProperties.X_INTERPOLATOR_NAME, _interpolatorName) .with(InterpolatedDataProperties.LEFT_X_EXTRAPOLATOR_NAME, _leftExtrapolatorName) .with(InterpolatedDataProperties.RIGHT_X_EXTRAPOLATOR_NAME, _rightExtrapolatorName) .get(); } /** * Gets the properties of the Jacobian with no values set. * * @return The properties. */ private ValueProperties getJacobianProperties(final String curveCalculationConfig) { return createValueProperties() .with(CURVE_CALCULATION_METHOD, IMPLIED_DEPOSIT) .with(CURVE_CALCULATION_CONFIG, curveCalculationConfig) .withAny(PROPERTY_ROOT_FINDER_ABSOLUTE_TOLERANCE) .withAny(PROPERTY_ROOT_FINDER_RELATIVE_TOLERANCE) .withAny(PROPERTY_ROOT_FINDER_MAX_ITERATIONS) .withAny(PROPERTY_DECOMPOSITION) .withAny(PROPERTY_USE_FINITE_DIFFERENCE) .with(InterpolatedDataProperties.X_INTERPOLATOR_NAME, _interpolatorName) .with(InterpolatedDataProperties.LEFT_X_EXTRAPOLATOR_NAME, _leftExtrapolatorName) .with(InterpolatedDataProperties.RIGHT_X_EXTRAPOLATOR_NAME, _rightExtrapolatorName) .get(); } } }