/**
* Copyright (C) 2014 - 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.NotImplementedException;
import org.threeten.bp.LocalDate;
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.CouponIborAverageFixingDates;
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.calendar.Calendar;
import com.opengamma.timeseries.DoubleTimeSeries;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.Currency;
/**
* Class describing an average coupon by weighted mean of index values with difference fixing dates.
*/
public class CouponIborAverageFixingDatesDefinition extends CouponDefinition implements InstrumentDefinitionWithData<Payment, DoubleTimeSeries<ZonedDateTime>> {
/** The index on which the fixing is done. The same index is used for all the fixings. */
private final IborIndex _index;
/** The fixing dates of the index. The dates are in increasing order. */
private final ZonedDateTime[] _fixingDate;
/** The weights or quantity used for each fixing. The total weight is not necessarily 1. Same size as _fixingDate. */
private final double[] _weight;
/** The start dates of the underlying index period. Same size as _fixingDate. */
private final ZonedDateTime[] _fixingPeriodStartDate;
/** The end dates of the underlying index period. Same size as _fixingDate. */
private final ZonedDateTime[] _fixingPeriodEndDate;
/** The index periods accrual factors. Same size as _fixingDate. */
private final double[] _fixingPeriodAccrualFactor;
/**
* Constructor.
* The start dates and end dates of fixing period are deduced from the index conventions.
* @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 coupon Ibor index
* @param fixingDate The coupon fixing dates
* @param weight The weights for the index
* @param iborCalendar The holiday calendar for the index
*/
public CouponIborAverageFixingDatesDefinition(final Currency currency, final ZonedDateTime paymentDate, final ZonedDateTime accrualStartDate, final ZonedDateTime accrualEndDate,
final double paymentAccrualFactor, final double notional, final IborIndex index, final ZonedDateTime[] fixingDate, final double[] weight, final Calendar iborCalendar) {
super(currency, paymentDate, accrualStartDate, accrualEndDate, paymentAccrualFactor, notional);
ArgumentChecker.notNull(fixingDate, "fixingDate");
ArgumentChecker.notNull(index, "index");
ArgumentChecker.notNull(weight, "weight");
ArgumentChecker.notNull(iborCalendar, "iborCalendar");
final int nDates = fixingDate.length;
ArgumentChecker.isTrue(nDates == weight.length, "weight length different from fixingDate length");
ArgumentChecker.isTrue(currency.equals(index.getCurrency()), "index currency different from payment currency");
for (int i = 0; i < nDates; ++i) {
ArgumentChecker.notNull(fixingDate[i], "element of fixingDate");
}
_fixingDate = Arrays.copyOf(fixingDate, nDates);
_index = index;
_weight = Arrays.copyOf(weight, nDates);
_fixingPeriodStartDate = new ZonedDateTime[nDates];
_fixingPeriodEndDate = new ZonedDateTime[nDates];
_fixingPeriodAccrualFactor = new double[nDates];
for (int i = 0; i < nDates; ++i) {
_fixingPeriodStartDate[i] = ScheduleCalculator.getAdjustedDate(fixingDate[i], index.getSpotLag(), iborCalendar);
_fixingPeriodEndDate[i] = ScheduleCalculator.getAdjustedDate(_fixingPeriodStartDate[i], index.getTenor(), index.getBusinessDayConvention(), iborCalendar, index.isEndOfMonth());
_fixingPeriodAccrualFactor[i] = index.getDayCount().getDayCountFraction(_fixingPeriodStartDate[i], _fixingPeriodEndDate[i], iborCalendar);
}
}
/**
* Constructor with start dates and end dates fixing period
* @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 fixingDate The coupon fixing dates
* @param index The coupon Ibor indices
* @param weight The weights for the indices
* @param fixingPeriodStartDate The start date of the fixing periods
* @param fixingPeriodEndDate The end date of the fixing periods
* @param fixingPeriodAccrualFactor The accrual factor of the fixing periods
*/
public CouponIborAverageFixingDatesDefinition(final Currency currency, final ZonedDateTime paymentDate, final ZonedDateTime accrualStartDate, final ZonedDateTime accrualEndDate,
final double paymentAccrualFactor, final double notional, final IborIndex index, final ZonedDateTime[] fixingDate, final double[] weight, final ZonedDateTime[] fixingPeriodStartDate,
final ZonedDateTime[] fixingPeriodEndDate, final double[] fixingPeriodAccrualFactor) {
super(currency, paymentDate, accrualStartDate, accrualEndDate, paymentAccrualFactor, notional);
ArgumentChecker.notNull(fixingDate, "fixingDate");
ArgumentChecker.notNull(index, "index");
ArgumentChecker.notNull(weight, "weight");
ArgumentChecker.notNull(fixingPeriodStartDate, "fixingPeriodStartDate");
ArgumentChecker.notNull(fixingPeriodEndDate, "fixingPeriodEndDate");
final int nRates = fixingDate.length;
ArgumentChecker.isTrue(nRates == weight.length, "weight length different from fixingDate length");
ArgumentChecker.isTrue(nRates == fixingPeriodStartDate.length, "fixingPeriodStartDate length different from fixingDate length");
ArgumentChecker.isTrue(nRates == fixingPeriodEndDate.length, "fixingPeriodEndDate length different from fixingDate length");
ArgumentChecker.isTrue(nRates == fixingPeriodAccrualFactor.length, "fixingPeriodAccrualFactor length different from fixingDate length");
ArgumentChecker.isTrue(currency.equals(index.getCurrency()), "index currency different from payment currency");
for (int i = 0; i < nRates; ++i) {
ArgumentChecker.notNull(fixingDate[i], "element of fixingDate");
ArgumentChecker.notNull(fixingPeriodStartDate[i], "element of fixingPeriodStartDate");
ArgumentChecker.notNull(fixingPeriodEndDate[i], "element of fixingPeriodEndDate");
}
_fixingDate = Arrays.copyOf(fixingDate, nRates);
_index = index;
_weight = Arrays.copyOf(weight, nRates);
_fixingPeriodStartDate = Arrays.copyOf(fixingPeriodStartDate, nRates);
_fixingPeriodEndDate = Arrays.copyOf(fixingPeriodEndDate, nRates);
_fixingPeriodAccrualFactor = Arrays.copyOf(fixingPeriodAccrualFactor, nRates);
}
/**
* Construct a coupon without start dates and end dates of fixing period
* @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 fixingDate The coupon fixing dates
* @param index The coupon Ibor indices
* @param weight The weights for the indices
* @param iborCalendar The holiday calendars for the indices
* @return The coupon
*/
public static CouponIborAverageFixingDatesDefinition from(final Currency currency, final ZonedDateTime paymentDate, final ZonedDateTime accrualStartDate, final ZonedDateTime accrualEndDate,
final double paymentAccrualFactor, final double notional, final IborIndex index, final ZonedDateTime[] fixingDate, final double[] weight, final Calendar iborCalendar) {
return new CouponIborAverageFixingDatesDefinition(currency, paymentDate, accrualStartDate, accrualEndDate, paymentAccrualFactor, notional, index, fixingDate, weight, iborCalendar);
}
/**
* Construct a coupon with start dates and end dates fixing period
* @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 fixingDate The coupon fixing dates
* @param index The coupon Ibor indices
* @param weight The weights for the indices
* @param fixingPeriodStartDate The start date of the fixing periods
* @param fixingPeriodEndDate The end date of the fixing periods
* @param fixingPeriodAccrualFactor The accrual factor of the fixing periods
* @return The coupon
*/
public static CouponIborAverageFixingDatesDefinition from(final Currency currency, final ZonedDateTime paymentDate, final ZonedDateTime accrualStartDate, final ZonedDateTime accrualEndDate,
final double paymentAccrualFactor, final double notional, final IborIndex index, final ZonedDateTime[] fixingDate, final double[] weight, final ZonedDateTime[] fixingPeriodStartDate,
final ZonedDateTime[] fixingPeriodEndDate, final double[] fixingPeriodAccrualFactor) {
return new CouponIborAverageFixingDatesDefinition(currency, paymentDate, accrualStartDate, accrualEndDate, paymentAccrualFactor, notional, index, fixingDate, weight, fixingPeriodStartDate,
fixingPeriodEndDate, fixingPeriodAccrualFactor);
}
/**
* Construct a new coupon with the same detail except notional
* @param notional The notional
* @return The coupon
*/
public CouponIborAverageFixingDatesDefinition withNotional(final double notional) {
return new CouponIborAverageFixingDatesDefinition(getCurrency(), getPaymentDate(), getAccrualStartDate(), getAccrualEndDate(), getPaymentYearFraction(), notional, getIndex(), getFixingDate(),
getWeight(), getFixingPeriodStartDate(), getFixingPeriodEndDate(), getFixingPeriodAccrualFactor());
}
@Override
public CouponIborAverageFixingDates toDerivative(final ZonedDateTime date) {
ArgumentChecker.notNull(date, "date");
final int nDates = _weight.length;
final LocalDate dayConversion = date.toLocalDate();
ArgumentChecker.isTrue(!dayConversion.isAfter(getPaymentDate().toLocalDate()), "date is after payment date");
ArgumentChecker.isTrue(!dayConversion.isAfter(getFixingDate()[0].toLocalDate()), "Do not have any fixing data but are asking for a derivative at " + date
+ " which is after fixing date " + getFixingDate()[0]);
// Fixing dates are in increasing order; only the first one need to be checked.
final double paymentTime = TimeCalculator.getTimeBetween(date, getPaymentDate());
final double[] fixingTime = new double[nDates];
final double[] fixingPeriodStartTime = new double[nDates];
final double[] fixingPeriodEndTime = new double[nDates];
for (int i = 0; i < nDates; ++i) {
fixingTime[i] = TimeCalculator.getTimeBetween(date, getFixingDate()[i]);
fixingPeriodStartTime[i] = TimeCalculator.getTimeBetween(date, getFixingPeriodStartDate()[i]);
fixingPeriodEndTime[i] = TimeCalculator.getTimeBetween(date, getFixingPeriodEndDate()[i]);
}
return new CouponIborAverageFixingDates(getCurrency(), paymentTime, getPaymentYearFraction(), getNotional(),
getIndex(), fixingTime, getWeight(), fixingPeriodStartTime, fixingPeriodEndTime, getFixingPeriodAccrualFactor(), 0);
}
@Override
public Coupon toDerivative(ZonedDateTime dateTime, DoubleTimeSeries<ZonedDateTime> indexFixingTimeSeries) {
ArgumentChecker.notNull(dateTime, "date");
final LocalDate dayConversion = dateTime.toLocalDate();
ArgumentChecker.notNull(indexFixingTimeSeries, "Index fixing time series");
ArgumentChecker.isTrue(!dayConversion.isAfter(getPaymentDate().toLocalDate()), "date is after payment date");
final double paymentTime = TimeCalculator.getTimeBetween(dateTime, getPaymentDate());
final int nDates = getFixingDate().length;
if (dayConversion.isBefore(getFixingDate()[0].toLocalDate())) {
return toDerivative(dateTime);
}
int position = 0;
double amountAccrued = 0.;
while (position < nDates && dayConversion.isAfter(getFixingDate()[position].toLocalDate())) { // Strictly after fixing date: fixing value should be available
final Double fixedRate = indexFixingTimeSeries.getValue(getFixingDate()[position]);
if (fixedRate == null) {
throw new OpenGammaRuntimeException("Could not get fixing value for date " + getFixingDate()[position]);
}
amountAccrued += getWeight()[position] * fixedRate;
++position;
}
if (position < nDates && dayConversion.equals(getFixingDate()[position].toLocalDate())) { // On Fixing date: use fixing value if available
final Double fixedRate = indexFixingTimeSeries.getValue(getFixingDate()[position]);
if (fixedRate != null) {
amountAccrued += getWeight()[position] * fixedRate;
++position;
}
}
if (position == nDates) { // If all fixing are known, return a couponFixed.
return new CouponFixed(getCurrency(), paymentTime, getPaymentYearFraction(), getNotional(), amountAccrued, getAccrualStartDate(), getAccrualEndDate());
}
final int nDatesLeft = nDates - position; // If not all fixing are known, create a average coupon on the remaining period.
final double[] fixingTime = new double[nDatesLeft];
final double[] fixingPeriodStartTime = new double[nDatesLeft];
final double[] fixingPeriodEndTime = new double[nDatesLeft];
for (int i = 0; i < nDatesLeft; ++i) {
fixingTime[i] = TimeCalculator.getTimeBetween(dateTime, getFixingDate()[position + i]);
fixingPeriodStartTime[i] = TimeCalculator.getTimeBetween(dateTime, getFixingPeriodStartDate()[position + i]);
fixingPeriodEndTime[i] = TimeCalculator.getTimeBetween(dateTime, getFixingPeriodEndDate()[position + i]);
}
final double[] weightLeft = new double[nDatesLeft];
final double[] fixingPeriodAccrualFactorLeft = new double[nDatesLeft];
System.arraycopy(getWeight(), position, weightLeft, 0, nDatesLeft);
System.arraycopy(getFixingPeriodAccrualFactor(), position, fixingPeriodAccrualFactorLeft, 0, nDatesLeft);
return new CouponIborAverageFixingDates(getCurrency(), paymentTime, getPaymentYearFraction(), getNotional(),
getIndex(), fixingTime, weightLeft, fixingPeriodStartTime, fixingPeriodEndTime, fixingPeriodAccrualFactorLeft, amountAccrued);
}
@Override
public <U, V> V accept(final InstrumentDefinitionVisitor<U, V> visitor, final U data) {
ArgumentChecker.notNull(visitor, "visitor");
return visitor.visitCouponIborAverageFixingDatesDefinition(this, data);
}
@Override
public <V> V accept(final InstrumentDefinitionVisitor<?, V> visitor) {
ArgumentChecker.notNull(visitor, "visitor");
return visitor.visitCouponIborAverageFixingDatesDefinition(this);
}
/**
* Gets the fixingDate.
* @return the fixingDate
*/
public ZonedDateTime[] getFixingDate() {
return _fixingDate;
}
/**
* Gets the index.
* @return the index
*/
public IborIndex getIndex() {
return _index;
}
/**
* Gets the weight.
* @return the weight
*/
public double[] getWeight() {
return _weight;
}
/**
* Gets the fixingPeriodStartDate.
* @return the fixingPeriodStartDate
*/
public ZonedDateTime[] getFixingPeriodStartDate() {
return _fixingPeriodStartDate;
}
/**
* Gets the fixingPeriodEndDate.
* @return the fixingPeriodEndDate
*/
public ZonedDateTime[] getFixingPeriodEndDate() {
return _fixingPeriodEndDate;
}
/**
* Gets the fixingPeriodAccrualFactor.
* @return the fixingPeriodAccrualFactor
*/
public double[] getFixingPeriodAccrualFactor() {
return _fixingPeriodAccrualFactor;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Arrays.hashCode(_fixingDate);
result = prime * result + Arrays.hashCode(_fixingPeriodAccrualFactor);
result = prime * result + Arrays.hashCode(_fixingPeriodEndDate);
result = prime * result + Arrays.hashCode(_fixingPeriodStartDate);
result = prime * result + ((_index == null) ? 0 : _index.hashCode());
result = prime * result + Arrays.hashCode(_weight);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof CouponIborAverageFixingDatesDefinition)) {
return false;
}
CouponIborAverageFixingDatesDefinition other = (CouponIborAverageFixingDatesDefinition) obj;
if (!Arrays.equals(_fixingDate, other._fixingDate)) {
return false;
}
if (!Arrays.equals(_fixingPeriodAccrualFactor, other._fixingPeriodAccrualFactor)) {
return false;
}
if (!Arrays.equals(_fixingPeriodEndDate, other._fixingPeriodEndDate)) {
return false;
}
if (!Arrays.equals(_fixingPeriodStartDate, other._fixingPeriodStartDate)) {
return false;
}
if (_index == null) {
if (other._index != null) {
return false;
}
} else if (!_index.equals(other._index)) {
return false;
}
if (!Arrays.equals(_weight, other._weight)) {
return false;
}
return true;
}
}