/** * 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.DayCount; import com.opengamma.strata.basics.date.HolidayCalendar; import com.opengamma.strata.basics.date.Tenor; import com.opengamma.strata.basics.schedule.StubConvention; import com.opengamma.strata.collect.ArgChecker; /** * */ public class MultiCdsAnalytic { private final double _lgd; private final boolean _payAccOnDefault; private final CdsCoupon[] _standardCoupons; //these will be common across many CDSs private final CdsCoupon[] _terminalCoupons; //these are the final coupons for each CDS private final double _accStart; private final double _effectiveProtectionStart; private final double _cashSettlementTime; private final double[] _protectionEnd; private final double[] _accrued; private final int[] _accruedDays; private final int _totalPayments; private final int _nMaturities; private final int[] _matIndexToPayments; /** * Set up a strip of increasing maturity CDSs that have some coupons in common. The trade date, step-in date and valuation date and * accrual start date are all common, as is the payment frequency. The maturities are expressed as integer multiples of the * payment interval from a reference date (the next IMM date after the trade date for standard CDSs) - this guarantees that premiums * will be the same across several CDSs. * @param tradeDate The trade date * @param stepinDate (aka Protection Effective sate 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 cashSettlementDate The cash settlement date. The date that values are PVed to. Is is normally today + 3 business days. * @param accStartDate Accrual Start Date. 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 maturityReferanceDate A reference date that maturities are measured from. For standard CDSSs, this is the next IMM date after * the trade date, so the actually maturities will be some fixed periods after this. * @param maturityIndexes The maturities are fixed integer multiples of the payment interval, so for 6M, 1Y and 2Y tenors with a 3M * payment interval, would require 2, 4, and 8 as the indices * @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 and it is not recommended to change this) */ public MultiCdsAnalytic( LocalDate tradeDate, LocalDate stepinDate, LocalDate cashSettlementDate, LocalDate accStartDate, LocalDate maturityReferanceDate, int[] maturityIndexes, boolean payAccOnDefault, Tenor 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(cashSettlementDate, "cashSettlementDate"); ArgChecker.notNull(accStartDate, "accStartDate"); ArgChecker.notNull(maturityReferanceDate, "maturityReferanceDate"); ArgChecker.notNull(paymentInterval, "tenor"); ArgChecker.notNull(stubType, "stubType"); ArgChecker.notNull(businessdayAdjustmentConvention, "businessdayAdjustmentConvention"); ArgChecker.notNull(accrualDayCount, "accuralDayCount"); ArgChecker.notNull(curveDayCount, "curveDayCount"); ArgChecker.isFalse(cashSettlementDate.isBefore(tradeDate), "Require valueDate >= today"); ArgChecker.isFalse(stepinDate.isBefore(tradeDate), "Require stepin >= today"); ArgChecker.notEmpty(maturityIndexes, "maturityIndexes"); _nMaturities = maturityIndexes.length; ArgChecker.isTrue(maturityIndexes[0] >= 0, "first maturity index < 0"); for (int i = 1; i < _nMaturities; i++) { ArgChecker.isTrue(maturityIndexes[i] > maturityIndexes[i - 1], "maturityIndexes not ascending"); } _payAccOnDefault = payAccOnDefault; _accStart = accStartDate.isBefore(tradeDate) ? -curveDayCount.yearFraction(accStartDate, tradeDate) : curveDayCount.yearFraction(tradeDate, accStartDate); LocalDate temp = stepinDate.isAfter(accStartDate) ? stepinDate : accStartDate; LocalDate effectiveStartDate = protectStart ? temp.minusDays(1) : temp; _cashSettlementTime = curveDayCount.yearFraction(tradeDate, cashSettlementDate); _effectiveProtectionStart = curveDayCount.yearFraction(tradeDate, effectiveStartDate); _lgd = 1 - recoveryRate; LocalDate[] maturities = new LocalDate[_nMaturities]; _protectionEnd = new double[_nMaturities]; Period period = paymentInterval.getPeriod(); for (int i = 0; i < _nMaturities; i++) { Period tStep = period.multipliedBy(maturityIndexes[i]); maturities[i] = maturityReferanceDate.plus(tStep); _protectionEnd[i] = curveDayCount.yearFraction(tradeDate, maturities[i]); } IsdaPremiumLegSchedule fullPaymentSchedule = new IsdaPremiumLegSchedule(accStartDate, maturities[_nMaturities - 1], period, stubType, businessdayAdjustmentConvention, calendar, protectStart); //remove already expired coupons IsdaPremiumLegSchedule paymentSchedule = fullPaymentSchedule.truncateSchedule(stepinDate); int couponOffset = fullPaymentSchedule.getNumPayments() - paymentSchedule.getNumPayments(); _totalPayments = paymentSchedule.getNumPayments(); _standardCoupons = new CdsCoupon[_totalPayments - 1]; for (int i = 0; i < (_totalPayments - 1); i++) { //The last coupon is actually a terminal coupon, so not included here _standardCoupons[i] = new CdsCoupon( tradeDate, paymentSchedule.getAccPaymentDateTriplet(i), protectStart, accrualDayCount, curveDayCount); } //find the terminal coupons _terminalCoupons = new CdsCoupon[_nMaturities]; _matIndexToPayments = new int[_nMaturities]; _accruedDays = new int[_nMaturities]; _accrued = new double[_nMaturities]; long secondJulianDate = stepinDate.getLong(JulianFields.MODIFIED_JULIAN_DAY); for (int i = 0; i < _nMaturities; i++) { int index = fullPaymentSchedule.getNominalPaymentDateIndex(maturities[i]); if (index < 0) { throw new IllegalStateException("should never see this. There is a bug in the code."); } //maturity is unadjusted, but if protectionStart=true (i.e. standard CDS) there is effectively an extra day of accrued interest LocalDate accEnd = protectStart ? maturities[i].plusDays(1) : maturities[i]; _terminalCoupons[i] = new CdsCoupon( tradeDate, fullPaymentSchedule.getAccStartDate(index), accEnd, fullPaymentSchedule.getPaymentDate(index), protectStart, accrualDayCount, curveDayCount); _matIndexToPayments[i] = index - couponOffset; //This will only matter for the edge case when the trade date is 1 day before maturity LocalDate tDate2 = _matIndexToPayments[i] < 0 ? fullPaymentSchedule.getAccStartDate(couponOffset - 1) : paymentSchedule.getAccStartDate(0); long firstJulianDate = tDate2.getLong(JulianFields.MODIFIED_JULIAN_DAY); _accruedDays[i] = secondJulianDate > firstJulianDate ? (int) (secondJulianDate - firstJulianDate) : 0; _accrued[i] = tDate2.isBefore(stepinDate) ? accrualDayCount.yearFraction(tDate2, stepinDate) : 0.0; } } private MultiCdsAnalytic( double lgd, boolean payAccOnDefault, CdsCoupon[] standardCoupons, CdsCoupon[] terminalCoupons, double accStart, double effectiveProtectionStart, double valuationTime, double[] protectionEnd, double[] accrued, int[] accruedDays, int totalPayments, int nMaturities, int[] matIndexToPayments) { _lgd = lgd; _payAccOnDefault = payAccOnDefault; _standardCoupons = standardCoupons; _terminalCoupons = terminalCoupons; _accStart = accStart; _effectiveProtectionStart = effectiveProtectionStart; _cashSettlementTime = valuationTime; _protectionEnd = protectionEnd; _accrued = accrued; _accruedDays = accruedDays; _totalPayments = totalPayments; _nMaturities = nMaturities; _matIndexToPayments = matIndexToPayments; } public int getNumMaturities() { return _nMaturities; } /** * This is the number of payments for the largest maturity CDS * @return totalPayments */ public int getTotalPayments() { return _totalPayments; } /** * get payment index for a particular maturity index. The standard coupon is one less than this * @param matIndex maturity index (0 for first maturity, etc) * @return payment index */ public int getPaymentIndexForMaturity(int matIndex) { return _matIndexToPayments[matIndex]; } /** * Gets the payAccOnDefault. * @return the payAccOnDefault */ public boolean isPayAccOnDefault() { return _payAccOnDefault; } /** * The loss-given-default. This is 1 - recovery rate * @return the LGD */ public double getLGD() { return _lgd; } public MultiCdsAnalytic withRecoveryRate(double recovery) { return new MultiCdsAnalytic( 1 - recovery, _payAccOnDefault, _standardCoupons, _terminalCoupons, _accStart, _effectiveProtectionStart, _cashSettlementTime, _protectionEnd, _accrued, _accruedDays, _totalPayments, _nMaturities, _matIndexToPayments); } /** * 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 at the given index (zero based). * @param matIndex the index * @return the protectionEnd */ public double getProtectionEnd(int matIndex) { return _protectionEnd[matIndex]; } /** * Get the coupon for the CDS at the given index (zero based). * @param matIndex the index * @return A coupon */ public CdsCoupon getTerminalCoupon(int matIndex) { return _terminalCoupons[matIndex]; } /** Get the standard (i.e. not the or terminal coupon of a CDS) at the given index * @param index the index * @return a coupon */ public CdsCoupon getStandardCoupon(int index) { return _standardCoupons[index]; } public CdsCoupon[] getStandardCoupons() { return _standardCoupons; } /** * 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) for the CDS at the given index (zero based). * @param matIndex the index * @return the accrued premium per unit of (fractional) spread (and unit of notional) */ public double getAccruedPremiumPerUnitSpread(int matIndex) { return _accrued[matIndex]; } /** * Gets the accrued premium per unit of notional for the CDS at the given index (zero based). * @param matIndex the index * @param fractionalSpread The <b>fraction</b> spread * @return the accrued premium */ public double getAccruedPremium(int matIndex, double fractionalSpread) { return _accrued[matIndex] * fractionalSpread; } /** * Get the number of days of accrued premium for the CDS at the given index (zero based) * @param matIndex the index * @return Accrued days */ public int getAccuredDays(int matIndex) { return _accruedDays[matIndex]; } @Override public int hashCode() { int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(_accStart); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + Arrays.hashCode(_accrued); result = prime * result + Arrays.hashCode(_accruedDays); 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 + Arrays.hashCode(_matIndexToPayments); result = prime * result + _nMaturities; result = prime * result + (_payAccOnDefault ? 1231 : 1237); result = prime * result + Arrays.hashCode(_protectionEnd); result = prime * result + Arrays.hashCode(_standardCoupons); result = prime * result + Arrays.hashCode(_terminalCoupons); result = prime * result + _totalPayments; temp = Double.doubleToLongBits(_cashSettlementTime); 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; } MultiCdsAnalytic other = (MultiCdsAnalytic) obj; if (Double.doubleToLongBits(_accStart) != Double.doubleToLongBits(other._accStart)) { return false; } if (!Arrays.equals(_accrued, other._accrued)) { return false; } if (!Arrays.equals(_accruedDays, other._accruedDays)) { return false; } if (Double.doubleToLongBits(_effectiveProtectionStart) != Double.doubleToLongBits(other._effectiveProtectionStart)) { return false; } if (Double.doubleToLongBits(_lgd) != Double.doubleToLongBits(other._lgd)) { return false; } if (!Arrays.equals(_matIndexToPayments, other._matIndexToPayments)) { return false; } if (_nMaturities != other._nMaturities) { return false; } if (_payAccOnDefault != other._payAccOnDefault) { return false; } if (!Arrays.equals(_protectionEnd, other._protectionEnd)) { return false; } if (!Arrays.equals(_standardCoupons, other._standardCoupons)) { return false; } if (!Arrays.equals(_terminalCoupons, other._terminalCoupons)) { return false; } if (_totalPayments != other._totalPayments) { return false; } if (Double.doubleToLongBits(_cashSettlementTime) != Double.doubleToLongBits(other._cashSettlementTime)) { return false; } return true; } }