/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.analytics.conversion; import java.util.List; import org.threeten.bp.LocalDate; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.analytics.financial.instrument.NotionalProvider; import com.opengamma.analytics.financial.instrument.annuity.AbstractAnnuityDefinitionBuilder.CouponStub; import com.opengamma.analytics.financial.instrument.annuity.AdjustedDateParameters; import com.opengamma.analytics.financial.instrument.annuity.AnnuityDefinition; import com.opengamma.analytics.financial.instrument.annuity.CompoundingMethod; import com.opengamma.analytics.financial.instrument.annuity.FixedAnnuityDefinitionBuilder; import com.opengamma.analytics.financial.instrument.annuity.FloatingAnnuityDefinitionBuilder; import com.opengamma.analytics.financial.instrument.annuity.OffsetAdjustedDateParameters; import com.opengamma.analytics.financial.instrument.annuity.OffsetType; import com.opengamma.analytics.financial.instrument.index.IborIndex; import com.opengamma.analytics.financial.instrument.index.IndexDeposit; import com.opengamma.core.convention.Convention; import com.opengamma.core.convention.ConventionSource; import com.opengamma.core.holiday.HolidaySource; import com.opengamma.core.security.Security; import com.opengamma.core.security.SecuritySource; import com.opengamma.financial.analytics.curve.ConverterUtils; import com.opengamma.financial.convention.HolidaySourceCalendarAdapter; import com.opengamma.financial.convention.IborIndexConvention; import com.opengamma.financial.convention.OvernightIndexConvention; import com.opengamma.financial.convention.StubType; import com.opengamma.financial.convention.VanillaIborLegConvention; import com.opengamma.financial.convention.businessday.BusinessDayConvention; import com.opengamma.financial.convention.calendar.Calendar; import com.opengamma.financial.convention.rolldate.EndOfMonthRollDateAdjuster; import com.opengamma.financial.convention.rolldate.RollConvention; import com.opengamma.financial.convention.rolldate.RollDateAdjuster; import com.opengamma.financial.security.index.OvernightIndex; import com.opengamma.financial.security.irs.FixedInterestRateSwapLeg; import com.opengamma.financial.security.irs.FloatingInterestRateSwapLeg; import com.opengamma.financial.security.irs.InterestRateSwapLeg; import com.opengamma.financial.security.irs.InterestRateSwapNotional; import com.opengamma.financial.security.irs.InterestRateSwapNotionalVisitor; import com.opengamma.financial.security.irs.NotionalExchange; import com.opengamma.financial.security.irs.StubCalculationMethod; import com.opengamma.financial.security.lookup.irs.InterestRateSwapNotionalAmountVisitor; import com.opengamma.financial.security.swap.FloatingRateType; import com.opengamma.id.ExternalId; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.tuple.Pair; import com.opengamma.util.tuple.Pairs; /** * Class containing utility methods for generating annuities from swap legs. */ public class AnnuityUtils { /** * Builds a floating annuity definition from an {@link InterestRateSwapLeg}. * @param conventionSource The convention source, not null * @param holidaySource The holiday source, not null * @param securitySource The security source, not null * @param payer True if the swap is a payer (i.e. pays a fixed rate) * @param startDate The start date, not null * @param endDate The end date, not null * @param notionalExchange The type of notional exchange, not null * @param leg The interest rate swap leg, not null * @return A floating annuity definition. The coupons are not necessarily the same type. */ public static AnnuityDefinition<?> buildFloatingAnnuityDefinition(final ConventionSource conventionSource, final HolidaySource holidaySource, SecuritySource securitySource, final boolean payer, final LocalDate startDate, final LocalDate endDate, final NotionalExchange notionalExchange, final InterestRateSwapLeg leg) { ArgumentChecker.notNull(conventionSource, "conventionSource"); ArgumentChecker.notNull(holidaySource, "holidaySource"); ArgumentChecker.notNull(securitySource, "securitySource"); ArgumentChecker.notNull(startDate, "startDate"); ArgumentChecker.notNull(endDate, "endDate"); ArgumentChecker.notNull(notionalExchange, "notionalExchange"); ArgumentChecker.notNull(leg, "leg"); final FloatingInterestRateSwapLeg floatLeg = (FloatingInterestRateSwapLeg) leg; AdjustedDateParameters maturityDateParameters = null; if (leg.getMaturityDateCalendars() != null && leg.getMaturityDateBusinessDayConvention() != null) { final Calendar maturityDateCalendar = new HolidaySourceCalendarAdapter(holidaySource, leg.getMaturityDateCalendars().toArray(new ExternalId[0])); maturityDateParameters = new AdjustedDateParameters(maturityDateCalendar, leg.getMaturityDateBusinessDayConvention()); } AdjustedDateParameters accrualPeriodParameters = null; if (leg.getAccrualPeriodCalendars() != null && leg.getAccrualPeriodBusinessDayConvention() != null) { final Calendar accrualPeriodCalendar = new HolidaySourceCalendarAdapter(holidaySource, leg.getAccrualPeriodCalendars().toArray(new ExternalId[0])); accrualPeriodParameters = new AdjustedDateParameters(accrualPeriodCalendar, leg.getAccrualPeriodBusinessDayConvention()); } RollDateAdjuster rollDateAdjuster = null; if (leg.getRollConvention() == RollConvention.EOM) { rollDateAdjuster = EndOfMonthRollDateAdjuster.getAdjuster(); } else { rollDateAdjuster = leg.getRollConvention().getRollDateAdjuster(0); } AdjustedDateParameters resetDateParameters = null; if (floatLeg.getResetPeriodCalendars() != null && floatLeg.getResetPeriodBusinessDayConvention() != null) { final Calendar resetCalendar = new HolidaySourceCalendarAdapter(holidaySource, floatLeg.getResetPeriodCalendars().toArray(new ExternalId[0])); resetDateParameters = new AdjustedDateParameters(resetCalendar, floatLeg.getResetPeriodBusinessDayConvention()); } double spread = Double.NaN; if (floatLeg.getSpreadSchedule() != null) { spread = floatLeg.getSpreadSchedule().getInitialRate(); } final int paymentOffset = floatLeg.getPaymentOffset(); final IndexDeposit index; final Security sec = securitySource.getSingle(floatLeg.getFloatingReferenceRateId().toBundle()); if (sec == null) { throw new OpenGammaRuntimeException("Failed to resolve security for " + floatLeg.getFloatingReferenceRateId().toBundle()); } CompoundingMethod compoundingMethod; if (FloatingRateType.IBOR == floatLeg.getFloatingRateType()) { com.opengamma.financial.security.index.IborIndex indexSecurity = (com.opengamma.financial.security.index.IborIndex) sec; IborIndexConvention indexConvention = conventionSource.getSingle(indexSecurity.getConventionId(), IborIndexConvention.class); index = ConverterUtils.indexIbor(indexSecurity.getName(), indexConvention, indexSecurity.getTenor()); /* The compounding method is only relevant for Ibor */ switch (floatLeg.getCompoundingMethod()) { case FLAT: compoundingMethod = CompoundingMethod.FLAT; break; case STRAIGHT: compoundingMethod = CompoundingMethod.STRAIGHT; break; case SPREAD_EXCLUSIVE: compoundingMethod = CompoundingMethod.SPREAD_EXCLUSIVE; break; case NONE: compoundingMethod = null; break; default: throw new OpenGammaRuntimeException("Unsupported compounding method"); } } else if (FloatingRateType.OIS == floatLeg.getFloatingRateType()) { OvernightIndex indexSecurity = (OvernightIndex) sec; OvernightIndexConvention indexConvention = conventionSource.getSingle(indexSecurity.getConventionId(), OvernightIndexConvention.class); index = ConverterUtils.indexON(indexSecurity.getName(), indexConvention); // TODO PLAT-6729 compounding is incorrectly set here to distinguish between ON Arithmetic Average // when used in the FloatingAnnuityDefinitionBuilder compoundingMethod = CompoundingMethod.FLAT; } else if (FloatingRateType.OVERNIGHT_ARITHMETIC_AVERAGE == floatLeg.getFloatingRateType()) { OvernightIndex indexSecurity = (OvernightIndex) sec; OvernightIndexConvention indexConvention = conventionSource.getSingle(indexSecurity.getConventionId(), OvernightIndexConvention.class); index = ConverterUtils.indexON(indexSecurity.getName(), indexConvention); // TODO PLAT-6729 compounding is incorrectly set here to distinguish between OIS // when used in the FloatingAnnuityDefinitionBuilder compoundingMethod = null; } else { throw new OpenGammaRuntimeException("Unsupported floating rate type " + floatLeg.getFloatingRateType()); } OffsetAdjustedDateParameters paymentDateParameters = null; if (leg.getPaymentDateCalendars() != null && leg.getPaymentDateBusinessDayConvention() != null) { final Calendar paymentDateCalendar = new HolidaySourceCalendarAdapter(holidaySource, leg.getPaymentDateCalendars().toArray(new ExternalId[leg.getPaymentDateCalendars().size()])); paymentDateParameters = new OffsetAdjustedDateParameters(paymentOffset, OffsetType.BUSINESS, paymentDateCalendar, leg.getPaymentDateBusinessDayConvention()); } OffsetAdjustedDateParameters fixingDateParameters = null; if (floatLeg.getFixingDateCalendars() != null && floatLeg.getFixingDateBusinessDayConvention() != null) { final Calendar fixingDateCalendar = new HolidaySourceCalendarAdapter(holidaySource, floatLeg.getFixingDateCalendars().toArray(new ExternalId[floatLeg.getFixingDateCalendars().size()])); fixingDateParameters = new OffsetAdjustedDateParameters(floatLeg.getFixingDateOffset(), floatLeg.getFixingDateOffsetType(), fixingDateCalendar, floatLeg.getFixingDateBusinessDayConvention()); } CouponStub startStub = null; CouponStub endStub = null; StubCalculationMethod stubCalcMethod = leg.getStubCalculationMethod(); if (stubCalcMethod != null) { stubCalcMethod.validate(); final Pair<CouponStub, CouponStub> stubs = parseStubs(leg.getStubCalculationMethod(), securitySource, conventionSource); startStub = stubs.getFirst(); endStub = stubs.getSecond(); } final List<Double> notionalList = leg.getNotional().getNotionals(); double[] notionalSchedule; if (notionalList.isEmpty()) { notionalSchedule = new double[] {(payer ? -1 : 1) * leg.getNotional().getInitialAmount()}; } else { notionalSchedule = new double[notionalList.size()]; for (int i = 0; i < notionalSchedule.length; i++) { notionalSchedule[i] = (payer ? -1 : 1) * notionalList.get(i); } } return new FloatingAnnuityDefinitionBuilder(). payer(payer). currency(leg.getNotional().getCurrency()). notional(getNotionalProvider(leg.getNotional(), leg.getAccrualPeriodBusinessDayConvention(), new HolidaySourceCalendarAdapter(holidaySource, leg.getAccrualPeriodCalendars().toArray(new ExternalId[leg.getAccrualPeriodCalendars().size()])))). startDate(startDate). endDate(endDate). endDateAdjustmentParameters(maturityDateParameters). startDateAdjustmentParameters(accrualPeriodParameters). dayCount(leg.getDayCountConvention()). rollDateAdjuster(rollDateAdjuster). exchangeInitialNotional(notionalExchange.isExchangeInitialNotional()). exchangeFinalNotional(notionalExchange.isExchangeFinalNotional()). accrualPeriodFrequency(ConversionUtils.getTenor(leg.getPaymentDateFrequency())). accrualPeriodParameters(accrualPeriodParameters). paymentDateRelativeTo(leg.getPaymentDateRelativeTo()). paymentDateAdjustmentParameters(paymentDateParameters). spread(spread). index(index). resetDateAdjustmentParameters(resetDateParameters). resetRelativeTo(floatLeg.getResetDateRelativeTo()). fixingDateAdjustmentParameters(fixingDateParameters). compoundingMethod(compoundingMethod). startStub(startStub). endStub(endStub). initialRate(floatLeg.getCustomRates() != null ? floatLeg.getCustomRates().getInitialRate() : Double.NaN). build(); } /** * Builds a fixed annuity definition from an {@link InterestRateSwapLeg}. * @param securitySource The security source, not null * @param holidaySource The holiday source, not null * @param conventionSource The convention source, not null * @param payer True if the swap is a payer (i.e. pays a fixed rate) * @param startDate The start date, not null * @param endDate The end date, not null * @param notionalExchange The type of notional exchange, not null * @param leg The interest rate swap leg, not null * @return A fixed annuity definition. The coupons are not necessarily the same type. */ public static AnnuityDefinition<?> buildFixedAnnuityDefinition(final SecuritySource securitySource, final HolidaySource holidaySource, final ConventionSource conventionSource, final boolean payer, final LocalDate startDate, final LocalDate endDate, final NotionalExchange notionalExchange, final InterestRateSwapLeg leg) { ArgumentChecker.notNull(securitySource, "securitySource"); ArgumentChecker.notNull(holidaySource, "holidaySource"); ArgumentChecker.notNull(conventionSource, "conventionSource"); ArgumentChecker.notNull(startDate, "startDate"); ArgumentChecker.notNull(endDate, "endDate"); ArgumentChecker.notNull(notionalExchange, "notionalExchange"); ArgumentChecker.notNull(leg, "leg"); final FixedInterestRateSwapLeg fixedLeg = (FixedInterestRateSwapLeg) leg; AdjustedDateParameters maturityDateParameters = null; if (leg.getMaturityDateCalendars() != null && leg.getMaturityDateBusinessDayConvention() != null) { final Calendar maturityDateCalendar = new HolidaySourceCalendarAdapter(holidaySource, leg.getMaturityDateCalendars().toArray(new ExternalId[leg.getMaturityDateCalendars().size()])); maturityDateParameters = new AdjustedDateParameters(maturityDateCalendar, leg.getMaturityDateBusinessDayConvention()); } AdjustedDateParameters accrualPeriodParameters = null; if (leg.getAccrualPeriodCalendars() != null && leg.getAccrualPeriodBusinessDayConvention() != null) { final Calendar accrualPeriodCalendar = new HolidaySourceCalendarAdapter(holidaySource, leg.getAccrualPeriodCalendars().toArray(new ExternalId[leg.getAccrualPeriodCalendars().size()])); accrualPeriodParameters = new AdjustedDateParameters(accrualPeriodCalendar, leg.getAccrualPeriodBusinessDayConvention()); } OffsetAdjustedDateParameters paymentDateParameters = null; if (leg.getPaymentDateCalendars() != null && leg.getPaymentDateBusinessDayConvention() != null) { final Calendar paymentDateCalendar = new HolidaySourceCalendarAdapter(holidaySource, leg.getPaymentDateCalendars().toArray(new ExternalId[leg.getPaymentDateCalendars().size()])); paymentDateParameters = new OffsetAdjustedDateParameters( leg.getPaymentOffset(), OffsetType.BUSINESS, paymentDateCalendar, leg.getPaymentDateBusinessDayConvention()); } RollDateAdjuster rollDateAdjuster = null; if (leg.getRollConvention() == RollConvention.EOM) { rollDateAdjuster = EndOfMonthRollDateAdjuster.getAdjuster(); } else { rollDateAdjuster = leg.getRollConvention().getRollDateAdjuster(0); } CouponStub startStub = null; CouponStub endStub = null; StubCalculationMethod stubCalcMethod = leg.getStubCalculationMethod(); if (stubCalcMethod != null) { stubCalcMethod.validate(); final Pair<CouponStub, CouponStub> stubs = parseStubs(leg.getStubCalculationMethod(), securitySource, conventionSource); startStub = stubs.getFirst(); endStub = stubs.getSecond(); } final List<Double> notionalList = leg.getNotional().getNotionals(); double[] notionalSchedule; if (notionalList.isEmpty()) { notionalSchedule = new double[] {(payer ? -1 : 1) * leg.getNotional().getInitialAmount()}; } else { notionalSchedule = new double[notionalList.size()]; for (int i = 0; i < notionalSchedule.length; i++) { notionalSchedule[i] = (payer ? -1 : 1) * notionalList.get(i); } } return new FixedAnnuityDefinitionBuilder(). payer(payer). currency(leg.getNotional().getCurrency()). notional(getNotionalProvider(leg.getNotional(), leg.getAccrualPeriodBusinessDayConvention(), new HolidaySourceCalendarAdapter(holidaySource, leg.getAccrualPeriodCalendars().toArray(new ExternalId[leg.getAccrualPeriodCalendars().size()])))). startDate(startDate). endDate(endDate). endDateAdjustmentParameters(maturityDateParameters). dayCount(leg.getDayCountConvention()). rollDateAdjuster(rollDateAdjuster). exchangeInitialNotional(notionalExchange.isExchangeInitialNotional()). exchangeFinalNotional(notionalExchange.isExchangeFinalNotional()). accrualPeriodFrequency(ConversionUtils.getTenor(leg.getPaymentDateFrequency())). accrualPeriodParameters(accrualPeriodParameters). paymentDateRelativeTo(leg.getPaymentDateRelativeTo()). paymentDateAdjustmentParameters(paymentDateParameters). rate(fixedLeg.getRate().getInitialRate()). startStub(startStub). endStub(endStub). build(); } /** * Converts the {@link StubCalculationMethod} to a {@link CouponStub}. * @param stubCalcMethod The stub calculation method, not null * @param securitySource The security source to perform a lookup of indices, not null * @param conventionSource The convention source to perform a lookup of conventions for backwards compatibility, not null * @return A pair of front and back stubs */ public static Pair<CouponStub, CouponStub> parseStubs(final StubCalculationMethod stubCalcMethod, SecuritySource securitySource, ConventionSource conventionSource) { //Walk the stub to determine the details; when done - build a CouponStub with those details CouponStub startStub = null; CouponStub endStub = null; if (stubCalcMethod != null) { stubCalcMethod.validate(); StubType stubType = stubCalcMethod.getType(); // first stub double firstStubRate = stubCalcMethod.hasFirstStubRate() ? stubCalcMethod.getFirstStubRate() : Double.NaN; LocalDate firstStubDate = stubCalcMethod.getFirstStubEndDate(); ExternalId firstStubStartReferenceRateId = stubCalcMethod.getFirstStubStartReferenceRateId(); IborIndex firstStubStartIndex = null; if (stubCalcMethod.hasFirstStubStartReferenceRateId()) { firstStubStartIndex = (IborIndex) getIborIndex(firstStubStartReferenceRateId, securitySource, conventionSource); } ExternalId firstStubEndReferenceRateId = stubCalcMethod.getFirstStubEndReferenceRateId(); IborIndex firstStubEndIndex = null; if (stubCalcMethod.hasFirstStubEndReferenceRateId()) { firstStubEndIndex = (IborIndex) getIborIndex(firstStubEndReferenceRateId, securitySource, conventionSource); } // last stub double finalStubRate = stubCalcMethod.hasLastStubRate() ? stubCalcMethod.getLastStubRate() : Double.NaN; LocalDate finalStubDate = stubCalcMethod.getLastStubEndDate(); ExternalId lastStubStartReferenceRateId = stubCalcMethod.getLastStubStartReferenceRateId(); IborIndex lastStubStartIndex = null; if (stubCalcMethod.hasLastStubStartReferenceRateId()) { lastStubStartIndex = (IborIndex) getIborIndex(lastStubStartReferenceRateId, securitySource, conventionSource); } ExternalId lastStubEndReferenceRateId = stubCalcMethod.getLastStubEndReferenceRateId(); IborIndex lastStubEndIndex = null; if (stubCalcMethod.hasLastStubEndReferenceRateId()) { lastStubEndIndex = (IborIndex) getIborIndex(lastStubEndReferenceRateId, securitySource, conventionSource); } //Determine the stub type and build with any subtleties encountered if (StubType.BOTH == stubType) { if (!Double.isNaN(firstStubRate)) { startStub = new CouponStub(stubType, firstStubDate, stubCalcMethod.getFirstStubRate()); } else if (firstStubStartIndex != null && firstStubEndIndex != null) { startStub = new CouponStub(stubType, firstStubDate, firstStubStartIndex, firstStubEndIndex); } else { startStub = new CouponStub(stubType, firstStubDate); } if (!Double.isNaN(finalStubRate)) { endStub = new CouponStub(stubType, finalStubDate, stubCalcMethod.getLastStubRate()); } else if (lastStubStartIndex != null && lastStubEndIndex != null) { endStub = new CouponStub(stubType, finalStubDate, lastStubStartIndex, lastStubEndIndex); } else { endStub = new CouponStub(stubType, finalStubDate); } } else if (StubType.LONG_START == stubType || StubType.SHORT_START == stubType) { if (!Double.isNaN(firstStubRate)) { startStub = new CouponStub(stubType, firstStubDate, firstStubRate); } else if (firstStubStartIndex != null && firstStubEndIndex != null) { startStub = new CouponStub(stubType, firstStubStartIndex, firstStubEndIndex); } else { startStub = new CouponStub(stubType); } } else if (StubType.LONG_END == stubType || StubType.SHORT_END == stubType) { if (!Double.isNaN(finalStubRate)) { endStub = new CouponStub(stubType, finalStubDate, finalStubRate); } else if (lastStubStartIndex != null && lastStubEndIndex != null) { endStub = new CouponStub(stubType, lastStubStartIndex, lastStubEndIndex); } else { endStub = new CouponStub(stubType); } } else if (stubType != null) { startStub = new CouponStub(stubType); endStub = new CouponStub(stubType); } } return Pairs.of(startStub, endStub); } public static IndexDeposit getIborIndex(ExternalId indexId, SecuritySource securitySource, ConventionSource conventionSource) { // try security lookup final Security sec = securitySource.getSingle(indexId.toBundle()); if (sec != null) { final com.opengamma.financial.security.index.IborIndex indexSecurity = (com.opengamma.financial.security.index.IborIndex) sec; IborIndexConvention indexConvention = conventionSource.getSingle(indexSecurity.getConventionId(), IborIndexConvention.class); return ConverterUtils.indexIbor(indexSecurity.getName(), indexConvention, indexSecurity.getTenor()); } // Fallback to convention lookup for old behaviour Convention iborLegConvention = conventionSource.getSingle(indexId); if (!(iborLegConvention instanceof VanillaIborLegConvention)) { throw new OpenGammaRuntimeException("Could not resolve an index convention for rate reference id: " + indexId.getValue()); } Convention iborConvention = conventionSource.getSingle(((VanillaIborLegConvention) iborLegConvention).getIborIndexConvention()); if (iborConvention == null) { throw new OpenGammaRuntimeException("Convention not found for " + ((VanillaIborLegConvention) iborLegConvention).getIborIndexConvention()); } IborIndexConvention iborIndexConvention = (IborIndexConvention) iborConvention; return new IborIndex(iborIndexConvention.getCurrency(), ((VanillaIborLegConvention) iborLegConvention).getResetTenor().getPeriod(), iborIndexConvention.getSettlementDays(), // fixing lag iborIndexConvention.getDayCount(), iborIndexConvention.getBusinessDayConvention(), ((IborIndexConvention) iborConvention).isIsEOM(), indexId.getValue()); } /** * Returns a notional provider that will supply absolute notionals (i.e. will convert relative * or delta notionals to absolute form). * @param notional The notional, not null * @param convention The business day convention, not null * @param calendar The calendar, not null * @return A notional provider supplies absolute notionals */ //TODO: Would be nice to make this support Notional public static NotionalProvider getNotionalProvider(final InterestRateSwapNotional notional, final BusinessDayConvention convention, final Calendar calendar) { final InterestRateSwapNotionalVisitor<LocalDate, Double> visitor = new InterestRateSwapNotionalAmountVisitor(); final InterestRateSwapNotional adjustedNotional; final List<LocalDate> dates = notional.getDates(); if (!dates.isEmpty()) { for (int i = 0; i < dates.size(); i++) { LocalDate date = dates.remove(i); date = convention.adjustDate(calendar, date); dates.add(i, date); } adjustedNotional = InterestRateSwapNotional.of(notional.getCurrency(), dates, notional.getNotionals(), notional.getShiftTypes()); } else { adjustedNotional = notional; } return new NotionalProvider() { @Override public double getAmount(final LocalDate date) { return adjustedNotional.accept(visitor, date); } }; } }