// Heston Price by Gauss-Laguerre Integration public double HestonPriceConsol(HParam param, OpSet settings, double[] x, double[] w) { double[] int1 = new Double[32]; // Numerical integration for (int j = 0; j <= 31; j++) { int1[j] = w[j] * HestonProbConsol(x[j], param, settings); } // Define P1 and P2 double pi = Math.PI; double I = int1.Sum(); // The call price double S = settings.S; double K = settings.K; double r = settings.r; double q = settings.q; double T = settings.T; string PutCall = settings.PutCall; double HestonC = 0.5 * S * Math.Exp(-q * T) - 0.5 * K * Math.Exp(-r * T) + I / pi; // The put price by put-call parity double HestonP = HestonC - S * Math.Exp(-q * T) + K * Math.Exp(-r * T); // Output the option price if (PutCall == "C") { return(HestonC); } else { return(HestonP); } }
// Mikhailov and Nogel (2003) Time dependent D function public Complex Dt(double phi, HParam param, double r, double q, double T, OpSet settings, int Pnum, Complex C0, Complex D0) { Complex i = new Complex(0.0, 1.0); // Imaginary unit double kappa = param.kappa; double theta = param.theta; double sigma = param.sigma; double v0 = param.v0; double rho = param.rho; int Trap = settings.trap; Complex b, u, d, g, G, D = new Complex(); // Parameters "u" and "b" are different for P1 and P2 if (Pnum == 1) { u = 0.5; b = kappa - rho * sigma; } else { u = -0.5; b = kappa; } d = Complex.Sqrt(Complex.Pow(rho * sigma * i * phi - b, 2) - sigma * sigma * (2.0 * u * i * phi - phi * phi)); g = (b - rho * sigma * i * phi + d - D0 * sigma * sigma) / (b - rho * sigma * i * phi - d - D0 * sigma * sigma); G = (1.0 - g * Complex.Exp(d * T)) / (1.0 - g); D = ((b - rho * sigma * i * phi) * (1 - g * Complex.Exp(d * T)) + d * (1 + g * Complex.Exp(d * T))) / (sigma * sigma * (1 - g * Complex.Exp(d * T))); return(D); }
static void Main(string[] args) { // 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]); } HParam param = new HParam(); param.kappa = 6.2; param.theta = 0.06; param.sigma = 0.5; param.v0 = 0.03; param.rho = -0.7; param.lambda = 0.0; OpSet settings = new OpSet(); settings.S = 100.0; settings.K = 100.0; settings.T = 0.25; settings.r = 0.03; settings.q = 0.02; settings.PutCall = "C"; settings.trap = 1; // Simulation settings double gamma1 = 0.5; double gamma2 = 0.5; double phic = 1.5; int MC = 1; int NT = 100; int NS = 1500; // Calculate the IJK price QESimulation QE = new QESimulation(); double QuadExpPrice = QE.QEPrice(param, settings, gamma1, gamma2, NT, NS, MC, phic, settings.PutCall); // Calculate the closed-form European option price HestonPrice HP = new HestonPrice(); double ClosedPrice = HP.HestonPriceGaussLaguerre(param, settings, x, w); // Calculate the errors; double QError = Math.Abs((QuadExpPrice - ClosedPrice) / ClosedPrice * 100); // Output the results Console.WriteLine("European Heston price by simulation --------------------"); Console.WriteLine("Uses {0:0} stock price paths and {1:0} time increments", NS, NT); Console.WriteLine("--------------------------------------------------------"); Console.WriteLine("Closed form price {0:F5}", ClosedPrice); Console.WriteLine("Quadratic exp price {0:F5} Error {1,5:F5}", QuadExpPrice, QError); Console.WriteLine("--------------------------------------------------------"); Console.WriteLine(" "); }
static void Main(string[] args) { // 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]); } HParam param = new HParam(); param.kappa = 6.2; param.theta = 0.06; param.sigma = 0.5; param.v0 = 0.03; param.rho = -0.7; param.lambda = 0.0; OpSet settings = new OpSet(); settings.S = 100.0; settings.K = 90.0; settings.T = 3.0 / 12.0; settings.r = 0.03; settings.q = 0.02; settings.PutCall = "C"; settings.trap = 1; // Simulation settings int NT = 100; int NS = 2000; // Calculate the Euler price and the transformed volatility price TVSimulation TV = new TVSimulation(); double TVolPrice = TV.TransVolPrice("TV", param, settings, NT, NS); double EulerPrice = TV.TransVolPrice("Euler", param, settings, NT, NS); // Calculate the closed-form European option price double ClosedPrice = HestonPriceGaussLaguerre(param, settings, x, w); // Calculate the errors; double TVolError = Math.Abs((TVolPrice - ClosedPrice) / ClosedPrice * 100); double EulerError = Math.Abs((EulerPrice - ClosedPrice) / ClosedPrice * 100); // Output the results Console.WriteLine("Uses {0:0} stock price paths and {1:0} time increments", NS, NT); Console.WriteLine("--------------------------------------------------------"); Console.WriteLine("Method Price PercentError"); Console.WriteLine("--------------------------------------------------------"); Console.WriteLine("Exact with Gauss Laguerre {0,10:F5}", ClosedPrice); Console.WriteLine("Euler discretization {0,10:F5} {1,10:F5}", EulerPrice, EulerError); Console.WriteLine("Transformed volatility {0,10:F5} {1,10:F5}", TVolPrice, TVolError); Console.WriteLine("--------------------------------------------------------"); Console.WriteLine(" "); }
// Heston Integrand public double HestonProb(double phi, HParam param, OpSet settings, int Pnum) { Complex i = new Complex(0.0, 1.0); // Imaginary unit double S = settings.S; double K = settings.K; double T = settings.T; double r = settings.r; double q = settings.q; double kappa = param.kappa; double theta = param.theta; double sigma = param.sigma; double v0 = param.v0; double rho = param.rho; double lambda = param.lambda; double x = Math.Log(S); double a = kappa * theta; int Trap = settings.trap; Complex b, u, d, g, c, D, G, C, f, integrand = new Complex(); // Parameters "u" and "b" are different for P1 and P2 if (Pnum == 1) { u = 0.5; b = kappa + lambda - rho * sigma; } else { u = -0.5; b = kappa + lambda; } d = Complex.Sqrt(Complex.Pow(rho * sigma * i * phi - b, 2) - sigma * sigma * (2.0 * u * i * phi - phi * phi)); g = (b - rho * sigma * i * phi + d) / (b - rho * sigma * i * phi - d); if (Trap == 1) { // "Little Heston Trap" formulation c = 1.0 / g; D = (b - rho * sigma * i * phi - d) / sigma / sigma * ((1.0 - Complex.Exp(-d * T)) / (1.0 - c * Complex.Exp(-d * T))); G = (1.0 - c * Complex.Exp(-d * T)) / (1 - c); C = (r - q) * i * phi * T + a / sigma / sigma * ((b - rho * sigma * i * phi - d) * T - 2.0 * Complex.Log(G)); } else { // Original Heston formulation. G = (1.0 - g * Complex.Exp(d * T)) / (1.0 - g); C = (r - q) * i * phi * T + a / sigma / sigma * ((b - rho * sigma * i * phi + d) * T - 2.0 * Complex.Log(G)); D = (b - rho * sigma * i * phi + d) / sigma / sigma * ((1.0 - Complex.Exp(d * T)) / (1.0 - g * Complex.Exp(d * T))); } // The characteristic function. f = Complex.Exp(C + D * v0 + i * phi * x); // The integrand. integrand = Complex.Exp(-i * phi * Complex.Log(K)) * f / i / phi; // Return the real part of the integrand. return(integrand.Real); }
// Characteristic function for the double Heston model public Complex DoubleHestonCF(Complex phi, DHParam param, OpSet settings) { Complex i = new Complex(0.0, 1.0); // Imaginary unit double S = settings.S; double K = settings.K; double T = settings.T; double r = settings.r; double q = settings.q; double kappa1 = param.kappa1; double theta1 = param.theta1; double sigma1 = param.sigma1; double v01 = param.v01; double rho1 = param.rho1; double kappa2 = param.kappa2; double theta2 = param.theta2; double sigma2 = param.sigma2; double v02 = param.v02; double rho2 = param.rho2; double x0 = Math.Log(S); int Trap = settings.trap; Complex d1, d2, G1, G2, B1, B2, X1, X2, A, g1, g2 = new Complex(); if (Trap == 1) { // Little Trap formulation of the characteristic function d1 = Complex.Sqrt(Complex.Pow(kappa1 - rho1 * sigma1 * i * phi, 2) + sigma1 * sigma1 * phi * (phi + i)); d2 = Complex.Sqrt(Complex.Pow(kappa2 - rho2 * sigma2 * i * phi, 2) + sigma2 * sigma2 * phi * (phi + i)); G1 = (kappa1 - rho1 * sigma1 * phi * i - d1) / (kappa1 - rho1 * sigma1 * phi * i + d1); G2 = (kappa2 - rho2 * sigma2 * phi * i - d2) / (kappa2 - rho2 * sigma2 * phi * i + d2); B1 = (kappa1 - rho1 * sigma1 * phi * i - d1) * (1.0 - Complex.Exp(-d1 * T)) / (sigma1 * sigma1) / (1.0 - G1 * Complex.Exp(-d1 * T)); B2 = (kappa2 - rho2 * sigma2 * phi * i - d2) * (1.0 - Complex.Exp(-d2 * T)) / (sigma2 * sigma2) / (1.0 - G2 * Complex.Exp(-d2 * T)); X1 = (1.0 - G1 * Complex.Exp(-d1 * T)) / (1.0 - G1); X2 = (1.0 - G2 * Complex.Exp(-d2 * T)) / (1.0 - G2); A = (r - q) * phi * i * T + kappa1 * theta1 / sigma1 / sigma1 * ((kappa1 - rho1 * sigma1 * phi * i - d1) * T - 2.0 * Complex.Log(X1)) + kappa2 * theta2 / sigma2 / sigma2 * ((kappa2 - rho2 * sigma2 * phi * i - d2) * T - 2.0 * Complex.Log(X2)); } else { // Original Heston formulation of the characteristic function d1 = Complex.Sqrt(Complex.Pow(kappa1 - rho1 * sigma1 * phi * i, 2) + sigma1 * sigma1 * (phi * i + phi * phi)); d2 = Complex.Sqrt(Complex.Pow(kappa2 - rho2 * sigma2 * phi * i, 2) + sigma2 * sigma2 * (phi * i + phi * phi)); g1 = (kappa1 - rho1 * sigma1 * phi * i + d1) / (kappa1 - rho1 * sigma1 * phi * i - d1); g2 = (kappa2 - rho2 * sigma2 * phi * i + d2) / (kappa2 - rho2 * sigma2 * phi * i - d2); B1 = (kappa1 - rho1 * sigma1 * phi * i + d1) * (1.0 - Complex.Exp(d1 * T)) / (sigma1 * sigma1) / (1.0 - g1 * Complex.Exp(d1 * T)); B2 = (kappa2 - rho2 * sigma2 * phi * i + d2) * (1.0 - Complex.Exp(d2 * T)) / (sigma2 * sigma2) / (1.0 - g2 * Complex.Exp(d2 * T)); X1 = (1.0 - g1 * Complex.Exp(d1 * T)) / (1.0 - g1); X2 = (1.0 - g2 * Complex.Exp(d2 * T)) / (1.0 - g2); A = (r - q) * phi * i * T + kappa1 * theta1 / sigma1 / sigma1 * ((kappa1 - rho1 * sigma1 * phi * i + d1) * T - 2.0 * Complex.Log(X1)) + kappa2 * theta2 / sigma2 / sigma2 * ((kappa2 - rho2 * sigma2 * phi * i + d2) * T - 2.0 * Complex.Log(X2)); } // The characteristic function. return(Complex.Exp(A + B1 * v01 + B2 * v02 + i * phi * x0)); }
public double DoubleHestonPriceGaussLaguerre(DHParam param, OpSet settings, double[] X, double[] W) { int N = X.Length; double S = settings.S; double K = settings.K; double r = settings.r; double q = settings.q; double T = settings.T; string PutCall = settings.PutCall; Complex i = new Complex(0.0, 1.0); Complex u = new Complex(0.0, 0.0); Complex[] f2 = new Complex[N]; Complex[] f1 = new Complex[N]; double[] Int1 = new double[N]; double[] Int2 = new double[N]; Complex I1 = new Complex(0.0, 0.0); Complex I2 = new Complex(0.0, 0.0); for (int k = 0; k <= N - 1; k++) { u = X[k]; f2[k] = HestonDoubleProb(u, param, settings); f1[k] = HestonDoubleProb(u - i, param, settings); I2 = Complex.Exp(-i * u * Complex.Log(K)) * f2[k] / i / u; I1 = Complex.Exp(-i * u * Complex.Log(K)) * f1[k] / i / u / S / Complex.Exp((r - q) * T); Int2[k] = W[k] * I2.Real; Int1[k] = W[k] * I1.Real; } // The ITM probabilities double pi = Math.PI; double P1 = 0.5 + 1.0 / pi * Int1.Sum(); double P2 = 0.5 + 1.0 / pi * Int2.Sum(); // The Double Heston call price double HestonC = S * Math.Exp(-q * T) * P1 - K * Math.Exp(-r * T) * P2; // The put price by put-call parity double HestonP = HestonC - S * Math.Exp(-q * T) + K * Math.Exp(-r * T); // Output the option price if (PutCall == "C") { return(HestonC); } else { return(HestonP); } }
// Simulation of stock price paths and variance paths using Euler or Milstein schemes public double[,] MMSim(HParam param, OpSet settings, int NT, int NS, double[,] Zv, double[,] Zs) { // Heston parameters double kappa = param.kappa; double theta = param.theta; double sigma = param.sigma; double v0 = param.v0; double rho = param.rho; double lambda = param.lambda; // option settings double r = settings.r; double q = settings.q; // Time increment double dt = settings.T / Convert.ToDouble(NT); // Initialize the variance and stock processes double[,] V = new double[NT, NS]; double[,] S = new double[NT, NS]; // Flags for negative variances int F = 0; // Starting values for the variance and stock processes for (int s = 0; s <= NS - 1; s++) { S[0, s] = settings.S; // Spot price V[0, s] = v0; // Heston v0 initial variance } // Generate the stock and volatility paths for (int s = 0; s <= NS - 1; s++) { for (int t = 1; t <= NT - 1; t++) { // Matched moment lognormal approximation double dW = Math.Sqrt(dt) * Zv[t, s]; double num = 0.5 * sigma * sigma * V[t - 1, s] * (1.0 - Math.Exp(-2.0 * kappa * dt)) / kappa; double den = Math.Pow(Math.Exp(-kappa * dt) * V[t - 1, s] + (1.0 - Math.Exp(-kappa * dt)) * theta, 2); double Gam = Math.Log(1 + num / den); V[t, s] = (Math.Exp(-kappa * dt) * V[t - 1, s] + (1.0 - Math.Exp(-kappa * dt)) * theta) * Math.Exp(-0.5 * Gam * Gam + Gam * Zv[t, s]); // Euler/Milstein discretization scheme for the log stock prices S[t, s] = S[t - 1, s] * Math.Exp((r - q - V[t - 1, s] / 2) * dt + Math.Sqrt(V[t - 1, s] * dt) * Zs[t, s]); } } return(S); }
// Heston Price by Gauss-Laguerre Integration public double HestonPriceGaussLaguerre(HParam param, OpSet settings, double[] x, double[] w) { Complex i = new Complex(0.0, 1.0); Complex[] f1 = new Complex[32]; Complex[] f2 = new Complex[32]; double[] int1 = new double[32]; double[] int2 = new double[32]; double S = settings.S; double K = settings.K; double T = settings.T; double r = settings.r; double q = settings.q; // Numerical integration for (int j = 0; j <= 31; j++) { double phi = x[j]; f1[j] = HestonCF(phi - i, param, settings) / (S * Math.Exp((r - q) * T)); f2[j] = HestonCF(phi, param, settings); Complex FF1 = Complex.Exp(-i * phi * Complex.Log(K)) * f1[j] / i / phi; Complex FF2 = Complex.Exp(-i * phi * Complex.Log(K)) * f2[j] / i / phi; int1[j] = w[j] * FF1.Real; int2[j] = w[j] * FF2.Real; } // Define P1 and P2 double pi = Math.PI; double P1 = 0.5 + 1.0 / pi * int1.Sum(); double P2 = 0.5 + 1.0 / pi * int2.Sum(); // The call price string PutCall = settings.PutCall; double HestonC = S * Math.Exp(-q * T) * P1 - K * Math.Exp(-r * T) * P2; // The put price by put-call parity double HestonP = HestonC - S * Math.Exp(-q * T) + K * Math.Exp(-r * T); // Output the option price if (PutCall == "C") { return(HestonC); } else { return(HestonP); } }
public Complex HestonBivariateCF(Complex phi1, Complex phi2, HParam param, OpSet settings) { Complex i = new Complex(0.0, 1.0); // Imaginary unit Complex S = new Complex(settings.S, 0.0); // Spot Price Complex K = new Complex(settings.K, 0.0); // Strike Price Complex T = new Complex(settings.T, 0.0); // Maturity in years Complex r = new Complex(settings.r, 0.0); // Interest rate Complex q = new Complex(settings.q, 0.0); // Dividend yield Complex rho = new Complex(param.rho, 0.0); // Heston parameter: correlation Complex kappa = new Complex(param.kappa, 0.0); // Heston parameter: mean reversion speed Complex theta = new Complex(param.theta, 0.0); // Heston parameter: mean reversion speed Complex lambda = new Complex(param.lambda, 0.0); // Heston parameter: price of volatility risk Complex sigma = new Complex(param.sigma, 0.0); // Heston parameter: volatility of variance Complex v0 = new Complex(param.v0, 0.0); // Heston parameter: initial variance Complex x = Complex.Log(S); Complex a = kappa * theta; int trap = settings.trap; Complex b, u, d, g, c, A = 0.0, C = 0.0, B, G = new Complex(); // Log stock price Complex x0 = Complex.Log(S); u = -0.5; b = kappa + lambda; d = Complex.Sqrt(Complex.Pow(rho * sigma * i * phi1 - b, 2) - sigma * sigma * (2.0 * u * i * phi1 - phi1 * phi1)); g = (b - rho * sigma * i * phi1 + d - sigma * sigma * i * phi2) / (b - rho * sigma * i * phi1 - d - sigma * sigma * i * phi2); c = 1 / g; B = i * phi1; if (trap == 1) { // Little Trap formulation in Kahl (2008) G = (c * Complex.Exp(-d * T) - 1.0) / (c - 1.0); A = (r - q) * i * phi1 * T + a / sigma / sigma * ((b - rho * sigma * i * phi1 - d) * T - 2.0 * Complex.Log(G)); C = ((b - rho * sigma * i * phi1 - d) - (b - rho * sigma * i * phi1 + d) * c * Complex.Exp(-d * T)) / (sigma * sigma) / (1 - c * Complex.Exp(-d * T)); } else if (trap == 0) { // Original Heston formulation. G = (1.0 - g * Complex.Exp(d * T)) / (1.0 - g); A = (r - q) * i * phi1 * T + a / sigma / sigma * ((b - rho * sigma * i * phi1 + d) * T - 2.0 * Complex.Log(G)); C = ((b - rho * sigma * i * phi1 + d) - (b - rho * sigma * i * phi1 - d) * g * Complex.Exp(d * T)) / (sigma * sigma) / (1.0 - g * Complex.Exp(d * T)); } // The characteristic function. return(Complex.Exp(A + B * x0 + C * v0)); }
static void Main(string[] args) { // 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]); } } // Heston parameters HParam param = new HParam(); param.kappa = 1.5; param.theta = 0.04; param.sigma = 0.3; param.v0 = 0.05412; param.rho = -0.9; param.lambda = 0.0; // Option settings OpSet settings = new OpSet(); settings.S = 101.52; settings.K = 100.0; settings.T = 0.15; settings.r = 0.02; settings.q = 0.0; settings.PutCall = "C"; settings.trap = 1; // The Heston price HestonPrice HP = new HestonPrice(); double Price = HP.HestonPriceGaussLaguerre(param, settings, x, w); Console.WriteLine("Heston price using 32-point Gauss Laguerre"); Console.WriteLine("------------------------------------------ "); Console.WriteLine("Option Flavor = {0,0:F5}", settings.PutCall); Console.WriteLine("Strike Price = {0,0:0}", settings.K); Console.WriteLine("Maturity = {0,0:F2}", settings.T); Console.WriteLine("Price = {0,0:F4}", Price); Console.WriteLine("------------------------------------------ "); Console.WriteLine(" "); }
static void Main(string[] args) { // 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]); } } HParam param = new HParam(); param.rho = -0.9; // Heston Parameter: Correlation param.kappa = 2; // Heston Parameter: Mean reversion speed param.theta = 0.05; // Heston Parameter: Mean reversion level param.lambda = 0.0; // Heston Parameter: Risk parameter param.sigma = 0.3; // Heston Parameter: Volatility of Variance param.v0 = 0.035; // Heston Parameter: Current Variance OpSet settings = new OpSet(); settings.S = 100.0; // Spot Price settings.K = 100.0; // Strike Price settings.T = 0.25; // Maturity in Years settings.r = 0.05; // Interest Rate settings.q = 0.02; // Dividend yield settings.PutCall = "P"; // "P"ut or "C"all settings.trap = 1; // 1="Little Trap" characteristic function // The Heston price HestonPriceConsolidated HPC = new HestonPriceConsolidated(); double Price = HPC.HestonPriceConsol(param, settings, x, w); Console.WriteLine("Heston price using consolidated integral"); Console.WriteLine("------------------------------------------------ "); Console.WriteLine("Option Flavor = {0,0:F5}", settings.PutCall); Console.WriteLine("Strike Price = {0,0:0}", settings.S); Console.WriteLine("Maturity = {0,0:F2}", settings.T); Console.WriteLine("Price = {0,0:F4}", Price); Console.WriteLine("------------------------------------------------ "); Console.WriteLine(" "); }
// Bisection Algorithm for Black Scholes Implied Volatility ================================================================================= public double BisecBSIV(OpSet settings, double K, double T, double a, double b, double MktPrice, double Tol, int MaxIter) { BlackScholesPrice BS = new BlackScholesPrice(); double S = settings.S; double rf = settings.r; double q = settings.q; string PutCall = settings.PutCall; double lowCdif = MktPrice - BS.BlackScholes(S, K, T, rf, q, a, PutCall); double highCdif = MktPrice - BS.BlackScholes(S, K, T, rf, q, b, PutCall); double BSIV = 0.0; double midP; if (lowCdif * highCdif > 0.0) { BSIV = -1.0; } else { for (int x = 0; x <= MaxIter; x++) { midP = (a + b) / 2.0; double midCdif = MktPrice - BS.BlackScholes(S, K, T, rf, q, midP, PutCall); if (Math.Abs(midCdif) < Tol) { break; } else { if (midCdif > 0.0) { a = midP; } else { b = midP; } } BSIV = midP; } } return(BSIV); }
// Price by simulation public double MMPrice(HParam param, OpSet settings, int NT, int NS) { RandomNumbers RN = new RandomNumbers(); double[] STe = MMSim(param, settings, NT, NS); double[] Price = new double[NS]; for (int s = 0; s <= NS - 1; s++) { if (settings.PutCall == "C") { Price[s] = Math.Max(STe[s] - settings.K, 0.0); } else if (settings.PutCall == "P") { Price[s] = Math.Max(settings.K - STe[s], 0.0); } } return(Math.Exp(-settings.r * settings.T) * RN.Mean(Price)); }
// Price by simulation public double EulerMilsteinPrice(string scheme, string negvar, HParam param, OpSet settings, double alpha, int NT, int NS, string PutCall) { RandomNumbers RA = new RandomNumbers(); double[] STe = EulerMilsteinSim(scheme, negvar, param, settings, alpha, NT, NS); double[] Price = new double[NS]; for (int s = 0; s <= NS - 1; s++) { if (settings.PutCall == "C") { Price[s] = Math.Max(STe[s] - settings.K, 0.0); } else if (settings.PutCall == "P") { Price[s] = Math.Max(settings.K - STe[s], 0.0); } } return(Math.Exp(-settings.r * settings.T) * RA.Mean(Price)); }
// Price by simulation public double QEPrice(HParam param, OpSet settings, double gamma1, double gamma2, int NT, int NS, int MC, double phic, string PutCall) { RandomNumbers RN = new RandomNumbers(); double[] STe = QESim(param, settings, gamma1, gamma2, NT, NS, MC, phic); double[] Price = new double[NS]; for (int s = 0; s <= NS - 1; s++) { if (settings.PutCall == "C") { Price[s] = Math.Max(STe[s] - settings.K, 0.0); } else if (settings.PutCall == "P") { Price[s] = Math.Max(settings.K - STe[s], 0.0); } } return(Math.Exp(-settings.r * settings.T) * RN.Mean(Price)); }
// Heston Univariate characteristic function (f2) public Complex HestonCF(Complex phi, HParam param, OpSet settings) { Complex i = new Complex(0.0, 1.0); // Imaginary unit Complex S = new Complex(settings.S, 0.0); // Spot Price Complex K = new Complex(settings.K, 0.0); // Strike Price Complex T = new Complex(settings.T, 0.0); // Maturity in years Complex r = new Complex(settings.r, 0.0); // Interest rate Complex q = new Complex(settings.q, 0.0); // Dividend yield Complex rho = new Complex(param.rho, 0.0); // Heston parameter: correlation Complex kappa = new Complex(param.kappa, 0.0); // Heston parameter: mean reversion speed Complex theta = new Complex(param.theta, 0.0); // Heston parameter: mean reversion speed Complex lambda = new Complex(param.lambda, 0.0); // Heston parameter: price of volatility risk Complex sigma = new Complex(param.sigma, 0.0); // Heston parameter: volatility of variance Complex v0 = new Complex(param.v0, 0.0); // Heston parameter: initial variance Complex x = Complex.Log(S); Complex a = kappa * theta; int Trap = settings.trap; Complex b, u, d, g, c, D, G, C = new Complex(); u = -0.5; b = kappa + lambda; d = Complex.Sqrt(Complex.Pow(rho * sigma * i * phi - b, 2) - sigma * sigma * (2.0 * u * i * phi - phi * phi)); g = (b - rho * sigma * i * phi + d) / (b - rho * sigma * i * phi - d); if (Trap == 1) { // "Little Heston Trap" formulation c = 1.0 / g; D = (b - rho * sigma * i * phi - d) / sigma / sigma * ((1.0 - Complex.Exp(-d * T)) / (1.0 - c * Complex.Exp(-d * T))); G = (1.0 - c * Complex.Exp(-d * T)) / (1 - c); C = (r - q) * i * phi * T + a / sigma / sigma * ((b - rho * sigma * i * phi - d) * T - 2.0 * Complex.Log(G)); } else { // Original Heston formulation. G = (1.0 - g * Complex.Exp(d * T)) / (1.0 - g); C = (r - q) * i * phi * T + a / sigma / sigma * ((b - rho * sigma * i * phi + d) * T - 2.0 * Complex.Log(G)); D = (b - rho * sigma * i * phi + d) / sigma / sigma * ((1.0 - Complex.Exp(d * T)) / (1.0 - g * Complex.Exp(d * T))); } // The characteristic function. return(Complex.Exp(C + D * v0 + i * phi * x)); }
// Heston characteristic function (f2) public Complex HestonCF(Complex phi, HParam param, OpSet settings) { Complex i = new Complex(0.0, 1.0); // Imaginary unit double S = settings.S; double K = settings.K; double T = settings.T; double r = settings.r; double q = settings.q; double kappa = param.kappa; double theta = param.theta; double sigma = param.sigma; double v0 = param.v0; double rho = param.rho; double lambda = param.lambda; double x = Math.Log(S); double a = kappa * theta; int Trap = settings.trap; Complex b, u, d, g, c, D, G, C = new Complex(); u = -0.5; b = kappa + lambda; d = Complex.Sqrt(Complex.Pow(rho * sigma * i * phi - b, 2) - sigma * sigma * (2.0 * u * i * phi - phi * phi)); g = (b - rho * sigma * i * phi + d) / (b - rho * sigma * i * phi - d); if (Trap == 1) { // "Little Heston Trap" formulation c = 1.0 / g; D = (b - rho * sigma * i * phi - d) / sigma / sigma * ((1.0 - Complex.Exp(-d * T)) / (1.0 - c * Complex.Exp(-d * T))); G = (1.0 - c * Complex.Exp(-d * T)) / (1 - c); C = (r - q) * i * phi * T + a / sigma / sigma * ((b - rho * sigma * i * phi - d) * T - 2.0 * Complex.Log(G)); } else { // Original Heston formulation. G = (1.0 - g * Complex.Exp(d * T)) / (1.0 - g); C = (r - q) * i * phi * T + a / sigma / sigma * ((b - rho * sigma * i * phi + d) * T - 2.0 * Complex.Log(G)); D = (b - rho * sigma * i * phi + d) / sigma / sigma * ((1.0 - Complex.Exp(d * T)) / (1.0 - g * Complex.Exp(d * T))); } // The characteristic function. return(Complex.Exp(C + D * v0 + i * phi * x)); }
// Heston Price by Gauss-Legendre Integration public double HestonPriceGaussLegendre(HParam param, OpSet settings, double[] x, double[] w, double a, double b) { double[] int1 = new Double[32]; double[] int2 = new Double[32]; // Numerical integration for (int j = 0; j <= 31; j++) { double X = (a + b) / 2.0 + (b - a) / 2.0 * x[j]; int1[j] = w[j] * HestonProb(X, param, settings, 1); int2[j] = w[j] * HestonProb(X, param, settings, 2); } // Define P1 and P2 double pi = Math.PI; double P1 = 0.5 + 1.0 / pi * int1.Sum() * (b - a) / 2.0; double P2 = 0.5 + 1.0 / pi * int2.Sum() * (b - a) / 2.0; // The call price double S = settings.S; double K = settings.K; double T = settings.T; double r = settings.r; double q = settings.q; string PutCall = settings.PutCall; double HestonC = S * Math.Exp(-q * T) * P1 - K * Math.Exp(-r * T) * P2; // The put price by put-call parity double HestonP = HestonC - S * Math.Exp(-q * T) + K * Math.Exp(-r * T); // Output the option price if (PutCall == "C") { return(HestonC); } else { return(HestonP); } }
public double CarrMadanGreeks(double alpha, double tau, HParam param, OpSet Opsettings, string Greek, double[] x, double[] w) { Complex i = new Complex(0.0, 1.0); // Imaginary unit double q = 0.0; int N = x.Length; // Perform the numerical integration Complex J = 0.0; double[] I = new double[N]; for (int j = 0; j <= N - 1; j++) { double u = x[j]; J = w[j] * Complex.Exp(-i * u * Math.Log(Opsettings.K)) * Math.Exp(-Opsettings.r * Opsettings.T) * HestonCFGreek(u - (alpha + 1) * i, param, Opsettings, Greek) / (alpha * alpha + alpha - u * u + i * (2.0 * alpha + 1.0) * u); I[j] = J.Real; } // Calculate the desired Greek double pi = Math.PI; if ((Greek == "Delta") || (Greek == "Gamma") || (Greek == "Rho") || (Greek == "Theta") || (Greek == "Volga") || (Greek == "Price")) { return(Math.Exp(-alpha * Math.Log(Opsettings.K)) * I.Sum() / pi); } else if ((Greek == "Vega1") || (Greek == "Vanna")) { return(Math.Exp(-alpha * Math.Log(Opsettings.K)) * I.Sum() / pi * 2.0 * Math.Sqrt(param.v0)); } else { return(0.0); } }
// Call price by Fractional Fast Fourier Transform ===================================================================== public double[,] HestonFRFTGreek(int N, double uplimit, double alpha, string rule, double lambdainc, double eta, HParam param, OpSet settings, string Greek) { HestonGreeks HG = new HestonGreeks(); double s0 = Math.Log(settings.S); double pi = Math.PI; // Initialize and specify the weights double[] w = new double[N]; if (rule == "Trapezoidal") { w[0] = 0.5; w[N - 1] = 0.5; for (int j = 1; j <= N - 2; j++) { w[j] = 1; } } else if (rule == "Simpsons") { w[0] = 1.0 / 3.0; w[N - 1] = 1.0 / 3.0; for (int j = 1; j <= N - 1; j++) { w[j] = (1.0 / 3.0) * (3 + Math.Pow(-1, j + 1)); } } // Specify the b parameter double b = Convert.ToDouble(N) * lambdainc / 2.0; // Create the grid for the integration double[] v = new double[N]; for (int j = 0; j <= N - 1; j++) { v[j] = eta * j; } // Create the grid for the log-strikes and strikes double[] k = new double[N]; double[] K = new double[N]; for (int j = 0; j <= N - 1; j++) { k[j] = -b + lambdainc * Convert.ToDouble(j) + s0; K[j] = Math.Exp(k[j]); } // Beta parameter for the FRFT double beta = lambdainc * eta / 2.0 / pi; // Option price settings double tau = settings.T; double r = settings.r; double q = settings.q; // Implement the FFT Complex i = new Complex(0.0, 1.0); Complex[] psi = new Complex[N]; Complex[] phi = new Complex[N]; Complex[] x = new Complex[N]; Complex[] y = new Complex[N]; double[] CallFRFT = new double[N]; double[] sume = new double[N]; // Build the input vectors for the FRFT for (int j = 0; j <= N - 1; j++) { psi[j] = HG.HestonCFGreek(v[j] - (alpha + 1.0) * i, param, settings, Greek); phi[j] = Complex.Exp(-r * tau) * psi[j] / (alpha * alpha + alpha - v[j] * v[j] + i * v[j] * (2.0 * alpha + 1.0)); x[j] = Complex.Exp(i * (b - s0) * v[j]) * phi[j] * w[j]; } y = FRFT(x, beta); Complex Call = new Complex(0.0, 0.0); for (int u = 0; u <= N - 1; u++) { Call = eta * Complex.Exp(-alpha * k[u]) * y[u] / pi; CallFRFT[u] = Call.Real; } // Return the FRFT call price and the strikes double[,] output = new double[N, 2]; for (int j = 0; j <= N - 1; j++) { output[j, 0] = K[j]; output[j, 1] = CallFRFT[j]; } return(output); }
private void GenerateLewisTable_MouseClick(object sender, MouseEventArgs e) { double S = 100.0; // Spot Price double T = 0.25; // Maturity in Years double rf = 0.0; // Interest Rate double q = 0.0; // Dividend yield string PutCall = "C"; // "P"ut or "Call" int trap = 1; // 1="Little Trap" characteristic function OpSet opset = new OpSet(); opset.S = S; opset.T = T; opset.r = rf; opset.q = q; opset.PutCall = PutCall; opset.trap = trap; double kappa = 4.0; // Heston Parameter double theta = 0.09 / 4.0; // Heston Parameter double sigma = 0.1; // Heston Parameter: Volatility of Variance double v0 = 0.0225; // Heston Parameter: Current Variance double rho = -0.5; // Heston Parameter: Correlation double lambda = 0.0; // Heston Parameter HParam param = new HParam(); param.kappa = kappa; param.theta = theta; param.sigma = sigma; param.v0 = v0; param.rho = rho; param.lambda = lambda; int NK = 7; double[] K = new double[7] // Strikes { 70.0, 80.0, 90.0, 100.0, 110.0, 120.0, 130.0 }; // Time average of the deterministic variance and volatility double vol = param.theta + (param.v0 - param.theta) * (1.0 - Math.Exp(-param.kappa * T)) / (param.kappa * T); double IV = Math.Sqrt(vol); // Classes BlackScholes BS = new BlackScholes(); NewtonCotes NC = new NewtonCotes(); Lewis L = new Lewis(); BisectionAlgo BA = new BisectionAlgo(); // Table 3.3.1 on Page 81 of Lewis (2001) double[] SeriesIPrice = new double[NK]; // Series I price and implied vol double[] IVI = new double[NK]; double[] SeriesIIPrice = new double[NK]; // Series II price and implied vol double[] IVII = new double[NK]; double[] HPrice = new double[NK]; // Exact Heston price and implied vol double[] IVe = new double[NK]; double[] BSPrice = new double[NK]; // Black Scholes price // Bisection algorithm settings double lo = 0.01; double hi = 2.0; double Tol = 1.0e-10; int MaxIter = 10000; // Newton Cotes settings int method = 3; double a = 1.0e-10; double b = 100.0; int N = 10000; double[] SeriesII = new double[2]; for (int k = 0; k <= NK - 1; k++) { // Generate the prices and implied vols opset.K = K[k]; SeriesIPrice[k] = L.SeriesICall(S, K[k], rf, q, T, v0, rho, theta, kappa, sigma); IVI[k] = BA.BisecBSIV(PutCall, S, K[k], rf, q, T, lo, hi, SeriesIPrice[k], Tol, MaxIter); SeriesII = L.SeriesIICall(S, K[k], rf, q, T, v0, rho, theta, kappa, sigma); SeriesIIPrice[k] = SeriesII[0]; IVII[k] = SeriesII[1]; HPrice[k] = NC.HestonPriceNewtonCotes(param, opset, method, a, b, N); IVe[k] = BA.BisecBSIV(PutCall, S, K[k], rf, q, T, lo, hi, HPrice[k], Tol, MaxIter); BSPrice[k] = BS.BSC(S, K[k], rf, q, vol, T); // Write to the List Boxes Strike.Items.Add(K[k]); ExactPrice.Items.Add(Math.Round(HPrice[k], 6)); IVExact.Items.Add(Math.Round(100 * IVe[k], 2)); SeriesICall.Items.Add(Math.Round(SeriesIPrice[k], 6)); IV1.Items.Add(Math.Round(100 * IVI[k], 2)); SeriesIICall.Items.Add(Math.Round(SeriesIIPrice[k], 6)); IV2.Items.Add(Math.Round(100 * IVII[k], 2)); BlackScholesPrice.Items.Add(Math.Round(BSPrice[k], 6)); IVBS.Items.Add(Math.Round(100 * IV, 2)); } }
static void Main(string[] args) { // 32-point Gauss-Laguerre Abscissas and weights double[] xGLa = new Double[32]; double[] wGLa = 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(' '); xGLa[k] = double.Parse(bits[0]); wGLa[k] = double.Parse(bits[1]); } } // 32-point Gauss-Legendre Abscissas and weights double[] xGLe = new Double[32]; double[] wGLe = new Double[32]; using (TextReader reader = File.OpenText("../../GaussLegendre32.txt")) { for (int k = 0; k <= 31; k++) { string text = reader.ReadLine(); string[] bits = text.Split(' '); xGLe[k] = double.Parse(bits[0]); wGLe[k] = double.Parse(bits[1]); } } // 32-point Gauss-Lobatto Abscissas and weights double[] xGLo = new Double[32]; double[] wGLo = new Double[32]; using (TextReader reader = File.OpenText("../../GaussLobatto32.txt")) { for (int k = 0; k <= 31; k++) { string text = reader.ReadLine(); string[] bits = text.Split(' '); xGLo[k] = double.Parse(bits[0]); wGLo[k] = double.Parse(bits[1]); } } // Heston parameters HParam param = new HParam(); param.kappa = 2.0; param.theta = 0.05; param.sigma = 0.3; param.v0 = 0.035; param.rho = -0.9; param.lambda = 0.0; // Option price settings OpSet settings = new OpSet(); settings.S = 100.0; settings.T = 0.25; settings.r = 0.05; settings.q = 0.02; settings.trap = 1; // Lower and upper integration limits double a = 0.0; // Lower Limit for Gauss Legendre and Newton Coates double b = 100.0; // Upper Limit for Gauss Legendre double A = 1e-5; // Lower Limit for Gauss Lobatto double B = 100.0; // Upper Limit for Gauss Lobatto int N = 5000; // Number of points for Newton Coates quadratures // Range of strikes and option flavor double[] Strikes = new double[] { 90.0, 95.0, 100.0, 105.0, 110.0 }; settings.PutCall = "C"; // Initialize the price vectors int M = Strikes.Length; double[] PriceGLa = new double[M]; double[] PriceGLe = new double[M]; double[] PriceGLo = new double[M]; double[] PriceMP = new double[M]; double[] PriceTrapz = new double[M]; double[] PriceSimp = new double[M]; double[] PriceSimp38 = new double[M]; // Obtain the prices and output to console HestonPrice HP = new HestonPrice(); NewtonCotes NC = new NewtonCotes(); Console.WriteLine("Using {0:0} points for Newton Cotes and 32 points for quadrature ", N); Console.WriteLine(" "); for (int k = 0; k <= M - 1; k++) { settings.K = Strikes[k]; PriceGLa[k] = HP.HestonPriceGaussLaguerre(param, settings, xGLa, wGLa); // Gauss Laguerre PriceGLe[k] = HP.HestonPriceGaussLegendre(param, settings, xGLe, wGLe, a, b); // Gauss Legendre PriceGLo[k] = HP.HestonPriceGaussLegendre(param, settings, xGLo, wGLo, A, B); // Gauss Lobatto PriceMP[k] = NC.HestonPriceNewtonCotes(param, settings, 1, a, b, N); // Mid point rule PriceTrapz[k] = NC.HestonPriceNewtonCotes(param, settings, 2, A, B, N); // Trapezoidal rule PriceSimp[k] = NC.HestonPriceNewtonCotes(param, settings, 3, A, B, N); // Simpson's Rule PriceSimp38[k] = NC.HestonPriceNewtonCotes(param, settings, 4, A, B, N); // Simpson's 3/8 rule Console.Write("Strike {0,2}", Strikes[k]); Console.WriteLine(" ------------------"); Console.WriteLine("Mid Point {0:F5}", PriceMP[k]); Console.WriteLine("Trapezoidal {0:F5}", PriceTrapz[k]); Console.WriteLine("Simpson's {0:F5}", PriceSimp[k]); Console.WriteLine("Simpson's 3/8 {0:F5}", PriceSimp38[k]); Console.WriteLine("Gauss Laguerre {0:F5}", PriceGLa[k]); Console.WriteLine("Gauss Legendre {0:F5}", PriceGLe[k]); Console.WriteLine("Gauss Lobatto {0:F5}", PriceGLo[k]); Console.WriteLine(" "); } }
// Objective function =========================================================================== public double DHObjFunSVC(double[] param, OpSet settings, MktData data, double[] X, double[] W, int objectivefun, double[] lb, double[] ub) { HestonPriceDH HPDH = new HestonPriceDH(); BisectionAlgo BA = new BisectionAlgo(); double S = settings.S; double r = settings.r; double q = settings.q; int trap = settings.trap; double[,] MktIV = data.MktIV; double[,] MktPrice = data.MktPrice; string[,] PutCall = data.PutCall; double[] K = data.K; double[] T = data.T; int NK = PutCall.GetLength(0); int NT = PutCall.GetLength(1); int NX = X.Length; DHParam param2 = new DHParam(); param2.kappa1 = param[0]; param2.theta1 = param[1]; param2.sigma1 = param[2]; param2.v01 = param[3]; param2.rho1 = param[4]; param2.kappa2 = param[5]; param2.theta2 = param[6]; param2.sigma2 = param[7]; param2.v02 = param[8]; param2.rho2 = param[9]; // Settings for the Bisection algorithm double a = 0.001; double b = 3.0; double Tol = 1e5; int BSIVMaxIter = 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 BSVega = 0.0; double Error = 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 v0LB = lb[3]; double v0UB = ub[3]; double rhoLB = lb[4]; double rhoUB = ub[4]; double phi, P1, P2, CallPrice; Complex Integrand1, Integrand2; Complex i = Complex.ImaginaryOne; Complex[] f2 = new Complex[NX]; Complex[] f1 = new Complex[NX]; double[] int1 = new double[NX]; double[] int2 = new double[NX]; if ((param2.kappa1 <= kappaLB) || (param2.theta1 <= thetaLB) || (param2.sigma1 <= sigmaLB) || (param2.v01 <= v0LB) || (param2.rho1 <= rhoLB) || (param2.kappa1 >= kappaUB) || (param2.theta1 >= thetaUB) || (param2.sigma1 >= sigmaUB) || (param2.v01 >= v0UB) || (param2.rho1 >= rhoUB) || (param2.kappa2 <= kappaLB) || (param2.theta2 <= thetaLB) || (param2.sigma2 <= sigmaLB) || (param2.v02 <= v0LB) || (param2.rho2 <= rhoLB) || (param2.kappa2 >= kappaUB) || (param2.theta2 >= thetaUB) || (param2.sigma2 >= sigmaUB) || (param2.v02 >= v0UB) || (param2.rho2 >= rhoUB)) { Error = 1e50; } else { for (int t = 0; t < NT; t++) { for (int j = 0; j < NX; j++) { settings.T = T[t]; phi = X[j]; f2[j] = HPDH.HestonDoubleCF(phi, param2, settings); f1[j] = HPDH.HestonDoubleCF(phi - i, param2, settings); } for (int k = 0; k < NK; k++) { for (int j = 0; j < NX; j++) { phi = X[j]; Integrand2 = Complex.Exp(-i * phi * Complex.Log(K[k])) * f2[j] / i / phi; int2[j] = W[j] * Integrand2.Real; Integrand1 = Complex.Exp(-i * phi * Complex.Log(K[k])) * f1[j] / i / phi / S / Complex.Exp((r - q) * T[t]); int1[j] = W[j] * Integrand1.Real; } P1 = 0.5 + 1.0 / pi * int1.Sum(); P2 = 0.5 + 1.0 / pi * int2.Sum(); // The call price CallPrice = S * Math.Exp(-q * T[t]) * P1 - K[k] * Math.Exp(-r * T[t]) * P2; 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]; } switch (objectivefun) { case 1: // MSE Loss Function Error += Math.Pow(ModelPrice[k, t] - MktPrice[k, t], 2.0); break; case 2: // RMSE Loss Function Error += Math.Pow(ModelPrice[k, t] - MktPrice[k, t], 2.0) / MktPrice[k, t]; 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, BSIVMaxIter); Error += Math.Pow(ModelIV[k, t] - MktIV[k, t], 2); 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.0 * pi); BSVega = S * NormPDF * Math.Sqrt(T[t]); Error += Math.Pow(ModelPrice[k, t] - MktPrice[k, t], 2.0) / (BSVega * BSVega); break; } } } } return(Error); }
static void Main(string[] args) { Stopwatch sw = new Stopwatch(); TimeSpan ts = sw.Elapsed; // 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]); } // Clarke and Parrott settings double[] TruePrice = new double[5] { 2.0, 1.1070641, 0.520030, 0.213668, 0.082036 }; double[] Spot = new double[5] { 8.0, 9.0, 10.0, 11.0, 12.0 }; HParam param = new HParam(); param.kappa = 5.0; param.theta = 0.16; param.sigma = 0.9; param.v0 = 0.0625; param.rho = 0.1; param.lambda = 0.0; OpSet settings = new OpSet(); settings.K = 10.0; settings.T = 0.25; settings.r = 0.1; settings.q = 0.0; settings.PutCall = "P"; settings.trap = 1; // Simulation settings int NT = 100; int NS = 5000; // Generate the correlated random variables RandomNumber RN = new RandomNumber(); double[,] Zv = new double[NT, NS]; double[,] Zs = new double[NT, NS]; double rho = param.rho; sw.Reset(); sw.Start(); for (int t = 0; t <= NT - 1; t++) { for (int s = 0; s <= NS - 1; s++) { Zv[t, s] = RN.RandomNorm(); Zs[t, s] = rho * Zv[t, s] + Math.Sqrt(1 - rho * rho) * RN.RandomNorm(); } } // Generate LSM prices using the correlated random variables generated above int M = Spot.Length; double[] ClosedEuro = new double[M]; double[] LSMEuro = new double[M]; double[] LSMAmer = new double[M]; double[] CVAmer = new double[M]; double[] ErrorLSM = new double[M]; double[] ErrorCV = new double[M]; double[] ErrorE = new double[M]; double TotalLSM = 0.0, TotalCV = 0.0, TotalE = 0.0; double[] LSMPrice = new double[2]; // Obtain the prices LSMonteCarlo LS = new LSMonteCarlo(); HestonPrice HP = new HestonPrice(); Regression RE = new Regression(); MMSimulation MM = new MMSimulation(); for (int k = 0; k <= M - 1; k++) { // LSM Euro and American prices settings.S = Spot[k]; LSMPrice = LS.HestonLSM(RE.MTrans(MM.MMSim(param, settings, NT, NS, Zv, Zs)), settings.K, settings.r, settings.q, settings.T, NT, NS, settings.PutCall); LSMEuro[k] = LSMPrice[0]; LSMAmer[k] = LSMPrice[1]; // Closed Euro price ClosedEuro[k] = HP.HestonPriceGaussLaguerre(param, settings, X, W); // Control variate price CVAmer[k] = ClosedEuro[k] + (LSMAmer[k] - LSMEuro[k]); // The errors and total absolute errors ErrorLSM[k] = TruePrice[k] - LSMAmer[k]; ErrorCV[k] = TruePrice[k] - CVAmer[k]; ErrorE[k] = ClosedEuro[k] - LSMEuro[k]; TotalLSM += Math.Abs(ErrorLSM[k]); TotalCV += Math.Abs(ErrorCV[k]); TotalE += Math.Abs(ErrorE[k]); } sw.Stop(); ts = sw.Elapsed; Console.WriteLine("Comparison of Clarke and Parrott American Put prices with L-S Monte Carlo"); Console.WriteLine("LSM uses {0:0} time steps and {1:0} stock paths", NT, NS); Console.WriteLine("-----------------------------------------------------------"); Console.WriteLine(" S(0) TrueAmer LSMAmer CVAmer ClosedEuro LSMEuro"); Console.WriteLine("-----------------------------------------------------------"); for (int k = 0; k <= M - 1; k++) { Console.WriteLine("{0,3} {1,10:F6} {2,10:F6} {3,10:F6} {4,10:F6} {5,10:F6}", Spot[k], TruePrice[k], LSMAmer[k], CVAmer[k], ClosedEuro[k], LSMEuro[k]); } Console.WriteLine("-----------------------------------------------------------"); Console.WriteLine("AbsError {0,15:F5} {1,10:F5} {2,21:F5}", TotalLSM, TotalCV, TotalE); Console.WriteLine("-----------------------------------------------------------"); Console.WriteLine("Simulation time {0:0} minutes and {1,5:F3} seconds ", ts.Minutes, ts.Seconds); }
public double[] MSPriceHeston(HParam param, OpSet opset, MSSet msset) { // param = Heston parameters // opset = option settings // msset = Medvedev-Scaillet expansion settings // Extract quantities from the option settings double K = opset.K; double S = opset.S; double v0 = param.v0; double T = opset.T; double r = opset.r; double q = opset.q; // Extract quantities from the MS structure int N = msset.N; double a = msset.a; double b = msset.b; double A = msset.A; double B = msset.B; double dt = msset.dt; double tol = msset.tol; int MaxIter = msset.MaxIter; int NumTerms = msset.NumTerms; double yinf = msset.yinf; int method = msset.method; // Closed-form European put NewtonCotes NC = new NewtonCotes(); double EuroPutClosed = NC.HestonPriceNewtonCotes(param, opset, method, A, B, N); // Moneyness double theta = Math.Log(K / S) / Math.Sqrt(v0) / Math.Sqrt(T); // Find the barrier level // Bisection method to find y // a = Math.Max(1.25,0.95*theta); // b = 3.0; // double y = BisectionMS(a,b,theta,K,param,r,q,T,NumTerms,tol,MaxIter,dt); // Golden section search method to find y GoldenSearch GS = new GoldenSearch(); a = 1.5; b = 2.5; double y = GS.GoldenSearchMS(a, b, tol, MaxIter, K, param, theta, r, q, T, NumTerms); if (y < theta) { y = theta; } // Find the early exercise premium MSExpansionHeston MS = new MSExpansionHeston(); double AmerPutMS = MS.MSPutHeston(y, theta, K, param, r, q, T, NumTerms); double EuroPutMS = MS.MSPutHeston(yinf, theta, K, param, r, q, T, NumTerms); double EEP = AmerPutMS - EuroPutMS; // Control variate American put double AmerPut = EuroPutClosed + EEP; // Output the results double[] output = new double[6]; output[0] = EuroPutClosed; output[1] = AmerPutMS; output[2] = AmerPut; // Medvedev and Scaillet recommend this one. output[3] = EEP; output[4] = theta; output[5] = y; return(output); }
public double MSGreeksFD(HParam param, OpSet opset, int method, double A, double B, int N, double hi, double tol, int MaxIter, int NumTerms, double yinf, string Greek) { MSExpansionHeston MS = new MSExpansionHeston(); double[] output = new double[6]; double AmerPut, AmerPutp, AmerPutm; double AmerPutpp, AmerPutpm, AmerPutmp, AmerPutmm; double S = opset.S; double v0 = param.v0; double T = opset.T; // Define the finite difference increments double ds = opset.S * 0.005; double dt = opset.T * 0.005; double dv = param.v0 * 0.005; if (Greek == "price") { output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPut = output[2]; return(AmerPut); } else if ((Greek == "delta") || (Greek == "gamma")) { opset.S = S + ds; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPutp = output[2]; opset.S = S - ds; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPutm = output[2]; if (Greek == "gamma") { opset.S = S; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPut = output[2]; return((AmerPutp - 2.0 * AmerPut + AmerPutm) / ds / ds); } else { return((AmerPutp - AmerPutm) / 2.0 / ds); } } else if (Greek == "theta") { opset.T = T + dt; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPutp = output[2]; opset.T = T - dt; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPutm = output[2]; return(-(AmerPutp - AmerPutm) / 2.0 / dt); } else if ((Greek == "vega1") || (Greek == "volga")) { param.v0 = v0 + dv; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPutp = output[2]; param.v0 = v0 - dv; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPutm = output[2]; double Vega1 = (AmerPutp - AmerPutm) / 2.0 / dv * 2.0 * Math.Sqrt(v0); if (Greek == "volga") { param.v0 = v0; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPut = output[2]; double dC2 = (AmerPutp - 2.0 * AmerPut + AmerPutm) / dv / dv; return(4.0 * Math.Sqrt(v0) * (dC2 * Math.Sqrt(v0) + Vega1 / 4.0 / v0)); } else { return(Vega1); } } else if (Greek == "vanna") { opset.S = S + ds; param.v0 = v0 + dv; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPutpp = output[2]; param.v0 = v0 - dv; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPutpm = output[2]; opset.S = S - ds; param.v0 = v0 + dv; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPutmp = output[2]; param.v0 = v0 - dv; output = MS.MSPrice(param, opset, method, A, B, N, hi, tol, MaxIter, NumTerms, yinf); AmerPutmm = output[2]; return((AmerPutpp - AmerPutpm - AmerPutmp + AmerPutmm) / 4.0 / dv / ds * 2.0 * Math.Sqrt(v0)); } else { return(0.0); } }
static void Main(string[] args) { // 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]); } // Reproduces ATM prices in Table 8 of Benhamou, Gobet, and Miri //"Time Dependent Heston Model" // SIAM Journal on Financial Mathematics, Vol. 1, (2010) // Exact prices from the BGM (2010) paper double[] TruePW = new double[8] { 3.93, 5.53, 7.85, 11.23, 13.92, 18.37, 22.15, 27.17 }; // Spot price, risk free rate, dividend yield OpSet settings = new OpSet(); settings.S = 100.0; settings.r = 0.0; settings.q = 0.0; settings.PutCall = "P"; settings.trap = 1; // Little trap formulation double K = 100.0; // Heston piecewise constant parameters. double kappa = 3.0; double v0 = 0.04; double[] T = new double[40]; double[] THETA = new double[40]; double[] SIGMA = new double[40]; double[] RHO = new double[40]; // Construct the piecewise constant parameters for (int t = 0; t <= 39; t++) { T[t] = (Convert.ToDouble(t) + 1) / 4.0; THETA[t] = 0.04 + Convert.ToDouble(t) * 0.05 / 100.0; SIGMA[t] = 0.30 + Convert.ToDouble(t) * 0.50 / 100.0; RHO[t] = -0.20 + Convert.ToDouble(t) * 0.35 / 100.0; } // Compute maturity intervals for the Mikhailov-Nogel model double[] tau = new double[40]; tau[0] = T[0]; for (int t = 1; t <= 39; t++) { tau[t] = T[t] - T[t - 1]; } // Obtain the Approximate Piecewise and Closed Form Piecewise put prices using the constructed parameters List <double> MatList = new List <double>(); List <List <double> > MNparam0 = new List <List <double> >(); List <double> tau0 = new List <double>(); List <double> thetaList = new List <double>(); List <double> sigmaList = new List <double>(); List <double> rhoList = new List <double>(); HParam param = new HParam(); BGMPrice BGM = new BGMPrice(); HestonPrice HP = new HestonPrice(); HestonPriceTD HPTD = new HestonPriceTD(); double[] ApproxPW = new double[40]; double[] ClosedPW = new double[40]; double[] Mat, theta, sigma, rho; for (int t = 0; t <= 39; t++) { // Stack the maturities and parameters, with the oldest ones at the top, and the newest ones the bottom MatList.Add(T[t]); Mat = MatList.ToArray(); thetaList.Add(THETA[t]); sigmaList.Add(SIGMA[t]); rhoList.Add(RHO[t]); theta = thetaList.ToArray(); sigma = sigmaList.ToArray(); rho = rhoList.ToArray(); // Calculate the approximate formula with the piecewise constant parameters ApproxPW[t] = BGM.BGMApproxPriceTD(kappa, v0, theta, sigma, rho, settings, K, Mat); if (t == 0) { // First iteration for the closed-form price with the PW constant parameters param.kappa = kappa; param.theta = theta[0]; param.sigma = sigma[0]; param.v0 = v0; param.rho = rho[0]; ClosedPW[t] = HP.HestonPriceGaussLaguerre(param, settings, K, T[0], X, W); } else if (t == 1) { // Second iteration for the closed form price with the PW constant parameters double[] OldMat = new double[1] { 0.25 }; double[,] param0 = new double[1, 5]; param0[0, 0] = kappa; param0[0, 1] = theta[0]; param0[0, 2] = sigma[0]; param0[0, 3] = v0; param0[0, 4] = rho[0]; ClosedPW[t] = HPTD.HestonPriceGaussLaguerreTD(param, param0, tau[t], OldMat, settings, K, X, W); } else if (t > 1) { // Remaing iteration for the closed form price with PW constant parameters // Current parameter values param.theta = theta[t]; param.sigma = sigma[t]; param.rho = rho[t]; // Stack the past maturities, oldest on the bottm, newest on top double[] OldMat = new double[t]; for (int k = 0; k <= t - 1; k++) { OldMat[k] = tau[t - k - 1]; } // Fill in the past parameter values, with the oldest on the bottom and newest on top double[,] param0 = new double[t, 5]; for (int k = 0; k <= t - 1; k++) { param0[k, 0] = kappa; param0[k, 1] = theta[t - k - 1]; param0[k, 2] = sigma[t - k - 1]; param0[k, 3] = v0; param0[k, 4] = rho[t - k - 1]; } ClosedPW[t] = HPTD.HestonPriceGaussLaguerreTD(param, param0, tau[t], OldMat, settings, K, X, W); } } // Select only the puts at the maturities double[] ClosedPW2 = new double[8]; double[] ApproxPW2 = new double[8]; double[] Mats = new double[8] { 0.25, 0.5, 1.0, 2.0, 3.0, 5.0, 7.0, 10.0 }; for (int t = 0; t <= 39; t++) { for (int k = 0; k <= 7; k++) { if (T[t] == Mats[k]) { ClosedPW2[k] = ClosedPW[t]; ApproxPW2[k] = ApproxPW[t]; } } } // The averaged parameter values // Note: v0(4) has been changed double[] v0new = new double[] { 0.04, 0.0397, 0.0328, 0.0464, 0.05624, 0.2858, 0.8492, 0.1454 }; double[] thetanew = new double[] { 0.04, 0.0404, 0.0438, 0.0402, 0.0404, 0.0268, 0.0059, 0.0457 }; double[] sigmanew = new double[] { 0.30, 0.3012, 0.3089, 0.3112, 0.3210, 0.3363, 0.3541, 0.3998 }; double[] rhonew = new double[] { -0.20, -0.1993, -0.1972, -0.1895, -0.1820, -0.1652, -0.1480, -0.1232 }; // The closed form prices using the averaged parameter volumes HParam newparam = new HParam(); double[] ClosedAvg = new double[8]; for (int t = 0; t <= 7; t++) { newparam.kappa = kappa; newparam.theta = thetanew[t]; newparam.sigma = sigmanew[t]; newparam.v0 = v0new[t]; newparam.rho = rhonew[t]; ClosedAvg[t] = HP.HestonPriceGaussLaguerre(newparam, settings, K, Mats[t], X, W); } // Output the results at the maturities Console.WriteLine("----------------------------------------------------------------------------"); Console.WriteLine(" Original Heston | Mihailov-Nogel | BGM Matlab | BGM True Price"); Console.WriteLine("Maturity Closed Form | Piecewise | Piecewise | Piecewise "); Console.WriteLine("----------------------------------------------------------------------------"); for (int t = 0; t <= 7; t++) { Console.WriteLine("{0,5:0.00} {1,14:F2} {2,16:F2} {3,16:F2} {4,16:F2}", Mats[t], ClosedAvg[t], ClosedPW2[t], ApproxPW2[t], TruePW[t]); } Console.WriteLine("----------------------------------------------------------------------------"); }
// Heston Price by Gauss-Legendre Integration public OutputMD HestonPriceGaussLegendreMD(HParam param, OpSet settings, double[] xGLe, double[] wGLe, double[] A, double tol) { int nA = A.Length; int nX = xGLe.Length; double[,] int1 = new double[nA, nX]; double[,] int2 = new double[nA, nX]; double[] sum1 = new double[nA]; double[] sum2 = new double[nA]; // Numerical integration HestonPrice HP = new HestonPrice(); int nj = 0; for (int j = 1; j <= nA - 1; j++) { // Counter for the last point of A used nj += 1; sum1[j] = 0.0; for (int k = 0; k <= nX - 1; k++) { // Lower and upper and limits of the subdomain double a = A[j - 1]; double b = A[j]; double X = (a + b) / 2.0 + (b - a) / 2.0 * xGLe[k]; int1[j, k] = wGLe[k] * HP.HestonProb(X, param, settings, 1) * (b - a) / 2.0; int2[j, k] = wGLe[k] * HP.HestonProb(X, param, settings, 2) * (b - a) / 2.0; sum1[j] += int1[j, k]; sum2[j] += int2[j, k]; } if (Math.Abs(sum1[j]) < tol && Math.Abs(sum2[j]) < tol) { break; } } // Define P1 and P2 double pi = Math.PI; double P1 = 0.5 + 1.0 / pi * sum1.Sum(); double P2 = 0.5 + 1.0 / pi * sum2.Sum(); // The call price double S = settings.S; double K = settings.K; double T = settings.T; double r = settings.r; double q = settings.q; string PutCall = settings.PutCall; double HestonC = S * Math.Exp(-q * T) * P1 - K * Math.Exp(-r * T) * P2; // The put price by put-call parity double HestonP = HestonC - S * Math.Exp(-q * T) + K * Math.Exp(-r * T); // The output structure OutputMD output = new OutputMD(); // Output the option price if (PutCall == "C") { output.Price = HestonC; } else { output.Price = HestonP; } // Output the integration domain; output.lower = A[0]; output.upper = A[nj]; // Output the number of integration points output.Npoints = (nj + 1) * nX; return(output); }
// Simulation of stock price paths and variance paths using Euler or Milstein schemes public double[] EulerMilsteinSim(string scheme, string negvar, HParam param, OpSet settings, double alpha, int NT, int NS) { RandomNumbers RN = new RandomNumbers(); // Heston parameters double kappa = param.kappa; double theta = param.theta; double sigma = param.sigma; double v0 = param.v0; double rho = param.rho; double lambda = param.lambda; // Time increment double dt = settings.T / Convert.ToDouble(NT); // Initialize the variance and stock processes double[,] V = new double[NT, NS]; double[,] S = new double[NT, NS]; // Flags for negative variances int F = 0; // Starting values for the variance and stock processes for (int s = 0; s <= NS - 1; s++) { S[0, s] = settings.S; // Spot price V[0, s] = v0; // Heston v0 initial variance } // Generate the stock and volatility paths double Zv, Zs; for (int s = 0; s <= NS - 1; s++) { for (int t = 1; t <= NT - 1; t++) { // Generate two dependent N(0,1) variables with correlation rho Zv = RN.RandomNorm(); Zs = rho * Zv + Math.Sqrt(1.0 - rho * rho) * RN.RandomNorm(); if (scheme == "Euler") { // Euler discretization for the variance V[t, s] = V[t - 1, s] + kappa * (theta - V[t - 1, s]) * dt + sigma * Math.Sqrt(V[t - 1, s] * dt) * Zv; } else if (scheme == "Milstein") { // Milstein discretization for the variance. V[t, s] = V[t - 1, s] + kappa * (theta - V[t - 1, s]) * dt + sigma * Math.Sqrt(V[t - 1, s] * dt) * Zv + 0.25 * sigma * sigma * dt * (Zv * Zv - 1.0); } else if (scheme == "IM") { // Implicit Milstein for the variance. V[t, s] = (V[t - 1, s] + kappa * theta * dt + sigma * Math.Sqrt(V[t - 1, s] * dt) * Zv + sigma * sigma * dt * (Zv * Zv - 1.0) / 4.0) / (1.0 + kappa * dt); } else if (scheme == "WM") { // Weighted Explicit-Implicit Milstein Scheme V[t, s] = (V[t - 1, s] + kappa * (theta - alpha * V[t - 1, s]) * dt + sigma * Math.Sqrt(V[t - 1, s] * dt) * Zv + sigma * sigma * dt * (Zv * Zv - 1.0) / 4.0) / (1.0 + (1.0 - alpha) * kappa * dt); } // Apply the full truncation or reflection scheme to the variance if (V[t, s] <= 0) { F += 1; if (negvar == "Reflection") // Reflection: take -V { V[t, s] = Math.Abs(V[t, s]); } else if (negvar == "Truncation") { V[t, s] = Math.Max(0, V[t, s]); // Truncation: take max(0,V) } } // Discretize the log stock price S[t, s] = S[t - 1, s] * Math.Exp((settings.r - settings.q - V[t - 1, s] / 2.0) * dt + Math.Sqrt(V[t - 1, s] * dt) * Zs); } } // Return the vector or terminal stock prices double[] output = new double[NS]; for (int s = 0; s <= NS - 1; s++) { output[s] = S[NT - 1, s]; } return(output); }