/** * 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 org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import org.testng.annotations.Test; import org.threeten.bp.LocalDate; import org.threeten.bp.Period; import com.opengamma.analytics.financial.credit.options.YieldCurveProvider; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.financial.convention.businessday.BusinessDayConvention; import com.opengamma.financial.convention.calendar.Calendar; import com.opengamma.financial.convention.daycount.DayCounts; /** * */ public class AnalyticBondPricerTest extends ISDABaseTest { private static boolean PRINT = false; static { if (PRINT) { System.out.println("BondEquivalentCDSSpreadTest - Set PRINT to false"); } } @Test public void bondPriceTest() { final double recoveryRate = 0.4; final CDSAnalyticFactory factory = new CDSAnalyticFactory(recoveryRate); final LocalDate tradeDate = LocalDate.of(2014, 3, 14); final CDSAnalytic cds = factory.makeIMMCDS(tradeDate, Period.ofYears(5)); final double bondCoupon = 0.05; //for now use the CDS mechanics to generate bond payment schedule final CDSAnalytic dummyCDS = factory.with(Period.ofMonths(6)).withProtectionStart(false).makeIMMCDS(tradeDate, Period.ofYears(5)); final CDSCoupon[] cdsCoupons = dummyCDS.getCoupons(); final int n = dummyCDS.getNumPayments(); final double[] paymentTimes = new double[n]; final double[] paymentAmounts = new double[n]; for (int i = 0; i < n; i++) { paymentTimes[i] = cdsCoupons[i].getPaymentTime(); paymentAmounts[i] = cdsCoupons[i].getYearFrac() * bondCoupon; } paymentAmounts[n - 1] += 1.0; final BondAnalytic bond = new BondAnalytic(paymentTimes, paymentAmounts, recoveryRate, dummyCDS.getAccruedPremium(bondCoupon)); final ISDACompliantYieldCurve yieldCurve = new ISDACompliantYieldCurve(1.0, 0.05); final double cleanPrice = defaultFreeBondCleanPrice(bond, yieldCurve); final AnalyticBondPricer bondSpreadCal = new AnalyticBondPricer(); final Function1D<Double, Double> bondPriceFunc = bondSpreadCal.getBondPriceForHazardRateFunction(bond, yieldCurve, PriceType.CLEAN); //now price will zero hazard rate - should get same number final double price = bondPriceFunc.evaluate(0.0); assertEquals(cleanPrice, price, 1e-15); assertEquals("Hazard rate limit", recoveryRate, bondSpreadCal.bondPriceForHazardRate(bond, yieldCurve, 1000.0, PriceType.DIRTY), 2e-5); //ramp up the hazard rate if (PRINT) { for (int i = 0; i < 100; i++) { final double lambda = i * 1.0 / 99; final double rPrice = bondPriceFunc.evaluate(lambda); final double s = bondSpreadCal.getEquivalentCDSSpread(bond, yieldCurve, rPrice, PriceType.CLEAN, cds); System.out.println(lambda + "\t" + rPrice + "\t" + s); } } } /** * Check our bond price is consistent with the CDS price. To do this we must price the protection leg of a CDS with protection from start true, but the annuity with * protection from start false (the annuity must also not have accrual-on-default) */ @Test public void bondPriceTest2() { final ISDACompliantYieldCurve yieldCurve = YieldCurveProvider.ISDA_USD_20140205; final double recoveryRate = 0.27; final Period couponPeriod = Period.ofMonths(6); final StubType stub = StubType.FRONTSHORT; final BusinessDayConvention bd = FOLLOWING; final Calendar cal = DEFAULT_CALENDAR; final CDSAnalyticFactory factory = new CDSAnalyticFactory(0.0).with(couponPeriod).withPayAccOnDefault(false); final LocalDate startDate = LocalDate.of(2013, 9, 20); final LocalDate endDate = LocalDate.of(2019, 3, 20); final LocalDate tradeDate = LocalDate.of(2014, 2, 5); final double exp = ACT365F.getDayCountFraction(tradeDate, endDate); final CDSAnalytic protectionLegCDS = factory.makeCDS(tradeDate, startDate, endDate); final CDSAnalytic annuityCDS = factory.withProtectionStart(false).makeCDS(tradeDate, startDate, endDate); final double bondCoupon = 0.07; final ISDAPremiumLegSchedule sch = new ISDAPremiumLegSchedule(startDate, endDate, couponPeriod, stub, bd, cal, false); final BondAnalytic bond = new BondAnalytic(tradeDate, bondCoupon, sch, recoveryRate, ACT360); final AnalyticCDSPricer cdsPricer = new AnalyticCDSPricer(); final AnalyticBondPricer bondPricer = new AnalyticBondPricer(); for (int i = 0; i < 10; i++) { final double lambda = 0.0 + 0.3 * i / 9.; final ISDACompliantCreditCurve cc = new ISDACompliantCreditCurve(10.0, lambda); final double bondPrice = bondPricer.bondPriceForHazardRate(bond, yieldCurve, lambda, PriceType.DIRTY); final double cdsProtLeg = cdsPricer.protectionLeg(protectionLegCDS, yieldCurve, cc, 0.0); final double cdsAnnuity = cdsPricer.annuity(annuityCDS, yieldCurve, cc, PriceType.DIRTY, 0.0); final double q = cc.getSurvivalProbability(exp); final double p = yieldCurve.getDiscountFactor(exp); final double bondPriceAsCDS = cdsAnnuity * bondCoupon + q * p + recoveryRate * cdsProtLeg; // System.out.println(cdsProtLeg); // System.out.println(bondPrice + "\t" + bondPriceAsCDS); assertEquals(bondPriceAsCDS, bondPrice, 1e-15); } } /** * Bond and CDS coincide for certain setup */ public void limitedCaseTest() { final double tol = 1.e-12; final AnalyticBondPricer bondPricer = new AnalyticBondPricer(); final AnalyticCDSPricer cdsPricer = new AnalyticCDSPricer(); final LocalDate tradeDate = LocalDate.of(2014, 2, 13); final LocalDate startDate = LocalDate.of(2013, 12, 20); final LocalDate endDate = LocalDate.of(2018, 12, 20); final Period couponPrd = Period.ofMonths(6); final StubType stubTp = StubType.FRONTSHORT; final BusinessDayConvention bdConv = MOD_FOLLOWING; final Calendar cal = DEFAULT_CALENDAR; boolean ProtStart = false; double rr = 0.; final PriceType priceTp = PriceType.DIRTY; final ISDACompliantYieldCurve yc = YieldCurveProvider.ISDA_USD_20140213; final double hr = 0.2; final ISDACompliantCreditCurve cc = new ISDACompliantCreditCurve(10., hr); final double coupon = 0.1; final ISDAPremiumLegSchedule schedule = new ISDAPremiumLegSchedule(startDate, endDate, couponPrd, stubTp, bdConv, cal, ProtStart); final BondAnalytic bond = new BondAnalytic(tradeDate, coupon, schedule, rr, ACT360); final CDSAnalytic cds = new CDSAnalytic(tradeDate, tradeDate.plusDays(1), tradeDate, startDate, endDate, false, couponPrd, stubTp, ProtStart, 1. - rr, bdConv, cal, ACT360, DayCounts.ACT_365); final double resBond1 = bondPricer.bondPriceForHazardRate(bond, yc, hr, priceTp); final double resCDS1 = -cdsPricer.pv(cds, yc, cc, coupon, priceTp); final double mat = bond.getPaymentTime(bond.getnPayments() - 1); assertEquals((resCDS1 + 1. * yc.getDiscountFactor(mat) * cc.getDiscountFactor(mat)), resBond1, tol); final double eqSp1 = bondPricer.getEquivalentCDSSpread(bond, yc, resBond1, priceTp, cds); assertEquals(0., eqSp1, tol); rr = 0.3; ProtStart = true; final CDSAnalytic cds2 = new CDSAnalytic(tradeDate, tradeDate.plusDays(1), tradeDate, startDate, endDate, false, couponPrd, stubTp, ProtStart, 1. - rr, bdConv, cal, ACT360, DayCounts.ACT_365); final BondAnalytic bond2 = new BondAnalytic(tradeDate, 0., schedule, rr, ACT360); final double resBond2 = bondPricer.bondPriceForHazardRate(bond2, yc, hr, priceTp); final double resCDS2 = cdsPricer.pv(cds2, yc, cc, 0., priceTp); assertEquals((resCDS2 + 1. * yc.getDiscountFactor(mat) * cc.getDiscountFactor(mat)), resBond2, tol); final double eqSp2 = bondPricer.getEquivalentCDSSpread(bond2, yc, resBond2, priceTp, cds2); final double sp2 = cdsPricer.parSpread(cds2, yc, cc); assertEquals(sp2, eqSp2, tol); } /** * */ public void hazardRateTest() { final double tol = 1.e-12; final AnalyticBondPricer bondPricer = new AnalyticBondPricer(); final LocalDate tradeDate = LocalDate.of(2014, 2, 13); final LocalDate startDate = LocalDate.of(2013, 12, 20); final LocalDate endDate = LocalDate.of(2018, 12, 20); final ISDACompliantYieldCurve yc = YieldCurveProvider.ISDA_USD_20140213; final double coupon = 0.11; double rr = 0.3; final Period couponPrd = Period.ofMonths(6); final StubType stubTp = StubType.FRONTSHORT; final BusinessDayConvention bdConv = MOD_FOLLOWING; final Calendar cal = DEFAULT_CALENDAR; final boolean ProtStart = true; final ISDAPremiumLegSchedule schedule = new ISDAPremiumLegSchedule(startDate, endDate, couponPrd, stubTp, bdConv, cal, ProtStart); final BondAnalytic bond = new BondAnalytic(tradeDate, coupon, schedule, rr, ACT360); final double hr = 0.15; final double cleanPrice = bondPricer.bondPriceForHazardRate(bond, yc, hr, PriceType.CLEAN); final double dirtyPrice = bondPricer.bondPriceForHazardRate(bond, yc, hr, PriceType.DIRTY); final double hrClean = bondPricer.getHazardRate(bond, yc, cleanPrice, PriceType.CLEAN); final double hrDirty = bondPricer.getHazardRate(bond, yc, dirtyPrice, PriceType.DIRTY); assertEquals(hr, hrClean, tol); assertEquals(hr, hrDirty, tol); final double priceZero = bondPricer.bondPriceForHazardRate(bond, yc, 0., PriceType.DIRTY); final double hrZero = bondPricer.getHazardRate(bond, yc, priceZero, PriceType.DIRTY); assertEquals(0., hrZero, tol); /* * Exception thrown */ try { bondPricer.getHazardRate(bond, yc, priceZero * 2., PriceType.CLEAN); throw new RuntimeException(); } catch (final Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { bondPricer.getHazardRate(bond, yc, bond.getRecoveryRate() * 0.5, PriceType.DIRTY); throw new RuntimeException(); } catch (final Exception e) { assertEquals("The dirty price of " + bond.getRecoveryRate() * 0.5 + " give, is less than the bond's recovery rate of " + bond.getRecoveryRate() + ". Please check inputs", e.getMessage()); } try { bondPricer.getHazardRate(bond, yc, -cleanPrice, PriceType.CLEAN); throw new RuntimeException(); } catch (final Exception e) { assertEquals("Bond price must be positive", e.getMessage()); } } /** * */ public void exceptionalBranchTest() { final double tol = 1.e-12; final AnalyticBondPricer bondPricer = new AnalyticBondPricer(); final LocalDate tradeDate = LocalDate.of(2014, 2, 13); final LocalDate startDate = LocalDate.of(2013, 12, 20); final LocalDate endDate = LocalDate.of(2014, 12, 20); final double coupon = 0.11; double rr = 0.3; final Period couponPrd = Period.ofMonths(6); final StubType stubTp = StubType.FRONTSHORT; final BusinessDayConvention bdConv = MOD_FOLLOWING; final Calendar cal = DEFAULT_CALENDAR; final boolean ProtStart = true; final ISDAPremiumLegSchedule schedule = new ISDAPremiumLegSchedule(startDate, endDate, couponPrd, stubTp, bdConv, cal, ProtStart); final BondAnalytic bond = new BondAnalytic(tradeDate, coupon, schedule, rr, ACT360); final double bondPaymentTimeLast = bond.getPaymentTime(bond.getnPayments() - 1); final double hr = 0.11; final ISDACompliantYieldCurve yc1 = new ISDACompliantYieldCurve(new double[] {0.3 * bondPaymentTimeLast, 0.7 * bondPaymentTimeLast, bondPaymentTimeLast, 1.2 * bondPaymentTimeLast, 2. * bondPaymentTimeLast }, new double[] {-hr, 0.11, 0.044, 0.1, 0.12 }); final ISDACompliantYieldCurve yc2 = new ISDACompliantYieldCurve(new double[] {1.1 * bondPaymentTimeLast, 1.2 * bondPaymentTimeLast, 1.3 * bondPaymentTimeLast, 1.5 * bondPaymentTimeLast, 2.1 * bondPaymentTimeLast }, new double[] {0.1, 0.11, 0.08, 0.12, 0.12 }); final ISDACompliantYieldCurve yc3 = new ISDACompliantYieldCurve(new double[] {bondPaymentTimeLast, 1.2 * bondPaymentTimeLast, 1.3 * bondPaymentTimeLast, 1.5 * bondPaymentTimeLast, 2.1 * bondPaymentTimeLast }, new double[] {0.12, 0.11, 0.08, 0.12, 0.12 }); final ISDACompliantYieldCurve[] ycArr = new ISDACompliantYieldCurve[] {yc1, yc2, yc3 }; final int nyc = ycArr.length; for (int i = 0; i < nyc; ++i) { final double bondPrice = bondPricer.bondPriceForHazardRate(bond, ycArr[i], hr, PriceType.CLEAN); final double impliedHr = bondPricer.getHazardRate(bond, ycArr[i], bondPrice, PriceType.CLEAN); assertEquals(hr, impliedHr, tol); } } private double defaultFreeBondCleanPrice(final BondAnalytic bond, final ISDACompliantYieldCurve yieldCurve) { double pv = -bond.getAccruedInterest(); final int n = bond.getnPayments(); for (int i = 0; i < n; i++) { pv += bond.getPaymentAmount(i) * yieldCurve.getDiscountFactor(bond.getPaymentTime(i)); } return pv; } }