/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.montecarlo.provider; import org.apache.commons.lang.Validate; import com.opengamma.analytics.financial.interestrate.InstrumentDerivativeVisitorAdapter; import com.opengamma.analytics.financial.interestrate.annuity.derivative.AnnuityCouponIborRatchet; import com.opengamma.analytics.financial.interestrate.payments.derivative.CapFloorIbor; import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponFixed; import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponIborGearing; import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponIborRatchet; import com.opengamma.analytics.financial.interestrate.swap.provider.SwapFixedCouponDiscountingMethod; import com.opengamma.analytics.financial.interestrate.swaption.derivative.SwaptionCashFixedIbor; import com.opengamma.analytics.financial.interestrate.swaption.derivative.SwaptionPhysicalFixedIbor; import com.opengamma.analytics.financial.montecarlo.MonteCarloIborRateDataBundle; /** * Computes the total instrument price over different paths (the sum of prices over the different paths, not its average). * The data bundle contains the different Ibor rates paths and the instrument reference amounts. The numeraire is the last time in the LMM description. */ public class MonteCarloIborRateCalculator extends InstrumentDerivativeVisitorAdapter<MonteCarloIborRateDataBundle, Double> { /** * The unique instance of the calculator. */ private static final MonteCarloIborRateCalculator INSTANCE = new MonteCarloIborRateCalculator(); /** * Gets the calculator instance. * @return The calculator. */ public static MonteCarloIborRateCalculator getInstance() { return INSTANCE; } /** * Constructor. */ MonteCarloIborRateCalculator() { } /** * The swap method. */ private static final SwapFixedCouponDiscountingMethod METHOD_SWAP = SwapFixedCouponDiscountingMethod.getInstance(); @Override public Double visitCapFloorIbor(final CapFloorIbor payment, final MonteCarloIborRateDataBundle mcResults) { Validate.isTrue(mcResults.getPathIborRate().length == 1, "Only one decision date for cap/floor."); final double[][] pathIborRate = mcResults.getPathIborRate()[0]; final double[] impactAmount = mcResults.getImpactAmount()[0]; final int[] impactIndex = mcResults.getImpactIndex()[0]; double price = 0; final int nbPath = pathIborRate[0].length; final int nbPeriod = pathIborRate.length; final double[] ibor = new double[nbPath]; double payoff; final double[] discounting = new double[nbPath]; final double omega = (payment.isCap() ? 1.0 : -1.0); for (int looppath = 0; looppath < nbPath; looppath++) { ibor[looppath] = impactAmount[0] * pathIborRate[impactIndex[0]][looppath] + (impactAmount[0] - 1.0) / payment.getFixingAccrualFactor(); // Ibor in the right convention; path in Dsc curve payoff = Math.max(omega * (ibor[looppath] - payment.getStrike()), 0); discounting[looppath] = 1.0; for (int loopdsc = impactIndex[2]; loopdsc < nbPeriod; loopdsc++) { discounting[looppath] *= (1.0 + pathIborRate[loopdsc][looppath] * mcResults.getDelta()[loopdsc]); } price += payoff * discounting[looppath]; } price *= payment.getNotional() * payment.getPaymentYearFraction(); return price; } @Override public Double visitSwaptionPhysicalFixedIbor(final SwaptionPhysicalFixedIbor swaption, final MonteCarloIborRateDataBundle mcResults) { final double[][] pathIborRate = mcResults.getPathIborRate()[0]; final double[] impactAmount = mcResults.getImpactAmount()[0]; final int[] impactIndex = mcResults.getImpactIndex()[0]; final int nbImpact = impactIndex.length; final int nbPath = pathIborRate[0].length; final int nbPeriod = pathIborRate.length; final double[][] discounting = new double[nbPath][nbPeriod + 1]; double price = 0.0; final double[] pricePath = new double[nbPath]; for (int looppath = 0; looppath < nbPath; looppath++) { discounting[looppath][nbPeriod] = 1.0; for (int loopdsc = nbPeriod - 1; loopdsc >= 0; loopdsc--) { discounting[looppath][loopdsc] = discounting[looppath][loopdsc + 1] * (1.0 + pathIborRate[loopdsc][looppath] * mcResults.getDelta()[loopdsc]); } for (int loopimpact = 0; loopimpact < nbImpact; loopimpact++) { pricePath[looppath] += impactAmount[impactIndex[loopimpact]] * discounting[looppath][impactIndex[loopimpact]]; } price += Math.max(pricePath[looppath], 0); } return price * (swaption.isLong() ? 1.0 : -1.0); } @Override public Double visitSwaptionCashFixedIbor(final SwaptionCashFixedIbor swaption, final MonteCarloIborRateDataBundle mcResults) { final double strike = swaption.getStrike(); final double[][] pathIborRate = mcResults.getPathIborRate()[0]; final double[] impactAmount = mcResults.getImpactAmount()[0]; final int[] impactIndex = mcResults.getImpactIndex()[0]; int nbFixed = 0; // The number of fixed coupons. while (impactIndex[nbFixed] < impactIndex[nbFixed + 1]) { nbFixed++; } nbFixed++; final int nbImpact = impactIndex.length; final int nbPath = pathIborRate[0].length; final int nbPeriod = pathIborRate.length; final double[][] discounting = new double[nbPath][nbPeriod + 1]; double price = 0.0; final double omega = (swaption.getUnderlyingSwap().getFixedLeg().isPayer() ? 1.0 : -1.0); for (int looppath = 0; looppath < nbPath; looppath++) { double fixedPath = 0.0; double floatPath = 0.0; double swapRatePath = 0.0; discounting[looppath][nbPeriod] = 1.0; for (int loopdsc = nbPeriod - 1; loopdsc >= 0; loopdsc--) { discounting[looppath][loopdsc] = discounting[looppath][loopdsc + 1] * (1.0 + pathIborRate[loopdsc][looppath] * mcResults.getDelta()[loopdsc]); } for (int loopfixed = 0; loopfixed < nbFixed; loopfixed++) { fixedPath += impactAmount[loopfixed] * discounting[looppath][impactIndex[loopfixed]]; } for (int loopfloat = nbFixed; loopfloat < nbImpact; loopfloat++) { floatPath += impactAmount[loopfloat] * discounting[looppath][impactIndex[loopfloat]]; } swapRatePath = -floatPath / fixedPath; final double annuityCashPath = METHOD_SWAP.getAnnuityCash(swaption.getUnderlyingSwap(), swapRatePath); price += annuityCashPath * Math.max(omega * (swapRatePath - strike), 0.0) * discounting[looppath][impactIndex[nbFixed]]; } return price * (swaption.isLong() ? 1.0 : -1.0); } @Override public Double visitAnnuityCouponIborRatchet(final AnnuityCouponIborRatchet annuity, final MonteCarloIborRateDataBundle mcResults) { final int nbCpn = annuity.getNumberOfPayments(); final double[][][] pathIborRate = mcResults.getPathIborRate(); // Size: nbJump x nbPeriodLMM x nbPath final int nbPath = pathIborRate[0][0].length; final int nbPeriod = pathIborRate[0].length; final double[][] impactAmount = mcResults.getImpactAmount(); // impact - amount final int[][] impactIndex = mcResults.getImpactIndex(); // impact - index final double[] delta = mcResults.getDelta(); // Discount factors final double[][][] discounting = new double[nbCpn][nbPeriod + 1][nbPath]; for (int loopcpn = 0; loopcpn < nbCpn; loopcpn++) { //nbCpn for (int looppath = 0; looppath < nbPath; looppath++) { discounting[loopcpn][nbPeriod][looppath] = 1.0; for (int loopdsc = nbPeriod - 1; loopdsc >= 0; loopdsc--) { discounting[loopcpn][loopdsc][looppath] = discounting[loopcpn][loopdsc + 1][looppath] * (1.0 + pathIborRate[loopcpn][loopdsc][looppath] * delta[loopdsc]); } } } // Coupons and annuity value final double[][] cpnRate = new double[nbCpn][nbPath]; final double[] annuityPathValue = new double[nbPath]; double ibor; for (int loopcpn = 0; loopcpn < nbCpn; loopcpn++) { //nbCpn if (annuity.isFixed()[loopcpn]) { // Coupon already fixed: only one cash flow final CouponFixed cpn = (CouponFixed) annuity.getNthPayment(loopcpn); for (int looppath = 0; looppath < nbPath; looppath++) { cpnRate[loopcpn][looppath] = cpn.getFixedRate(); annuityPathValue[looppath] += impactAmount[loopcpn][0] * discounting[loopcpn][impactIndex[loopcpn][0]][looppath]; } } else { if (annuity.getNthPayment(loopcpn) instanceof CouponIborRatchet) { final CouponIborRatchet cpn = (CouponIborRatchet) annuity.getNthPayment(loopcpn); for (int looppath = 0; looppath < nbPath; looppath++) { ibor = (-impactAmount[loopcpn][0] * discounting[loopcpn][impactIndex[loopcpn][0]][looppath] / (impactAmount[loopcpn][1] * discounting[loopcpn][impactIndex[loopcpn][1]][looppath]) - 1.0) / cpn.getFixingAccrualFactor(); final double cpnMain = cpn.getMainCoefficients()[0] * cpnRate[loopcpn - 1][looppath] + cpn.getMainCoefficients()[1] * ibor + cpn.getMainCoefficients()[2]; final double cpnFloor = cpn.getFloorCoefficients()[0] * cpnRate[loopcpn - 1][looppath] + cpn.getFloorCoefficients()[1] * ibor + cpn.getFloorCoefficients()[2]; final double cpnCap = cpn.getCapCoefficients()[0] * cpnRate[loopcpn - 1][looppath] + cpn.getCapCoefficients()[1] * ibor + cpn.getCapCoefficients()[2]; cpnRate[loopcpn][looppath] = Math.min(Math.max(cpnFloor, cpnMain), cpnCap); annuityPathValue[looppath] += cpnRate[loopcpn][looppath] * cpn.getPaymentYearFraction() * cpn.getNotional() * discounting[loopcpn][impactIndex[loopcpn][1]][looppath]; } } else { final CouponIborGearing cpn = (CouponIborGearing) annuity.getNthPayment(loopcpn); // Only possible for the first coupon for (int looppath = 0; looppath < nbPath; looppath++) { ibor = (-impactAmount[0][0] * discounting[loopcpn][impactIndex[loopcpn][0]][looppath] / (impactAmount[0][1] * discounting[loopcpn][impactIndex[loopcpn][1]][looppath]) - 1.0) / cpn.getFixingAccrualFactor(); cpnRate[loopcpn][looppath] = cpn.getFactor() * ibor + cpn.getSpread(); annuityPathValue[looppath] += cpnRate[loopcpn][looppath] * cpn.getPaymentYearFraction() * cpn.getNotional() * discounting[loopcpn][impactIndex[loopcpn][1]][looppath]; } } } } double price = 0.0; for (int looppath = 0; looppath < nbPath; looppath++) { price += annuityPathValue[looppath]; } return price; } }