/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.credit.isdastandardmodel;
import java.util.Arrays;
import com.opengamma.analytics.financial.model.BumpType;
import com.opengamma.analytics.math.differentiation.FiniteDifferenceType;
import com.opengamma.util.ArgumentChecker;
/**
* 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.<br> 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;
public FiniteDifferenceSpreadSensitivityCalculator() {
_pufConverter = new MarketQuoteConverter();
_curveBuilder = new FastCreditCurveBuilder();
_pricer = new AnalyticCDSPricer();
}
public FiniteDifferenceSpreadSensitivityCalculator(final 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 - 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 analytic description of a CDS traded at a certain time - it is this CDS that we are calculation CDV01 for
* @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(final CDSAnalytic cds, final CDSQuoteConvention quote, final ISDACompliantYieldCurve yieldCurve, final double fracBumpAmount) {
if (quote instanceof QuotedSpread) {
final QuotedSpread qSpread = (QuotedSpread) quote;
return parallelCS01FromParSpreads(cds, qSpread.getCoupon(), yieldCurve, new CDSAnalytic[] {cds }, new double[] {qSpread.getQuotedSpread() }, fracBumpAmount, BumpType.ADDITIVE);
} else if (quote instanceof PointsUpFront) {
final PointsUpFront puf = (PointsUpFront) quote;
return parallelCS01FromPUF(cds, puf.getCoupon(), yieldCurve, puf.getPointsUpFront(), fracBumpAmount);
} else if (quote instanceof ParSpread) {
return parallelCS01FromParSpreads(cds, quote.getCoupon(), yieldCurve, new CDSAnalytic[] {cds }, new double[] {quote.getCoupon() }, fracBumpAmount, BumpType.ADDITIVE);
}
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>.<br>
*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 - it is this CDS that we are calculation CDV01 for
* @param coupon the of the traded CDS (expressed as <b>fractions not basis points</b>)
* @param yieldCurve The yield (or discount) curve
* @param puf 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(final CDSAnalytic cds, final double coupon, final ISDACompliantYieldCurve yieldCurve, final double puf, final double fracBumpAmount) {
final double bumpedQSpread = _pufConverter.pufToQuotedSpread(cds, coupon, yieldCurve, puf) + fracBumpAmount;
final ISDACompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(cds, bumpedQSpread, yieldCurve);
final 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). 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 analytic description of a CDS traded at a certain time - it is this CDS that we are calculation CDV01 for
* @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 bumpType ADDITIVE or MULTIPLICATIVE
* @return The credit DV01
*/
public double parallelCS01FromSpread(final CDSAnalytic cds, final double coupon, final ISDACompliantYieldCurve yieldCurve, final double marketSpread, final double fracBumpAmount,
final BumpType bumpType) {
return parallelCS01FromParSpreads(cds, coupon, yieldCurve, new CDSAnalytic[] {cds }, new double[] {marketSpread }, fracBumpAmount, bumpType);
}
//***************************************************************************************************************
// 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. 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 analytic description of a CDS traded at a certain time - it is this CDS that we are calculation CDV01 for
* @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 bumpType ADDITIVE or MULTIPLICATIVE
* @return The credit DV01
*/
public double parallelCS01FromQuotedSpread(final CDSAnalytic cds, final double coupon, final ISDACompliantYieldCurve yieldCurve, final CDSAnalytic referenceCDS, final double quotedSpread,
final double fracBumpAmount, final BumpType bumpType) {
ArgumentChecker.notNull(cds, "cds");
ArgumentChecker.notNull(referenceCDS, "referanceCDS");
ArgumentChecker.notNull(yieldCurve, "yieldCurve");
ArgumentChecker.notNull(bumpType, "bumpType");
ArgumentChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
return parallelCS01FromParSpreads(cds, coupon, yieldCurve, new CDSAnalytic[] {referenceCDS }, new double[] {quotedSpread }, fracBumpAmount, bumpType);
}
//***************************************************************************************************************
// 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). 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.<br>
* 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 analytic description of a CDS traded at a certain time - it is this CDS that we are calculation CDV01 for
* @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(final CDSAnalytic cds, final double cdsCoupon, final ISDACompliantYieldCurve yieldCurve, final CDSAnalytic[] marketCDSs,
final CDSQuoteConvention[] quotes, final double fracBumpAmount) {
ArgumentChecker.notNull(cds, "cds");
ArgumentChecker.noNulls(marketCDSs, "curvePoints");
ArgumentChecker.notNull(yieldCurve, "yieldCurve");
ArgumentChecker.noNulls(quotes, "quotes");
ArgumentChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
final int n = marketCDSs.length;
ArgumentChecker.isTrue(n == quotes.length, "speads length does not match curvePoints");
final ISDACompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, quotes, yieldCurve);
final double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon);
final CDSQuoteConvention[] bumpedQuotes = bumpQuotes(marketCDSs, quotes, yieldCurve, fracBumpAmount);
final ISDACompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, bumpedQuotes, yieldCurve);
final 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). 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.<br>
* 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 bumpType ADDITIVE
* @param cds 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 bumpType ADDITIVE or MULTIPLICATIVE
* @return The credit DV01
*/
public double parallelCS01FromParSpreads(final CDSAnalytic cds, final double cdsFracSpread, final ISDACompliantYieldCurve yieldCurve, final CDSAnalytic[] marketCDSs, final double[] parSpreads,
final double fracBumpAmount, final BumpType bumpType) {
ArgumentChecker.notNull(cds, "cds");
ArgumentChecker.noNulls(marketCDSs, "curvePoints");
ArgumentChecker.notEmpty(parSpreads, "spreads");
ArgumentChecker.notNull(yieldCurve, "yieldCurve");
ArgumentChecker.notNull(bumpType, "bumpType");
ArgumentChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
final int n = marketCDSs.length;
ArgumentChecker.isTrue(n == parSpreads.length, "speads length does not match curvePoints");
final double[] bumpedSpreads = makeBumpedSpreads(parSpreads, fracBumpAmount, bumpType);
final double diff = fdCreditDV01(cds, cdsFracSpread, marketCDSs, bumpedSpreads, parSpreads, yieldCurve, PriceType.DIRTY);
return diff / fracBumpAmount;
}
public double parallelCS01FromCreditCurve(final CDSAnalytic cds, final double cdsCoupon, final CDSAnalytic[] pillarCDSs, final ISDACompliantYieldCurve yieldCurve,
final ISDACompliantCreditCurve creditCurve, final double fracBumpAmount) {
ArgumentChecker.notNull(cds, "cds");
ArgumentChecker.noNulls(pillarCDSs, "pillarCDSs");
ArgumentChecker.notNull(creditCurve, "creditCurve");
ArgumentChecker.notNull(yieldCurve, "yieldCurve");
ArgumentChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
final int n = pillarCDSs.length;
final double[] impSpreads = new double[n];
final 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) {
ArgumentChecker.isTrue(t[i] > t[i - 1], "pillars must be assending");
}
}
//build a new curve from the implied spreads
final ISDACompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(pillarCDSs, impSpreads, yieldCurve);
final double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon);
final double[] bumpedSpreads = makeBumpedSpreads(impSpreads, fracBumpAmount, BumpType.ADDITIVE);
final ISDACompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(pillarCDSs, bumpedSpreads, yieldCurve);
final double price = _pricer.pv(cds, yieldCurve, bumpedCurve, cdsCoupon);
final 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. 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.<br>
* 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 analytic description of a CDS traded at a certain time - it is this CDS that we are calculation CDV01 for
* @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(final CDSAnalytic cds, final double cdsCoupon, final ISDACompliantYieldCurve yieldCurve, final CDSAnalytic[] marketCDSs,
final CDSQuoteConvention[] quotes, final double fracBumpAmount) {
ArgumentChecker.notNull(cds, "cds");
ArgumentChecker.noNulls(marketCDSs, "curvePoints");
ArgumentChecker.noNulls(quotes, "quotes");
ArgumentChecker.notNull(yieldCurve, "yieldCurve");
ArgumentChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
final int n = marketCDSs.length;
ArgumentChecker.isTrue(n == quotes.length, "speads length does not match curvePoints");
final ISDACompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, quotes, yieldCurve);
final double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon);
final double[] res = new double[n];
for (int i = 0; i < n; i++) {
final CDSQuoteConvention[] bumpedQuotes = bumpQuoteAtIndex(marketCDSs, quotes, yieldCurve, fracBumpAmount, i);
final ISDACompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, bumpedQuotes, yieldCurve);
final 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. 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.<br>
* 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<br>
* @param cds 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 bumpType ADDITIVE or MULTIPLICATIVE
* @return The credit CS01
*/
public double[] bucketedCS01FromParSpreads(final CDSAnalytic cds, final double cdsCoupon, final ISDACompliantYieldCurve yieldCurve, final CDSAnalytic[] marketCDSs, final double[] marketParSpreads,
final double fracBumpAmount, final BumpType bumpType) {
ArgumentChecker.notNull(cds, "cds");
ArgumentChecker.noNulls(marketCDSs, "curvePoints");
ArgumentChecker.notEmpty(marketParSpreads, "spreads");
ArgumentChecker.notNull(yieldCurve, "yieldCurve");
ArgumentChecker.notNull(bumpType, "bumpType");
ArgumentChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
final int n = marketCDSs.length;
ArgumentChecker.isTrue(n == marketParSpreads.length, "speads length does not match curvePoints");
final PriceType priceType = PriceType.DIRTY;
final ISDACompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, marketParSpreads, yieldCurve);
final double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon, priceType);
final double[] res = new double[n];
for (int i = 0; i < n; i++) {
final double[] temp = makeBumpedSpreads(marketParSpreads, fracBumpAmount, bumpType, i);
final ISDACompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, temp, yieldCurve);
final 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. 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.<br>
* 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 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 bumpType ADDITIVE or MULTIPLICATIVE
* @return The bucked CS01 for a single CDS
*/
public double[] bucketedCS01FromQuotedSpreads(final CDSAnalytic cds, final double dealSpread, final ISDACompliantYieldCurve yieldCurve, final CDSAnalytic[] marketCDSs, final double[] quotedSpreads,
final double fracBumpAmount, final BumpType bumpType) {
ArgumentChecker.notNull(cds, "cds");
ArgumentChecker.noNulls(marketCDSs, "curvePoints");
ArgumentChecker.notEmpty(quotedSpreads, "spreads");
ArgumentChecker.notNull(yieldCurve, "yieldCurve");
ArgumentChecker.notNull(bumpType, "bumpType");
ArgumentChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
final int n = marketCDSs.length;
ArgumentChecker.isTrue(n == quotedSpreads.length, "speads length does not match curvePoints");
final PriceType priceType = PriceType.DIRTY;
final double[] premiums = new double[n];
Arrays.fill(premiums, dealSpread); // assume the premiums of all CDS are equal
final double[] puf = _pufConverter.quotedSpreadsToPUF(marketCDSs, premiums, yieldCurve, quotedSpreads);
final ISDACompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, premiums, yieldCurve, puf);
final double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, dealSpread, priceType);
final double[] bumpedPUF = new double[n];
final double[] res = new double[n];
for (int i = 0; i < n; i++) {
System.arraycopy(puf, 0, bumpedPUF, 0, n);
final double bumpedSpread = bumpedSpread(quotedSpreads[i], fracBumpAmount, bumpType);
bumpedPUF[i] = _pufConverter.quotedSpreadToPUF(marketCDSs[i], premiums[i], yieldCurve, bumpedSpread);
// TODO a lot of unnecessary recalibration here
final ISDACompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, premiums, yieldCurve, bumpedPUF);
final 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. 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.<br>
* 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 bumpType ADDITIVE or MULTIPLICATIVE
* @return The bucked CS01 for a set of CDSs
*/
public double[][] bucketedCS01FromQuotedSpreads(final CDSAnalytic[] cds, final double dealSpread, final ISDACompliantYieldCurve yieldCurve, final CDSAnalytic[] marketCDSs,
final double[] quotedSpreads, final double fracBumpAmount, final BumpType bumpType) {
ArgumentChecker.noNulls(cds, "cds");
ArgumentChecker.noNulls(marketCDSs, "curvePoints");
ArgumentChecker.notEmpty(quotedSpreads, "spreads");
ArgumentChecker.notNull(yieldCurve, "yieldCurve");
ArgumentChecker.notNull(bumpType, "bumpType");
ArgumentChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
final int nMarketCDSs = marketCDSs.length;
ArgumentChecker.isTrue(nMarketCDSs == quotedSpreads.length, "speads length does not match curvePoints");
final PriceType priceType = PriceType.DIRTY;
final double[] premiums = new double[nMarketCDSs];
Arrays.fill(premiums, dealSpread); // assume the premiums of all CDS are equal
final int nTradeCDSs = cds.length;
final double[] puf = _pufConverter.quotedSpreadsToPUF(marketCDSs, premiums, yieldCurve, quotedSpreads);
//TODO not needed
final ISDACompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, premiums, yieldCurve, puf);
final double[] basePrices = new double[nTradeCDSs];
for (int j = 0; j < nTradeCDSs; j++) {
basePrices[j] = _pricer.pv(cds[j], yieldCurve, baseCurve, dealSpread, priceType);
}
final double[] bumpedPUF = new double[nMarketCDSs];
final 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);
final double bumpedSpread = bumpedSpread(quotedSpreads[i], fracBumpAmount, bumpType);
bumpedPUF[i] = _pufConverter.quotedSpreadToPUF(marketCDSs[i], premiums[i], yieldCurve, bumpedSpread);
// TODO a lot of unnecessary recalibration here
final ISDACompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(marketCDSs, premiums, yieldCurve, bumpedPUF);
for (int j = 0; j < nTradeCDSs; j++) {
final 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. 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 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(final CDSAnalytic cds, final double cdsCoupon, final CDSAnalytic[] bucketCDSs, final ISDACompliantYieldCurve yieldCurve, final CDSAnalytic[] pillarCDSs,
final double[] pillarSpreads, final double fracBumpAmount) {
ArgumentChecker.noNulls(pillarCDSs, "pillarCDSs");
ArgumentChecker.notEmpty(pillarSpreads, "pillarSpreads");
final ISDACompliantCreditCurve creditCurve = _curveBuilder.calibrateCreditCurve(pillarCDSs, pillarSpreads, yieldCurve);
return bucketedCS01FromCreditCurve(cds, cdsCoupon, bucketCDSs, yieldCurve, creditCurve, fracBumpAmount);
}
public double[] bucketedCS01FromPUF(final CDSAnalytic cds, final PointsUpFront puf, final ISDACompliantYieldCurve yieldCurve, final CDSAnalytic[] bucketCDSs, final double fracBumpAmount) {
final 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. 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 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(final CDSAnalytic cds, final double cdsCoupon, final CDSAnalytic[] bucketCDSs, final ISDACompliantYieldCurve yieldCurve,
final ISDACompliantCreditCurve creditCurve, final double fracBumpAmount) {
ArgumentChecker.notNull(cds, "cds");
ArgumentChecker.noNulls(bucketCDSs, "bucketCDSs");
ArgumentChecker.notNull(creditCurve, "creditCurve");
ArgumentChecker.notNull(yieldCurve, "yieldCurve");
ArgumentChecker.isTrue(Math.abs(fracBumpAmount) > 1e-10, "bump amount too small");
final int n = bucketCDSs.length;
final double[] impSpreads = new double[n];
final 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) {
ArgumentChecker.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);
//build a new curve from the implied spreads
final ISDACompliantCreditCurve baseCurve = _curveBuilder.calibrateCreditCurve(bucketCDSs, impSpreads, yieldCurve);
final double basePrice = _pricer.pv(cds, yieldCurve, baseCurve, cdsCoupon);
final double[] res = new double[n];
for (int i = 0; i <= index; i++) { //don't bother calculating where there is no sensitivity
final double[] bumpedSpreads = makeBumpedSpreads(impSpreads, fracBumpAmount, BumpType.ADDITIVE, i);
final ISDACompliantCreditCurve bumpedCurve = _curveBuilder.calibrateCreditCurve(bucketCDSs, bumpedSpreads, yieldCurve);
final 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 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(final CDSAnalytic cds, final double cdsFracSpread, final PriceType priceType, final ISDACompliantYieldCurve yieldCurve,
final CDSAnalytic[] marketCDSs, final double[] marketFracSpreads, final double[] fracDeltaSpreads, final FiniteDifferenceType fdType) {
ArgumentChecker.notNull(cds, "cds");
ArgumentChecker.noNulls(marketCDSs, "curvePoints");
ArgumentChecker.notEmpty(marketFracSpreads, "spreads");
ArgumentChecker.notEmpty(fracDeltaSpreads, "deltaSpreads");
ArgumentChecker.notNull(yieldCurve, "yieldCurve");
ArgumentChecker.notNull(priceType, "priceType");
final int n = marketCDSs.length;
ArgumentChecker.isTrue(n == marketFracSpreads.length, "speads length does not match curvePoints");
ArgumentChecker.isTrue(n == fracDeltaSpreads.length, "deltaSpreads length does not match curvePoints");
for (int i = 0; i < n; i++) {
ArgumentChecker.isTrue(marketFracSpreads[i] > 0, "spreads must be positive");
ArgumentChecker.isTrue(fracDeltaSpreads[i] >= 0, "deltaSpreads must none negative");
ArgumentChecker.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(final CDSAnalytic pricingCDS, final double cdsSpread, final CDSAnalytic[] curvePoints, final double[] spreadsUp, final double[] spreadsDown,
final ISDACompliantYieldCurve yieldCurve, final PriceType priceType) {
final ISDACompliantCreditCurve curveUp = _curveBuilder.calibrateCreditCurve(curvePoints, spreadsUp, yieldCurve);
final ISDACompliantCreditCurve curveDown = _curveBuilder.calibrateCreditCurve(curvePoints, spreadsDown, yieldCurve);
final double up = _pricer.pv(pricingCDS, yieldCurve, curveUp, cdsSpread, priceType);
final double down = _pricer.pv(pricingCDS, yieldCurve, curveDown, cdsSpread, priceType);
return up - down;
}
private double fdCentral(final CDSAnalytic pricingCDS, final double cdsSpread, final CDSAnalytic[] curvePoints, final double[] spreads, final double[] deltaSpreads,
final ISDACompliantYieldCurve yieldCurve, final PriceType priceType) {
final int n = curvePoints.length;
final double[] spreadUp = new double[n];
final double[] spreadDown = new double[n];
for (int i = 0; i < n; i++) {
spreadUp[i] = spreads[i] + deltaSpreads[i];
spreadDown[i] = spreads[i] - deltaSpreads[i];
}
final ISDACompliantCreditCurve curveUp = _curveBuilder.calibrateCreditCurve(curvePoints, spreadUp, yieldCurve);
final ISDACompliantCreditCurve curveDown = _curveBuilder.calibrateCreditCurve(curvePoints, spreadDown, yieldCurve);
final double up = _pricer.pv(pricingCDS, yieldCurve, curveUp, cdsSpread, priceType);
final double down = _pricer.pv(pricingCDS, yieldCurve, curveDown, cdsSpread, priceType);
return up - down;
}
private double fdForward(final CDSAnalytic pricingCDS, final double cdsSpread, final CDSAnalytic[] curvePoints, final double[] spreads, final double[] deltaSpreads,
final ISDACompliantYieldCurve yieldCurve, final PriceType priceType) {
final int n = curvePoints.length;
final double[] spreadUp = new double[n];
for (int i = 0; i < n; i++) {
spreadUp[i] = spreads[i] + deltaSpreads[i];
}
final ISDACompliantCreditCurve curveUp = _curveBuilder.calibrateCreditCurve(curvePoints, spreadUp, yieldCurve);
final ISDACompliantCreditCurve curveMid = _curveBuilder.calibrateCreditCurve(curvePoints, spreads, yieldCurve);
final double up = _pricer.pv(pricingCDS, yieldCurve, curveUp, cdsSpread, priceType);
final double mid = _pricer.pv(pricingCDS, yieldCurve, curveMid, cdsSpread, priceType);
return up - mid;
}
private double fdBackwards(final CDSAnalytic pricingCDS, final double cdsSpread, final CDSAnalytic[] curvePoints, final double[] spreads, final double[] deltaSpreads,
final ISDACompliantYieldCurve yieldCurve, final PriceType priceType) {
final int n = curvePoints.length;
final double[] spreadDown = new double[n];
for (int i = 0; i < n; i++) {
spreadDown[i] = spreads[i] - deltaSpreads[i];
}
final ISDACompliantCreditCurve curveMid = _curveBuilder.calibrateCreditCurve(curvePoints, spreads, yieldCurve);
final ISDACompliantCreditCurve curveDown = _curveBuilder.calibrateCreditCurve(curvePoints, spreadDown, yieldCurve);
final double mid = _pricer.pv(pricingCDS, yieldCurve, curveMid, cdsSpread, priceType);
final double down = _pricer.pv(pricingCDS, yieldCurve, curveDown, cdsSpread, priceType);
return mid - down;
}
private double bumpedSpread(final double spread, final double amount, final BumpType bumpType) {
if (bumpType == BumpType.ADDITIVE) {
return spread + amount;
} else if (bumpType == BumpType.MULTIPLICATIVE) {
return spread * (1 + amount);
} else {
throw new IllegalArgumentException("BumpType " + bumpType + " is not supported");
}
}
private double[] makeBumpedSpreads(final double[] spreads, final double amount, final BumpType bumpType) {
final int n = spreads.length;
final double[] res = new double[n];
if (bumpType == BumpType.ADDITIVE) {
for (int i = 0; i < n; i++) {
res[i] = spreads[i] + amount;
}
} else if (bumpType == BumpType.MULTIPLICATIVE) {
final double a = 1 + amount;
for (int i = 0; i < n; i++) {
res[i] = spreads[i] * a;
}
} else {
throw new IllegalArgumentException("BumpType " + bumpType + " is not supported");
}
return res;
}
/**
* Bump a market quote by a specified amount, eps. 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 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(final CDSAnalytic cds, final CDSQuoteConvention quote, final ISDACompliantYieldCurve yieldCurve, final double eps) {
if (quote instanceof ParSpread) {
return new ParSpread(quote.getCoupon() + eps);
} else if (quote instanceof QuotedSpread) {
final QuotedSpread qSpread = (QuotedSpread) quote;
return new QuotedSpread(qSpread.getCoupon(), qSpread.getQuotedSpread() + eps);
} else if (quote instanceof PointsUpFront) {
final PointsUpFront puf = (PointsUpFront) quote;
final 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. 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(final CDSAnalytic[] cds, final CDSQuoteConvention[] quotes, final ISDACompliantYieldCurve yieldCurve, final double eps) {
final int n = cds.length;
final 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(final CDSAnalytic[] cds, final CDSQuoteConvention[] quotes, final ISDACompliantYieldCurve yieldCurve, final double eps, final int index) {
final int n = cds.length;
final 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(final double[] spreads, final double amount, final BumpType bumpType, final int index) {
final int n = spreads.length;
final double[] res = new double[n];
System.arraycopy(spreads, 0, res, 0, n);
switch (bumpType) {
case ADDITIVE:
res[index] += amount;
break;
case MULTIPLICATIVE:
res[index] += res[index] * amount;
break;
default:
throw new IllegalArgumentException("BumpType " + bumpType + " is not supported");
}
return res;
}
}