/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.instrument.annuity; import java.util.ArrayList; import org.threeten.bp.LocalDate; import org.threeten.bp.LocalTime; import org.threeten.bp.Period; import org.threeten.bp.ZoneId; import org.threeten.bp.ZoneOffset; import org.threeten.bp.ZonedDateTime; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.analytics.financial.instrument.NotionalProvider; import com.opengamma.analytics.financial.instrument.VariableNotionalProvider; import com.opengamma.analytics.financial.instrument.index.IborIndex; import com.opengamma.analytics.financial.instrument.payment.CouponFixedDefinition; import com.opengamma.analytics.financial.schedule.ScheduleCalculator; import com.opengamma.financial.convention.StubType; import com.opengamma.financial.convention.daycount.DayCount; import com.opengamma.financial.convention.rolldate.RollDateAdjuster; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.money.Currency; /** * Base class for building annuity definitions. * @param <T> the implementing class */ public abstract class AbstractAnnuityDefinitionBuilder<T extends AbstractAnnuityDefinitionBuilder<T>> { /** * Description of the coupon stub. */ public static class CouponStub { private StubType _stubType; private double _stubRate = Double.NaN; private LocalDate _effectiveDate; private IborIndex _firstIborIndex; private IborIndex _secondIborIndex; public CouponStub(StubType stubType) { _stubType = stubType; } public CouponStub(StubType stubType, LocalDate effectiveDate) { _stubType = stubType; _effectiveDate = effectiveDate; } public CouponStub(StubType stubType, LocalDate effectiveDate, double stubRate) { _stubType = stubType; _effectiveDate = effectiveDate; _stubRate = stubRate; } public CouponStub(StubType stubType, IborIndex firstStubIndex, IborIndex secondStubIndex) { _stubType = stubType; _firstIborIndex = firstStubIndex; _secondIborIndex = secondStubIndex; } public CouponStub(StubType stubType, LocalDate effectiveDate, IborIndex firstStubIndex, IborIndex secondStubIndex) { _stubType = stubType; _effectiveDate = effectiveDate; _firstIborIndex = firstStubIndex; _secondIborIndex = secondStubIndex; } public StubType getStubType() { return _stubType; } public double getStubRate() { return _stubRate; } public boolean hasStubRate() { return !Double.isNaN(_stubRate); } public LocalDate getEffectiveDate() { return _effectiveDate; } public IborIndex getFirstIborIndex() { return _firstIborIndex; } public IborIndex getSecondIborIndex() { return _secondIborIndex; } public boolean isInterpolated() { return _firstIborIndex != null && _secondIborIndex != null && !_firstIborIndex.equals(_secondIborIndex); } } /** * Flag to describe the direction of the coupons. This is a required field. */ private boolean _payer; /** * The daycount of the annuity. This is a required field. */ private DayCount _dayCount; /** * The currency of the coupons in the annuity. This is a required field. */ private Currency _currency; /** * The notional of the annuity. This is a required field. */ private NotionalProvider _notional; /** * The start date of the annuity. This is a required field. */ private LocalDate _startDate; /** * The end date of the annuity. This is a required field. */ private LocalDate _endDate; /** * The stub type at the start of the series of coupons. This is an optional field and will default to a short start stub. */ private CouponStub _startStub = new CouponStub(StubType.SHORT_START); /** * The stub type at the end of the series of coupons. This is an optional field, and will default to none if unset. */ private CouponStub _endStub = new CouponStub(StubType.NONE); /** * The roll date adjuster used to adjust the accrual dates. This is an optional field. */ private RollDateAdjuster _rollDateAdjuster; /** * Flag to exchange initial notional, defaults to false. */ private boolean _exchangeInitialNotional; /** * Flag to exchange final notional, defaults to false. */ private boolean _exchangeFinalNotional; /** * The frequency of the accrual periods. */ private Period _accrualPeriodFrequency; /** * Parameters used to adjust the accrual period dates. This is an optional field. */ private AdjustedDateParameters _adjustedAccrualDateParameters; /** * Parameters used to adjust the start date of the annuity. This is an optional field. */ private AdjustedDateParameters _adjustedStartDateParameters; /** * Parameters used to adjust the end date of the annuity. This is an optional field. */ private AdjustedDateParameters _adjustedEndDateParameters; /** * Flag to indicate the payment date relative to accrual period. This is an optional field, and will default to the * end of the accrual period. */ private DateRelativeTo _paymentDateRelativeTo = DateRelativeTo.END; /** * Parameters used to create payment dates relative to the accrual periods of the annuity. This is an optional field. */ private OffsetAdjustedDateParameters _adjustedPaymentDateParameters; /** * The compounding. */ private CompoundingMethod _compoundingMethod; protected boolean isPayer() { return _payer; } protected DayCount getDayCount() { return _dayCount; } protected Currency getCurrency() { return _currency; } protected NotionalProvider getNotional() { return _notional; } /** * If notional provider is VariableNotionalProvider and the date set is null, construct a new provider with the computed dates * @param dates Set of dates specifying notional, i.e., accrual start date for coupon payment, adjusted start/end date for notional exchange */ protected void resetNotionalProvider(ZonedDateTime[] dates) { if (getNotional() instanceof VariableNotionalProvider) { VariableNotionalProvider provider = (VariableNotionalProvider) getNotional(); if (provider.getDates() == null) { ArrayList<ZonedDateTime> list = new ArrayList<>(); if (isExchangeInitialNotional()) { ZonedDateTime startDate = getStartDateAdjustmentParameters().getBusinessDayConvention().adjustDate( getStartDateAdjustmentParameters().getCalendar(), getStartDate()); list.add(startDate); } int nDates = dates.length; for (int i = 0; i < nDates; ++i) { list.add(dates[i]); } if (isExchangeFinalNotional()) { ZonedDateTime endDate = getEndDateAdjustmentParameters().getBusinessDayConvention().adjustDate( getEndDateAdjustmentParameters().getCalendar(), getEndDate()); list.add(endDate); } _notional = provider.withZonedDateTime(list); } } } /** * Returns the unadjusted start date of the annuity. * @return the unadjusted start date of the annuity. */ protected ZonedDateTime getStartDate() { return _startDate.atTime(LocalTime.MIN).atZone(ZoneOffset.UTC); } /** * Returns the unadjusted end date of the annuity. * @return the unadjusted end date of the annuity. */ protected ZonedDateTime getEndDate() { return _endDate.atTime(LocalTime.MIN).atZone(ZoneOffset.UTC); } protected CouponStub getStartStub() { return _startStub; } protected CouponStub getEndStub() { return _endStub; } protected RollDateAdjuster getRollDateAdjuster() { return _rollDateAdjuster; } protected boolean isExchangeInitialNotional() { return _exchangeInitialNotional; } protected boolean isExchangeFinalNotional() { return _exchangeFinalNotional; } protected Period getAccrualPeriodFrequency() { return _accrualPeriodFrequency; } protected AdjustedDateParameters getAccrualPeriodAdjustmentParameters() { return _adjustedAccrualDateParameters; } protected AdjustedDateParameters getStartDateAdjustmentParameters() { return _adjustedStartDateParameters; } protected AdjustedDateParameters getEndDateAdjustmentParameters() { return _adjustedEndDateParameters; } protected DateRelativeTo getPaymentDateRelativeTo() { return _paymentDateRelativeTo; } protected OffsetAdjustedDateParameters getPaymentDateAdjustmentParameters() { return _adjustedPaymentDateParameters; } /** * Sets the flag to describe whether the annuity is paying or receiving. This is a required field. * @param payer whether the annuity is paying or receiving. * @return itself. */ @SuppressWarnings("unchecked") public T payer(boolean payer) { _payer = payer; return (T) this; } /** * Sets the daycount of the annuity. This is a required field. * @param dayCount the daycount of the annuity. * @return itself */ @SuppressWarnings("unchecked") public T dayCount(DayCount dayCount) { _dayCount = dayCount; return (T) this; } /** * Sets the notional of the annuity. This is a required field. * @param notional the notional of the annuity. * @return itself */ @SuppressWarnings("unchecked") public T notional(NotionalProvider notional) { _notional = notional; return (T) this; } @SuppressWarnings("unchecked") public T currency(Currency currency) { _currency = currency; return (T) this; } @SuppressWarnings("unchecked") public T startDate(LocalDate startDate) { _startDate = startDate; return (T) this; } @SuppressWarnings("unchecked") public T endDate(LocalDate endDate) { _endDate = endDate; return (T) this; } /** * Sets the stub type at the start of the series of coupons. This is optional and will default to StubType.NONE if unset. * @param startStub the stub type at the end of the series of coupons. * @return itself */ @SuppressWarnings("unchecked") public T startStub(CouponStub startStub) { if (startStub == null) { _startStub = null; } else { ArgumentChecker.isFalse(startStub.getStubType() == StubType.SHORT_END || startStub.getStubType() == StubType.LONG_END, "startStub should be start stub type, but {}", startStub.getStubType()); _startStub = startStub; if (startStub.getStubType() != StubType.BOTH && startStub.getStubType() != StubType.NONE) { _endStub = null; // reset end stub. } } return (T) this; } /** * Sets the stub type at the end of the series of coupons. This is optional and will default to StubType.NONE if unset. * @param endStub the stub type at the end of the series of coupons. * @return itself */ @SuppressWarnings("unchecked") public T endStub(CouponStub endStub) { if (endStub == null) { _endStub = null; } else { ArgumentChecker.isFalse(endStub.getStubType() == StubType.SHORT_START || endStub.getStubType() == StubType.LONG_START, "endStub should be end stub type, but {}", endStub.getStubType()); _endStub = endStub; if (endStub.getStubType() != StubType.BOTH && endStub.getStubType() != StubType.NONE) { _startStub = null; // reset start stub. } } return (T) this; } @SuppressWarnings("unchecked") public T rollDateAdjuster(RollDateAdjuster rollDateAdjuster) { _rollDateAdjuster = rollDateAdjuster; return (T) this; } /** * Sets the flag indicating whether the notional is exchanged at the start of the annuity. * @param exchangeInitialNotional the flag indicating whether the notional is exchanged at the start of the annuity. * @return itself */ @SuppressWarnings("unchecked") public T exchangeInitialNotional(boolean exchangeInitialNotional) { _exchangeInitialNotional = exchangeInitialNotional; return (T) this; } /** * Sets the flag indicating whether the notional is exchanged at the end of the annuity. * @param exchangeFinalNotional the flag indicating whether the notional is exchanged at the end of the annuity. * @return itself */ @SuppressWarnings("unchecked") public T exchangeFinalNotional(boolean exchangeFinalNotional) { _exchangeFinalNotional = exchangeFinalNotional; return (T) this; } /** * Sets the accrual period frequency of the annuity. This is a required field. * @param accrualPeriodFrequency the frequency of the coupons of the annuity. * @return itself */ @SuppressWarnings("unchecked") public T accrualPeriodFrequency(Period accrualPeriodFrequency) { _accrualPeriodFrequency = accrualPeriodFrequency; return (T) this; } /** * Sets the parameters used to adjust the accrual period dates. This is an optional field. * @param accrualDateAdjustmentParameters the parameters used to adjust the accrual periods. * @return the parameters used to adjust the accrual periods. */ @SuppressWarnings("unchecked") public T accrualPeriodParameters(AdjustedDateParameters accrualDateAdjustmentParameters) { _adjustedAccrualDateParameters = accrualDateAdjustmentParameters; return (T) this; } @SuppressWarnings("unchecked") public T startDateAdjustmentParameters(AdjustedDateParameters startDateAdjustmentParameters) { _adjustedStartDateParameters = startDateAdjustmentParameters; return (T) this; } @SuppressWarnings("unchecked") public T endDateAdjustmentParameters(AdjustedDateParameters endDateAdjustmentParameters) { if (_adjustedAccrualDateParameters != null && _adjustedAccrualDateParameters.getBusinessDayConvention() == null && _adjustedEndDateParameters.getBusinessDayConvention() != null) { throw new OpenGammaRuntimeException("End date adjustment business day convention does not match accrual period business day convention"); } _adjustedEndDateParameters = endDateAdjustmentParameters; return (T) this; } @SuppressWarnings("unchecked") public T paymentDateRelativeTo(DateRelativeTo paymentDateRelativeTo) { _paymentDateRelativeTo = paymentDateRelativeTo; return (T) this; } @SuppressWarnings("unchecked") public T paymentDateAdjustmentParameters(OffsetAdjustedDateParameters paymentDateAdjustmentParameters) { _adjustedPaymentDateParameters = paymentDateAdjustmentParameters; return (T) this; } /** * Returns the accrual end dates, adjusted if the parameters are set. * @return the accrual end dates, adjusted if the parameters are set. */ protected ZonedDateTime[] getAccrualEndDates() { return getAccrualEndDates(true); } protected ZonedDateTime[] getAccrualEndDates(boolean adjusted) { StubType stubType = null; if (_startStub != null && _startStub.getStubType() != StubType.NONE) { stubType = _startStub.getStubType(); } else if (_endStub != null) { stubType = _endStub.getStubType(); } if (stubType == null) { stubType = StubType.NONE; } ZonedDateTime actualStartDate = getStartDate(); ZonedDateTime actualEndDate = getEndDate(); ZonedDateTime startDate; ZonedDateTime endDate; if (StubType.BOTH == stubType) { startDate = ZonedDateTime.of(_startStub.getEffectiveDate(), LocalTime.of(0, 0), ZoneId.of("UTC")); endDate = ZonedDateTime.of(_endStub.getEffectiveDate(), LocalTime.of(0, 0), ZoneId.of("UTC")); } else { startDate = actualStartDate; endDate = actualEndDate; } ZonedDateTime[] accrualEndDates; if (adjusted && _adjustedAccrualDateParameters != null) { accrualEndDates = ScheduleCalculator.getAdjustedDateSchedule( startDate, endDate, _accrualPeriodFrequency, stubType, _adjustedAccrualDateParameters.getBusinessDayConvention(), _adjustedAccrualDateParameters.getCalendar(), getRollDateAdjuster()); //_rollDateAdjuster instanceof GeneralRollDateAdjuster ? null : _rollDateAdjuster); } else { accrualEndDates = ScheduleCalculator.getUnadjustedDateSchedule( startDate, endDate, _accrualPeriodFrequency, stubType); } if (StubType.BOTH == stubType) { ZonedDateTime[] bothStubAccrualEndDates = new ZonedDateTime[accrualEndDates.length + 2]; System.arraycopy(accrualEndDates, 0, bothStubAccrualEndDates, 1, accrualEndDates.length); bothStubAccrualEndDates[0] = startDate; bothStubAccrualEndDates[bothStubAccrualEndDates.length - 1] = actualEndDate; accrualEndDates = bothStubAccrualEndDates; } return accrualEndDates; } protected ZonedDateTime[] getPaymentDates(ZonedDateTime[] accrualDates) { if (_adjustedPaymentDateParameters != null) { return ScheduleCalculator.getAdjustedDateSchedule( accrualDates, _adjustedPaymentDateParameters.getBusinessDayConvention(), _adjustedPaymentDateParameters.getCalendar(), _adjustedPaymentDateParameters.getOffset()); } else { return accrualDates; } } protected CouponFixedDefinition getExchangeInitialNotionalCoupon() { if (!_exchangeInitialNotional) { return null; } ZonedDateTime startDate = getStartDateAdjustmentParameters().getBusinessDayConvention().adjustDate(getStartDateAdjustmentParameters().getCalendar(), getStartDate()); return new CouponFixedDefinition( _currency, startDate, // payment startDate, // accrual start startDate, // accrual end 1.0, // year frac (isPayer() ? 1 : -1) * getNotional().getAmount(_startDate), // The initial notional has opposite sign. 1.0); // rate } protected CouponFixedDefinition getExchangeFinalNotionalCoupon() { ZonedDateTime endDate = getEndDateAdjustmentParameters().getBusinessDayConvention().adjustDate(getEndDateAdjustmentParameters().getCalendar(), getEndDate()); return new CouponFixedDefinition(_currency, endDate, // payment endDate, // accrual start endDate, // accrual end 1.0, // year frac (isPayer() ? -1 : 1) * getNotional().getAmount(_endDate), 1.0); // rate } public CompoundingMethod getCompoundingMethod() { return _compoundingMethod; } @SuppressWarnings("unchecked") public T compoundingMethod(CompoundingMethod compoundingMethod) { _compoundingMethod = compoundingMethod; return (T) this; } public abstract AnnuityDefinition<?> build(); }