Java tutorial
/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.instrument.payment; import java.util.Arrays; import javax.time.calendar.LocalDate; import javax.time.calendar.LocalTime; import javax.time.calendar.Period; import javax.time.calendar.TimeZone; import javax.time.calendar.ZonedDateTime; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.Validate; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.analytics.financial.instrument.InstrumentDefinitionVisitor; import com.opengamma.analytics.financial.instrument.InstrumentDefinitionWithData; import com.opengamma.analytics.financial.instrument.index.IborIndex; import com.opengamma.analytics.financial.interestrate.payments.derivative.Coupon; import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponFixed; import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponIborCompounded; import com.opengamma.analytics.financial.interestrate.payments.derivative.Payment; import com.opengamma.analytics.financial.schedule.ScheduleCalculator; import com.opengamma.analytics.util.time.TimeCalculator; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.money.Currency; import com.opengamma.util.timeseries.DoubleTimeSeries; /** * Class describing a Ibor-like compounded coupon. The Ibor fixing are compounded over several sub-periods. * The amount paid is equal to * $$ * \begin{equation*} * \left(\prod_{i=1}^n (1+\delta_i r_i) \right)-1 * \end{equation*} * $$ * where the $\delta_i$ are the accrual factors of the sub periods and the $r_i$ the fixing for the same periods. * The fixing have their own start dates, end dates and accrual factors. In general they are close to the accrual * dates used to compute the coupon accrual factors. */ public final class CouponIborCompoundedDefinition extends CouponDefinition implements InstrumentDefinitionWithData<Payment, DoubleTimeSeries<ZonedDateTime>> { /** * The Ibor-like index on which the coupon fixes. The index currency should be the same as the coupon currency. * All the coupon sub-periods fix on the same index. */ private final IborIndex _index; /** * The start dates of the accrual sub-periods. */ private final ZonedDateTime[] _accrualStartDates; /** * The end dates of the accrual sub-periods. */ private final ZonedDateTime[] _accrualEndDates; /** * The accrual factors (or year fraction) associated to the sub-periods. */ private final double[] _paymentAccrualFactors; /** * The coupon fixing dates. */ private final ZonedDateTime[] _fixingDates; /** * The start dates of the fixing periods. */ private final ZonedDateTime[] _fixingPeriodStartDates; /** * The end dates of the fixing periods. */ private final ZonedDateTime[] _fixingPeriodEndDates; /** * The accrual factors (or year fraction) associated with the fixing periods in the Index day count convention. */ private final double[] _fixingPeriodAccrualFactors; /** * Constructor. * @param currency The coupon currency. * @param paymentDate The coupon payment date. * @param accrualStartDate The start date of the accrual period. * @param accrualEndDate The end date of the accrual period. * @param paymentAccrualFactor The accrual factor of the accrual period. * @param notional The coupon notional. * @param index The Ibor-like index on which the coupon fixes. The index currency should be the same as the coupon currency. * @param accrualStartDates The start dates of the accrual sub-periods. * @param accrualEndDates The end dates of the accrual sub-periods. * @param paymentAccrualFactors The accrual factors (or year fraction) associated to the sub-periods. * @param fixingDates The coupon fixing dates. * @param fixingPeriodStartDates The start dates of the fixing periods. * @param fixingPeriodEndDates The end dates of the fixing periods. * @param fixingPeriodAccrualFactors The accrual factors (or year fraction) associated with the fixing periods in the Index day count convention. */ private CouponIborCompoundedDefinition(Currency currency, ZonedDateTime paymentDate, ZonedDateTime accrualStartDate, ZonedDateTime accrualEndDate, double paymentAccrualFactor, double notional, IborIndex index, ZonedDateTime[] accrualStartDates, ZonedDateTime[] accrualEndDates, double[] paymentAccrualFactors, ZonedDateTime[] fixingDates, ZonedDateTime[] fixingPeriodStartDates, ZonedDateTime[] fixingPeriodEndDates, double[] fixingPeriodAccrualFactors) { super(currency, paymentDate, accrualStartDate, accrualEndDate, paymentAccrualFactor, notional); ArgumentChecker.isTrue(accrualStartDates.length == accrualEndDates.length, "Accrual start and end dates should have same length"); ArgumentChecker.isTrue(accrualStartDates.length == fixingDates.length, "Same length"); ArgumentChecker.isTrue(accrualStartDates.length == fixingPeriodStartDates.length, "Same length"); ArgumentChecker.isTrue(accrualStartDates.length == fixingPeriodEndDates.length, "Same length"); ArgumentChecker.isTrue(accrualStartDates.length == fixingPeriodAccrualFactors.length, "Same length"); _index = index; _accrualStartDates = accrualStartDates; _accrualEndDates = accrualEndDates; _paymentAccrualFactors = paymentAccrualFactors; _fixingDates = fixingDates; _fixingPeriodStartDates = fixingPeriodStartDates; _fixingPeriodEndDates = fixingPeriodEndDates; _fixingPeriodAccrualFactors = fixingPeriodAccrualFactors; } /** * Builds an Ibor compounded coupon from all the details. * @param paymentDate The coupon payment date. * @param accrualStartDate The start date of the accrual period. * @param accrualEndDate The end date of the accrual period. * @param paymentAccrualFactor The accrual factor of the accrual period. * @param notional The coupon notional. * @param index The Ibor-like index on which the coupon fixes. The index currency should be the same as the coupon currency. * @param accrualStartDates The start dates of the accrual sub-periods. * @param accrualEndDates The end dates of the accrual sub-periods. * @param paymentAccrualFactors The accrual factors (or year fraction) associated to the sub-periods. * @param fixingDates The coupon fixing dates. * @param fixingPeriodStartDates The start dates of the fixing periods. * @param fixingPeriodEndDates The end dates of the fixing periods. * @param fixingPeriodAccrualFactors The accrual factors (or year fraction) associated with the fixing periods in the Index day count convention. * @return The compounded coupon. */ public static CouponIborCompoundedDefinition from(ZonedDateTime paymentDate, ZonedDateTime accrualStartDate, ZonedDateTime accrualEndDate, double paymentAccrualFactor, double notional, IborIndex index, ZonedDateTime[] accrualStartDates, ZonedDateTime[] accrualEndDates, double[] paymentAccrualFactors, ZonedDateTime[] fixingDates, ZonedDateTime[] fixingPeriodStartDates, ZonedDateTime[] fixingPeriodEndDates, double[] fixingPeriodAccrualFactors) { return new CouponIborCompoundedDefinition(index.getCurrency(), paymentDate, accrualStartDate, accrualEndDate, paymentAccrualFactor, notional, index, accrualStartDates, accrualEndDates, paymentAccrualFactors, fixingDates, fixingPeriodStartDates, fixingPeriodEndDates, fixingPeriodAccrualFactors); } /** * Builds an Ibor compounded coupon from the accrual and payment details. The fixing dates and fixing accrual periods are computed from those dates using the index conventions. * @param paymentDate The coupon payment date. * @param notional The coupon notional. * @param index The Ibor-like index on which the coupon fixes. The index currency should be the same as the coupon currency. * @param accrualStartDates The start dates of the accrual sub-periods. * @param accrualEndDates The end dates of the accrual sub-periods. * @param paymentAccrualFactors The accrual factors (or year fraction) associated to the sub-periods. * @return The compounded coupon. */ public static CouponIborCompoundedDefinition from(ZonedDateTime paymentDate, double notional, IborIndex index, ZonedDateTime[] accrualStartDates, ZonedDateTime[] accrualEndDates, double[] paymentAccrualFactors) { int nbSubPeriod = accrualEndDates.length; ZonedDateTime accrualStartDate = accrualStartDates[0]; ZonedDateTime accrualEndDate = accrualEndDates[nbSubPeriod - 1]; double paymentAccrualFactor = 0.0; ZonedDateTime[] fixingDates = new ZonedDateTime[nbSubPeriod]; ZonedDateTime[] fixingPeriodEndDates = new ZonedDateTime[nbSubPeriod]; double[] fixingPeriodAccrualFactors = new double[nbSubPeriod]; for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) { paymentAccrualFactor += paymentAccrualFactors[loopsub]; fixingDates[loopsub] = ScheduleCalculator.getAdjustedDate(accrualStartDates[loopsub], -index.getSpotLag(), index.getCalendar()); fixingPeriodEndDates[loopsub] = ScheduleCalculator.getAdjustedDate(accrualStartDates[loopsub], index); fixingPeriodAccrualFactors[loopsub] = index.getDayCount() .getDayCountFraction(accrualStartDates[loopsub], fixingPeriodEndDates[loopsub]); } return new CouponIborCompoundedDefinition(index.getCurrency(), paymentDate, accrualStartDate, accrualEndDate, paymentAccrualFactor, notional, index, accrualStartDates, accrualEndDates, paymentAccrualFactors, fixingDates, accrualStartDates, fixingPeriodEndDates, fixingPeriodAccrualFactors); } /** * Builds an Ibor compounded coupon from a total period and the Ibor index. The Ibor day count is used to compute the accrual factors. * If required the stub of the sub-periods will be short and last. The payment date is the start accrual date plus the tenor in the index conventions. * @param notional The coupon notional. * @param accrualStartDate The first accrual date. The other one are computed from that one and the index conventions. * @param tenor The total coupon tenor. * @param index The underlying Ibor index. * @return The compounded coupon. */ public static CouponIborCompoundedDefinition from(double notional, ZonedDateTime accrualStartDate, Period tenor, IborIndex index) { ZonedDateTime[] accrualEndDates = ScheduleCalculator.getAdjustedDateSchedule(accrualStartDate, tenor, true, false, index); int nbSubPeriod = accrualEndDates.length; ZonedDateTime[] accrualStartDates = new ZonedDateTime[nbSubPeriod]; accrualStartDates[0] = accrualStartDate; System.arraycopy(accrualEndDates, 0, accrualStartDates, 1, nbSubPeriod - 1); double[] paymentAccrualFactors = new double[nbSubPeriod]; for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) { paymentAccrualFactors[loopsub] = index.getDayCount().getDayCountFraction(accrualStartDates[loopsub], accrualEndDates[loopsub]); } return from(accrualEndDates[nbSubPeriod - 1], notional, index, accrualStartDates, accrualEndDates, paymentAccrualFactors); } /** * Returns the Ibor index underlying the coupon. * @return The index. */ public IborIndex getIndex() { return _index; } /** * Returns the accrual start dates of each sub-period. * @return The dates. */ public ZonedDateTime[] getAccrualStartDates() { return _accrualStartDates; } /** * Returns the accrual end dates of each sub-period. * @return The dates. */ public ZonedDateTime[] getAccrualEndDates() { return _accrualEndDates; } /** * Returns the payment accrual factors for each sub-period. * @return The factors. */ public double[] getPaymentAccrualFactors() { return _paymentAccrualFactors; } /** * Returns the fixing dates for each sub-period. * @return The dates. */ public ZonedDateTime[] getFixingDates() { return _fixingDates; } /** * Returns the fixing period start dates for each sub-period. * @return The dates. */ public ZonedDateTime[] getFixingPeriodStartDates() { return _fixingPeriodStartDates; } /** * Returns the fixing period end dates for each sub-period. * @return The dates. */ public ZonedDateTime[] getFixingPeriodEndDates() { return _fixingPeriodEndDates; } /** * Returns the fixing period accrual factors for each sub-period. * @return The factors. */ public double[] getFixingPeriodAccrualFactors() { return _fixingPeriodAccrualFactors; } @Override public Coupon toDerivative(ZonedDateTime dateTime, DoubleTimeSeries<ZonedDateTime> indexFixingTimeSeries, String... yieldCurveNames) { LocalDate dateConversion = dateTime.toLocalDate(); Validate.notNull(indexFixingTimeSeries, "Index fixing time series"); Validate.notNull(yieldCurveNames, "yield curve names"); Validate.isTrue(yieldCurveNames.length > 1, "at least two curves required"); Validate.isTrue(!dateConversion.isAfter(getPaymentDate().toLocalDate()), "date is after payment date"); final String discountingCurveName = yieldCurveNames[0]; final String forwardCurveName = yieldCurveNames[1]; final double paymentTime = TimeCalculator.getTimeBetween(dateTime, getPaymentDate()); final int nbSubPeriods = _fixingDates.length; int nbFixed = 0; double ratioAccrued = 1.0; while ((nbFixed < nbSubPeriods) && (dateConversion.isAfter(_fixingDates[nbFixed].toLocalDate()))) { final ZonedDateTime rezonedFixingDate = ZonedDateTime.of(_fixingDates[nbFixed].toLocalDate(), LocalTime.of(0, 0), TimeZone.UTC); final Double fixedRate = indexFixingTimeSeries.getValue(rezonedFixingDate); if (fixedRate == null) { throw new OpenGammaRuntimeException("Could not get fixing value for date " + rezonedFixingDate); } ratioAccrued *= 1.0 + _paymentAccrualFactors[nbFixed] * fixedRate; nbFixed++; } if ((nbFixed < nbSubPeriods) && dateConversion.equals(_fixingDates[nbFixed].toLocalDate())) { final ZonedDateTime rezonedFixingDate = ZonedDateTime.of(_fixingDates[nbFixed].toLocalDate(), LocalTime.of(0, 0), TimeZone.UTC); final Double fixedRate = indexFixingTimeSeries.getValue(rezonedFixingDate); if (fixedRate != null) { // Implementation note: on the fixing date and fixing already known. ratioAccrued *= 1.0 + _paymentAccrualFactors[nbFixed] * fixedRate; nbFixed++; } } if (nbFixed == nbSubPeriods) { // Implementation note: all dates already fixed: CouponFixed final double rate = (ratioAccrued - 1.0) / getPaymentYearFraction(); return new CouponFixed(getCurrency(), paymentTime, discountingCurveName, getPaymentYearFraction(), getNotional(), rate, getAccrualStartDate(), getAccrualEndDate()); } final double notionalAccrued = getNotional() * ratioAccrued; int nbSubPeriodLeft = nbSubPeriods - nbFixed; final double[] paymentAccrualFactorsLeft = new double[nbSubPeriodLeft]; System.arraycopy(_paymentAccrualFactors, nbFixed, paymentAccrualFactorsLeft, 0, nbSubPeriodLeft); final double[] fixingTimesLeft = new double[nbSubPeriodLeft]; System.arraycopy(TimeCalculator.getTimeBetween(dateTime, _fixingDates), nbFixed, fixingTimesLeft, 0, nbSubPeriodLeft); final double[] fixingPeriodStartTimesLeft = new double[nbSubPeriodLeft]; System.arraycopy(TimeCalculator.getTimeBetween(dateTime, _fixingPeriodStartDates), nbFixed, fixingPeriodStartTimesLeft, 0, nbSubPeriodLeft); final double[] fixingPeriodEndTimesLeft = new double[nbSubPeriodLeft]; System.arraycopy(TimeCalculator.getTimeBetween(dateTime, _fixingPeriodEndDates), nbFixed, fixingPeriodEndTimesLeft, 0, nbSubPeriodLeft); final double[] fixingPeriodAccrualFactorsLeft = new double[nbSubPeriodLeft]; System.arraycopy(_fixingPeriodAccrualFactors, nbFixed, fixingPeriodAccrualFactorsLeft, 0, nbSubPeriodLeft); return new CouponIborCompounded(getCurrency(), paymentTime, discountingCurveName, getPaymentYearFraction(), getNotional(), notionalAccrued, _index, paymentAccrualFactorsLeft, fixingTimesLeft, fixingPeriodStartTimesLeft, fixingPeriodEndTimesLeft, fixingPeriodAccrualFactorsLeft, forwardCurveName); } @Override public CouponIborCompounded toDerivative(ZonedDateTime dateTime, String... yieldCurveNames) { LocalDate dateConversion = dateTime.toLocalDate(); ArgumentChecker.isTrue(!dateConversion.isAfter(_fixingDates[0].toLocalDate()), "toDerivative without time series should have a date before the first fixing date."); String discountingCurveName = yieldCurveNames[0]; String forwardCurveName = yieldCurveNames[1]; double paymentTime = TimeCalculator.getTimeBetween(dateTime, getPaymentDate()); double[] fixingTimes = TimeCalculator.getTimeBetween(dateTime, _fixingDates); double[] fixingPeriodStartTimes = TimeCalculator.getTimeBetween(dateTime, _fixingPeriodStartDates); double[] fixingPeriodEndTimes = TimeCalculator.getTimeBetween(dateTime, _fixingPeriodEndDates); return new CouponIborCompounded(getCurrency(), paymentTime, discountingCurveName, getPaymentYearFraction(), getNotional(), getNotional(), _index, _paymentAccrualFactors, fixingTimes, fixingPeriodStartTimes, fixingPeriodEndTimes, _fixingPeriodAccrualFactors, forwardCurveName); } @Override public <U, V> V accept(InstrumentDefinitionVisitor<U, V> visitor, U data) { return visitor.visitCouponIborCompoundedDefinition(this, data); } @Override public <V> V accept(InstrumentDefinitionVisitor<?, V> visitor) { return visitor.visitCouponIborCompoundedDefinition(this); } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + Arrays.hashCode(_accrualEndDates); result = prime * result + Arrays.hashCode(_accrualStartDates); result = prime * result + Arrays.hashCode(_fixingDates); result = prime * result + Arrays.hashCode(_fixingPeriodAccrualFactors); result = prime * result + Arrays.hashCode(_fixingPeriodEndDates); result = prime * result + Arrays.hashCode(_fixingPeriodStartDates); result = prime * result + _index.hashCode(); result = prime * result + Arrays.hashCode(_paymentAccrualFactors); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!super.equals(obj)) { return false; } if (getClass() != obj.getClass()) { return false; } CouponIborCompoundedDefinition other = (CouponIborCompoundedDefinition) obj; if (!Arrays.equals(_accrualEndDates, other._accrualEndDates)) { return false; } if (!Arrays.equals(_accrualStartDates, other._accrualStartDates)) { return false; } if (!Arrays.equals(_fixingDates, other._fixingDates)) { return false; } if (!Arrays.equals(_fixingPeriodAccrualFactors, other._fixingPeriodAccrualFactors)) { return false; } if (!Arrays.equals(_fixingPeriodEndDates, other._fixingPeriodEndDates)) { return false; } if (!Arrays.equals(_fixingPeriodStartDates, other._fixingPeriodStartDates)) { return false; } if (_index == null) { if (other._index != null) { return false; } } else if (!_index.equals(other._index)) { return false; } if (!ObjectUtils.equals(_index, other._index)) { return false; } return true; } }