/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.impl.credit.isda;
import static com.opengamma.strata.math.impl.util.Epsilon.epsilon;
import java.util.Arrays;
import java.util.function.Function;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.math.impl.rootfinding.NewtonRaphsonSingleRootFinder;
/**
* The bond pricer.
*/
public class AnalyticBondPricer {
private static final NewtonRaphsonSingleRootFinder ROOTFINDER = new NewtonRaphsonSingleRootFinder();
private final AnalyticCdsPricer pricer = new AnalyticCdsPricer();
//-------------------------------------------------------------------------
/**
* Compute the equivalent CDS spread for a bond. This works by first finding a constant
* hazard rate that reprices the bond (given the supplied yield curve), the using this hazard
* rate to calculate the par spread of a CDS.
*
* @param bond the simple analytic representation of a fixed coupon bond
* @param yieldCurve the yield curve
* @param bondPrice the bond price (for unit notional). Can be given clean or dirty (see below).
* The dirty price cannot be below that of the bond's recovery rate or greater than its risk free price
* @param cleanOrDirty the clean or dirty price for the bond
* @param cds the analytic description of a CDS traded at a certain time for which the spread is calculated
* @see #getHazardRate
* @return the equivalent CDS spread
*/
public double getEquivalentCdsSpread(
BondAnalytic bond,
IsdaCompliantYieldCurve yieldCurve,
double bondPrice,
CdsPriceType cleanOrDirty,
CdsAnalytic cds) {
double lambda = getHazardRate(bond, yieldCurve, bondPrice, cleanOrDirty);
IsdaCompliantCreditCurve cc = new IsdaCompliantCreditCurve(cds.getProtectionEnd(), lambda);
return pricer.parSpread(cds, yieldCurve, cc);
}
//-------------------------------------------------------------------------
/**
* Gets the constant hazard rate implied from a bond price.
*
* @param bond the simple analytic representation of a fixed coupon bond
* @param yieldCurve the yield curve
* @param bondPrice the bond price (for unit notional). Can be given clean or dirty (see below).
* The dirty price cannot be below that of the bond's recovery rate or greater than its risk free price
* @param cleanOrDirty the clean or dirty price for the bond
* @return the implied hazard rate
*/
public double getHazardRate(
BondAnalytic bond,
IsdaCompliantYieldCurve yieldCurve,
double bondPrice,
CdsPriceType cleanOrDirty) {
ArgChecker.isTrue(bondPrice > 0.0, "Bond price must be positive");
Function<Double, Double> priceFunc = getBondPriceForHazardRateFunction(bond, yieldCurve, cleanOrDirty);
double zeroRiskPrice = priceFunc.apply(0.);
if (bondPrice == zeroRiskPrice) {
return 0.0;
}
if (bondPrice > zeroRiskPrice) {
throw new IllegalArgumentException("Bond price of " + bondPrice + ", is greater that zero-risk price of " +
zeroRiskPrice + ". It is not possible to imply a hazard rate for this bond. Please check inputs");
}
double dp = cleanOrDirty == CdsPriceType.DIRTY ? bondPrice : bondPrice + bond.getAccruedInterest();
if (dp <= bond.getRecoveryRate()) {
throw new IllegalArgumentException("The dirty price of " + dp + " give, is less than the bond's recovery rate of " +
bond.getRecoveryRate() + ". Please check inputs");
}
Function<Double, Double> func = new Function<Double, Double>() {
@Override
public Double apply(Double lambda) {
return priceFunc.apply(lambda) - bondPrice;
}
};
double guess = 0.01;
return ROOTFINDER.getRoot(func, guess);
}
//-------------------------------------------------------------------------
/**
* Computes the bond price for a given level of a constant hazard rate.
*
* @param bond the simple analytic representation of a fixed coupon bond
* @param yieldCurve the yield curve
* @param hazardRate the hazard rate, can be zero
* @param cleanOrDirty the clean or dirty price for the bond
* @see #getBondPriceForHazardRateFunction
* @return the bond price
*/
public double bondPriceForHazardRate(
BondAnalytic bond, IsdaCompliantYieldCurve yieldCurve, double hazardRate, CdsPriceType cleanOrDirty) {
return getBondPriceForHazardRateFunction(bond, yieldCurve, cleanOrDirty).apply(hazardRate);
}
//-------------------------------------------------------------------------
/**
* This gives a function that allows you to price a bond for any level of a constant hazard rate.
*
* @param bond the simple analytic representation of a fixed coupon bond
* @param yieldCurve the yield curve
* @param cleanOrDirty the clean or dirty price for the bond
* @return a function of hazard rate to bond price
*/
public Function<Double, Double> getBondPriceForHazardRateFunction(
BondAnalytic bond,
IsdaCompliantYieldCurve yieldCurve,
CdsPriceType cleanOrDirty) {
ArgChecker.notNull(bond, "bond");
ArgChecker.notNull(yieldCurve, "yieldCurve");
ArgChecker.notNull(cleanOrDirty, "cleanOrDirty");
int nPayments = bond.getnPayments();
double[] discPayments = new double[nPayments];
for (int i = 0; i < nPayments; i++) {
discPayments[i] = bond.getPaymentAmount(i) * yieldCurve.getDiscountFactor(bond.getPaymentTime(i));
}
double exp = bond.getPaymentTime(nPayments - 1);
int index = Arrays.binarySearch(yieldCurve.getKnotTimes(), exp);
double[] temp;
if (index >= 0) {
temp = new double[index + 1];
System.arraycopy(yieldCurve.getKnotTimes(), 0, temp, 0, index + 1);
} else {
index = -(index + 1);
temp = new double[index + 1];
System.arraycopy(yieldCurve.getKnotTimes(), 0, temp, 0, index);
temp[index] = exp;
}
double[] intNodes = temp;
int nNodes = intNodes.length;
double[] rt = new double[nNodes];
for (int i = 0; i < nNodes; i++) {
rt[i] = yieldCurve.getRT(intNodes[i]);
}
return new Function<Double, Double>() {
@Override
public Double apply(Double lambda) {
double riskyDisPayments = cleanOrDirty == CdsPriceType.CLEAN ? -bond.getAccruedInterest() : 0.0;
for (int i = 0; i < nPayments; i++) {
double q = Math.exp(-lambda * bond.getPaymentTime(i));
riskyDisPayments += discPayments[i] * q;
}
if (bond.getRecoveryRate() == 0.0) {
return riskyDisPayments;
}
double[] ht = new double[nNodes];
double[] b = new double[nNodes];
for (int i = 0; i < nNodes; ++i) {
ht[i] = lambda * intNodes[i];
b[i] = Math.exp(-rt[i] - ht[i]);
}
double defaultPV = 0.0;
{
double dht = ht[0];
double drt = rt[0];
double dhrt = dht + drt;
double dPV;
if (Math.abs(dhrt) < 1e-5) {
dPV = dht * epsilon(-dhrt);
} else {
dPV = (1 - b[0]) * dht / dhrt;
}
defaultPV += dPV;
}
for (int i = 1; i < nNodes; ++i) {
double dht = ht[i] - ht[i - 1];
double drt = rt[i] - rt[i - 1];
double dhrt = dht + drt;
double dPV;
if (Math.abs(dhrt) < 1e-5) {
dPV = dht * b[i - 1] * epsilon(-dhrt);
} else {
dPV = (b[i - 1] - b[i]) * dht / dhrt;
}
defaultPV += dPV;
}
return riskyDisPayments + bond.getRecoveryRate() * defaultPV;
}
};
}
}