/**
* Copyright (C) 2013 - 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 org.apache.commons.lang.ObjectUtils;
import org.threeten.bp.LocalDate;
import org.threeten.bp.LocalDateTime;
import org.threeten.bp.LocalTime;
import org.threeten.bp.ZoneOffset;
import org.threeten.bp.ZonedDateTime;
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.CouponIborCompoundingSimpleSpread;
import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponIborCompoundingSpread;
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.financial.convention.StubType;
import com.opengamma.financial.convention.businessday.BusinessDayConvention;
import com.opengamma.financial.convention.calendar.Calendar;
import com.opengamma.timeseries.DoubleTimeSeries;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.Currency;
/**
* Class describing a Ibor-like coupon with compounding and spread. There are three ISDA versions of compounding with spread.
* The one referred in this class is the "Compounding treating spread as simple interest" (not "Compounding" and not "Flat Compounding").
* The Ibor fixing are compounded over several sub-periods.
* The amount paid is described in the reference below.
* 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.
* <p> Reference: Mengle, D. (2009). Alternative compounding methods for over-the-counter derivative transactions. ISDA.
*/
public class CouponIborCompoundingSimpleSpreadDefinition 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[] _subperiodAccrualStartDates;
/**
* The end dates of the accrual sub-periods.
*/
private final ZonedDateTime[] _subperiodAccrualEndDates;
/**
* The accrual factors (or year fraction) associated to the sub-periods.
*/
private final double[] _subperiodAccrualFactors;
/**
* The coupon fixing dates.
*/
private final ZonedDateTime[] _fixingDates;
/**
* The start dates of the fixing periods.
*/
private final ZonedDateTime[] _fixingSubperiodStartDates;
/**
* The end dates of the fixing periods.
*/
private final ZonedDateTime[] _fixingSubperiodEndDates;
/**
* The accrual factors (or year fraction) associated with the fixing periods in the Index day count convention.
*/
private final double[] _fixingSubperiodAccrualFactors;
/**
* The spread paid above the Ibor rate.
*/
private final double _spread;
/**
* The rate of the first compounded rate. This is an optional field.
*/
private final double _initialRate;
/**
* 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.
* @param spread The spread paid above the Ibor rate.
* @param initialRate The rate of the first compound period.
*/
protected CouponIborCompoundingSimpleSpreadDefinition(
final Currency currency,
final ZonedDateTime paymentDate,
final ZonedDateTime accrualStartDate,
final ZonedDateTime accrualEndDate,
final double paymentAccrualFactor,
final double notional,
final IborIndex index,
final ZonedDateTime[] accrualStartDates,
final ZonedDateTime[] accrualEndDates,
final double[] paymentAccrualFactors,
final ZonedDateTime[] fixingDates,
final ZonedDateTime[] fixingPeriodStartDates,
final ZonedDateTime[] fixingPeriodEndDates,
final double[] fixingPeriodAccrualFactors,
final double spread,
final double initialRate) {
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;
_subperiodAccrualStartDates = accrualStartDates;
_subperiodAccrualEndDates = accrualEndDates;
_subperiodAccrualFactors = paymentAccrualFactors;
_fixingDates = fixingDates;
_fixingSubperiodStartDates = fixingPeriodStartDates;
_fixingSubperiodEndDates = fixingPeriodEndDates;
_fixingSubperiodAccrualFactors = fixingPeriodAccrualFactors;
_spread = spread;
_initialRate = initialRate;
}
/**
* Builds a Ibor compounded coupon with a spread using the "Flat compounded" to calculate each compounded period rate.
* @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.
* @param spread The spread paid above the Ibor rate.
* @param initialRate The rate of the first compounded period.
* @return The compounded coupon.
*/
public static CouponIborCompoundingSimpleSpreadDefinition from(
final Currency currency,
final ZonedDateTime paymentDate,
final ZonedDateTime accrualStartDate,
final ZonedDateTime accrualEndDate,
final double paymentAccrualFactor,
final double notional,
final IborIndex index,
final ZonedDateTime[] accrualStartDates,
final ZonedDateTime[] accrualEndDates,
final double[] paymentAccrualFactors,
final ZonedDateTime[] fixingDates,
final ZonedDateTime[] fixingPeriodStartDates,
final ZonedDateTime[] fixingPeriodEndDates,
final double[] fixingPeriodAccrualFactors,
final double spread,
final double initialRate) {
return new CouponIborCompoundingSimpleSpreadDefinition(
currency,
paymentDate,
accrualStartDate,
accrualEndDate,
paymentAccrualFactor,
notional,
index,
accrualStartDates,
accrualEndDates,
paymentAccrualFactors,
fixingDates,
fixingPeriodStartDates,
fixingPeriodEndDates,
fixingPeriodAccrualFactors,
spread,
initialRate);
}
/**
* 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.
* @param spread The spread paid above the Ibor rate.
* @param calendar The holiday calendar for the ibor index.
* @return The compounded coupon.
*/
public static CouponIborCompoundingSimpleSpreadDefinition from(final ZonedDateTime paymentDate, final double notional, final IborIndex index,
final ZonedDateTime[] accrualStartDates, final ZonedDateTime[] accrualEndDates, final double[] paymentAccrualFactors, final double spread,
final Calendar calendar) {
final int nbSubPeriod = accrualEndDates.length;
final ZonedDateTime accrualStartDate = accrualStartDates[0];
final ZonedDateTime accrualEndDate = accrualEndDates[nbSubPeriod - 1];
double paymentAccrualFactor = 0.0;
final ZonedDateTime[] fixingDates = new ZonedDateTime[nbSubPeriod];
final ZonedDateTime[] fixingPeriodEndDates = new ZonedDateTime[nbSubPeriod];
final double[] fixingPeriodAccrualFactors = new double[nbSubPeriod];
for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) {
paymentAccrualFactor += paymentAccrualFactors[loopsub];
fixingDates[loopsub] = ScheduleCalculator.getAdjustedDate(accrualStartDates[loopsub], -index.getSpotLag(), calendar);
fixingPeriodEndDates[loopsub] = ScheduleCalculator.getAdjustedDate(accrualStartDates[loopsub], index, calendar);
fixingPeriodAccrualFactors[loopsub] = index.getDayCount().getDayCountFraction(accrualStartDates[loopsub], fixingPeriodEndDates[loopsub], calendar);
}
return new CouponIborCompoundingSimpleSpreadDefinition(
index.getCurrency(),
paymentDate, accrualStartDate, accrualEndDate, paymentAccrualFactor,
notional, index,
accrualStartDates, accrualEndDates, paymentAccrualFactors,
fixingDates, accrualStartDates, fixingPeriodEndDates, fixingPeriodAccrualFactors,
spread, Double.NaN);
}
/**
* 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 adjusted end accrual date.
* The payment accrual factors are in the day count of the index.
* @param notional The coupon notional.
* @param accrualStartDate The first accrual date.
* @param accrualEndDate The end accrual date.
* @param index The underlying Ibor index.
* @param spread The spread paid above the Ibor rate.
* @param stub The stub type used for the compounding sub-periods. Not null.
* @param businessDayConvention The leg business day convention.
* @param endOfMonth The leg end-of-month convention.
* @param calendar The holiday calendar for the ibor leg.
* @return The compounded coupon.
*/
public static CouponIborCompoundingSimpleSpreadDefinition from(final double notional, final ZonedDateTime accrualStartDate, final ZonedDateTime accrualEndDate, final IborIndex index,
final double spread, final StubType stub, final BusinessDayConvention businessDayConvention, final boolean endOfMonth, final Calendar calendar) {
ArgumentChecker.notNull(accrualStartDate, "Accrual start date");
ArgumentChecker.notNull(accrualEndDate, "Accrual end date");
ArgumentChecker.notNull(index, "Index");
ArgumentChecker.notNull(stub, "Stub type");
ArgumentChecker.notNull(calendar, "Calendar");
final boolean isStubShort = stub.equals(StubType.SHORT_END) || stub.equals(StubType.SHORT_START);
final boolean isStubStart = stub.equals(StubType.LONG_START) || stub.equals(StubType.SHORT_START); // Implementation note: dates computed from the end.
final ZonedDateTime[] accrualEndDates = ScheduleCalculator.getAdjustedDateSchedule(accrualStartDate, accrualEndDate, index.getTenor(), isStubShort, isStubStart,
businessDayConvention, calendar, endOfMonth);
final int nbSubPeriod = accrualEndDates.length;
final ZonedDateTime[] accrualStartDates = new ZonedDateTime[nbSubPeriod];
accrualStartDates[0] = accrualStartDate;
System.arraycopy(accrualEndDates, 0, accrualStartDates, 1, nbSubPeriod - 1);
final double[] paymentAccrualFactors = new double[nbSubPeriod];
for (int loopsub = 0; loopsub < nbSubPeriod; loopsub++) {
paymentAccrualFactors[loopsub] = index.getDayCount().getDayCountFraction(accrualStartDates[loopsub], accrualEndDates[loopsub], calendar);
}
return from(accrualEndDates[nbSubPeriod - 1], notional, index, accrualStartDates, accrualEndDates, paymentAccrualFactors, spread, calendar);
}
/**
* 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[] getSubperiodsAccrualStartDates() {
return _subperiodAccrualStartDates;
}
/**
* Returns the accrual end dates of each sub-period.
* @return The dates.
*/
public ZonedDateTime[] getSubperiodsAccrualEndDates() {
return _subperiodAccrualEndDates;
}
/**
* Returns the payment accrual factors for each sub-period.
* @return The factors.
*/
public double[] getSubperiodsAccrualFactors() {
return _subperiodAccrualFactors;
}
/**
* 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[] getFixingSubperiodStartDates() {
return _fixingSubperiodStartDates;
}
/**
* Returns the fixing period end dates for each sub-period.
* @return The dates.
*/
public ZonedDateTime[] getFixingSubperiodEndDates() {
return _fixingSubperiodEndDates;
}
/**
* Returns the fixing period accrual factors for each sub-period.
* @return The factors.
*/
public double[] getFixingSubperiodAccrualFactors() {
return _fixingSubperiodAccrualFactors;
}
/**
* Returns the spread.
* @return the spread
*/
public double getSpread() {
return _spread;
}
/**
* Returns the rate of the first compounded period.
* @return the rate of the first compounded period.
*/
public double getInitialRate() {
return _initialRate;
}
@Override
public CouponIborCompoundingSimpleSpread toDerivative(final ZonedDateTime dateTime) {
final LocalDate dateConversion = dateTime.toLocalDate();
ArgumentChecker.isTrue(!dateConversion.isAfter(_fixingDates[0].toLocalDate()), "toDerivative without time series should have a date before the first fixing date.");
final double paymentTime = TimeCalculator.getTimeBetween(dateTime, getPaymentDate());
final double[] fixingTimes = TimeCalculator.getTimeBetween(dateTime, _fixingDates);
final double[] fixingPeriodStartTimes = TimeCalculator.getTimeBetween(dateTime, _fixingSubperiodStartDates);
final double[] fixingPeriodEndTimes = TimeCalculator.getTimeBetween(dateTime, _fixingSubperiodEndDates);
return new CouponIborCompoundingSimpleSpread(getCurrency(), paymentTime, getPaymentYearFraction(), getNotional(), getNotional(), _index, _subperiodAccrualFactors,
fixingTimes, fixingPeriodStartTimes, fixingPeriodEndTimes, _fixingSubperiodAccrualFactors, _spread);
}
@Override
public Coupon toDerivative(final ZonedDateTime dateTime, final DoubleTimeSeries<ZonedDateTime> indexFixingTimeSeries) {
final LocalDate dateLocal = dateTime.toLocalDate();
ArgumentChecker.notNull(indexFixingTimeSeries, "Index fixing time series");
ArgumentChecker.isTrue(!dateLocal.isAfter(getPaymentDate().toLocalDate()), "date is after payment date");
final double paymentTime = TimeCalculator.getTimeBetween(dateTime, getPaymentDate());
final int nbSubPeriods = _fixingDates.length;
int nbFixed = 0;
if (!Double.isNaN(_initialRate)) { // Force the initial rate to be used
nbFixed++;
}
while ((nbFixed < nbSubPeriods) && (dateLocal.isAfter(_fixingDates[nbFixed].toLocalDate()))) { // If fixing is strictly before today, period has fixed
nbFixed++;
}
if ((nbFixed < nbSubPeriods) && (dateLocal.equals(_fixingDates[nbFixed].toLocalDate()))) { // Not all periods already fixed, checking if todays fixing is available
final ZonedDateTime rezonedFixingDateNext = ZonedDateTime.of(LocalDateTime.of(_fixingDates[nbFixed].toLocalDate(), LocalTime.of(0, 0)), ZoneOffset.UTC);
final Double fixedRate = indexFixingTimeSeries.getValue(rezonedFixingDateNext);
if (fixedRate != null) {
nbFixed++;
}
}
double[] rateFixed = new double[nbFixed];
double cpa = 1.0d;
for (int loopsub = 0; loopsub < nbFixed; loopsub++) {
if (!Double.isNaN(_initialRate) && loopsub == 0) {
cpa *= 1.0d + _initialRate * _subperiodAccrualFactors[0];
} else {
final Double fixing;
final ZonedDateTime rezonedFixingDate = ZonedDateTime.of(LocalDateTime.of(_fixingDates[loopsub].toLocalDate(), LocalTime.of(0, 0)), ZoneOffset.UTC);
fixing = indexFixingTimeSeries.getValue(rezonedFixingDate);
if (fixing == null) {
throw new OpenGammaRuntimeException("Could not get fixing value for date " + rezonedFixingDate);
}
rateFixed[loopsub] = fixing;
cpa *= 1.0d + fixing * _subperiodAccrualFactors[loopsub];
}
}
if (nbFixed == nbSubPeriods) { // All dates already fixed: CouponFixed
double rate = (cpa - 1.0d) / getPaymentYearFraction() + _spread;
return new CouponFixed(getCurrency(), paymentTime, getPaymentYearFraction(), getNotional(), rate, getAccrualStartDate(), getAccrualEndDate());
}
// Copying the remaining periods
final int nbSubPeriodLeft = nbSubPeriods - nbFixed;
final double[] paymentAccrualFactorsLeft = new double[nbSubPeriodLeft];
System.arraycopy(_subperiodAccrualFactors, 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, _fixingSubperiodStartDates), nbFixed, fixingPeriodStartTimesLeft, 0, nbSubPeriodLeft);
final double[] fixingPeriodEndTimesLeft = new double[nbSubPeriodLeft];
System.arraycopy(TimeCalculator.getTimeBetween(dateTime, _fixingSubperiodEndDates), nbFixed, fixingPeriodEndTimesLeft, 0, nbSubPeriodLeft);
final double[] fixingPeriodAccrualFactorsLeft = new double[nbSubPeriodLeft];
System.arraycopy(_fixingSubperiodAccrualFactors, nbFixed, fixingPeriodAccrualFactorsLeft, 0, nbSubPeriodLeft);
return new CouponIborCompoundingSimpleSpread(getCurrency(), paymentTime, getPaymentYearFraction(), getNotional(), getNotional() * cpa, _index, paymentAccrualFactorsLeft,
fixingTimesLeft, fixingPeriodStartTimesLeft, fixingPeriodEndTimesLeft, fixingPeriodAccrualFactorsLeft, _spread);
}
@Override
public <U, V> V accept(final InstrumentDefinitionVisitor<U, V> visitor, final U data) {
ArgumentChecker.notNull(visitor, "visitor");
return visitor.visitCouponIborCompoundingSimpleSpreadDefinition(this, data);
}
@Override
public <V> V accept(final InstrumentDefinitionVisitor<?, V> visitor) {
ArgumentChecker.notNull(visitor, "visitor");
return visitor.visitCouponIborCompoundingSimpleSpreadDefinition(this);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Arrays.hashCode(_fixingDates);
result = prime * result + Arrays.hashCode(_fixingSubperiodAccrualFactors);
result = prime * result + Arrays.hashCode(_fixingSubperiodEndDates);
result = prime * result + Arrays.hashCode(_fixingSubperiodStartDates);
result = prime * result + ((_index == null) ? 0 : _index.hashCode());
long temp;
temp = Double.doubleToLongBits(_spread);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + Arrays.hashCode(_subperiodAccrualEndDates);
result = prime * result + Arrays.hashCode(_subperiodAccrualFactors);
result = prime * result + Arrays.hashCode(_subperiodAccrualStartDates);
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;
}
CouponIborCompoundingSimpleSpreadDefinition other = (CouponIborCompoundingSimpleSpreadDefinition) obj;
if (!Arrays.equals(_fixingDates, other._fixingDates)) {
return false;
}
if (!Arrays.equals(_fixingSubperiodAccrualFactors, other._fixingSubperiodAccrualFactors)) {
return false;
}
if (!Arrays.equals(_fixingSubperiodEndDates, other._fixingSubperiodEndDates)) {
return false;
}
if (!Arrays.equals(_fixingSubperiodStartDates, other._fixingSubperiodStartDates)) {
return false;
}
if (!ObjectUtils.equals(_index, other._index)) {
return false;
}
if (Double.doubleToLongBits(_spread) != Double.doubleToLongBits(other._spread)) {
return false;
}
if (!Arrays.equals(_subperiodAccrualEndDates, other._subperiodAccrualEndDates)) {
return false;
}
if (!Arrays.equals(_subperiodAccrualFactors, other._subperiodAccrualFactors)) {
return false;
}
if (!Arrays.equals(_subperiodAccrualStartDates, other._subperiodAccrualStartDates)) {
return false;
}
return true;
}
}