/* * (c) Copyright Christian P. Fries, Germany. All rights reserved. Contact: email@christianfries.com. * * Created on 10.05.2016 */ package net.finmath.functions; import java.text.DecimalFormat; import java.util.Arrays; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import net.finmath.optimizer.LevenbergMarquardt; import net.finmath.optimizer.SolverException; /** * @author Christian Fries */ public class AnalyticFormulasTest { @Before public void setUp() throws Exception { } @Test public void testSABRCalibration() throws SolverException { /* * Using Levenberg Marquardt to calibrate SABR */ final double[] givenStrikes = { -0.01, -0.005, 0.0, 0.005, 0.01, 0.02, 0.03 }; final double[] givenVolatilities = { 0.0055, 0.0059, 0.0060, 0.0061, 0.0063, 0.0066, 0.0070 }; final double underlying = 0.0076; final double maturity = 20; double alpha = 0.006; double beta = 0.05; double rho = 0.95; double nu = 0.075; double displacement = 0.02; double[] initialParameters = { alpha, beta, rho, nu, displacement }; double[] targetValues = givenVolatilities; int maxIteration = 500; int numberOfThreads = 8; for(double displacement2 = 0.5; displacement2>0; displacement2 -= 0.001) { givenVolatilities[0] = givenVolatilities[0] + 0.00001; final double displacement3 = displacement2; LevenbergMarquardt lm = new LevenbergMarquardt(initialParameters, targetValues, maxIteration, numberOfThreads) { @Override public void setValues(double[] parameters, double[] values) throws SolverException { for(int strikeIndex = 0; strikeIndex < givenStrikes.length; strikeIndex++) { double strike = givenStrikes[strikeIndex]; values[strikeIndex] = AnalyticFormulas.sabrBerestyckiNormalVolatilityApproximation(parameters[0] /* alpha */, parameters[1] /* beta */, parameters[2] /* rho */, parameters[3] /* nu */, parameters[4] /* displacement */, underlying, strike, maturity); } } }; lm.setErrorTolerance(1E-16); lm.run(); double[] bestParameters = lm.getBestFitParameters(); // System.out.println(lm.getRootMeanSquaredError() + "\t"); System.out.println(givenVolatilities[0] + "\t" + lm.getRootMeanSquaredError() + "\t" + Arrays.toString(bestParameters)); } } @Test public void testSABRSkewApproximation() { double alpha, beta, rho, nu, displacement, underlying, maturity; for(int testCase = 1; testCase <= 3; testCase++) { switch (testCase) { case 1: default: alpha = 0.1122; beta = 0.9; rho = 0.2; nu = 0.4; displacement = 0.02; underlying = 0.015; maturity = 1; break; case 2: alpha = 0.006; beta = 0.0001; rho = 0.95; nu = 0.075; displacement = 0.02; underlying = 0.0076; maturity = 20; break; case 3: alpha = 0.1122; beta = 0.2; rho = 0.9; nu = 0.4; displacement = 0.02; underlying = 0.015; maturity = 10; break; } double riskReversal = AnalyticFormulas.sabrNormalVolatilitySkewApproximation(alpha, beta, rho, nu, displacement, underlying, maturity); double epsilon = 1E-4; double valueUp = AnalyticFormulas.sabrBerestyckiNormalVolatilityApproximation(alpha, beta, rho, nu, displacement, underlying, underlying+epsilon, maturity); double valueDn = AnalyticFormulas.sabrBerestyckiNormalVolatilityApproximation(alpha, beta, rho, nu, displacement, underlying, underlying-epsilon, maturity); double riskReversalNumerical = (valueUp-valueDn) / 2 / epsilon; System.out.println(riskReversal); System.out.println(riskReversalNumerical); Assert.assertEquals("RR", riskReversalNumerical, riskReversal, 5.0/100/100); } } @Test public void testSABRCurvatureApproximation() { double alpha, beta, rho, nu, displacement, underlying, maturity; for(int testCase = 1; testCase <= 3; testCase++) { switch (testCase) { case 1: default: alpha = 0.1122; beta = 0.9; rho = 0.2; nu = 0.4; displacement = 0.02; underlying = 0.015; maturity = 1; break; case 2: alpha = 0.006; beta = 0.0001; rho = 0.95; nu = 0.075; displacement = 0.02; underlying = 0.0076; maturity = 20; break; case 3: alpha = 0.1122; beta = 0.2; rho = 0.9; nu = 0.4; displacement = 0.02; underlying = 0.015; maturity = 10; break; } double curvature = AnalyticFormulas.sabrNormalVolatilityCurvatureApproximation(alpha, beta, rho, nu, displacement, underlying, maturity); /* * Finite difference approximation of the curvature. */ double epsilon = 1E-4; double value = AnalyticFormulas.sabrBerestyckiNormalVolatilityApproximation(alpha, beta, rho, nu, displacement, underlying, underlying, maturity); double valueUp = AnalyticFormulas.sabrBerestyckiNormalVolatilityApproximation(alpha, beta, rho, nu, displacement, underlying, underlying+epsilon, maturity); double valueDn = AnalyticFormulas.sabrBerestyckiNormalVolatilityApproximation(alpha, beta, rho, nu, displacement, underlying, underlying-epsilon, maturity); double curvatureNumerical = (valueUp - 2.0*value + valueDn) / epsilon / epsilon; System.out.println(curvature); System.out.println(curvatureNumerical); Assert.assertEquals("Curvature", curvatureNumerical, curvature, 5.0/100/100); } } @Test public void testBlackScholesPutCallParityATM() { double initialStockValue = 100.0; Double riskFreeRate = 0.02; Double volatility = 0.20; double optionMaturity = 8.0; double optionStrike = initialStockValue * Math.exp(riskFreeRate * optionMaturity); double valueCall = AnalyticFormulas.blackScholesOptionValue(initialStockValue, riskFreeRate, volatility, optionMaturity, optionStrike); double valuePut = AnalyticFormulas.blackScholesOptionValue(initialStockValue, riskFreeRate, volatility, optionMaturity, optionStrike, false); Assert.assertEquals(valueCall, valuePut, 1E-15); } /** * This test shows the Bachelier risk neutral probabilities * compared to Black-Scholes risk neutral probabilities. * * The Bachelier model allows for negative values of the underlying. * * The parameters in this test are such that value of the ATM option * is similar in both models. * */ @Test public void testBachelierRiskNeutralProbabilities() { DecimalFormat numberFormatStrike = new DecimalFormat(" 0.00% "); DecimalFormat numberFormatValue = new DecimalFormat(" 0.000%"); DecimalFormat numberFormatProbability = new DecimalFormat(" 0.00%; -0.00%"); Double riskFreeRate = 0.01; Double volatilityN = 0.0065; Double volatilityLN = 0.849; Double optionMaturity = 10.0; // We calculate risk neutral probs using a finite difference approx. of Breden-Litzenberger double eps = 1E-8; System.out.println("Strike K" + " \t" + "Bachelier Value " + " \t" + "Bachelier P(S<K) " + " \t" + "Black-Scholes Value " + " \t" + "Black-Scholes P(S<K) " + "\t"); for(double optionStrike = 0.02; optionStrike > -0.10; optionStrike -= 0.005) { double payoffUnit = Math.exp(-riskFreeRate * optionMaturity); double forward = 0.01; double valuePutBa1 = -(forward-optionStrike)*payoffUnit + AnalyticFormulas.bachelierOptionValue(forward, volatilityN, optionMaturity, optionStrike, payoffUnit); double valuePutBa2 = -(forward-optionStrike-eps)*payoffUnit + AnalyticFormulas.bachelierOptionValue(forward, volatilityN, optionMaturity, optionStrike+eps, payoffUnit); double probabilityBachelier = Math.max((valuePutBa2 - valuePutBa1) / eps / payoffUnit,0); double valuePutBS1 = -(forward-optionStrike)*payoffUnit + AnalyticFormulas.blackScholesGeneralizedOptionValue(forward, volatilityLN, optionMaturity, optionStrike, payoffUnit); double valuePutBS2 = -(forward-optionStrike-eps)*payoffUnit + AnalyticFormulas.blackScholesGeneralizedOptionValue(forward, volatilityLN, optionMaturity, optionStrike+eps, payoffUnit); double probabilityBlackScholes = Math.max((valuePutBS2 - valuePutBS1) / eps / payoffUnit,0); System.out.println( numberFormatStrike.format(optionStrike) + " \t" + numberFormatValue.format(valuePutBa1) + " \t" + numberFormatProbability.format(probabilityBachelier) + " \t" + numberFormatValue.format(valuePutBS1) + " \t" + numberFormatProbability.format(probabilityBlackScholes)); if(optionStrike > forward) { Assert.assertTrue("For strike>forward: Bacherlier probability for high underlying value should be lower than Black Scholes:", probabilityBlackScholes > probabilityBachelier); } if(optionStrike < -eps) { Assert.assertTrue("For strike<0: Bacherlier probability for low underlying value should be higher than Black Scholes:", probabilityBlackScholes < probabilityBachelier); Assert.assertTrue("For strike<0: Black Scholes probability for underlying < 0 should be 0:", probabilityBlackScholes < 1E-8); } } } }