/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.instrument;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.LocalDate;
import com.opengamma.analytics.financial.forex.definition.ForexDefinition;
import com.opengamma.analytics.financial.forex.definition.ForexNonDeliverableForwardDefinition;
import com.opengamma.analytics.financial.instrument.annuity.AnnuityDefinition;
import com.opengamma.analytics.financial.instrument.cash.CashDefinition;
import com.opengamma.analytics.financial.instrument.fra.ForwardRateAgreementDefinition;
import com.opengamma.analytics.financial.instrument.payment.CouponFixedDefinition;
import com.opengamma.analytics.financial.instrument.payment.CouponIborDefinition;
import com.opengamma.analytics.financial.instrument.payment.CouponIborSpreadDefinition;
import com.opengamma.analytics.financial.instrument.payment.PaymentDefinition;
import com.opengamma.analytics.financial.instrument.payment.PaymentFixedDefinition;
import com.opengamma.analytics.financial.instrument.swap.SwapFixedIborDefinition;
import com.opengamma.analytics.financial.instrument.swap.SwapFixedIborSpreadDefinition;
import com.opengamma.timeseries.DoubleTimeSeries;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.MultipleCurrencyAmount;
/**
* Returns all of the known receive cash-flows, including floating payments that have fixed.
* The payments are always positive.
*/
public final class FixedReceiveCashFlowVisitor extends InstrumentDefinitionVisitorSameValueAdapter<DoubleTimeSeries<LocalDate>, Map<LocalDate, MultipleCurrencyAmount>> {
private static final Logger s_logger = LoggerFactory.getLogger(FixedReceiveCashFlowVisitor.class);
private static final FixedReceiveCashFlowVisitor INSTANCE = new FixedReceiveCashFlowVisitor();
public static FixedReceiveCashFlowVisitor getInstance() {
return INSTANCE;
}
private FixedReceiveCashFlowVisitor() {
super(Collections.<LocalDate, MultipleCurrencyAmount>emptyMap());
}
/**
* If the notional is negative (i.e. an amount is to be paid), returns an empty map.
* Otherwise, returns a map containing a single payment date and amount to be received.
* @param cash The cash definition, not null
* @return A map containing the (single) payment date and amount, or an empty map, as appropriate
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitCashDefinition(final CashDefinition cash) {
ArgumentChecker.notNull(cash, "cash");
if (cash.getNotional() < 0) {
return Collections.emptyMap();
}
final LocalDate endDate = cash.getEndDate().toLocalDate();
return Collections.singletonMap(endDate, MultipleCurrencyAmount.of(cash.getCurrency(), cash.getInterestAmount()));
}
/**
* If the notional is negative (i.e. an amount is to be paid), returns an empty map.
* Otherwise, returns a map containing a single payment date and amount to be received.
* @param cash The cash definition, not null
* @param indexFixingTimeSeries Not used
* @return A map containing the (single) payment date and amount, or an empty map, as appropriate
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitCashDefinition(final CashDefinition cash, final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
s_logger.info("An index fixing time series was supplied, but will not be used");
return visitCashDefinition(cash);
}
/**
* If the notional is negative (i.e. the payment is to be paid), returns an empty map.
* Otherwise, returns a map containing a single payment date and amount to be received.
* @param payment The payment, not null
* @return A map containing the (single) payment date and amount
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitPaymentFixedDefinition(final PaymentFixedDefinition payment) {
ArgumentChecker.notNull(payment, "payment");
if (payment.getReferenceAmount() < 0) {
return Collections.emptyMap();
}
final LocalDate endDate = payment.getPaymentDate().toLocalDate();
return Collections.singletonMap(endDate, MultipleCurrencyAmount.of(payment.getCurrency(), payment.getReferenceAmount()));
}
/**
* If the notional is negative (i.e. the payment is to be paid), returns an empty map.
* Otherwise, returns a map containing a single payment date and amount to be received.
* @param payment The payment, not null
* @param indexFixingTimeSeries Not used
* @return A map containing the (single) payment date and amount
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitPaymentFixedDefinition(final PaymentFixedDefinition payment, final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
s_logger.info("An index fixing time series was supplied, but will not be used");
return visitPaymentFixedDefinition(payment);
}
/**
* If the notional is negative (i.e. the coupon will be paid), returns an empty map.
* Otherwise, returns a map containing a single payment date and amount to be received.
* @param coupon The fixed coupon, not null
* @return A map containing the (single) payment date and amount
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitCouponFixedDefinition(final CouponFixedDefinition coupon) {
ArgumentChecker.notNull(coupon, "coupon");
if (coupon.getNotional() < 0) {
return Collections.emptyMap();
}
final LocalDate endDate = coupon.getPaymentDate().toLocalDate();
return Collections.singletonMap(endDate, MultipleCurrencyAmount.of(coupon.getCurrency(), coupon.getAmount()));
}
/**
* If the notional is negative (i.e. the coupon will be paid), returns an empty map.
* Otherwise, returns a map containing a single payment date and amount to be received.
* @param coupon The fixed coupon, not null
* @param indexFixingTimeSeries Not used
* @return A map containing the (single) payment date and amount
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitCouponFixedDefinition(final CouponFixedDefinition coupon, final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
s_logger.info("An index fixing time series was supplied, but will not be used");
return visitCouponFixedDefinition(coupon);
}
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitCouponIborDefinition(final CouponIborDefinition coupon) {
return visitCouponIborDefinition(coupon, null);
}
/**
* If the notional is negative (i.e. the coupon will be paid), returns an empty map.
* If the fixing date is before the last date in the index fixing time series (i.e. the fixing has taken place),
* returns a map containing a simple payment date and amount to be paid. Otherwise, returns
* an empty map.
* @param coupon The floating coupon, not null
* @param indexFixingTimeSeries The fixing time series, not null if the coupon is to be paid.
* @return A map containing the (single) payment date and amount if fixing has taken place, otherwise an empty map
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitCouponIborDefinition(final CouponIborDefinition coupon, final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
ArgumentChecker.notNull(coupon, "coupon");
if (coupon.getNotional() < 0) {
return Collections.emptyMap();
}
ArgumentChecker.notNull(indexFixingTimeSeries, "index fixing time series");
final LocalDate fixingDate = coupon.getFixingDate().toLocalDate();
if (!indexFixingTimeSeries.getLatestTime().isBefore(fixingDate)) {
final LocalDate endDate = coupon.getPaymentDate().toLocalDate();
if (indexFixingTimeSeries.getValue(fixingDate) != null) {
final double fixedRate = indexFixingTimeSeries.getValue(fixingDate);
final double payment = coupon.getNotional() * coupon.getPaymentYearFraction() * fixedRate;
return Collections.singletonMap(endDate, MultipleCurrencyAmount.of(coupon.getCurrency(), payment));
}
throw new IllegalArgumentException("Could not get fixing value for date " + fixingDate);
}
return Collections.emptyMap();
}
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitCouponIborSpreadDefinition(final CouponIborSpreadDefinition coupon) {
return visitCouponIborSpreadDefinition(coupon, null);
}
/**
* If the notional is negative (i.e. the coupon will be paid), returns an empty map.
* If the fixing date is before the last date in the index fixing time series (i.e. the fixing has taken place),
* returns a map containing a simple payment date and amount to be paid. Otherwise, returns
* an empty map.
* @param coupon The floating coupon, not null
* @param indexFixingTimeSeries The fixing time series, not null if the coupon is to be paid.
* @return A map containing the (single) payment date and amount if fixing has taken place, otherwise an empty map
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitCouponIborSpreadDefinition(final CouponIborSpreadDefinition coupon,
final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
ArgumentChecker.notNull(coupon, "coupon");
if (coupon.getNotional() < 0) {
return Collections.emptyMap();
}
ArgumentChecker.notNull(indexFixingTimeSeries, "index fixing time series");
final LocalDate fixingDate = coupon.getFixingDate().toLocalDate();
if (!indexFixingTimeSeries.getLatestTime().isBefore(fixingDate)) {
final LocalDate endDate = coupon.getPaymentDate().toLocalDate();
if (indexFixingTimeSeries.getValue(fixingDate) != null) {
final double fixedRate = indexFixingTimeSeries.getValue(fixingDate);
final double payment = coupon.getNotional() * coupon.getPaymentYearFraction() * (fixedRate + coupon.getSpread());
return Collections.singletonMap(endDate, MultipleCurrencyAmount.of(coupon.getCurrency(), payment));
}
throw new IllegalArgumentException("Could not get fixing value for date " + fixingDate);
}
return Collections.emptyMap();
}
/**
* If the FRA is a receiver, returns a map containing a single payment. Otherwise, throws an exception (as the index fixing series is needed).
* @param forwardRateAgreement The FRA, not null
* @return A map containing the (single) payment date and amount
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitForwardRateAgreementDefinition(final ForwardRateAgreementDefinition forwardRateAgreement) {
ArgumentChecker.notNull(forwardRateAgreement, "FRA");
ArgumentChecker.isTrue(forwardRateAgreement.getNotional() < 0, "Receive floating FRAs need an index fixing time series to find receive cash flows");
return visitForwardRateAgreementDefinition(forwardRateAgreement, null);
}
/**
* If the FRA is a receiver, or if the FRA is a payer and the fixing date is before the last date in the index fixing time series (i.e. the fixing has taken place),
* returns a map containing a single payment.
* @param forwardRateAgreement The FRA, not null
* @param indexFixingTimeSeries The fixing time series for the floating index, not null if the FRA is a receiver
* @return A map containing the (single) payment date and amount
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitForwardRateAgreementDefinition(final ForwardRateAgreementDefinition forwardRateAgreement,
final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
ArgumentChecker.notNull(forwardRateAgreement, "FRA");
final LocalDate endDate = forwardRateAgreement.getPaymentDate().toLocalDate();
if (forwardRateAgreement.getNotional() > 0) {
ArgumentChecker.notNull(indexFixingTimeSeries, "index fixing time series");
final LocalDate fixingDate = forwardRateAgreement.getFixingDate().toLocalDate();
if (!indexFixingTimeSeries.getLatestTime().isBefore(fixingDate)) {
if (indexFixingTimeSeries.getValue(fixingDate) != null) {
final double fixedRate = indexFixingTimeSeries.getValue(fixingDate);
final double payment = forwardRateAgreement.getPaymentYearFraction() * forwardRateAgreement.getNotional() * fixedRate;
return Collections.singletonMap(endDate, MultipleCurrencyAmount.of(forwardRateAgreement.getCurrency(), payment));
}
throw new IllegalArgumentException("Could not get fixing value for " + fixingDate);
}
return Collections.emptyMap();
}
final double payment = -forwardRateAgreement.getReferenceAmount() * forwardRateAgreement.getRate() * forwardRateAgreement.getFixingPeriodAccrualFactor();
return Collections.singletonMap(endDate, MultipleCurrencyAmount.of(forwardRateAgreement.getCurrency(), payment));
}
/**
* Returns a map containing all of the known payments to be received in an annuity. If there are no payments to be received, an empty map is returned.
* @param annuity The annuity, not null
* @param indexFixingTimeSeries The fixing time series for the floating index, not null
* @return A map containing the payment dates and amounts
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitAnnuityDefinition(final AnnuityDefinition<? extends PaymentDefinition> annuity,
final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
ArgumentChecker.notNull(annuity, "annuity");
return getDatesAndPaymentsFromAnnuity(annuity, indexFixingTimeSeries);
}
/**
* If the swap is a receiver, returns a map containing all of the fixed payments. Otherwise, throws an exception (as the index fixing series is needed).
* @param swap The swap, not null
* @return A map containing the fixed payment dates and amounts
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitSwapFixedIborDefinition(final SwapFixedIborDefinition swap) {
ArgumentChecker.notNull(swap, "swap");
ArgumentChecker.isFalse(swap.getFixedLeg().isPayer(), "Payer swaps need an index fixing series to calculate receive cash-flows");
return visitSwapFixedIborDefinition(swap, null);
}
/**
* If the swap is a receiver, returns a map containing all of the fixed payments. If the swap is a payer, returns a map containing
* all of the payment amounts that have been fixed.
* @param swap The swap, not null
* @param indexFixingTimeSeries The fixing time series for the floating index, not null
* @return A map containing the payment dates and amounts
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitSwapFixedIborDefinition(final SwapFixedIborDefinition swap, final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
ArgumentChecker.notNull(swap, "swap");
if (swap.getFixedLeg().isPayer()) {
ArgumentChecker.notNull(indexFixingTimeSeries, "index fixing time series");
return swap.getIborLeg().accept(this, indexFixingTimeSeries);
}
return swap.getFixedLeg().accept(this, indexFixingTimeSeries);
}
/**
* If the swap is a receiver, returns a map containing all of the fixed payments. Otherwise, throws an exception (as the index fixing series is needed).
* @param swap The swap, not null
* @return A map containing the fixed payment dates and amounts
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitSwapFixedIborSpreadDefinition(final SwapFixedIborSpreadDefinition swap) {
ArgumentChecker.notNull(swap, "swap");
ArgumentChecker.isFalse(swap.getFixedLeg().isPayer(), "Payer swaps need an index fixing series to calculate receive cash-flows");
return visitSwapFixedIborSpreadDefinition(swap, null);
}
/**
* If the swap is a receiver, returns a map containing all of the fixed payments. If the swap is a payer, returns a map containing
* all of the payments amounts that have been fixed.
* @param swap The swap, not null
* @param indexFixingTimeSeries The fixing time series for the floating index, not null
* @return A map containing the payment dates and amounts
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitSwapFixedIborSpreadDefinition(final SwapFixedIborSpreadDefinition swap,
final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
ArgumentChecker.notNull(swap, "swap");
if (swap.getFixedLeg().isPayer()) {
ArgumentChecker.notNull(indexFixingTimeSeries, "index fixing time series");
return swap.getIborLeg().accept(this, indexFixingTimeSeries);
}
return swap.getFixedLeg().accept(this, indexFixingTimeSeries);
}
/**
* Returns a map containing a single date and payment.
* @param fx The FX instrument, not null
* @return A map containing the (single) payment date and amount
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitForexDefinition(final ForexDefinition fx) {
ArgumentChecker.notNull(fx, "fx");
if (fx.getPaymentCurrency1().getReferenceAmount() > 0) {
return fx.getPaymentCurrency1().accept(this);
}
return fx.getPaymentCurrency2().accept(this);
}
/**
* Returns a map containing a single date and payment.
* @param fx The FX instrument, not null
* @param indexFixingTimeSeries Not used
* @return A map containing the (single) payment date and amount
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitForexDefinition(final ForexDefinition fx, final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
s_logger.info("An index fixing time series was supplied, but will not be used");
return visitForexDefinition(fx);
}
/**
* If the cash settlement amount is positive (i.e. it will be received), returns a map containing a single date and payment. Otherwise, returns
* an empty map.
* @param ndf The NDF, not null
* @return A map containing the (single) payment date and amount
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitForexNonDeliverableForwardDefinition(final ForexNonDeliverableForwardDefinition ndf) {
ArgumentChecker.notNull(ndf, "ndf");
if (ndf.getNotional() < 0) {
return Collections.emptyMap();
}
final LocalDate endDate = ndf.getPaymentDate().toLocalDate();
return Collections.singletonMap(endDate, MultipleCurrencyAmount.of(ndf.getCurrency2(), Math.abs(ndf.getNotional())));
}
/**
* If the cash settlement amount is positive (i.e. it will be received), returns a map containing a single date and payment. Otherwise, returns
* an empty map.
* @param ndf The NDF, not null
* @param indexFixingTimeSeries Not used
* @return A map containing the (single) payment date and amount
*/
@Override
public Map<LocalDate, MultipleCurrencyAmount> visitForexNonDeliverableForwardDefinition(final ForexNonDeliverableForwardDefinition ndf,
final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
s_logger.info("An index fixing time series was supplied but will not be used");
return visitForexNonDeliverableForwardDefinition(ndf);
}
private Map<LocalDate, MultipleCurrencyAmount> getDatesAndPaymentsFromAnnuity(final AnnuityDefinition<? extends PaymentDefinition> annuity,
final DoubleTimeSeries<LocalDate> indexFixingTimeSeries) {
final Map<LocalDate, MultipleCurrencyAmount> result = new HashMap<>();
for (final PaymentDefinition payment : annuity.getPayments()) {
final Map<LocalDate, MultipleCurrencyAmount> payments = payment.accept(this, indexFixingTimeSeries);
for (final Map.Entry<LocalDate, MultipleCurrencyAmount> entry : payments.entrySet()) {
final int scale = entry.getValue().getCurrencyAmounts()[0].getAmount() < 0 ? -1 : 1;
final MultipleCurrencyAmount mca = entry.getValue().multipliedBy(scale);
final LocalDate key = entry.getKey();
if (result.containsKey(key)) {
result.put(key, result.get(key).plus(mca));
} else {
result.put(key, mca);
}
}
}
return result;
}
}