All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.opengamma.strata.pricer.impl.rate.swap.CashFlowEquivalentCalculator Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.pricer.impl.rate.swap;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.Payment;
import com.opengamma.strata.basics.index.IborIndex;
import com.opengamma.strata.basics.index.IborIndexObservation;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder;
import com.opengamma.strata.pricer.DiscountFactors;
import com.opengamma.strata.pricer.ZeroRateSensitivity;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.product.common.PayReceive;
import com.opengamma.strata.product.rate.FixedRateComputation;
import com.opengamma.strata.product.rate.IborRateComputation;
import com.opengamma.strata.product.rate.OvernightCompoundedRateComputation;
import com.opengamma.strata.product.rate.RateComputation;
import com.opengamma.strata.product.swap.NotionalExchange;
import com.opengamma.strata.product.swap.RateAccrualPeriod;
import com.opengamma.strata.product.swap.RatePaymentPeriod;
import com.opengamma.strata.product.swap.ResolvedSwap;
import com.opengamma.strata.product.swap.ResolvedSwapLeg;
import com.opengamma.strata.product.swap.SwapLegType;
import com.opengamma.strata.product.swap.SwapPaymentEvent;
import com.opengamma.strata.product.swap.SwapPaymentPeriod;

/**
 * Computes cash flow equivalent of products.
 * 

* Reference: Henrard, M. The Irony in the derivatives discounting Part II: the crisis. Wilmott Journal, 2010, 2, 301-316. */ public final class CashFlowEquivalentCalculator { /** * Computes cash flow equivalent of swap. *

* The swap should be a fix-for-Ibor swap without compounding, and its swap legs * should not involve {@code PaymentEvent}. *

* The return type is {@code ResolvedSwapLeg} in which individual payments are * represented in terms of {@code NotionalExchange}. * * @param swap the swap product * @param ratesProvider the rates provider * @return the cash flow equivalent */ public static ResolvedSwapLeg cashFlowEquivalentSwap(ResolvedSwap swap, RatesProvider ratesProvider) { List cfEquivalent = new ArrayList<>(); for (ResolvedSwapLeg leg : swap.getLegs()) { if (leg.getType().equals(SwapLegType.FIXED)) { cfEquivalent.addAll(cashFlowEquivalentFixedLeg(leg, ratesProvider).getPaymentEvents()); } else if (leg.getType().equals(SwapLegType.IBOR)) { cfEquivalent.addAll(cashFlowEquivalentIborLeg(leg, ratesProvider).getPaymentEvents()); } else if (leg.getType().equals(SwapLegType.OVERNIGHT)) { cfEquivalent.addAll(cashFlowEquivalentOnLeg(leg, ratesProvider).getPaymentEvents()); } else { throw new IllegalArgumentException("leg type must be FIXED, IBOR or OVERNIGHT"); } } ResolvedSwapLeg leg = ResolvedSwapLeg.builder() .paymentEvents(cfEquivalent) .payReceive(PayReceive.RECEIVE) .type(SwapLegType.OTHER) .build(); return leg; } /** * Computes cash flow equivalent of Ibor leg. *

* The return type is {@code ResolvedSwapLeg} in which individual payments are * represented in terms of {@code NotionalExchange}. * * @param iborLeg the Ibor leg * @param ratesProvider the rates provider * @return the cash flow equivalent */ public static ResolvedSwapLeg cashFlowEquivalentIborLeg(ResolvedSwapLeg iborLeg, RatesProvider ratesProvider) { ArgChecker.isTrue(iborLeg.getType().equals(SwapLegType.IBOR), "Leg type should be IBOR"); ArgChecker.isTrue(iborLeg.getPaymentEvents().isEmpty(), "PaymentEvent should be empty"); List paymentEvents = new ArrayList(); for (SwapPaymentPeriod paymentPeriod : iborLeg.getPaymentPeriods()) { ArgChecker.isTrue(paymentPeriod instanceof RatePaymentPeriod, "rate payment should be RatePaymentPeriod"); RatePaymentPeriod ratePaymentPeriod = (RatePaymentPeriod) paymentPeriod; ArgChecker.isTrue(ratePaymentPeriod.getAccrualPeriods().size() == 1, "rate payment should not be compounding"); RateAccrualPeriod rateAccrualPeriod = ratePaymentPeriod.getAccrualPeriods().get(0); CurrencyAmount notional = ratePaymentPeriod.getNotionalAmount(); LocalDate paymentDate = ratePaymentPeriod.getPaymentDate(); IborIndexObservation obs = ((IborRateComputation) rateAccrualPeriod.getRateComputation()).getObservation(); IborIndex index = obs.getIndex(); LocalDate fixingStartDate = obs.getEffectiveDate(); double fixingYearFraction = obs.getYearFraction(); double beta = (1d + fixingYearFraction * ratesProvider.iborIndexRates(index).rate(obs)) * ratesProvider.discountFactor(paymentPeriod.getCurrency(), paymentPeriod.getPaymentDate()) / ratesProvider.discountFactor(paymentPeriod.getCurrency(), fixingStartDate); double ycRatio = rateAccrualPeriod.getYearFraction() / fixingYearFraction; NotionalExchange payStart = NotionalExchange.of(notional.multipliedBy(beta * ycRatio), fixingStartDate); NotionalExchange payEnd = NotionalExchange.of(notional.multipliedBy(-ycRatio), paymentDate); paymentEvents.add(payStart); paymentEvents.add(payEnd); } ResolvedSwapLeg leg = ResolvedSwapLeg.builder() .paymentEvents(paymentEvents) .payReceive(PayReceive.RECEIVE) .type(SwapLegType.OTHER) .build(); return leg; } /** * Computes cash flow equivalent of fixed leg. *

* The return type is {@code ResolvedSwapLeg} in which individual payments are * represented in terms of {@code NotionalExchange}. * * @param fixedLeg the fixed leg * @param ratesProvider the rates provider * @return the cash flow equivalent */ public static ResolvedSwapLeg cashFlowEquivalentFixedLeg(ResolvedSwapLeg fixedLeg, RatesProvider ratesProvider) { ArgChecker.isTrue(fixedLeg.getType().equals(SwapLegType.FIXED), "Leg type should be FIXED"); ArgChecker.isTrue(fixedLeg.getPaymentEvents().isEmpty(), "PaymentEvent should be empty"); List paymentEvents = new ArrayList(); for (SwapPaymentPeriod paymentPeriod : fixedLeg.getPaymentPeriods()) { ArgChecker.isTrue(paymentPeriod instanceof RatePaymentPeriod, "rate payment should be RatePaymentPeriod"); RatePaymentPeriod ratePaymentPeriod = (RatePaymentPeriod) paymentPeriod; ArgChecker.isTrue(ratePaymentPeriod.getAccrualPeriods().size() == 1, "rate payment should not be compounding"); RateAccrualPeriod rateAccrualPeriod = ratePaymentPeriod.getAccrualPeriods().get(0); double factor = rateAccrualPeriod.getYearFraction() * ((FixedRateComputation) rateAccrualPeriod.getRateComputation()).getRate(); CurrencyAmount notional = ratePaymentPeriod.getNotionalAmount().multipliedBy(factor); LocalDate paymentDate = ratePaymentPeriod.getPaymentDate(); NotionalExchange pay = NotionalExchange.of(notional, paymentDate); paymentEvents.add(pay); } ResolvedSwapLeg leg = ResolvedSwapLeg.builder() .paymentEvents(paymentEvents) .payReceive(PayReceive.RECEIVE) .type(SwapLegType.OTHER) .build(); return leg; } /** * Computes cash flow equivalent of overnight leg. *

* Each payment period should contain one accrual period of type {@link OvernightCompoundedRateComputation}. * When the payment date is not equal to the end composition date, the start and end cashflow equivalent are * adjusted by the ratio of discount factors between the payment date and the end date. *

* The return type is {@code ResolvedSwapLeg} in which individual payments are * represented in terms of {@code NotionalExchange}. * * @param onLeg the overnight leg * @param multicurve the multi-curve rates provider * @return the cash flow equivalent */ public static ResolvedSwapLeg cashFlowEquivalentOnLeg( ResolvedSwapLeg onLeg, RatesProvider multicurve) { Currency ccy = onLeg.getCurrency(); ArgChecker.isTrue(onLeg.getType().equals(SwapLegType.OVERNIGHT), "Leg type should be OVERNIGHT"); ArgChecker.isTrue(onLeg.getPaymentEvents().isEmpty(), "PaymentEvent should be empty"); List paymentEvents = new ArrayList(); for (SwapPaymentPeriod paymentPeriod : onLeg.getPaymentPeriods()) { ArgChecker.isTrue(paymentPeriod instanceof RatePaymentPeriod, "rate payment should be RatePaymentPeriod"); RatePaymentPeriod ratePaymentPeriod = (RatePaymentPeriod) paymentPeriod; ArgChecker.isTrue(ratePaymentPeriod.getAccrualPeriods().size() == 1, "rate payment should not be compounding"); RateAccrualPeriod rateAccrualPeriod = ratePaymentPeriod.getAccrualPeriods().get(0); CurrencyAmount notional = ratePaymentPeriod.getNotionalAmount(); RateComputation rateComputation = rateAccrualPeriod.getRateComputation(); ArgChecker.isTrue(rateComputation instanceof OvernightCompoundedRateComputation, "RateComputation should be of type OvernightCompoundedRateComputation"); OvernightCompoundedRateComputation onComputation = (OvernightCompoundedRateComputation) rateComputation; LocalDate startDate = rateAccrualPeriod.getStartDate(); LocalDate endDate = rateAccrualPeriod.getEndDate(); double computationAccrual = onComputation.getIndex().getDayCount().yearFraction(startDate, endDate); LocalDate paymentDate = ratePaymentPeriod.getPaymentDate(); double paymentAccural = rateAccrualPeriod.getYearFraction(); double payDateRatio = multicurve.discountFactor(ccy, paymentDate) / multicurve.discountFactor(ccy, endDate); NotionalExchange payStart = NotionalExchange.of( notional.multipliedBy(payDateRatio * paymentAccural / computationAccrual), startDate); double spread = rateAccrualPeriod.getSpread(); NotionalExchange payEnd = NotionalExchange.of( notional.multipliedBy(-paymentAccural / computationAccrual + spread * paymentAccural), paymentDate); paymentEvents.add(payStart); paymentEvents.add(payEnd); } ResolvedSwapLeg leg = ResolvedSwapLeg.builder() .paymentEvents(paymentEvents) .payReceive(PayReceive.RECEIVE) .type(SwapLegType.OTHER) .build(); return leg; } //------------------------------------------------------------------------- /** * Computes cash flow equivalent and sensitivity of swap. *

* The swap should be a fix-for-Ibor swap without compounding, and its swap legs should not involve {@code PaymentEvent}. *

* The return type is a map of {@code NotionalExchange} and {@code PointSensitivityBuilder}. * * @param swap the swap product * @param ratesProvider the rates provider * @return the cash flow equivalent and sensitivity */ public static ImmutableMap cashFlowEquivalentAndSensitivitySwap( ResolvedSwap swap, RatesProvider ratesProvider) { Builder cfEquivalentSensitivity = ImmutableMap.builder(); for (ResolvedSwapLeg leg : swap.getLegs()) { if (leg.getType().equals(SwapLegType.FIXED)) { cfEquivalentSensitivity.putAll(cashFlowEquivalentAndSensitivityFixedLeg(leg, ratesProvider)); } else if (leg.getType().equals(SwapLegType.IBOR)) { cfEquivalentSensitivity.putAll(cashFlowEquivalentAndSensitivityIborLeg(leg, ratesProvider)); } else if (leg.getType().equals(SwapLegType.OVERNIGHT)) { cfEquivalentSensitivity.putAll(cashFlowEquivalentAndSensitivityOnLeg(leg, ratesProvider)); } else { throw new IllegalArgumentException("leg type must be FIXED, IBOR or OVERNIGHT"); } } return cfEquivalentSensitivity.build(); } /** * Computes cash flow equivalent and sensitivity of Ibor leg. *

* The return type is a map of {@code NotionalExchange} and {@code PointSensitivityBuilder}. * * @param iborLeg the Ibor leg * @param ratesProvider the rates provider * @return the cash flow equivalent and sensitivity */ public static ImmutableMap cashFlowEquivalentAndSensitivityIborLeg( ResolvedSwapLeg iborLeg, RatesProvider ratesProvider) { ArgChecker.isTrue(iborLeg.getType().equals(SwapLegType.IBOR), "Leg type should be IBOR"); ArgChecker.isTrue(iborLeg.getPaymentEvents().isEmpty(), "PaymentEvent should be empty"); Map res = new HashMap(); for (SwapPaymentPeriod paymentPeriod : iborLeg.getPaymentPeriods()) { ArgChecker.isTrue(paymentPeriod instanceof RatePaymentPeriod, "rate payment should be RatePaymentPeriod"); RatePaymentPeriod ratePaymentPeriod = (RatePaymentPeriod) paymentPeriod; ArgChecker.isTrue(ratePaymentPeriod.getAccrualPeriods().size() == 1, "rate payment should not be compounding"); RateAccrualPeriod rateAccrualPeriod = ratePaymentPeriod.getAccrualPeriods().get(0); CurrencyAmount notional = ratePaymentPeriod.getNotionalAmount(); LocalDate paymentDate = ratePaymentPeriod.getPaymentDate(); IborIndexObservation obs = ((IborRateComputation) rateAccrualPeriod.getRateComputation()).getObservation(); IborIndex index = obs.getIndex(); LocalDate fixingStartDate = obs.getEffectiveDate(); double fixingYearFraction = obs.getYearFraction(); double factorIndex = (1d + fixingYearFraction * ratesProvider.iborIndexRates(index).rate(obs)); double dfPayment = ratesProvider.discountFactor(paymentPeriod.getCurrency(), paymentPeriod.getPaymentDate()); double dfStart = ratesProvider.discountFactor(paymentPeriod.getCurrency(), fixingStartDate); double beta = factorIndex * dfPayment / dfStart; double ycRatio = rateAccrualPeriod.getYearFraction() / fixingYearFraction; Payment payStart = Payment.of(notional.multipliedBy(beta * ycRatio), fixingStartDate); Payment payEnd = Payment.of(notional.multipliedBy(-ycRatio), paymentDate); double factor = ycRatio * notional.getAmount() / dfStart; PointSensitivityBuilder factorIndexSensi = ratesProvider.iborIndexRates(index) .ratePointSensitivity(obs).multipliedBy(fixingYearFraction * dfPayment * factor); PointSensitivityBuilder dfPaymentSensitivity = ratesProvider.discountFactors(paymentPeriod.getCurrency()) .zeroRatePointSensitivity(paymentPeriod.getPaymentDate()).multipliedBy(factorIndex * factor); PointSensitivityBuilder dfStartSensitivity = ratesProvider.discountFactors(paymentPeriod.getCurrency()) .zeroRatePointSensitivity(fixingStartDate).multipliedBy(-factorIndex * dfPayment * factor / dfStart); res.put(payStart, factorIndexSensi.combinedWith(dfPaymentSensitivity).combinedWith(dfStartSensitivity)); res.put(payEnd, PointSensitivityBuilder.none()); } return ImmutableMap.copyOf(res); } /** * Computes cash flow equivalent and sensitivity of fixed leg. *

* The return type is a map of {@code NotionalExchange} and {@code PointSensitivityBuilder}. * * @param fixedLeg the fixed leg * @param ratesProvider the rates provider * @return the cash flow equivalent and sensitivity */ public static ImmutableMap cashFlowEquivalentAndSensitivityFixedLeg( ResolvedSwapLeg fixedLeg, RatesProvider ratesProvider) { ArgChecker.isTrue(fixedLeg.getType().equals(SwapLegType.FIXED), "Leg type should be FIXED"); ArgChecker.isTrue(fixedLeg.getPaymentEvents().isEmpty(), "PaymentEvent should be empty"); Map res = new HashMap(); for (SwapPaymentPeriod paymentPeriod : fixedLeg.getPaymentPeriods()) { ArgChecker.isTrue(paymentPeriod instanceof RatePaymentPeriod, "rate payment should be RatePaymentPeriod"); RatePaymentPeriod ratePaymentPeriod = (RatePaymentPeriod) paymentPeriod; ArgChecker.isTrue(ratePaymentPeriod.getAccrualPeriods().size() == 1, "rate payment should not be compounding"); RateAccrualPeriod rateAccrualPeriod = ratePaymentPeriod.getAccrualPeriods().get(0); double factor = rateAccrualPeriod.getYearFraction() * ((FixedRateComputation) rateAccrualPeriod.getRateComputation()).getRate(); CurrencyAmount notional = ratePaymentPeriod.getNotionalAmount().multipliedBy(factor); LocalDate paymentDate = ratePaymentPeriod.getPaymentDate(); Payment pay = Payment.of(notional, paymentDate); res.put(pay, PointSensitivityBuilder.none()); } return ImmutableMap.copyOf(res); } /** * Computes cash flow equivalent of and sensitivity overnight leg. *

* Each payment period should contain one accrual period of type {@link OvernightCompoundedRateComputation}. * When the payment date is not equal to the end composition date, the start and end cashflow equivalent are * adjusted by the ratio of discount factors between the payment date and the end date. *

* The return type is a map of {@code NotionalExchange} and {@code PointSensitivityBuilder}. * * @param onLeg the overnight leg * @param multicurve the multi-curve rates provider * @return the cash flow equivalent */ public static ImmutableMap cashFlowEquivalentAndSensitivityOnLeg( ResolvedSwapLeg onLeg, RatesProvider multicurve) { Currency ccy = onLeg.getCurrency(); DiscountFactors df = multicurve.discountFactors(ccy); ArgChecker.isTrue(onLeg.getType().equals(SwapLegType.OVERNIGHT), "Leg type should be OVERNIGHT"); ArgChecker.isTrue(onLeg.getPaymentEvents().isEmpty(), "PaymentEvent should be empty"); Map res = new HashMap(); for (SwapPaymentPeriod paymentPeriod : onLeg.getPaymentPeriods()) { ArgChecker.isTrue(paymentPeriod instanceof RatePaymentPeriod, "rate payment should be RatePaymentPeriod"); RatePaymentPeriod ratePaymentPeriod = (RatePaymentPeriod) paymentPeriod; ArgChecker.isTrue(ratePaymentPeriod.getAccrualPeriods().size() == 1, "rate payment should not be compounding"); RateAccrualPeriod rateAccrualPeriod = ratePaymentPeriod.getAccrualPeriods().get(0); CurrencyAmount notional = ratePaymentPeriod.getNotionalAmount(); RateComputation rateComputation = rateAccrualPeriod.getRateComputation(); ArgChecker.isTrue(rateComputation instanceof OvernightCompoundedRateComputation, "RateComputation should be of type OvernightCompoundedRateComputation"); OvernightCompoundedRateComputation onComputation = (OvernightCompoundedRateComputation) rateComputation; LocalDate startDate = rateAccrualPeriod.getStartDate(); LocalDate endDate = rateAccrualPeriod.getEndDate(); double computationAccrual = onComputation.getIndex().getDayCount().yearFraction(startDate, endDate); LocalDate paymentDate = ratePaymentPeriod.getPaymentDate(); double paymentAccural = rateAccrualPeriod.getYearFraction(); double dfPay = df.discountFactor(paymentDate); double dfEnd = df.discountFactor(endDate); ZeroRateSensitivity dfPayDr = df.zeroRatePointSensitivity(paymentDate); ZeroRateSensitivity dfEndDr = df.zeroRatePointSensitivity(endDate); double payDateRatio = dfPay / dfEnd; PointSensitivityBuilder payDateRatioDr = dfPayDr.multipliedBy(1.0d / dfEnd) .combinedWith(dfEndDr.multipliedBy(-dfPay / (dfEnd * dfEnd))); Payment payStart = Payment.of( notional.multipliedBy(payDateRatio * paymentAccural / computationAccrual), startDate); double spread = rateAccrualPeriod.getSpread(); Payment payEnd = Payment.of( notional.multipliedBy(-paymentAccural / computationAccrual + spread * paymentAccural), paymentDate); res.put(payStart, payDateRatioDr .multipliedBy(notional.getAmount() * paymentAccural / computationAccrual)); res.put(payEnd, PointSensitivityBuilder.none()); } return ImmutableMap.copyOf(res); } /** * Extract the payments from the {@link NotionalExchange} in the SwapLeg. * Generate a list with the dates sorted and the amounts of elements with same payment date compressed. *

* The original SwapLeg is unchanged. * * @param input the starting swap leg * @return the normalized list */ public static List normalize(ResolvedSwapLeg input) { List cfe2 = new ArrayList<>(); for (SwapPaymentEvent cf : input.getPaymentEvents()) { ArgChecker.isTrue(cf instanceof NotionalExchange, "the swap leg must consist of NotionalExchange"); cfe2.add(((NotionalExchange) cf).getPayment()); } return normalize(cfe2); } /** * Generate a new payment list with the dates sorted and the amounts of elements with same payment date compressed. *

* The original list is unchanged. * * @param input the starting list * @return the normalized list */ public static List normalize(List input) { List sorted = new ArrayList<>(input); // copy for sorting Collections.sort(sorted, (a, b) -> (int) (a.getDate().toEpochDay() - b.getDate().toEpochDay())); Payment previous = sorted.get(0); for (int i = 1; i < sorted.size(); i++) { Payment current = sorted.get(i); if (current.getDate().equals(previous.getDate()) && current.getCurrency().equals(previous.getCurrency())) { current = Payment.of( CurrencyAmount.of(current.getCurrency(), current.getAmount() + previous.getAmount()), current.getDate()); sorted.set(i - 1, current); sorted.remove(i); i--; } previous = current; } return sorted; } /** * Generate a new map with each payment date unique and the amounts and sensitivities of elements * with same payment date compressed. *

* The original map is unchanged. * * @param input the starting map * @return the normalized map */ public static Map normalize(Map input) { Map output = new HashMap<>(input); List sortedPayments = new ArrayList<>(input.keySet()); // copy for sorting Collections.sort(sortedPayments, (a, b) -> (int) (a.getDate().toEpochDay() - b.getDate().toEpochDay())); Payment previousPayment = sortedPayments.get(0); for (int i = 1; i < sortedPayments.size(); i++) { Payment currentPayment = sortedPayments.get(i); PointSensitivityBuilder currentSensi = input.get(currentPayment); if (currentPayment.getDate().equals(previousPayment.getDate()) && currentPayment.getCurrency().equals(previousPayment.getCurrency())) { output.remove(currentPayment); currentPayment = Payment.of( CurrencyAmount.of(currentPayment.getCurrency(), currentPayment.getAmount() + previousPayment.getAmount()), currentPayment.getDate()); currentSensi = currentSensi.combinedWith(output.get(previousPayment)); output.remove(previousPayment); output.put(currentPayment, currentSensi); sortedPayments.set(i - 1, currentPayment); sortedPayments.remove(i); i--; } previousPayment = currentPayment; } return output; } //------------------------------------------------------------------------- // restricted constructor private CashFlowEquivalentCalculator() { } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy