static void Main(string[] args) { // Classes MiscFunctions MF = new MiscFunctions(); DiffEvoAlgo DE = new DiffEvoAlgo(); NelderMeadAlgo NM = new NelderMeadAlgo(); ObjectiveFunction OF = new ObjectiveFunction(); // 32-point Gauss-Laguerre Abscissas and weights double[] X = new Double[32]; double[] W = new Double[32]; using (TextReader reader = File.OpenText("../../GaussLaguerre32.txt")) for (int k = 0; k <= 31; k++) { string text = reader.ReadLine(); string[] bits = text.Split(' '); X[k] = double.Parse(bits[0]); W[k] = double.Parse(bits[1]); } // Option settings OPSet opset; opset.S = 137.14; opset.r = 0.0010; opset.q = 0.0068; opset.trap = 1; // Bisection algorithm settings double a = 0.01; double b = 3.0; double Tol = 1e-5; int MaxIter = 1000; int ObjectiveFun = 1; // Read in SP500 implied volatilities int NT = 4; int NK = 7; double[,] MktIV = new Double[7, 4] { { 0.2780, 0.2638, 0.2532, 0.2518 }, { 0.2477, 0.2402, 0.2364, 0.2369 }, { 0.2186, 0.2158, 0.2203, 0.2239 }, { 0.1878, 0.1930, 0.2047, 0.2098 }, { 0.1572, 0.1712, 0.1894, 0.1970 }, { 0.1334, 0.1517, 0.1748, 0.1849 }, { 0.1323, 0.1373, 0.1618, 0.1736 } }; double[] K = new Double[7] { 120.0, 125.0, 130.0, 135.0, 140.0, 145.0, 150.0 }; double[] T = new Double[4] { 0.123287671232877, 0.268493150684932, 0.715068493150685, 0.953424657534247 }; // PutCall identifiers string[,] PutCall = new String[NK, NT]; for (int k = 0; k <= NK - 1; k++) { for (int t = 0; t <= NT - 1; t++) { PutCall[k, t] = "C"; } } // Obtain the market prices BlackScholesPrice BS = new BlackScholesPrice(); double[,] MktPrice = new Double[NK, NT]; for (int k = 0; k <= NK - 1; k++) { for (int t = 0; t <= NT - 1; t++) { MktPrice[k, t] = BS.BlackScholes(opset.S, K[k], T[t], opset.r, opset.q, MktIV[k, t], PutCall[k, t]); } } // Place the market data in the structure MktData data = new MktData(); data.MktIV = MktIV; data.MktPrice = MktPrice; data.K = K; data.T = T; data.PutCall = PutCall; // Estimation bounds double e = 1e-5; double kappaL = e; double kappaU = 10; double thetaL = e; double thetaU = 5; double sigmaL = e; double sigmaU = 5; double v0L = e; double v0U = 1; double rhoL = -.9; double rhoU = 0; double[] ub = new double[5] { kappaU, thetaU, sigmaU, v0U, rhoU }; double[] lb = new double[5] { kappaL, thetaL, sigmaL, v0L, rhoL }; // Objective function settings; OFSet ofset; ofset.opset = opset; ofset.data = data; ofset.X = X; ofset.W = W; ofset.LossFunction = 4; // Choice of loss function 1=MSE, 2=RMSE, 3=IVMSE, 4=Christoffersen et al. ofset.lb = lb; ofset.ub = ub; ofset.CF = "Heston"; // Choice of c.f. "Heston" or "Attari" // Settings for the Differential Evolution algorithm DEParam ParamLim = new DEParam(); ParamLim.ub = ub; ParamLim.lb = lb; ParamLim.NG = 500; ParamLim.NP = 75; ParamLim.F = 0.5; ParamLim.CR = 0.8; // Run the differential evolution algorithm HParam DEparam = DE.HestonDE(ParamLim, opset, data, ObjectiveFun, a, b, Tol, MaxIter, X, W, ofset.CF); // Starting values (vertices) in vector form. Add random increment about each starting value double kappaS = 9; double thetaS = 0.05; double sigmaS = 0.3; double v0S = 0.05; double rhoS = -0.8; int N = 5; double[,] s = new double[N, N + 1]; for (int j = 0; j <= N; j++) { s[0, j] = kappaS + MF.RandomNum(-0.10, 0.10); s[1, j] = thetaS + MF.RandomNum(-0.01, 0.01); s[2, j] = sigmaS + MF.RandomNum(-0.05, 0.05); s[3, j] = v0S + MF.RandomNum(-0.01, 0.01); s[4, j] = rhoS + MF.RandomNum(-0.05, 0.05); } // Nelder Mead settings NMSet nmset; nmset.ofset = ofset; nmset.N = 5; // Number of Heston parameters nmset.MaxIters = 1000; // Maximum number of iterations nmset.Tolerance = 1e-4; // Tolerance on best and worst function values // Run the Nelder Mead algorithm double[] B = NM.NelderMead(OF.f, nmset, s); HParam NMparam = new HParam(); NMparam.kappa = B[0]; NMparam.theta = B[1]; NMparam.sigma = B[2]; NMparam.v0 = B[3]; NMparam.rho = B[4]; // Calculate IVMSE under both parameter estimates double[,] NMPrice = new double[NK, NT]; double[,] DEPrice = new double[NK, NT]; double[,] NMIV = new double[NK, NT]; double[,] DEIV = new double[NK, NT]; double NMIVMSE = 0.0; double DEIVMSE = 0.0; double S = opset.S; double r = opset.r; double q = opset.q; int trap = opset.trap; HestonPrice HP = new HestonPrice(); Bisection BA = new Bisection(); for (int t = 0; t < NT; t++) { for (int k = 0; k < NK; k++) { NMPrice[k, t] = HP.HestonPriceGaussLaguerre(NMparam, S, K[k], r, q, T[t], trap, PutCall[k, t], X, W); NMIV[k, t] = BA.BisecBSIV(PutCall[k, t], S, K[k], r, q, T[t], a, b, NMPrice[k, t], Tol, MaxIter); NMIVMSE += Math.Pow(MktIV[k, t] - NMIV[k, t], 2) / Convert.ToDouble(NT * NK); DEPrice[k, t] = HP.HestonPriceGaussLaguerre(DEparam, S, K[k], r, q, T[t], trap, PutCall[k, t], X, W); DEIV[k, t] = BA.BisecBSIV(PutCall[k, t], S, K[k], r, q, T[t], a, b, DEPrice[k, t], Tol, MaxIter); DEIVMSE += Math.Pow(MktIV[k, t] - DEIV[k, t], 2) / Convert.ToDouble(NT * NK); } } // Output the result Console.WriteLine(" "); Console.WriteLine("Parameter Estimates --------------------"); Console.WriteLine(" Differential Evolution Nelder Mead"); Console.WriteLine("kappa {0,10:F4} {1,25:F4}", DEparam.kappa, NMparam.kappa); Console.WriteLine("theta {0,10:F4} {1,25:F4}", DEparam.theta, NMparam.theta); Console.WriteLine("sigma {0,10:F4} {1,25:F4}", DEparam.sigma, NMparam.sigma); Console.WriteLine("v0 {0,10:F4} {1,25:F4}", DEparam.v0, NMparam.v0); Console.WriteLine("rho {0,10:F4} {1,25:F4}", DEparam.rho, NMparam.rho); Console.WriteLine(" "); Console.WriteLine("IV MSE ---------------------------------"); Console.WriteLine("Differential Evolution IVMSE {0:E8}", DEIVMSE); Console.WriteLine("Nelder Mead IVMSE {0:E8}", NMIVMSE); Console.WriteLine(" "); }
// Differential evolution algorithm public HParam HestonDE(DEParam DEsettings, OPSet settings, MktData data, int LossFunction, double a, double b, double Tol, int MaxIter, double[] X, double[] W, string CF) { // NG = Number of generations (iterations) // NP = Number of population members // CR = Crossover ratio (=0.5) // F = Threshold (=0.8) // Hi = Vector of upper bounds for the parameters // Lo = Vector of lower bounds for the parameters // S = Spot Price // K1 = First strike point for the FRFT // rf = Risk free rate // q = Dividend Yield // MktPrice = Market quotes for prices // K = Vector of Strikes // T = Vector of Maturities // PutCall = Matrix of 'P'ut or 'C'all // MktIV = Market quotes for implied volatilities // LossFunction = Type of Objective Function 1 = MSE; 2 = RMSE; 3 = IVMSE; 4 = Christoffersen, Jacobs, Heston (2009) // Bisection method settings; a = Lower limit; b = upper limit; Tol = Tolerance; MaxIter = Max number of iterations // trap = 1 is "Little Trap" c.f., 0 is Heston c.f. // X, W = Gauss Laguerre abscissas and weights // CF = "Heston" or "Attari" characteristic function ObjectiveFunction OF = new ObjectiveFunction(); MiscFunctions MF = new MiscFunctions(); double[] Hi = DEsettings.ub; double[] Lo = DEsettings.lb; double CR = DEsettings.CR; double kappaU = Hi[0]; double kappaL = Lo[0]; double thetaU = Hi[1]; double thetaL = Lo[1]; double sigmaU = Hi[2]; double sigmaL = Lo[2]; double v0U = Hi[3]; double v0L = Lo[3]; double rhoU = Hi[4]; double rhoL = Lo[4]; // Create the structure for the objective function; OFSet ofset; ofset.opset = settings; ofset.data = data; ofset.X = X; ofset.W = W; ofset.LossFunction = LossFunction; ofset.lb = DEsettings.lb; ofset.ub = DEsettings.ub; ofset.CF = CF; // Step1. Generate the population matrix of random parameters int NP = DEsettings.NP; int NG = DEsettings.NG; double F = DEsettings.F; double[,] P = new double[5, NP]; for (int j = 0; j <= NP - 1; j++) { P[0, j] = kappaL + (kappaU - kappaL) * MF.RandomNum(0.0, 1.0); P[1, j] = thetaL + (thetaU - thetaL) * MF.RandomNum(0.0, 1.0); P[2, j] = sigmaL + (sigmaU - sigmaL) * MF.RandomNum(0.0, 1.0); P[3, j] = v0L + (v0U - v0L) * MF.RandomNum(0.0, 1.0); P[4, j] = rhoL + (rhoU - rhoL) * MF.RandomNum(0.0, 1.0); } // Generate the random numbers outside the loop double[, ,] U = new double[5, NP, NG]; for (int k = 0; k <= NG - 1; k++) { for (int j = 0; j <= NP - 1; j++) { for (int i = 0; i <= 4; i++) { U[i, j, k] = MF.RandomNum(0.0, 1.0); } } } // Initialize the variables double[] Pr1 = new double[5]; double[] Pr2 = new double[5]; double[] Pr3 = new double[5]; double[] P0 = new double[5]; // Loop through the generations for (int k = 0; k <= NG - 1; k++) { Console.Write("Differential Evolution iteration "); Console.WriteLine(k); // Loop through the population for (int i = 0; i <= NP - 1; i++) { // Select the i-th member of the population for (int s = 0; s <= 4; s++) { P0[s] = P[s, i]; } Step0: // Select random indices for three other distinct members int[] Integers0toNP1 = new int[NP]; for (int s = 0; s <= NP - 1; s++) { Integers0toNP1[s] = s; } // Random perumation of the indices (0,1,...,NP-1) int[] I = MF.RandomPerm(Integers0toNP1); // Find the index in I that is not equal to i and keep the first 3 positions int[] L = MF.RemoveIndex(I, i); int[] r = new int[3]; for (int s = 0; s <= 2; s++) { r[s] = L[s]; } // The three distinct members of the population for (int s = 0; s <= 4; s++) { Pr1[s] = P[s, r[0]]; Pr2[s] = P[s, r[1]]; Pr3[s] = P[s, r[2]]; } int[] Integers1to5 = { 1, 2, 3, 4, 5 }; int[] R = MF.RandomPerm(Integers1to5); double[] Pnew = { 0.0, 0.0, 0.0, 0.0, 0.0 }; // Steps 2 and 3. Mutation and recombination double Ri; double u; for (int j = 0; j <= 4; j++) { Ri = R[0]; u = U[j, i, k]; if ((u <= CR) | (j == Ri)) { Pnew[j] = Pr1[j] + F * (Pr2[j] - Pr3[j]); } else { Pnew[j] = P0[j]; } } // Repeat above to code to ensure new members fall within the // range of acceptable parameter values int[] Flag = new int[5] { 0, 0, 0, 0, 0 }; for (int s = 0; s <= 4; s++) { if (Pnew[s] <= Lo[s] | Pnew[s] >= Hi[s]) { Flag[s] = 1; } } int Condition = Flag.Sum(); if (Condition > 0) { goto Step0; } // Step 4. Selection // Calculate the objective function for the ith member and for the candidate double f0 = OF.f(P0, ofset); double fnew = OF.f(Pnew, ofset); // Verify whether the candidate should replace the i-th member // in the population and replace if conditions are satisfied if (fnew < f0) { for (int s = 0; s <= 4; s++) { P[s, i] = Pnew[s]; } } } } // Calculate the objective function for each member in the updated population double[] fs = new double[NP]; double[] Pmember = new double[5]; for (int s = 0; s <= NP - 1; s++) { for (int t = 0; t <= 4; t++) { Pmember[t] = P[t, s]; } fs[s] = OF.f(Pmember, ofset); } // Find the member with the lowest objective function double Minf = fs[0]; int Minpos = 0; for (int s = 0; s <= NP - 1; s++) { if (fs[s] < Minf) { Minf = fs[s]; Minpos = s; } } // Return the selected member/parameter HParam Pfinal = new HParam(); Pfinal.kappa = P[0, Minpos]; Pfinal.theta = P[1, Minpos]; Pfinal.sigma = P[2, Minpos]; Pfinal.v0 = P[3, Minpos]; Pfinal.rho = P[4, Minpos]; return(Pfinal); }