@Override public MulticurveSensitivity visitSwap( final Swap<?, ?> swap, final MulticurveProviderInterface multicurves) { ArgumentChecker.notNull(multicurves, "multicurve"); ArgumentChecker.notNull(swap, "Swap"); // if the swap is an On compounded (ie Brazilian like), the parspread formula is not the same. if (swap.getSecondLeg().getNthPayment(0) instanceof CouponONCompounded && swap.getFirstLeg().getNthPayment(0) instanceof CouponFixedAccruedCompounding && swap.getFirstLeg().getNumberOfPayments() == 1) { // Implementation note: check if the swap is a Brazilian swap. final MulticurveSensitivity pvcsFirstLeg = swap.getFirstLeg() .accept(PVCSMC, multicurves) .getSensitivity(swap.getFirstLeg().getCurrency()); final MulticurveSensitivity pvcsSecondLeg = swap.getSecondLeg() .accept(PVCSMC, multicurves) .getSensitivity(swap.getSecondLeg().getCurrency()); final CouponFixedAccruedCompounding cpnFixed = (CouponFixedAccruedCompounding) swap.getFirstLeg().getNthPayment(0); final double pvONCompoundedLeg = swap.getSecondLeg() .accept(PVMC, multicurves) .getAmount(swap.getSecondLeg().getCurrency()); final double discountFactor = multicurves.getDiscountFactor( swap.getFirstLeg().getCurrency(), cpnFixed.getPaymentTime()); final double paymentYearFraction = cpnFixed.getPaymentYearFraction(); final double notional = ((CouponONCompounded) swap.getSecondLeg().getNthPayment(0)).getNotional(); final double intermediateVariable = (1 / paymentYearFraction) * Math.pow(pvONCompoundedLeg / discountFactor / notional, 1 / paymentYearFraction - 1) / (discountFactor * notional); final MulticurveSensitivity modifiedpvcsFirstLeg = pvcsFirstLeg.multipliedBy(pvONCompoundedLeg * intermediateVariable / discountFactor); final MulticurveSensitivity modifiedpvcsSecondLeg = pvcsSecondLeg.multipliedBy(-intermediateVariable); return modifiedpvcsFirstLeg.plus(modifiedpvcsSecondLeg); } final Currency ccy1 = swap.getFirstLeg().getCurrency(); final MultipleCurrencyMulticurveSensitivity pvcs = swap.accept(PVCSMC, multicurves); final MulticurveSensitivity pvcs1 = pvcs.converted(ccy1, multicurves.getFxRates()).getSensitivity(ccy1); final MulticurveSensitivity pvmqscs = swap.getFirstLeg().accept(PVMQSCSMC, multicurves); final double pvmqs = swap.getFirstLeg().accept(PVMQSMC, multicurves); final double pv = multicurves.getFxRates().convert(swap.accept(PVMC, multicurves), ccy1).getAmount(); // Implementation note: Total pv in currency 1. return pvcs1.multipliedBy(-1.0 / pvmqs).plus(pvmqscs.multipliedBy(pv / (pvmqs * pvmqs))); }
@Test /** Tests long/short parity. */ public void presentValueCurveSensitivityLongShortParityExplicit() { final MultipleCurrencyMulticurveSensitivity pvhwsLong = METHOD_HW.presentValueCurveSensitivity(SWAPTION_LONG_PAYER, HW_MULTICURVES); final MultipleCurrencyMulticurveSensitivity pvhwsShort = METHOD_HW.presentValueCurveSensitivity(SWAPTION_SHORT_PAYER, HW_MULTICURVES); AssertSensivityObjects.assertEquals( "Swaption physical - Hull-White - presentValueCurveSensitivity - long/short parity", pvhwsLong, pvhwsShort.multipliedBy(-1.0), TOLERANCE_PV_DELTA); }
@Test /** Tests payer/receiver/swap parity. */ public void presentValueCurveSensitivityPayerReceiverParityExplicit() { final MultipleCurrencyMulticurveSensitivity pvhwsReceiverLong = METHOD_HW.presentValueCurveSensitivity(SWAPTION_LONG_RECEIVER, HW_MULTICURVES); final MultipleCurrencyMulticurveSensitivity pvhwsPayerShort = METHOD_HW.presentValueCurveSensitivity(SWAPTION_SHORT_PAYER, HW_MULTICURVES); final MultipleCurrencyMulticurveSensitivity pvSwap = SWAP_RECEIVER.accept(PVCSDC, MULTICURVES); AssertSensivityObjects.assertEquals( "Swaption physical - Hull-White - presentValueCurveSensitivity - payer/receiver/swap parity", pvSwap.cleaned(TOLERANCE_PV_DELTA), pvhwsReceiverLong.plus(pvhwsPayerShort).cleaned(TOLERANCE_PV_DELTA), TOLERANCE_PV_DELTA); }
@Test public void presentValueCurveSensitivityDiscountingCalculator() { final MultipleCurrencyMulticurveSensitivity pvcsSwap = SWAP_MULTI_LEG.accept(PVCSDC, MULTICURVES); MultipleCurrencyMulticurveSensitivity pvcsLegs = SWAP_MULTI_LEG.getLegs()[0].accept(PVCSDC, MULTICURVES); for (int loopleg = 1; loopleg < NB_LEGS; loopleg++) { pvcsLegs = pvcsLegs.plus(SWAP_MULTI_LEG.getLegs()[loopleg].accept(PVCSDC, MULTICURVES)); } AssertSensitivityObjects.assertEquals( "SwapMultileg: presentValueCurveSensitivityDiscountingCalculator", pvcsLegs, pvcsSwap, TOLERANCE_PV_DELTA); }
@Test(enabled = false) /** Tests of performance. "enabled = false" for the standard testing. */ public void performanceCurveSensitivity() { long startTime, endTime; final int nbTest = 25; MultipleCurrencyAmount pvMC = MultipleCurrencyAmount.of(EUR, 0.0); final MultipleCurrencyMulticurveSensitivity pvcsExplicit = METHOD_HW.presentValueCurveSensitivity(SWAPTION_LONG_PAYER, HW_MULTICURVES); MultipleCurrencyMulticurveSensitivity pvcsMC = pvcsExplicit; final HullWhiteMonteCarloMethod methodMC = new HullWhiteMonteCarloMethod( new NormalRandomNumberGenerator(0.0, 1.0, new MersenneTwister()), NB_PATH); startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { pvMC = METHOD_HW_MONTECARLO.presentValue(SWAPTION_LONG_PAYER, EUR, HW_MULTICURVES); } endTime = System.currentTimeMillis(); System.out.println( nbTest + " swaption Hull-White Monte Carlo method (" + NB_PATH + " paths): " + (endTime - startTime) + " ms / price:" + pvMC.toString()); // Performance note: HW approximation: 03-Dec-2012: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 250 // ms for 25 swaptions (12500 paths). startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { pvcsMC = methodMC.presentValueCurveSensitivity(SWAPTION_LONG_PAYER, EUR, HW_MULTICURVES); } endTime = System.currentTimeMillis(); System.out.println( nbTest + " curve sensitivity swaption Hull-White MC method: (" + NB_PATH + " paths) " + (endTime - startTime) + " ms / risk:" + pvcsMC.toString()); // Performance note: curve sensitivity (40): 03-Dec-2012: On Mac Pro 3.2 GHz Quad-Core Intel // Xeon: 600 ms for 25 swaptions (12500 paths). }
/** * Compute the present value sensitivity to rates of a bond future by discounting. * * @param future The future. * @param issuerMulticurves The issuer and multi-curves provider. * @return The present value rate sensitivity. */ public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity( final BondFuture future, final IssuerProviderInterface issuerMulticurves) { Currency ccy = future.getCurrency(); final MulticurveSensitivity priceSensitivity = priceCurveSensitivity(future, issuerMulticurves); final MultipleCurrencyMulticurveSensitivity transactionSensitivity = MultipleCurrencyMulticurveSensitivity.of( ccy, priceSensitivity.multipliedBy(future.getNotional())); return transactionSensitivity; }
/** * Compute the present value sensitivity to rates of a Ibor compounded coupon by discounting. * * @param coupon The coupon. * @param multicurve The multi-curve provider. * @return The present value sensitivity. */ public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity( final CouponIborCompounding coupon, final MulticurveProviderInterface multicurve) { ArgumentChecker.notNull(coupon, "Coupon"); ArgumentChecker.notNull(multicurve, "Multi-curves provider"); final int nbSubPeriod = coupon.getFixingTimes().length; double notionalAccrued = coupon.getNotionalAccrued(); final double[] forward = new double[nbSubPeriod]; final double[] ratioForward = new double[nbSubPeriod]; for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) { forward[loopsub] = multicurve.getForwardRate( coupon.getIndex(), coupon.getFixingPeriodStartTimes()[loopsub], coupon.getFixingPeriodEndTimes()[loopsub], coupon.getFixingPeriodAccrualFactors()[loopsub]); ratioForward[loopsub] = 1.0 + coupon.getPaymentAccrualFactors()[loopsub] * forward[loopsub]; notionalAccrued *= ratioForward[loopsub]; } final double dfPayment = multicurve.getDiscountFactor(coupon.getCurrency(), coupon.getPaymentTime()); // Backward sweep final double pvBar = 1.0; final double dfPaymentBar = (notionalAccrued - coupon.getNotional()) * pvBar; final double notionalAccruedBar = dfPayment * pvBar; final double[] ratioForwardBar = new double[nbSubPeriod]; final double[] forwardBar = new double[nbSubPeriod]; for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) { ratioForwardBar[loopsub] = notionalAccrued / ratioForward[loopsub] * notionalAccruedBar; forwardBar[loopsub] = coupon.getPaymentAccrualFactors()[loopsub] * ratioForwardBar[loopsub]; } final Map<String, List<DoublesPair>> mapDsc = new HashMap<>(); final List<DoublesPair> listDiscounting = new ArrayList<>(); listDiscounting.add( new DoublesPair( coupon.getPaymentTime(), -coupon.getPaymentTime() * dfPayment * dfPaymentBar)); mapDsc.put(multicurve.getName(coupon.getCurrency()), listDiscounting); final Map<String, List<ForwardSensitivity>> mapFwd = new HashMap<>(); final List<ForwardSensitivity> listForward = new ArrayList<>(); for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) { listForward.add( new ForwardSensitivity( coupon.getFixingPeriodStartTimes()[loopsub], coupon.getFixingPeriodEndTimes()[loopsub], coupon.getFixingPeriodAccrualFactors()[loopsub], forwardBar[loopsub])); } mapFwd.put(multicurve.getName(coupon.getIndex()), listForward); return MultipleCurrencyMulticurveSensitivity.of( coupon.getCurrency(), MulticurveSensitivity.of(mapDsc, mapFwd)); }
/** * Computes the present value curve sensitivity of a fixed payment by discounting. * * @param payment The fixed payment. * @param multicurves The multi-curve provider. * @return The sensitivity. */ public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity( final PaymentFixed payment, final MulticurveProviderInterface multicurves) { final double time = payment.getPaymentTime(); final DoublesPair s = DoublesPair.of( time, -time * payment.getAmount() * multicurves.getDiscountFactor(payment.getCurrency(), time)); final List<DoublesPair> list = new ArrayList<>(); list.add(s); final Map<String, List<DoublesPair>> result = new HashMap<>(); result.put(multicurves.getName(payment.getCurrency()), list); return MultipleCurrencyMulticurveSensitivity.of( payment.getCurrency(), MulticurveSensitivity.ofYieldDiscounting(result)); }
@Test(enabled = false) /** Tests of performance. "enabled = false" for the standard testing. */ public void performance() { long startTime, endTime; final int nbTest = 1000; MultipleCurrencyAmount pvPayerLongExplicit = MultipleCurrencyAmount.of(EUR, 0.0); MultipleCurrencyAmount pvPayerLongIntegration = MultipleCurrencyAmount.of(EUR, 0.0); MultipleCurrencyAmount pvPayerLongApproximation = MultipleCurrencyAmount.of(EUR, 0.0); @SuppressWarnings("unused") MultipleCurrencyAmount pvPayerLongMC = MultipleCurrencyAmount.of(EUR, 0.0); double[] pvhws = METHOD_HW.presentValueHullWhiteSensitivity(SWAPTION_LONG_PAYER, HW_MULTICURVES); MultipleCurrencyMulticurveSensitivity pvcs = METHOD_HW.presentValueCurveSensitivity(SWAPTION_LONG_PAYER, HW_MULTICURVES); startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { pvPayerLongExplicit = METHOD_HW.presentValue(SWAPTION_LONG_PAYER, HW_MULTICURVES); } endTime = System.currentTimeMillis(); System.out.println( nbTest + " pv swaption Hull-White explicit method: " + (endTime - startTime) + " ms"); // Performance note: HW price: 19-Nov-2012: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 380 ms for // 10000 swaptions. startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { pvhws = METHOD_HW.presentValueHullWhiteSensitivity(SWAPTION_LONG_PAYER, HW_MULTICURVES); } endTime = System.currentTimeMillis(); System.out.println( nbTest + " HW sensitivity swaption Hull-White explicit method: " + (endTime - startTime) + " ms"); // Performance note: HW sensitivity (3): 19-Nov-2012: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: // 430 ms for 10000 swaptions. startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { pvcs = METHOD_HW.presentValueCurveSensitivity(SWAPTION_LONG_PAYER, HW_MULTICURVES); } endTime = System.currentTimeMillis(); System.out.println( nbTest + " curve sensitivity swaption Hull-White explicit method: " + (endTime - startTime) + " ms"); // Performance note: curve sensitivity (40): 19-Nov-2012: On Mac Pro 3.2 GHz Quad-Core Intel // Xeon: 855 ms for 10000 swaptions. startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { pvhws = METHOD_HW.presentValueHullWhiteSensitivity(SWAPTION_LONG_PAYER, HW_MULTICURVES); pvcs = METHOD_HW.presentValueCurveSensitivity(SWAPTION_LONG_PAYER, HW_MULTICURVES); pvhws = METHOD_HW.presentValueHullWhiteSensitivity(SWAPTION_LONG_PAYER, HW_MULTICURVES); } endTime = System.currentTimeMillis(); System.out.println( nbTest + " price/delta/vega swaption Hull-White explicit method: " + (endTime - startTime) + " ms"); // Performance note: present value/delta/vega: 19-Nov-2012: On Mac Pro 3.2 GHz Quad-Core Intel // Xeon: 1730 ms for 10000 swaptions. startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { pvPayerLongIntegration = METHOD_HW_INTEGRATION.presentValue(SWAPTION_LONG_PAYER, HW_MULTICURVES); } endTime = System.currentTimeMillis(); System.out.println( nbTest + " swaption Hull-White numerical integration method: " + (endTime - startTime) + " ms"); // Performance note: HW numerical integration: 19-Nov-2012: On Mac Pro 3.2 GHz Quad-Core Intel // Xeon: 1700 ms for 10000 swaptions. startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { pvPayerLongApproximation = METHOD_HW_APPROXIMATION.presentValue(SWAPTION_LONG_PAYER, HW_MULTICURVES); } endTime = System.currentTimeMillis(); System.out.println( nbTest + " swaption Hull-White approximation method: " + (endTime - startTime) + " ms"); // Performance note: HW approximation: 19-Nov-2012: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 250 // ms for 10000 swaptions. startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { pvPayerLongMC = METHOD_HW_MONTECARLO.presentValue(SWAPTION_LONG_PAYER, EUR, HW_MULTICURVES); } endTime = System.currentTimeMillis(); System.out.println( nbTest + " swaption Hull-White Monte Carlo method (" + NB_PATH + " paths): " + (endTime - startTime) + " ms"); // Performance note: HW approximation: 18-Aug-11: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 9200 // ms for 1000 swaptions (12500 paths). final double difference = pvPayerLongExplicit.getAmount(EUR) - pvPayerLongIntegration.getAmount(EUR); final double difference2 = pvPayerLongExplicit.getAmount(EUR) - pvPayerLongApproximation.getAmount(EUR); // double difference3 = pvPayerLongExplicit.getAmount(CUR) - pvPayerLongMC.getAmount(CUR); System.out.println("Difference explicit-integration: " + difference); System.out.println("Difference explicit-approximation: " + difference2); // System.out.println("Difference explicit-Monte Carlo: " + difference3); System.out.println("Curve sensitivity: " + pvcs.toString()); System.out.println("HW sensitivity: " + Arrays.toString(pvhws)); }
/** * Computes the pv sensitivity of an Ibor cap/floor in arrears * * @param cap The cap/floor * @param curves The curves * @return The sensitivity */ public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity( final CapFloorIbor cap, final MulticurveProviderInterface curves) { ArgumentChecker.notNull(cap, "The cap/floor shoud not be null"); ArgumentChecker.notNull(curves, "curves"); final Currency ccy = cap.getCurrency(); // Construct a "standard" CapFloorIbor whose paymentTime is set to be fixingPeriodEndTime CapFloorIbor capStandard = new CapFloorIbor( cap.getCurrency(), cap.getFixingPeriodEndTime(), cap.getPaymentYearFraction(), cap.getNotional(), cap.getFixingTime(), cap.getIndex(), cap.getFixingPeriodStartTime(), cap.getFixingPeriodEndTime(), cap.getFixingAccrualFactor(), cap.getStrike(), cap.isCap()); final double forward = curves.getSimplyCompoundForwardRate( cap.getIndex(), cap.getFixingPeriodStartTime(), cap.getFixingPeriodEndTime(), cap.getFixingAccrualFactor()); final double beta = (1.0 + cap.getFixingAccrualFactor() * forward) * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime()) / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime()); double df = curves.getDiscountFactor(capStandard.getCurrency(), capStandard.getPaymentTime()); double strikePart = (1.0 + cap.getFixingAccrualFactor() * capStandard.getStrike()) * presentValueStandard( forward, capStandard.getStrike(), capStandard.getFixingTime(), capStandard.isCap(), df, capStandard.getNotional(), capStandard.getPaymentYearFraction()); double strikePartDelta = (1.0 + cap.getFixingAccrualFactor() * capStandard.getStrike()) * presentValueDeltaStandard( forward, capStandard.getStrike(), capStandard.getFixingTime(), capStandard.isCap(), df, capStandard.getNotional(), capStandard.getPaymentYearFraction()); final InArrearsIntegrant integrant = new InArrearsIntegrant(capStandard, curves); double integralPart; double upper = 0.0; try { if (cap.isCap()) { double atmVol = _smileFunction.getVolatility(forward); upper = forward * Math.exp(6.0 * atmVol * Math.sqrt(cap.getFixingTime())); double strike = cap.getStrike(); integralPart = INTEGRATOR.integrate(integrant, strike, upper); double reminder = integrant.evaluate(upper) * upper; double error = reminder / integralPart; int count = 0; while (Math.abs(error) > REL_ERROR && count < MAX_COUNT) { integralPart += INTEGRATOR.integrate(integrant, upper, 2.0 * upper); upper *= 2.0; // The increase of integralPart in the next loop is bounded by reminder reminder = integrant.evaluate(upper) * upper; error = reminder / integralPart; ++count; if (count == MAX_COUNT) { LOGGER.info( "Maximum iteration count, " + MAX_COUNT + ", has been reached. Relative error is greater than " + REL_ERROR); } } } else { double strike = cap.getStrike(); integralPart = INTEGRATOR.integrate(integrant, REL_TOL * strike, strike); } } catch (final Exception e) { throw new MathException(e); } integralPart *= 2.0 * cap.getFixingAccrualFactor(); double pv = (strikePart + integralPart) / beta; double betaFwd = cap.getFixingAccrualFactor() * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime()) / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime()); double betaDscStart = (1.0 + cap.getFixingAccrualFactor() * forward) * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime()) * cap.getFixingPeriodStartTime() / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime()); double betaDscEnd = -(1.0 + cap.getFixingAccrualFactor() * forward) * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime()) * cap.getFixingPeriodEndTime() / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime()); List<DoublesPair> listDiscounting = new ArrayList<>(); double strikePartDsc = -capStandard.getPaymentTime() * strikePart; double integralPartDsc = -capStandard.getPaymentTime() * integralPart; listDiscounting.add( DoublesPair.of(capStandard.getPaymentTime(), (strikePartDsc + integralPartDsc) / beta)); listDiscounting.add(DoublesPair.of(cap.getFixingPeriodStartTime(), -pv * betaDscStart / beta)); listDiscounting.add(DoublesPair.of(cap.getFixingPeriodEndTime(), -pv * betaDscEnd / beta)); Map<String, List<DoublesPair>> mapDsc = new HashMap<>(); mapDsc.put(curves.getName(capStandard.getCurrency()), listDiscounting); final List<ForwardSensitivity> listForward = new ArrayList<>(); double strikePartFwd = strikePartDelta; double integralPartFwd = 0.0; final InArrearsDeltaIntegrant integrantFwd = new InArrearsDeltaIntegrant(capStandard, curves); try { if (cap.isCap()) { double strike = cap.getStrike(); integralPartFwd = INTEGRATOR.integrate(integrantFwd, strike, upper); } else { double strike = cap.getStrike(); integralPartFwd = INTEGRATOR.integrate(integrantFwd, REL_TOL * strike, strike); } } catch (final Exception e) { throw new MathException(e); } integralPartFwd *= 2.0 * cap.getFixingAccrualFactor(); listForward.add( new SimplyCompoundedForwardSensitivity( capStandard.getFixingPeriodStartTime(), capStandard.getFixingPeriodEndTime(), capStandard.getFixingAccrualFactor(), (strikePartFwd + integralPartFwd - pv * betaFwd) / beta)); Map<String, List<ForwardSensitivity>> mapFwd = new HashMap<>(); mapFwd.put(curves.getName(capStandard.getIndex()), listForward); return MultipleCurrencyMulticurveSensitivity.of( cap.getCurrency(), MulticurveSensitivity.of(mapDsc, mapFwd)); }