/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.impl.credit.isda; import java.time.LocalDate; import java.time.Period; import com.opengamma.strata.basics.schedule.StubConvention; import com.opengamma.strata.collect.ArgChecker; /** * Construct a credit curve that is consistent with the ISDA c code - i.e. the credit curve is piecewise constant in the (forward) * hazard rates, and agrees with ISDA (up to numerical round-off) for the same inputs */ public abstract class IsdaCompliantCreditCurveBuilder { private static final ArbitrageHandling DEFAULT_ARBITRAGE_HANDLING = ArbitrageHandling.Ignore; private static final AccrualOnDefaultFormulae DEFAULT_FORMULA = AccrualOnDefaultFormulae.ORIGINAL_ISDA; private final ArbitrageHandling _arbHandling; private final AccrualOnDefaultFormulae _formula; protected IsdaCompliantCreditCurveBuilder() { _arbHandling = DEFAULT_ARBITRAGE_HANDLING; _formula = DEFAULT_FORMULA; } protected IsdaCompliantCreditCurveBuilder(AccrualOnDefaultFormulae formula) { ArgChecker.notNull(formula, "formula"); _arbHandling = DEFAULT_ARBITRAGE_HANDLING; _formula = formula; } protected IsdaCompliantCreditCurveBuilder(AccrualOnDefaultFormulae formula, ArbitrageHandling arbHandling) { ArgChecker.notNull(formula, "formula"); ArgChecker.notNull(arbHandling, "arbHandling"); _arbHandling = arbHandling; _formula = formula; } public ArbitrageHandling getArbHanding() { return _arbHandling; } public AccrualOnDefaultFormulae getAccOnDefaultFormula() { return _formula; } /** * Bootstrapper the credit curve from a single market CDS quote. Obviously the resulting credit (hazard) * curve will be flat. * * @param calibrationCDS The single market CDS - this is the reference instruments used to build the credit curve * @param marketQuote The market quote of the CDS * @param yieldCurve The yield (or discount) curve * @return The credit curve */ public IsdaCompliantCreditCurve calibrateCreditCurve( CdsAnalytic calibrationCDS, CdsQuoteConvention marketQuote, IsdaCompliantYieldCurve yieldCurve) { double puf; double coupon; if (marketQuote instanceof CdsParSpread) { puf = 0.0; coupon = marketQuote.getCoupon(); } else if (marketQuote instanceof CdsQuotedSpread) { puf = 0.0; coupon = ((CdsQuotedSpread) marketQuote).getQuotedSpread(); } else if (marketQuote instanceof PointsUpFront) { PointsUpFront temp = (PointsUpFront) marketQuote; puf = temp.getPointsUpFront(); coupon = temp.getCoupon(); } else { throw new IllegalArgumentException("Unknown CDSQuoteConvention type " + marketQuote.getClass()); } return calibrateCreditCurve( new CdsAnalytic[] {calibrationCDS}, new double[] {coupon}, yieldCurve, new double[] {puf}); } /** * Bootstrapper the credit curve from a set of reference/calibration CDSs with market quotes * @param calibrationCDSs The market CDSs - these are the reference instruments used to build the credit curve * @param marketQuotes The market quotes of the CDSs * @param yieldCurve The yield (or discount) curve * @return The credit curve */ public IsdaCompliantCreditCurve calibrateCreditCurve( CdsAnalytic[] calibrationCDSs, CdsQuoteConvention[] marketQuotes, IsdaCompliantYieldCurve yieldCurve) { ArgChecker.noNulls(marketQuotes, "marketQuotes"); int n = marketQuotes.length; double[] coupons = new double[n]; double[] pufs = new double[n]; for (int i = 0; i < n; i++) { double[] temp = getStandardQuoteForm(calibrationCDSs[i], marketQuotes[i], yieldCurve); coupons[i] = temp[0]; pufs[i] = temp[1]; } return calibrateCreditCurve(calibrationCDSs, coupons, yieldCurve, pufs); } /** * Bootstrapper the credit curve from a single market CDS quote given as a par spread. Obviously the resulting credit (hazard) * curve will be flat. * @param cds The single market CDS - this is the reference instruments used to build the credit curve * @param parSpread The <b>fractional</b> par spread of the market CDS * @param yieldCurve The yield (or discount) curve * @return The credit curve */ public IsdaCompliantCreditCurve calibrateCreditCurve(CdsAnalytic cds, double parSpread, IsdaCompliantYieldCurve yieldCurve) { return calibrateCreditCurve(new CdsAnalytic[] {cds}, new double[] {parSpread}, yieldCurve, new double[1]); } /** * Bootstrapper the credit curve from a single market CDS quote given as points up-front (PUF) and a standard premium. * * @param cds The single market CDS - this is the reference instruments used to build the credit curve * @param premium The standard premium (coupon) as a fraction (these are 0.01 or 0.05 in North America) * @param yieldCurve The yield (or discount) curve * @param pointsUpfront points up-front as a fraction of notional * @return The credit curve */ public IsdaCompliantCreditCurve calibrateCreditCurve( CdsAnalytic cds, double premium, IsdaCompliantYieldCurve yieldCurve, double pointsUpfront) { return calibrateCreditCurve(new CdsAnalytic[] {cds}, new double[] {premium}, yieldCurve, new double[] {pointsUpfront}); } /** * Bootstrapper the credit curve from a set of reference/calibration CDSs quoted with par spreads. * * @param calibrationCDSs The market CDSs - these are the reference instruments used to build the credit curve * @param parSpreads The <b>fractional</b> par spreads of the market CDSs * @param yieldCurve The yield (or discount) curve * @return The credit curve */ public IsdaCompliantCreditCurve calibrateCreditCurve( CdsAnalytic[] calibrationCDSs, double[] parSpreads, IsdaCompliantYieldCurve yieldCurve) { ArgChecker.notNull(calibrationCDSs, "null CDS"); int n = calibrationCDSs.length; double[] pointsUpfront = new double[n]; return calibrateCreditCurve(calibrationCDSs, parSpreads, yieldCurve, pointsUpfront); } /** * Bootstrapper the credit curve from a set of reference/calibration CDSs quoted with points up-front and standard premiums . * * @param calibrationCDSs The market CDSs - these are the reference instruments used to build the credit curve * @param premiums The standard premiums (coupons) as fractions (these are 0.01 or 0.05 in North America) * @param yieldCurve The yield (or discount) curve * @param pointsUpfront points up-front as fractions of notional * @return The credit curve */ public abstract IsdaCompliantCreditCurve calibrateCreditCurve( CdsAnalytic[] calibrationCDSs, double[] premiums, IsdaCompliantYieldCurve yieldCurve, double[] pointsUpfront); /** * Bootstrapper the credit curve from a single CDS, by making it have zero clean price. * Obviously the resulting credit (hazard) curve will be flat. * * @param tradeDate The 'current' date * @param stepinDate Date when party assumes ownership. This is normally today + 1 (T+1). Aka assignment date or effective date. * @param valueDate The valuation date. The date that values are PVed to. Is is normally today + 3 business days. Aka cash-settle date. * @param startDate The protection start date. If protectStart = true, then protections starts at the beginning of the day, otherwise it * is at the end. * @param endDate The maturity (or end of protection) of the CDS * @param fractionalParSpread - the (fractional) coupon that makes the CDS worth par (i.e. zero clean price) * @param payAccOnDefault Is the accrued premium paid in the event of a default * @param tenor The nominal step between premium payments (e.g. 3 months, 6 months). * @param stubType the stub convention * @param protectStart Does protection start at the beginning of the day * @param yieldCurve Curve from which payments are discounted * @param recoveryRate the recovery rate * @return The credit curve */ public IsdaCompliantCreditCurve calibrateCreditCurve( LocalDate tradeDate, LocalDate stepinDate, LocalDate valueDate, LocalDate startDate, LocalDate endDate, double fractionalParSpread, boolean payAccOnDefault, Period tenor, StubConvention stubType, boolean protectStart, IsdaCompliantYieldCurve yieldCurve, double recoveryRate) { return calibrateCreditCurve( tradeDate, stepinDate, valueDate, startDate, new LocalDate[] {endDate}, new double[] {fractionalParSpread}, payAccOnDefault, tenor, stubType, protectStart, yieldCurve, recoveryRate); } /** * Bootstrapper the credit curve, by making each market CDS in turn have zero clean price. * * @param tradeDate The 'current' date * @param stepinDate Date when party assumes ownership. This is normally today + 1 (T+1). Aka assignment date or effective date. * @param valueDate The valuation date. The date that values are PVed to. Is is normally today + 3 business days. Aka cash-settle date. * @param startDate The protection start date. If protectStart = true, then protections starts at the beginning of the day, otherwise it * is at the end. * @param endDates The maturities (or end of protection) of each of the CDSs - must be ascending * @param fractionalParSpreads - the (fractional) coupon that makes each CDS worth par (i.e. zero clean price) * @param payAccOnDefault Is the accrued premium paid in the event of a default * @param tenor The nominal step between premium payments (e.g. 3 months, 6 months). * @param stubType the stub convention * @param protectStart Does protection start at the beginning of the day * @param yieldCurve Curve from which payments are discounted * @param recoveryRate the recovery rate * @return The credit curve */ public IsdaCompliantCreditCurve calibrateCreditCurve( LocalDate tradeDate, LocalDate stepinDate, LocalDate valueDate, LocalDate startDate, LocalDate[] endDates, double[] fractionalParSpreads, boolean payAccOnDefault, Period tenor, StubConvention stubType, boolean protectStart, IsdaCompliantYieldCurve yieldCurve, double recoveryRate) { ArgChecker.notNull(endDates, "null endDates"); ArgChecker.notEmpty(fractionalParSpreads, "no or null couponRates"); int n = endDates.length; ArgChecker.isTrue(n == fractionalParSpreads.length, "length of couponRates does not match endDates"); CdsAnalytic[] cds = new CdsAnalytic[n]; for (int i = 0; i < n; i++) { cds[i] = new CdsAnalytic( tradeDate, stepinDate, valueDate, startDate, endDates[i], payAccOnDefault, tenor, stubType, protectStart, recoveryRate); } return calibrateCreditCurve(cds, fractionalParSpreads, yieldCurve); } /** * Put any CDS market quote into the form needed for the curve builder, * namely coupon and points up-front (which can be zero). * * @param calibrationCDS * @param marketQuote * @param yieldCurve * @return The market quotes in the form required by the curve builder */ private double[] getStandardQuoteForm( CdsAnalytic calibrationCDS, CdsQuoteConvention marketQuote, IsdaCompliantYieldCurve yieldCurve) { AnalyticCdsPricer pricer = new AnalyticCdsPricer(_formula); double[] res = new double[2]; if (marketQuote instanceof CdsParSpread) { res[0] = marketQuote.getCoupon(); } else if (marketQuote instanceof CdsQuotedSpread) { CdsQuotedSpread temp = (CdsQuotedSpread) marketQuote; double coupon = temp.getCoupon(); double qSpread = temp.getQuotedSpread(); IsdaCompliantCreditCurve cc = calibrateCreditCurve( new CdsAnalytic[] {calibrationCDS}, new double[] {qSpread}, yieldCurve, new double[1]); res[0] = coupon; res[1] = pricer.pv(calibrationCDS, yieldCurve, cc, coupon, CdsPriceType.CLEAN); } else if (marketQuote instanceof PointsUpFront) { PointsUpFront temp = (PointsUpFront) marketQuote; res[0] = temp.getCoupon(); res[1] = temp.getPointsUpFront(); } else { throw new IllegalArgumentException("Unknown CDSQuoteConvention type " + marketQuote.getClass()); } return res; } /** * How should any arbitrage in the input data be handled */ public enum ArbitrageHandling { /** * If the market data has arbitrage, the curve will still build, but the survival probability will not be monotonically * decreasing (equivalently, some forward hazard rates will be negative) */ Ignore, /** * An exception is throw if an arbitrage is found */ Fail, /** * If a particular spread implies a negative forward hazard rate, the hazard rate is set to zero, and the calibration * continues. The resultant curve will of course not exactly reprice the input CDSs, but will find new spreads that * just avoid arbitrage. */ ZeroHazardRate } }