/**
* 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.isdastandardmodel.IMMDateLogic.getNextIMMDate;
import static com.opengamma.analytics.financial.credit.options.YieldCurveProvider.ISDA_EUR_20140206;
import static org.testng.AssertJUnit.assertEquals;
import java.util.Arrays;
import org.testng.annotations.Test;
import org.threeten.bp.LocalDate;
import org.threeten.bp.Period;
import cern.jet.random.engine.MersenneTwister;
import cern.jet.random.engine.MersenneTwister64;
import cern.jet.random.engine.RandomEngine;
import com.opengamma.analytics.financial.credit.index.CDSIndexCalculator;
import com.opengamma.analytics.financial.credit.index.IntrinsicIndexDataBundle;
import com.opengamma.analytics.financial.credit.index.PortfolioSwapAdjustment;
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.math.statistics.distribution.NormalDistribution;
import com.opengamma.analytics.math.statistics.distribution.ProbabilityDistribution;
/**
*
*/
public class IndexOptionPricerTest extends ISDABaseTest {
protected static final RandomEngine RANDOM = new MersenneTwister64(MersenneTwister.DEFAULT_SEED);
protected static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1, RANDOM);
private static final CDSAnalyticFactory FACTORY = new CDSAnalyticFactory();
private static final int INDEX_SIZE = 125;
private static final double INDEX_COUPON = 0.01;
private static final ISDACompliantCreditCurve[] CREDIT_CURVES;
private static final ISDACompliantYieldCurve YIELD_CURVE = ISDA_EUR_20140206;
private static final double[] RECOVERY_RATES;
private static final IntrinsicIndexDataBundle INTRINSIC_DATA;
private static final LocalDate TRADE_DATE = LocalDate.of(2014, 02, 06);
private static final PortfolioSwapAdjustment PSA = new PortfolioSwapAdjustment();
private static final CDSIndexCalculator INDEX_CAL = new CDSIndexCalculator();
private static final boolean PRINT = false;
static {
final double[] ccNodes = new double[] {0.5, 1, 3, 5, 7, 10 };
CREDIT_CURVES = new ISDACompliantCreditCurve[INDEX_SIZE];
RECOVERY_RATES = new double[INDEX_SIZE];
final int nKnots = ccNodes.length;
final double sigma1 = 0.7;
final double sigma2 = 0.3;
for (int i = 0; i < INDEX_SIZE; i++) {
final double u = RANDOM.nextDouble();
final double z = NORMAL.getInverseCDF(u);
final double mean = 0.03 * Math.exp(sigma1 * z - sigma1 * sigma1 / 2);
RECOVERY_RATES[i] = 0.9 - 0.7 * u;
final double[] fwd = new double[nKnots];
for (int jj = 0; jj < nKnots; jj++) {
fwd[jj] = mean * Math.exp(sigma2 * NORMAL.nextRandom() - sigma2 * sigma2 / 2);
}
CREDIT_CURVES[i] = ISDACompliantCreditCurve.makeFromForwardRates(ccNodes, fwd);
}
INTRINSIC_DATA = new IntrinsicIndexDataBundle(CREDIT_CURVES, RECOVERY_RATES);
if (PRINT) {
System.out.println("IndexOptionPricerTest - set PRINT to false before push");
}
}
/**
* This is a pure regression test of the intrinsic value of the index
*/
@Test
public void priceIndexTest() {
final CDSAnalytic cds = FACTORY.makeIMMCDS(TRADE_DATE, Period.ofYears(5));
final double price = INDEX_CAL.indexPV(cds, INDEX_COUPON, YIELD_CURVE, INTRINSIC_DATA);
final double expected = 0.02361802336968797; //clean index value (on cash settlement date)
if (PRINT) {
System.out.println(price);
}
assertEquals(expected, price, 1e-15);
}
@Test
public void indexAdjTest() {
final CDSAnalytic cds = FACTORY.makeIMMCDS(TRADE_DATE, Period.ofYears(5));
final double indexPrice = 0.021;
final IntrinsicIndexDataBundle adjCurves = PSA.adjustCurves(indexPrice, cds, INDEX_COUPON, YIELD_CURVE, INTRINSIC_DATA);
final double price = INDEX_CAL.indexPUF(cds, INDEX_COUPON, YIELD_CURVE, adjCurves);
assertEquals(indexPrice, price, 1e-15);
}
/**
* Price an index option with homogeneous 'flat' credit curves for the individual CDS, and perform a regression test on the results.
*/
@Test
public void indexOptionTest() {
final double[] expOTMPrices = new double[] {4.1631007571372696E-5, 2.6973611070343375E-4, 9.295502243146243E-4, 0.0022070362751108927, 0.004085595156615987, 0.002640736254260279,
0.0016694319443711508, 0.0010387325595910084, 6.392918916192971E-4, 3.907074536166385E-4, 2.3783431950800257E-4, 1.4453687319597615E-4, 8.784959620724592E-5, 5.347507099057006E-5,
3.263367733083826E-5, 1.9981456126459862E-5, 1.2282675436274992E-5, 7.583278420259651E-6, 4.703919588472932E-6, 2.932259282757757E-6 };
final double lambda = 0.01;
final ISDACompliantCreditCurve creditCurve = new ISDACompliantCreditCurve(1.0, lambda);
final ISDACompliantCreditCurve[] creditCurves = new ISDACompliantCreditCurve[INDEX_SIZE];
final double[] recoeryRates = new double[INDEX_SIZE];
Arrays.fill(creditCurves, creditCurve);
Arrays.fill(recoeryRates, RECOVERY_RATE);
final double f = 1.0; //the index factor
//start by adjusting the curves
final LocalDate optionExpiry = getNextIMMDate(TRADE_DATE).minusDays(1); //make expiry the next IMM - 1 (19/12/2013)
final CDSAnalytic fwdCDS = FACTORY.makeIMMCDS(TRADE_DATE, Period.ofYears(5));
final CDSAnalytic fwdStartingCDS = FACTORY.makeForwardStartingIMMCDS(TRADE_DATE, optionExpiry, Period.ofYears(5));
final double expiry = ACT365F.getDayCountFraction(TRADE_DATE, optionExpiry);
final double atmFwd = INDEX_CAL.defaultAdjustedForwardIndexValue(fwdStartingCDS, expiry, YIELD_CURVE, INDEX_COUPON, creditCurve);
final IndexOptionPricer pricer = new IndexOptionPricer(fwdCDS, expiry, YIELD_CURVE, INDEX_COUPON);
final double vol = 1;
final double atmPayer = pricer.getOptionPriceForPriceQuotedIndex(atmFwd, vol, atmFwd, true);
final double atmRec = pricer.getOptionPriceForPriceQuotedIndex(atmFwd, vol, atmFwd, false);
assertEquals(atmPayer, atmRec);
final double atmK = (new MarketQuoteConverter()).pufToQuotedSpread(fwdCDS, INDEX_COUPON, YIELD_CURVE.withOffset(expiry), atmFwd);
for (int i = 0; i < 20; i++) {
final double k = 0.003 + 0.015 * i / 19.;
final boolean isPayer = k >= atmK;
final double price = pricer.getOptionPriceForSpreadQuotedIndex(atmFwd, vol, k, isPayer);
if (PRINT) {
System.out.println(price + ",");
}
assertEquals(expOTMPrices[i], price, 1e-15);
}
}
@Test
public void putCallTest() {
final LocalDate optionExpiry = TRADE_DATE.plusMonths(1);
final CDSAnalytic fwdCDS = FACTORY.makeIMMCDS(TRADE_DATE, Period.ofYears(5));
final CDSAnalytic fwdStartingCDS = FACTORY.makeForwardStartingIMMCDS(TRADE_DATE, optionExpiry, Period.ofYears(5));
final double expiry = ACT365F.getDayCountFraction(TRADE_DATE, optionExpiry);
final double atmFwd = -0.02;
final IndexOptionPricer pricer = new IndexOptionPricer(fwdCDS, expiry, YIELD_CURVE, INDEX_COUPON);
final double vol = 0.34;
final double df = YIELD_CURVE.getDiscountFactor(fwdStartingCDS.getCashSettleTime());
for (int i = 0; i < 20; i++) {
final double exercisePrice = -0.04 + 0.1 * i / 19.;
final double putCall = df * (atmFwd - exercisePrice);
final double payer = pricer.getOptionPremium(atmFwd, vol, new ExerciseAmount(exercisePrice), true);
final double reciver = pricer.getOptionPremium(atmFwd, vol, new ExerciseAmount(exercisePrice), false);
// System.out.println(exercisePrice + "\t" + payer + "\t" + reciver);
assertEquals(payer - reciver, putCall, 1e-12);
}
}
/**
* This test the time to calculate an option price using two different methods to compute the annuity from a (flat/quoted) spread.
* This was run on R White's Mac Pro on 14/01/2014 using 200 warmups and 1000 hotspots
* Time using ISDA for annuity: 5.814947999999999ms
* Time using approximation for annuity: 1.43962ms
*/
@Test
public void speedTest() {
//this is repeated from above
final CDSAnalytic spotCDS = FACTORY.makeIMMCDS(TRADE_DATE, Period.ofYears(5));
final double indexPrice = 0.021;
final double f = 1.0; //the index factor
//start by adjusting the curves to a single index value
final IntrinsicIndexDataBundle adjCurves = PSA.adjustCurves(indexPrice, spotCDS, INDEX_COUPON, YIELD_CURVE, INTRINSIC_DATA);
final LocalDate optionExpiry = TRADE_DATE.plusMonths(1); //option expiry/(effective) start of protection is in 1 month
final double timeToExpiry = ACT365F.getDayCountFraction(TRADE_DATE, optionExpiry);
final CDSAnalytic fwdStartCDS = FACTORY.makeForwardStartingIMMCDS(TRADE_DATE, optionExpiry, Period.ofYears(5));
final CDSAnalytic fwdCDS = FACTORY.makeIMMCDS(TRADE_DATE, Period.ofYears(5));
final IndexOptionPricer pricerISDA = new IndexOptionPricer(fwdCDS, timeToExpiry, YIELD_CURVE, INDEX_COUPON, true);//this uses to ISDA model to compute annuity
final IndexOptionPricer pricerApprox = new IndexOptionPricer(fwdCDS, timeToExpiry, YIELD_CURVE, INDEX_COUPON); //this uses the credit triangle to compute annuity
final double atmFwdVal = INDEX_CAL.defaultAdjustedForwardIndexValue(fwdStartCDS, timeToExpiry, YIELD_CURVE, INDEX_COUPON, adjCurves);
if (PRINT) {
final int warmups = 200;
final int hotspots = 1000;
for (int i = 0; i < warmups; i++) {
final double vol = RANDOM.nextDouble();
final double k = 0.005 + 0.02 * RANDOM.nextDouble();
final double p = pricerISDA.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, true);
}
long t0 = System.nanoTime();
for (int i = 0; i < hotspots; i++) {
final double vol = RANDOM.nextDouble();
final double k = 0.005 + 0.02 * RANDOM.nextDouble();
final double p = pricerISDA.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, true);
}
long t1 = System.nanoTime();
System.out.println("Time using ISDA for annuity: " + (t1 - t0) * 1e-6 / hotspots + "ms");
for (int i = 0; i < warmups; i++) {
final double vol = RANDOM.nextDouble();
final double k = 0.005 + 0.02 * RANDOM.nextDouble();
final double p = pricerApprox.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, true);
}
t0 = System.nanoTime();
for (int i = 0; i < hotspots; i++) {
final double vol = RANDOM.nextDouble();
final double k = 0.005 + 0.02 * RANDOM.nextDouble();
final double p = pricerApprox.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, true);
}
t1 = System.nanoTime();
System.out.println("Time using approximation for annuity: " + (t1 - t0) * 1e-6 / hotspots + "ms");
}
}
/**
* Check options priced with the annuity approximation are close to those using the ISDA model
*/
@Test
public void isdaVApproxTest() {
final double indexPrice = 0.021;
final CDSAnalytic spotCDS = FACTORY.makeIMMCDS(TRADE_DATE, Period.ofYears(5));
//start by adjusting the curves to a single index value
final IntrinsicIndexDataBundle adjCurves = PSA.adjustCurves(indexPrice, spotCDS, INDEX_COUPON, YIELD_CURVE, INTRINSIC_DATA);
final LocalDate optionExpiry = TRADE_DATE.plusMonths(1); //option expiry/(effective) start of protection is in 1 month
final double timeToExpiry = ACT365F.getDayCountFraction(TRADE_DATE, optionExpiry);
final CDSAnalytic fwdStartCDS = FACTORY.makeForwardStartingIMMCDS(TRADE_DATE, optionExpiry, Period.ofYears(5));
final CDSAnalytic fwdCDS = FACTORY.makeIMMCDS(TRADE_DATE, Period.ofYears(5));
final IndexOptionPricer pricerISDA = new IndexOptionPricer(fwdCDS, timeToExpiry, YIELD_CURVE, INDEX_COUPON, true);//this uses to ISDA model to compute annuity
final IndexOptionPricer pricerApprox = new IndexOptionPricer(fwdCDS, timeToExpiry, YIELD_CURVE, INDEX_COUPON); //this uses the credit triangle to compute annuity
final double atmFwdVal = INDEX_CAL.defaultAdjustedForwardIndexValue(fwdStartCDS, timeToExpiry, YIELD_CURVE, INDEX_COUPON, adjCurves);
final double vol = 0.4;
for (int i = 0; i < 100; i++) {
final double k = 0.002 + 0.02 * i / 100.;
final double payer1 = pricerISDA.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, true);
final double payer2 = pricerApprox.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, true);
final double rec1 = pricerISDA.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, false);
final double rec2 = pricerApprox.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, false);
if (PRINT) {
System.out.println(k + "\t" + payer1 + "\t" + payer2);
}
assertEquals(payer1, payer2, 1e-6 + payer1 * 1e-4);
assertEquals(rec1, rec2, 1e-6 + rec1 * 1e-4);
}
}
@Test
public void defaultedNamesTest() {
//this is repeated from above
final double indexPrice = 0.021;
final double[] expOTM = new double[] {9.076800764955738E-58, 3.4226674270476496E-53, 3.421305456107505E-49, 1.1578431585829338E-45, 1.5971443685030326E-42, 1.0368924201927302E-39,
3.5488934506809077E-37, 7.013017170443694E-35, 8.614686159820895E-33, 6.989713086397245E-31, 3.9397404998361574E-29, 1.6092967109704648E-27, 4.937503232924944E-26, 1.1730934910278846E-24,
2.2155898144875945E-23, 3.4025886679708334E-22, 4.333505166418396E-21, 4.6563803518661164E-20, 4.2854645659737556E-19, 3.4235577931791882E-18, 2.402256063322698E-17, 1.4961984880648425E-16,
8.349609439174132E-16, 4.210233474792479E-15, 1.9328458757406006E-14, 8.134023739062972E-14, 3.15729001261722E-13, 1.1367368714819529E-12, 3.815542543731805E-12, 1.1995608173294043E-11,
3.547357283025416E-11, 9.905947198985513E-11, 2.62149200845133E-10, 6.59616065568106E-10, 1.582854456644385E-9, 3.63256974036388E-9, 7.993468395411526E-9, 1.6906724004197517E-8,
3.444534406470765E-8, 6.774232294873387E-8, 1.2885050063100898E-7, 2.374612436930876E-7, 4.2472177780647457E-7, 7.384252217101014E-7, 1.2497928944235257E-6, 2.0620462115765177E-6,
3.3208584405433833E-6, 5.22664910774526E-6, 8.048489915315826E-6, 1.2139283310038822E-5, 1.7951546138525213E-5, 2.6053032280397427E-5, 3.7141442120650334E-5, 5.2056601696062696E-5,
7.179001552326956E-5, 9.748982249311326E-5, 1.3046086022755916E-4, 1.7215897710912286E-4, 2.2417926032948268E-4, 2.882381110269799E-4, 3.6614946597432273E-4, 4.5979578000882737E-4,
5.710947311635238E-4, 7.019627942388466E-4, 8.542770685297848E-4, 0.0010298368141732719, 0.001230326139299173, 0.0014572792580807967, 0.0017120495336512811, 0.0019957833508774444,
0.0023093995935475906, 0.0026535752237636084, 0.0030287371954614726, 0.0034350606417693345, 0.003872473042891695, 0.004273779463476312, 0.003854085486068055, 0.0034654428042082794,
0.0031069260932821844, 0.0027774504027998047, 0.0024757964184719925, 0.0022006362856210955, 0.0019505592571228772, 0.0017240964970243375, 0.0015197445557299247, 0.001335986936817134,
0.0011713136864685823, 0.0010242385494673638, 8.93313737411912E-4, 7.771422330342129E-4, 6.743876948715313E-4, 5.837820728043969E-4, 5.041310934645091E-4, 4.343178207319807E-4,
3.733044819095235E-4, 3.2013281811392165E-4, 2.7392309091215267E-4, 2.338722206547276E-4, 1.9925083793707264E-4, 1.693998770868527E-4 };
final CDSAnalytic spotCDS = FACTORY.makeIMMCDS(TRADE_DATE, Period.ofYears(5));
//start by adjusting the curves to a single index value
final IntrinsicIndexDataBundle adjCurves = PSA.adjustCurves(indexPrice, spotCDS, INDEX_COUPON, YIELD_CURVE, INTRINSIC_DATA);
final LocalDate optionExpiry = TRADE_DATE.plusMonths(1); //option expiry/(effective) start of protection is in 1 month
final double timeToExpiry = ACT365F.getDayCountFraction(TRADE_DATE, optionExpiry);
final CDSAnalytic fwdStartCDS = FACTORY.makeForwardStartingIMMCDS(TRADE_DATE, optionExpiry, Period.ofYears(5));
final CDSAnalytic fwdCDS = FACTORY.makeIMMCDS(TRADE_DATE, Period.ofYears(5));
final IndexOptionPricer pricerISDA = new IndexOptionPricer(fwdCDS, timeToExpiry, YIELD_CURVE, INDEX_COUPON, true);//this uses to ISDA model to compute annuity
final IndexOptionPricer pricerApprox = new IndexOptionPricer(fwdCDS, timeToExpiry, YIELD_CURVE, INDEX_COUPON); //this uses the credit triangle to compute annuity
//default three random names
final IntrinsicIndexDataBundle adjCurvesWithDefaults = adjCurves.withDefault(0, 34, 111);
final double atmFwdVal = INDEX_CAL.defaultAdjustedForwardIndexValue(fwdStartCDS, timeToExpiry, YIELD_CURVE, INDEX_COUPON, adjCurvesWithDefaults);
final double atmK = (new MarketQuoteConverter()).pufToQuotedSpread(fwdCDS, INDEX_COUPON, YIELD_CURVE.withOffset(timeToExpiry), atmFwdVal);
final double vol = 0.5;
for (int i = 0; i < 100; i++) {
final double k = 0.002 + 0.02 * i / 100.;
final double payer1 = pricerISDA.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, true);
final double payer2 = pricerApprox.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, true);
final double rec1 = pricerISDA.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, false);
final double rec2 = pricerApprox.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, false);
final double otm = pricerISDA.getOptionPriceForSpreadQuotedIndex(atmFwdVal, vol, k, k >= atmK);
if (PRINT) {
System.out.println(k + "\t" + payer1 + "\t" + payer2);
// System.out.println(otm + ",");
}
assertEquals(payer1, payer2, 1e-6 + payer1 * 1e-4);
assertEquals(rec1, rec2, 1e-6 + rec1 * 1e-4);
assertEquals(expOTM[i], otm, expOTM[i] * 1e-12);
}
}
}