/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.volatilityswap; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import org.testng.annotations.Test; import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository; import com.opengamma.analytics.math.FunctionUtils; import com.opengamma.analytics.math.function.DoubleFunction1D; import com.opengamma.analytics.math.function.RealPolynomialFunction1D; import com.opengamma.analytics.math.interpolation.PolynomialsLeastSquaresFitter; import com.opengamma.analytics.math.regression.LeastSquaresRegressionResult; import com.opengamma.util.test.TestGroup; /** * */ @Test(groups = TestGroup.UNIT) public class CarrLeeSeasonedSyntheticVolatilitySwapCalculatorTest { private static final CarrLeeSeasonedSyntheticVolatilitySwapCalculator calculator = new CarrLeeSeasonedSyntheticVolatilitySwapCalculator(); private static final double EPS = 1.e-10; /** * */ @Test public void putModTest() { final double s0 = 1.35; final double time = 3. / 12.; final double timeS = 1. / 12.; final double[] volData = new double[] {8. / 100., 7.35 / 100., 6.75 / 100., 6.65 / 100., 6.85 / 100. }; final double rd = 6. * 0.01; final double rf = 8.2 * 0.01; final double rQV = 6.7 * 6.7; final double forward = s0 * Math.exp((rd - rf) * time); final double[] deltas = new double[] {10.0, 25.0, 50.0, 25.0, 10.0 }; final int nPoints = 40; final double[] strikesData = new double[5]; strikesData[0] = BlackFormulaRepository.strikeForDelta(forward, -deltas[0] / 100., time, volData[0], false); strikesData[1] = BlackFormulaRepository.strikeForDelta(forward, -deltas[1] / 100., time, volData[1], false); strikesData[2] = s0 * Math.exp((rd - rf) * time + 0.5 * volData[2] * volData[2] * time); strikesData[3] = BlackFormulaRepository.strikeForDelta(forward, deltas[3] / 100., time, volData[3], true); strikesData[4] = BlackFormulaRepository.strikeForDelta(forward, deltas[4] / 100., time, volData[4], true); final PolynomialsLeastSquaresFitter fitter = new PolynomialsLeastSquaresFitter(); final LeastSquaresRegressionResult polyRes = fitter.regress(strikesData, volData, 2); final DoubleFunction1D func = new RealPolynomialFunction1D(polyRes.getBetas()); final double tmp = 3.0 * Math.sqrt(rQV * timeS) / 100.; strikesData[0] = Math.min(strikesData[0], forward * Math.exp(-tmp)); strikesData[4] = Math.max(strikesData[4], forward * Math.exp(tmp)); final double deltaK = (strikesData[4] - strikesData[0]) / nPoints; final double[] strikes = new double[nPoints + 1]; for (int i = 0; i < nPoints; ++i) { strikes[i] = strikesData[0] + deltaK * i; } strikes[nPoints] = strikesData[4]; final int index = FunctionUtils.getLowerBoundIndex(strikes, forward); //wrong if strikes[i] == forward final int nPuts = index + 1; final int nCalls = nPoints - index; final double[] putStrikes = new double[nPuts]; final double[] callStrikes = new double[nCalls]; final double[] putVols = new double[nPuts]; final double[] callVols = new double[nCalls]; System.arraycopy(strikes, 0, putStrikes, 0, nPuts); System.arraycopy(strikes, index + 1, callStrikes, 0, nCalls); final double[] putVolsExp = new double[] {0.0832552878863839, 0.0820658728548438, 0.0809211151890594, 0.0798210148890308, 0.0787655719547581, 0.0777547863862412, 0.0767886581834796, 0.0758671873464743, 0.0749903738752249, 0.0741582177697308, 0.0733707190299930, 0.0726278776560110, 0.0719296936477843, 0.0712761670053135, 0.0706672977285990, 0.0701030858176398, 0.0695835312724364, 0.0691086340929894, 0.0686783942792977, 0.0682928118313618 }; final double[] callVolsExp = new double[] {0.0679518867491817, 0.0676556190327574, 0.0674040086820889, 0.0671970556971769, 0.0670347600780201, 0.0669171218246191, 0.0668441409369740, 0.0668158174150847, 0.0668321512589506, 0.0668931424685729, 0.0669987910439511, 0.0671490969850845, 0.0673440602919743, 0.0675836809646199, 0.0678679590030208, 0.0681968944071775, 0.0685704871770906, 0.0689887373127590, 0.0694516448141837, 0.0699592096813638, 0.0705114319142996 }; for (int i = 0; i < nPuts; ++i) { putVols[i] = func.evaluate(putStrikes[i]); assertEquals(putVolsExp[i], putVols[i], 1.e-12); } for (int i = 0; i < nCalls; ++i) { callVols[i] = func.evaluate(callStrikes[i]); assertEquals(callVolsExp[i], callVols[i], 1.e-12); } final double[] putWeightsExp = new double[] {0.493726715658473, 0.628628866130493, 0.827612209950565, 1.11222515249467, 1.50711421506441, 2.03860572903232, 2.73241720665300, 3.61052218485728, 4.68736633875543, 5.96582751113782, 7.43348794602065, 9.05989559493323, 10.7954886714047, 12.5727165376716, 14.3096121480158, 15.9156924643967, 17.2996501421644, 18.3779367534582, 19.0831073735407, 19.3525727276836 }; final double[] callWeightsExp = new double[] {19.2240572893118, 18.6552385457138, 17.7038720939929, 16.4322515091469, 14.9186283170502, 13.2493060912483, 11.5106812103248, 9.78221050947683, 8.13103318509909, 6.60864038436859, 5.24964209388712, 4.07238883753048, 3.08100597371603, 2.26830633613504, 1.61905459910282, 1.11313960277885, 0.728336809569212, 0.442480546242545, 0.234989746438180, 0.0877860725848968, -0.0142962965209807 }; final double[] putPricesExp = new double[] {0.00198741313032555, 0.00220991254128430, 0.00246339326567738, 0.00275207254083201, 0.00308064108585229, 0.00345428045427093, 0.00387867000987358, 0.00435998037307128, 0.00490485005152944, 0.00552034205138247, 0.00621387763252368, 0.00699314508220150, 0.00786598247302106, 0.00884023484680213, 0.00992358807879118, 0.0111233837243506, 0.0124464212698491, 0.0138987561867745, 0.0154855037767936, 0.0172106597478404 }; final double[] callPricesExp = new double[] {0.0168498807190164, 0.0150193614714781, 0.0133312017321700, 0.0117838205591906, 0.0103742071364744, 0.00909801123052711, 0.00794968063068208, 0.00692263713685887, 0.00600948031305543, 0.00520220691869372, 0.00449243374340727, 0.00387161243813322, 0.00333122666620575, 0.00286296421789065, 0.00245885933057685, 0.00211140303779320, 0.00181362168791471, 0.00155912565745443, 0.00134213164620579, 0.00115746277012070, 0.00100053101036114 }; final double cashExp = 3.300124997670261; final double optionTotalExp = 3.441793507870262; final double fairValueExp = 6.741918505540522; final VolatilitySwapCalculatorResult res = calculator.evaluate(s0, putStrikes, callStrikes, time, timeS, rd, rf, putVols, callVols, rQV); final double[] putWeights = res.getPutWeights(); final double[] callWeights = res.getCallWeights(); final double[] putPrices = res.getPutPrices(); final double[] callPrices = res.getCallPrices(); for (int i = 0; i < nPuts; ++i) { assertEquals(putWeightsExp[i], putWeights[i], Math.max(Math.abs(putWeightsExp[i]), 1.) * EPS); assertEquals(putPricesExp[i], putPrices[i], Math.max(Math.abs(putPricesExp[i]), 1.) * EPS); } for (int i = 0; i < nCalls; ++i) { assertEquals(callWeightsExp[i], callWeights[i], Math.max(Math.abs(callWeightsExp[i]), 1.) * EPS); assertEquals(callPricesExp[i], callPrices[i], Math.max(Math.abs(callPricesExp[i]), 1.) * EPS); } assertEquals(cashExp, res.getCash(), Math.max(Math.abs(cashExp), 1.) * EPS); assertEquals(optionTotalExp, res.getOptionTotal(), Math.max(Math.abs(optionTotalExp), 1.) * EPS); assertEquals(fairValueExp, res.getFairValue(), Math.max(Math.abs(fairValueExp), 1.) * EPS); assertEquals(0., res.getStraddleWeight()); assertEquals(0., res.getStraddlePrice()); } /** * */ @Test public void noModTest() { final double s0 = 100.; final double time = 0.5; final double timeS = 0.6; final double forward = 105.; final double rate = Math.log(forward / s0) / time; final double rQV = 6.7 * 6.7; final double[] putStrikes = new double[] {70., 80., 90., 100. }; final double[] callStrikes = new double[] {110., 120., 130. }; final double[] putVols = new double[] {0.01 * 30., 0.01 * 22., 0.01 * 19., 0.01 * 15. }; final double[] callVols = new double[] {0.01 * 11., 0.01 * 11., 0.01 * 9. }; final int nPuts = putStrikes.length; final int nCalls = callStrikes.length; final double[] putWeightsExp = new double[] {0.110713674914064, 0.0874082803121186, 0.0962983872737028, 1.19103156830239 }; final double[] callWeightsExp = new double[] {1.00843583960796, 0.00731609252897404, -0.0370857540745987 }; final double[] putPricesExp = new double[] {0.185069301821616, 0.220173838830819, 0.775393995934012, 2.17655187245948 }; final double[] callPricesExp = new double[] {1.34648036125710, 0.145887392233156, 0.000727204210875021 }; final double cashExp = 4.712645654637311; final double optionTotalExp = 4.065625310961250; final double fairValueExp = 8.778270965598562; final VolatilitySwapCalculatorResult res = calculator.evaluate(s0, putStrikes, callStrikes, time, timeS, rate, 0., putVols, callVols, rQV); final double[] putWeights = res.getPutWeights(); final double[] callWeights = res.getCallWeights(); final double[] putPrices = res.getPutPrices(); final double[] callPrices = res.getCallPrices(); for (int i = 0; i < nPuts; ++i) { assertEquals(putWeightsExp[i], putWeights[i], Math.max(Math.abs(putWeightsExp[i]), 1.) * EPS); assertEquals(putPricesExp[i], putPrices[i], Math.max(Math.abs(putPricesExp[i]), 1.) * EPS); } for (int i = 0; i < nCalls; ++i) { assertEquals(callWeightsExp[i], callWeights[i], Math.max(Math.abs(callWeightsExp[i]), 1.) * EPS); assertEquals(callPricesExp[i], callPrices[i], Math.max(Math.abs(callPricesExp[i]), 1.) * EPS); } assertEquals(cashExp, res.getCash(), Math.max(Math.abs(cashExp), 1.) * EPS); assertEquals(optionTotalExp, res.getOptionTotal(), Math.max(Math.abs(optionTotalExp), 1.) * EPS); assertEquals(fairValueExp, res.getFairValue(), Math.max(Math.abs(fairValueExp), 1.) * EPS); assertEquals(0., res.getStraddleWeight()); assertEquals(0., res.getStraddlePrice()); } /** * */ @Test public void callModTest() { final double s0 = 93.; final double time = 3. / 12.; final double timeS = 9. / 12.; final double[] volData = new double[] {5. / 100., 5.35 / 100., 3.75 / 100., 6.65 / 100., 9.85 / 100. }; final double rd = 0.01 * 11.; final double rf = 0.01 * 7.6; final double rQV = 11. * 11.; final double forward = s0 * Math.exp((rd - rf) * time); final double[] deltas = new double[] {10.0, 25.0, 50.0, 25.0, 10.0 }; final int nPoints = 30; final double[] strikesData = new double[5]; strikesData[0] = BlackFormulaRepository.strikeForDelta(forward, -deltas[0] / 100., time, volData[0], false); strikesData[1] = BlackFormulaRepository.strikeForDelta(forward, -deltas[1] / 100., time, volData[1], false); strikesData[2] = s0 * Math.exp((rd - rf) * time + 0.5 * volData[2] * volData[2] * time); strikesData[3] = BlackFormulaRepository.strikeForDelta(forward, deltas[3] / 100., time, volData[3], true); strikesData[4] = BlackFormulaRepository.strikeForDelta(forward, deltas[4] / 100., time, volData[4], true); final PolynomialsLeastSquaresFitter fitter = new PolynomialsLeastSquaresFitter(); final LeastSquaresRegressionResult polyRes = fitter.regress(strikesData, volData, 2); final DoubleFunction1D func = new RealPolynomialFunction1D(polyRes.getBetas()); final double tmp = 3.0 * Math.sqrt(rQV * timeS) / 100.; strikesData[0] = Math.min(strikesData[0], forward * Math.exp(-tmp)); strikesData[4] = Math.max(strikesData[4], forward * Math.exp(tmp)); final double deltaK = (strikesData[4] - strikesData[0]) / nPoints; final double[] strikes = new double[nPoints + 1]; for (int i = 0; i < nPoints; ++i) { strikes[i] = strikesData[0] + deltaK * i; } strikes[nPoints] = strikesData[4]; final int index = FunctionUtils.getLowerBoundIndex(strikes, forward); //wrong if strikes[i] == forward final int nPuts = index + 1; final int nCalls = nPoints - index; final double[] putStrikes = new double[nPuts]; final double[] callStrikes = new double[nCalls]; final double[] putVols = new double[nPuts]; final double[] callVols = new double[nCalls]; System.arraycopy(strikes, 0, putStrikes, 0, nPuts); System.arraycopy(strikes, index + 1, callStrikes, 0, nCalls); for (int i = 0; i < nPuts; ++i) { putVols[i] = func.evaluate(putStrikes[i]); } for (int i = 0; i < nCalls; ++i) { callVols[i] = func.evaluate(callStrikes[i]); } final double[] putWeightsExp = new double[] {0.0249474493801584, 0.0278727573073654, 0.0336865845443051, 0.0434456985487412, 0.0580049953306695, 0.0776753838333571, 0.101919999499214, 0.129212732725244, 0.157145855665674, 0.182792230742784, 0.203236485321117, 0.216128826978535, 0.220109753615713 }; final double[] callWeightsExp = new double[] {0.214660120905978, 0.201745432852571, 0.182119954984908, 0.158359265590309, 0.132753739494195, 0.107329622250425, 0.0836477737614967, 0.0627278642026246, 0.0450752973751680, 0.0307742980521283, 0.0196098079587889, 0.0111885052370588, 0.00504032962375662, 0.000692458429669695, -0.00228441565968938, -0.00425299389125248, -0.00550311359439542, -0.00625606821071965, }; final double[] putPricesExp = new double[] {1.19412646189383, 0.895574027475140, 0.647382415012651, 0.448389905653081, 0.295854285146079, 0.185315976208589, 0.110656287875750, 0.0645371103123034, 0.0394278594390807, 0.0294929145915459, 0.0349762054007776, 0.0813391379385813, 0.304192858788216, }; final double[] callPricesExp = new double[] {0.799795056444808, 0.354129640932978, 0.222288963309337, 0.209442912694495, 0.258327513592855, 0.362120332889372, 0.528740559236759, 0.770056173891117, 1.09820783807841, 1.52424446460731, 2.05768514647612, 2.70647688359544, 3.47710383758826, 4.37473310344900, 5.40334703896093, 6.56584443143825, 7.86410774179835, 9.29903990372668 }; final double cashExp = 9.267876087690137; final double optionTotalExp = 0.778792024176818; final double fairValueExp = 10.046668111866955; final VolatilitySwapCalculatorResult res = calculator.evaluate(s0, putStrikes, callStrikes, time, timeS, rd, rf, putVols, callVols, rQV); final double[] putWeights = res.getPutWeights(); final double[] callWeights = res.getCallWeights(); final double[] putPrices = res.getPutPrices(); final double[] callPrices = res.getCallPrices(); for (int i = 0; i < nPuts; ++i) { assertEquals(putWeightsExp[i], putWeights[i], Math.max(Math.abs(putWeightsExp[i]), 1.) * EPS); assertEquals(putPricesExp[i], putPrices[i], Math.max(Math.abs(putPricesExp[i]), 1.) * EPS); } for (int i = 0; i < nCalls; ++i) { assertEquals(callWeightsExp[i], callWeights[i], Math.max(Math.abs(callWeightsExp[i]), 1.) * EPS); assertEquals(callPricesExp[i], callPrices[i], Math.max(Math.abs(callPricesExp[i]), 1.) * EPS); } assertEquals(cashExp, res.getCash(), Math.max(Math.abs(cashExp), 1.) * EPS); assertEquals(optionTotalExp, res.getOptionTotal(), Math.max(Math.abs(optionTotalExp), 1.) * EPS); assertEquals(fairValueExp, res.getFairValue(), Math.max(Math.abs(fairValueExp), 1.) * EPS); assertEquals(0., res.getStraddleWeight()); assertEquals(0., res.getStraddlePrice()); } /** * */ @Test public void errorTest() { final double timeToExpiry = 3.; final double timeSeasoned = 2.; final double spot = 45.; // final double forward = 102.; // final double interestRate = Math.log(forward / spot) / timeToExpiry; final double interestRate = 0.025; final double dividend = 0.015; final double[] putStrikes = new double[] {35., 40., 45, }; final double[] callStrikes = new double[] {50., 55. }; final double rvReturns = 100.; final int nCalls = callStrikes.length; final int nPuts = putStrikes.length; final double[] callVols = new double[nCalls]; final double[] putVols = new double[nPuts]; for (int i = 0; i < nCalls; ++i) { callVols[i] = (0.5 - 0.005 * (callStrikes[i] - 50.)); } for (int i = 0; i < nPuts; ++i) { putVols[i] = (0.5 - 0.005 * (putStrikes[i] - 50.)); } try { calculator.evaluate(spot, putStrikes, new double[] {50., 55., 60. }, timeToExpiry, timeSeasoned, interestRate, dividend, putVols, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot, new double[] {30., 35., 40., 45, }, callStrikes, timeToExpiry, timeSeasoned, interestRate, dividend, putVols, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(-spot, putStrikes, callStrikes, timeToExpiry, timeSeasoned, interestRate, dividend, putVols, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot, putStrikes, callStrikes, -timeToExpiry, timeSeasoned, interestRate, dividend, putVols, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot, putStrikes, callStrikes, timeToExpiry, -timeSeasoned, interestRate, dividend, putVols, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot, putStrikes, callStrikes, timeToExpiry, timeSeasoned, interestRate, dividend, putVols, callVols, -rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot, putStrikes, new double[] {-50., 55. }, timeToExpiry, timeSeasoned, interestRate, dividend, putVols, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot, new double[] {-10., -5., 0., }, new double[] {5., 10. }, timeToExpiry, timeSeasoned, interestRate, dividend, putVols, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot, new double[] {35., 40., 43, }, callStrikes, timeToExpiry, timeSeasoned, interestRate, dividend, putVols, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot, putStrikes, new double[] {50., 60. }, timeToExpiry, timeSeasoned, interestRate, dividend, putVols, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot - 10., putStrikes, callStrikes, timeToExpiry, timeSeasoned, interestRate, dividend, putVols, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot + 10., putStrikes, callStrikes, timeToExpiry, timeSeasoned, interestRate, dividend, putVols, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot, putStrikes, callStrikes, timeToExpiry, timeSeasoned, interestRate, dividend, putVols, new double[] {-15., 20. }, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { calculator.evaluate(spot, putStrikes, callStrikes, timeToExpiry, timeSeasoned, interestRate, dividend, new double[] {-15., 20., 10 }, callVols, rvReturns); throw new RuntimeException(); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } } }