/** * 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.IMMDateLogic.getIMMDateSet; import static com.opengamma.analytics.financial.credit.isdastandardmodel.IMMDateLogic.getNextIMMDate; import static com.opengamma.analytics.financial.credit.isdastandardmodel.IMMDateLogic.isIMMDate; import static com.opengamma.financial.convention.businessday.BusinessDayDateUtils.addWorkDays; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.testng.annotations.Test; import org.threeten.bp.LocalDate; import org.threeten.bp.Month; import org.threeten.bp.Period; import com.opengamma.analytics.financial.credit.isdastandardmodel.CDSAnalytic; import com.opengamma.analytics.financial.credit.isdastandardmodel.CDSQuoteConvention; import com.opengamma.analytics.financial.credit.isdastandardmodel.ISDACompliantCreditCurve; import com.opengamma.analytics.financial.credit.isdastandardmodel.ISDACompliantYieldCurve; import com.opengamma.analytics.financial.credit.isdastandardmodel.MarketQuoteConverter; import com.opengamma.analytics.financial.credit.isdastandardmodel.ParSpread; import com.opengamma.analytics.financial.credit.isdastandardmodel.QuotedSpread; import com.opengamma.analytics.financial.model.BumpType; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.test.TestGroup; /** * Test. */ @Test(groups = TestGroup.UNIT) public class ParVsQuotedSpreadTest extends ISDABaseTest { private static final MarketQuoteConverter PUF_CONVERTER = new MarketQuoteConverter(); protected static final double NOTIONAL = 1e6; private static final LocalDate TRADE_DATE = LocalDate.of(2013, Month.FEBRUARY, 27); //Today private static final LocalDate EFFECTIVE_DATE = TRADE_DATE.plusDays(1); // AKA stepin date private static final LocalDate CASH_SETTLE_DATE = addWorkDays(TRADE_DATE, 3, DEFAULT_CALENDAR); // AKA valuation date private static final LocalDate STARTDATE = LocalDate.of(2012, Month.DECEMBER, 20);//last IMM date before TRADE_DATE; private static final double COUPON = 100; private static final double RECOVERY = 0.25; private static final Period NON_IMM_TENOR = Period.ofMonths(6); //yield curve private static final LocalDate SPOT_DATE = LocalDate.of(2013, Month.MARCH, 1); private static final String[] YIELD_CURVE_POINTS = new String[] {"1M", "2M", "3M", "6M", "9M", "1Y", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "12Y", "15Y", "20Y", "25Y", "30Y" }; private static final String[] YIELD_CURVE_INSTRUMENTS = new String[] {"M", "M", "M", "M", "M", "M", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S", "S" }; private static final double[] YIELD_CURVE_RATES = new double[] {0.002037, 0.00243, 0.002866, 0.004569, 0.00612, 0.007525, 0.00398, 0.005075, 0.00697, 0.00933, 0.01182, 0.01413, 0.016225, 0.01809, 0.01973, 0.022455, 0.02519, 0.02753, 0.02867, 0.029325 }; private static final ISDACompliantYieldCurve YIELD_CURVE = makeYieldCurve(TRADE_DATE, SPOT_DATE, YIELD_CURVE_POINTS, YIELD_CURVE_INSTRUMENTS, YIELD_CURVE_RATES); private static final String[] MATURITY_DATE_STRINGS = new String[] {"20/03/2013", "20/04/2013", "20/05/2013", "20/06/2013", "20/07/2013", "20/08/2013", "20/09/2013", "20/10/2013", "20/11/2013", "20/12/2013", "20/01/2014", "20/02/2014", "20/03/2014", "20/04/2014", "20/05/2014", "20/06/2014", "20/07/2014", "20/08/2014", "20/09/2014", "20/10/2014", "20/11/2014", "20/12/2014", "20/01/2015", "20/02/2015", "20/03/2015", "20/04/2015", "20/05/2015", "20/06/2015", "20/07/2015", "20/08/2015", "20/09/2015", "20/10/2015", "20/11/2015", "20/12/2015", "20/01/2016", "20/02/2016", "20/03/2016", "20/04/2016", "20/05/2016", "20/06/2016", "20/07/2016", "20/08/2016", "20/09/2016", "20/10/2016", "20/11/2016", "20/12/2016", "20/01/2017", "20/02/2017", "20/03/2017", "20/04/2017", "20/05/2017", "20/06/2017", "20/07/2017", "20/08/2017", "20/09/2017", "20/10/2017", "20/11/2017", "20/12/2017", "20/01/2018", "20/02/2018", "20/03/2018", "20/04/2018", "20/05/2018", "20/06/2018", "20/07/2018", "20/08/2018", "20/09/2018", "20/10/2018", "20/11/2018", "20/12/2018", "20/01/2019", "20/02/2019", "20/03/2019", "20/04/2019", "20/05/2019", "20/06/2019", "20/07/2019", "20/08/2019", "20/09/2019", "20/10/2019", "20/11/2019", "20/12/2019", "20/01/2020", "20/02/2020", "20/03/2020", "20/04/2020", "20/05/2020", "20/06/2020", "20/07/2020", "20/08/2020", "20/09/2020", "20/10/2020", "20/11/2020", "20/12/2020", "20/01/2021", "20/02/2021", "20/03/2021", "20/04/2021", "20/05/2021", "20/06/2021", "20/07/2021", "20/08/2021", "20/09/2021", "20/10/2021", "20/11/2021", "20/12/2021", "20/01/2022", "20/02/2022", "20/03/2022", "20/04/2022", "20/05/2022", "20/06/2022", "20/07/2022", "20/08/2022", "20/09/2022", "20/10/2022", "20/11/2022", "20/12/2022", "20/01/2023", "20/02/2023", "20/03/2023" }; private static final LocalDate[] MATURITY_DATES = parseDateStrings(MATURITY_DATE_STRINGS); private static final LocalDate[] IMM_DATES; private static final LocalDate[] NON_IMM_DATES; //these are a mix of quoted spreads (for IMM dates) and par spreads (for non IMM dates) private static final double[] SPREADS_BPS_ALL = new double[] {60.04, 58.48, 58.68, 60.39, 61.22, 63.02, 64.85, 68.32, 71.47, 74.93, 76.28, 78.24, 81.63, 84.76, 88.94, 94.66, 96.45, 99.81, 104.32, 105.65, 108.27, 111.37, 112.99, 115.26, 117.63, 120.8, 124.09, 127.81, 130.38, 133.34, 136.82, 138.77, 141.3, 144.53, 146.03, 148.33, 151.36, 154.12, 157.58, 162.05, 164.28, 167.56, 171.71, 173.55, 176.45, 180.1, 181.9, 184.58, 187.87, 189.95, 192.85, 196.78, 198.42, 201.2, 205.21, 206.35, 208.85, 212.71, 213.4, 215.48, 219.05, 219.11, 220.81, 224.53, 224.07, 225.66, 229.56, 228.61, 230, 234.04, 232.6, 233.87, 237.97, 236.37, 237.64, 241.91, 240.19, 241.42, 245.56, 243.79, 244.94, 248.97, 247.07, 248.09, 252.11, 249.88, 250.7, 254.82, 252.31, 253.09, 257.4, 254.54, 255.22, 259.64, 256.52, 257.17, 261.7, 258.42, 259.07, 263.84, 260.35, 260.98, 265.81, 262.14, 262.71, 267.6, 263.78, 264.32, 269.28, 265.33, 265.85, 270.98, 266.89, 267.42, 272.63, 268.42, 268.94, 274.2, 269.85, 270.28, 275.49 }; //treat spreads on IMM dates as quoted (aka flat) spreads, and those on non-IMM dates as par spreads; private static final List<CDSQuoteConvention> IMM_QUOTES; private static final List<CDSQuoteConvention> NON_IMM_QUOTES; //the buckets private static final Period[] TENORS = new Period[] {Period.ofMonths(6), Period.ofYears(1), Period.ofYears(2), Period.ofYears(3), Period.ofYears(4), Period.ofYears(5), Period.ofYears(6), Period.ofYears(7), Period.ofYears(8), Period.ofYears(9), Period.ofYears(10) }; //the bucket (or pillar) dates private static final LocalDate NEXT_IMM = getNextIMMDate(TRADE_DATE); private static final LocalDate[] PILLAR_DATES = getIMMDateSet(NEXT_IMM, TENORS); //the spreads at the buckets (pillars) - it is not clear whether these are par or quoted spreads private static final double[] PILLAR_SPREADS; static { final double coupon = COUPON * ONE_BP; final int n = SPREADS_BPS_ALL.length; final List<LocalDate> immDates = new ArrayList<>(n / 4 + 2); final List<LocalDate> nonImmDates = new ArrayList<>(3 * n / 4 + 2); IMM_QUOTES = new ArrayList<>(n / 4 + 2); NON_IMM_QUOTES = new ArrayList<>(3 * n / 4 + 2); for (int i = 0; i < n; i++) { if (isIMMDate(MATURITY_DATES[i])) { IMM_QUOTES.add(new QuotedSpread(coupon, SPREADS_BPS_ALL[i] * ONE_BP)); immDates.add(MATURITY_DATES[i]); } else { NON_IMM_QUOTES.add(new ParSpread(SPREADS_BPS_ALL[i] * ONE_BP)); nonImmDates.add(MATURITY_DATES[i]); } } IMM_DATES = new LocalDate[immDates.size()]; NON_IMM_DATES = new LocalDate[nonImmDates.size()]; immDates.toArray(IMM_DATES); nonImmDates.toArray(NON_IMM_DATES); final int nPillars = PILLAR_DATES.length; PILLAR_SPREADS = new double[nPillars]; //do not distinguish between par and quoted spreads for (int i = 0; i < nPillars; i++) { final int index = Arrays.binarySearch(MATURITY_DATES, PILLAR_DATES[i]); if (index < 0) { final int insPoint = -(index + 1); PILLAR_SPREADS[i] = SPREADS_BPS_ALL[insPoint - 1]; } else { PILLAR_SPREADS[i] = SPREADS_BPS_ALL[index]; } } } @Test(enabled = false) public void buildCurveFromPillarsTest() { final int nPillars = PILLAR_DATES.length; final CDSAnalytic[] pillarCDSs = new CDSAnalytic[nPillars]; final CDSQuoteConvention[] quotes = new CDSQuoteConvention[nPillars]; final double[] spreads = new double[nPillars]; for (int i = 0; i < nPillars; i++) { pillarCDSs[i] = new CDSAnalytic(TRADE_DATE, SPOT_DATE, EFFECTIVE_DATE, STARTDATE, PILLAR_DATES[i], PAY_ACC_ON_DEFAULT, PAYMENT_INTERVAL, STUB, PROCTECTION_START, RECOVERY); spreads[i] = PILLAR_SPREADS[i] * ONE_BP; quotes[i] = new QuotedSpread(COUPON * ONE_BP, spreads[i]); System.out.println(TENORS[i] + "\t" + PILLAR_DATES[i]); } //build curve from pillar dates final ISDACompliantCreditCurve creditCurve = CREDIT_CURVE_BUILDER.calibrateCreditCurve(pillarCDSs, quotes, YIELD_CURVE); for (int i = 0; i < SPREADS_BPS_ALL.length; i++) { final LocalDate mat = MATURITY_DATES[i]; if (isIMMDate(mat)) { final Period couponInterval = PAYMENT_INTERVAL; final CDSAnalytic cds = new CDSAnalytic(TRADE_DATE, SPOT_DATE, EFFECTIVE_DATE, STARTDATE, mat, PAY_ACC_ON_DEFAULT, couponInterval, STUB, PROCTECTION_START, RECOVERY); final double puf = PRICER.pv(cds, YIELD_CURVE, creditCurve, COUPON * ONE_BP); final double qSpread = PUF_CONVERTER.pufToQuotedSpread(cds, COUPON * ONE_BP, YIELD_CURVE, puf); System.out.println(mat + "\tIMM\t" + qSpread * TEN_THOUSAND); } else { final Period couponInterval = NON_IMM_TENOR; final CDSAnalytic cds = new CDSAnalytic(TRADE_DATE, SPOT_DATE, EFFECTIVE_DATE, STARTDATE, mat, PAY_ACC_ON_DEFAULT, couponInterval, STUB, PROCTECTION_START, RECOVERY); final double parSpread = PRICER.parSpread(cds, YIELD_CURVE, creditCurve); System.out.println(mat + "\tnon-IMM\t" + parSpread * TEN_THOUSAND); } } } @Test(enabled = false) public void bucketCS01test() { final double scale = ONE_BP * NOTIONAL; final double imm_coupon = COUPON * ONE_BP; final int nPillars = PILLAR_DATES.length; final CDSAnalytic[] pillarCDSs_IMM = new CDSAnalytic[nPillars]; final CDSAnalytic[] pillarCDSs_nonIMM = new CDSAnalytic[nPillars]; final CDSQuoteConvention[] pillar_quotes = new CDSQuoteConvention[nPillars]; final double[] pillar_qSpreads = new double[nPillars]; for (int i = 0; i < nPillars; i++) { pillarCDSs_IMM[i] = new CDSAnalytic(TRADE_DATE, EFFECTIVE_DATE, CASH_SETTLE_DATE, STARTDATE, PILLAR_DATES[i], PAY_ACC_ON_DEFAULT, PAYMENT_INTERVAL, STUB, PROCTECTION_START, RECOVERY); pillarCDSs_nonIMM[i] = new CDSAnalytic(TRADE_DATE, EFFECTIVE_DATE, CASH_SETTLE_DATE, STARTDATE, PILLAR_DATES[i], PAY_ACC_ON_DEFAULT, NON_IMM_TENOR, STUB, PROCTECTION_START, RECOVERY); pillar_qSpreads[i] = PILLAR_SPREADS[i] * ONE_BP; if (isIMMDate(PILLAR_DATES[i])) { pillar_quotes[i] = new QuotedSpread(imm_coupon, pillar_qSpreads[i]); } else { pillar_quotes[i] = new ParSpread(pillar_qSpreads[i]); } System.out.println(PILLAR_DATES[i] + "\t" + PILLAR_SPREADS[i]); } System.out.print("\n"); //The spreads at the pillar date (which are IMM dates) are quoted spreads - convert to equivalent par spread final double[] pillar_parSpreads = PUF_CONVERTER.quotedSpreadToParSpreads(pillarCDSs_IMM, imm_coupon, YIELD_CURVE, pillar_qSpreads); final int nIMMDates = IMM_DATES.length; final int nNonIMMDates = NON_IMM_DATES.length; final double[] parellelCS01_IMM = new double[nIMMDates]; final double[][] bucketedCS01_IMM_qSpread = new double[nIMMDates][]; final double[][] bucketedCS01_IMM_pSpread = new double[nIMMDates][]; final double[][] bucketedCS01_IMM_flatSpread = new double[nIMMDates][]; for (int i = 0; i < nIMMDates; i++) { final CDSAnalytic pricingCDS = new CDSAnalytic(TRADE_DATE, EFFECTIVE_DATE, CASH_SETTLE_DATE, STARTDATE, IMM_DATES[i], PAY_ACC_ON_DEFAULT, PAYMENT_INTERVAL, STUB, PROCTECTION_START, RECOVERY); final double[] flatSpreadTS = new double[nPillars]; Arrays.fill(flatSpreadTS, ((QuotedSpread) IMM_QUOTES.get(i)).getQuotedSpread()); parellelCS01_IMM[i] = CS01_CAL.parallelCS01(pricingCDS, IMM_QUOTES.get(i), YIELD_CURVE, ONE_BP); bucketedCS01_IMM_qSpread[i] = CS01_CAL.bucketedCS01FromPillarQuotes(pricingCDS, imm_coupon, YIELD_CURVE, pillarCDSs_IMM, pillar_quotes, ONE_BP); bucketedCS01_IMM_pSpread[i] = CS01_CAL.bucketedCS01FromParSpreads(pricingCDS, imm_coupon, YIELD_CURVE, pillarCDSs_IMM, pillar_parSpreads, ONE_BP, BumpType.ADDITIVE); bucketedCS01_IMM_flatSpread[i] = CS01_CAL.bucketedCS01FromParSpreads(pricingCDS, imm_coupon, YIELD_CURVE, pillarCDSs_IMM, flatSpreadTS, ONE_BP, BumpType.ADDITIVE); } final double[] parellelCS01_nonIMM_pSpread = new double[nNonIMMDates]; final double[] parellelCS01_nonIMM_qSpread = new double[nNonIMMDates]; final double[] parellelCS01_nonIMM_qSpread_SA = new double[nNonIMMDates]; final double[] parellelCS01_nonIMM_qSpread_bumped = new double[nNonIMMDates]; final double[][] bucketedCS01_nonIMM_pSpread = new double[nNonIMMDates][]; final double[][] bucketedCS01_nonIMM_qSpread = new double[nNonIMMDates][]; final double[][] bucketedCS01_nonIMM_qSpread_SA = new double[nNonIMMDates][]; final double[][] bucketedCS01_nonIMM_qSpread_bumped = new double[nNonIMMDates][]; for (int i = 0; i < nNonIMMDates; i++) { final CDSAnalytic pricingCDS = new CDSAnalytic(TRADE_DATE, EFFECTIVE_DATE, CASH_SETTLE_DATE, STARTDATE, NON_IMM_DATES[i], PAY_ACC_ON_DEFAULT, NON_IMM_TENOR, STUB, PROCTECTION_START, RECOVERY); final double coupon = NON_IMM_QUOTES.get(i).getCoupon(); //convert pillar spreads to par spreads first parellelCS01_nonIMM_pSpread[i] = CS01_CAL.parallelCS01FromParSpreads(pricingCDS, coupon, YIELD_CURVE, pillarCDSs_IMM, pillar_parSpreads, ONE_BP, BumpType.ADDITIVE); bucketedCS01_nonIMM_pSpread[i] = CS01_CAL.bucketedCS01FromParSpreads(pricingCDS, coupon, YIELD_CURVE, pillarCDSs_IMM, pillar_parSpreads, ONE_BP, BumpType.ADDITIVE); //treat the pillar quotes (which are quoted spreads) as par spreads parellelCS01_nonIMM_qSpread[i] = CS01_CAL.parallelCS01FromParSpreads(pricingCDS, coupon, YIELD_CURVE, pillarCDSs_IMM, pillar_qSpreads, ONE_BP, BumpType.ADDITIVE); bucketedCS01_nonIMM_qSpread[i] = CS01_CAL.bucketedCS01FromParSpreads(pricingCDS, coupon, YIELD_CURVE, pillarCDSs_IMM, pillar_qSpreads, ONE_BP, BumpType.ADDITIVE); //treat the pillar quotes (which are quoted spreads) as par spreads with SA coupon interval parellelCS01_nonIMM_qSpread_SA[i] = CS01_CAL.parallelCS01FromParSpreads(pricingCDS, coupon, YIELD_CURVE, pillarCDSs_nonIMM, pillar_qSpreads, ONE_BP, BumpType.ADDITIVE); bucketedCS01_nonIMM_qSpread_SA[i] = CS01_CAL.bucketedCS01FromParSpreads(pricingCDS, coupon, YIELD_CURVE, pillarCDSs_nonIMM, pillar_qSpreads, ONE_BP, BumpType.ADDITIVE); parellelCS01_nonIMM_qSpread_bumped[i] = CS01_CAL.parallelCS01FromPillarQuotes(pricingCDS, coupon, YIELD_CURVE, pillarCDSs_IMM, pillar_quotes, ONE_BP); bucketedCS01_nonIMM_qSpread_bumped[i] = CS01_CAL.bucketedCS01FromPillarQuotes(pricingCDS, coupon, YIELD_CURVE, pillarCDSs_IMM, pillar_quotes, ONE_BP); //debug } output("CS01 IMM quoted spreads", IMM_DATES, TENORS, bucketedCS01_IMM_qSpread, parellelCS01_IMM, scale); output("CS01 IMM par spreads", IMM_DATES, TENORS, bucketedCS01_IMM_pSpread, parellelCS01_IMM, scale); output("CS01 IMM flat spread term structure", IMM_DATES, TENORS, bucketedCS01_IMM_flatSpread, parellelCS01_IMM, scale); output("CS01 non-IMM par spreads", NON_IMM_DATES, TENORS, bucketedCS01_nonIMM_pSpread, parellelCS01_nonIMM_pSpread, scale); output("CS01 non-IMM quoted spreads", NON_IMM_DATES, TENORS, bucketedCS01_nonIMM_qSpread, parellelCS01_nonIMM_qSpread, scale); output("CS01 non-IMM quoted spreads (SA)", NON_IMM_DATES, TENORS, bucketedCS01_nonIMM_qSpread_SA, parellelCS01_nonIMM_qSpread_SA, scale); output("CS01 non-IMM quoted spreads bumped", NON_IMM_DATES, TENORS, bucketedCS01_nonIMM_qSpread_bumped, parellelCS01_nonIMM_qSpread_bumped, scale); } private void output(final String name, final LocalDate[] maturities, final Period[] pillars, final double[][] bCS01, final double[] pCS01, final double scale) { ArgumentChecker.notNull(name, "name"); ArgumentChecker.noNulls(maturities, "maturities"); ArgumentChecker.noNulls(pillars, "pillars"); ArgumentChecker.noNulls(bCS01, "bCS01"); ArgumentChecker.notEmpty(pCS01, "pCS01"); final int rows = maturities.length; ArgumentChecker.isTrue(rows == bCS01.length, "bCS01 length wrong"); ArgumentChecker.isTrue(rows == pCS01.length, "pCS01 length wrong"); final int columns = pillars.length; ArgumentChecker.isTrue(columns == bCS01[0].length, "bCS01 width wrong"); System.out.println(name); System.out.print("Maturity"); for (int j = 0; j < columns; j++) { System.out.print("\t" + pillars[j]); } System.out.print("\t\tSum\tParallel\n"); for (int i = 0; i < rows; i++) { System.out.print(maturities[i]); double sum = 0.0; for (int j = 0; j < columns; j++) { final double temp = bCS01[i][j] * scale; sum += temp; System.out.print("\t" + temp); } System.out.print("\t\t" + sum + "\t" + scale * pCS01[i] + "\n"); } System.out.print("\n"); } }