/**
* 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 java.util.Arrays;
import org.threeten.bp.LocalDate;
import org.threeten.bp.Period;
import org.threeten.bp.temporal.JulianFields;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.financial.convention.businessday.BusinessDayConvention;
import com.opengamma.financial.convention.calendar.Calendar;
import com.opengamma.financial.convention.daycount.DayCount;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.time.Tenor;
/**
*
*/
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 Options are FRONTSHORT, FRONTLONG, BACKSHORT, BACKLONG or NONE
* - <b>Note</b> in this code NONE is not allowed
* @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 Calendar 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(final LocalDate tradeDate, final LocalDate stepinDate, final LocalDate cashSettlementDate, final LocalDate accStartDate, final LocalDate maturityReferanceDate,
final int[] maturityIndexes, final boolean payAccOnDefault, final Tenor paymentInterval, final StubType stubType, final boolean protectStart, final double recoveryRate,
final BusinessDayConvention businessdayAdjustmentConvention, final Calendar calendar, final DayCount accrualDayCount, final DayCount curveDayCount) {
ArgumentChecker.notNull(tradeDate, "tradeDate");
ArgumentChecker.notNull(stepinDate, "stepinDate");
ArgumentChecker.notNull(cashSettlementDate, "cashSettlementDate");
ArgumentChecker.notNull(accStartDate, "accStartDate");
ArgumentChecker.notNull(maturityReferanceDate, "maturityReferanceDate");
ArgumentChecker.notNull(paymentInterval, "tenor");
ArgumentChecker.notNull(stubType, "stubType");
ArgumentChecker.notNull(businessdayAdjustmentConvention, "businessdayAdjustmentConvention");
ArgumentChecker.notNull(accrualDayCount, "accuralDayCount");
ArgumentChecker.notNull(curveDayCount, "curveDayCount");
ArgumentChecker.isFalse(cashSettlementDate.isBefore(tradeDate), "Require valueDate >= today");
ArgumentChecker.isFalse(stepinDate.isBefore(tradeDate), "Require stepin >= today");
// ArgumentChecker.isFalse(tradeDate.isAfter(maturityReferanceDate), "First CDS has expired");
ArgumentChecker.notEmpty(maturityIndexes, "maturityIndexes");
_nMaturities = maturityIndexes.length;
ArgumentChecker.isTrue(maturityIndexes[0] >= 0, "first maturity index < 0");
for (int i = 1; i < _nMaturities; i++) {
ArgumentChecker.isTrue(maturityIndexes[i] > maturityIndexes[i - 1], "maturityIndexes not ascending");
}
_payAccOnDefault = payAccOnDefault;
_accStart = accStartDate.isBefore(tradeDate) ? -curveDayCount.getDayCountFraction(accStartDate, tradeDate) : curveDayCount.getDayCountFraction(tradeDate, accStartDate);
final LocalDate temp = stepinDate.isAfter(accStartDate) ? stepinDate : accStartDate;
final LocalDate effectiveStartDate = protectStart ? temp.minusDays(1) : temp;
_cashSettlementTime = curveDayCount.getDayCountFraction(tradeDate, cashSettlementDate);
_effectiveProtectionStart = curveDayCount.getDayCountFraction(tradeDate, effectiveStartDate);
_lgd = 1 - recoveryRate;
final LocalDate[] maturities = new LocalDate[_nMaturities];
_protectionEnd = new double[_nMaturities];
final Period period = paymentInterval.getPeriod();
for (int i = 0; i < _nMaturities; i++) {
final Period tStep = period.multipliedBy(maturityIndexes[i]);
maturities[i] = maturityReferanceDate.plus(tStep);
_protectionEnd[i] = curveDayCount.getDayCountFraction(tradeDate, maturities[i]);
}
final ISDAPremiumLegSchedule fullPaymentSchedule = new ISDAPremiumLegSchedule(accStartDate, maturities[_nMaturities - 1], period, stubType, businessdayAdjustmentConvention, calendar, protectStart);
//remove already expired coupons
final ISDAPremiumLegSchedule paymentSchedule = fullPaymentSchedule.truncateSchedule(stepinDate);
final 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];
final long secondJulianDate = stepinDate.getLong(JulianFields.MODIFIED_JULIAN_DAY);
for (int i = 0; i < _nMaturities; i++) {
final int index = fullPaymentSchedule.getNominalPaymentDateIndex(maturities[i]);
if (index < 0) {
throw new OpenGammaRuntimeException("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
final 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
final LocalDate tDate2 = _matIndexToPayments[i] < 0 ? fullPaymentSchedule.getAccStartDate(couponOffset - 1) : paymentSchedule.getAccStartDate(0);
final long firstJulianDate = tDate2.getLong(JulianFields.MODIFIED_JULIAN_DAY);
_accruedDays[i] = secondJulianDate > firstJulianDate ? (int) (secondJulianDate - firstJulianDate) : 0;
_accrued[i] = tDate2.isBefore(stepinDate) ? accrualDayCount.getDayCountFraction(tDate2, stepinDate) : 0.0;
}
}
private MultiCDSAnalytic(final double lgd, final boolean payAccOnDefault, final CDSCoupon[] standardCoupons, final CDSCoupon[] terminalCoupons, final double accStart,
final double effectiveProtectionStart, final double valuationTime, final double[] protectionEnd, final double[] accrued, final int[] accruedDays, final int totalPayments, final int nMaturities,
final 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 final standard coupon is one less than this
* @param matIndex maturity index (0 for first maturity, etc)
* @return payment index
*/
public int getPaymentIndexForMaturity(final int matIndex) {
return _matIndexToPayments[matIndex];
}
// /**
// *
// * @param matIndex
// * @return
// */
// @Deprecated
// public double getCoupon(final int matIndex) {
// return _couponAmts[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(final 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(final int matIndex) {
return _protectionEnd[matIndex];
}
/**
* Get the final coupon for the CDS at the given index (zero based).
* @param matIndex the index
* @return A coupon
*/
public CDSCoupon getTerminalCoupon(final int matIndex) {
return _terminalCoupons[matIndex];
}
/** Get the standard (i.e. not the final or terminal coupon of a CDS) at the given index
* @param index the index
* @return a coupon
*/
public CDSCoupon getStandardCoupon(final 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(final 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(final int matIndex, final 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(final int matIndex) {
return _accruedDays[matIndex];
}
@Override
public int hashCode() {
final 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(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final 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;
}
}