/** * Copyright (C) 2014 - 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.math.utilities.Epsilon.epsilon; import java.util.Arrays; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.math.rootfinding.NewtonRaphsonSingleRootFinder; import com.opengamma.util.ArgumentChecker; /** * */ 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 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 low that the bond's recovery rate or greater * than its risk free price. * @param cleanOrDirty Clean or dirty price for the bond * @param cds analytic description of a CDS traded at a certain time. The spread is calculated for this CDS. * @see {@link getHazardRate} * @return equivalent CDS spread */ public double getEquivalentCDSSpread(final BondAnalytic bond, final ISDACompliantYieldCurve yieldCurve, final double bondPrice, final PriceType cleanOrDirty, final CDSAnalytic cds) { final double lambda = getHazardRate(bond, yieldCurve, bondPrice, cleanOrDirty); final ISDACompliantCreditCurve cc = new ISDACompliantCreditCurve(cds.getProtectionEnd(), lambda); return _pricer.parSpread(cds, yieldCurve, cc); } /** *Get the constant hazard rate implied from a bond price * @param bond 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 low that the bond's recovery rate or greater * than its risk free price. * @param cleanOrDirty Clean or dirty price for the bond * @return The implied hazard rate */ public double getHazardRate(final BondAnalytic bond, final ISDACompliantYieldCurve yieldCurve, final double bondPrice, final PriceType cleanOrDirty) { ArgumentChecker.isTrue(bondPrice > 0.0, "Bond price must be positive"); final Function1D<Double, Double> priceFunc = getBondPriceForHazardRateFunction(bond, yieldCurve, cleanOrDirty); final double zeroRiskPrice = priceFunc.evaluate(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"); } final double dp = cleanOrDirty == PriceType.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"); } final Function1D<Double, Double> func = new Function1D<Double, Double>() { @Override public Double evaluate(final Double lambda) { return priceFunc.evaluate(lambda) - bondPrice; } }; final double guess = 0.01; return ROOTFINDER.getRoot(func, guess); } /** * Compute the bond price for a given level of a constant hazard rate * @param bond Simple analytic representation of a fixed coupon bond * @param yieldCurve The yield curve * @param hazardRate The hazard rate. Can be zero. * @param cleanOrDirty Clean or dirty price for the bond * @see {@link getBondPriceForHazardRateFunction} * @return The bond price */ public double bondPriceForHazardRate(final BondAnalytic bond, final ISDACompliantYieldCurve yieldCurve, final double hazardRate, final PriceType cleanOrDirty) { return getBondPriceForHazardRateFunction(bond, yieldCurve, cleanOrDirty).evaluate(hazardRate); } /** * This gives a function (Function1D<Double, Double>) that allows you to price a bond for any level of a constant hazard rate * @param bond Simple analytic representation of a fixed coupon bond * @param yieldCurve The yield curve * @param cleanOrDirty Clean or dirty price for the bond * @return a function of hazard rate -> bond price */ public Function1D<Double, Double> getBondPriceForHazardRateFunction(final BondAnalytic bond, final ISDACompliantYieldCurve yieldCurve, final PriceType cleanOrDirty) { ArgumentChecker.notNull(bond, "bond"); ArgumentChecker.notNull(yieldCurve, "yieldCurve"); ArgumentChecker.notNull(cleanOrDirty, "cleanOrDirty"); final int nPayments = bond.getnPayments(); final double[] discPayments = new double[nPayments]; for (int i = 0; i < nPayments; i++) { discPayments[i] = bond.getPaymentAmount(i) * yieldCurve.getDiscountFactor(bond.getPaymentTime(i)); } final 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; } final double[] intNodes = temp; final int nNodes = intNodes.length; final double[] rt = new double[nNodes]; for (int i = 0; i < nNodes; i++) { rt[i] = yieldCurve.getRT(intNodes[i]); } return new Function1D<Double, Double>() { @Override public Double evaluate(final Double lambda) { double riskyDisPayments = cleanOrDirty == PriceType.CLEAN ? -bond.getAccruedInterest() : 0.0; for (int i = 0; i < nPayments; i++) { final double q = Math.exp(-lambda * bond.getPaymentTime(i)); riskyDisPayments += discPayments[i] * q; } if (bond.getRecoveryRate() == 0.0) { return riskyDisPayments; } final double[] ht = new double[nNodes]; final 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; { final double dht = ht[0]; final double drt = rt[0]; final 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) { final double dht = ht[i] - ht[i - 1]; final double drt = rt[i] - rt[i - 1]; final 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; } }; } }