/** * 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 java.time.temporal.JulianFields; import java.util.Arrays; import com.opengamma.strata.basics.date.BusinessDayConvention; import com.opengamma.strata.basics.date.BusinessDayConventions; import com.opengamma.strata.basics.date.DayCount; import com.opengamma.strata.basics.date.DayCounts; import com.opengamma.strata.basics.date.HolidayCalendar; import com.opengamma.strata.basics.date.HolidayCalendars; import com.opengamma.strata.basics.schedule.StubConvention; import com.opengamma.strata.collect.ArgChecker; /** * This converts and stores all the date logic as doubles for CDS pricing on a particular date.<p> * For convenient ways to generate sets of CDSs * @see CdsAnalyticFactory */ public class CdsAnalytic { private static final HolidayCalendar DEFAULT_CALENDAR = HolidayCalendars.SAT_SUN; private static final BusinessDayConvention FOLLOWING = BusinessDayConventions.FOLLOWING; /** Curve daycount generally fixed to Act/365 in ISDA */ private static final DayCount ACT_365 = DayCounts.ACT_365F; private static final DayCount ACT_360 = DayCounts.ACT_360; private final double _lgd; private final boolean _payAccOnDefault; private final CdsCoupon[] _coupons; //important time measures for the curve zero time ('now') using the curve DCC private final double _accStart; //the start of the first accrual period (usually < 0) private final double _effectiveProtectionStart; //when protection starts (usually zero unless forward starting CDS) private final double _cashSettlementTime; //Time when CDS is cash settled (valuation time defaults to this) private final double _protectionEnd; //when the CDS ends private final double _accrued; private final int _accruedDays; /** * Generates an analytic description of a CDS trade on a particular date. * This can then be passed to a analytic CDS pricer. * <p> * This using a weekend only calendar with a following convention. ACT/360 is used for accrual * and ACT/365 to convert payment dates to year-fractions (doubles). * * @param tradeDate the trade date, this is the date other times are measured from (i.e. t = 0) * @param stepinDate (aka Protection Effective date or assignment date). Date when party assumes ownership. * This is usually T+1. This is when protection (and risk) starts in terms of the model. Note, this is * sometimes just called the Effective Date, however this can cause confusion with the legal effective * date which is T-60 or T-90. * @param valueDate the valuation date. The date that values are PVed to. * Is is normally today + 3 business days. Aka cash-settle date. * @param accStartDate this is when the CDS nominally starts in terms of premium payments. i.e. the number * of days in the first period (and thus the amount of the first premium payment) is counted from this date. * @param endDate (aka maturity date) This is when the contract expires and protection ends - * any default after this date does not trigger a payment. (the protection ends at end of day) * @param payAccOnDefault is the accrued premium paid in the event of a default * @param paymentInterval the nominal step between premium payments (e.g. 3 months, 6 months). * @param stubType the stub convention * @param protectStart if protectStart = true, then protections starts at the beginning of the day, otherwise it is at the end. * @param recoveryRate the recovery rate */ public CdsAnalytic( LocalDate tradeDate, LocalDate stepinDate, LocalDate valueDate, LocalDate accStartDate, LocalDate endDate, boolean payAccOnDefault, Period paymentInterval, StubConvention stubType, boolean protectStart, double recoveryRate) { this(tradeDate, stepinDate, valueDate, accStartDate, endDate, payAccOnDefault, paymentInterval, stubType, protectStart, recoveryRate, FOLLOWING, DEFAULT_CALENDAR, ACT_360, ACT_365); } /** * Generates an analytic description of a CDS trade on a particular date. * This can then be passed to a analytic CDS pricer. * <p> * This using a weekend only calendar with a following convention. ACT/360 is used for accrual * and ACT/365 to convert payment dates to year-fractions (doubles). * <p> * Note this uses a curve daycount of ACT/365 to match the ISDA methodology. * * @param tradeDate the trade date, this is the date other times are measured from (i.e. t = 0) * @param stepinDate (aka Protection Effective date or assignment date). Date when party assumes ownership. * This is usually T+1. This is when protection (and risk) starts in terms of the model. Note, this is * sometimes just called the Effective Date, however this can cause confusion with the legal effective * date which is T-60 or T-90. * @param valueDate the valuation date. The date that values are PVed to. * Is is normally today + 3 business days. Aka cash-settle date. * @param accStartDate this is when the CDS nominally starts in terms of premium payments. i.e. the number * of days in the first period (and thus the amount of the first premium payment) is counted from this date. * @param endDate (aka maturity date) This is when the contract expires and protection ends - * any default after this date does not trigger a payment. (the protection ends at end of day) * @param payAccOnDefault is the accrued premium paid in the event of a default * @param paymentInterval the nominal step between premium payments (e.g. 3 months, 6 months). * @param stubType the stub convention * @param protectStart if protectStart = true, then protections starts at the beginning of the day, otherwise it is at the end. * @param recoveryRate the recovery rate * @param businessdayAdjustmentConvention how are adjustments for non-business days made * @param calendar the holiday calendar defining what is a non-business day * @param accrualDayCount the day count used for accrual */ public CdsAnalytic( LocalDate tradeDate, LocalDate stepinDate, LocalDate valueDate, LocalDate accStartDate, LocalDate endDate, boolean payAccOnDefault, Period paymentInterval, StubConvention stubType, boolean protectStart, double recoveryRate, BusinessDayConvention businessdayAdjustmentConvention, HolidayCalendar calendar, DayCount accrualDayCount) { this(tradeDate, stepinDate, valueDate, accStartDate, endDate, payAccOnDefault, paymentInterval, stubType, protectStart, recoveryRate, businessdayAdjustmentConvention, calendar, accrualDayCount, ACT_365); } /** * Generates an analytic description of a CDS trade on a particular date. * This can then be passed to a analytic CDS pricer. * * @param tradeDate the trade date, this is the date other times are measured from (i.e. t = 0) * @param stepinDate (aka Protection Effective date or assignment date). Date when party assumes ownership. * This is usually T+1. This is when protection (and risk) starts in terms of the model. Note, this is * sometimes just called the Effective Date, however this can cause confusion with the legal effective * date which is T-60 or T-90. * @param valueDate the valuation date. The date that values are PVed to. * Is is normally today + 3 business days. Aka cash-settle date. * @param accStartDate this is when the CDS nominally starts in terms of premium payments. i.e. the number * of days in the first period (and thus the amount of the first premium payment) is counted from this date. * @param endDate (aka maturity date) This is when the contract expires and protection ends - * any default after this date does not trigger a payment. (the protection ends at end of day) * @param payAccOnDefault is the accrued premium paid in the event of a default * @param paymentInterval the nominal step between premium payments (e.g. 3 months, 6 months). * @param stubType the stub convention * @param protectStart if protectStart = true, then protections starts at the beginning of the day, otherwise it is at the end. * @param recoveryRate the recovery rate * @param businessdayAdjustmentConvention How are adjustments for non-business days made * @param calendar HolidayCalendar defining what is a non-business day * @param accrualDayCount Day count used for accrual * @param curveDayCount Day count used on curve (NOTE ISDA uses ACT/365 (fixed) and it is not recommended to change this) */ public CdsAnalytic( LocalDate tradeDate, LocalDate stepinDate, LocalDate valueDate, LocalDate accStartDate, LocalDate endDate, boolean payAccOnDefault, Period paymentInterval, StubConvention stubType, boolean protectStart, double recoveryRate, BusinessDayConvention businessdayAdjustmentConvention, HolidayCalendar calendar, DayCount accrualDayCount, DayCount curveDayCount) { ArgChecker.notNull(tradeDate, "tradeDate"); ArgChecker.notNull(stepinDate, "stepinDate"); ArgChecker.notNull(valueDate, "valueDate"); ArgChecker.notNull(accStartDate, "accStartDate"); ArgChecker.notNull(endDate, "endDate"); ArgChecker.notNull(paymentInterval, "tenor"); ArgChecker.notNull(stubType, "stubType"); ArgChecker.notNull(businessdayAdjustmentConvention, "businessdayAdjustmentConvention"); ArgChecker.notNull(accrualDayCount, "accuralDayCount"); ArgChecker.notNull(curveDayCount, "curveDayCount"); ArgChecker.inRangeInclusive(recoveryRate, 0d, 1d, "recoveryRate"); ArgChecker.isFalse(valueDate.isBefore(tradeDate), "Require valueDate >= today"); ArgChecker.isFalse(stepinDate.isBefore(tradeDate), "Require stepin >= today"); //TODO should not allow the accrual start to be after the stepin (protection start), // since this is 'free' protection. Currently some tests have this and need to be changed ArgChecker.isFalse(tradeDate.isAfter(endDate), "CDS has expired"); _payAccOnDefault = payAccOnDefault; LocalDate startDate = stepinDate.isAfter(accStartDate) ? stepinDate : accStartDate; LocalDate effectiveStartDate = protectStart ? startDate.minusDays(1) : startDate; _accStart = accStartDate.isBefore(tradeDate) ? -curveDayCount.yearFraction(accStartDate, tradeDate) : curveDayCount.yearFraction(tradeDate, accStartDate); _cashSettlementTime = curveDayCount.yearFraction(tradeDate, valueDate); _effectiveProtectionStart = effectiveStartDate.isBefore(tradeDate) ? -curveDayCount.yearFraction(effectiveStartDate, tradeDate) : curveDayCount.yearFraction(tradeDate, effectiveStartDate); _protectionEnd = curveDayCount.yearFraction(tradeDate, endDate); _lgd = 1 - recoveryRate; IsdaPremiumLegSchedule fullPaymentSchedule = new IsdaPremiumLegSchedule( accStartDate, endDate, paymentInterval, stubType, businessdayAdjustmentConvention, calendar, protectStart); IsdaPremiumLegSchedule paymentSchedule = IsdaPremiumLegSchedule.truncateSchedule(stepinDate, fullPaymentSchedule); _coupons = CdsCoupon.makeCoupons(tradeDate, paymentSchedule, protectStart, accrualDayCount, curveDayCount); LocalDate accStart = paymentSchedule.getAccStartDate(0); long firstJulianDate = accStart.getLong(JulianFields.MODIFIED_JULIAN_DAY); long secondJulianDate = stepinDate.getLong(JulianFields.MODIFIED_JULIAN_DAY); _accruedDays = secondJulianDate > firstJulianDate ? (int) (secondJulianDate - firstJulianDate) : 0; _accrued = accStart.isBefore(stepinDate) ? accrualDayCount.yearFraction(accStart, stepinDate) : 0.0; } public int getNumPayments() { return _coupons.length; } /** * Gets the payAccOnDefault. * @return the payAccOnDefault */ public boolean isPayAccOnDefault() { return _payAccOnDefault; } /** * Gets the protectionFromStartOfDay. * @return the protectionFromStartOfDay */ /** * The loss-given-default. This is 1 - recovery rate * @return the LGD */ public double getLGD() { return _lgd; } /** * Gets year fraction (according to curve DCC) between the trade date and the cash-settle date * @return the CashSettleTime */ public double getCashSettleTime() { return _cashSettlementTime; } /** * Year fraction (according to curve DCC) from trade date to accrual start date. * This will be negative for spot starting CDS, but will be positive for forward starting CDS. * @return accrual start year-fraction. */ public double getAccStart() { return _accStart; } /** * Year fraction (according to curve DCC) from trade date to effective protection start date. * The effective protection start date is the greater of the accrual start date * and the step-in date; if protection is from start of day, this is adjusted back one * day - so for a standard CDS it is the trade date. * @return the effectiveProtectionStart */ public double getEffectiveProtectionStart() { return _effectiveProtectionStart; } /** * Year fraction (according to curve DCC) from trade date to the maturity of the CDS. * @return the protectionEnd */ public double getProtectionEnd() { return _protectionEnd; } /** * Get all the coupons on the premium leg. * @return the coupons. */ public CdsCoupon[] getCoupons() { return _coupons; } /** * get a coupon at a particular index (zero based). * @param index the index * @return a coupon */ public CdsCoupon getCoupon(int index) { return _coupons[index]; } /** * Gets the accrued premium per unit of (fractional) spread - i.e. if the quoted spread * (coupon) was 500bps the actual accrued premium paid would be this times 0.05 * @return the accrued premium per unit of (fractional) spread (and unit of notional) */ public double getAccruedYearFraction() { return _accrued; } /** * Gets the accrued premium per unit of notional * @param fractionalSpread The <b>fraction</b> spread * @return the accrued premium */ public double getAccruedPremium(double fractionalSpread) { return _accrued * fractionalSpread; } /** * Get the number of days of accrued premium. * @return Accrued days */ public int getAccuredDays() { return _accruedDays; } private CdsAnalytic( double lgd, CdsCoupon[] coupons, double start, double effectiveProtectionStart, double protectionEnd, double valuationTime, boolean payAccOnDefault, double accrued, int accruedDays) { ArgChecker.noNulls(coupons, "coupons"); _accStart = start; _effectiveProtectionStart = effectiveProtectionStart; _protectionEnd = protectionEnd; _cashSettlementTime = valuationTime; _lgd = lgd; _coupons = coupons; _payAccOnDefault = payAccOnDefault; _accrued = accrued; _accruedDays = accruedDays; } /** * produce a copy of the CDS with a new recovery rate * @param recoveryRate The recovery rate * @return a new CDS */ public CdsAnalytic withRecoveryRate(double recoveryRate) { ArgChecker.inRangeInclusive(recoveryRate, 0d, 1d, "recoveryRate"); return new CdsAnalytic( 1 - recoveryRate, _coupons, _accStart, _effectiveProtectionStart, _protectionEnd, _cashSettlementTime, _payAccOnDefault, _accrued, _accruedDays); } /** * Generate a CDS with a time offset. The main use is to produce a forward starting CDS from a forward CDS. * A forward CDS is a CDS with a future trade date viewed from that date (i.e. it is a spot CDS view * from the future trade date). A forward starting CDS is a CDS (seen today) that starts on some future date. * The effect of this operation is to shift all time-to based numbers by offset. * * @param offset The offset (in years) - must be positive * @return an offset (i.e. forward starting) CDS */ public CdsAnalytic withOffset(double offset) { ArgChecker.isTrue(offset >= 0, "offset must be positive"); if (offset == 0.0) { return this; } int n = getNumPayments(); CdsCoupon[] coupons = new CdsCoupon[n]; for (int i = 0; i < n; i++) { coupons[i] = _coupons[i].withOffset(offset); } return new CdsAnalytic( _lgd, coupons, _accStart + offset, _effectiveProtectionStart + offset, _protectionEnd + offset, _cashSettlementTime + offset, _payAccOnDefault, _accrued, _accruedDays); } //------------------------------------------------------------------------- @Override public int hashCode() { int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(_accStart); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(_accrued); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + _accruedDays; temp = Double.doubleToLongBits(_cashSettlementTime); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + Arrays.hashCode(_coupons); temp = Double.doubleToLongBits(_effectiveProtectionStart); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(_lgd); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + (_payAccOnDefault ? 1231 : 1237); temp = Double.doubleToLongBits(_protectionEnd); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } CdsAnalytic other = (CdsAnalytic) obj; if (Double.doubleToLongBits(_accStart) != Double.doubleToLongBits(other._accStart)) { return false; } if (Double.doubleToLongBits(_accrued) != Double.doubleToLongBits(other._accrued)) { return false; } if (_accruedDays != other._accruedDays) { return false; } if (Double.doubleToLongBits(_cashSettlementTime) != Double.doubleToLongBits(other._cashSettlementTime)) { return false; } if (!Arrays.equals(_coupons, other._coupons)) { return false; } if (Double.doubleToLongBits(_effectiveProtectionStart) != Double.doubleToLongBits(other._effectiveProtectionStart)) { return false; } if (Double.doubleToLongBits(_lgd) != Double.doubleToLongBits(other._lgd)) { return false; } if (_payAccOnDefault != other._payAccOnDefault) { return false; } if (Double.doubleToLongBits(_protectionEnd) != Double.doubleToLongBits(other._protectionEnd)) { return false; } return true; } }