/** * 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 org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import java.time.LocalDate; import java.time.Period; import java.util.function.Function; import org.testng.annotations.Test; import com.opengamma.strata.basics.date.BusinessDayConvention; import com.opengamma.strata.basics.date.DayCounts; import com.opengamma.strata.basics.date.HolidayCalendar; import com.opengamma.strata.basics.schedule.StubConvention; /** * */ @Test public class AnalyticBondPricerTest extends IsdaBaseTest { public void bondPriceTest() { final double recoveryRate = 0.4; final CdsAnalyticFactory factory = new CdsAnalyticFactory(recoveryRate); final LocalDate tradeDate = LocalDate.of(2014, 3, 14); 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 Function<Double, Double> bondPriceFunc = bondSpreadCal.getBondPriceForHazardRateFunction(bond, yieldCurve, CdsPriceType.CLEAN); //now price will zero hazard rate - should get same number final double price = bondPriceFunc.apply(0.0); assertEquals(cleanPrice, price, 1e-15); assertEquals("Hazard rate limit", recoveryRate, bondSpreadCal.bondPriceForHazardRate(bond, yieldCurve, 1000.0, CdsPriceType.DIRTY), 2e-5); } /** * 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) */ public void bondPriceTest2() { final IsdaCompliantYieldCurve yieldCurve = YieldCurveProvider.ISDA_USD_20140205; final double recoveryRate = 0.27; final Period couponPeriod = Period.ofMonths(6); final StubConvention stub = StubConvention.SHORT_INITIAL; final BusinessDayConvention bd = FOLLOWING; final HolidayCalendar 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.yearFraction(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, CdsPriceType.DIRTY); final double cdsProtLeg = cdsPricer.protectionLeg(protectionLegCDS, yieldCurve, cc, 0.0); final double cdsAnnuity = cdsPricer.annuity(annuityCDS, yieldCurve, cc, CdsPriceType.DIRTY, 0.0); final double q = cc.getSurvivalProbability(exp); final double p = yieldCurve.getDiscountFactor(exp); final double bondPriceAsCDS = cdsAnnuity * bondCoupon + q * p + recoveryRate * cdsProtLeg; 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 StubConvention stubTp = StubConvention.SHORT_INITIAL; final BusinessDayConvention bdConv = MOD_FOLLOWING; final HolidayCalendar cal = DEFAULT_CALENDAR; boolean ProtStart = false; double rr = 0.; final CdsPriceType priceTp = CdsPriceType.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_365F); 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_365F); 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 StubConvention stubTp = StubConvention.SHORT_INITIAL; final BusinessDayConvention bdConv = MOD_FOLLOWING; final HolidayCalendar 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, CdsPriceType.CLEAN); final double dirtyPrice = bondPricer.bondPriceForHazardRate(bond, yc, hr, CdsPriceType.DIRTY); final double hrClean = bondPricer.getHazardRate(bond, yc, cleanPrice, CdsPriceType.CLEAN); final double hrDirty = bondPricer.getHazardRate(bond, yc, dirtyPrice, CdsPriceType.DIRTY); assertEquals(hr, hrClean, tol); assertEquals(hr, hrDirty, tol); final double priceZero = bondPricer.bondPriceForHazardRate(bond, yc, 0., CdsPriceType.DIRTY); final double hrZero = bondPricer.getHazardRate(bond, yc, priceZero, CdsPriceType.DIRTY); assertEquals(0., hrZero, tol); /* * Exception thrown */ try { bondPricer.getHazardRate(bond, yc, priceZero * 2., CdsPriceType.CLEAN); throw new RuntimeException(); } catch (final Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { bondPricer.getHazardRate(bond, yc, bond.getRecoveryRate() * 0.5, CdsPriceType.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, CdsPriceType.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 StubConvention stubTp = StubConvention.SHORT_INITIAL; final BusinessDayConvention bdConv = MOD_FOLLOWING; final HolidayCalendar 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, CdsPriceType.CLEAN); final double impliedHr = bondPricer.getHazardRate(bond, ycArr[i], bondPrice, CdsPriceType.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; } }