/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.impl.credit.isda; import java.util.Arrays; import com.opengamma.strata.collect.ArgChecker; import com.opengamma.strata.market.ShiftType; import com.opengamma.strata.math.impl.differentiation.FiniteDifferenceType; /** * This calculates the sensitivity of the present value of a CDS to various (finite) shifts * of the market spreads - this is performed by a "bump and reprice" so is accurate for * arbitrarily large shifts/bumps. * <p> * For small bumps (typically less than 1bps) it approximates the derivative * $$\frac{\partial V}{\partial S}$$ where $V$ is the present value and $S$ is * either a single market spread or the entire market spread curve. However, it is better * (in accuracy and speed) to use AnalyticSpreadSensitivityCalculator if this derivative is required. */ public class FiniteDifferenceSpreadSensitivityCalculator { private final MarketQuoteConverter _pufConverter; private final IsdaCompliantCreditCurveBuilder _curveBuilder; private final AnalyticCdsPricer _pricer; /** * Creates an instance using the default formula. */ public FiniteDifferenceSpreadSensitivityCalculator() { _pufConverter = new MarketQuoteConverter(); _curveBuilder = new FastCreditCurveBuilder(); _pricer = new AnalyticCdsPricer(); } /** * Creates an instance specifying the formula. * * @param formula the formula to use */ public FiniteDifferenceSpreadSensitivityCalculator(AccrualOnDefaultFormulae formula) { _pufConverter = new MarketQuoteConverter(formula); _curveBuilder = new FastCreditCurveBuilder(formula); _pricer = new AnalyticCdsPricer(formula); } //*************************************************************************************************************** // parallel CS01 of a CDS from single market quote of that CDS //*************************************************************************************************************** /** * The CS01 (or credit DV01) of a CDS. * <p> * This is the sensitivity of the PV to a finite increase of market spread (on NOT the CDS's coupon). * If the CDS is quoted as points up-front, this is first converted to a quoted spread, and <b>this</b> is bumped. * * @param cds the analytic description of a CDS traded at a certain time * @param quote the market quote for the CDS - these can be ParSpread, PointsUpFront or QuotedSpread * @param yieldCurve the yield (or discount) curve * @param fracBumpAmount The fraction bump amount of the spread so a 1pb bump is 1e-4 * @return the parallel CS01 */ public double parallelCS01( CdsAnalytic cds, CdsQuoteConvention quote, IsdaCompliantYieldCurve yieldCurve, double fracBumpAmount) { if (quote instanceof CdsQuotedSpread) { CdsQuotedSpread qSpread = (CdsQuotedSpread) quote; return parallelCS01FromParSpreads(cds, qSpread.getCoupon(), yieldCurve, new CdsAnalytic[] {cds}, new double[] {qSpread.getQuotedSpread()}, fracBumpAmount, ShiftType.ABSOLUTE); } else if (quote instanceof PointsUpFront) { PointsUpFront puf = (PointsUpFront) quote; return parallelCS01FromPUF(cds, puf.getCoupon(), yieldCurve, puf.getPointsUpFront(), fracBumpAmount); } else if (quote instanceof CdsParSpread) { return parallelCS01FromParSpreads(cds, quote.getCoupon(), yieldCurve, new CdsAnalytic[] {cds}, new double[] {quote.getCoupon()}, fracBumpAmount, ShiftType.ABSOLUTE); } throw new IllegalArgumentException("unknow type " + quote.getClass()); } /** * The CS01 (or credit DV01) by a shift of the quoted (or flat) spread of the CDS * <b>when the CDS is quoted as points up-front (PUF)</b>. * <p> * This simply converts the PUF quote to a quoted (or flat) spread then calls parallelCS01FromQuotedSpread. * * @param cds analytic description of a CDS traded at a certain time * @param coupon the of the traded CDS (expressed as <b>fractions not basis points</b>) * @param yieldCurve the yield (or discount) curve * @param puf the points up-front (as a fraction) * @param fracBumpAmount the fraction bump amount <b>of the quoted (or flat) spread</b>, so a 1pb bump is 1e-4 * @return the credit DV01 */ public double parallelCS01FromPUF( CdsAnalytic cds, double coupon, IsdaCompliantYieldCurve yieldCurve, double puf, double fracBumpAmount) { double bumpedQSpread = _pufConverter.pufToQuotedSpread(cds, coupon, yieldCurve, puf) + fracBumpAmount; IsdaCompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(cds, bumpedQSpread, yieldCurve); double bumpedPrice = _pricer.pv(cds, yieldCurve, bumpedCurve, coupon); return (bumpedPrice - puf) / fracBumpAmount; } /** * The CS01 (or credit DV01) by a shift of the market spread of the CDS (the coupon is unchanged). * <p> * This finds two flat credit/hazard curves from the CDS with its original spread * and with the spread bumped. The traded CDS is then priced off both curves, using its coupon, * and the difference (divided by the bump size) is the credit DV01. * * @param cds the analytic description of a CDS traded at a certain time * @param coupon the of the traded CDS (expressed as <b>fractions not basis points</b>) * @param yieldCurve the yield (or discount) curve * @param marketSpread the market spread of the reference CDS (in this case it is irrelevant * whether this is par or quoted spread) * @param fracBumpAmount the fraction bump amount, so a 1pb bump is 1e-4 * @param shiftType ABSOLUTE or RELATIVE * @return the credit DV01 */ public double parallelCS01FromSpread( CdsAnalytic cds, double coupon, IsdaCompliantYieldCurve yieldCurve, double marketSpread, double fracBumpAmount, ShiftType shiftType) { return parallelCS01FromParSpreads(cds, coupon, yieldCurve, new CdsAnalytic[] {cds}, new double[] {marketSpread}, fracBumpAmount, shiftType); } //*************************************************************************************************************** // parallel CS01 of CDS from single market quote of (potentially) different CDS //*************************************************************************************************************** /** * The CS01 (or credit DV01) by a shift of the quoted (or flat) spread of the reference CDS. * <p> * This finds two flat credit/hazard curves from a reference CDS with its original quoted * (or flat) spread and with the spread bumped. The traded CDS is then priced off both * curves, using its coupon, and the difference (divided by the bump size) is the credit DV01. * * @param cds the analytic description of a CDS traded at a certain time * @param coupon the coupon of the traded CDS (expressed as <b>fractions not basis points</b>) * @param yieldCurve the yield (or discount) curve * @param referenceCDS the reference CDS use to find the flat credit/hazard curve (this is often the same as the traded CDS) * @param quotedSpread the quoted (or flat) spread of the reference CDS * @param fracBumpAmount The fraction bump amount, so a 1pb bump is 1e-4 * @param shiftType ABSOLUTE or RELATIVE * @return the credit DV01 */ public double parallelCS01FromQuotedSpread( CdsAnalytic cds, double coupon, IsdaCompliantYieldCurve yieldCurve, CdsAnalytic referenceCDS, double quotedSpread, double fracBumpAmount, ShiftType shiftType) { ArgChecker.notNull(cds, "cds"); ArgChecker.notNull(referenceCDS, "referanceCDS"); ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.notNull(shiftType, "shiftType"); ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small"); return parallelCS01FromParSpreads(cds, coupon, yieldCurve, new CdsAnalytic[] {referenceCDS}, new double[] {quotedSpread}, fracBumpAmount, shiftType); } //*************************************************************************************************************** // parallel CS01 of a CDS from a set of market quotes at pillar dates (e.g. 6M, 1Y, 3Y, 5Y, 10Y) //*************************************************************************************************************** /** * The CS01 (or credit DV01) by a parallel shift of the market spreads (CDS spread curve). * <p> * This takes an extraneous yield curve, a set of reference CDSs (marketCDSs) and their * market quotes and bootstraps a credit (hazard) curve - the target CDS is then priced * with this credit curve. * This is then repeated with the market spreads bumped in parallel by some amount. * The result is the difference (bumped minus base price) is divided by the bump amount. * <p> * This can take quotes as ParSpread, PointsUpFront or QuotedSpread (or some mix). * For par-spreads, these are bumped and a new credit curve built; for quoted-spreads, there * are bumped and a new curve build be first converting to PUF; and finally for PUF, these are * converted to quoted spreads, bumped and converted back to build the credit curve. * * @param cds the analytic description of a CDS traded at a certain time * @param cdsCoupon the coupon of the traded CDS (expressed as <b>fractions not basis points</b>) * @param yieldCurve the yield (or discount) curve * @param marketCDSs the market CDSs - these are the reference instruments used to build the credit curve * @param quotes the quotes for the market CDSs - these can be ParSpread, PointsUpFront or QuotedSpread * (or any mixture of these) * @param fracBumpAmount the fraction bump amount, so a 1pb bump is 1e-4 * @return the credit DV01 */ public double parallelCS01FromPillarQuotes( CdsAnalytic cds, double cdsCoupon, IsdaCompliantYieldCurve yieldCurve, CdsAnalytic[] marketCDSs, CdsQuoteConvention[] quotes, double fracBumpAmount) { ArgChecker.notNull(cds, "cds"); ArgChecker.noNulls(marketCDSs, "curvePoints"); ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.noNulls(quotes, "quotes"); ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small"); int n = marketCDSs.length; ArgChecker.isTrue(n == quotes.length, "speads length does not match curvePoints"); IsdaCompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, quotes, yieldCurve); double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon); CdsQuoteConvention[] bumpedQuotes = bumpQuotes(marketCDSs, quotes, yieldCurve, fracBumpAmount); IsdaCompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, bumpedQuotes, yieldCurve); double bumpedPrice = _pricer.pv(cds, yieldCurve, bumpedCurve, cdsCoupon); return (bumpedPrice - basePrice) / fracBumpAmount; } /** * The CS01 (or credit DV01) by a parallel shift of the market par spreads (CDS par spread curve). * <p> * This takes an extraneous yield curve, a set of reference CDSs (marketCDSs) and their * par-spreads (expressed as <b>fractions not basis points</b>) and bootstraps a credit * (hazard) curve - the target CDS is then priced with this credit curve. * This is then repeated with the market spreads bumped in parallel by some amount. * The result is the difference (bumped minus base price) is divided by the bump amount. * <p> * For small bumps (<1e-4) this approximates $$\frac{\partial V}{\partial S}$$<br> * Credit DV01 is (often) defined as -( V(S + 1bp) - V(s)) - to achieve this use * fracBumpAmount = 1e-4 and shiftType ABSOLUTE. * * @param cds the analytic description of a CDS traded at a certain time * @param cdsFracSpread the <b>fraction</b> spread of the CDS * @param yieldCurve the yield (or discount) curve * @param marketCDSs the market CDSs - these are the reference instruments used to build the credit curve * @param parSpreads the <b>fractional</b> spreads of the market CDSs * @param fracBumpAmount the fraction bump amount, so a 1pb bump is 1e-4 * @param shiftType ABSOLUTE or RELATIVE * @return the credit DV01 */ public double parallelCS01FromParSpreads( CdsAnalytic cds, double cdsFracSpread, IsdaCompliantYieldCurve yieldCurve, CdsAnalytic[] marketCDSs, double[] parSpreads, double fracBumpAmount, ShiftType shiftType) { ArgChecker.notNull(cds, "cds"); ArgChecker.noNulls(marketCDSs, "curvePoints"); ArgChecker.notEmpty(parSpreads, "spreads"); ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.notNull(shiftType, "shiftType"); ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small"); int n = marketCDSs.length; ArgChecker.isTrue(n == parSpreads.length, "speads length does not match curvePoints"); double[] bumpedSpreads = makeBumpedSpreads(parSpreads, fracBumpAmount, shiftType); double diff = fdCreditDV01(cds, cdsFracSpread, marketCDSs, bumpedSpreads, parSpreads, yieldCurve, CdsPriceType.DIRTY); return diff / fracBumpAmount; } public double parallelCS01FromCreditCurve( CdsAnalytic cds, double cdsCoupon, CdsAnalytic[] pillarCDSs, IsdaCompliantYieldCurve yieldCurve, IsdaCompliantCreditCurve creditCurve, double fracBumpAmount) { ArgChecker.notNull(cds, "cds"); ArgChecker.noNulls(pillarCDSs, "pillarCDSs"); ArgChecker.notNull(creditCurve, "creditCurve"); ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small"); int n = pillarCDSs.length; double[] impSpreads = new double[n]; double[] t = new double[n]; for (int i = 0; i < n; i++) { impSpreads[i] = _pricer.parSpread(pillarCDSs[i], yieldCurve, creditCurve); t[i] = pillarCDSs[i].getProtectionEnd(); if (i > 0) { ArgChecker.isTrue(t[i] > t[i - 1], "pillars must be assending"); } } IsdaCompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(pillarCDSs, impSpreads, yieldCurve); double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon); double[] bumpedSpreads = makeBumpedSpreads(impSpreads, fracBumpAmount, ShiftType.ABSOLUTE); IsdaCompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(pillarCDSs, bumpedSpreads, yieldCurve); double price = _pricer.pv(cds, yieldCurve, bumpedCurve, cdsCoupon); double res = (price - basePrice) / fracBumpAmount; return res; } //*************************************************************************************************************** // bucked CS01 - the sensitivity of the CDS's PV to the market spreads used to build the credit curve - these are // the pillar dates (e.g. 6M, 1Y, 3Y, 5Y, 10Y) //*************************************************************************************************************** /** * The bucked CS01 (or credit DV01) by a shift of each the market spread in turn. * <p> * This takes an extraneous yield curve, a set of reference CDSs (marketCDSs) and their * market quotes and bootstraps a credit (hazard) curve - the target CDS is then priced * with this credit curve. This is then repeated with each market spreads bumped in turn by * some amount. The result is the array of differences (bumped minus base price) is divided * by the bump amount. * <p> * This can take quotes as ParSpread, PointsUpFront or QuotedSpread (or some mix). * For par-spreads, these are bumped and a new credit curve built; for quoted-spreads, there * are bumped and a new curve build be first converting to PUF; and finally for PUF, these * are converted to quoted spreads, bumped and converted back to build the credit curve. * * @param cds the analytic description of a CDS traded at a certain time * @param cdsCoupon the coupon of the traded CDS (expressed as <b>fractions not basis points</b>) * @param yieldCurve the yield (or discount) curve * @param marketCDSs the market CDSs - these are the reference instruments used to build the credit curve * @param quotes the quotes for the market CDSs - these can be ParSpread, PointsUpFront or QuotedSpread * (or any mixture of these) * @param fracBumpAmount the fraction bump amount, so a 1pb bump is 1e-4 * @return the bucketed credit DV01 */ public double[] bucketedCS01FromPillarQuotes( CdsAnalytic cds, double cdsCoupon, IsdaCompliantYieldCurve yieldCurve, CdsAnalytic[] marketCDSs, CdsQuoteConvention[] quotes, double fracBumpAmount) { ArgChecker.notNull(cds, "cds"); ArgChecker.noNulls(marketCDSs, "curvePoints"); ArgChecker.noNulls(quotes, "quotes"); ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small"); int n = marketCDSs.length; ArgChecker.isTrue(n == quotes.length, "speads length does not match curvePoints"); IsdaCompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, quotes, yieldCurve); double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon); double[] res = new double[n]; for (int i = 0; i < n; i++) { CdsQuoteConvention[] bumpedQuotes = bumpQuoteAtIndex(marketCDSs, quotes, yieldCurve, fracBumpAmount, i); IsdaCompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, bumpedQuotes, yieldCurve); double price = _pricer.pv(cds, yieldCurve, bumpedCurve, cdsCoupon); res[i] = (price - basePrice) / fracBumpAmount; } return res; } /** * The bucked CS01 (or credit DV01) by shifting each market par-spread in turn. * <p> * This takes an extraneous yield curve, a set of reference CDSs (marketCDSs) and their * par-spreads (expressed as <b>fractions not basis points</b>) and bootstraps a credit * (hazard) curve - the target CDS is then priced with this credit curve. * This is then repeated with each market spreads bumped in turn. * The result is the vector of differences (bumped minus base price) divided by the bump amount. * <p> * For small bumps (<1e-4) this approximates $$\frac{\partial V}{\partial S_i}$$ where $$S_i$$ * is the spread of the $$1^{th}$$ market CDS. * * @param cds the analytic description of a CDS traded at a certain time * @param cdsCoupon the <b>fraction</b> spread of the CDS * @param yieldCurve the yield (or discount) curve * @param marketCDSs the market CDSs - these are the reference instruments used to build the credit curve * @param marketParSpreads the <b>fractional</b> par-spreads of the market CDSs * @param fracBumpAmount the fraction bump amount, so a 1pb bump is 1e-4 * @param shiftType ABSOLUTE or RELATIVE * @return the credit CS01 */ public double[] bucketedCS01FromParSpreads( CdsAnalytic cds, double cdsCoupon, IsdaCompliantYieldCurve yieldCurve, CdsAnalytic[] marketCDSs, double[] marketParSpreads, double fracBumpAmount, ShiftType shiftType) { ArgChecker.notNull(cds, "cds"); ArgChecker.noNulls(marketCDSs, "curvePoints"); ArgChecker.notEmpty(marketParSpreads, "spreads"); ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.notNull(shiftType, "shiftType"); ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small"); int n = marketCDSs.length; ArgChecker.isTrue(n == marketParSpreads.length, "speads length does not match curvePoints"); CdsPriceType priceType = CdsPriceType.DIRTY; IsdaCompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, marketParSpreads, yieldCurve); double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon, priceType); double[] res = new double[n]; for (int i = 0; i < n; i++) { double[] temp = makeBumpedSpreads(marketParSpreads, fracBumpAmount, shiftType, i); IsdaCompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, temp, yieldCurve); double price = _pricer.pv(cds, yieldCurve, bumpedCurve, cdsCoupon, priceType); res[i] = (price - basePrice) / fracBumpAmount; } return res; } /** * The bucked CS01 (or credit DV01) by bumping each quoted (or flat) spread in turn. * <p> * This takes an extraneous yield curve, a set of reference CDSs (marketCDSs) and their quoted * (or flat) spreads (expressed as <b>fractions not basis points</b>) and bootstraps a credit * (hazard) curve - the target CDS is then priced with this credit curve. * This is then repeated with each market spreads bumped in turn. * The result is the vector of differences (bumped minus base price) divided by the bump amount. * <p> * For small bumps (<1e-4) this approximates $$\frac{\partial V}{\partial S_i}$$ * for a flat curve where $$S_i$$ is the spread of the $$1^{th}$$ market CDS. * * @param cds the analytic description of a CDS traded at a certain time * @param dealSpread the <b>fraction</b> spread of the CDS * @param yieldCurve the yield (or discount) curve * @param marketCDSs the market CDSs - these are the reference instruments used to build the credit curve * @param quotedSpreads the <b>fractional</b> spreads of the market CDSs * @param fracBumpAmount the fraction bump amount, so a 1pb bump is 1e-4 * @param shiftType ABSOLUTE or RELATIVE * @return the bucked CS01 for a single CDS */ public double[] bucketedCS01FromQuotedSpreads( CdsAnalytic cds, double dealSpread, IsdaCompliantYieldCurve yieldCurve, CdsAnalytic[] marketCDSs, double[] quotedSpreads, double fracBumpAmount, ShiftType shiftType) { ArgChecker.notNull(cds, "cds"); ArgChecker.noNulls(marketCDSs, "curvePoints"); ArgChecker.notEmpty(quotedSpreads, "spreads"); ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.notNull(shiftType, "shiftType"); ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small"); int n = marketCDSs.length; ArgChecker.isTrue(n == quotedSpreads.length, "speads length does not match curvePoints"); CdsPriceType priceType = CdsPriceType.DIRTY; double[] premiums = new double[n]; Arrays.fill(premiums, dealSpread); // assume the premiums of all CDS are equal double[] puf = _pufConverter.quotedSpreadsToPUF(marketCDSs, premiums, yieldCurve, quotedSpreads); IsdaCompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, premiums, yieldCurve, puf); double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, dealSpread, priceType); double[] bumpedPUF = new double[n]; double[] res = new double[n]; for (int i = 0; i < n; i++) { System.arraycopy(puf, 0, bumpedPUF, 0, n); double bumpedSpread = bumpedSpread(quotedSpreads[i], fracBumpAmount, shiftType); bumpedPUF[i] = _pufConverter.quotedSpreadToPUF(marketCDSs[i], premiums[i], yieldCurve, bumpedSpread); // TODO a lot of unnecessary recalibration here IsdaCompliantCreditCurve bumpedCurve = _curveBuilder .calibrateCreditCurve(marketCDSs, premiums, yieldCurve, bumpedPUF); double price = _pricer.pv(cds, yieldCurve, bumpedCurve, dealSpread, priceType); res[i] = (price - basePrice) / fracBumpAmount; } return res; } /** * The bucked CS01 (or credit DV01) on a set of CDSS by bumping each quoted (or flat) spread in turn. * <p> * This takes an extraneous yield curve, a set of reference CDSs (marketCDSs) and their quoted * (or flat) spreads (expressed as <b>fractions not basis points</b>) and bootstraps a credit * (hazard) curve - the target CDS is then priced with this credit curve. * This is then repeated with each market spreads bumped in turn. * The result is the vector of differences (bumped minus base price) divided by the bump amount. * <p> * For small bumps (<1e-4) this approximates $$\frac{\partial V}{\partial S_i}$$ for a flat curve where $$S_i$$. * * @param cds a set of analytic description of CDSs traded at a certain times * @param dealSpread the <b>fraction</b> spread of the CDS * @param yieldCurve the yield (or discount) curve * @param marketCDSs the market CDSs - these are the reference instruments used to build the credit curve * @param quotedSpreads the <b>fractional</b> spreads of the market CDSs * @param fracBumpAmount the fraction bump amount, so a 1pb bump is 1e-4 * @param shiftType ABSOLUTE or RELATIVE * @return the bucked CS01 for a set of CDSs */ public double[][] bucketedCS01FromQuotedSpreads( CdsAnalytic[] cds, double dealSpread, IsdaCompliantYieldCurve yieldCurve, CdsAnalytic[] marketCDSs, double[] quotedSpreads, double fracBumpAmount, ShiftType shiftType) { ArgChecker.noNulls(cds, "cds"); ArgChecker.noNulls(marketCDSs, "curvePoints"); ArgChecker.notEmpty(quotedSpreads, "spreads"); ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.notNull(shiftType, "shiftType"); ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small"); int nMarketCDSs = marketCDSs.length; ArgChecker.isTrue(nMarketCDSs == quotedSpreads.length, "speads length does not match curvePoints"); CdsPriceType priceType = CdsPriceType.DIRTY; double[] premiums = new double[nMarketCDSs]; Arrays.fill(premiums, dealSpread); // assume the premiums of all CDS are equal int nTradeCDSs = cds.length; double[] puf = _pufConverter.quotedSpreadsToPUF(marketCDSs, premiums, yieldCurve, quotedSpreads); //TODO not needed IsdaCompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, premiums, yieldCurve, puf); double[] basePrices = new double[nTradeCDSs]; for (int j = 0; j < nTradeCDSs; j++) { basePrices[j] = _pricer.pv(cds[j], yieldCurve, baseCurve, dealSpread, priceType); } double[] bumpedPUF = new double[nMarketCDSs]; double[][] res = new double[nTradeCDSs][nMarketCDSs]; for (int i = 0; i < nMarketCDSs; i++) { //Outer loop is over bumps System.arraycopy(puf, 0, bumpedPUF, 0, nMarketCDSs); double bumpedSpread = bumpedSpread(quotedSpreads[i], fracBumpAmount, shiftType); bumpedPUF[i] = _pufConverter.quotedSpreadToPUF(marketCDSs[i], premiums[i], yieldCurve, bumpedSpread); // TODO a lot of unnecessary recalibration here IsdaCompliantCreditCurve bumpedCurve = _curveBuilder .calibrateCreditCurve(marketCDSs, premiums, yieldCurve, bumpedPUF); for (int j = 0; j < nTradeCDSs; j++) { double price = _pricer.pv(cds[j], yieldCurve, bumpedCurve, dealSpread, priceType); res[j][i] = (price - basePrices[j]) / fracBumpAmount; } } return res; } /** * The bucked CS01 (or credit DV01) by shifting each implied par-spread in turn. * <p> * This takes an extraneous yield curve, a set of pillar CDSs and their corresponding * par-spread, and a set of bucket CDSs (CDSs with maturities equal to the bucket points). * A credit curve is bootstrapped from the pillar CDSs - this is then used to imply * spreads at the bucket maturities. These spreads form pseudo market spreads to bootstraps * a new credit (hazard) curve - the target CDS is then priced with this credit curve. * This is then repeated with each spreads bumped in turn. * The result is the vector of differences (bumped minus base price) divided by the bump amount. * * @param cds the analytic description of a CDS traded at a certain time * @param cdsCoupon the <b>fraction</b> spread of the CDS * @param bucketCDSs these are the reference instruments that correspond to maturity buckets * @param yieldCurve the yield (or discount) curve * @param pillarCDSs these are the market CDSs used to build the credit curve * @param pillarSpreads these are the par-spreads of the market (pillar) CDSs * @param fracBumpAmount the fraction bump amount, so a 1pb bump is 1e-4 * @return the credit DV01 */ public double[] bucketedCS01FromParSpreads( CdsAnalytic cds, double cdsCoupon, CdsAnalytic[] bucketCDSs, IsdaCompliantYieldCurve yieldCurve, CdsAnalytic[] pillarCDSs, double[] pillarSpreads, double fracBumpAmount) { ArgChecker.noNulls(pillarCDSs, "pillarCDSs"); ArgChecker.notEmpty(pillarSpreads, "pillarSpreads"); IsdaCompliantCreditCurve creditCurve = _curveBuilder.calibrateCreditCurve(pillarCDSs, pillarSpreads, yieldCurve); return bucketedCS01FromCreditCurve(cds, cdsCoupon, bucketCDSs, yieldCurve, creditCurve, fracBumpAmount); } public double[] bucketedCS01FromPUF( CdsAnalytic cds, PointsUpFront puf, IsdaCompliantYieldCurve yieldCurve, CdsAnalytic[] bucketCDSs, double fracBumpAmount) { IsdaCompliantCreditCurve cc = _curveBuilder.calibrateCreditCurve(cds, puf, yieldCurve); return bucketedCS01FromCreditCurve(cds, puf.getCoupon(), bucketCDSs, yieldCurve, cc, fracBumpAmount); } /** * The bucked CS01 (or credit DV01) by shifting each implied par-spread in turn. * <p> * This takes an extraneous yield curve and a credit curve and a set of bucket CDSs * (CDSs with maturities equal to the bucket points). Par-spreads at the bucket maturities * are implied from the credit curve. These spreads form pseudo market spreads to bootstraps * a new credit (hazard) curve - the target CDS is then priced with this credit curve. * This is then repeated with each spreads bumped in turn. * The result is the vector of differences (bumped minus base price) divided by the bump amount. * * @param cds the analytic description of a CDS traded at a certain time * @param cdsCoupon the <b>fraction</b> spread of the CDS * @param bucketCDSs these are the reference instruments that correspond to maturity buckets * @param yieldCurve the yield (or discount) curve * @param creditCurve the credit curve * @param fracBumpAmount the fraction bump amount, so a 1pb bump is 1e-4 * @return the credit DV01 */ public double[] bucketedCS01FromCreditCurve( CdsAnalytic cds, double cdsCoupon, CdsAnalytic[] bucketCDSs, IsdaCompliantYieldCurve yieldCurve, IsdaCompliantCreditCurve creditCurve, double fracBumpAmount) { ArgChecker.notNull(cds, "cds"); ArgChecker.noNulls(bucketCDSs, "bucketCDSs"); ArgChecker.notNull(creditCurve, "creditCurve"); ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small"); int n = bucketCDSs.length; double[] impSpreads = new double[n]; double[] t = new double[n]; for (int i = 0; i < n; i++) { impSpreads[i] = _pricer.parSpread(bucketCDSs[i], yieldCurve, creditCurve); t[i] = bucketCDSs[i].getProtectionEnd(); if (i > 0) { ArgChecker.isTrue(t[i] > t[i - 1], "buckets must be assending"); } } int index = Arrays.binarySearch(t, cds.getProtectionEnd()); if (index < 0) { index = -1 - index; } index = Math.min(index, n - 1); IsdaCompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(bucketCDSs, impSpreads, yieldCurve); double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon); double[] res = new double[n]; for (int i = 0; i <= index; i++) { //don't bother calculating where there is no sensitivity double[] bumpedSpreads = makeBumpedSpreads(impSpreads, fracBumpAmount, ShiftType.ABSOLUTE, i); IsdaCompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(bucketCDSs, bumpedSpreads, yieldCurve); double price = _pricer.pv(cds, yieldCurve, bumpedCurve, cdsCoupon); res[i] = (price - basePrice) / fracBumpAmount; } return res; } /** * The difference in PV between two market spreads. * * @param cds the analytic description of a CDS traded at a certain time * @param cdsFracSpread The <b>fraction</b> spread of the CDS * @param priceType Clean or dirty price * @param yieldCurve The yield (or discount) curve * @param marketCDSs The market CDSs - these are the reference instruments used to build the credit curve * @param marketFracSpreads The <b>fractional</b> spreads of the market CDSs * @param fracDeltaSpreads Non-negative shifts * @param fdType The finite difference type (forward, central or backward) * @return the difference in PV between two market spreads */ public double finiteDifferenceSpreadSensitivity( CdsAnalytic cds, double cdsFracSpread, CdsPriceType priceType, IsdaCompliantYieldCurve yieldCurve, CdsAnalytic[] marketCDSs, double[] marketFracSpreads, double[] fracDeltaSpreads, FiniteDifferenceType fdType) { ArgChecker.notNull(cds, "cds"); ArgChecker.noNulls(marketCDSs, "curvePoints"); ArgChecker.notEmpty(marketFracSpreads, "spreads"); ArgChecker.notEmpty(fracDeltaSpreads, "deltaSpreads"); ArgChecker.notNull(yieldCurve, "yieldCurve"); ArgChecker.notNull(priceType, "priceType"); int n = marketCDSs.length; ArgChecker.isTrue(n == marketFracSpreads.length, "speads length does not match curvePoints"); ArgChecker.isTrue(n == fracDeltaSpreads.length, "deltaSpreads length does not match curvePoints"); for (int i = 0; i < n; i++) { ArgChecker.isTrue(marketFracSpreads[i] > 0, "spreads must be positive"); ArgChecker.isTrue(fracDeltaSpreads[i] >= 0, "deltaSpreads must none negative"); ArgChecker.isTrue(fdType == FiniteDifferenceType.FORWARD || fracDeltaSpreads[i] < marketFracSpreads[i], "deltaSpread must be less spread, unless forward difference is used"); } switch (fdType) { case CENTRAL: return fdCentral(cds, cdsFracSpread, marketCDSs, marketFracSpreads, fracDeltaSpreads, yieldCurve, priceType); case FORWARD: return fdForward(cds, cdsFracSpread, marketCDSs, marketFracSpreads, fracDeltaSpreads, yieldCurve, priceType); case BACKWARD: return fdBackwards(cds, cdsFracSpread, marketCDSs, marketFracSpreads, fracDeltaSpreads, yieldCurve, priceType); default: throw new IllegalArgumentException("unknown type " + fdType); } } private double fdCreditDV01( CdsAnalytic pricingCDS, double cdsSpread, CdsAnalytic[] curvePoints, double[] spreadsUp, double[] spreadsDown, IsdaCompliantYieldCurve yieldCurve, CdsPriceType priceType) { IsdaCompliantCreditCurve curveUp = _curveBuilder.calibrateCreditCurve(curvePoints, spreadsUp, yieldCurve); IsdaCompliantCreditCurve curveDown = _curveBuilder.calibrateCreditCurve(curvePoints, spreadsDown, yieldCurve); double up = _pricer.pv(pricingCDS, yieldCurve, curveUp, cdsSpread, priceType); double down = _pricer.pv(pricingCDS, yieldCurve, curveDown, cdsSpread, priceType); return up - down; } private double fdCentral( CdsAnalytic pricingCDS, double cdsSpread, CdsAnalytic[] curvePoints, double[] spreads, double[] deltaSpreads, IsdaCompliantYieldCurve yieldCurve, CdsPriceType priceType) { int n = curvePoints.length; double[] spreadUp = new double[n]; double[] spreadDown = new double[n]; for (int i = 0; i < n; i++) { spreadUp[i] = spreads[i] + deltaSpreads[i]; spreadDown[i] = spreads[i] - deltaSpreads[i]; } IsdaCompliantCreditCurve curveUp = _curveBuilder.calibrateCreditCurve(curvePoints, spreadUp, yieldCurve); IsdaCompliantCreditCurve curveDown = _curveBuilder.calibrateCreditCurve(curvePoints, spreadDown, yieldCurve); double up = _pricer.pv(pricingCDS, yieldCurve, curveUp, cdsSpread, priceType); double down = _pricer.pv(pricingCDS, yieldCurve, curveDown, cdsSpread, priceType); return up - down; } private double fdForward( CdsAnalytic pricingCDS, double cdsSpread, CdsAnalytic[] curvePoints, double[] spreads, double[] deltaSpreads, IsdaCompliantYieldCurve yieldCurve, CdsPriceType priceType) { int n = curvePoints.length; double[] spreadUp = new double[n]; for (int i = 0; i < n; i++) { spreadUp[i] = spreads[i] + deltaSpreads[i]; } IsdaCompliantCreditCurve curveUp = _curveBuilder.calibrateCreditCurve(curvePoints, spreadUp, yieldCurve); IsdaCompliantCreditCurve curveMid = _curveBuilder.calibrateCreditCurve(curvePoints, spreads, yieldCurve); double up = _pricer.pv(pricingCDS, yieldCurve, curveUp, cdsSpread, priceType); double mid = _pricer.pv(pricingCDS, yieldCurve, curveMid, cdsSpread, priceType); return up - mid; } private double fdBackwards( CdsAnalytic pricingCDS, double cdsSpread, CdsAnalytic[] curvePoints, double[] spreads, double[] deltaSpreads, IsdaCompliantYieldCurve yieldCurve, CdsPriceType priceType) { int n = curvePoints.length; double[] spreadDown = new double[n]; for (int i = 0; i < n; i++) { spreadDown[i] = spreads[i] - deltaSpreads[i]; } IsdaCompliantCreditCurve curveMid = _curveBuilder.calibrateCreditCurve(curvePoints, spreads, yieldCurve); IsdaCompliantCreditCurve curveDown = _curveBuilder.calibrateCreditCurve(curvePoints, spreadDown, yieldCurve); double mid = _pricer.pv(pricingCDS, yieldCurve, curveMid, cdsSpread, priceType); double down = _pricer.pv(pricingCDS, yieldCurve, curveDown, cdsSpread, priceType); return mid - down; } private double bumpedSpread(double spread, double amount, ShiftType shiftType) { return shiftType.applyShift(spread, amount); } private double[] makeBumpedSpreads(double[] spreads, double amount, ShiftType shiftType) { int n = spreads.length; double[] res = new double[n]; for (int i = 0; i < n; i++) { res[i] = shiftType.applyShift(spreads[i], amount); } return res; } /** * Bump a market quote by a specified amount, eps. * <p> * If the quote is a spread type (ParSpread or QuotedSpread) the amount is simply bumped * by given amount; however if the quote is PointsUpFront this is first converted to * a QuotedSpread, bumped by eps, then converted back to PointsUpFront. * * @param cds the analytic description of a CDS traded at a certain time * @param quote The market quote * @param yieldCurve The yield curve * @param eps Bump amount (as a fraction, so a 1bps bumps is 1e-4) * @return the bumped quote */ public CdsQuoteConvention bumpQuote(CdsAnalytic cds, CdsQuoteConvention quote, IsdaCompliantYieldCurve yieldCurve, double eps) { if (quote instanceof CdsParSpread) { return new CdsParSpread(quote.getCoupon() + eps); } else if (quote instanceof CdsQuotedSpread) { CdsQuotedSpread qSpread = (CdsQuotedSpread) quote; return new CdsQuotedSpread(qSpread.getCoupon(), qSpread.getQuotedSpread() + eps); } else if (quote instanceof PointsUpFront) { PointsUpFront puf = (PointsUpFront) quote; double bumpedQSpread = _pufConverter.pufToQuotedSpread(cds, puf.getCoupon(), yieldCurve, puf.getPointsUpFront()) + eps; return new PointsUpFront(puf.getCoupon(), _pufConverter.quotedSpreadToPUF(cds, puf.getCoupon(), yieldCurve, bumpedQSpread)); } else { throw new IllegalArgumentException("unknow type " + quote.getClass()); } } /** * Bump a set of market quotes by a specified amount, eps. * <p> * If an individual quote is a spread type (ParSpread or QuotedSpread) the amount is simply bumped * by given amount; however if the quote is PointsUpFront this is first converted to a QuotedSpread, * bumped by eps, then converted back to PointsUpFront. The set of quotes may be of mixed type. * * @param cds Set of analytic descriptions of CDSs traded at a certain times * @param quotes The market quotes * @param yieldCurve he yield curve * @param eps Bump amount (as a fraction, so a 1bps bumps is 1e-4) * @return the bumped quotes */ public CdsQuoteConvention[] bumpQuotes( CdsAnalytic[] cds, CdsQuoteConvention[] quotes, IsdaCompliantYieldCurve yieldCurve, double eps) { int n = cds.length; CdsQuoteConvention[] res = new CdsQuoteConvention[n]; for (int i = 0; i < n; i++) { res[i] = bumpQuote(cds[i], quotes[i], yieldCurve, eps); } return res; } private CdsQuoteConvention[] bumpQuoteAtIndex( CdsAnalytic[] cds, CdsQuoteConvention[] quotes, IsdaCompliantYieldCurve yieldCurve, double eps, int index) { int n = cds.length; CdsQuoteConvention[] res = new CdsQuoteConvention[n]; System.arraycopy(quotes, 0, res, 0, n); res[index] = bumpQuote(cds[index], quotes[index], yieldCurve, eps); return res; } private double[] makeBumpedSpreads(double[] spreads, double amount, ShiftType shiftType, int index) { int n = spreads.length; double[] res = new double[n]; System.arraycopy(spreads, 0, res, 0, n); res[index] = shiftType.applyShift(res[index], amount); return res; } }