/**
* 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 static com.opengamma.analytics.financial.credit.isdastandardmodel.DoublesScheduleGenerator.getIntegrationsPoints;
import static com.opengamma.analytics.financial.credit.isdastandardmodel.DoublesScheduleGenerator.truncateSetInclusive;
import static com.opengamma.analytics.math.utilities.Epsilon.epsilon;
import static com.opengamma.analytics.math.utilities.Epsilon.epsilonP;
import java.util.Arrays;
import com.opengamma.util.ArgumentChecker;
/**
*
*/
public class MultiAnalyticCDSPricer {
private static final double HALFDAY = 1 / 730.;
/** Default value for determining if results consistent with ISDA model versions 1.8.2 or lower are to be calculated */
private static final AccrualOnDefaultFormulae DEFAULT_FORMULA = AccrualOnDefaultFormulae.OrignalISDA;
/** True if results consistent with ISDA model versions 1.8.2 or lower are to be calculated */
private final AccrualOnDefaultFormulae _formula;
private final double _omega;
/**
* For consistency with the ISDA model version 1.8.2 and lower, a bug in the accrual on default calculation
* has been reproduced.
*/
public MultiAnalyticCDSPricer() {
_formula = DEFAULT_FORMULA;
_omega = HALFDAY;
}
/**
* For consistency with the ISDA model version 1.8.2 and lower, a bug in the accrual on default calculation
* has been reproduced.
* @param formula which accrual on default formulae to use.
*/
public MultiAnalyticCDSPricer(final AccrualOnDefaultFormulae formula) {
ArgumentChecker.notNull(formula, "formula");
_formula = formula;
if (formula == AccrualOnDefaultFormulae.OrignalISDA) {
_omega = HALFDAY;
} else {
_omega = 0.0;
}
}
/**
* Present value for the payer of premiums (i.e. the buyer of protection)
* @param cds analytic description of a CDS traded at a certain time
* @param yieldCurve The yield (or discount) curve
* @param creditCurve the credit (or survival) curve
* @param premium The common CDS premium (as a fraction)
* @param cleanOrDirty Clean or dirty price
* @return The PV on unit notional
*/
public double[] pv(final MultiCDSAnalytic cds, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve, final double premium, final PriceType cleanOrDirty) {
final int n = cds.getNumMaturities();
final double[] premiums = new double[n];
Arrays.fill(premiums, premium);
return pv(cds, yieldCurve, creditCurve, premiums, cleanOrDirty);
}
/**
* Present value for the payer of premiums (i.e. the buyer of protection)
* @param cds analytic description of a CDS traded at a certain time
* @param yieldCurve The yield (or discount) curve
* @param creditCurve the credit (or survival) curve
* @param premiums The CDS premiums (as fractions)
* @param cleanOrDirty Clean or dirty price
* @return The PV on unit notional
*/
public double[] pv(final MultiCDSAnalytic cds, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve, final double[] premiums, final PriceType cleanOrDirty) {
final int n = cds.getNumMaturities();
ArgumentChecker.notEmpty(premiums, "premiums");
ArgumentChecker.isTrue(n == premiums.length, "premiums wrong length. Should be {}, but is {}", n, premiums.length);
final double[] pv = new double[n];
if (cds.getProtectionEnd(cds.getNumMaturities() - 1) <= 0.0) { //all CDSs have expired
return pv;
}
// TODO check for any repeat calculations
final double[] rpv01 = pvPremiumLegPerUnitSpread(cds, yieldCurve, creditCurve, cleanOrDirty);
final double[] proLeg = protectionLeg(cds, yieldCurve, creditCurve);
for (int i = 0; i < n; i++) {
pv[i] = proLeg[i] - premiums[i] * rpv01[i];
}
return pv;
}
/**
* Present value (clean price) for the payer of premiums (i.e. the buyer of protection)
* @param cds analytic description of a CDS traded at a certain time
* @param yieldCurve The yield (or discount) curve
* @param creditCurve the credit (or survival) curve
* @param premiums The CDS premiums (as fractions)
* @return The PV
*/
public double[] pv(final MultiCDSAnalytic cds, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve, final double[] premiums) {
return pv(cds, yieldCurve, creditCurve, premiums, PriceType.CLEAN);
}
/**
* Present value (clean price) for the payer of premiums (i.e. the buyer of protection)
* @param cds analytic description of a CDS traded at a certain time
* @param yieldCurve The yield (or discount) curve
* @param creditCurve the credit (or survival) curve
* @param premium The common CDS premium (as a fraction)
* @return The PV
*/
public double[] pv(final MultiCDSAnalytic cds, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve, final double premium) {
return pv(cds, yieldCurve, creditCurve, premium, PriceType.CLEAN);
}
/**
* Sensitivity of the present value (for the payer of premiums, i.e. the buyer of protection) to the zero hazard rate
* of a given node (knot) of the credit curve. This is per unit of notional
* @param cds analytic description of a CDS traded at a certain time
* @param yieldCurve The yield (or discount) curve
* @param creditCurve the credit (or survival) curve
* @param fractionalSpread The <b>fraction</b> spread
* @param creditCurveNode The credit curve node
* @return PV sensitivity to one node (knot) on the credit (hazard rate/survival) curve
*/
// public double[] pvCreditSensitivity(final MultiCDSAnalytic cds, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve, final int creditCurveNode) {
// if (cds.getProtectionEnd(cds.getNumMaturities() - 1) <= 0.0) { //all CDSs have expired
// return 0.0;
// }
// final double rpv01Sense = pvPremiumLegCreditSensitivity(cds, yieldCurve, creditCurve, creditCurveNode);
// final double proLegSense = protectionLegCreditSensitivity(cds, yieldCurve, creditCurve, creditCurveNode);
// return proLegSense - fractionalSpread * rpv01Sense;
// }
/**
* The par spread par spread for a given yield and credit (hazard rate/survival) curve)
* @param cds analytic description of a CDS traded at a certain time
* @param yieldCurve The yield (or discount) curve
* @param creditCurve the credit (or survival) curve
* @return the par spread
*/
public double[] parSpread(final MultiCDSAnalytic cds, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve) {
if (cds.getProtectionEnd(0) <= 0.0) { //short cut already expired CDSs
throw new IllegalArgumentException("A CDSs has expired - cannot compute a par spread for it");
}
final double[] rpv01 = pvPremiumLegPerUnitSpread(cds, yieldCurve, creditCurve, PriceType.CLEAN);
final double[] proLeg = protectionLeg(cds, yieldCurve, creditCurve);
final int n = cds.getNumMaturities();
final double[] s = new double[n];
for (int i = 0; i < n; i++) {
s[i] = proLeg[i] / rpv01[i];
}
return s;
}
/**
* Sensitivity of the par spread (the fixed payment on the premium leg that make the PV of the CDS zero for a given yield
* and credit (hazard rate/survival) curve) to the zero hazard rate of a given node (knot) of the credit curve.
* @param cds analytic description of a CDS traded at a certain time
* @param yieldCurve The yield (or discount) curve
* @param creditCurve the credit (or survival) curve
* @param creditCurveNode The credit curve node
* @return Par spread sensitivity to one node (knot) on the credit (hazard rate/survival) curve
*/
// public double parSpreadCreditSensitivity(final MultiCDSAnalytic cds, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve, final int creditCurveNode) {
// if (cds.getProtectionEnd(0) <= 0.0) { //short cut already expired CDSs
// throw new IllegalArgumentException("A CDSs has expired - cannot compute a par spread for it");
// }
//
// final double a = protectionLeg(cds, yieldCurve, creditCurve);
// final double b = pvPremiumLegPerUnitSpread(cds, yieldCurve, creditCurve, PriceType.CLEAN);
// final double spread = a / b;
// final double dadh = protectionLegCreditSensitivity(cds, yieldCurve, creditCurve, creditCurveNode);
// final double dbdh = pvPremiumLegCreditSensitivity(cds, yieldCurve, creditCurve, creditCurveNode);
// return spread * (dadh / a - dbdh / b);
// }
/**
* This is the present value of the premium leg per unit of fractional spread - hence it is equal to 10,000 times the RPV01
* (Risky PV01). The actual PV of the leg is this multiplied by the notional and the fractional spread (i.e. spread in basis
* points divided by 10,000)
* @param cds analytic description of a CDS traded at a certain time
* @param yieldCurve The yield (or discount) curve
* @param creditCurve the credit (or survival) curve
* @param cleanOrDirty Clean or dirty price
* @return 10,000 times the RPV01 (on a notional of 1)
*/
public double[] pvPremiumLegPerUnitSpread(final MultiCDSAnalytic cds, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve, final PriceType cleanOrDirty) {
ArgumentChecker.notNull(cds, "null cds");
ArgumentChecker.notNull(yieldCurve, "null yieldCurve");
ArgumentChecker.notNull(creditCurve, "null creditCurve");
double[] integrationSchedule = null;
final int nMat = cds.getNumMaturities();
if (cds.isPayAccOnDefault()) {
integrationSchedule = getIntegrationsPoints(cds.getEffectiveProtectionStart(), cds.getProtectionEnd(nMat - 1), yieldCurve, creditCurve);
}
final double df = yieldCurve.getDiscountFactor(cds.getCashSettleTime());
final double[] pv = new double[nMat];
int start = 0;
double runningPV = 0;
for (int matIndex = 0; matIndex < nMat; matIndex++) {
if (cds.getProtectionEnd(matIndex) <= 0.0) { //skip expired CDSs (they have zero pv)
continue;
}
final int end = cds.getPaymentIndexForMaturity(matIndex);
for (int i = start; i < end; i++) {
final CDSCoupon coupon = cds.getStandardCoupon(i);
final double q = creditCurve.getDiscountFactor(coupon.getEffEnd());
final double p = yieldCurve.getDiscountFactor(coupon.getPaymentTime());
runningPV += coupon.getYearFrac() * p * q;
}
if (cds.isPayAccOnDefault()) {
double accPV = 0;
for (int i = start; i < end; i++) {
final CDSCoupon coupon = cds.getStandardCoupon(i);
accPV += calculateSinglePeriodAccrualOnDefault(coupon, cds.getEffectiveProtectionStart(), integrationSchedule, yieldCurve, creditCurve);
}
runningPV += accPV;
}
double pvMat = runningPV;
final CDSCoupon terminalCoupon = cds.getTerminalCoupon(matIndex);
final double q = creditCurve.getDiscountFactor(terminalCoupon.getEffEnd());
final double p = yieldCurve.getDiscountFactor(terminalCoupon.getPaymentTime());
pvMat += terminalCoupon.getYearFrac() * p * q;
if (cds.isPayAccOnDefault()) {
pvMat += calculateSinglePeriodAccrualOnDefault(terminalCoupon, cds.getEffectiveProtectionStart(), integrationSchedule, yieldCurve, creditCurve);
}
pv[matIndex] = pvMat / df;
if (cleanOrDirty == PriceType.CLEAN) {
pv[matIndex] -= cds.getAccruedPremiumPerUnitSpread(matIndex);
}
start = Math.max(0, end);
}
return pv;
}
/**
* The sensitivity (on a unit notional) of the (scaled) RPV01 to the zero hazard rate of a given node (knot) of the credit curve.
* @param cds analytic description of a CDS traded at a certain time
* @param yieldCurve The yield (or discount) curve
* @param creditCurve the credit (or survival) curve
* @param creditCurveNode The credit curve node
* @return sensitivity (on a unit notional)
*/
// public double pvPremiumLegCreditSensitivity(final MultiCDSAnalytic cds, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve, final int creditCurveNode) {
// ArgumentChecker.notNull(cds, "null cds");
// ArgumentChecker.notNull(yieldCurve, "null yieldCurve");
// ArgumentChecker.notNull(creditCurve, "null creditCurve");
//
// final int n = cds.getNumPayments();
// double pvSense = 0.0;
// for (int i = 0; i < n; i++) {
// final double dqdh = creditCurve.getSingleNodeDiscountFactorSensitivity(cds.getCreditObservationTime(i), creditCurveNode);
// final double p = yieldCurve.getDiscountFactor(cds.getPaymentTime(i));
// pvSense += cds.getAccrualFraction(i) * p * dqdh;
// }
//
// if (cds.isPayAccOnDefault()) {
// final double offset = cds.isProtectionFromStartOfDay() ? -cds.getCurveOneDay() : 0.0;
// final double[] integrationSchedule = getIntegrationsPoints(cds.getAccStart(0), cds.getAccEnd(n - 1), yieldCurve, creditCurve);
// final double offsetStepin = cds.getStepin() + offset;
//
// double accPVSense = 0.0;
// for (int i = 0; i < n; i++) {
// final double offsetAccStart = cds.getAccStart(i) + offset;
// final double offsetAccEnd = cds.getAccEnd(i) + offset;
// final double accRate = cds.getAccrualFraction(i) / (offsetAccEnd - offsetAccStart);
// accPVSense += calculateSinglePeriodAccrualOnDefaultSensitivity(accRate, offsetStepin, offsetAccStart, offsetAccEnd, integrationSchedule, yieldCurve, creditCurve, creditCurveNode);
// }
// pvSense += accPVSense;
// }
//
// final double df = yieldCurve.getDiscountFactor(cds.getValuationTime());
// pvSense /= df;
// return pvSense;
// }
//TODO this is identical to the function in AnalyticCDSPricer
private double calculateSinglePeriodAccrualOnDefault(final CDSCoupon coupon, final double stepin, final double[] integrationPoints, final ISDACompliantYieldCurve yieldCurve,
final ISDACompliantCreditCurve creditCurve) {
final double start = Math.max(coupon.getEffStart(), stepin);
if (start >= coupon.getEffEnd()) {
return 0.0; //this coupon has already expired
}
final double[] knots = truncateSetInclusive(start, coupon.getEffEnd(), integrationPoints);
double t = knots[0];
double ht0 = creditCurve.getRT(t);
double rt0 = yieldCurve.getRT(t);
double b0 = Math.exp(-rt0 - ht0); // this is the risky discount factor
double t0 = t - coupon.getEffStart() + _omega;
double pv = 0.0;
final int nItems = knots.length;
for (int j = 1; j < nItems; ++j) {
t = knots[j];
final double ht1 = creditCurve.getRT(t);
final double rt1 = yieldCurve.getRT(t);
final double b1 = Math.exp(-rt1 - ht1);
final double dt = knots[j] - knots[j - 1];
final double dht = ht1 - ht0;
final double drt = rt1 - rt0;
final double dhrt = dht + drt + 1e-50; // to keep consistent with ISDA c code
double tPV;
if (_formula == AccrualOnDefaultFormulae.MarkitFix) {
if (Math.abs(dhrt) < 1e-5) {
tPV = dht * dt * b0 * epsilonP(-dhrt);
} else {
tPV = dht * dt / dhrt * ((b0 - b1) / dhrt - b1);
}
} else {
final double t1 = t - coupon.getEffStart() + _omega;
if (Math.abs(dhrt) < 1e-5) {
tPV = dht * b0 * (t0 * epsilon(-dhrt) + dt * epsilonP(-dhrt));
} else {
tPV = dht / dhrt * (t0 * b0 - t1 * b1 + dt / dhrt * (b0 - b1));
}
t0 = t1;
}
pv += tPV;
ht0 = ht1;
rt0 = rt1;
b0 = b1;
}
return coupon.getYFRatio() * pv;
}
// private double calculateSinglePeriodAccrualOnDefaultSensitivity(final double accRate, final double stepin, final double accStart, final double accEnd, final double[] integrationPoints,
// final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve, final int creditCurveNode) {
//
// final double start = Math.max(accStart, stepin);
// if (start >= accEnd) {
// return 0.0;
// }
// final double[] knots = truncateSetInclusive(start, accEnd, integrationPoints);
//
// double t = knots[0];
// double ht0 = creditCurve.getRT(t);
// double rt0 = yieldCurve.getRT(t);
// double p0 = Math.exp(-rt0);
// double q0 = Math.exp(-ht0);
// double b0 = p0 * q0; // this is the risky discount factor
// double dqdr0 = creditCurve.getSingleNodeDiscountFactorSensitivity(t, creditCurveNode);
//
// double t0 = t - accStart + _omega;
// double pvSense = 0.0;
// final int nItems = knots.length;
// for (int j = 1; j < nItems; ++j) {
// t = knots[j];
// final double ht1 = creditCurve.getRT(t);
// final double rt1 = yieldCurve.getRT(t);
// final double p1 = Math.exp(-rt1);
// final double q1 = Math.exp(-ht1);
// final double b1 = p1 * q1;
// final double dqdr1 = creditCurve.getSingleNodeDiscountFactorSensitivity(t, creditCurveNode);
//
// final double dt = knots[j] - knots[j - 1];
//
// final double dht = ht1 - ht0;
// final double drt = rt1 - rt0;
// final double dhrt = dht + drt + 1e-50; // to keep consistent with ISDA c code
//
// double tPvSense;
// // TODO once the maths is written up in a white paper, check these formula again, since tests again finite difference
// // could miss some subtle error
//
// if (_formula == AccrualOnDefaultFormulae.MarkitFix) {
// if (Math.abs(dhrt) < 1e-5) {
// final double eP = epsilonP(-dhrt);
// final double ePP = epsilonPP(-dhrt);
// final double dPVdq0 = p0 * dt * ((1 + dht) * eP - dht * ePP);
// final double dPVdq1 = b0 * dt / q1 * (-eP + dht * ePP);
// tPvSense = dPVdq0 * dqdr0 + dPVdq1 * dqdr1;
// } else {
// final double w5 = (b0 - b1) / dhrt;
// final double w1 = w5 - b1;
// final double w2 = dht / dhrt;
// final double w3 = dt / dhrt;
// final double w4 = (1 - w2) * w1;
// final double dPVdq0 = w3 / q0 * (w4 + w2 * (b0 - w5));
// final double dPVdq1 = w3 / q1 * (w4 + w2 * (b1 * (1 + dhrt) - w5));
// tPvSense = dPVdq0 * dqdr0 - dPVdq1 * dqdr1;
// }
// } else {
// final double t1 = t - accStart + _omega;
// if (Math.abs(dhrt) < 1e-5) {
// final double e = epsilon(-dhrt);
// final double eP = epsilonP(-dhrt);
// final double ePP = epsilonPP(-dhrt);
// final double w1 = t0 * e + dt * eP;
// final double w2 = t0 * eP + dt * ePP;
// final double dPVdq0 = p0 * ((1 + dhrt) * w1 - dht * w2);
// final double dPVdq1 = b0 / q1 * (-w1 + dht * w2);
// tPvSense = dPVdq0 * dqdr0 + dPVdq1 * dqdr1;
//
// } else {
// final double w1 = dt / dhrt;
// final double w2 = dht / dhrt;
// final double w3 = (t0 + w1) * b0 - (t1 + w1) * b1;
// final double w4 = (1 - w2) / dhrt;
// final double w5 = w1 / dhrt * (b0 - b1);
// final double dPVdq0 = w4 * w3 / q0 + w2 * ((t0 + w1) * p0 - w5 / q0);
// final double dPVdq1 = w4 * w3 / q1 + w2 * ((t1 + w1) * p1 - w5 / q1);
// tPvSense = dPVdq0 * dqdr0 - dPVdq1 * dqdr1;
// }
// t0 = t1;
// }
//
// pvSense += tPvSense;
// ht0 = ht1;
// rt0 = rt1;
// p0 = p1;
// q0 = q1;
// b0 = b1;
// dqdr0 = dqdr1;
// }
// return accRate * pvSense;
// }
/**
* Compute the present value of the protection leg with a notional of 1, which is given by the integral
* $\frac{1-R}{P(T_{v})} \int_{T_a} ^{T_b} P(t) \frac{dQ(t)}{dt} dt$ where $P(t)$ and $Q(t)$ are the discount and survival curves
* respectively, $T_a$ and $T_b$ are the start and end of the protection respectively, $T_v$ is the valuation time (all measured
* from $t = 0$, 'today') and $R$ is the recovery rate.
* @param cds analytic description of a CDS traded at a certain time
* @param yieldCurve The yield (or discount) curve
* @param creditCurve the credit (or survival) curve
* @return The value of the protection leg (on a unit notional)
*/
public double[] protectionLeg(final MultiCDSAnalytic cds, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve) {
ArgumentChecker.notNull(cds, "null cds");
ArgumentChecker.notNull(yieldCurve, "null yieldCurve");
ArgumentChecker.notNull(creditCurve, "null creditCurve");
// Compute the discount factor discounting the upfront payment made on the cash settlement date back to the valuation date
final double df = yieldCurve.getDiscountFactor(cds.getCashSettleTime());
final double factor = cds.getLGD() / df;
final int nMat = cds.getNumMaturities();
double start = cds.getEffectiveProtectionStart();
final double[] fullIntegrationSchedule = getIntegrationsPoints(start, cds.getProtectionEnd(nMat - 1), yieldCurve, creditCurve);
final double[] pv = new double[nMat];
double runningPV = 0;
for (int matIndex = 0; matIndex < nMat; matIndex++) {
final double end = cds.getProtectionEnd(matIndex);
if (end <= 0.0) {
continue; //short cut already expired CDSs
}
final double[] integrationSchedule = truncateSetInclusive(start, end, fullIntegrationSchedule);
runningPV += protectionLegInterval(integrationSchedule, yieldCurve, creditCurve);
pv[matIndex] = runningPV * factor;
start = end;
}
return pv;
}
private double protectionLegInterval(final double[] integrationSchedule, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve) {
double ht0 = creditCurve.getRT(integrationSchedule[0]);
double rt0 = yieldCurve.getRT(integrationSchedule[0]);
double b0 = Math.exp(-ht0 - rt0); // risky discount factor
double pv = 0.0;
final int n = integrationSchedule.length;
for (int i = 1; i < n; ++i) {
final double ht1 = creditCurve.getRT(integrationSchedule[i]);
final double rt1 = yieldCurve.getRT(integrationSchedule[i]);
final double b1 = Math.exp(-ht1 - rt1);
final double dht = ht1 - ht0;
final double drt = rt1 - rt0;
final 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;
if (Math.abs(dhrt) < 1e-5) {
dPV = dht * b0 * epsilon(-dhrt);
} else {
dPV = (b0 - b1) * dht / dhrt;
}
pv += dPV;
ht0 = ht1;
rt0 = rt1;
b0 = b1;
}
return pv;
}
/**
* The sensitivity of the PV of the protection leg to the zero hazard rate of a given node (knot) of the credit curve.
* @param cds analytic description of a CDS traded at a certain time
* @param yieldCurve The yield (or discount) curve
* @param creditCurve the credit (or survival) curve
* @param creditCurveNode The credit curve node
* @return sensitivity (on a unit notional)
*/
// public double protectionLegCreditSensitivity(final MultiCDSAnalytic cds, final ISDACompliantYieldCurve yieldCurve, final ISDACompliantCreditCurve creditCurve, final int creditCurveNode) {
// ArgumentChecker.notNull(cds, "null cds");
// ArgumentChecker.notNull(yieldCurve, "null yieldCurve");
// ArgumentChecker.notNull(creditCurve, "null creditCurve");
// ArgumentChecker.isTrue(creditCurveNode >= 0 && creditCurveNode < creditCurve.getNumberOfKnots(), "creditCurveNode out of range");
// if ((creditCurveNode != 0 && cds.getProtectionEnd() <= creditCurve.getTimeAtIndex(creditCurveNode - 1)) ||
// (creditCurveNode != creditCurve.getNumberOfKnots() - 1 && cds.getProtectionStart() >= creditCurve.getTimeAtIndex(creditCurveNode + 1))) {
// return 0.0; // can't have any sensitivity in this case
// }
// if (cds.getProtectionEnd() <= 0.0) { //short cut already expired CDSs
// return 0.0;
// }
//
// final double[] integrationSchedule = getIntegrationsPoints(cds.getProtectionStart(), cds.getProtectionEnd(), yieldCurve, creditCurve);
//
// double t = integrationSchedule[0];
// double ht0 = creditCurve.getRT(t);
// double rt0 = yieldCurve.getRT(t);
// double dqdr0 = creditCurve.getSingleNodeDiscountFactorSensitivity(t, creditCurveNode);
// double q0 = Math.exp(-ht0);
// double p0 = Math.exp(-rt0);
// // double pv = 0.0;
// double pvSense = 0.0;
// final int n = integrationSchedule.length;
// for (int i = 1; i < n; ++i) {
//
// t = integrationSchedule[i];
// final double ht1 = creditCurve.getRT(t);
// final double dqdr1 = creditCurve.getSingleNodeDiscountFactorSensitivity(t, creditCurveNode);
// final double rt1 = yieldCurve.getRT(t);
// final double q1 = Math.exp(-ht1);
// final double p1 = Math.exp(-rt1);
//
// if (dqdr0 == 0.0 && dqdr1 == 0.0) {
// ht0 = ht1;
// rt0 = rt1;
// p0 = p1;
// q0 = q1;
// continue;
// }
//
// final double dht = ht1 - ht0;
// final double drt = rt1 - rt0;
// final double dhrt = dht + drt;
//
// double dPVSense;
// if (Math.abs(dhrt) < 1e-5) {
// final double e = epsilon(-dhrt);
// final double eP = epsilonP(-dhrt);
// final double dPVdq0 = p0 * ((1 + dht) * e - dht * eP);
// final double dPVdq1 = -p0 * q0 / q1 * (e - dht * eP);
// dPVSense = dPVdq0 * dqdr0 + dPVdq1 * dqdr1;
// } else {
// final double w2 = dht / dhrt;
// final double w3 = (1 - w2) * (p0 * q0 - p1 * q1);
// dPVSense = ((w3 / q0 + dht * p0) / dhrt) * dqdr0 - ((w3 / q1 + dht * p1) / dhrt) * dqdr1;
// }
//
// pvSense += dPVSense;
//
// ht0 = ht1;
// dqdr0 = dqdr1;
// rt0 = rt1;
// p0 = p1;
// q0 = q1;
//
// }
// pvSense *= cds.getLGD();
//
// // Compute the discount factor discounting the upfront payment made on the cash settlement date back to the valuation date
// final double df = yieldCurve.getDiscountFactor(cds.getValuationTime());
//
// pvSense /= df;
//
// return pvSense;
// }
}