/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.credit.options;
import static com.opengamma.analytics.financial.credit.options.YieldCurveProvider.ISDA_EUR_20140206;
import static org.testng.AssertJUnit.assertEquals;
import org.testng.annotations.Test;
import org.threeten.bp.LocalDate;
import org.threeten.bp.Period;
import com.opengamma.analytics.financial.credit.index.CDSIndexCalculator;
import com.opengamma.analytics.financial.credit.isdastandardmodel.AnnuityForSpreadApproxFunction;
import com.opengamma.analytics.financial.credit.isdastandardmodel.AnnuityForSpreadFunction;
import com.opengamma.analytics.financial.credit.isdastandardmodel.CDSAnalytic;
import com.opengamma.analytics.financial.credit.isdastandardmodel.CDSAnalyticFactory;
import com.opengamma.analytics.financial.credit.isdastandardmodel.ISDABaseTest;
import com.opengamma.analytics.financial.credit.isdastandardmodel.ISDACompliantCreditCurve;
import com.opengamma.analytics.financial.credit.isdastandardmodel.ISDACompliantYieldCurve;
import com.opengamma.analytics.financial.credit.isdastandardmodel.MarketQuoteConverter;
import com.opengamma.analytics.financial.credit.isdastandardmodel.PriceType;
import com.opengamma.analytics.financial.credit.isdastandardmodel.QuotedSpread;
/**
*
*/
public class ItraxxXoverTest extends ISDABaseTest {
private static final double NOTIONAL = 1e8;
private static final LocalDate ACC_START = LocalDate.of(2013, 12, 20);
private static final LocalDate TRADE_DATE = LocalDate.of(2014, 2, 6);
private static final LocalDate EXPIRY = LocalDate.of(2014, 3, 19);
private static final LocalDate EXERCISE_SETTLE = LocalDate.of(2014, 3, 24);
private static final LocalDate MATURITY = LocalDate.of(2018, 12, 20);
private static final double TRADE_SPREAD = 318.25 * ONE_BP;
private static final double COUPON = 500 * ONE_BP;
// private static final double EXP_FWD_ANNUITY = 4.83644025;
private static final double BBG_ATM_FWD = 331.7902 * ONE_BP;
private static final double TRUE_ATM_FWD = 326.6135 * ONE_BP;
private static final double DEFAULT_ADJ_INDEX = -7195598.53; //found by setting strike to 500bps
private static final Period TENOR = Period.ofYears(5);
private static final Period[] PILLAR_TENORS = new Period[] {Period.ofMonths(6), Period.ofYears(1), Period.ofYears(2), Period.ofYears(3), Period.ofYears(4), Period.ofYears(5), Period.ofYears(7),
Period.ofYears(10) };
private static final double[] PILLAR_PAR_SPREADS;
private static final QuotedSpread[] PILLAR_QUOTED_SPREADS;
private static final MarketQuoteConverter CONVERTER = new MarketQuoteConverter();
private static final CDSAnalyticFactory FACTORY = new CDSAnalyticFactory();
private static final CDSAnalytic SPOT_CDX = FACTORY.makeCDX(TRADE_DATE, TENOR);
private static final CDSAnalytic FWD_START_CDX = FACTORY.makeCDS(TRADE_DATE, EXPIRY.plusDays(1), EXERCISE_SETTLE, ACC_START, MATURITY);
private static final CDSAnalytic FWD_CDX = FACTORY.makeCDX(EXPIRY, TENOR);
private static final CDSAnalytic[] PILLAR_CDX = FACTORY.makeCDX(TRADE_DATE, PILLAR_TENORS);
private static ISDACompliantYieldCurve YIELD_CURVE = ISDA_EUR_20140206;
private static final double[] STRIKES = new double[] {10, 260, 280, 300, 320, 326.6135, 331.7902, 340, 360, 380, 400, 420, 440, 500, 800, 10000 };
private static final double[] CALLPRICE = new double[] {15806482.5, 3026637.28, 2136488.22, 1347217.39, 736789.83, 582864.50, 479143.09, 343256.18, 135570.31, 45626.11, 13229.32, 3350.15, 751.64,
4.69, 0, 0 };
private static final double[] PUTPRICE = new double[] {0, 5586.6, 38353.12, 158280.03, 443543.41, 582864.64, 707672.3, 932401.6, 1594012.99, 2360472.75, 3171784.78, 3993114.37, 4809416.57,
7193399.12, 17646108.69, 68831330.13 };
private static final double[] EXERCISE_PRICE = new double[] {-0.230069242448716, -0.102175748837439, -0.0929437651628761, -0.0838490019206849, -0.074889348064225, -0.0719559839373113,
-0.0696699930090803, -0.0660627259570898, -0.0573670897726163, -0.0488004260940694, -0.0403607527142736, -0.0320461182354909, -0.02385460196939, 0, 0.104559170409921, 0.61656821972402 };
private static CDSIndexCalculator INDEX_CAL = new CDSIndexCalculator();
private static boolean PRINT = false;
static {
final double[] spreads = new double[] {204.87, 204.87, 204.87, 204.87, 261.56, 318.25, 377.98, 401.39 };
final int n = spreads.length;
PILLAR_PAR_SPREADS = new double[n];
PILLAR_QUOTED_SPREADS = new QuotedSpread[n];
for (int i = 0; i < n; i++) {
PILLAR_PAR_SPREADS[i] = spreads[i] * ONE_BP;
PILLAR_QUOTED_SPREADS[i] = new QuotedSpread(COUPON, PILLAR_PAR_SPREADS[i]);
}
if (PRINT) {
System.out.println("ItraxxXoverTest - set PRINT to false before push");
}
}
/**
* Should be given by P(t,T_E)E[V + D - G(k)], where V is the index value at expiry, D is the default settlement value and G(K) = (k-C)A(K) is the excise price
*/
@Test
//(enabled = false)
public void putCallTest() {
if (PRINT) {
final double tE = ACT365F.getDayCountFraction(TRADE_DATE, EXPIRY);
final double tES = ACT365F.getDayCountFraction(TRADE_DATE, EXERCISE_SETTLE);
//build an index curve treating the index spreads and single name par spreads
final ISDACompliantCreditCurve cc = CREDIT_CURVE_BUILDER.calibrateCreditCurve(PILLAR_CDX, PILLAR_PAR_SPREADS, YIELD_CURVE);
final double indexPV = PRICER.pv(SPOT_CDX, YIELD_CURVE, cc, COUPON, PriceType.CLEAN, 0.0);
final double df = YIELD_CURVE.getDiscountFactor(tES);
final double q = cc.getSurvivalProbability(tE);
final double defaultSettlePV = df * (1 - q) * (1 - RECOVERY_RATE);
System.out.println("discount fact: " + df);
final ISDACompliantYieldCurve fwdYC = YIELD_CURVE.withOffset(tES);
System.out.println(indexPV * NOTIONAL + "\t" + defaultSettlePV * NOTIONAL);
final AnnuityForSpreadFunction annuity = new AnnuityForSpreadApproxFunction(FWD_CDX, fwdYC);
final int n = STRIKES.length;
for (int i = 0; i < n; i++) {
final double exPrice = NOTIONAL * CONVERTER.quotedSpreadToPUF(FWD_CDX, COUPON, fwdYC, STRIKES[i] * ONE_BP);
final double exPrice2 = NOTIONAL * (STRIKES[i] * ONE_BP - COUPON) * annuity.evaluate(STRIKES[i] * ONE_BP);
final double putCall = df * (DEFAULT_ADJ_INDEX - exPrice2);
final double error = putCall - (CALLPRICE[i] - PUTPRICE[i]);
// System.out.println(STRIKES[i] + "\t" + exPrice * NOTIONAL + "\t" + (indexPV + defaultSettlePV - exPrice) * NOTIONAL);
System.out.println(STRIKES[i] + "\t" + exPrice + "\t" + exPrice2 + "\t" + putCall + "\t" + error);
}
}
}
/**
* Regression test for forward values
*/
@Test
public void forwardValueTest() {
final double expFwdIndexVal = -7201983.340857886;
final double expFwdSpread = 331.649277309528 * ONE_BP;
final double expATMFwdSpread = 327.38630192687924 * ONE_BP;
final double tE = ACT365F.getDayCountFraction(TRADE_DATE, EXPIRY);
//build credit curve by first converting the quoted spreads to PUF
final ISDACompliantCreditCurve cc = CREDIT_CURVE_BUILDER.calibrateCreditCurve(PILLAR_CDX, PILLAR_QUOTED_SPREADS, YIELD_CURVE);
final double fwdIndexVal = INDEX_CAL.defaultAdjustedForwardIndexValue(FWD_START_CDX, tE, YIELD_CURVE, COUPON, cc);
final double fwdSpread = INDEX_CAL.defaultAdjustedForwardSpread(FWD_START_CDX, tE, YIELD_CURVE, cc);
final ISDACompliantYieldCurve fwdYC = YIELD_CURVE.withOffset(tE);
final double atmFwdSpread = CONVERTER.pufToQuotedSpread(FWD_CDX, COUPON, fwdYC, fwdIndexVal);
if (PRINT) {
System.out.println("Fwd Index val:\t" + NOTIONAL * fwdIndexVal);
System.out.println("Fwd Spread:\t" + fwdSpread * TEN_THOUSAND);
System.out.println("ATM Forward:\t" + atmFwdSpread * TEN_THOUSAND);
}
assertEquals("FwdIndexVal", expFwdIndexVal, NOTIONAL * fwdIndexVal, NOTIONAL * 1e-16);
assertEquals("fwdSpread", expFwdSpread, fwdSpread, 1e-16);
assertEquals("ATMFwdSpread", expATMFwdSpread, atmFwdSpread, 1e-16);
}
@Test
public void optionPrices() {
final double expX0a = 327.91390602255956 * ONE_BP;
final double expX0b = 327.7699441941618 * ONE_BP;
final double[] expOTMprices = new double[] {6.1327369003985E-231, 5647.51524217555, 38556.0028211884, 158651.691867189, 443955.180447323, 583250.500215998, 479498.475809142, 343550.920743509,
135711.32921182, 45670.1459990407, 13236.2006801549, 3348.89825017615, 750.312519414825, 4.65015781997449, 7.92961638482859E-14 };
final double tE = ACT365F.getDayCountFraction(TRADE_DATE, EXPIRY);
final double tES = ACT365F.getDayCountFraction(TRADE_DATE, EXERCISE_SETTLE);
final double df = YIELD_CURVE.getDiscountFactor(tES);
final double vol = 0.3;
// final BloombergIndexOptionPricer pricer = new BloombergIndexOptionPricer(TRADE_DATE, EXPIRY, EXPIRY.plusDays(1), EXERCISE_SETTLE, ACC_START, MATURITY, YIELD_CURVE, RECOVERY_RATE);
final IndexOptionPricer oPricer = new IndexOptionPricer(FWD_CDX, tE, YIELD_CURVE, COUPON);
final ISDACompliantCreditCurve cc = CREDIT_CURVE_BUILDER.calibrateCreditCurve(PILLAR_CDX, PILLAR_QUOTED_SPREADS, YIELD_CURVE);
final double fwdIndexVal = INDEX_CAL.defaultAdjustedForwardIndexValue(FWD_START_CDX, tE, YIELD_CURVE, COUPON, cc);
final double x0a = oPricer.calibrateX0(DEFAULT_ADJ_INDEX / NOTIONAL, vol);
final double x0b = oPricer.calibrateX0(fwdIndexVal, vol);
if (PRINT) {
System.out.println("BBG fwd index val:\t" + DEFAULT_ADJ_INDEX);
System.out.println("Cal fwd index val:\t" + fwdIndexVal * NOTIONAL);
System.out.println("BBG X0:\t" + x0a * TEN_THOUSAND);
System.out.println("Cal X0:\t" + x0b * TEN_THOUSAND);
}
assertEquals(expX0a, x0a, expX0a * 1e-15);
assertEquals(expX0a, x0a, expX0b * 1e-15);
final int n = STRIKES.length - 1;
for (int i = 0; i < n; i++) {
final double payer = NOTIONAL * oPricer.getOptionPriceForPriceQuotedIndex(DEFAULT_ADJ_INDEX / NOTIONAL, vol, EXERCISE_PRICE[i], true);
final double receiver = NOTIONAL * oPricer.getOptionPriceForPriceQuotedIndex(DEFAULT_ADJ_INDEX / NOTIONAL, vol, EXERCISE_PRICE[i], false);
final double payer2 = NOTIONAL * oPricer.getOptionPriceForSpreadQuotedIndex(fwdIndexVal, vol, STRIKES[i] * ONE_BP, true);
final double receiver2 = NOTIONAL * oPricer.getOptionPriceForSpreadQuotedIndex(fwdIndexVal, vol, STRIKES[i] * ONE_BP, false);
double impVol = 0;
//TODO PLAT-5993 The tolerance has been turned down to pass in Bamboo
final double tol = 1e-12 * expOTMprices[i];
if (DEFAULT_ADJ_INDEX / NOTIONAL < EXERCISE_PRICE[i]) {
assertEquals(expOTMprices[i], payer, tol);
if (CALLPRICE[i] > 0) {
impVol = oPricer.impliedVol(DEFAULT_ADJ_INDEX / NOTIONAL, EXERCISE_PRICE[i], CALLPRICE[i] / NOTIONAL, true);
}
} else {
assertEquals(expOTMprices[i], receiver, tol);
if (PUTPRICE[i] > 0) {
impVol = oPricer.impliedVol(DEFAULT_ADJ_INDEX / NOTIONAL, EXERCISE_PRICE[i], PUTPRICE[i] / NOTIONAL, false);
}
}
if (PRINT) {
System.out.println(STRIKES[i] + "\t" + payer + "\t" + receiver + "\t" + payer2 + "\t" + receiver2 + "\t" + impVol);
}
}
//
}
private double bbgIndexVal(final double lambda, final double r, final double tE, final double tM) {
final double rlambda = r + lambda;
final double df = Math.exp(-r * tE);
final double e1 = Math.exp(-rlambda * tE);
final double e2 = Math.exp(-rlambda * tM);
final double ann = (e1 - e2) / rlambda;
return ((1 - RECOVERY_RATE) * lambda - COUPON) * ann / df;
}
}