/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.credit; import java.time.LocalDate; import java.time.Period; import java.util.Optional; import java.util.OptionalDouble; import java.util.stream.Stream; import com.opengamma.strata.basics.ReferenceData; import com.opengamma.strata.basics.currency.CurrencyAmount; import com.opengamma.strata.basics.date.BusinessDayConvention; import com.opengamma.strata.basics.date.DayCount; import com.opengamma.strata.basics.date.DayCounts; import com.opengamma.strata.basics.date.HolidayCalendarId; import com.opengamma.strata.market.curve.NodalCurve; import com.opengamma.strata.pricer.PricingException; import com.opengamma.strata.pricer.impl.credit.isda.AccrualOnDefaultFormulae; import com.opengamma.strata.pricer.impl.credit.isda.AnalyticCdsPricer; import com.opengamma.strata.pricer.impl.credit.isda.CdsAnalytic; import com.opengamma.strata.pricer.impl.credit.isda.CdsPriceType; import com.opengamma.strata.pricer.impl.credit.isda.FastCreditCurveBuilder; import com.opengamma.strata.pricer.impl.credit.isda.IsdaCompliantCreditCurve; import com.opengamma.strata.pricer.impl.credit.isda.IsdaCompliantCreditCurveBuilder; import com.opengamma.strata.pricer.impl.credit.isda.IsdaCompliantYieldCurve; import com.opengamma.strata.pricer.impl.credit.isda.IsdaCompliantYieldCurveBuild; import com.opengamma.strata.pricer.impl.credit.isda.IsdaInstrumentTypes; import com.opengamma.strata.product.credit.ResolvedCds; import com.opengamma.strata.product.credit.type.CdsConvention; import com.opengamma.strata.product.credit.type.IsdaYieldCurveConvention; /** * Helper for interacting with the underlying Analytics layer for CDS pricing. * <p> * Translation from Strata business objects such as DayCount and StubMethod is done here. * The translation of underlying types for the yield curve is performed here. * Par rate representations of the curves are calibrated and converted to ISDA calibrated curves. * Present value of the expanded CDS product (single name or index) is calculated here. */ class IsdaCdsHelper { // hard-coded reference data public static final ReferenceData REF_DATA = ReferenceData.standard(); /** * DayCount used with calculating time during curve calibration. * <p> * The model expects ACT_365F, but this value is not on the trade or the convention. */ private static final DayCount CURVE_DAY_COUNT = DayCounts.ACT_365F; /** * When protection starts, at the start or end of the day. * <p> * If true, then protections starts at the beginning of the day, otherwise it is at the end. * The model expects this, but it is not a property of the trade or convention. * If true the protection is from the start of day and the effective accrual start and end dates are one day less. * The exception is the final accrual end date which should have one day added * (if protectionFromStartOfDay = true) in the final CDSCouponDes to compensate for this, so the * accrual end date is just the CDS maturity. The effect of having protectionFromStartOfDay = true * is to add an extra day of protection. */ private static final boolean PROTECT_START = true; /** * ISDA Standard model implementation in analytics. */ private static final AnalyticCdsPricer CALCULATOR = new AnalyticCdsPricer(); //------------------------------------------------------------------------- /** * Calculate present value on the specified valuation date. * * @param valuationDate date that present value is calculated on, also date that curves will be calibrated to * @param product the expanded CDS product * @param yieldCurve the par rates representation of the ISDA yield curve * @param creditCurve the par rates representation of the ISDA credit curve * @param recoveryRate the recovery rate for the reference entity/issue * @param scalingFactor the scaling factor * @return the present value of the expanded CDS product */ public static CurrencyAmount price( LocalDate valuationDate, ResolvedCds product, NodalCurve yieldCurve, NodalCurve creditCurve, double recoveryRate, double scalingFactor) { // setup CdsAnalytic cdsAnalytic = toAnalytic(valuationDate, product, recoveryRate); IsdaCompliantYieldCurve yieldCurveAnalytics = IsdaCompliantYieldCurve.makeFromRT(yieldCurve.getXValues(), yieldCurve.getYValues()); IsdaCompliantCreditCurve creditCurveAnalytics = IsdaCompliantCreditCurve.makeFromRT(creditCurve.getXValues(), creditCurve.getYValues()); // calculate double coupon = product.getCoupon(); double pv = CALCULATOR.pv(cdsAnalytic, yieldCurveAnalytics, creditCurveAnalytics, coupon, CdsPriceType.DIRTY, 0d); // create result int sign = product.getBuySellProtection().isBuy() ? 1 : -1; double notional = product.getNotional(); double factor = scalingFactor; double adjusted = pv * notional * sign * factor; double upfrontFeeAmount = priceUpfrontFee( valuationDate, product.getUpfrontFeeAmount(), product.getUpfrontFeePaymentDate(), yieldCurveAnalytics) * sign; double adjustedPlusFee = adjusted + upfrontFeeAmount; return CurrencyAmount.of(product.getCurrency(), adjustedPlusFee); } //------------------------------------------------------------------------- // The fee is always calculated as being payable by the protection buyer. // If the seller should pay the fee, then a negative amount is used. private static double priceUpfrontFee( LocalDate valuationDate, OptionalDouble amount, Optional<LocalDate> paymentDate, IsdaCompliantYieldCurve yieldCurve) { if (!amount.isPresent()) { return 0d; // no fee } if (!paymentDate.get().isAfter(valuationDate)) { return 0d; // fee already paid } double feeSettleYearFraction = CURVE_DAY_COUNT.yearFraction(valuationDate, paymentDate.get()); double discountFactor = yieldCurve.getDiscountFactor(feeSettleYearFraction); return discountFactor * amount.getAsDouble(); } /** * Calculate par spread on the specified valuation date. * * @param valuationDate date that par spread is calculated on, also date that curves will be calibrated to * @param product the expanded CDS product * @param yieldCurve the par rates representation of the ISDA yield curve * @param creditCurve the par rates representation of the ISDA credit curve * @param recoveryRate the recovery rate for the reference entity/issue * @return the par spread of the expanded CDS product */ public static double parSpread(LocalDate valuationDate, ResolvedCds product, NodalCurve yieldCurve, NodalCurve creditCurve, double recoveryRate) { // setup CdsAnalytic cdsAnalytic = toAnalytic(valuationDate, product, recoveryRate); IsdaCompliantYieldCurve yieldCurveAnalytics = IsdaCompliantYieldCurve.makeFromRT(yieldCurve.getXValues(), yieldCurve.getYValues()); IsdaCompliantCreditCurve creditCurveAnalytics = IsdaCompliantCreditCurve.makeFromRT(creditCurve.getXValues(), creditCurve.getYValues()); return CALCULATOR.parSpread(cdsAnalytic, yieldCurveAnalytics, creditCurveAnalytics); } // Converts the interest rate curve par rates to the corresponding analytics form. // Calibration is performed here. public static IsdaCompliantYieldCurve createIsdaDiscountCurve( LocalDate valuationDate, IsdaYieldCurveInputs yieldCurve) { try { // model does not use floating leg of underlying IRS IsdaYieldCurveConvention curveConvention = yieldCurve.getCurveConvention(); Period swapInterval = curveConvention.getFixedPaymentFrequency().getPeriod(); DayCount mmDayCount = curveConvention.getMoneyMarketDayCount(); DayCount swapDayCount = curveConvention.getFixedDayCount(); BusinessDayConvention convention = curveConvention.getBusinessDayConvention(); HolidayCalendarId holidayCalendar = curveConvention.getHolidayCalendar(); LocalDate spotDate = curveConvention.calculateSpotDateFromTradeDate(valuationDate, REF_DATA); IsdaInstrumentTypes[] types = Stream.of(yieldCurve.getYieldCurveInstruments()) .map(IsdaCdsHelper::mapInstrumentType) .toArray(IsdaInstrumentTypes[]::new); IsdaCompliantYieldCurveBuild builder = new IsdaCompliantYieldCurveBuild( valuationDate, spotDate, types, yieldCurve.getYieldCurvePoints(), mmDayCount, swapDayCount, swapInterval, CURVE_DAY_COUNT, convention, holidayCalendar.resolve(REF_DATA)); return builder.build(yieldCurve.getParRates()); } catch (Exception ex) { throw new PricingException("Error converting the ISDA Discount Curve: " + ex.getMessage(), ex); } } // Converts the credit curve par rates to the corresponding analytics form. // Calibration is performed here. public static IsdaCompliantCreditCurve createIsdaCreditCurve( LocalDate valuationDate, IsdaCreditCurveInputs curveCurve, IsdaCompliantYieldCurve yieldCurve, double recoveryRate) { try { CdsConvention cdsConvention = curveCurve.getCdsConvention(); FastCreditCurveBuilder builder = new FastCreditCurveBuilder( AccrualOnDefaultFormulae.ORIGINAL_ISDA, IsdaCompliantCreditCurveBuilder.ArbitrageHandling.Fail); return builder.calibrateCreditCurve( valuationDate, cdsConvention.calculateUnadjustedStepInDate(valuationDate), cdsConvention.calculateAdjustedSettleDate(valuationDate, REF_DATA), cdsConvention.calculateAdjustedStartDate(valuationDate, REF_DATA), curveCurve.getEndDatePoints(), curveCurve.getParRates(), cdsConvention.isPayAccruedOnDefault(), cdsConvention.getPaymentFrequency().getPeriod(), cdsConvention.getStubConvention(), PROTECT_START, yieldCurve, recoveryRate); } catch (Exception ex) { throw new PricingException("Error converting the ISDA Credit Curve: " + ex.getMessage(), ex); } } // Converts the credit curve par rates to the corresponding analytics form. // Calibration is performed here. public static IsdaCompliantCreditCurve createIsdaCreditCurve( LocalDate valuationDate, IsdaCreditCurveInputs curveCurve, NodalCurve yieldCurve, double recoveryRate) { try { IsdaCompliantYieldCurve yieldCurveAnalytics = IsdaCompliantYieldCurve.makeFromRT(yieldCurve.getXValues(), yieldCurve.getYValues()); CdsConvention cdsConvention = curveCurve.getCdsConvention(); FastCreditCurveBuilder builder = new FastCreditCurveBuilder( AccrualOnDefaultFormulae.ORIGINAL_ISDA, IsdaCompliantCreditCurveBuilder.ArbitrageHandling.Fail); return builder.calibrateCreditCurve( valuationDate, cdsConvention.calculateUnadjustedStepInDate(valuationDate), cdsConvention.calculateAdjustedSettleDate(valuationDate, REF_DATA), cdsConvention.calculateAdjustedStartDate(valuationDate, REF_DATA), curveCurve.getEndDatePoints(), curveCurve.getParRates(), cdsConvention.isPayAccruedOnDefault(), cdsConvention.getPaymentFrequency().getPeriod(), cdsConvention.getStubConvention(), PROTECT_START, yieldCurveAnalytics, recoveryRate); } catch (Exception ex) { throw new PricingException("Error converting the ISDA Credit Curve: " + ex.getMessage(), ex); } } // Converts the expanded CDS product to the corresponding analytics form. private static CdsAnalytic toAnalytic(LocalDate valuationDate, ResolvedCds product, double recoveryRate) { try { return new CdsAnalytic( valuationDate, valuationDate.plusDays(1), valuationDate, product.getStartDate(), product.getEndDate(), product.isPayAccruedOnDefault(), product.getPaymentInterval(), product.getStubConvention(), PROTECT_START, recoveryRate, product.getBusinessDayAdjustment().getConvention(), product.getBusinessDayAdjustment().getCalendar().resolve(REF_DATA), product.getAccrualDayCount(), CURVE_DAY_COUNT); } catch (Exception ex) { throw new PricingException("Error converting the trade to an analytic: " + ex.getMessage(), ex); } } //------------------------------------------------------------------------- // Converts type of interest curve underlying to the corresponding analytics value. private static IsdaInstrumentTypes mapInstrumentType(IsdaYieldCurveUnderlyingType input) { switch (input) { case ISDA_MONEY_MARKET: return IsdaInstrumentTypes.MONEY_MARKET; case ISDA_SWAP: return IsdaInstrumentTypes.SWAP; default: throw new IllegalStateException("Unexpected underlying type: " + input); } } }