public VolStruct BuildVolTree(float kappa, float theta, float sigma, float V0, float dt, int NT, float threshold)
        {
            // Creates the Beliaeva-Nawalkha tree for the variance process
            // INPUTS
            //   kappa = Heston parameter for mean reversion speed
            //   theta = Heston parameter for mean reversion level
            //   sigma = Heston parameter for vol of vol
            //   V0     = Heston parameter for initial variance
            //   rf = Risk free rate
            //   dt = time increment
            //   NT = Number of time steps
            //   threshold = threshold for which V can be zero
            // OUTPUTS
            //   X  = the tree for transformed variance
            //   V  = the tree for the original variance
            //   RBound = the row where the V(n,t) variances are zero
            //   M = row where volatility starts at time 1

            // Initial quantities
            float X0 = Convert.ToSingle(2.0 * Math.Sqrt(V0) / sigma);

            float be = Convert.ToSingle(X0 / Math.Sqrt(dt) / Math.Floor(X0 / Math.Sqrt(1.5 * dt)));
            float bc = Convert.ToSingle(X0 / Math.Sqrt(dt) / Math.Floor(X0 / Math.Sqrt(1.5 * dt) + 1.0));
            float b;

            if (Math.Abs(bc - Math.Sqrt(1.5)) < Math.Abs(be - Math.Sqrt(1.5)))
            {
                b = bc;
            }
            else
            {
                b = be;
            }
            if ((b < 1.0) || (b > Math.Sqrt(2.0)))
            {
                Console.WriteLine("Warning b = {0:F5} but should be 1 < b < 1.4142\n", b);
            }

            // Initialize the X-matrix and V-matrix
            int NR = 2 * NT - 1;

            float[,] X = new float[NR, NT];
            float[,] V = new float[NR, NT];
            int M = (NR + 1) / 2 - 1;

            X[M, 0] = X0;

            // Time 1 node for X
            float muX = Convert.ToSingle(1.0 / X0 * (0.5 * kappa * (4.0 * theta / sigma / sigma - X0 * X0) - 0.5));
            float J   = Convert.ToSingle(Math.Floor(muX * Math.Sqrt(dt) / b + 1.0 / b / b));

            X[M - 1, 1] = Convert.ToSingle(X[M, 0] + b * (J + 1.0) * Math.Sqrt(dt));
            X[M + 0, 1] = Convert.ToSingle(X[M, 0] + b * (J + 0.0) * Math.Sqrt(dt));
            X[M + 1, 1] = Convert.ToSingle(X[M, 0] + b * (J - 1.0) * Math.Sqrt(dt));

            // Remaining nodes for X
            for (int t = 1; t <= NT - 2; t++)
            {
                for (int n = M - t; n <= M + t; n++)
                {
                    muX = Convert.ToSingle(1.0 / X[n, t] * (0.5 * kappa * (4.0 * theta / sigma / sigma - X[n, t] * X[n, t]) - 0.5));
                    J   = Convert.ToSingle(Math.Floor(muX * Math.Sqrt(dt) / b + 1 / b / b));
                    // Nodes where X > 0
                    if (X[n, t] > threshold & X[n, t] * X[n, t] * sigma * sigma / 4.0 > threshold)
                    {
                        X[n - 1, t + 1] = Convert.ToSingle(X[n, t] + b * (J + 1.0) * Math.Sqrt(dt));
                        X[n + 0, t + 1] = Convert.ToSingle(X[n, t] + b * (J + 0.0) * Math.Sqrt(dt));
                        X[n + 1, t + 1] = Convert.ToSingle(X[n, t] + b * (J - 1.0) * Math.Sqrt(dt));
                    }
                    // Nodes where X = 0
                    else
                    {
                        X[n - 1, t + 1] = X[n - 1, t];
                        X[n + 0, t + 1] = X[n + 0, t];
                        X[n + 1, t + 1] = X[n + 1, t];
                    }
                }
            }

            // Identify row of the tree where X = 0
            int RBound = 0;

            while (X[RBound, M] >= threshold && RBound < NR - 1)
            {
                RBound += 1;
            }

            //// Build the volatility tree V(n,t)
            for (int t = 0; t <= NT - 1; t++)
            {
                for (int n = 0; n <= NR - 1; n++)
                {
                    V[n, t] = Convert.ToSingle(X[n, t] * X[n, t] * sigma * sigma / 4.0);
                    if (V[n, t] < threshold)
                    {
                        V[n, t] = 0.0f;
                    }
                    if (X[n, t] < threshold)
                    {
                        X[n, t] = 0.0f;
                    }
                }
            }

            // Output the results
            VolStruct output = new VolStruct();

            output.X      = X;
            output.V      = V;
            output.M      = M;
            output.RBound = RBound;
            return(output);
        }
        public BivariateStruct BuildBivariateTree(float S0, string PutCall, float Strike, float T, float rf, int NT, float kappa, float theta, float sigma, float V0, float rho, float threshold)
        {
            float sigmay0 = Convert.ToSingle(Math.Sqrt(1 - rho * rho) * Math.Sqrt(V0));
            float dt      = T / NT;

            // Generate the volatility trees
            VolTree   VT      = new VolTree();
            VolStruct VolTree = VT.BuildVolTree(kappa, theta, sigma, V0, dt, NT, threshold);

            X = VolTree.X;
            V = VolTree.V;

            // Erase the VolTree structured object since it's no longer needed
            VolTree.Dispose();

            int RBound = VolTree.RBound;
            int M      = VolTree.M;

//            if(RBound != 2*NT-1)
//                Console.WriteLine("Vt matrix not triangular");

            // Find the column where the tree changes from triangular to rectangular
            int ColChange = RBound - M + 1;

            // The values of k(t) from Equation (11) and I(t) from (14)
            int[,] k = new int[2 * NT - 1, NT];
            for (int t = 0; t <= NT - 1; t++)
            {
                for (int n = M - t; n <= M + t; n++)
                {
                    if (V[n, t] > 0)
                    {
                        k[n, t] = Convert.ToInt16(Math.Ceiling(Math.Sqrt(V[n, t] / V0)));
                    }
                    else
                    {
                        k[n, t] = 1;
                    }
                }
            }

            // Break up k[n,t] into columns and store the maximum of each column
            int[] maxK = new int[NT];
            int[] kcol = new int[2 * NT - 1];

            for (int t = 0; t <= NT - 1; t++)
            {
                for (int n = 0; n <= 2 * NT - 2; n++)
                {
                    kcol[n] = k[n, t];
                }
                maxK[t] = kcol.Max();
            }

            int[] numY    = new int[NT];
            int[] numV    = new int[NT];
            int[] numRows = new int[NT];
            numY[0]    = 1;
            numY[1]    = 3;
            numV[0]    = 1;
            numV[1]    = 3;
            numRows[0] = 1;
            numRows[1] = 9;
            for (int t = 2; t <= NT - 1; t++)
            {
                numY[t]    = numY[t - 1] + 2 * maxK[t];
                numV[t]    = 2 * (t + 1) - 1;
                numRows[t] = numV[t] * numY[t];
            }
            // Number of rows required for the main Yt(n,t) matrix
            int NR = numRows.Max();

            Console.WriteLine("Yt matrix for log stock price has {0:0,0} rows and {1:0,0} columns = {2:0,0} elements", NR, NT, NR * NT);

            // Find the branch indices
            int[,] Branch = new int[numRows[NT - 2], 9 * (NT - 1)];
            for (int j = 0; j <= 8; j++)
            {
                Branch[0, j] = j;
            }

            for (int t = 1; t <= NT - 2; t++)
            {
                int   nY    = numY[t];
                int   First = maxK[t];
                int   nK    = 2 * t + 1;
                int[] K     = new int[nK];
                for (int j = 0; j <= nK - 1; j++)
                {
                    K[j] = k[M - t + j, t];
                }
                for (int n = 0; n <= numRows[t] - 1; n++)
                {
                    int a = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(n / nY)));
                    int b = n % nY;

                    // Find the middle-to-middle branches
                    Branch[n, 9 * t + 1] = First + a * numY[t + 1] + b;
                    Branch[n, 9 * t + 4] = Branch[n, 9 * t + 1] + numY[t + 1];
                    Branch[n, 9 * t + 7] = Branch[n, 9 * t + 1] + 2 * numY[t + 1];

                    // Find the rest of the branches
                    Branch[n, 9 * t + 0] = Branch[n, 9 * t + 1] - K[a];
                    Branch[n, 9 * t + 2] = Branch[n, 9 * t + 1] + K[a];
                    Branch[n, 9 * t + 3] = Branch[n, 9 * t + 4] - K[a];
                    Branch[n, 9 * t + 5] = Branch[n, 9 * t + 4] + K[a];
                    Branch[n, 9 * t + 6] = Branch[n, 9 * t + 7] - K[a];
                    Branch[n, 9 * t + 8] = Branch[n, 9 * t + 7] + K[a];
                }
            }

            // Adjust the last branch upward
            for (int t = ColChange; t <= NT - 2; t++)
            {
                for (int j = 6; j <= 8; j++)
                {
                    Branch[numRows[t] - 1, 9 * t + j] = Branch[numRows[t] - 2, 9 * t + j];
                }
            }

            // Find the values for the indices and for the probabilities
            float Vt = new float();
            float Xt = new float();
            int   Kt = new int();

            // Log stock tree (Yt), stock price tree (St), and probabilities (Prob)
            float[,] Yt = new float[NR, NT];
            float Y0 = Convert.ToSingle(Math.Log(S0) - rho * V0 / sigma);

            Yt[0, 0]      = Y0;
            float[,] Prob = new float[numRows[NT - 2], 9 * (NT - 1)];
            float X0 = X[M, 0];

            Probabilities PR = new Probabilities();
            float         ht, muy;
            int           I;

            int[] NewBranch = new int[9];
            for (int t = 0; t <= NT - 2; t++)
            {
                int   n = -1;
                int[] J = new int[2 * t + 1];
                for (int j = 0; j <= 2 * t; j++)
                {
                    J[j] = -t + j;
                }
                for (int j = 0; j <= numV[t] - 1; j++)
                {
                    for (int r = 0; r <= numY[t] - 1; r++)
                    {
                        n += 1;
                        Vt = V[M + J[j], t];   // Volatility
                        Xt = X[M + J[j], t];   // Transformed volatility
                        Kt = k[M + J[j], t];   // Node jump k(t)
                        for (int s = 0; s <= 8; s++)
                        {
                            NewBranch[s] = Branch[n, 9 * t + s];
                        }
                        muy = Convert.ToSingle((rho * kappa / sigma - 0.5) * Vt);
                        I   = Convert.ToInt32(Math.Round(muy / Kt / sigmay0 * Math.Sqrt(dt)));
                        if (Yt[n, t] > 0)
                        {
                            for (int s = 0; s <= 8; s++)
                            {
                                if (s == 1 || s == 4 || s == 7)
                                {
                                    // Middle node
                                    Yt[NewBranch[s], t + 1] = Yt[n, t] + (I + 0) * Kt * sigmay0 * Convert.ToSingle(Math.Sqrt(dt));
                                }
                                else if (s == 0 || s == 3 || s == 6)
                                {
                                    // Up node
                                    Yt[NewBranch[s], t + 1] = Yt[n, t] + (I + 1) * Kt * sigmay0 * Convert.ToSingle(Math.Sqrt(dt));
                                }
                                else if (s == 2 || s == 5 || s == 8)
                                {
                                    // Down node
                                    Yt[NewBranch[s], t + 1] = Yt[n, t] + (I - 1) * Kt * sigmay0 * Convert.ToSingle(Math.Sqrt(dt));
                                }
                            }
                        }
                        if (Yt[n, t] > 0.0f)  // Construct the joint probabilities
                        {
                            float[] pv   = PR.probV(Xt, X0, dt, kappa, theta, sigma);
                            float   pvu  = pv[0];
                            float   pvm  = pv[1];
                            float   pvd  = pv[2];
                            float[] py   = PR.probY(Vt, V0, Yt[n, t], dt, rho, sigma, kappa);
                            float   pyu  = py[0];
                            float   pym  = py[1];
                            float   pyd  = py[2];
                            float[] prob = new float[9];
                            prob[0] = pvu * pyu;
                            prob[1] = pvu * pym;
                            prob[2] = pvu * pyd;
                            prob[3] = pvm * pyu;
                            prob[4] = pvm * pym;
                            prob[5] = pvm * pyd;
                            prob[6] = pvd * pyu;
                            prob[7] = pvd * pym;
                            prob[8] = pvd * pyd;
                            for (int s = 0; s <= 8; s++)
                            {
                                Prob[n, 9 * t + s] = prob[s];
                            }
                        }
                    }
                }
            }

            // Find the American and European option prices
            float[,] Euro = new float[NR, NT];
            float[,] Amer = new float[NR, NT];

            // Stock price at maturity
            float[] ST = new float[numRows[NT - 1]];
            ht = Convert.ToSingle((rf - rho * kappa * theta / sigma) * Convert.ToDouble(NT - 1) * dt);
            int[] JJ = new int[2 * (NT - 1) + 1];
            for (int j = 0; j <= 2 * (NT - 1); j++)
            {
                JJ[j] = -(NT - 1) + j;
            }
            int m = -1;

            for (int j = 0; j <= numV[NT - 1] - 1; j++)
            {
                for (int r = 0; r <= numY[NT - 1] - 1; r++)
                {
                    m += 1;
                    Vt = V[M + JJ[j], NT - 1];
                    if (Yt[m, NT - 1] > 0)
                    {
                        ST[m] = Convert.ToSingle(Math.Exp(Yt[m, NT - 1] + rho / sigma * Vt + ht));
                    }
                }
            }

            // Payoff at maturity
            for (int n = 0; n <= NR - 1; n++)
            {
                if (PutCall == "C")
                {
                    Euro[n, NT - 1] = Convert.ToSingle(Math.Max(ST[n] - Strike, 0.0));
                    Amer[n, NT - 1] = Convert.ToSingle(Math.Max(ST[n] - Strike, 0.0));
                }
                else if (PutCall == "P")
                {
                    Euro[n, NT - 1] = Convert.ToSingle(Math.Max(Strike - ST[n], 0.0));
                    Amer[n, NT - 1] = Convert.ToSingle(Math.Max(Strike - ST[n], 0.0));
                }
            }

            float[] P  = new float[9];
            int[]   B  = new int[9];
            float   St = new float();

            for (int t = NT - 2; t >= 0; t--)
            {
                int n = -1;
                ht = Convert.ToSingle((rf - rho * kappa * theta / sigma) * Convert.ToDouble(t) * dt);
                int[] J = new int[2 * t + 1];
                for (int j = 0; j <= 2 * t; j++)
                {
                    J[j] = -t + j;
                }
                for (int j = 0; j <= numV[t] - 1; j++)
                {
                    for (int r = 0; r <= numY[t] - 1; r++)
                    {
                        n += 1;
                        Vt = V[M + J[j], t];
                        if (Yt[n, t] > 0)
                        {
                            St = Convert.ToSingle(Math.Exp(Yt[n, t] + rho / sigma * Vt + ht));  // Stock price
                            for (int s = 0; s <= 8; s++)
                            {
                                P[s] = Prob[n, 9 * t + s];
                                B[s] = Branch[n, 9 * t + s];
                            }
                            Euro[n, t] = P[0] * Euro[B[0], t + 1] + P[1] * Euro[B[1], t + 1] + P[2] * Euro[B[2], t + 1]
                                         + P[3] * Euro[B[3], t + 1] + P[4] * Euro[B[4], t + 1] + P[5] * Euro[B[5], t + 1]
                                         + P[6] * Euro[B[6], t + 1] + P[7] * Euro[B[7], t + 1] + P[8] * Euro[B[8], t + 1];
                            Euro[n, t] = Convert.ToSingle(Math.Exp(-rf * dt) * Euro[n, t]);
                            Amer[n, t] = P[0] * Amer[B[0], t + 1] + P[1] * Amer[B[1], t + 1] + P[2] * Amer[B[2], t + 1]
                                         + P[3] * Amer[B[3], t + 1] + P[4] * Amer[B[4], t + 1] + P[5] * Amer[B[5], t + 1]
                                         + P[6] * Amer[B[6], t + 1] + P[7] * Amer[B[7], t + 1] + P[8] * Amer[B[8], t + 1];
                            Amer[n, t] = Convert.ToSingle(Math.Exp(-rf * dt) * Amer[n, t]);
                            if (PutCall == "C")
                            {
                                Amer[n, t] = Math.Max(St - Strike, Amer[n, t]);
                            }
                            else if (PutCall == "P")
                            {
                                Amer[n, t] = Math.Max(Strike - St, Amer[n, t]);
                            }
                        }
                    }
                }
            }
            float EuroPrice = Euro[0, 0];  // European option
            float AmerPrice = Amer[0, 0];  // American option

            // Output the results
            BivariateStruct output = new BivariateStruct();

            output.Euro = Euro;
            output.Amer = Amer;
            output.Yt   = Yt;
            output.V    = V;
            output.X    = X;
            //            output.St = St;
            output.Prob      = Prob;
            output.EuroPrice = EuroPrice;
            output.AmerPrice = AmerPrice;
            return(output);
        }