/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.interestrate.capletstripping; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import java.util.Arrays; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.Test; import com.opengamma.analytics.financial.model.volatility.surface.VolatilitySurface; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount; import com.opengamma.analytics.math.function.Function2D; import com.opengamma.analytics.math.matrix.DoubleMatrix2D; import com.opengamma.analytics.math.surface.FunctionalDoublesSurface; import com.opengamma.analytics.util.AssertMatrix; import com.opengamma.util.monitor.OperationTimer; import com.opengamma.util.test.TestGroup; import com.opengamma.util.tuple.DoublesPair; /** * */ @Test(groups = TestGroup.UNIT) public class MultiCapFloorPricerTest extends CapletStrippingSetup { private static final Logger LOGGER = LoggerFactory.getLogger(MultiCapFloorPricerTest.class); private static final VolatilitySurface s_VolSurface; static { Function2D<Double, Double> vol = new Function2D<Double, Double>() { @Override public Double evaluate(Double t, Double k) { return 0.3 - 0.5 * k + 0.8 * Math.exp(-0.3 * t); } }; s_VolSurface = new VolatilitySurface(FunctionalDoublesSurface.from(vol)); } /** * Test prices from {@link MultiCapFloorPricer} against those from {@link CapFloorPricer} when presented with * a {@link VolatilitySurface} */ @Test public void volSurfacePriceTest() { MulticurveProviderDiscount yieldCurve = getYieldCurves(); List<CapFloor> caps = getAllCaps(); int n = caps.size(); MultiCapFloorPricer multiPricer = new MultiCapFloorPricer(caps, yieldCurve); double[] prices = multiPricer.price(s_VolSurface); assertEquals("wrong number of prices", n, prices.length); for (int j = 0; j < n; j++) { CapFloorPricer pricer = new CapFloorPricer(caps.get(j), yieldCurve); double p = pricer.price(s_VolSurface); assertEquals(p, prices[j], 1e-13); } } /** * Test prices from {@link MultiCapFloorPricer} against those from {@link CapFloorPricer} when presented with * cap volatilities */ @Test public void capVolPriceTest() { MulticurveProviderDiscount yieldCurve = getYieldCurves(); List<CapFloor> caps = getAllCaps(); double[] capVols = getAllCapVols(); int n = caps.size(); MultiCapFloorPricer multiPricer = new MultiCapFloorPricer(caps, yieldCurve); double[] prices = multiPricer.price(capVols); assertEquals("wrong number of prices", n, prices.length); for (int j = 0; j < n; j++) { CapFloorPricer pricer = new CapFloorPricer(caps.get(j), yieldCurve); double p = pricer.price(capVols[j]); assertEquals(p, prices[j], 1e-13); } } @Test public void volSurfaceImpVolTest() { MulticurveProviderDiscount yieldCurve = getYieldCurves(); List<CapFloor> caps = getAllCaps(); int n = caps.size(); MultiCapFloorPricer multiPricer = new MultiCapFloorPricer(caps, yieldCurve); double[] vols = multiPricer.impliedVols(s_VolSurface); assertEquals("wrong number of vols", n, vols.length); for (int j = 0; j < n; j++) { CapFloorPricer pricer = new CapFloorPricer(caps.get(j), yieldCurve); double v = pricer.impliedVol(s_VolSurface); assertEquals(v, vols[j], 1e-9); } } @Test public void vegaFromCapVolsTest() { MulticurveProviderDiscount yieldCurve = getYieldCurves(); double[] capVols = getAllCapVols(); List<CapFloor> caps = getAllCaps(); int n = caps.size(); MultiCapFloorPricer multiPricer = new MultiCapFloorPricer(caps, yieldCurve); double[] vega = multiPricer.vega(capVols); assertEquals("wrong number of vegas", n, vega.length); for (int j = 0; j < n; j++) { CapFloorPricer pricer = new CapFloorPricer(caps.get(j), yieldCurve); double v = pricer.vega(capVols[j]); assertEquals(v, vega[j], 1e-9); } } /** * compute vega by finite difference and compare it too what is calculated by MultiCapFloorPricer */ @Test public void vegaTest() { double eps = 1e-5; MulticurveProviderDiscount yieldCurve = getYieldCurves(); List<CapFloor> caps = getAllCaps(); int n = caps.size(); MultiCapFloorPricer multiPricer = new MultiCapFloorPricer(caps, yieldCurve); int m = multiPricer.getNumCaplets(); double[] capletVols = new double[m]; Arrays.fill(capletVols, 0.5); double[] capletPrices = multiPricer.priceFromCapletVols(capletVols); // calculate vega by finite difference double[][] fdVega = new double[n][m]; for (int j = 0; j < m; j++) { double temp = capletVols[j]; capletVols[j] += eps; double[] up = multiPricer.priceFromCapletVols(capletVols); capletVols[j] -= 2 * eps; double[] down = multiPricer.priceFromCapletVols(capletVols); capletVols[j] = temp; for (int i = 0; i < n; i++) { fdVega[i][j] = (up[i] - down[i]) / 2 / eps; } } double[][] vega = multiPricer.vegaFromCapletVols(capletVols).getData(); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { assertEquals(i + "\t" + j, fdVega[i][j], vega[i][j], Math.max(1e-12, 1e-8 * capletPrices[i])); } } } /** * the cap vol-vega is the sensitivity of a cap's implied volatility to the volatility of its constituent caplets. * Here we calculate it by finite difference (which takes a while as there are 109 caps and 823 unique caplets), * and compare this against what {@link MultiCapFloorPricer} produces */ @Test public void capVolVegaTest() { double eps = 1e-5; MulticurveProviderDiscount yieldCurve = getYieldCurves(); List<CapFloor> caps = getAllCaps(); int n = caps.size(); MultiCapFloorPricer multiPricer = new MultiCapFloorPricer(caps, yieldCurve); int m = multiPricer.getNumCaplets(); double[] capletVols = new double[m]; Arrays.fill(capletVols, 0.35); double[] capVols = multiPricer.impliedVols(multiPricer.priceFromCapletVols(capletVols)); assertEquals(n, capVols.length); for (int i = 0; i < n; i++) { assertEquals("cap vol", 0.35, capVols[i], 1e-9); } DoubleMatrix2D capVolVega = multiPricer.capVolVega(capletVols); // calculate vega by finite difference DoubleMatrix2D fdVega = new DoubleMatrix2D(n, m); double[][] data = fdVega.getData(); for (int j = 0; j < m; j++) { double temp = capletVols[j]; capletVols[j] += eps; double[] up = multiPricer.impliedVols(multiPricer.priceFromCapletVols(capletVols)); capletVols[j] -= 2 * eps; double[] down = multiPricer.impliedVols(multiPricer.priceFromCapletVols(capletVols)); capletVols[j] = temp; for (int i = 0; i < n; i++) { data[i][j] = (up[i] - down[i]) / 2 / eps; } } //fdVega is a finite difference calculation inside which is a root-finding (for the implied vol), hence the tolerance //is fairly low AssertMatrix.assertEqualsMatrix(fdVega, capVolVega, 5e-6); } @Test public void accessTest() { MulticurveProviderDiscount yieldCurve = getYieldCurves(); List<CapFloor> caps = getCaps(0); // this includes the 10 year cap int n = caps.size(); MultiCapFloorPricer multiPricer = new MultiCapFloorPricer(caps, yieldCurve); assertEquals(n, multiPricer.getNumCaps()); assertEquals(39, multiPricer.getCapletExpiries().length); //4*10 - 1 assertEquals(1, multiPricer.getStrikes().length); assertEquals(1, multiPricer.getCapStartTimes().length); //one common start time assertEquals(n, multiPricer.getCapEndTimes().length); } @Test public void capletFwdTest() { MulticurveProviderDiscount yieldCurve = getYieldCurves(); List<CapFloor> caps = getATMCaps(); MultiCapFloorPricer multiPricer = new MultiCapFloorPricer(caps, yieldCurve); double[] exp = multiPricer.getCapletExpiries(); double[] fwd = multiPricer.getCapletForwardRates(); DoublesPair[] points = multiPricer.getExpiryStrikeArray(); int nCaplets = multiPricer.getNumCaplets(); int nExp = exp.length; assertEquals("fwd length", nExp, fwd.length); assertEquals("points length", nCaplets, points.length); } @Test public void capIntrTest() { MulticurveProviderDiscount yieldCurve = getYieldCurves(); List<CapFloor> caps = getATMCaps(); MultiCapFloorPricer multiPricer = new MultiCapFloorPricer(caps, yieldCurve); double[] intrVal = multiPricer.getIntrinsicCapValues(); int nCaps = caps.size(); assertEquals(nCaps, intrVal.length); for (int i = 0; i < nCaps; i++) { assertTrue(intrVal[i] > 0.0); //Cap ATM means the strike equals the swap rate - individual caplets may be ITM } } @SuppressWarnings("unused") @Test public void priceTimeTest() { int warmup = 1; int benchmarkCycles = 0; MulticurveProviderDiscount yieldCurve = getYieldCurves(); List<CapFloor> caps = getAllCaps(); int nCaps = caps.size(); MultiCapFloorPricer multiPricer = new MultiCapFloorPricer(caps, yieldCurve); CapFloorPricer[] pricers = new CapFloorPricer[nCaps]; // List<List<CapFloor>> allCaps = new ArrayList<>(nStrikes); for (int i = 0; i < nCaps; i++) { pricers[i] = new CapFloorPricer(caps.get(i), yieldCurve); } for (int count = 0; count < warmup; count++) { double[] prices = multiPricer.price(s_VolSurface); } if (benchmarkCycles > 0) { OperationTimer timer = new OperationTimer(LOGGER, "processing {} cycles on timeTest - multiPricer", benchmarkCycles); for (int count = 0; count < benchmarkCycles; count++) { double[] prices = multiPricer.price(s_VolSurface); } timer.finished(); } for (int count = 0; count < warmup; count++) { for (int i = 0; i < nCaps; i++) { double p = pricers[i].price(s_VolSurface); } } if (benchmarkCycles > 0) { OperationTimer timer2 = new OperationTimer(LOGGER, "processing {} cycles on timeTest - single Pricer", benchmarkCycles); for (int count = 0; count < benchmarkCycles; count++) { for (int i = 0; i < nCaps; i++) { double p = pricers[i].price(s_VolSurface); } } timer2.finished(); } } // since finding implied vol is expensive compared to finding a price (10-20 times more), there is little (relative) difference here. This is why it is // quicker to to caplet stripping based on price (weighted by vega) rather than implied vol. @SuppressWarnings("unused") @Test public void volTimeTest() { int warmup = 1; int benchmarkCycles = 0; MulticurveProviderDiscount yieldCurve = getYieldCurves(); List<CapFloor> caps = getAllCaps(); int nCaps = caps.size(); MultiCapFloorPricer multiPricer = new MultiCapFloorPricer(caps, yieldCurve); CapFloorPricer[] pricers = new CapFloorPricer[nCaps]; for (int i = 0; i < nCaps; i++) { pricers[i] = new CapFloorPricer(caps.get(i), yieldCurve); } for (int count = 0; count < warmup; count++) { double[] vols = multiPricer.impliedVols(s_VolSurface); } if (benchmarkCycles > 0) { OperationTimer timer = new OperationTimer(LOGGER, "processing {} cycles on timeTest - multiPricer", benchmarkCycles); for (int count = 0; count < benchmarkCycles; count++) { double[] vols = multiPricer.impliedVols(s_VolSurface); } timer.finished(); } for (int count = 0; count < warmup; count++) { for (int i = 0; i < nCaps; i++) { double v = pricers[i].impliedVol(s_VolSurface); } } if (benchmarkCycles > 0) { OperationTimer timer2 = new OperationTimer(LOGGER, "processing {} cycles on timeTest - single Pricer", benchmarkCycles); for (int count = 0; count < benchmarkCycles; count++) { for (int i = 0; i < nCaps; i++) { double v = pricers[i].impliedVol(s_VolSurface); } } timer2.finished(); } } }