/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.instrument.annuity; import org.threeten.bp.Period; import org.threeten.bp.ZonedDateTime; import com.opengamma.analytics.financial.instrument.payment.CouponDefinition; import com.opengamma.analytics.financial.instrument.payment.CouponFixedCompoundingDefinition; import com.opengamma.analytics.financial.instrument.payment.CouponFixedDefinition; import com.opengamma.analytics.financial.instrument.payment.PaymentDefinition; import com.opengamma.analytics.financial.schedule.ScheduleCalculator; import com.opengamma.financial.convention.StubType; import com.opengamma.financial.convention.calendar.Calendar; /** * Generates an annuity of fixed rate coupons. */ public class FixedAnnuityDefinitionBuilder extends AbstractAnnuityDefinitionBuilder<FixedAnnuityDefinitionBuilder> { /** * The fixed rate of the annuity. This will be ignored for zero coupon annuities. * TODO schedule */ private double _rate; public FixedAnnuityDefinitionBuilder rate(double rate) { _rate = rate; return this; } @Override public AnnuityDefinition<?> build() { PaymentDefinition[] coupons = null; int exchangeNotionalCoupons = 0; if (isExchangeInitialNotional()) { exchangeNotionalCoupons++; } if (isExchangeFinalNotional()) { exchangeNotionalCoupons++; } Calendar accrualCalendar = null; if (getAccrualPeriodAdjustmentParameters() != null) { accrualCalendar = getAccrualPeriodAdjustmentParameters().getCalendar(); } if (Period.ZERO.equals(getAccrualPeriodFrequency())) { coupons = generateZeroCouponFlows(exchangeNotionalCoupons, accrualCalendar, getCompoundingMethod() == CompoundingMethod.NONE); } else { coupons = generateFixedCouponFlows(exchangeNotionalCoupons, accrualCalendar); } if (isExchangeInitialNotional()) { coupons[0] = getExchangeInitialNotionalCoupon(); } if (isExchangeFinalNotional()) { coupons[coupons.length - 1] = getExchangeFinalNotionalCoupon(); } /* * This assumes that the dates are adjusted, which may not always be true. Use the payment date adjustment calendar * if not null, otherwise use accrual date adjustment calendar. */ Calendar calendar; if (getPaymentDateAdjustmentParameters() != null) { calendar = getPaymentDateAdjustmentParameters().getCalendar(); } else if (getAccrualPeriodAdjustmentParameters() != null) { calendar = accrualCalendar; } else { calendar = null; } return new AnnuityDefinition<>(coupons, calendar); } private PaymentDefinition[] generateFixedCouponFlows(int exchangeNotionalCoupons, Calendar accrualCalendar) { CouponDefinition[] coupons; ZonedDateTime[] accrualEndDates = getAccrualEndDates(); ZonedDateTime startDate = getStartDate(); // startDate = getAccrualPeriodAdjustmentParameters().getBusinessDayConvention().adjustDate( // getAccrualPeriodAdjustmentParameters().getCalendar(), startDate); ZonedDateTime[] accrualStartDates = ScheduleCalculator.getStartDates(startDate, accrualEndDates); resetNotionalProvider(accrualStartDates); ZonedDateTime[] paymentDates; if (DateRelativeTo.START == getPaymentDateRelativeTo()) { paymentDates = getPaymentDates(accrualStartDates); } else { paymentDates = getPaymentDates(accrualEndDates); } coupons = new CouponFixedDefinition[exchangeNotionalCoupons + accrualEndDates.length]; int couponOffset = isExchangeInitialNotional() ? 1 : 0; for (int c = 0; c < accrualEndDates.length; c++) { coupons[c + couponOffset] = new CouponFixedDefinition( getCurrency(), paymentDates[c], accrualStartDates[c], accrualEndDates[c], AnnuityDefinitionBuilder.getDayCountFraction(getAccrualPeriodFrequency(), accrualCalendar, getDayCount(), getStartStub() != null ? getStartStub().getStubType() : StubType.NONE, getEndStub() != null ? getEndStub().getStubType() : StubType.NONE, accrualStartDates[c], accrualEndDates[c], c == 0, c == accrualEndDates.length - 1), (isPayer() ? -1 : 1) * getNotional().getAmount(accrualStartDates[c].toLocalDate()), _rate); } return coupons; } private CouponDefinition[] generateZeroCouponFlows(int exchangeNotionalCoupons, Calendar accrualCalendar, boolean noCompounding) { CouponDefinition[] coupons; coupons = new CouponDefinition[exchangeNotionalCoupons + 1]; // TODO missing support for start and end stub types StubType stubType = null; if (getStartStub() != null) { stubType = getStartStub().getStubType(); } else if (getEndStub() != null) { stubType = getEndStub().getStubType(); } if (stubType == null) { stubType = StubType.NONE; } final ZonedDateTime adjustedEndDate = getAccrualPeriodAdjustmentParameters().getBusinessDayConvention().adjustDate( getAccrualPeriodAdjustmentParameters().getCalendar(), getEndDate()); ZonedDateTime paymentDate = getPaymentDates(new ZonedDateTime[] {adjustedEndDate })[0]; if (noCompounding) { coupons[0] = new CouponFixedDefinition( getCurrency(), paymentDate, getStartDate(), adjustedEndDate, AnnuityDefinitionBuilder.getDayCountFraction(getAccrualPeriodFrequency(), accrualCalendar, getDayCount(), getStartStub() != null ? getStartStub().getStubType() : StubType.NONE, getEndStub() != null ? getEndStub().getStubType() : StubType.NONE, getStartDate(), adjustedEndDate, true, true), (isPayer() ? -1 : 1) * getNotional().getAmount(getStartDate().toLocalDate()), _rate); } else { ZonedDateTime[] accrualEndDates; if (getAccrualPeriodAdjustmentParameters() != null) { accrualEndDates = ScheduleCalculator.getAdjustedDateSchedule( getStartDate(), getEndDate(), Period.ofYears(1), // PLAT-6810 stubType, getAccrualPeriodAdjustmentParameters().getBusinessDayConvention(), getAccrualPeriodAdjustmentParameters().getCalendar(), getRollDateAdjuster()); } else { accrualEndDates = ScheduleCalculator.getUnadjustedDateSchedule( getStartDate(), getEndDate(), Period.ofYears(1), // PLAT-6810 stubType); } ZonedDateTime[] accrualStartDates = ScheduleCalculator.getStartDates(getStartDate(), accrualEndDates); double[] paymentYearFractions = new double[accrualEndDates.length]; for (int i = 0; i < accrualEndDates.length; i++) { paymentYearFractions[i] = AnnuityDefinitionBuilder.getDayCountFraction(Period.ofYears(1), // PLAT-6810 accrualCalendar, getDayCount(), stubType, stubType, accrualStartDates[i], accrualEndDates[i], i == 0, i == accrualEndDates.length - 1); } coupons[0] = CouponFixedCompoundingDefinition.from( getCurrency(), paymentDate, // pmt getStartDate(), // acc start adjustedEndDate, // acc end AnnuityDefinitionBuilder.getDayCountFraction(Period.ofYears(1), getPaymentDateAdjustmentParameters().getCalendar(), getDayCount(), stubType, stubType, getStartDate(), adjustedEndDate, true, true), // pmt yf (isPayer() ? -1 : 1) * getNotional().getAmount(getStartDate().toLocalDate()), _rate, accrualStartDates, accrualEndDates, paymentYearFractions); } return coupons; } }