/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.interestrate.payments.provider; import com.opengamma.analytics.financial.interestrate.annuity.derivative.AnnuityPaymentFixed; import com.opengamma.analytics.financial.interestrate.payments.derivative.CapFloorCMSSpread; import com.opengamma.analytics.financial.model.interestrate.G2ppPiecewiseConstantModel; import com.opengamma.analytics.financial.model.interestrate.definition.G2ppPiecewiseConstantParameters; import com.opengamma.analytics.financial.provider.calculator.discounting.CashFlowEquivalentCalculator; import com.opengamma.analytics.financial.provider.description.interestrate.G2ppProviderInterface; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderInterface; import com.opengamma.analytics.math.MathException; import com.opengamma.analytics.math.function.Function2D; import com.opengamma.analytics.math.integration.IntegratorRepeated2D; import com.opengamma.analytics.math.integration.RungeKuttaIntegrator1D; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.money.Currency; import com.opengamma.util.money.MultipleCurrencyAmount; /** * Method to compute the present value of CMS spread cap/floor with the G2++ model by numerical integration. */ public final class CapFloorCMSSpreadG2ppNumericalIntegrationMethod { /** * The method unique instance. */ private static final CapFloorCMSSpreadG2ppNumericalIntegrationMethod INSTANCE = new CapFloorCMSSpreadG2ppNumericalIntegrationMethod(); /** * Private constructor. */ private CapFloorCMSSpreadG2ppNumericalIntegrationMethod() { } /** * Return the unique instance of the class. * @return The instance. */ public static CapFloorCMSSpreadG2ppNumericalIntegrationMethod getInstance() { return INSTANCE; } /** * The model used in computations. */ private static final G2ppPiecewiseConstantModel MODEL_G2PP = new G2ppPiecewiseConstantModel(); /** * The cash flow equivalent calculator used in computations. */ private static final CashFlowEquivalentCalculator CFEC = CashFlowEquivalentCalculator.getInstance(); /** * Minimal number of integration steps. */ private static final int NB_INTEGRATION = 10; /** * Limits used in the numerical integration (the number of standard deviation from the mean). */ private static final double INTEGRATION_LIMIT = 10.0; /** * Computes the present value of a CMS spread by numerical integration. * @param cmsSpread The CMS spread cap/floor. * @param g2Data The curves and G2++ parameters. * @return The present value. */ public MultipleCurrencyAmount presentValue(final CapFloorCMSSpread cmsSpread, final G2ppProviderInterface g2Data) { ArgumentChecker.notNull(cmsSpread, "CMS spread"); ArgumentChecker.notNull(g2Data, "Yield curves and G2++ parameters"); final Currency ccy = cmsSpread.getCurrency(); ArgumentChecker.isTrue(g2Data.getG2ppCurrency().equals(ccy), "CMS spread currency incompatible with data"); final MulticurveProviderInterface multicurves = g2Data.getMulticurveProvider(); final G2ppPiecewiseConstantParameters parameters = g2Data.getG2ppParameters(); final double strike = cmsSpread.getStrike(); final double theta = cmsSpread.getFixingTime(); final double tp = cmsSpread.getPaymentTime(); final double dftp = multicurves.getDiscountFactor(ccy, tp); final int[] nbCfFixed = new int[] {cmsSpread.getUnderlyingSwap1().getFixedLeg().getNumberOfPayments(), cmsSpread.getUnderlyingSwap2().getFixedLeg().getNumberOfPayments()}; final double[][] tFixed = new double[2][]; final double[][] dfFixed = new double[2][]; final double[][] discountedCashFlowFixed = new double[2][]; final double[][] tIbor = new double[2][]; final double[][] dfIbor = new double[2][]; final double[][] discountedCashFlowIbor = new double[2][]; final AnnuityPaymentFixed[] cfeIbor = new AnnuityPaymentFixed[] {cmsSpread.getUnderlyingSwap1().getSecondLeg().accept(CFEC, multicurves), cmsSpread.getUnderlyingSwap2().getSecondLeg().accept(CFEC, multicurves)}; final int[] nbCfIbor = new int[] {cfeIbor[0].getNumberOfPayments(), cfeIbor[1].getNumberOfPayments()}; final double[] notionalSwap = new double[] {cmsSpread.getUnderlyingSwap1().getFixedLeg().getNthPayment(0).getNotional(), cmsSpread.getUnderlyingSwap2().getFixedLeg().getNthPayment(0).getNotional()}; // Swaps - Float for (int loopswap = 0; loopswap < 2; loopswap++) { tIbor[loopswap] = new double[nbCfIbor[loopswap]]; dfIbor[loopswap] = new double[nbCfIbor[loopswap]]; discountedCashFlowIbor[loopswap] = new double[nbCfIbor[loopswap]]; for (int loopcf = 0; loopcf < nbCfIbor[loopswap]; loopcf++) { tIbor[loopswap][loopcf] = cfeIbor[loopswap].getNthPayment(loopcf).getPaymentTime(); dfIbor[loopswap][loopcf] = multicurves.getDiscountFactor(ccy, tIbor[loopswap][loopcf]); discountedCashFlowIbor[loopswap][loopcf] = dfIbor[loopswap][loopcf] * cfeIbor[loopswap].getNthPayment(loopcf).getAmount() / notionalSwap[loopswap]; } tFixed[loopswap] = new double[nbCfFixed[loopswap]]; dfFixed[loopswap] = new double[nbCfFixed[loopswap]]; discountedCashFlowFixed[loopswap] = new double[nbCfFixed[loopswap]]; } // Swap - Fixed for (int loopcf = 0; loopcf < nbCfFixed[0]; loopcf++) { tFixed[0][loopcf] = cmsSpread.getUnderlyingSwap1().getFixedLeg().getNthPayment(loopcf).getPaymentTime(); dfFixed[0][loopcf] = multicurves.getDiscountFactor(ccy, tFixed[0][loopcf]); discountedCashFlowFixed[0][loopcf] = dfFixed[0][loopcf] * cmsSpread.getUnderlyingSwap1().getFixedLeg().getNthPayment(loopcf).getPaymentYearFraction(); } for (int loopcf = 0; loopcf < nbCfFixed[1]; loopcf++) { tFixed[1][loopcf] = cmsSpread.getUnderlyingSwap2().getFixedLeg().getNthPayment(loopcf).getPaymentTime(); dfFixed[1][loopcf] = multicurves.getDiscountFactor(ccy, tFixed[1][loopcf]); discountedCashFlowFixed[1][loopcf] = dfFixed[1][loopcf] * cmsSpread.getUnderlyingSwap2().getFixedLeg().getNthPayment(loopcf).getPaymentYearFraction(); } // Model parameters final double rhog2pp = g2Data.getG2ppParameters().getCorrelation(); final double[][] gamma = MODEL_G2PP.gamma(parameters, 0, theta); final double rhobar = rhog2pp * gamma[0][1] / Math.sqrt(gamma[0][0] * gamma[1][1]); final double[][][] alphaFixed = new double[2][][]; final double[][] tau2Fixed = new double[2][]; final double[][][] alphaIbor = new double[2][][]; final double[][] tau2Ibor = new double[2][]; for (int loopswap = 0; loopswap < 2; loopswap++) { final double[][] hthetaFixed = MODEL_G2PP.volatilityMaturityPart(parameters, theta, tFixed[loopswap]); alphaFixed[loopswap] = new double[nbCfFixed[loopswap]][2]; tau2Fixed[loopswap] = new double[nbCfFixed[loopswap]]; for (int loopcf = 0; loopcf < nbCfFixed[loopswap]; loopcf++) { alphaFixed[loopswap][loopcf][0] = Math.sqrt(gamma[0][0]) * hthetaFixed[0][loopcf]; alphaFixed[loopswap][loopcf][1] = Math.sqrt(gamma[1][1]) * hthetaFixed[1][loopcf]; tau2Fixed[loopswap][loopcf] = alphaFixed[loopswap][loopcf][0] * alphaFixed[loopswap][loopcf][0] + alphaFixed[loopswap][loopcf][1] * alphaFixed[loopswap][loopcf][1] + 2 * rhog2pp * gamma[0][1] * hthetaFixed[0][loopcf] * hthetaFixed[1][loopcf]; } final double[][] hthetaIbor = MODEL_G2PP.volatilityMaturityPart(parameters, theta, tIbor[loopswap]); alphaIbor[loopswap] = new double[nbCfIbor[loopswap]][2]; tau2Ibor[loopswap] = new double[nbCfIbor[loopswap]]; for (int loopcf = 0; loopcf < nbCfIbor[loopswap]; loopcf++) { alphaIbor[loopswap][loopcf][0] = Math.sqrt(gamma[0][0]) * hthetaIbor[0][loopcf]; alphaIbor[loopswap][loopcf][1] = Math.sqrt(gamma[1][1]) * hthetaIbor[1][loopcf]; tau2Ibor[loopswap][loopcf] = alphaIbor[loopswap][loopcf][0] * alphaIbor[loopswap][loopcf][0] + alphaIbor[loopswap][loopcf][1] * alphaIbor[loopswap][loopcf][1] + 2 * rhog2pp * gamma[0][1] * hthetaIbor[0][loopcf] * hthetaIbor[1][loopcf]; } } final double[] hthetaTp = MODEL_G2PP.volatilityMaturityPart(parameters, theta, tp); final double[] alphaTp = new double[] {Math.sqrt(gamma[0][0]) * hthetaTp[0], Math.sqrt(gamma[1][1]) * hthetaTp[1]}; final double tau2Tp = alphaTp[0] * alphaTp[0] + alphaTp[1] * alphaTp[1] + 2 * rhog2pp * gamma[0][1] * hthetaTp[0] * hthetaTp[1]; // Integration final SpreadIntegrant integrant = new SpreadIntegrant(discountedCashFlowFixed, alphaFixed, tau2Fixed, discountedCashFlowIbor, alphaIbor, tau2Ibor, alphaTp, tau2Tp, rhobar, strike, cmsSpread.isCap()); final double absoluteTolerance = 1.0E-6; // 1.0E-5; final double relativeTolerance = 1.0E-6; // 1.0E-5 final RungeKuttaIntegrator1D integrator1D = new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, NB_INTEGRATION); final IntegratorRepeated2D integrator2D = new IntegratorRepeated2D(integrator1D); double pv = 0.0; try { pv = 1.0 / (2.0 * Math.PI * Math.sqrt(1 - rhobar * rhobar)) * integrator2D.integrate(integrant, new Double[] {-INTEGRATION_LIMIT, -INTEGRATION_LIMIT}, new Double[] {INTEGRATION_LIMIT, INTEGRATION_LIMIT}); } catch (final Exception e) { throw new MathException(e); } return MultipleCurrencyAmount.of(cmsSpread.getCurrency(), dftp * pv * cmsSpread.getNotional() * cmsSpread.getPaymentYearFraction()); } /** * Inner class to implement the integration used in price replication. */ private static final class SpreadIntegrant extends Function2D<Double, Double> { private final double[][] _discountedCashFlowFixed; private final double[][][] _alphaFixed; private final double[][] _tau2Fixed; private final double[][] _discountedCashFlowIbor; private final double[][][] _alphaIbor; private final double[][] _tau2Ibor; private final double[] _alphaTp; private final double _tau2Tp; private final double _rhobar; private final double _strike; private final double _omega; public SpreadIntegrant(final double[][] discountedCashFlowFixed, final double[][][] alphaFixed, final double[][] tau2Fixed, final double[][] discountedCashFlowIbor, final double[][][] alphaIbor, final double[][] tau2Ibor, final double[] alphaTp, final double tau2Tp, final double rhobar, final double strike, final boolean isCall) { _discountedCashFlowFixed = discountedCashFlowFixed; _alphaFixed = alphaFixed; _tau2Fixed = tau2Fixed; _discountedCashFlowIbor = discountedCashFlowIbor; _alphaIbor = alphaIbor; _tau2Ibor = tau2Ibor; _alphaTp = alphaTp; _tau2Tp = tau2Tp; _rhobar = rhobar; _strike = strike; _omega = (isCall ? 1.0 : -1.0); } @SuppressWarnings("synthetic-access") @Override public Double evaluate(final Double x0, final Double x1) { final double[] rate = new double[2]; final double[] x = new double[] {x0, x1}; rate[0] = MODEL_G2PP.swapRate(x, _discountedCashFlowFixed[0], _alphaFixed[0], _tau2Fixed[0], _discountedCashFlowIbor[0], _alphaIbor[0], _tau2Ibor[0]); rate[1] = MODEL_G2PP.swapRate(x, _discountedCashFlowFixed[1], _alphaFixed[1], _tau2Fixed[1], _discountedCashFlowIbor[1], _alphaIbor[1], _tau2Ibor[1]); final double densityPart = -(x0 * x0 + x1 * x1 - 2 * _rhobar * x0 * x1) / (2.0 * (1 - _rhobar * _rhobar)); final double discounting = Math.exp(-_alphaTp[0] * x0 - _alphaTp[1] * x1 - _tau2Tp / 2.0 + densityPart); return discounting * Math.max(_omega * (rate[0] - rate[1] - _strike), 0.0); } } }