/**
* Copyright (C) 2011 - 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 java.util.Arrays;
import java.util.List;
import org.threeten.bp.ZonedDateTime;
import com.opengamma.analytics.financial.instrument.InstrumentDefinitionVisitor;
import com.opengamma.analytics.financial.instrument.InstrumentDefinitionWithData;
import com.opengamma.analytics.financial.instrument.payment.PaymentDefinition;
import com.opengamma.analytics.financial.interestrate.annuity.derivative.Annuity;
import com.opengamma.analytics.financial.interestrate.payments.derivative.Payment;
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 generic annuity (or leg) with at least one payment. All the annuity payments are in the same currency.
* @param <P> The payment type
*
*/
public class AnnuityDefinition<P extends PaymentDefinition> implements InstrumentDefinitionWithData<Annuity<? extends Payment>, DoubleTimeSeries<ZonedDateTime>> {
/**
* The list of payments or coupons. All payments have the same currency. All payments have the same sign or are 0.
* There should be at least one payment.
*/
private final P[] _payments;
/**
* Flag indicating if the annuity is payer (true) or receiver (false). Deduced from the first non-zero amount;
* if all amounts don't have the same sign, the flag can be incorrect.
* @deprecated This flag does not work correctly if the amounts do not have the same sign or if the notionals
* are zero
*/
@Deprecated
private final boolean _isPayer;
/**
* The calendar, not null
*/
private final Calendar _calendar;
/**
* Constructor from an array of payments.
* @param payments The payments, not null. All of them should have the same currency.
* @param calendar The holiday calendar, not null
*/
public AnnuityDefinition(final P[] payments, final Calendar calendar) {
ArgumentChecker.noNulls(payments, "payments");
ArgumentChecker.isTrue(payments.length > 0, "Have no payments in annuity");
ArgumentChecker.notNull(calendar, "calendar");
final double amount = payments[0].getReferenceAmount();
final Currency currency0 = payments[0].getCurrency();
for (int loopcpn = 1; loopcpn < payments.length; loopcpn++) {
ArgumentChecker.isTrue(currency0.equals(payments[loopcpn].getCurrency()), "currency not the same for all payments");
}
_payments = payments;
_isPayer = amount < 0;
_calendar = calendar;
}
/**
* Gets the _payments field.
* @return the payments
*/
public P[] getPayments() {
return _payments;
}
/**
* Return one of the payments.
* @param n The payment index.
* @return The payment.
*/
public P getNthPayment(final int n) {
return _payments[n];
}
/**
* Return the currency of the annuity.
* @return The currency.
*/
public Currency getCurrency() {
return _payments[0].getCurrency();
}
/**
* Gets the isPayer field.
* @return isPayer flag.
* @deprecated The payer flag is no longer used; the sign of the notional
* determines whether a leg is paid or received
*/
@Deprecated
public boolean isPayer() {
return _isPayer;
}
/**
* The number of payments of the annuity.
* @return The number of payments.
*/
public int getNumberOfPayments() {
return _payments.length;
}
/**
* Gets the holiday calendar.
* @return The holiday calendar
*/
public Calendar getCalendar() {
return _calendar;
}
/**
* Remove the payments paying on or before the given date.
* @param trimDate The date.
* @return The trimmed annuity.
*/
public AnnuityDefinition<?> trimBefore(final ZonedDateTime trimDate) {
final List<PaymentDefinition> list = new ArrayList<>();
for (final PaymentDefinition payment : getPayments()) {
if (payment.getPaymentDate().isAfter(trimDate)) {
list.add(payment);
}
}
return new AnnuityDefinition<>(list.toArray(new PaymentDefinition[list.size()]), _calendar);
}
@Override
public String toString() {
final StringBuffer result = new StringBuffer("Annuity:");
for (final P payment : _payments) {
result.append(payment.toString());
result.append(" ");
}
return result.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (_isPayer ? 1231 : 1237);
result = prime * result + Arrays.hashCode(_payments);
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final AnnuityDefinition<?> other = (AnnuityDefinition<?>) obj;
if (_isPayer != other._isPayer) {
return false;
}
if (!Arrays.equals(_payments, other._payments)) {
return false;
}
return true;
}
@Override
public Annuity<? extends Payment> toDerivative(final ZonedDateTime date) {
ArgumentChecker.notNull(date, "date");
final List<Payment> resultList = new ArrayList<>();
for (int loopcoupon = 0; loopcoupon < _payments.length; loopcoupon++) {
if (!date.isAfter(_payments[loopcoupon].getPaymentDate())) {
resultList.add(_payments[loopcoupon].toDerivative(date));
}
}
return new Annuity<>(resultList.toArray(new Payment[resultList.size()]));
}
@SuppressWarnings("unchecked")
@Override
public Annuity<? extends Payment> toDerivative(final ZonedDateTime date, final DoubleTimeSeries<ZonedDateTime> indexFixingTS) {
ArgumentChecker.notNull(date, "date");
ArgumentChecker.notNull(indexFixingTS, "index fixing time series");
final List<Payment> resultList = new ArrayList<>();
for (final P payment : _payments) {
//TODO check this
//TODO The comparison should be done on LocalDate and not on ZonedDateTime, to avoid jumps during the day. PLAT-6872
if (!date.isAfter(payment.getPaymentDate())) {
if (payment instanceof InstrumentDefinitionWithData) {
resultList.add(((InstrumentDefinitionWithData<? extends Payment, DoubleTimeSeries<ZonedDateTime>>) payment).toDerivative(date, indexFixingTS));
} else {
resultList.add(payment.toDerivative(date));
}
}
}
return new Annuity<>(resultList.toArray(new Payment[resultList.size()]));
}
@Override
public <U, V> V accept(final InstrumentDefinitionVisitor<U, V> visitor, final U data) {
ArgumentChecker.notNull(visitor, "visitor");
return visitor.visitAnnuityDefinition(this, data);
}
@Override
public <V> V accept(final InstrumentDefinitionVisitor<?, V> visitor) {
ArgumentChecker.notNull(visitor, "visitor");
return visitor.visitAnnuityDefinition(this);
}
}