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

com.opengamma.strata.pricer.credit.IsdaCdsProductPricer Maven / Gradle / Ivy

There is a newer version: 2.12.46
Show newest version
/*
 * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.pricer.credit;

import static com.opengamma.strata.math.impl.util.Epsilon.epsilon;
import static com.opengamma.strata.math.impl.util.Epsilon.epsilonP;
import static com.opengamma.strata.math.impl.util.Epsilon.epsilonPP;

import java.time.LocalDate;

import com.google.common.collect.ImmutableMap;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.StandardId;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.tuple.Pair;
import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder;
import com.opengamma.strata.math.impl.util.Epsilon;
import com.opengamma.strata.pricer.common.PriceType;
import com.opengamma.strata.product.credit.CreditCouponPaymentPeriod;
import com.opengamma.strata.product.credit.ResolvedCds;

/**
 * Pricer for single-name credit default swaps (CDS) based on ISDA standard model. 
 * 

* The implementation is based on the ISDA model versions 1.8.2. *

* A CDS product is priced based on {@code referenceDate}. * This is typically valuation date, or settlement date if the product is associated with a {@code Trade}. */ public class IsdaCdsProductPricer { /** * Default implementation. */ public static final IsdaCdsProductPricer DEFAULT = new IsdaCdsProductPricer(AccrualOnDefaultFormula.ORIGINAL_ISDA); /** * The small parameter. *

* An approximation formula is used if a certain variable is smaller than this parameter. */ private static final double SMALL = 1.0e-5; /** * The formula */ private final AccrualOnDefaultFormula formula; /** * The omega parameter. */ private final double omega; /** * Constructor specifying the formula to use for the accrued on default calculation. *

* Options are the formula given in the ISDA model (version 1.8.2 and lower); * the proposed fix by Markit (given as a comment in version 1.8.2), or the mathematically correct formula. * * @param formula the formula */ public IsdaCdsProductPricer(AccrualOnDefaultFormula formula) { this.formula = ArgChecker.notNull(formula, "formula"); this.omega = formula.getOmega(); } //------------------------------------------------------------------------- /** * Gets the accrual-on-default formula used in this pricer. * * @return the formula */ public AccrualOnDefaultFormula getAccrualOnDefaultFormula() { return formula; } //------------------------------------------------------------------------- /** * Calculates the price of the CDS product, which is the present value per unit notional. *

* This method can calculate the clean or dirty price, see {@link PriceType}. * If calculating the clean price, the accrued interest is calculated based on the step-in date. *

* This is coherent with {@link #presentValue(ResolvedCds, CreditRatesProvider, LocalDate, PriceType, ReferenceData)}. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param priceType the price type * @param refData the reference data * @return the price */ public double price( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, PriceType priceType, ReferenceData refData) { return price(cds, ratesProvider, cds.getFixedRate(), referenceDate, priceType, refData); } // internal price computation with specified coupon rate double price( ResolvedCds cds, CreditRatesProvider ratesProvider, double fractionalSpread, LocalDate referenceDate, PriceType priceType, ReferenceData refData) { if (!cds.getProtectionEndDate().isAfter(ratesProvider.getValuationDate())) { //short cut already expired CDSs return 0d; } LocalDate stepinDate = cds.getStepinDateOffset().adjust(ratesProvider.getValuationDate(), refData); LocalDate effectiveStartDate = cds.calculateEffectiveStartDate(stepinDate); double recoveryRate = recoveryRate(cds, ratesProvider); Pair rates = reduceDiscountFactors(cds, ratesProvider); double protectionLeg = protectionLeg(cds, rates.getFirst(), rates.getSecond(), referenceDate, effectiveStartDate, recoveryRate); double rpv01 = riskyAnnuity( cds, rates.getFirst(), rates.getSecond(), referenceDate, stepinDate, effectiveStartDate, priceType); return protectionLeg - rpv01 * fractionalSpread; } /** * Calculates the price sensitivity of the product. *

* The price sensitivity of the product is the sensitivity of price to the underlying curves. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param refData the reference data * @return the present value sensitivity */ public PointSensitivityBuilder priceSensitivity( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, ReferenceData refData) { if (isExpired(cds, ratesProvider)) { return PointSensitivityBuilder.none(); } LocalDate stepinDate = cds.getStepinDateOffset().adjust(ratesProvider.getValuationDate(), refData); LocalDate effectiveStartDate = cds.calculateEffectiveStartDate(stepinDate); double recoveryRate = recoveryRate(cds, ratesProvider); Pair rates = reduceDiscountFactors(cds, ratesProvider); PointSensitivityBuilder protectionLegSensi = protectionLegSensitivity(cds, rates.getFirst(), rates.getSecond(), referenceDate, effectiveStartDate, recoveryRate); PointSensitivityBuilder riskyAnnuitySensi = riskyAnnuitySensitivity(cds, rates.getFirst(), rates.getSecond(), referenceDate, stepinDate, effectiveStartDate) .multipliedBy(-cds.getFixedRate()); return protectionLegSensi.combinedWith(riskyAnnuitySensi); } //------------------------------------------------------------------------- /** * Calculates the present value of the CDS product. *

* The present value of the product is based on {@code referenceDate}. * This is typically the valuation date, or cash settlement date if the product is associated with a {@code Trade}. *

* This method can calculate the clean or dirty present value, see {@link PriceType}. * If calculating the clean value, the accrued interest is calculated based on the step-in date. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param priceType the price type * @param refData the reference data * @return the present value */ public CurrencyAmount presentValue( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, PriceType priceType, ReferenceData refData) { double price = price(cds, ratesProvider, referenceDate, priceType, refData); return CurrencyAmount.of(cds.getCurrency(), cds.getBuySell().normalize(cds.getNotional()) * price); } /** * Calculates the present value sensitivity of the product. *

* The present value sensitivity of the product is the sensitivity of present value to the underlying curves. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param refData the reference data * @return the present value sensitivity */ public PointSensitivityBuilder presentValueSensitivity( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, ReferenceData refData) { if (isExpired(cds, ratesProvider)) { return PointSensitivityBuilder.none(); } LocalDate stepinDate = cds.getStepinDateOffset().adjust(ratesProvider.getValuationDate(), refData); LocalDate effectiveStartDate = cds.calculateEffectiveStartDate(stepinDate); double recoveryRate = recoveryRate(cds, ratesProvider); Pair rates = reduceDiscountFactors(cds, ratesProvider); double signedNotional = cds.getBuySell().normalize(cds.getNotional()); PointSensitivityBuilder protectionLegSensi = protectionLegSensitivity(cds, rates.getFirst(), rates.getSecond(), referenceDate, effectiveStartDate, recoveryRate) .multipliedBy(signedNotional); PointSensitivityBuilder riskyAnnuitySensi = riskyAnnuitySensitivity( cds, rates.getFirst(), rates.getSecond(), referenceDate, stepinDate, effectiveStartDate) .multipliedBy(-cds.getFixedRate() * signedNotional); return protectionLegSensi.combinedWith(riskyAnnuitySensi); } //------------------------------------------------------------------------- /** * Calculates the par spread of the CDS product. *

* The par spread is a coupon rate such that the clean PV is 0. * The result is represented in decimal form. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param refData the reference data * @return the par spread */ public double parSpread( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, ReferenceData refData) { ArgChecker.isTrue(cds.getProtectionEndDate().isAfter(ratesProvider.getValuationDate()), "CDS already expired"); LocalDate stepinDate = cds.getStepinDateOffset().adjust(ratesProvider.getValuationDate(), refData); LocalDate effectiveStartDate = cds.calculateEffectiveStartDate(stepinDate); double recoveryRate = recoveryRate(cds, ratesProvider); Pair rates = reduceDiscountFactors(cds, ratesProvider); double protectionLeg = protectionLeg(cds, rates.getFirst(), rates.getSecond(), referenceDate, effectiveStartDate, recoveryRate); double riskyAnnuity = riskyAnnuity(cds, rates.getFirst(), rates.getSecond(), referenceDate, stepinDate, effectiveStartDate, PriceType.CLEAN); return protectionLeg / riskyAnnuity; } /** * Calculates the par spread sensitivity of the product. *

* The par spread sensitivity of the product is the sensitivity of par spread to the underlying curves. * The resulting sensitivity is based on the currency of the CDS product. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param refData the reference data * @return the par spread */ public PointSensitivityBuilder parSpreadSensitivity( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, ReferenceData refData) { ArgChecker.isTrue(cds.getProtectionEndDate().isAfter(ratesProvider.getValuationDate()), "CDS already expired"); LocalDate stepinDate = cds.getStepinDateOffset().adjust(ratesProvider.getValuationDate(), refData); LocalDate effectiveStartDate = cds.calculateEffectiveStartDate(stepinDate); double recoveryRate = recoveryRate(cds, ratesProvider); Pair rates = reduceDiscountFactors(cds, ratesProvider); double protectionLeg = protectionLeg(cds, rates.getFirst(), rates.getSecond(), referenceDate, effectiveStartDate, recoveryRate); double riskyAnnuityInv = 1d / riskyAnnuity(cds, rates.getFirst(), rates.getSecond(), referenceDate, stepinDate, effectiveStartDate, PriceType.CLEAN); PointSensitivityBuilder protectionLegSensi = protectionLegSensitivity(cds, rates.getFirst(), rates.getSecond(), referenceDate, effectiveStartDate, recoveryRate) .multipliedBy(riskyAnnuityInv); PointSensitivityBuilder riskyAnnuitySensi = riskyAnnuitySensitivity( cds, rates.getFirst(), rates.getSecond(), referenceDate, stepinDate, effectiveStartDate) .multipliedBy(-protectionLeg * riskyAnnuityInv * riskyAnnuityInv); return protectionLegSensi.combinedWith(riskyAnnuitySensi); } //------------------------------------------------------------------------- /** * Calculates the price of the protection leg, which is the protection leg present value per unit notional. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param refData the reference data * @return the protection leg price */ public double protectionLeg( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, ReferenceData refData) { if (isExpired(cds, ratesProvider)) { return 0d; } LocalDate stepinDate = cds.getStepinDateOffset().adjust(ratesProvider.getValuationDate(), refData); LocalDate effectiveStartDate = cds.calculateEffectiveStartDate(stepinDate); double recoveryRate = recoveryRate(cds, ratesProvider); Pair rates = reduceDiscountFactors(cds, ratesProvider); return protectionLeg(cds, rates.getFirst(), rates.getSecond(), referenceDate, effectiveStartDate, recoveryRate); } //------------------------------------------------------------------------- /** * Calculates the risky annuity, which is RPV01 per unit notional. *

* Zero is returned if the CDS already expired. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param priceType the price type * @param refData the reference data * @return the risky annuity */ public double riskyAnnuity( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, PriceType priceType, ReferenceData refData) { if (isExpired(cds, ratesProvider)) { return 0d; } LocalDate stepinDate = cds.getStepinDateOffset().adjust(ratesProvider.getValuationDate(), refData); LocalDate effectiveStartDate = cds.calculateEffectiveStartDate(stepinDate); Pair rates = reduceDiscountFactors(cds, ratesProvider); return riskyAnnuity(cds, rates.getFirst(), rates.getSecond(), referenceDate, stepinDate, effectiveStartDate, priceType); } /** * Calculates the risky annuity sensitivity of the product. *

* The risky annuity sensitivity of the product is the sensitivity of risky annuity to the underlying curves. * The resulting sensitivity is based on the currency of the CDS product. *

* Empty sensitivity is returned if the CDS already expired. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param refData the reference data * @return the risky annuity sensitivity */ public PointSensitivityBuilder riskyAnnuitySensitivity( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, ReferenceData refData) { if (isExpired(cds, ratesProvider)) { return PointSensitivityBuilder.none(); } LocalDate stepinDate = cds.getStepinDateOffset().adjust(ratesProvider.getValuationDate(), refData); LocalDate effectiveStartDate = cds.calculateEffectiveStartDate(stepinDate); Pair rates = reduceDiscountFactors(cds, ratesProvider); return riskyAnnuitySensitivity( cds, rates.getFirst(), rates.getSecond(), referenceDate, stepinDate, effectiveStartDate); } //------------------------------------------------------------------------- /** * Calculates the risky PV01 of the CDS product. *

* RPV01 is defined as minus of the present value sensitivity to coupon rate. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param priceType the price type * @param refData the reference date * @return the RPV01 */ public CurrencyAmount rpv01( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, PriceType priceType, ReferenceData refData) { double riskyAnnuity = riskyAnnuity(cds, ratesProvider, referenceDate, priceType, refData); return CurrencyAmount.of(cds.getCurrency(), cds.getBuySell().normalize(cds.getNotional()) * riskyAnnuity); } //------------------------------------------------------------------------- /** * Calculates the recovery01 of the CDS product. *

* The recovery01 is defined as the present value sensitivity to the recovery rate. * Since the ISDA standard model requires the recovery rate to be constant throughout the lifetime of the CDS, * one currency amount is returned by this method. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param refData the reference data * @return the recovery01 */ public CurrencyAmount recovery01( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, ReferenceData refData) { if (isExpired(cds, ratesProvider)) { return CurrencyAmount.of(cds.getCurrency(), 0d); } LocalDate stepinDate = cds.getStepinDateOffset().adjust(ratesProvider.getValuationDate(), refData); LocalDate effectiveStartDate = cds.calculateEffectiveStartDate(stepinDate); validateRecoveryRates(cds, ratesProvider); Pair rates = reduceDiscountFactors(cds, ratesProvider); double protectionFull = protectionFull(cds, rates.getFirst(), rates.getSecond(), referenceDate, effectiveStartDate); return CurrencyAmount.of(cds.getCurrency(), -cds.getBuySell().normalize(cds.getNotional()) * protectionFull); } //------------------------------------------------------------------------- /** * Calculates the jump-to-default of the CDS product. *

* The jump-to-default is the value of the product in case of immediate default. * * @param cds the product * @param ratesProvider the rates provider * @param referenceDate the reference date * @param refData the reference data * @return the jump-to-default */ public JumpToDefault jumpToDefault( ResolvedCds cds, CreditRatesProvider ratesProvider, LocalDate referenceDate, ReferenceData refData) { StandardId legalEntityId = cds.getLegalEntityId(); Currency currency = cds.getCurrency(); if (isExpired(cds, ratesProvider)) { return JumpToDefault.of(currency, ImmutableMap.of(legalEntityId, 0d)); } LocalDate stepinDate = cds.getStepinDateOffset().adjust(ratesProvider.getValuationDate(), refData); LocalDate effectiveStartDate = cds.calculateEffectiveStartDate(stepinDate); double recoveryRate = recoveryRate(cds, ratesProvider); Pair rates = reduceDiscountFactors(cds, ratesProvider); double protectionFull = protectionFull(cds, rates.getFirst(), rates.getSecond(), referenceDate, effectiveStartDate); double lgd = 1d - recoveryRate; double rpv01 = riskyAnnuity( cds, rates.getFirst(), rates.getSecond(), referenceDate, stepinDate, effectiveStartDate, PriceType.CLEAN); double jtd = lgd - (lgd * protectionFull - cds.getFixedRate() * rpv01); return JumpToDefault.of(currency, ImmutableMap.of(legalEntityId, cds.getBuySell().normalize(cds.getNotional()) * jtd)); } /** * Calculates the expected loss of the CDS product. *

* The expected loss is the (undiscounted) expected default settlement value paid by the protection seller. * The resulting value is always positive. * * @param cds the product * @param ratesProvider the rates provider * @return the expected loss */ public CurrencyAmount expectedLoss( ResolvedCds cds, CreditRatesProvider ratesProvider) { if (isExpired(cds, ratesProvider)) { return CurrencyAmount.of(cds.getCurrency(), 0d); } double recoveryRate = recoveryRate(cds, ratesProvider); Pair rates = reduceDiscountFactors(cds, ratesProvider); double survivalProbability = rates.getSecond().survivalProbability(cds.getProtectionEndDate()); double el = (1d - recoveryRate) * (1d - survivalProbability); return CurrencyAmount.of(cds.getCurrency(), Math.abs(cds.getNotional()) * el); } //------------------------------------------------------------------------- // computes protection leg pv per unit notional private double protectionLeg( ResolvedCds cds, CreditDiscountFactors discountFactors, LegalEntitySurvivalProbabilities survivalProbabilities, LocalDate referenceDate, LocalDate effectiveStartDate, double recoveryRate) { double protectionFull = protectionFull(cds, discountFactors, survivalProbabilities, referenceDate, effectiveStartDate); return (1d - recoveryRate) * protectionFull; } // computes protection leg pv per unit notional, without loss-given-default rate multiplied double protectionFull( ResolvedCds cds, CreditDiscountFactors discountFactors, LegalEntitySurvivalProbabilities survivalProbabilities, LocalDate referenceDate, LocalDate effectiveStartDate) { DoubleArray integrationSchedule = DoublesScheduleGenerator.getIntegrationsPoints( discountFactors.relativeYearFraction(effectiveStartDate), discountFactors.relativeYearFraction(cds.getProtectionEndDate()), discountFactors.getParameterKeys(), survivalProbabilities.getParameterKeys()); double pv = 0d; double ht0 = survivalProbabilities.zeroRate(integrationSchedule.get(0)) * integrationSchedule.get(0); double rt0 = discountFactors.zeroRate(integrationSchedule.get(0)) * integrationSchedule.get(0); double b0 = Math.exp(-ht0 - rt0); int n = integrationSchedule.size(); for (int i = 1; i < n; ++i) { double ht1 = survivalProbabilities.zeroRate(integrationSchedule.get(i)) * integrationSchedule.get(i); double rt1 = discountFactors.zeroRate(integrationSchedule.get(i)) * integrationSchedule.get(i); double b1 = Math.exp(-ht1 - rt1); double dht = ht1 - ht0; double drt = rt1 - rt0; double dhrt = dht + drt; // The formula has been modified from ISDA (but is equivalent) to avoid log(exp(x)) and explicitly // calculating the time step - it also handles the limit double dPV = 0d; if (Math.abs(dhrt) < SMALL) { dPV = dht * b0 * epsilon(-dhrt); } else { dPV = (b0 - b1) * dht / dhrt; } pv += dPV; ht0 = ht1; rt0 = rt1; b0 = b1; } // roll to the cash settle date double df = discountFactors.discountFactor(referenceDate); return pv / df; } // computes risky annuity double riskyAnnuity( ResolvedCds cds, CreditDiscountFactors discountFactors, LegalEntitySurvivalProbabilities survivalProbabilities, LocalDate referenceDate, LocalDate stepinDate, LocalDate effectiveStartDate, PriceType priceType) { double pv = 0d; for (CreditCouponPaymentPeriod coupon : cds.getPaymentPeriods()) { if (stepinDate.isBefore(coupon.getEndDate())) { double q = survivalProbabilities.survivalProbability(coupon.getEffectiveEndDate()); double p = discountFactors.discountFactor(coupon.getPaymentDate()); pv += coupon.getYearFraction() * p * q; } } if (cds.getPaymentOnDefault().isAccruedInterest()) { // This is needed so that the code is consistent with ISDA C when the Markit `fix' is used. LocalDate start = cds.getPaymentPeriods().size() == 1 ? effectiveStartDate : cds.getAccrualStartDate(); DoubleArray integrationSchedule = DoublesScheduleGenerator.getIntegrationsPoints( discountFactors.relativeYearFraction(start), discountFactors.relativeYearFraction(cds.getProtectionEndDate()), discountFactors.getParameterKeys(), survivalProbabilities.getParameterKeys()); for (CreditCouponPaymentPeriod coupon : cds.getPaymentPeriods()) { pv += singlePeriodAccrualOnDefault( coupon, effectiveStartDate, integrationSchedule, discountFactors, survivalProbabilities); } } // roll to the cash settle date double df = discountFactors.discountFactor(referenceDate); pv /= df; if (priceType.isCleanPrice()) { pv -= cds.accruedYearFraction(stepinDate); } return pv; } // computes accrual-on-default pv per unit notional for a single payment period private double singlePeriodAccrualOnDefault( CreditCouponPaymentPeriod coupon, LocalDate effectiveStartDate, DoubleArray integrationSchedule, CreditDiscountFactors discountFactors, LegalEntitySurvivalProbabilities survivalProbabilities) { LocalDate start = coupon.getEffectiveStartDate().isBefore(effectiveStartDate) ? effectiveStartDate : coupon.getEffectiveStartDate(); if (!start.isBefore(coupon.getEffectiveEndDate())) { return 0d; // this coupon has already expired } DoubleArray knots = DoublesScheduleGenerator.truncateSetInclusive(discountFactors.relativeYearFraction(start), discountFactors.relativeYearFraction(coupon.getEffectiveEndDate()), integrationSchedule); double t0Knot = knots.get(0); double ht0 = survivalProbabilities.zeroRate(t0Knot) * t0Knot; double rt0 = discountFactors.zeroRate(t0Knot) * t0Knot; double b0 = Math.exp(-rt0 - ht0); double effStart = discountFactors.relativeYearFraction(coupon.getEffectiveStartDate()); double t0 = t0Knot - effStart + omega; double pv = 0d; final int nItems = knots.size(); for (int j = 1; j < nItems; ++j) { double t = knots.get(j); double ht1 = survivalProbabilities.zeroRate(t) * t; double rt1 = discountFactors.zeroRate(t) * t; double b1 = Math.exp(-rt1 - ht1); double dt = knots.get(j) - knots.get(j - 1); double dht = ht1 - ht0; double drt = rt1 - rt0; double dhrt = dht + drt; double tPV; if (formula == AccrualOnDefaultFormula.MARKIT_FIX) { if (Math.abs(dhrt) < SMALL) { tPV = dht * dt * b0 * Epsilon.epsilonP(-dhrt); } else { tPV = dht * dt / dhrt * ((b0 - b1) / dhrt - b1); } } else { double t1 = t - effStart + omega; if (Math.abs(dhrt) < SMALL) { tPV = dht * b0 * (t0 * epsilon(-dhrt) + dt * Epsilon.epsilonP(-dhrt)); } else { tPV = dht / dhrt * (t0 * b0 - t1 * b1 + dt / dhrt * (b0 - b1)); } t0 = t1; } pv += tPV; ht0 = ht1; rt0 = rt1; b0 = b1; } double yearFractionCurve = discountFactors.getDayCount().relativeYearFraction(coupon.getStartDate(), coupon.getEndDate()); return coupon.getYearFraction() * pv / yearFractionCurve; } //------------------------------------------------------------------------- PointSensitivityBuilder protectionLegSensitivity( ResolvedCds cds, CreditDiscountFactors discountFactors, LegalEntitySurvivalProbabilities survivalProbabilities, LocalDate referenceDate, LocalDate effectiveStartDate, double recoveryRate) { DoubleArray integrationSchedule = DoublesScheduleGenerator.getIntegrationsPoints( discountFactors.relativeYearFraction(effectiveStartDate), discountFactors.relativeYearFraction(cds.getProtectionEndDate()), discountFactors.getParameterKeys(), survivalProbabilities.getParameterKeys()); int n = integrationSchedule.size(); double[] dht = new double[n - 1]; double[] drt = new double[n - 1]; double[] dhrt = new double[n - 1]; double[] p = new double[n]; double[] q = new double[n]; // pv double pv = 0d; double ht0 = survivalProbabilities.zeroRate(integrationSchedule.get(0)) * integrationSchedule.get(0); double rt0 = discountFactors.zeroRate(integrationSchedule.get(0)) * integrationSchedule.get(0); p[0] = Math.exp(-rt0); q[0] = Math.exp(-ht0); double b0 = p[0] * q[0]; for (int i = 1; i < n; ++i) { double ht1 = survivalProbabilities.zeroRate(integrationSchedule.get(i)) * integrationSchedule.get(i); double rt1 = discountFactors.zeroRate(integrationSchedule.get(i)) * integrationSchedule.get(i); p[i] = Math.exp(-rt1); q[i] = Math.exp(-ht1); double b1 = p[i] * q[i]; dht[i - 1] = ht1 - ht0; drt[i - 1] = rt1 - rt0; dhrt[i - 1] = dht[i - 1] + drt[i - 1]; double dPv = 0d; if (Math.abs(dhrt[i - 1]) < SMALL) { double eps = epsilon(-dhrt[i - 1]); dPv = dht[i - 1] * b0 * eps; } else { dPv = (b0 - b1) * dht[i - 1] / dhrt[i - 1]; } pv += dPv; ht0 = ht1; rt0 = rt1; b0 = b1; } double df = discountFactors.discountFactor(referenceDate); // pv sensitivity double factor = (1d - recoveryRate) / df; double eps0 = computeExtendedEpsilon(-dhrt[0], p[1], q[1], p[0], q[0]); PointSensitivityBuilder pvSensi = discountFactors.zeroRatePointSensitivity(integrationSchedule.get(0)) .multipliedBy(-dht[0] * q[0] * eps0 * factor); pvSensi = pvSensi.combinedWith(survivalProbabilities.zeroRatePointSensitivity(integrationSchedule.get(0)) .multipliedBy(factor * (drt[0] * p[0] * eps0 + p[0]))); for (int i = 1; i < n - 1; ++i) { double epsp = computeExtendedEpsilon(-dhrt[i], p[i + 1], q[i + 1], p[i], q[i]); double epsm = computeExtendedEpsilon(dhrt[i - 1], p[i - 1], q[i - 1], p[i], q[i]); PointSensitivityBuilder pSensi = discountFactors.zeroRatePointSensitivity(integrationSchedule.get(i)) .multipliedBy(factor * (-dht[i] * q[i] * epsp - dht[i - 1] * q[i] * epsm)); PointSensitivityBuilder qSensi = survivalProbabilities.zeroRatePointSensitivity(integrationSchedule.get(i)) .multipliedBy(factor * (drt[i - 1] * p[i] * epsm + drt[i] * p[i] * epsp)); pvSensi = pvSensi.combinedWith(pSensi).combinedWith(qSensi); } if (n > 1) { double epsLast = computeExtendedEpsilon(dhrt[n - 2], p[n - 2], q[n - 2], p[n - 1], q[n - 1]); pvSensi = pvSensi.combinedWith(discountFactors.zeroRatePointSensitivity(integrationSchedule.get(n - 1)) .multipliedBy(-dht[n - 2] * q[n - 1] * epsLast * factor)); pvSensi = pvSensi.combinedWith(survivalProbabilities.zeroRatePointSensitivity(integrationSchedule.get(n - 1)) .multipliedBy(factor * (drt[n - 2] * p[n - 1] * epsLast - p[n - 1]))); } PointSensitivityBuilder dfSensi = discountFactors.zeroRatePointSensitivity(referenceDate).multipliedBy(-pv * factor / df); return dfSensi.combinedWith(pvSensi); } private double computeExtendedEpsilon(double dhrt, double pn, double qn, double pd, double qd) { if (Math.abs(dhrt) < SMALL) { return -0.5 - dhrt / 6d - dhrt * dhrt / 24d; } return (1d - (pn * qn / (pd * qd) - 1d) / dhrt) / dhrt; } PointSensitivityBuilder riskyAnnuitySensitivity( ResolvedCds cds, CreditDiscountFactors discountFactors, LegalEntitySurvivalProbabilities survivalProbabilities, LocalDate referenceDate, LocalDate stepinDate, LocalDate effectiveStartDate) { double pv = 0d; PointSensitivityBuilder pvSensi = PointSensitivityBuilder.none(); for (CreditCouponPaymentPeriod coupon : cds.getPaymentPeriods()) { if (stepinDate.isBefore(coupon.getEndDate())) { double q = survivalProbabilities.survivalProbability(coupon.getEffectiveEndDate()); PointSensitivityBuilder qSensi = survivalProbabilities.zeroRatePointSensitivity(coupon.getEffectiveEndDate()); double p = discountFactors.discountFactor(coupon.getPaymentDate()); PointSensitivityBuilder pSensi = discountFactors.zeroRatePointSensitivity(coupon.getPaymentDate()); pv += coupon.getYearFraction() * p * q; pvSensi = pvSensi.combinedWith(pSensi.multipliedBy(coupon.getYearFraction() * q) .combinedWith(qSensi.multipliedBy(coupon.getYearFraction() * p))); } } if (cds.getPaymentOnDefault().isAccruedInterest()) { // This is needed so that the code is consistent with ISDA C when the Markit `fix' is used. LocalDate start = cds.getPaymentPeriods().size() == 1 ? effectiveStartDate : cds.getAccrualStartDate(); DoubleArray integrationSchedule = DoublesScheduleGenerator.getIntegrationsPoints( discountFactors.relativeYearFraction(start), discountFactors.relativeYearFraction(cds.getProtectionEndDate()), discountFactors.getParameterKeys(), survivalProbabilities.getParameterKeys()); for (CreditCouponPaymentPeriod coupon : cds.getPaymentPeriods()) { Pair pvAndSensi = singlePeriodAccrualOnDefaultSensitivity( coupon, effectiveStartDate, integrationSchedule, discountFactors, survivalProbabilities); pv += pvAndSensi.getFirst(); pvSensi = pvSensi.combinedWith(pvAndSensi.getSecond()); } } double df = discountFactors.discountFactor(referenceDate); PointSensitivityBuilder dfSensi = discountFactors.zeroRatePointSensitivity(referenceDate).multipliedBy(-pv / (df * df)); pvSensi = pvSensi.multipliedBy(1d / df); return dfSensi.combinedWith(pvSensi); } private Pair singlePeriodAccrualOnDefaultSensitivity( CreditCouponPaymentPeriod coupon, LocalDate effectiveStartDate, DoubleArray integrationSchedule, CreditDiscountFactors discountFactors, LegalEntitySurvivalProbabilities survivalProbabilities) { LocalDate start = coupon.getEffectiveStartDate().isBefore(effectiveStartDate) ? effectiveStartDate : coupon.getEffectiveStartDate(); if (!start.isBefore(coupon.getEffectiveEndDate())) { return Pair.of(0d, PointSensitivityBuilder.none()); //this coupon has already expired } DoubleArray knots = DoublesScheduleGenerator.truncateSetInclusive(discountFactors.relativeYearFraction(start), discountFactors.relativeYearFraction(coupon.getEffectiveEndDate()), integrationSchedule); // pv double pv = 0d; final int nItems = knots.size(); double[] dhrtBar = new double[nItems - 1]; double[] dhtBar = new double[nItems - 1]; double[] bBar = new double[nItems]; double[] p = new double[nItems]; double[] q = new double[nItems]; double t = knots.get(0); double ht0 = survivalProbabilities.zeroRate(t) * t; double rt0 = discountFactors.zeroRate(t) * t; q[0] = Math.exp(-ht0); p[0] = Math.exp(-rt0); double b0 = q[0] * p[0]; double effStart = discountFactors.relativeYearFraction(coupon.getEffectiveStartDate()); double t0 = t - effStart + omega; for (int i = 1; i < nItems; ++i) { t = knots.get(i); double ht1 = survivalProbabilities.zeroRate(t) * t; double rt1 = discountFactors.zeroRate(t) * t; q[i] = Math.exp(-ht1); p[i] = Math.exp(-rt1); double b1 = q[i] * p[i]; double dt = knots.get(i) - knots.get(i - 1); double dht = ht1 - ht0; double drt = rt1 - rt0; double dhrt = dht + drt; double tPv; if (formula == AccrualOnDefaultFormula.MARKIT_FIX) { if (Math.abs(dhrt) < SMALL) { double eps = epsilonP(-dhrt); tPv = dht * dt * b0 * eps; dhtBar[i - 1] = dt * b0 * eps; dhrtBar[i - 1] = -dht * dt * b0 * epsilonPP(-dhrt); bBar[i - 1] += dht * eps; } else { tPv = dht * dt / dhrt * ((b0 - b1) / dhrt - b1); dhtBar[i - 1] = dt / dhrt * ((b0 - b1) / dhrt - b1); dhrtBar[i - 1] = dht * dt / (dhrt * dhrt) * (b1 - 2d * (b0 - b1) / dhrt); bBar[i - 1] += dht * dt / (dhrt * dhrt); bBar[i] += -dht * dt / dhrt * (1d + 1d / dhrt); } } else { double t1 = t - effStart + omega; if (Math.abs(dhrt) < SMALL) { double eps = epsilon(-dhrt); double epsp = epsilonP(-dhrt); tPv = dht * b0 * (t0 * eps + dt * epsp); dhtBar[i - 1] = b0 * (t0 * eps + dt * epsp); dhrtBar[i - 1] = -dht * b0 * (t0 * epsp + dt * epsilonPP(-dhrt)); bBar[i - 1] += dht * (t0 * eps + dt * epsp); } else { tPv = dht / dhrt * (t0 * b0 - t1 * b1 + dt / dhrt * (b0 - b1)); dhtBar[i - 1] = (t0 * b0 - t1 * b1 + dt / dhrt * (b0 - b1)) / dhrt; dhrtBar[i - 1] = dht / (dhrt * dhrt) * (-2d * dt / dhrt * (b0 - b1) - t0 * b0 + t1 * b1); bBar[i - 1] += dht / dhrt * (t0 + dt / dhrt); bBar[i] += dht / dhrt * (-t1 - dt / dhrt); } t0 = t1; } pv += tPv; ht0 = ht1; rt0 = rt1; b0 = b1; } double yfRatio = coupon.getYearFraction() / discountFactors.getDayCount().relativeYearFraction(coupon.getStartDate(), coupon.getEndDate()); // pv sensitivity PointSensitivityBuilder qSensiFirst = survivalProbabilities.zeroRatePointSensitivity(knots.get(0)) .multipliedBy(yfRatio * ((dhrtBar[0] + dhtBar[0]) / q[0] + bBar[0] * p[0])); PointSensitivityBuilder pSensiFirst = discountFactors.zeroRatePointSensitivity(knots.get(0)) .multipliedBy(yfRatio * (dhrtBar[0] / p[0] + bBar[0] * q[0])); PointSensitivityBuilder pvSensi = pSensiFirst.combinedWith(qSensiFirst); for (int i = 1; i < nItems - 1; ++i) { PointSensitivityBuilder qSensi = survivalProbabilities.zeroRatePointSensitivity(knots.get(i)).multipliedBy( yfRatio * (-(dhrtBar[i - 1] + dhtBar[i - 1]) / q[i] + (dhrtBar[i] + dhtBar[i]) / q[i] + bBar[i] * p[i])); PointSensitivityBuilder pSensi = discountFactors.zeroRatePointSensitivity(knots.get(i)).multipliedBy( yfRatio * (-dhrtBar[i - 1] / p[i] + dhrtBar[i] / p[i] + bBar[i] * q[i])); pvSensi = pvSensi.combinedWith(pSensi).combinedWith(qSensi); } if (nItems > 1) { PointSensitivityBuilder qSensiLast = survivalProbabilities.zeroRatePointSensitivity(knots.get(nItems - 1)).multipliedBy( yfRatio * (-(dhrtBar[nItems - 2] + dhtBar[nItems - 2]) / q[nItems - 1] + bBar[nItems - 1] * p[nItems - 1])); PointSensitivityBuilder pSensiLast = discountFactors.zeroRatePointSensitivity(knots.get(nItems - 1)).multipliedBy( yfRatio * (-dhrtBar[nItems - 2] / p[nItems - 1] + bBar[nItems - 1] * q[nItems - 1])); pvSensi = pvSensi.combinedWith(pSensiLast).combinedWith(qSensiLast); } return Pair.of(yfRatio * pv, pvSensi); } //------------------------------------------------------------------------- private boolean isExpired(ResolvedCds cds, CreditRatesProvider ratesProvider) { return !cds.getProtectionEndDate().isAfter(ratesProvider.getValuationDate()); } double recoveryRate(ResolvedCds cds, CreditRatesProvider ratesProvider) { RecoveryRates recoveryRates = ratesProvider.recoveryRates(cds.getLegalEntityId()); ArgChecker.isTrue(recoveryRates instanceof ConstantRecoveryRates, "recoveryRates must be ConstantRecoveryRates"); return recoveryRates.recoveryRate(cds.getProtectionEndDate()); } void validateRecoveryRates(ResolvedCds cds, CreditRatesProvider ratesProvider) { RecoveryRates recoveryRates = ratesProvider.recoveryRates(cds.getLegalEntityId()); ArgChecker.isTrue(recoveryRates instanceof ConstantRecoveryRates, "recoveryRates must be ConstantRecoveryRates"); } private Pair reduceDiscountFactors( ResolvedCds cds, CreditRatesProvider ratesProvider) { Currency currency = cds.getCurrency(); CreditDiscountFactors discountFactors = ratesProvider.discountFactors(currency); ArgChecker.isTrue(discountFactors.isIsdaCompliant(), "discount factors must be IsdaCompliantZeroRateDiscountFactors"); LegalEntitySurvivalProbabilities survivalProbabilities = ratesProvider.survivalProbabilities(cds.getLegalEntityId(), currency); ArgChecker.isTrue(survivalProbabilities.getSurvivalProbabilities().isIsdaCompliant(), "survival probabilities must be IsdaCompliantZeroRateDiscountFactors"); ArgChecker.isTrue(discountFactors.getDayCount().equals(survivalProbabilities.getSurvivalProbabilities().getDayCount()), "day count conventions of discounting curve and credit curve must be the same"); return Pair.of(discountFactors, survivalProbabilities); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy