/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.interestrate; import org.threeten.bp.LocalDate; import org.threeten.bp.ZonedDateTime; import com.opengamma.analytics.financial.instrument.annuity.AnnuityCouponFixedDefinition; import com.opengamma.analytics.financial.instrument.annuity.AnnuityCouponIborDefinition; import com.opengamma.analytics.financial.instrument.annuity.AnnuityDefinition; import com.opengamma.analytics.financial.instrument.payment.CouponDefinition; import com.opengamma.analytics.financial.instrument.payment.CouponFixedDefinition; import com.opengamma.analytics.financial.instrument.payment.CouponIborDefinition; import com.opengamma.analytics.financial.instrument.swap.SwapFixedIborDefinition; import com.opengamma.analytics.financial.interestrate.annuity.derivative.Annuity; import com.opengamma.analytics.financial.interestrate.annuity.derivative.AnnuityCouponFixed; import com.opengamma.analytics.financial.interestrate.payments.derivative.Coupon; import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponFixed; import com.opengamma.analytics.financial.interestrate.swap.derivative.SwapFixedCoupon; import com.opengamma.analytics.financial.interestrate.swap.provider.SwapFixedCouponDiscountingMethod; import com.opengamma.analytics.financial.provider.calculator.discounting.ParRateDiscountingCalculator; import com.opengamma.analytics.financial.provider.calculator.discounting.PresentValueDiscountingCalculator; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount; import com.opengamma.financial.convention.calendar.Calendar; import com.opengamma.financial.convention.daycount.DayCount; import com.opengamma.timeseries.precise.zdt.ZonedDateTimeDoubleTimeSeries; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.money.MultipleCurrencyAmount; /** * Computes par rate (excluding accrued interests) and accrued interest. <br> * <b>Use {@link ParRateDiscountingCalculator} for standard definition of par rate.<b> */ public class SwapCleanDiscountingCalculator { private static final PresentValueDiscountingCalculator PVDC = PresentValueDiscountingCalculator.getInstance(); private static final SwapFixedCouponDiscountingMethod METHOD_SWAP = SwapFixedCouponDiscountingMethod.getInstance(); /** * Computes par rate excluding accrued interest. * @param swapDefinition Fixed vs Ibor swap definition * @param fixedLegDayCount Day count for fixed leg * @param iborLegDayCount Day count for Ibor leg * @param calendar The calendar * @param valuationDate The valuation date * @param indexTimeSeries Index fixing time series * @param multicurves The multi-curve * @return The par rate */ public Double parRate(SwapFixedIborDefinition swapDefinition, DayCount fixedLegDayCount, DayCount iborLegDayCount, Calendar calendar, ZonedDateTime valuationDate, ZonedDateTimeDoubleTimeSeries indexTimeSeries, MulticurveProviderDiscount multicurves) { ArgumentChecker.notNull(swapDefinition, "swapDefinition"); ArgumentChecker.notNull(fixedLegDayCount, "fixedLegDayCount"); ArgumentChecker.notNull(iborLegDayCount, "iborLegDayCount"); ArgumentChecker.notNull(calendar, "calendar"); ArgumentChecker.notNull(valuationDate, "valuationDate"); ArgumentChecker.notNull(indexTimeSeries, "indexTimeSeries"); ArgumentChecker.notNull(multicurves, "multicurves"); checkNotionalAndFixedRate(swapDefinition); Annuity<? extends Coupon> iborLeg = swapDefinition.getIborLeg().toDerivative(valuationDate, indexTimeSeries); double dirtyIborLegPV = iborLeg.accept(PVDC, multicurves).getAmount(iborLeg.getCurrency()); double iborLegAccruedInterest = getAccrued(iborLegDayCount, calendar, valuationDate, swapDefinition.getIborLeg(), indexTimeSeries); double cleanFloatingPV = (dirtyIborLegPV - iborLegAccruedInterest) * Math.signum(iborLeg.getNthPayment(0).getNotional()); AnnuityCouponFixed fixedLeg = swapDefinition.getFixedLeg().toDerivative(valuationDate); double accruedAmount = getAccrued(fixedLegDayCount, calendar, valuationDate, swapDefinition.getFixedLeg(), indexTimeSeries) * Math.signum(fixedLeg.getNthPayment(0).getNotional()); double dirtyAnnuity = METHOD_SWAP.presentValueBasisPoint(new SwapFixedCoupon<>(fixedLeg, iborLeg), multicurves); double cleanAnnuity = dirtyAnnuity - accruedAmount; return cleanFloatingPV / cleanAnnuity; } /** * Computes accrued interest * @param swapDefinition Fixed vs Ibor swap definition * @param fixedLegDayCount Day count for fixed leg * @param iborLegDayCount Day count for Ibor leg * @param calendar The calendar * @param valuationDate The valuation date * @param indexTimeSeries Index fixing time series * @param multicurves The multi-curve * @return The accrued interest */ public MultipleCurrencyAmount accruedInterest(SwapFixedIborDefinition swapDefinition, DayCount fixedLegDayCount, DayCount iborLegDayCount, Calendar calendar, ZonedDateTime valuationDate, ZonedDateTimeDoubleTimeSeries indexTimeSeries, MulticurveProviderDiscount multicurves) { ArgumentChecker.notNull(swapDefinition, "swapDefinition"); ArgumentChecker.notNull(fixedLegDayCount, "fixedLegDayCount"); ArgumentChecker.notNull(iborLegDayCount, "iborLegDayCount"); ArgumentChecker.notNull(calendar, "calendar"); ArgumentChecker.notNull(valuationDate, "valuationDate"); ArgumentChecker.notNull(indexTimeSeries, "indexTimeSeries"); ArgumentChecker.notNull(multicurves, "multicurves"); checkNotionalAndFixedRate(swapDefinition); double iborLegAccruedInterest = getAccrued(iborLegDayCount, calendar, valuationDate, swapDefinition.getIborLeg(), indexTimeSeries); CouponFixedDefinition refFixed = swapDefinition.getFixedLeg().getNthPayment(0); double fixedLegAccruedInterest = getAccrued(fixedLegDayCount, calendar, valuationDate, swapDefinition.getFixedLeg(), indexTimeSeries) * refFixed.getRate(); return MultipleCurrencyAmount.of(swapDefinition.getCurrency(), iborLegAccruedInterest + fixedLegAccruedInterest); } private double getAccrued(DayCount dayCount, Calendar calendar, ZonedDateTime valuationDate, AnnuityDefinition<? extends CouponDefinition> annuity, ZonedDateTimeDoubleTimeSeries indexTimeSeries) { LocalDate date = valuationDate.toLocalDate(); double res = 0.0; CouponDefinition[] payments = annuity.getPayments(); for (CouponDefinition payment : payments) { if (payment.getAccrualStartDate().toLocalDate().isBefore(date) && !payment.getPaymentDate().toLocalDate().isBefore(date)) { double rate; if (payment instanceof CouponIborDefinition) { CouponIborDefinition casted = (CouponIborDefinition) payment; Coupon coupon = casted.toDerivative(valuationDate, indexTimeSeries); ArgumentChecker.isTrue(coupon instanceof CouponFixed, "index should be fixed before accrual starts for standard vanilla swap"); CouponFixed couponFixed = (CouponFixed) coupon; rate = couponFixed.getFixedRate(); } else if (payment instanceof CouponFixedDefinition) { rate = 1.0; } else { throw new IllegalArgumentException("This annuity type is not supported"); } res += getAccrued(dayCount, calendar, valuationDate, payment) * rate; } } return res; } private double getAccrued(DayCount dayCount, Calendar calendar, ZonedDateTime valuationDate, CouponDefinition coupon) { double accruedYearFraction = dayCount.getDayCountFraction(coupon.getAccrualStartDate(), valuationDate, calendar); return accruedYearFraction * coupon.getNotional(); } private void checkNotionalAndFixedRate(SwapFixedIborDefinition swapDefinition) { AnnuityCouponIborDefinition iborLeg = swapDefinition.getIborLeg(); int nIbor = iborLeg.getNumberOfPayments(); double notioanl = iborLeg.getNthPayment(0).getNotional(); for (int i = 1; i < nIbor; ++i) { ArgumentChecker.isTrue(notioanl == iborLeg.getNthPayment(i).getNotional(), "Notional should be constant in both the legs"); } AnnuityCouponFixedDefinition fixedLeg = swapDefinition.getFixedLeg(); int nFixed = fixedLeg.getNumberOfPayments(); double rate = fixedLeg.getNthPayment(0).getRate(); notioanl *= -1.0; // payer/receiver conversion ArgumentChecker.isTrue(notioanl == fixedLeg.getNthPayment(0).getNotional(), "Notional should be constant in both the legs"); for (int i = 1; i < nFixed; ++i) { ArgumentChecker.isTrue(rate == fixedLeg.getNthPayment(i).getRate(), "Fixed rate should be constant"); ArgumentChecker.isTrue(notioanl == fixedLeg.getNthPayment(i).getNotional(), "Notional should be constant in both the legs"); } } }