com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer Maven / Gradle / Ivy
/*
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.swap;
import static com.opengamma.strata.basics.currency.MultiCurrencyAmount.toMultiCurrencyAmount;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.ToDoubleBiFunction;
import com.google.common.collect.ImmutableList;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.MultiCurrencyAmount;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.tuple.Triple;
import com.opengamma.strata.market.amount.CashFlows;
import com.opengamma.strata.market.explain.ExplainKey;
import com.opengamma.strata.market.explain.ExplainMap;
import com.opengamma.strata.market.explain.ExplainMapBuilder;
import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.product.rate.FixedOvernightCompoundedAnnualRateComputation;
import com.opengamma.strata.product.rate.FixedRateComputation;
import com.opengamma.strata.product.swap.CompoundingMethod;
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.SwapPaymentPeriod;
/**
* Pricer for for rate swap products.
*
* This function provides the ability to price a {@link ResolvedSwap}.
* The product is priced by pricing each leg.
*/
public class DiscountingSwapProductPricer {
/**
* Default implementation.
*/
public static final DiscountingSwapProductPricer DEFAULT = new DiscountingSwapProductPricer(
DiscountingSwapLegPricer.DEFAULT);
/**
* Pricer for {@link ResolvedSwapLeg}.
*/
private final DiscountingSwapLegPricer legPricer;
/**
* Creates an instance.
*
* @param legPricer the pricer for {@link ResolvedSwapLeg}
*/
public DiscountingSwapProductPricer(
DiscountingSwapLegPricer legPricer) {
this.legPricer = ArgChecker.notNull(legPricer, "legPricer");
}
//-------------------------------------------------------------------------
/**
* Gets the underlying leg pricer.
*
* @return the leg pricer
*/
public DiscountingSwapLegPricer getLegPricer() {
return legPricer;
}
//-------------------------------------------------------------------------
/**
* Calculates the present value of the swap product, converted to the specified currency.
*
* The present value of the product is the value on the valuation date.
* This is the discounted forecast value.
* The result is converted to the specified currency.
*
* @param swap the product
* @param currency the currency to convert to
* @param provider the rates provider
* @return the present value of the swap product in the specified currency
*/
public CurrencyAmount presentValue(ResolvedSwap swap, Currency currency, RatesProvider provider) {
double totalPv = 0;
for (ResolvedSwapLeg leg : swap.getLegs()) {
double pv = legPricer.presentValueInternal(leg, provider);
totalPv += (pv * provider.fxRate(leg.getCurrency(), currency));
}
return CurrencyAmount.of(currency, totalPv);
}
/**
* Calculates the present value of the swap product.
*
* The present value of the product is the value on the valuation date.
* This is the discounted forecast value.
* The result is expressed using the payment currency of each leg.
*
* @param swap the product
* @param provider the rates provider
* @return the present value of the swap product
*/
public MultiCurrencyAmount presentValue(ResolvedSwap swap, RatesProvider provider) {
return swapValue(provider, swap, legPricer::presentValueInternal);
}
/**
* Calculates the forecast value of the swap product.
*
* The forecast value of the product is the value on the valuation date without present value discounting.
* The result is expressed using the payment currency of each leg.
*
* @param swap the product
* @param provider the rates provider
* @return the forecast value of the swap product
*/
public MultiCurrencyAmount forecastValue(ResolvedSwap swap, RatesProvider provider) {
return swapValue(provider, swap, legPricer::forecastValueInternal);
}
//-------------------------------------------------------------------------
// calculate present or forecast value for the swap
private static MultiCurrencyAmount swapValue(
RatesProvider provider,
ResolvedSwap swap,
ToDoubleBiFunction legFn) {
if (swap.isCrossCurrency()) {
return swap.getLegs().stream()
.map(leg -> CurrencyAmount.of(leg.getCurrency(), legFn.applyAsDouble(leg, provider)))
.collect(toMultiCurrencyAmount());
} else {
Currency currency = swap.getLegs().iterator().next().getCurrency();
double total = 0d;
for (ResolvedSwapLeg leg : swap.getLegs()) {
total += legFn.applyAsDouble(leg, provider);
}
return MultiCurrencyAmount.of(currency, total);
}
}
//-------------------------------------------------------------------------
/**
* Calculates the accrued interest since the last payment.
*
* This determines the payment period applicable at the valuation date and calculates
* the accrued interest since the last payment.
*
* @param swap the product
* @param provider the rates provider
* @return the accrued interest of the swap product
*/
public MultiCurrencyAmount accruedInterest(ResolvedSwap swap, RatesProvider provider) {
MultiCurrencyAmount result = MultiCurrencyAmount.empty();
for (ResolvedSwapLeg leg : swap.getLegs()) {
result = result.plus(legPricer.accruedInterest(leg, provider));
}
return result;
}
//-------------------------------------------------------------------------
/**
* Computes the par rate for swaps with a fixed leg.
*
* The par rate is the common rate on all payments of the fixed leg for which the total swap present value is 0.
*
* At least one leg must be a fixed leg. The par rate will be computed with respect to the first fixed leg
* in which all the payments are fixed payments with a unique accrual period (no compounding) and no FX reset.
* If the fixed leg is compounding, the par rate is computed only when the number of fixed coupon payments is 1 and
* accrual factor of each sub-period is 1
*
* @param swap the product
* @param provider the rates provider
* @return the par rate
* @throws IllegalArgumentException if there is no fixed leg
*/
public double parRate(ResolvedSwap swap, RatesProvider provider) {
// find fixed leg
ResolvedSwapLeg fixedLeg = fixedLeg(swap);
Currency ccyFixedLeg = fixedLeg.getCurrency();
// other payments (not fixed leg coupons) converted in fixed leg currency
double otherLegsConvertedPv = 0.0;
for (ResolvedSwapLeg leg : swap.getLegs()) {
if (leg != fixedLeg) {
double pvLocal = legPricer.presentValueInternal(leg, provider);
otherLegsConvertedPv += (pvLocal * provider.fxRate(leg.getCurrency(), ccyFixedLeg));
}
}
double fixedLegEventsPv = legPricer.presentValueEventsInternal(fixedLeg, provider);
if (fixedLeg.getPaymentPeriods().size() > 1) { // try multiperiod par-rate
// PVBP
double pvbpFixedLeg = legPricer.pvbp(fixedLeg, provider);
// Par rate
return -(otherLegsConvertedPv + fixedLegEventsPv) / pvbpFixedLeg;
}
SwapPaymentPeriod firstPeriod = fixedLeg.getPaymentPeriods().get(0);
ArgChecker.isTrue(firstPeriod instanceof RatePaymentPeriod, "PaymentPeriod must be instance of RatePaymentPeriod");
RatePaymentPeriod payment = (RatePaymentPeriod) firstPeriod;
if (payment.getAccrualPeriods().size() == 1) {
RateAccrualPeriod firstAccrualPeriod = payment.getAccrualPeriods().get(0);
// check for future value notional
if (firstAccrualPeriod.getRateComputation() instanceof FixedOvernightCompoundedAnnualRateComputation) {
double accrualFactor = payment.getAccrualPeriods().get(0).getYearFraction();
double notional = payment.getNotional();
double df = provider.discountFactor(ccyFixedLeg, payment.getPaymentDate());
return Math.pow(-otherLegsConvertedPv / (notional * df) + 1.0d, 1.0 / accrualFactor) - 1.0d;
} else { // no compounding
// PVBP
double pvbpFixedLeg = legPricer.pvbp(fixedLeg, provider);
// Par rate
return -(otherLegsConvertedPv + fixedLegEventsPv) / pvbpFixedLeg;
}
}
// try Compounding
Triple fixedCompounded = checkFixedCompounded(fixedLeg);
ArgChecker.isTrue(fixedCompounded.getFirst(),
"Swap should have a fixed leg and for one payment it should be based on compunding witout spread.");
double notional = payment.getNotional();
double df = provider.discountFactor(ccyFixedLeg, payment.getPaymentDate());
return Math.pow(-(otherLegsConvertedPv + fixedLegEventsPv) / (notional * df) + 1.0d,
1.0 / fixedCompounded.getSecond()) - 1.0d;
}
/**
* Computes the market quote of swaps.
*
* For swaps with a fixed leg, this is equal to the par rate. For swaps without a fixed leg, this is the
* constant spread on the first leg required to have a PV of 0.
*
* All the payment periods of the first leg must be of the type {@link RatePaymentPeriod}.
*
* @param swap the product
* @param provider the rates provider
* @return the market quote
* @throws IllegalArgumentException if there is no fixed leg and not all the payment periods of the first leg
* are of the type RatePaymentPeriod
*/
public double marketQuote(ResolvedSwap swap, RatesProvider provider) {
if (!swap.getLegs(SwapLegType.FIXED).isEmpty()) {
return parRate(swap, provider);
}
// For non-fixed first legs: generate a new swap with spread to 0
ResolvedSwapLeg referenceLeg = swap.getLegs().get(0);
Currency ccyReferenceLeg = referenceLeg.getCurrency();
List paymentPeriods0 = new ArrayList<>();
for (SwapPaymentPeriod period : referenceLeg.getPaymentPeriods()) {
ArgChecker.isTrue(period instanceof RatePaymentPeriod, "Must be RatePaymentPeriod");
RatePaymentPeriod ratePeriod = (RatePaymentPeriod) period;
List accrualPeriods0 = new ArrayList<>();
for (RateAccrualPeriod accrualPeriod : ratePeriod.getAccrualPeriods()) {
accrualPeriods0.add(accrualPeriod.toBuilder().spread(0.0).build());
}
paymentPeriods0.add(ratePeriod.toBuilder().accrualPeriods(accrualPeriods0).build());
}
ResolvedSwapLeg referenceLeg0 = referenceLeg.toBuilder().paymentPeriods(paymentPeriods0).build();
double convertedPvOtherLegs = 0.0;
for (int i = 1; i < swap.getLegs().size(); i++) {
convertedPvOtherLegs += legPricer.presentValue(swap.getLegs().get(i), ccyReferenceLeg, provider).getAmount();
}
double convertedPvLeg0 = legPricer.presentValue(referenceLeg0, provider).getAmount();
double pvbp = legPricer.pvbp(referenceLeg, provider);
return -(convertedPvOtherLegs + convertedPvLeg0) / pvbp;
}
/**
* Computes the par spread for swaps.
*
* The par spread is the common spread on all payments of the first leg for which the total swap present value is 0.
*
* The par spread will be computed with respect to the first leg. For that leg, all the payments have a unique
* accrual period or multiple accrual periods with Flat compounding and no FX reset.
*
* @param swap the product
* @param provider the rates provider
* @return the par rate
*/
public double parSpread(ResolvedSwap swap, RatesProvider provider) {
// does the fixed leg, if it exists, of the swap have a future value notional
if (!swap.getLegs(SwapLegType.FIXED).isEmpty()) {
ResolvedSwapLeg fixedLeg = fixedLeg(swap);
Optional annualRateCompOpt = findAnnualRateComputation(fixedLeg);
if (annualRateCompOpt.isPresent()) {
return parRate(swap, provider) - annualRateCompOpt.get().getRate();
}
}
ResolvedSwapLeg referenceLeg = swap.getLegs().get(0);
Currency ccyReferenceLeg = referenceLeg.getCurrency();
// try one payment compounding, typically for inflation swaps
Triple fixedCompounded = checkFixedCompounded(referenceLeg);
if (fixedCompounded.getFirst()) {
double df = provider.discountFactor(ccyReferenceLeg, referenceLeg.getPaymentPeriods().get(0).getPaymentDate());
double convertedPv = presentValue(swap, ccyReferenceLeg, provider).getAmount();
double referenceConvertedPv = legPricer.presentValue(referenceLeg, provider).getAmount();
double notional = ((RatePaymentPeriod) referenceLeg.getPaymentPeriods().get(0)).getNotional();
double parSpread =
Math.pow(-(convertedPv - referenceConvertedPv) / (df * notional) + 1.0d, 1.0d / fixedCompounded.getSecond()) -
(1.0d + fixedCompounded.getThird());
return parSpread;
}
// In other cases, try the standard multiperiod par spread
double convertedPv = presentValue(swap, ccyReferenceLeg, provider).getAmount();
double pvbp = legPricer.pvbp(referenceLeg, provider);
return -convertedPv / pvbp;
}
//-------------------------------------------------------------------------
/**
* Calculates the present value sensitivity of the swap product.
*
* The present value sensitivity of the product is the sensitivity of the present value to
* the underlying curves.
*
* @param swap the product
* @param provider the rates provider
* @return the present value curve sensitivity of the swap product
*/
public PointSensitivityBuilder presentValueSensitivity(ResolvedSwap swap, RatesProvider provider) {
return swapValueSensitivity(swap, provider, legPricer::presentValueSensitivity);
}
/**
* Calculates the present value sensitivity of the swap product converted in a given currency.
*
* The present value sensitivity of the product is the sensitivity of the present value to
* the underlying curves.
*
* @param swap the product
* @param currency the currency to convert to
* @param provider the rates provider
* @return the present value curve sensitivity of the swap product converted in the given currency
*/
public PointSensitivityBuilder presentValueSensitivity(ResolvedSwap swap, Currency currency, RatesProvider provider) {
PointSensitivityBuilder builder = PointSensitivityBuilder.none();
for (ResolvedSwapLeg leg : swap.getLegs()) {
PointSensitivityBuilder ls = legPricer.presentValueSensitivity(leg, provider);
PointSensitivityBuilder lsConverted =
ls.withCurrency(currency).multipliedBy(provider.fxRate(leg.getCurrency(), currency));
builder = builder.combinedWith(lsConverted);
}
return builder;
}
/**
* Calculates the forecast value sensitivity of the swap product.
*
* The forecast value sensitivity of the product is the sensitivity of the forecast value to
* the underlying curves.
*
* @param swap the product
* @param provider the rates provider
* @return the forecast value curve sensitivity of the swap product
*/
public PointSensitivityBuilder forecastValueSensitivity(ResolvedSwap swap, RatesProvider provider) {
return swapValueSensitivity(swap, provider, legPricer::forecastValueSensitivity);
}
// calculate present or forecast value sensitivity for the swap
private static PointSensitivityBuilder swapValueSensitivity(
ResolvedSwap swap,
RatesProvider provider,
BiFunction legFn) {
PointSensitivityBuilder builder = PointSensitivityBuilder.none();
for (ResolvedSwapLeg leg : swap.getLegs()) {
builder = builder.combinedWith(legFn.apply(leg, provider));
}
return builder;
}
/**
* Calculates the par rate curve sensitivity for a swap with a fixed leg.
*
* The par rate is the common rate on all payments of the fixed leg for which the total swap present value is 0.
*
* At least one leg must be a fixed leg. The par rate will be computed with respect to the first fixed leg.
* All the payments in that leg should be fixed payments with a unique accrual period (no compounding) and no FX reset.
*
* @param swap the product
* @param provider the rates provider
* @return the par rate curve sensitivity of the swap product
* @throws IllegalArgumentException if there is no fixed leg
*/
public PointSensitivityBuilder parRateSensitivity(ResolvedSwap swap, RatesProvider provider) {
ResolvedSwapLeg fixedLeg = fixedLeg(swap);
Currency ccyFixedLeg = fixedLeg.getCurrency();
// other payments (not fixed leg coupons) converted in fixed leg currency
double otherLegsConvertedPv = 0.0;
for (ResolvedSwapLeg leg : swap.getLegs()) {
if (leg != fixedLeg) {
double pvLocal = legPricer.presentValueInternal(leg, provider);
otherLegsConvertedPv += (pvLocal * provider.fxRate(leg.getCurrency(), ccyFixedLeg));
}
}
// does the fixed leg have a future value notional
Optional annualRateCompOpt = findAnnualRateComputation(fixedLeg);
if (annualRateCompOpt.isPresent()) {
RatePaymentPeriod payment = (RatePaymentPeriod) fixedLeg.getPaymentPeriods().get(0);
double af = annualRateCompOpt.get().getAccrualFactor();
double notional = payment.getNotional();
double df = provider.discountFactor(ccyFixedLeg, payment.getPaymentDate());
double otherLegsConvertedPvBar = -Math.pow(-otherLegsConvertedPv / (notional * df) + 1.0d, 1.0 / af - 1.0d) / (af * notional * df);
double dfBar = Math.pow(
-otherLegsConvertedPv / (notional * df) + 1.0d, 1.0 / af - 1.0d) * otherLegsConvertedPv / (af * notional * df * df);
PointSensitivityBuilder otherLegsConvertedPvDr = PointSensitivityBuilder.none();
for (ResolvedSwapLeg leg : swap.getLegs()) {
if (leg != fixedLeg) {
PointSensitivityBuilder pvLegDr = getLegPricer().presentValueSensitivity(leg, provider)
.multipliedBy(provider.fxRate(leg.getCurrency(), ccyFixedLeg));
otherLegsConvertedPvDr = otherLegsConvertedPvDr.combinedWith(pvLegDr);
}
}
otherLegsConvertedPvDr = otherLegsConvertedPvDr.withCurrency(ccyFixedLeg);
PointSensitivityBuilder dfDr = provider.discountFactors(
ccyFixedLeg).zeroRatePointSensitivity(fixedLeg.getPaymentPeriods().get(0).getPaymentDate());
return dfDr.multipliedBy(dfBar).combinedWith(otherLegsConvertedPvDr.multipliedBy(otherLegsConvertedPvBar));
}
double fixedLegEventsPv = legPricer.presentValueEventsInternal(fixedLeg, provider);
double pvbpFixedLeg = legPricer.pvbp(fixedLeg, provider);
// Backward sweep
double otherLegsConvertedPvBar = -1.0d / pvbpFixedLeg;
double fixedLegEventsPvBar = -1.0d / pvbpFixedLeg;
double pvbpFixedLegBar = (otherLegsConvertedPv + fixedLegEventsPv) / (pvbpFixedLeg * pvbpFixedLeg);
PointSensitivityBuilder pvbpFixedLegDr = legPricer.pvbpSensitivity(fixedLeg, provider);
PointSensitivityBuilder fixedLegEventsPvDr = legPricer.presentValueSensitivityEventsInternal(fixedLeg, provider);
PointSensitivityBuilder otherLegsConvertedPvDr = PointSensitivityBuilder.none();
for (ResolvedSwapLeg leg : swap.getLegs()) {
if (leg != fixedLeg) {
PointSensitivityBuilder pvLegDr = legPricer.presentValueSensitivity(leg, provider)
.multipliedBy(provider.fxRate(leg.getCurrency(), ccyFixedLeg));
otherLegsConvertedPvDr = otherLegsConvertedPvDr.combinedWith(pvLegDr);
}
}
otherLegsConvertedPvDr = otherLegsConvertedPvDr.withCurrency(ccyFixedLeg);
return pvbpFixedLegDr.multipliedBy(pvbpFixedLegBar)
.combinedWith(fixedLegEventsPvDr.multipliedBy(fixedLegEventsPvBar))
.combinedWith(otherLegsConvertedPvDr.multipliedBy(otherLegsConvertedPvBar));
}
/**
* Computes the market quote curve sensitivity for swaps.
*
* For swaps with a fixed leg, this is equal to the par rate. For swaps without a fixed leg, this is the
* constant spread on the first leg required to have a PV of 0.
*
* All the payment periods of the first leg must be of the type {@link RatePaymentPeriod}.
*
* The figures are reported in the currency of the first leg, even if in theory they should be dimensionless.
*
* @param swap the product
* @param provider the rates provider
* @return the par rate curve sensitivity of the swap product
* @throws IllegalArgumentException if there is no fixed leg and not all the payment periods of the first leg
* are of the type RatePaymentPeriod
*/
public PointSensitivityBuilder marketQuoteSensitivity(ResolvedSwap swap, RatesProvider provider) {
if (!swap.getLegs(SwapLegType.FIXED).isEmpty()) {
return parRateSensitivity(swap, provider);
}
// For non-fixed first legs: generate a new swap with spread to 0
ResolvedSwapLeg referenceLeg = swap.getLegs().get(0);
Currency ccyReferenceLeg = referenceLeg.getCurrency();
List paymentPeriods0 = new ArrayList<>();
for (SwapPaymentPeriod period : referenceLeg.getPaymentPeriods()) {
ArgChecker.isTrue(period instanceof RatePaymentPeriod, "Must be RatePaymentPeriod");
RatePaymentPeriod ratePeriod = (RatePaymentPeriod) period;
List accrualPeriods0 = new ArrayList<>();
for (RateAccrualPeriod accrualPeriod : ratePeriod.getAccrualPeriods()) {
accrualPeriods0.add(accrualPeriod.toBuilder().spread(0.0).build());
}
paymentPeriods0.add(ratePeriod.toBuilder().accrualPeriods(accrualPeriods0).build());
}
ResolvedSwapLeg referenceLeg0 = referenceLeg.toBuilder().paymentPeriods(paymentPeriods0).build();
double convertedPvOtherLegs = 0.0;
for (int i = 1; i < swap.getLegs().size(); i++) {
convertedPvOtherLegs += legPricer.presentValue(swap.getLegs().get(i), ccyReferenceLeg, provider).getAmount();
}
double convertedPvLeg0 = legPricer.presentValue(referenceLeg0, provider).getAmount();
double pvbp = legPricer.pvbp(referenceLeg, provider);
// double marketQuote = -(convertedPvOtherLegs + convertedPvLeg0) / pvbp;
// Backward sweep
double marketQuoteBar = 1.0d;
double convertedPvOtherLegsBar = -marketQuoteBar / pvbp;
double convertedPvLeg0Bar = -marketQuoteBar / pvbp;
double pvbpBar = (convertedPvOtherLegs + convertedPvLeg0) / (pvbp * pvbp) * marketQuoteBar;
PointSensitivityBuilder dconvertedPvLeg0dr = legPricer.presentValueSensitivity(referenceLeg0, provider);
PointSensitivityBuilder dconvertedPvOtherLegsdr = PointSensitivityBuilder.none();
for (int i = 1; i < swap.getLegs().size(); i++) {
ResolvedSwapLeg leg = swap.getLegs().get(i);
dconvertedPvOtherLegsdr = dconvertedPvOtherLegsdr.combinedWith(
legPricer.presentValueSensitivity(leg, provider)
.multipliedBy(provider.fxRate(leg.getCurrency(), ccyReferenceLeg))
.withCurrency(ccyReferenceLeg));
}
PointSensitivityBuilder dpvbpdr = legPricer.pvbpSensitivity(referenceLeg, provider);
return dconvertedPvLeg0dr.multipliedBy(convertedPvLeg0Bar)
.combinedWith(dconvertedPvOtherLegsdr.multipliedBy(convertedPvOtherLegsBar))
.combinedWith(dpvbpdr.multipliedBy(pvbpBar));
}
/**
* Calculates the par spread curve sensitivity for a swap.
*
* The par spread is the common spread on all payments of the first leg for which the total swap present value is 0.
*
* The par spread is computed with respect to the first leg. For that leg, all the payments have a unique
* accrual period (no compounding) and no FX reset.
*
* @param swap the product
* @param provider the rates provider
* @return the par spread curve sensitivity of the swap product
*/
public PointSensitivityBuilder parSpreadSensitivity(ResolvedSwap swap, RatesProvider provider) {
// does the fixed leg of the swap, if it exists, have a future value notional
if (!swap.getLegs(SwapLegType.FIXED).isEmpty()) {
ResolvedSwapLeg fixedLeg = fixedLeg(swap);
Optional annualRateCompOpt = findAnnualRateComputation(fixedLeg);
if (annualRateCompOpt.isPresent()) {
return parRateSensitivity(swap, provider);
}
}
ResolvedSwapLeg referenceLeg = swap.getLegs().get(0);
Currency ccyReferenceLeg = referenceLeg.getCurrency();
double convertedPv = presentValue(swap, ccyReferenceLeg, provider).getAmount();
PointSensitivityBuilder convertedPvDr = presentValueSensitivity(swap, ccyReferenceLeg, provider);
// try one payment compounding, typically for inflation swaps
Triple fixedCompounded = checkFixedCompounded(referenceLeg);
if (fixedCompounded.getFirst()) {
double df = provider.discountFactor(ccyReferenceLeg, referenceLeg.getPaymentPeriods().get(0).getPaymentDate());
PointSensitivityBuilder dfDr = provider.discountFactors(ccyReferenceLeg)
.zeroRatePointSensitivity(referenceLeg.getPaymentPeriods().get(0).getPaymentDate());
double referenceConvertedPv = legPricer.presentValue(referenceLeg, provider).getAmount();
PointSensitivityBuilder referenceConvertedPvDr = legPricer.presentValueSensitivity(referenceLeg, provider);
double notional = ((RatePaymentPeriod) referenceLeg.getPaymentPeriods().get(0)).getNotional();
PointSensitivityBuilder dParSpreadDr =
convertedPvDr.combinedWith(referenceConvertedPvDr.multipliedBy(-1)).multipliedBy(-1.0d / (df * notional))
.combinedWith(dfDr.multipliedBy((convertedPv - referenceConvertedPv) / (df * df * notional)))
.multipliedBy(1.0d / fixedCompounded.getSecond() *
Math.pow(-(convertedPv - referenceConvertedPv) / (df * notional) + 1.0d,
1.0d / fixedCompounded.getSecond() - 1.0d));
return dParSpreadDr;
}
double pvbp = legPricer.pvbp(referenceLeg, provider);
// Backward sweep
double convertedPvBar = -1d / pvbp;
double pvbpBar = convertedPv / (pvbp * pvbp);
PointSensitivityBuilder pvbpDr = legPricer.pvbpSensitivity(referenceLeg, provider);
return convertedPvDr.multipliedBy(convertedPvBar).combinedWith(pvbpDr.multipliedBy(pvbpBar));
}
//-------------------------------------------------------------------------
/**
* Calculates the future cash flows of the swap product.
*
* Each expected cash flow is added to the result.
* This is based on {@link #forecastValue(ResolvedSwap, RatesProvider)}.
*
* @param swap the product
* @param provider the rates provider
* @return the cash flow
*/
public CashFlows cashFlows(ResolvedSwap swap, RatesProvider provider) {
return swap.getLegs().stream()
.map(leg -> legPricer.cashFlows(leg, provider))
.reduce(CashFlows.NONE, CashFlows::combinedWith);
}
//-------------------------------------------------------------------------
/**
* Explains the present value of the swap product.
*
* This returns explanatory information about the calculation.
*
* @param swap the product
* @param provider the rates provider
* @return the explanatory information
*/
public ExplainMap explainPresentValue(ResolvedSwap swap, RatesProvider provider) {
ExplainMapBuilder builder = ExplainMap.builder();
builder.put(ExplainKey.ENTRY_TYPE, "Swap");
for (ResolvedSwapLeg leg : swap.getLegs()) {
builder.addListEntryWithIndex(
ExplainKey.LEGS, child -> legPricer.explainPresentValueInternal(leg, provider, child));
}
return builder.build();
}
//-------------------------------------------------------------------------
/**
* Calculates the currency exposure of the swap product.
*
* @param swap the product
* @param provider the rates provider
* @return the currency exposure of the swap product
*/
public MultiCurrencyAmount currencyExposure(ResolvedSwap swap, RatesProvider provider) {
MultiCurrencyAmount ce = MultiCurrencyAmount.empty();
for (ResolvedSwapLeg leg : swap.getLegs()) {
ce = ce.plus(legPricer.currencyExposure(leg, provider));
}
return ce;
}
/**
* Calculates the current cash of the swap product.
*
* @param swap the product
* @param provider the rates provider
* @return the current cash of the swap product
*/
public MultiCurrencyAmount currentCash(ResolvedSwap swap, RatesProvider provider) {
MultiCurrencyAmount ce = MultiCurrencyAmount.empty();
for (ResolvedSwapLeg leg : swap.getLegs()) {
ce = ce.plus(legPricer.currentCash(leg, provider));
}
return ce;
}
// finds a fixed overnight compounded annual rate computation if present
private Optional findAnnualRateComputation(ResolvedSwapLeg fixedLeg) {
SwapPaymentPeriod firstPeriod = fixedLeg.getPaymentPeriods().get(0);
if (firstPeriod instanceof RatePaymentPeriod) {
RatePaymentPeriod payment = (RatePaymentPeriod) firstPeriod;
RateAccrualPeriod firstAccrualPeriod = payment.getAccrualPeriods().get(0);
if (firstAccrualPeriod.getRateComputation() instanceof FixedOvernightCompoundedAnnualRateComputation) {
return Optional.of((FixedOvernightCompoundedAnnualRateComputation) firstAccrualPeriod.getRateComputation());
}
}
return Optional.empty();
}
//-------------------------------------------------------------------------
// checking that at least one leg is a fixed leg and returning the first one
private ResolvedSwapLeg fixedLeg(ResolvedSwap swap) {
List fixedLegs = swap.getLegs(SwapLegType.FIXED);
if (fixedLegs.isEmpty()) {
throw new IllegalArgumentException("Swap must contain a fixed leg");
}
return fixedLegs.get(0);
}
// Checks if the leg is a fixed leg with one payment and compounding
// This type of leg is used in zero-coupon inflation swaps
// When returning a 'true' for the first element, the second element is the number of periods which are used in
// par rate/spread computation and the third element is the common fixed rate
private Triple checkFixedCompounded(ResolvedSwapLeg leg) {
if (leg.getPaymentEvents().size() != 0) {
return Triple.of(false, 0, 0.0d); // No event
}
RatePaymentPeriod ratePaymentPeriod = (RatePaymentPeriod) leg.getPaymentPeriods().get(0);
if (ratePaymentPeriod.getCompoundingMethod() == CompoundingMethod.NONE) {
return Triple.of(false, 0, 0.0d); // Should be compounded
}
ImmutableList accrualPeriods = ratePaymentPeriod.getAccrualPeriods();
int nbAccrualPeriods = accrualPeriods.size();
double fixedRate = 0;
for (int i = 0; i < nbAccrualPeriods; i++) {
if (!(accrualPeriods.get(i).getRateComputation() instanceof FixedRateComputation)) {
return Triple.of(false, 0, 0.0d); // Should be fixed period
}
if ((i > 0) && (((FixedRateComputation) accrualPeriods.get(i).getRateComputation()).getRate() != fixedRate)) {
return Triple.of(false, 0, 0.0d); // All fixed rates should be the same
}
fixedRate = ((FixedRateComputation) accrualPeriods.get(i).getRateComputation()).getRate();
if (accrualPeriods.get(i).getSpread() != 0) {
return Triple.of(false, 0, 0.0d); // Should have no spread
}
if (accrualPeriods.get(i).getGearing() != 1.0d) {
return Triple.of(false, 0, 0.0d); // Should have a gearing of 1.
}
if (accrualPeriods.get(i).getYearFraction() != 1.0d) {
return Triple.of(false, 0, 0.0d); // Should have a year fraction of 1.
}
}
return Triple.of(true, nbAccrualPeriods, fixedRate);
}
}