public void Build(DateTime originDate, double[][] strikes, DateTime[] expiries, double[][] vols, Func <double, double> forwardCurve) { OriginDate = originDate; Expiries = expiries; ExpiriesDouble = Expiries.Select(t => TimeBasis.CalculateYearFraction(originDate, t)).ToArray(); if (Expiries.Length != strikes.Length) { throw new InvalidOperationException("Expiries and first dimension of Strikes must of same length"); } if (Expiries.Length != vols.Length) { throw new InvalidOperationException("Expiries and first dimension of Vols must of same length"); } Alphas = new double[Expiries.Length]; Betas = new double[Expiries.Length]; Nus = new double[Expiries.Length]; Rhos = new double[Expiries.Length]; for (var i = 0; i < expiries.Length; i++) { var vs = vols[i]; var ks = strikes[i]; var t = ExpiriesDouble[i]; var fwd = forwardCurve(t); Betas[i] = 1.0; Func <double[], double[]> errorFunc = (x => { var err = ks.Select((k, ix) => vs[ix] - SABR.CalcImpVol_Beta1(fwd, k, t, x[0], x[1], x[2])); return(err.ToArray()); }); var n2Sol = new Math.Solvers.GaussNewton { ObjectiveFunction = errorFunc, InitialGuess = new double[] { vs.Average(), 0.1, 0.1 }, Tollerance = 1e-8, JacobianBump = 0.0000001 }; var paramArr = n2Sol.Solve(); Alphas[i] = paramArr[0]; Rhos[i] = paramArr[1]; Nus[i] = paramArr[2]; } var fwds = ExpiriesDouble.Select(x => forwardCurve(x)).ToArray(); _alphaInterp = InterpolatorFactory.GetInterpolator(ExpiriesDouble, Alphas, TimeInterpolatorType); _betaInterp = InterpolatorFactory.GetInterpolator(ExpiriesDouble, Betas, TimeInterpolatorType); _rhoInterp = InterpolatorFactory.GetInterpolator(ExpiriesDouble, Rhos, TimeInterpolatorType); _nuInterp = InterpolatorFactory.GetInterpolator(ExpiriesDouble, Nus, TimeInterpolatorType); _fwdsInterp = InterpolatorFactory.GetInterpolator(ExpiriesDouble, fwds, TimeInterpolatorType); }
public void SABRParamToIVFacts() { //for beta==1 and zero nu and rho we get a flat surface with vol==alpha var vol = SABR.CalcImpVol_Beta1(100, 100, 1, 0.32, 0, 0); Assert.Equal(0.32, vol, 10); vol = SABR.CalcImpVol_Beta1(100, 150, 1, 0.32, 0, 0); Assert.Equal(0.32, vol, 10); vol = SABR.CalcImpVol_Beta1(100, 50, 1, 0.32, 0, 0); Assert.Equal(0.32, vol, 10); //for positive rho should see upside vols > downside var volU = SABR.CalcImpVol_Beta1(100, 150, 1, 0.32, 0.5, 0.16); var volD = SABR.CalcImpVol_Beta1(100, 75, 1, 0.32, 0.5, 0.16); Assert.True(volU > volD); //for negative rho should see upside vols < downside volU = SABR.CalcImpVol_Beta1(100, 150, 1, 0.32, -0.5, 0.16); volD = SABR.CalcImpVol_Beta1(100, 75, 1, 0.32, -0.5, 0.16); Assert.True(volU < volD); //for larger nu should see upside wing vols higher volU = SABR.CalcImpVol_Beta1(100, 150, 1, 0.32, 0, 0.16); volD = SABR.CalcImpVol_Beta1(100, 75, 1, 0.32, 0, 0.16); var wingLow = volU + volD; volU = SABR.CalcImpVol_Beta1(100, 150, 1, 0.32, 0, 0.32); volD = SABR.CalcImpVol_Beta1(100, 75, 1, 0.32, 0, 0.32); var wingHigh = volU + volD; Assert.True(wingHigh > wingLow); //for GB parameterization, should see no sensitivity to rho and nu at ATM var volA = SABR.CalcImpVol_GB(100, 100, 1, 0.32, 0, 0.16); var volB = SABR.CalcImpVol_GB(100, 100, 1, 0.32, -0.5, 0.26); Assert.Equal(volA, volB, 10); //for hagan and berestycki, should see agreement in most cases var volHa = SABR.CalcImpVol_Hagan(100, 100, 1, 0.32, 0.9, 0.5, 0.16); var volBe = SABR.CalcImpVol_Berestycki(100, 100, 1, 0.32, 0.9, 0.5, 0.16); Assert.Equal(volHa, volBe, 5); volHa = SABR.CalcImpVol_Hagan(100, 150, 1, 0.32, 0.5, 0.5, 0.16); volBe = SABR.CalcImpVol_Berestycki(100, 150, 1, 0.32, 0.5, 0.5, 0.16); Assert.Equal(volHa, volBe, 3); }
public double GetVolForAbsoluteStrike(double strike, double maturity, double forward) { var alpha = _alphaInterp.Interpolate(maturity); var beta = _betaInterp.Interpolate(maturity); var nu = _nuInterp.Interpolate(maturity); var rho = _rhoInterp.Interpolate(maturity); var fwd = forward; if (beta >= 1.0) { return(SABR.CalcImpVol_Beta1(fwd, strike, maturity, alpha, rho, nu)); } else { return(SABR.CalcImpVol_Hagan(fwd, strike, maturity, alpha, beta, rho, nu)); } }
public double CalcImpVol_Beta1(double k, SABRParameters currentSABR) => SABR.CalcImpVol_Beta1(_fwd, k, _tExp, currentSABR.Alpha, currentSABR.Rho, currentSABR.Nu);
public static double GetVolForAbsoluteStrike(double strike, double maturity, double forward, SABRParameters sabrParams) => sabrParams.Beta >= 1.0 ? SABR.CalcImpVol_Beta1(forward, strike, maturity, sabrParams.Alpha, sabrParams.Rho, sabrParams.Nu) : SABR.CalcImpVol_Hagan(forward, strike, maturity, sabrParams.Alpha, sabrParams.Beta, sabrParams.Rho, sabrParams.Nu);