static void Main(string[] args) { // Classes BisectionAlgo BA = new BisectionAlgo(); BlackScholesPrice BS = new BlackScholesPrice(); HestonPrice HP = new HestonPrice(); ElicesAlgo EA = new ElicesAlgo(); 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]); } } // Bounds on the parameter estimates // kappa theta sigma v0 rho double e = 1.0e-2; double[] lb = new double[5] { e, e, e, -0.99, e }; double[] ub = new double[5] { 10.0, 3.0, 10.0, 0.99, 3.0 }; // Read in SP500 implied volatilities const int NT = 4; const int NK = 13; double[][] MktIV = new double[NT][]; MktIV[0] = new double[NK] { 0.1962, 0.1910, 0.1860, 0.1810, 0.1761, 0.1718, 0.1671, 0.1644, 0.1645, 0.1661, 0.1701, 0.1755, 0.1796 }; MktIV[1] = new double[NK] { 0.1947, 0.1905, 0.1861, 0.1812, 0.1774, 0.1743, 0.1706, 0.1671, 0.1641, 0.1625, 0.1602, 0.1610, 0.1657 }; MktIV[2] = new double[NK] { 0.2019, 0.1980, 0.1943, 0.1907, 0.1871, 0.1842, 0.1813, 0.1783, 0.1760, 0.1743, 0.1726, 0.1716, 0.1724 }; MktIV[3] = new double[NK] { 0.2115, 0.2082, 0.2057, 0.2021, 0.2000, 0.1974, 0.1950, 0.1927, 0.1899, 0.1884, 0.1862, 0.1846, 0.1842 }; double[] K = new double[NK] { 124.0, 125.0, 126.0, 127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0, 134.0, 135.0, 136.0, }; double[] T = new double[NT] { 37.0 / 365.0, 72.0 / 365.0, 135.0 / 365.0, 226.0 / 365.0 }; // PutCall identifier string PutCall = "P"; // Maturity increments double[] tau = new double[NT]; tau[0] = T[0]; for (int t = 1; t <= NT - 1; t++) { tau[t] = T[t] - T[t - 1]; } // Option settings OPSet opsettings; opsettings.S = 129.14; opsettings.r = 0.0010; opsettings.q = 0.0068; opsettings.trap = 1; // Obtain the market prices for Elices, jagged array, and Heston (two dimensional array) double[][] MktPrice = new double[4][]; double[,] MktPriceH = new double[NT, NK]; double[,] MktIVH = new double[NT, NK]; for (int t = 0; t <= NT - 1; t++) { MktPrice[t] = new double[13]; for (int k = 0; k <= NK - 1; k++) { MktPrice[t][k] = BS.BlackScholes(opsettings.S, K[k], T[t], opsettings.r, opsettings.q, MktIV[t][k], PutCall); MktPriceH[t, k] = MktPrice[t][k]; MktIVH[t, k] = MktIV[t][k]; } } // Settings for the Elices objective function OFSetE ofsetE = new OFSetE(); ofsetE.opsettings = opsettings; ofsetE.X = X; ofsetE.W = W; ofsetE.LossFunction = 1; ofsetE.lb = lb; ofsetE.ub = ub; // Elices Settings for the Market data MktData MktData = new MktData(); MktData.K = K; MktData.PutCall = PutCall; ofsetE.data = MktData; // Settings for the Heston objective function OFSetH ofsetH = new OFSetH(); ofsetH.opsettings = opsettings; ofsetH.X = X; ofsetH.W = W; ofsetH.LossFunction = ofsetE.LossFunction; ofsetH.lb = ofsetE.lb; ofsetH.ub = ofsetE.ub; ofsetH.MktPrice = MktPriceH; ofsetH.MktIV = MktIVH; ofsetH.K = K; ofsetH.T = T; ofsetH.PutCall = PutCall; // Parameter initial values double kappaS = 2.00; double thetaS = 0.10; double sigmaS = 1.20; double v0S = 0.03; double rhoS = -0.80; // Heston and Elices starting values (vertices) in vector form. Add random increment about each starting value double[,] startH = new double[5, 6]; double[,] startE = new double[4, 5]; for (int j = 0; j <= 5; j++) { startH[0, j] = kappaS + BA.RandomNum(-0.05, 0.05) * kappaS; startH[1, j] = thetaS + BA.RandomNum(-0.05, 0.05) * thetaS; startH[2, j] = sigmaS + BA.RandomNum(-0.05, 0.05) * sigmaS; startH[3, j] = rhoS + BA.RandomNum(-0.05, 0.05) * rhoS; startH[4, j] = v0S + BA.RandomNum(-0.05, 0.05) * v0S; } for (int j = 0; j <= 4; j++) { startE[0, j] = startH[0, j]; startE[1, j] = startH[1, j]; startE[2, j] = startH[2, j]; startE[3, j] = startH[3, j]; } // Settings for the Nelder Mead algorithm NMSet nmsettings = new NMSet(); nmsettings.MaxIters = 50; // Maximum number of iterations nmsettings.Tolerance = 1e-20; // Tolerance on best and worst function values nmsettings.ofsettingsE = ofsetE; // Settings for the Elices objective function nmsettings.ofsettingsH = ofsetH; // Settings for the Heston objective function // Heston Parameter Estimation all maturities =========================================================================================== nmsettings.choice = "Heston"; int N = 5; nmsettings.N = N; double[] ParamH = new double[7]; ParamH = NM.NelderMead(OF.f, nmsettings, startH); // Elices Parameter Estimation maturity by maturity ==================================================================================== nmsettings.choice = "Elices"; N = 4; nmsettings.N = N; double v0 = ParamH[4]; ofsetE.v0 = v0; double[][] ParamTD = new double[4][]; double[] B = new double[6]; double[] LossFunction = new double[NT]; // Initialize maturities List <double> MatsList = new List <double>(); double[] Mats; MatsList.Add(tau[0]); MatsList.Add(tau[1]); Mats = MatsList.ToArray(); // Loop through the maturities, estimating parameters for (int t = 0; t <= NT - 1; t++) { ofsetE.t = t; ofsetE.data.MktIV = MktIV[t]; // Vector of market Implied vol ofsetE.data.MktPrice = MktPrice[t]; // Vector of market prices ofsetE.paramfixed = ParamTD; // Fixed parameters if (t >= 1) { for (int j = 0; j <= N; j++) // Starting values are last period's estimates { startE[0, j] = B[0] + BA.RandomNum(-0.01, 0.01) * B[0]; startE[1, j] = B[1] + BA.RandomNum(-0.01, 0.01) * B[1]; startE[2, j] = B[2] + BA.RandomNum(-0.01, 0.01) * B[2]; startE[3, j] = B[3] + BA.RandomNum(-0.01, 0.01) * B[3]; } } if (t >= 2) { MatsList.Add(tau[t]); Mats = MatsList.ToArray(); } ofsetE.T = Mats; nmsettings.ofsettingsE = ofsetE; B = NM.NelderMead(OF.f, nmsettings, startE); ParamTD[t] = new double[4] { B[0], B[1], B[2], B[3] }; LossFunction[t] = B[4]; Console.WriteLine("------ Finished maturity {0:F0} ------", ofsetE.t); } // Write the parameters Console.WriteLine("Heston statict parameters"); Console.WriteLine("------------------------------------------------------------------"); Console.WriteLine(" kappa theta sigma rho v0 LossFunction"); Console.WriteLine("------------------------------------------------------------------"); Console.WriteLine("{0,10:F5} {1,10:F5} {2,10:F5} {3,10:F5} {4,10:F5} {5,10:F5}", ParamH[0], ParamH[1], ParamH[2], ParamH[3], ParamH[4], ParamH[5]); Console.WriteLine("------------------------------------------------------------------"); Console.WriteLine(" "); Console.WriteLine("Elices Time dependent parameters"); Console.WriteLine("-------------------------------------------------------------"); Console.WriteLine(" kappa theta sigma rho LossFunction"); Console.WriteLine("-------------------------------------------------------------"); for (int t = 0; t <= NT - 1; t++) { Console.WriteLine("{0,10:F5} {1,10:F5} {2,10:F5} {3,10:F5} {4,10:F5}", ParamTD[t][0], ParamTD[t][1], ParamTD[t][2], ParamTD[t][3], LossFunction[t]); } Console.WriteLine("-------------------------------------------------------------"); // Fitting Elices prices and implied vols ========================================================================================================== // Matrices for prices, parameters, and fixed parameters double[,] PriceE = new double[NK, NT]; double[] param = new double[4]; double[][] paramfixed = new double[NT - 1][]; paramfixed[0] = ParamTD[0]; // Define the dynamic array for the maturities and assign the first two maturities List <double> MatList = new List <double>(); double[] Mat; MatList.Add(tau[0]); MatList.Add(tau[1]); Mat = MatList.ToArray(); // Elices Implied volatilities double[,] IVE = new double[NK, NT]; double a = 0.01; double b = 3.0; double tol = 1.0e-5; int MaxIter = 1000; double ElicesIVRMSE = 0.0; // Find the prices and implied vols for (int t = 0; t <= NT - 1; t++) { for (int j = 0; j <= 3; j++) { param[j] = ParamTD[t][j]; } if (t == 0) { for (int k = 0; k <= NK - 1; k++) { PriceE[k, 0] = EA.ElicesPrice(PutCall, opsettings.S, K[k], Mat, opsettings.r, opsettings.q, param, v0, opsettings.trap, X, W); } } if (t == 1) { for (int k = 0; k <= NK - 1; k++) { PriceE[k, 1] = EA.ElicesPrice(PutCall, opsettings.S, K[k], Mat, opsettings.r, opsettings.q, param, v0, opsettings.trap, X, W, paramfixed); } } if (t >= 2) { MatList.Add(tau[t]); Mat = MatList.ToArray(); paramfixed[t - 1] = ParamTD[t - 1]; for (int k = 0; k <= NK - 1; k++) { PriceE[k, t] = EA.ElicesPrice(PutCall, opsettings.S, K[k], Mat, opsettings.r, opsettings.q, param, v0, opsettings.trap, X, W, paramfixed); } } for (int k = 0; k <= NK - 1; k++) { IVE[k, t] = BA.BisecBSIV(PutCall, opsettings.S, K[k], opsettings.r, opsettings.q, T[t], a, b, PriceE[k, t], tol, MaxIter); ElicesIVRMSE += Math.Pow(IVE[k, t] - MktIV[t][k], 2.0); } } // Fitting Heston prices and implied vols ========================================================================================================== double[,] PriceH = new double[NK, NT]; double[,] IVH = new double[NK, NT]; HParam ParamTI = new HParam(); ParamTI.kappa = ParamH[0]; ParamTI.sigma = ParamH[1]; ParamTI.theta = ParamH[2]; ParamTI.rho = ParamH[3]; ParamTI.v0 = ParamH[4]; double HestonIVRMSE = 0.0; for (int k = 0; k <= NK - 1; k++) { for (int t = 0; t <= NT - 1; t++) { PriceH[k, t] = HP.HestonPriceGaussLaguerre(ParamTI, ofsetH.opsettings, K[k], T[t], PutCall, X, W); IVH[k, t] = BA.BisecBSIV(PutCall, opsettings.S, K[k], opsettings.r, opsettings.q, T[t], a, b, PriceH[k, t], tol, MaxIter); HestonIVRMSE += Math.Pow(IVH[k, t] - MktIVH[t, k], 2.0); } } Console.WriteLine("Heston IVRMSE {0,2:E5}", HestonIVRMSE); Console.WriteLine("Elices IVRMSE {0,2:E5}", ElicesIVRMSE); }
// Nelder Mead Algorithm =============================================================================================== public double[] NelderMead(ObjFun f, NMSet nmsettings, double[,] x) { ObjectiveFunction OF = new ObjectiveFunction(); OFSetE ofsetE = nmsettings.ofsettingsE; OFSetH ofsetH = nmsettings.ofsettingsH; int N = nmsettings.N; int MaxIters = nmsettings.MaxIters; string choice = nmsettings.choice; double Tolerance = nmsettings.Tolerance; int NumIters = 0; int i, j; // Value of the function at the vertices double[][] F = new Double[N + 1][]; for (i = 0; i <= N; i++) { F[i] = new double[2] { 0.0, 0.0 } } ; // Step 0. Ordering and Best and Worst points // Order according to the functional values, compute the best and worst points step0: NumIters = NumIters + 1; Console.Write("Nelder Mead iteration "); Console.WriteLine(NumIters); for (j = 0; j <= N; j++) { double[] z = new double[N]; for (i = 0; i <= N - 1; i++) { z[i] = x[i, j]; F[j][0] = OF.f(z, ofsetE, ofsetH, choice); // Function values F[j][1] = j; // Original index positions } } // Sort the F array w.r.t column 0 int column = 0; Array.Sort(F, delegate(double[] w1, double[] w2) { return((w1[column] as IComparable).CompareTo(w2[column])); }); // New vertices order first N best initial vectors and // last (N+1)st vertice is the worst vector // y is the matrix of vertices, ordered so that the worst vertice is last double[,] y = new double[N, N + 1]; for (j = 0; j <= N; j++) { for (i = 0; i <= N - 1; i++) { y[i, j] = x[i, Convert.ToInt32(F[j][1])]; } } // First best vector y(1) and function value f1 double[] x1 = new double[N]; for (i = 0; i <= N - 1; i++) { x1[i] = y[i, 0]; } double f1 = OF.f(x1, ofsetE, ofsetH, choice); // Last best vector y(N) and function value fn double[] xn = new Double[N]; for (i = 0; i <= N - 1; i++) { xn[i] = y[i, N - 1]; } double fn = OF.f(xn, ofsetE, ofsetH, choice); // Worst vector y(N+1) and function value fn1 double[] xn1 = new Double[N]; for (i = 0; i <= N - 1; i++) { xn1[i] = y[i, N]; } double fn1 = OF.f(xn1, ofsetE, ofsetH, choice); // z is the first N vectors from y, excludes the worst y(N+1) double[,] zz = new Double[N, N]; for (j = 0; j <= N - 1; j++) { for (i = 0; i <= N - 1; i++) { zz[i, j] = y[i, j]; } } // Mean of best N values and function value fm double[] xm = new Double[N]; xm = VMean(zz, N); double fm = OF.f(xm, ofsetE, ofsetH, choice); // Reflection point xr and function fr double[] xr = new Double[N]; xr = VSub(VAdd(xm, xm), xn1); double fr = OF.f(xr, ofsetE, ofsetH, choice); // Expansion point xe and function fe double[] xe = new Double[N]; xe = VSub(VAdd(xr, xr), xm); double fe = OF.f(xe, ofsetE, ofsetH, choice); // Outside contraction point and function foc double[] xoc = new Double[N]; xoc = VAdd(VMult(xr, 0.5), VMult(xm, 0.5)); double foc = OF.f(xoc, ofsetE, ofsetH, choice); // Inside contraction point and function foc double[] xic = new Double[N]; xic = VAdd(VMult(xm, 0.5), VMult(xn1, 0.5)); double fic = OF.f(xic, ofsetE, ofsetH, choice); while ((NumIters <= MaxIters) && (Math.Abs(f1 - fn1) >= Tolerance)) { // Step 1. Reflection Rule if ((f1 <= fr) && (fr < fn)) { for (j = 0; j <= N - 1; j++) { for (i = 0; i <= N - 1; i++) { x[i, j] = y[i, j]; } } for (i = 0; i <= N - 1; i++) { x[i, N] = xr[i]; } goto step0; } // Step 2. Expansion Rule if (fr < f1) { for (j = 0; j <= N - 1; j++) { for (i = 0; i <= N - 1; i++) { x[i, j] = y[i, j]; } } if (fe < fr) { for (i = 0; i <= N - 1; i++) { x[i, N] = xe[i]; } } else { for (i = 0; i <= N - 1; i++) { x[i, N] = xr[i]; } } goto step0; } // Step 3. Outside contraction Rule if ((fn <= fr) && (fr < fn1) && (foc <= fr)) { for (j = 0; j <= N - 1; j++) { for (i = 0; i <= N - 1; i++) { x[i, j] = y[i, j]; } } for (i = 0; i <= N - 1; i++) { x[i, N] = xoc[i]; } goto step0; } // Step 4. Inside contraction Rule if ((fr >= fn1) && (fic < fn1)) { for (j = 0; j <= N - 1; j++) { for (i = 0; i <= N - 1; i++) { x[i, j] = y[i, j]; } } for (i = 0; i <= N - 1; i++) { x[i, N] = xic[i]; } goto step0; } // Step 5. Shrink Step for (i = 0; i <= N - 1; i++) { x[i, 0] = y[i, 0]; } for (i = 0; i <= N - 1; i++) { for (j = 1; j <= N; j++) { x[i, j] = 0.5 * (y[i, j] + x[i, 0]); } } goto step0; } // Output component double[] outvec = new Double[N + 2]; for (i = 0; i <= N - 1; i++) { outvec[i] = x1[i]; } outvec[N] = f1; outvec[N + 1] = NumIters; return(outvec); }
// Objective function for Elices prices =========================================================================== public double f(double[] param, OFSetE ofsettingsE, OFSetH ofsettingsH, string choice) { ElicesAlgo EA = new ElicesAlgo(); HestonPrice HP = new HestonPrice(); double Error = 0.0; if (choice == "Elices") { double S = ofsettingsE.opsettings.S; double r = ofsettingsE.opsettings.r; double q = ofsettingsE.opsettings.q; int trap = ofsettingsE.opsettings.trap; double[] MktIV = ofsettingsE.data.MktIV; double[] MktPrice = ofsettingsE.data.MktPrice; string PutCall = ofsettingsE.data.PutCall; double[] K = ofsettingsE.data.K; int NK = K.Length; int t = ofsettingsE.t; double[][] paramfixed = ofsettingsE.paramfixed; double[] T = ofsettingsE.T; double v0 = ofsettingsE.v0; int NT = T.Length; double[] lb = ofsettingsE.lb; double[] ub = ofsettingsE.ub; int LossFunction = ofsettingsE.LossFunction; double[] X = ofsettingsE.X; double[] W = ofsettingsE.W; // Initialize the model price and model implied vol vectors, and the objective function value double[] ModelPrice = new double[NK]; double[] ModelIV = new double[NK]; double Vega = 0.0; double pi = Math.PI; double kappaLB = lb[0]; double kappaUB = ub[0]; double thetaLB = lb[1]; double thetaUB = ub[1]; double sigmaLB = lb[2]; double sigmaUB = ub[2]; double rhoLB = lb[3]; double rhoUB = ub[3]; // Penalty for inadmissible parameter values if ((param[0] <= kappaLB) || (param[1] <= thetaLB) || (param[2] <= sigmaLB) || (param[3] <= rhoLB) || (param[0] >= kappaUB) || (param[1] >= thetaUB) || (param[2] >= sigmaUB) || (param[3] >= rhoUB)) { Error = 1.0e50; } else { for (int k = 0; k < NK - 1; k++) { if (t == 0) { ModelPrice[k] = EA.ElicesPrice(PutCall, S, K[k], T, r, q, param, v0, trap, X, W); } else { ModelPrice[k] = EA.ElicesPrice(PutCall, S, K[k], T, r, q, param, v0, trap, X, W, paramfixed); } switch (LossFunction) { case 1: // MSE Loss Function Error += Math.Pow(ModelPrice[k] - MktPrice[k], 2.0); break; case 2: // RMSE Loss Function Error += Math.Pow(ModelPrice[k] - MktPrice[k], 2.0) / MktPrice[k]; break; case 3: // IVRMSE Christoffersen, Heston, Jacobs proxy double mat = T[NT - 1]; double d = (Math.Log(S / K[k]) + (r + MktIV[k] * MktIV[k] / 2.0) * mat) / MktIV[k] / Math.Sqrt(mat); double NormPDF = Math.Exp(-0.5 * d * d) / Math.Sqrt(2.0 * pi); Vega = S * NormPDF * Math.Sqrt(mat); Error += Math.Pow(ModelPrice[k] - MktPrice[k], 2.0) / (Vega * Vega); break; } } Error /= Convert.ToDouble(NK); } } else if (choice == "Heston") // Objective function for Heston prices =========================================================================== { double[,] MktPrice = ofsettingsH.MktPrice; double[,] MktIV = ofsettingsH.MktIV; string PutCall = ofsettingsH.PutCall; double[] K = ofsettingsH.K; int NK = K.Length; double[] T = ofsettingsH.T; int NT = T.Length; double[] lb = ofsettingsH.lb; double[] ub = ofsettingsH.ub; int LossFunction = ofsettingsH.LossFunction; double[] X = ofsettingsH.X; double[] W = ofsettingsH.W; // Initialize the model price and model implied vol vectors, and the objective function value double[,] ModelPrice = new double[NK, NT]; double[,] ModelIV = new double[NK, NT]; double Vega = 0.0; double pi = Math.PI; double kappaLB = lb[0]; double kappaUB = ub[0]; double thetaLB = lb[1]; double thetaUB = ub[1]; double sigmaLB = lb[2]; double sigmaUB = ub[2]; double rhoLB = lb[3]; double rhoUB = ub[3]; double v0LB = lb[4]; double v0UB = ub[4]; HParam param2 = new HParam(); param2.kappa = param[0]; param2.theta = param[1]; param2.sigma = param[2]; param2.rho = param[3]; param2.v0 = param[4]; // Penalty for inadmissible parameter values if ((param[0] <= kappaLB) || (param[1] <= thetaLB) || (param[2] <= sigmaLB) || (param[3] <= rhoLB) || (param[4] <= v0LB) || (param[0] >= kappaUB) || (param[1] >= thetaUB) || (param[2] >= sigmaUB) || (param[3] >= rhoUB) || (param[4] >= v0UB)) { Error = 1.0e50; } else { for (int t = 0; t <= NT - 1; t++) { for (int k = 0; k <= NK - 1; k++) { ModelPrice[k, t] = HP.HestonPriceGaussLaguerre(param2, ofsettingsH.opsettings, K[k], T[t], PutCall, X, W); switch (LossFunction) { case 1: // MSE Loss Function Error += Math.Pow(ModelPrice[k, t] - MktPrice[t, k], 2.0); break; case 2: // RMSE Loss Function Error += Math.Pow(ModelPrice[k, t] - MktPrice[t, k], 2.0) / MktPrice[k, t]; break; case 3: // IVRMSE Christoffersen, Heston, Jacobs proxy double S = ofsettingsH.opsettings.S; double r = ofsettingsH.opsettings.r; double q = ofsettingsH.opsettings.q; double mat = T[NT - 1]; double d = (Math.Log(S / K[k]) + (r + MktIV[t, k] * MktIV[t, k] / 2.0) * mat) / MktIV[k, t] / Math.Sqrt(mat); double NormPDF = Math.Exp(-0.5 * d * d) / Math.Sqrt(2.0 * pi); Vega = S * NormPDF * Math.Sqrt(mat); Error += Math.Pow(ModelPrice[k, t] - MktPrice[t, k], 2.0) / (Vega * Vega); break; } } } Error /= Convert.ToDouble(NK * NT); } } return(Error); }