// Objective function =========================================================================== public double f(double[] param, OFSet ofset) { HestonPrice HP = new HestonPrice(); Bisection BA = new Bisection(); double S = ofset.opset.S; double r = ofset.opset.r; double q = ofset.opset.q; int trap = ofset.opset.trap; double[,] MktIV = ofset.data.MktIV; double[,] MktPrice = ofset.data.MktPrice; string[,] PutCall = ofset.data.PutCall; double[] K = ofset.data.K; double[] T = ofset.data.T; double[] X = ofset.X; double[] W = ofset.W; string CF = ofset.CF; int LossFunction = ofset.LossFunction; int NK = PutCall.GetLength(0); int NT = PutCall.GetLength(1); int NX = X.Length; HParam param2 = new HParam(); param2.kappa = param[0]; param2.theta = param[1]; param2.sigma = param[2]; param2.v0 = param[3]; param2.rho = param[4]; // Settings for the Bisection algorithm double a = 0.001; double b = 3.0; double Tol = 1e5; int MaxIter = 10000; // 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 Error = 0.0; double pi = Math.PI; double[] lb = ofset.lb; double[] ub = ofset.ub; 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 v0LB = lb[3]; double v0UB = ub[3]; double rhoLB = lb[4]; double rhoUB = ub[4]; if ((param2.kappa <= kappaLB) || (param2.theta <= thetaLB) || (param2.sigma <= sigmaLB) || (param2.v0 <= v0LB) || (param2.rho <= rhoLB) || (param2.kappa >= kappaUB) || (param2.theta >= thetaUB) || (param2.sigma >= sigmaUB) || (param2.v0 >= v0UB) || (param2.rho >= rhoUB)) { Error = 1e50; } else { Complex phi = new Complex(0.0, 0.0); double phi2 = 0.0; Complex i = new Complex(0.0, 1.0); Complex[] f2 = new Complex[NX]; Complex[] f1 = new Complex[NX]; Complex[] f = new Complex[NX]; double[] int1 = new double[NX]; double[] int2 = new double[NX]; Complex I1 = new Complex(0.0, 0.0); Complex I2 = new Complex(0.0, 0.0); double CallPrice = 0.0; for (int t = 0; t < NT; t++) { for (int j = 0; j < NX; j++) { phi = X[j]; if (CF == "Heston") { f2[j] = HP.HestonCF(phi, param2, S, r, q, T[t], trap); f1[j] = HP.HestonCF(phi - i, param2, S, r, q, T[t], trap) / (S * Math.Exp((r - q) * T[t])); } else if (CF == "Attari") { phi2 = X[j]; } f[j] = HP.AttariCF(phi2, param2, T[t], S, r, q, trap); } for (int k = 0; k < NK; k++) { double L = Math.Log(Math.Exp(-r * T[t]) * K[k] / S); for (int j = 0; j < NX; j++) { phi = X[j]; if (CF == "Heston") { I1 = Complex.Exp(-i * phi * Complex.Log(K[k])) * f1[j] / i / phi; int1[j] = W[j] * I1.Real; I2 = Complex.Exp(-i * phi * Complex.Log(K[k])) * f2[j] / i / phi; int2[j] = W[j] * I2.Real; } else if (CF == "Attari") { phi2 = X[j]; double fR = f[j].Real; double fI = f[j].Imaginary; int1[j] = W[j] * ((fR + fI / phi2) * Math.Cos(L * phi2) + (fI - fR / phi2) * Math.Sin(L * phi2)) / (1 + phi2 * phi2); } } if (CF == "Heston") { double P1 = 0.5 + 1.0 / pi * int1.Sum(); double P2 = 0.5 + 1.0 / pi * int2.Sum(); CallPrice = S * Math.Exp(-q * T[t]) * P1 - K[k] * Math.Exp(-r * T[t]) * P2; } else if (CF == "Attari") { CallPrice = S * Math.Exp(-q * T[t]) - K[k] * Math.Exp(-r * T[t]) * (0.5 + 1.0 / pi * int1.Sum()); } if (PutCall[k, t] == "C") { ModelPrice[k, t] = CallPrice; } else { ModelPrice[k, t] = CallPrice - S * Math.Exp(-q * T[t]) + Math.Exp(-r * T[t]) * K[k]; } // Select the objective function switch (LossFunction) { case 1: // MSE Loss Function Error += Math.Pow(ModelPrice[k, t] - MktPrice[k, t], 2) / Convert.ToDouble(NT * NK);; break; case 2: // RMSE Loss Function Error += Math.Pow(ModelPrice[k, t] - MktPrice[k, t], 2) / MktPrice[k, t] / Convert.ToDouble(NT * NK);; break; case 3: // IVMSE Loss Function ModelIV[k, t] = BA.BisecBSIV(PutCall[k, t], S, K[k], r, q, T[t], a, b, ModelPrice[k, t], Tol, MaxIter); Error += Math.Pow(ModelIV[k, t] - MktIV[k, t], 2) / Convert.ToDouble(NT * NK);; break; case 4: // IVRMSE Christoffersen, Heston, Jacobs proxy double d = (Math.Log(S / K[k]) + (r - q + MktIV[k, t] * MktIV[k, t] / 2.0) * T[t]) / MktIV[k, t] / Math.Sqrt(T[t]); double NormPDF = Math.Exp(-0.5 * d * d) / Math.Sqrt(2 * pi); Vega = S * NormPDF * Math.Sqrt(T[t]); Error += Math.Pow(ModelPrice[k, t] - MktPrice[k, t], 2) / Vega / Vega / Convert.ToDouble(NT * NK); break; } } } } return(Error); }
static void Main(string[] args) { // Classes HestonPrice HP = new HestonPrice(); BlackScholesPrice BS = new BlackScholesPrice(); Bisection BA = new Bisection(); 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; // 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 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]); } } // settings for the market data MktData data = new MktData(); data.MktIV = MktIV; data.MktPrice = MktPrice; data.K = K; data.T = T; data.PutCall = PutCall; // Bounds on the parameter estimates // kappa theta sigma v0 rho double e = 1e-5; double[] lb = new double[5] { e, e, e, e, -0.99 }; double[] ub = new double[5] { 20.0, 2.0, 2.0, 2.0, 0.99 }; // C.F. choice and Loss function choice string CF = "Heston"; // Choice of c.f. "Heston" or "Attari" int LossFunction = 1; // Choice of loss function // 1=MSE, 2=RMSE, 3=IVMSE, 4=Christoffersen et al. // Settings for the objective function OFSet ofset; ofset.opset = opset; ofset.data = data; ofset.X = X; ofset.W = W; ofset.LossFunction = LossFunction; ofset.lb = lb; ofset.ub = ub; ofset.CF = CF; // Settings for the Nelder Mead algorithm NMSet nmsettings; nmsettings.ofset = ofset; nmsettings.N = 5; // Number of Heston parameters nmsettings.MaxIters = 1000; // Maximum number of iterations nmsettings.Tolerance = 1e-4; // Tolerance on best and worst function values // 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 = nmsettings.N; double[,] s = new double[N, N + 1]; for (int j = 0; j <= N; j++) { s[0, j] = kappaS + NM.RandomNum(-0.10, 0.10); s[1, j] = thetaS + NM.RandomNum(-0.01, 0.01); s[2, j] = sigmaS + NM.RandomNum(-0.05, 0.05); s[3, j] = v0S + NM.RandomNum(-0.01, 0.01); s[4, j] = rhoS + NM.RandomNum(-0.05, 0.05); } // Find the Nelder-Mead parameter estimates double[] B = NM.NelderMead(OF.f, nmsettings, s); // Output the estimation result Console.WriteLine(" "); Console.WriteLine("Parameter Estimates --------------------"); Console.WriteLine(" "); Console.WriteLine("kappa = {0:F5}", B[0]); Console.WriteLine("theta = {0:F5}", B[1]); Console.WriteLine("sigma = {0:F5}", B[2]); Console.WriteLine("v0 = {0:F5}", B[3]); Console.WriteLine("rho = {0:F5}", B[4]); Console.WriteLine(" "); Console.WriteLine("Value of the objective function is {0:F5}", B[5]); Console.WriteLine(" "); Console.WriteLine("Number of iterations required {0}", B[6]); Console.WriteLine(" "); Console.WriteLine("----------------------------------------"); // Obtain the model prices and model implied volatilities HParam paramNM = new HParam(); paramNM.kappa = B[0]; paramNM.theta = B[1]; paramNM.sigma = B[2]; paramNM.v0 = B[3]; paramNM.rho = B[4]; double a = 0.01; double b = 3.0; double Tol = 1e-5; int MaxIter = 1000; double[,] ModelPrice = new double[NK, NT]; double[,] ModelIV = new double[NK, NT]; double IVMSE = 0.0; double S = ofset.opset.S; double r = ofset.opset.r; double q = ofset.opset.q; int trap = ofset.opset.trap; for (int k = 0; k < NK; k++) { for (int t = 0; t < NT; t++) { ModelPrice[k, t] = HP.HestonPriceGaussLaguerre(paramNM, S, K[k], r, q, T[t], trap, PutCall[k, t], X, W); ModelIV[k, t] = BA.BisecBSIV(PutCall[k, t], S, K[k], r, q, T[t], a, b, ModelPrice[k, t], Tol, MaxIter); IVMSE += Math.Pow(MktIV[k, t] - ModelIV[k, t], 2) / Convert.ToDouble(NT * NK); } } // Output the results Console.Write("MSE between model and market implied vols = {0}", IVMSE); Console.WriteLine(" "); Console.WriteLine("----------------------------------------"); Console.WriteLine("Market implied volatilities"); Console.WriteLine(" "); for (int k = 0; k < NK; k++) { for (int t = 0; t < NT; t++) { if (t < NT - 1) { Console.Write("{0:F4} ", MktIV[k, t]); } else { Console.WriteLine("{0:F4} ", MktIV[k, t]); } } } Console.WriteLine(" "); Console.WriteLine("----------------------------------------"); Console.WriteLine("Model implied volatilities"); Console.WriteLine(" "); for (int k = 0; k < NK; k++) { for (int t = 0; t < NT; t++) { if (t < NT - 1) { Console.Write("{0:F4} ", ModelIV[k, t]); } else { Console.WriteLine("{0:F4} ", ModelIV[k, t]); } } } Console.WriteLine("----------------------------------------"); }