@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); }
/** * 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); }
/** Check C2 smoothness of Benaim-Dodgson-Kainth extrapolation */ @Test public void BDKSmoothnessGeneralTest() { double eps = 1.0e-5; double expiry = 1.5; double forward = 1.1; int nStrikes = 10; double[] strikes = new double[nStrikes]; double[] impliedVols = new double[] {0.97, 0.92, 0.802, 0.745, 0.781, 0.812, 0.8334, 0.878, 0.899, 0.9252}; for (int i = 0; i < nStrikes; ++i) { strikes[i] = forward * (0.85 + i * 0.05); } double muLow = strikes[0] * BlackFormulaRepository.dualDelta(forward, strikes[0], expiry, impliedVols[0], false) / BlackFormulaRepository.price(forward, strikes[0], expiry, impliedVols[0], false); double muHigh = -strikes[nStrikes - 1] * BlackFormulaRepository.dualDelta( forward, strikes[nStrikes - 1], expiry, impliedVols[nStrikes - 1], true) / BlackFormulaRepository.price( forward, strikes[nStrikes - 1], expiry, impliedVols[nStrikes - 1], true); SmileExtrapolationFunctionSABRProvider extrapBDK = new BenaimDodgsonKainthExtrapolationFunctionProvider(muLow, muHigh); SmileInterpolatorSABRWithExtrapolation interpBDK = new SmileInterpolatorSABRWithExtrapolation( new SABRBerestyckiVolatilityFunction(), extrapBDK); InterpolatedSmileFunction funcBDK = new InterpolatedSmileFunction(interpBDK, forward, strikes, expiry, impliedVols); List<SABRFormulaData> modelParams = (new SmileInterpolatorSABR()) .getFittedModelParameters(forward, strikes, expiry, impliedVols); SABRExtrapolationLeftFunction sabrLeftExtrapolation = new SABRExtrapolationLeftFunction( forward, modelParams.get(0), strikes[0], expiry, muLow, new SABRHaganVolatilityFunction()); SABRExtrapolationRightFunction sabrRightExtrapolation = new SABRExtrapolationRightFunction( forward, modelParams.get(nStrikes - 3), strikes[nStrikes - 1], expiry, muHigh, new SABRHaganVolatilityFunction()); /* * left interpolation */ { // Checking underlying extrapolation double boundaryValue = sabrLeftExtrapolation.price(new EuropeanVanillaOption(strikes[0], expiry, false)); double CutoffUp = strikes[0] + eps; double CutoffDw = strikes[0] - eps; double optionPriceExt = sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffDw, expiry, false)); double optionPriceInt = sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffUp, expiry, false)); assertEquals(boundaryValue, optionPriceExt, eps); assertEquals(boundaryValue, optionPriceInt, eps); double optionPriceExtDw = sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffDw - eps, expiry, false)); double firstExt = (1.5 * boundaryValue + 0.5 * optionPriceExtDw - 2.0 * optionPriceExt) / eps; double optionPriceIntUp = sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffUp + eps, expiry, false)); double firstInt = (2.0 * optionPriceInt - 0.5 * optionPriceIntUp - 1.5 * boundaryValue) / eps; assertEquals(firstInt, firstExt, eps); double secondExt = (boundaryValue + optionPriceExtDw - 2.0 * optionPriceExt) / eps / eps; double secondInt = (optionPriceIntUp + boundaryValue - 2.0 * optionPriceInt) / eps / eps; assertEquals(secondInt, secondExt, Math.abs(secondInt) * 1.0e-3); // Checking volatility function double volInt = funcBDK.getVolatility(CutoffUp); double volExt = funcBDK.getVolatility(CutoffDw); double volBoundary = funcBDK.getVolatility(strikes[0]); assertEquals(volBoundary, volInt, eps); assertEquals(volBoundary, volExt, eps); double volExtDw = funcBDK.getVolatility(CutoffDw - eps); double volFirstExt = (1.5 * volBoundary + 0.5 * volExtDw - 2.0 * volExt) / eps; double volIntUp = funcBDK.getVolatility(CutoffUp + eps); double volFirstInt = (2.0 * volInt - 0.5 * volIntUp - 1.5 * volBoundary) / eps; assertEquals(volFirstInt, volFirstExt, eps); double volSecondExt = (volBoundary + volExtDw - 2.0 * volExt) / eps / eps; double volSecondInt = (volIntUp + volBoundary - 2.0 * volInt) / eps / eps; assertEquals(volSecondInt, volSecondExt, Math.abs(volSecondInt) * 1.0e-3); } /* * right interpolation */ { // Checking underlying extrapolation double boundaryValue = sabrRightExtrapolation.price( new EuropeanVanillaOption(strikes[nStrikes - 1], expiry, true)); double CutoffUp = strikes[nStrikes - 1] + eps; double CutoffDw = strikes[nStrikes - 1] - eps; double optionPriceExt = sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffUp, expiry, true)); double optionPriceInt = sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffDw, expiry, true)); assertEquals(boundaryValue, optionPriceExt, eps); assertEquals(boundaryValue, optionPriceInt, eps); double optionPriceExtUp = sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffUp + eps, expiry, true)); double firstExt = (2.0 * optionPriceExt - 0.5 * optionPriceExtUp - 1.5 * boundaryValue) / eps; double optionPriceIntDw = sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffDw - eps, expiry, true)); double firstInt = (-2.0 * optionPriceInt + 1.5 * boundaryValue + 0.5 * optionPriceIntDw) / eps; assertEquals(firstInt, firstExt, eps); double secondExt = (optionPriceExtUp + boundaryValue - 2.0 * optionPriceExt) / eps / eps; double secondInt = (boundaryValue + optionPriceIntDw - 2.0 * optionPriceInt) / eps / eps; assertEquals(secondInt, secondExt, Math.abs(secondInt) * 1.0e-3); // Checking volatility function double volInt = funcBDK.getVolatility(CutoffDw); double volExt = funcBDK.getVolatility(CutoffUp); double volBoundary = funcBDK.getVolatility(strikes[nStrikes - 1]); assertEquals(volBoundary, volInt, eps); assertEquals(volBoundary, volExt, eps); double volExtUp = funcBDK.getVolatility(CutoffUp + eps); double volFirstExt = (2.0 * volExt - 0.5 * volExtUp - 1.5 * volBoundary) / eps; double volIntDw = funcBDK.getVolatility(CutoffDw - eps); double volFirstInt = (-2.0 * volInt + 1.5 * volBoundary + 0.5 * volIntDw) / eps; assertEquals(volFirstInt, volFirstExt, eps); double volSecondExt = (volBoundary + volExtUp - 2.0 * volExt) / eps / eps; double volSecondInt = (volIntDw + volBoundary - 2.0 * volInt) / eps / eps; assertEquals(volSecondInt, volSecondExt, Math.abs(volSecondInt) * 1.0e-3); } }
/** * 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); }
/** * 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; }