/** * 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.CDSIndexPrvider.INDEX_TENORS; import static com.opengamma.analytics.financial.credit.options.YieldCurveProvider.ISDA_EUR_20140206; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; 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.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.financial.model.volatility.BlackFormulaRepository; import com.opengamma.util.test.TestGroup; /** * */ @Test(groups = TestGroup.UNIT) public class BlackIndexOptionPricerTest extends ISDABaseTest { private static final CDSAnalyticFactory FACTORY = new CDSAnalyticFactory(); private static final MarketQuoteConverter CONVERTER = new MarketQuoteConverter(); 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 Period[] INDEX_PILLARS = INDEX_TENORS; private static final PortfolioSwapAdjustment PSA = new PortfolioSwapAdjustment(); private static final CDSIndexCalculator INDEX_CAL = new CDSIndexCalculator(); static { final double[] ccNodes = new double[] {0.5, 1.0, 3.0, 5.0, 7.0, 10.0 }; final double[] fwdBase = new double[] {0.12, 0.1, 0.03, 0.02, 0.04, 0.06, }; final int nNodes = ccNodes.length; CREDIT_CURVES = new ISDACompliantCreditCurve[INDEX_SIZE]; RECOVERY_RATES = new double[INDEX_SIZE]; for (int i = 0; i < INDEX_SIZE; i++) { RECOVERY_RATES[i] = 0.5 - 0.3 * Math.cos(i); final double[] fwd = new double[nNodes]; for (int k = 0; k < nNodes; ++k) { fwd[k] = fwdBase[k] * (1. + 0.3 * Math.sin(i * k)); } CREDIT_CURVES[i] = ISDACompliantCreditCurve.makeFromForwardRates(ccNodes, fwd); } INTRINSIC_DATA = new IntrinsicIndexDataBundle(CREDIT_CURVES, RECOVERY_RATES); } /** * */ @Test public void consistencyTest() { final double tol = 1.e-12; final LocalDate optionExpiry = getNextIMMDate(TRADE_DATE).minusDays(1); final double timeToExpiry = ACT365F.getDayCountFraction(TRADE_DATE, optionExpiry); final CDSAnalytic fwdCDX = FACTORY.makeCDX(optionExpiry, Period.ofYears(5)); final CDSAnalytic fwdStartingCDX = fwdCDX.withOffset(timeToExpiry); // final CDSAnalytic fwdStartingCDS = FACTORY.makeForwardStartingIMMCDS(TRADE_DATE, optionExpiry, Period.ofYears(5)); final double[] indexPUF = new double[] {0.0556, 0.0582, 0.0771, 0.0652 }; final CDSAnalytic[] indexCDS = FACTORY.makeCDX(TRADE_DATE, INDEX_PILLARS); final IntrinsicIndexDataBundle adjCurves = PSA.adjustCurves(indexPUF, indexCDS, INDEX_COUPON, YIELD_CURVE, INTRINSIC_DATA); final double fwdSpread = INDEX_CAL.defaultAdjustedForwardSpread(fwdStartingCDX, timeToExpiry, YIELD_CURVE, adjCurves); final double fwdAnnuity = INDEX_CAL.indexAnnuity(fwdStartingCDX, YIELD_CURVE, adjCurves); final BlackIndexOptionPricer pricerWithFwd = new BlackIndexOptionPricer(fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity); final BlackIndexOptionPricer pricerNoFwd = new BlackIndexOptionPricer(fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, adjCurves); final boolean[] payer = new boolean[] {true, false }; final double vol = 0.4; final double strikeSpread = 0.015; final ISDACompliantYieldCurve fwdYC = YIELD_CURVE.withOffset(timeToExpiry); final double strikePrice = CONVERTER.quotedSpreadToPUF(fwdCDX, INDEX_COUPON, fwdYC, strikeSpread); final IndexOptionStrike exPriceAmount = new ExerciseAmount(strikePrice); final IndexOptionStrike exSpreadAmount = new SpreadBasedStrike(strikeSpread); for (int i = 0; i < 2; ++i) { /** * Consistency between option pricing methods */ final double premFromPrice = pricerWithFwd.getOptionPremium(exPriceAmount, vol, payer[i]); final double premFromSpread = pricerWithFwd.getOptionPremium(exSpreadAmount, vol, payer[i]); final double premFromPriceBare = pricerWithFwd.getOptionPriceForPriceQuotedIndex(strikePrice, vol, payer[i]); final double premFromSpreadBare = pricerWithFwd.getOptionPriceForSpreadQuotedIndex(strikeSpread, vol, payer[i]); assertEquals(premFromPrice, premFromSpread, tol); assertEquals(premFromSpread, premFromPriceBare, tol); assertEquals(premFromPriceBare, premFromSpreadBare, tol); /** * Consistency between constructors */ final double PremFromPriceNoFwd = pricerNoFwd.getOptionPremium(exPriceAmount, vol, payer[i]); assertEquals(premFromPrice, PremFromPriceNoFwd, tol); /** * Consistency with implied vol */ final double vol1 = pricerWithFwd.getImpliedVolatility(exPriceAmount, premFromPrice, payer[i]); final double vol2 = pricerWithFwd.getImpliedVolatility(exSpreadAmount, premFromSpread, payer[i]); final double vol3 = pricerWithFwd.getImpliedVolForExercisePrice(strikePrice, premFromPriceBare, payer[i]); final double vol4 = pricerWithFwd.getImpliedVolForSpreadStrike(strikeSpread, premFromSpreadBare, payer[i]); assertEquals(vol, vol1, tol); assertEquals(vol, vol2, tol); assertEquals(vol, vol3, tol); assertEquals(vol, vol4, tol); } } /** * */ @SuppressWarnings("unused") @Test public void limitTest() { final LocalDate optionExpiry = getNextIMMDate(TRADE_DATE).minusDays(1); final double timeToExpiry = ACT365F.getDayCountFraction(TRADE_DATE, optionExpiry); final CDSAnalytic fwdCDX = FACTORY.makeCDX(optionExpiry, Period.ofYears(5)); final CDSAnalytic fwdStartingCDX = fwdCDX.withOffset(timeToExpiry); final double[] indexPUF = new double[] {0.0556, 0.0582, 0.0771, 0.0652 }; final CDSAnalytic[] indexCDS = FACTORY.makeCDX(TRADE_DATE, INDEX_PILLARS); final IntrinsicIndexDataBundle adjCurves = PSA.adjustCurves(indexPUF, indexCDS, INDEX_COUPON, YIELD_CURVE, INTRINSIC_DATA); final double fwdSpread = INDEX_CAL.defaultAdjustedForwardSpread(fwdStartingCDX, timeToExpiry, YIELD_CURVE, adjCurves); final double fwdAnnuity = INDEX_CAL.indexAnnuity(fwdStartingCDX, YIELD_CURVE, adjCurves); final BlackIndexOptionPricer pricerWithFwd = new BlackIndexOptionPricer(fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity); final double modStrikeLimit = INDEX_COUPON + fwdCDX.getLGD() / fwdAnnuity; final double vol = 0.4; final double payLargeSpLimit = fwdAnnuity * BlackFormulaRepository.price(fwdSpread, modStrikeLimit, timeToExpiry, vol, true); final double recLargeSpLimit = fwdAnnuity * BlackFormulaRepository.price(fwdSpread, modStrikeLimit, timeToExpiry, vol, false); final double largeStrikeSp = 75.0; final double payLargeStrikeSp = pricerWithFwd.getOptionPriceForSpreadQuotedIndex(largeStrikeSp, vol, true); final double recLargeStrikeSp = pricerWithFwd.getOptionPriceForSpreadQuotedIndex(largeStrikeSp, vol, false); assertEquals(payLargeSpLimit, payLargeStrikeSp, 1.e-12); assertEquals(recLargeSpLimit, recLargeStrikeSp, 1.e-3);//larger strike spread ends up with failure in root-finding /** * Exception expected */ final double negativeTime = -0.5; try { new BlackIndexOptionPricer(fwdCDX, negativeTime, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity); throw new RuntimeException(); } catch (final Exception e) { assertEquals("timeToExpiry must be positive. Value given " + negativeTime, e.getMessage()); } try { new BlackIndexOptionPricer(fwdStartingCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, fwdAnnuity); throw new RuntimeException(); } catch (final Exception e) { assertEquals("fwdCDS should be a Forward CDS", e.getMessage()); } final double negativeCoupon = -150.0 * 1.0e-4; try { new BlackIndexOptionPricer(fwdCDX, timeToExpiry, YIELD_CURVE, negativeCoupon, fwdSpread, fwdAnnuity); throw new RuntimeException(); } catch (final Exception e) { assertEquals("indexCoupon must be positive", e.getMessage()); } final double negativeFwdSp = -0.5; try { new BlackIndexOptionPricer(fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, negativeFwdSp, fwdAnnuity); throw new RuntimeException(); } catch (final Exception e) { assertEquals("defaultAdjustedFwdSpread must be positive", e.getMessage()); } final double negativeAnn = -0.3; try { new BlackIndexOptionPricer(fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, negativeAnn); throw new RuntimeException(); } catch (final Exception e) { assertEquals("pvFwdAnnuity must be positive", e.getMessage()); } final double largeAnn = fwdCDX.getProtectionEnd() * 2.0; try { new BlackIndexOptionPricer(fwdCDX, timeToExpiry, YIELD_CURVE, INDEX_COUPON, fwdSpread, largeAnn); throw new RuntimeException(); } catch (final Exception e) { assertEquals("Value of annuity of " + largeAnn + " is greater than length (in years) of forward CDS. Annuity should be given for unit notional", e.getMessage()); } final double smallStrike = -0.9; try { pricerWithFwd.getOptionPriceForPriceQuotedIndex(smallStrike, vol, true); throw new RuntimeException(); } catch (final Exception e) { assertTrue(e instanceof IllegalArgumentException); } final double largeStrike = 0.9; try { pricerWithFwd.getOptionPriceForPriceQuotedIndex(largeStrike, vol, true); throw new RuntimeException(); } catch (final Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { pricerWithFwd.getOptionPriceForSpreadQuotedIndex(smallStrike, vol, true); throw new RuntimeException(); } catch (final Exception e) { assertTrue(e instanceof IllegalArgumentException); } } }