public static double Delta(double forward, double knownAverage, double sigma, double K, double tAvgStart, double tExpiry, double riskFree, OptionType callPut) { var tau = Max(0, tAvgStart); var M = 2 * (Exp(sigma * sigma * tExpiry) - Exp(sigma * sigma * tau) * (1 + sigma * sigma * (tExpiry - tau))); M /= Pow(sigma, 4.0) * (tExpiry - tau) * (tExpiry - tau); var sigma_a = Sqrt(Log(M) / tExpiry); K = AsianUtils.AdjustedStrike(K, knownAverage, tExpiry, tAvgStart); if (K <= 0) { if (callPut == OptionType.P) { return(0); } var t2 = tExpiry - tAvgStart; var expAvg = knownAverage * (t2 - tExpiry) / t2 + forward * tExpiry / t2; var df = Exp(-riskFree * tExpiry); return(df * (expAvg - K)); } var delta = BlackFunctions.BlackDelta(forward, K, riskFree, tExpiry, sigma_a, callPut); if (tAvgStart < 0) { delta *= tExpiry / (tExpiry - tAvgStart); } return(delta); }
public void PathsGenerated() { var vol = 0.32; using var engine = new PathEngine(IsCoverageOnly ? 2.IntPow(6) : 2 << 10); engine.AddPathProcess(new Random.MersenneTwister.MersenneTwister64() { UseNormalInverse = true }); var asset2 = new ConstantVolSingleAsset ( startDate: DateTime.Now.Date, expiry: DateTime.Now.Date.AddYears(1), vol: vol, spot: 500, drift: 0.00, numberOfSteps: IsCoverageOnly ? 1 : 365, name: "TestAsset2" ); engine.AddPathProcess(asset2); var payoff = new EuropeanPut("TestAsset2", 500, DateTime.Now.Date.AddYears(1)); engine.AddPathProcess(payoff); engine.SetupFeatures(); engine.RunProcess(); var pv = payoff.AverageResult; var blackPv = BlackFunctions.BlackPV(500, 500, 0, 1, vol, OptionType.P); if (!IsCoverageOnly) { Assert.Equal(blackPv, pv, 0); } }
private double GetDeltaStrikeForAbs(double fwd, double strike, double maturity) { var cp = strike < 0 ? OptionType.Put : OptionType.Call; Func <double, double> testFunc = (deltaK => { var interpForStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, ExpiriesDouble.Select(e => GetVolForDeltaStrike(deltaK, e, fwd)).ToArray(), TimeInterpolatorType); var vol2 = interpForStrike.Interpolate(maturity); var absK = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, deltaK, 0, maturity, vol2); return(absK - strike); }); var solvedStrike = -Math.Solvers.Brent.BrentsMethodSolve(testFunc, -0.99999999999, -0.00000000001, 1e-8); if (solvedStrike == 0.00000000001 || solvedStrike == 0.99999999999) //out of bounds { var upperK = testFunc(-0.00000000001); var lowerK = testFunc(-0.99999999999); if (Abs(upperK - fwd) < Abs(lowerK - fwd)) { solvedStrike = 0.00000000001; } else { solvedStrike = 0.99999999999; } } return(solvedStrike); }
public void SABRSurfaceRRBF() { //flat surface var origin = new DateTime(2017, 02, 07); var expiry = origin.AddYears(1); var t = (expiry - origin).TotalDays / 365.0; var fwd = 1.5; var vol = 0.32; var rr = new[] { new[] { 0.02, 0.03 } }; var bf = new[] { new[] { 0.005, 0.007 } }; Func <double, double> fwdCurve = (tt => { return(fwd); }); var surface = new SabrVolSurface(origin, new[] { vol }, new[] { expiry }, new[] { 0.25, 0.1 }, rr, bf, new[] { 100.0 }, WingQuoteType.Arithmatic, AtmVolType.ZeroDeltaStraddle, Math.Interpolation.Interpolator1DType.Linear); var gSurface = new RiskyFlySurface(origin, new[] { vol }, new[] { expiry }, new[] { 0.25, 0.1 }, rr, bf, new[] { 100.0 }, WingQuoteType.Arithmatic, AtmVolType.ZeroDeltaStraddle, Math.Interpolation.Interpolator1DType.Linear, Math.Interpolation.Interpolator1DType.Linear); var atmK = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, 0.5, 0.0, t, vol); Assert.Equal(vol, surface.GetVolForAbsoluteStrike(atmK, expiry, fwd), 2); var v25c = gSurface.GetVolForDeltaStrike(0.75, expiry, fwd); var v25p = gSurface.GetVolForDeltaStrike(0.25, expiry, fwd); var k25c = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, 0.25, 0.0, t, v25c); var k25p = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, -0.25, 0.0, t, v25p); var t25c = surface.GetVolForAbsoluteStrike(k25c, expiry, fwd); var t25p = surface.GetVolForAbsoluteStrike(k25p, expiry, fwd); Assert.Equal(rr[0][0], t25c - t25p, 2); }
public static IInterpolator1D GenerateCDF2(this IVolSurface surface, int numSamples, DateTime expiry, double fwd, bool returnInverse = false, double strikeScale = 1.0, bool logStrikes = false) { var premInterp = GeneratePremiumInterpolator(surface, numSamples, expiry, fwd, OptionType.P); var t = surface.OriginDate.CalculateYearFraction(expiry, DayCountBasis.Act365F); var x = new double[numSamples]; var y = new double[numSamples]; var deltaKLow = 0.0000000001; var deltaKHi = 0.9999999999; var kStepD = (deltaKHi - deltaKLow) / numSamples; for (var i = 0; i < x.Length; i++) { var deltaKNew = -deltaKLow - i * kStepD; var newStrikeVol = surface.GetVolForDeltaStrike(-deltaKNew, t, fwd); var k = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, deltaKNew, 0, t, newStrikeVol); var digital = premInterp.FirstDerivative(k); y[i] = digital; x[i] = k * strikeScale; if (logStrikes) { x[i] = Log(x[i]); } } return(returnInverse ? InterpolatorFactory.GetInterpolator(y, x, Interpolator1DType.MonotoneCubicSpline) : InterpolatorFactory.GetInterpolator(x, y, Interpolator1DType.MonotoneCubicSpline)); }
public void BarrierFacts() { var t = 1.0; var k = 100; var b = 0.0; var f = 100; var vol = 0.32; var rf = 0.05; var cp = OptionType.C; //zero barrier knock-in down is worthless var PV = BlackFunctions.BarrierOptionPV(f, k, rf, t, vol, cp, b, BarrierType.KI, BarrierSide.Down); Assert.Equal(0.0, PV, 10); //zero barrier knock-in up is worth vanilla var vanillaPV = BlackFunctions.BlackPV(f, k, rf, t, vol, cp); PV = BlackFunctions.BarrierOptionPV(f, k, rf, t, vol, cp, b, BarrierType.KI, BarrierSide.Up); Assert.Equal(vanillaPV, PV, 10); //ki forward is worth same as fwd b = 100; k = 110; var PVc = BlackFunctions.BarrierOptionPV(f, k, rf, t, vol, OptionType.C, b, BarrierType.KI, BarrierSide.Down); var PVp = BlackFunctions.BarrierOptionPV(f, k, rf, t, vol, OptionType.P, b, BarrierType.KI, BarrierSide.Down); var df = Exp(-rf * t); var fwdPV = (f - k) * df; Assert.Equal(fwdPV, PVc - PVp, 10); }
public void VegaFacts() { var t = 1.0; var k = 100; var f = 100; var vol = 0.32; var rf = 0.05; var cp = OptionType.P; //vega closely matches numerical estimate var PV1 = BlackFunctions.BlackPV(f, k, rf, t, vol - 0.00005, cp); var PV2 = BlackFunctions.BlackPV(f, k, rf, t, vol + 0.00005, cp); var vegaEst = (PV2 - PV1) / 0.0001 * 0.01; var vega = BlackFunctions.BlackVega(f, k, rf, t, vol); Assert.Equal(vegaEst, vega, 6); //all else the same, more time==more vega var vegaNear = BlackFunctions.BlackVega(f, k, rf, t, vol); var vegaFar = BlackFunctions.BlackVega(f, k, rf, t * 2, vol); Assert.True(vegaFar > vegaNear); //cases for zero vega vega = BlackFunctions.BlackVega(f, 0, rf, t, vol); Assert.Equal(0, vega, 8); vega = BlackFunctions.BlackVega(f, 1e6, rf, t, vol); Assert.Equal(0, vega, 8); }
public void ThetaFacts() { var t = 1.0; var k = 100; var f = 100; var vol = 0.32; var rf = 0.05; var cp = OptionType.P; //theta closely matches numerical estimate var bumpT = 1e-10; var PV1 = BlackFunctions.BlackPV(f, k, rf, t, vol, cp); var PV2 = BlackFunctions.BlackPV(f, k, rf, t - bumpT, vol, cp); var thetaEst = (PV2 - PV1) / bumpT; var theta = BlackFunctions.BlackTheta(f, k, rf, t, vol, cp); Assert.Equal(thetaEst, theta, 3); //theta closely matches numerical estimate cp = OptionType.C; PV1 = BlackFunctions.BlackPV(f, k, rf, t, vol, cp); PV2 = BlackFunctions.BlackPV(f, k, rf, t - bumpT, vol, cp); thetaEst = (PV2 - PV1) / bumpT; theta = BlackFunctions.BlackTheta(f, k, rf, t, vol, cp); Assert.Equal(thetaEst, theta, 3); }
public double GetVolForAbsoluteStrike(double strike, double maturity, double forward) { if (StrikeType == StrikeType.Absolute) { var interpForStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(strike)).ToArray(), TimeInterpolatorType); return(interpForStrike.Interpolate(maturity)); } else { var fwd = forward; var cp = strike < 0 ? OptionType.Put : OptionType.Call; Func <double, double> testFunc = (deltaK => { var interpForStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(deltaK)).ToArray(), TimeInterpolatorType); var vol = interpForStrike.Interpolate(maturity); var absK = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, deltaK, 0, maturity, vol); return(absK - strike); }); var solvedStrike = Qwack.Math.Solvers.Brent.BrentsMethodSolve(testFunc, 0.000000001, 0.999999999, 1e-8); var interpForSolvedStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(solvedStrike)).ToArray(), TimeInterpolatorType); return(interpForSolvedStrike.Interpolate(maturity)); } }
public double GetVolForDeltaStrike(double deltaStrike, double maturity, double forward) { if (StrikeType == StrikeType.ForwardDelta) { var interpForStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(deltaStrike)).ToArray(), TimeInterpolatorType); return(interpForStrike.Interpolate(maturity)); } else { var fwd = forward; var cp = deltaStrike < 0 ? OptionType.Put : OptionType.Call; Func <double, double> testFunc = (absK => { var interpForStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(absK)).ToArray(), TimeInterpolatorType); var vol = interpForStrike.Interpolate(maturity); var deltaK = BlackFunctions.BlackDelta(fwd, absK, 0, maturity, vol, cp); return(deltaK - System.Math.Abs(deltaStrike)); }); var solvedStrike = Qwack.Math.Solvers.Brent.BrentsMethodSolve(testFunc, 0.000000001, 10 * fwd, 1e-8); var interpForSolvedStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(solvedStrike)).ToArray(), TimeInterpolatorType); return(interpForSolvedStrike.Interpolate(maturity)); } }
public double GetVolForAbsoluteStrike(double strike, double maturity, double forward) { var key = $"{strike:f6}~{maturity:f3}~{forward:f6}"; if (_allowCaching && _absVolCache.TryGetValue(key, out var vol)) { return(vol); } if (StrikeType == StrikeType.Absolute) { var interpForStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(strike)).ToArray(), TimeInterpolatorType); vol = interpForStrike.Interpolate(maturity); } else { var fwd = forward; var cp = strike < 0 ? OptionType.Put : OptionType.Call; Func <double, double> testFunc = (deltaK => { var dkModified = FlatDeltaSmileInExtreme ? Min(1.0 - FlatDeltaPoint, Max(deltaK, FlatDeltaPoint)) : deltaK; var interpForStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(-dkModified)).ToArray(), TimeInterpolatorType); var vol2 = interpForStrike.Interpolate(maturity); var absK = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, deltaK, 0, maturity, vol2); return(absK - strike); }); var hiK = FlatDeltaSmileInExtreme ? 1.0 - FlatDeltaPoint : 0.999999999; var loK = FlatDeltaSmileInExtreme ? FlatDeltaPoint : 0.000000001; var solvedStrike = -Math.Solvers.Brent.BrentsMethodSolve(testFunc, -hiK, -loK, 1e-12); if (solvedStrike == loK || solvedStrike == hiK) //out of bounds { var upperK = testFunc(-loK); var lowerK = testFunc(-hiK); if (Abs(upperK - fwd) < Abs(lowerK - fwd)) { solvedStrike = loK; } else { solvedStrike = hiK; } } var interpForSolvedStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(solvedStrike)).ToArray(), TimeInterpolatorType); vol = interpForSolvedStrike.Interpolate(maturity); } if (_allowCaching) { _absVolCache[key] = vol; } return(vol); }
public static IInterpolator1D GenerateCompositeSmileBasic(this IVolSurface surface, IVolSurface fxSurface, int numSamples, DateTime expiry, double fwdAsset, double fwdFx, double correlation, bool deltaStrikeOutput = false) { var deltaKa = fwdAsset * 0.00001; var deltaKfx = fwdFx * 0.00001; var t = surface.OriginDate.CalculateYearFraction(expiry, DayCountBasis.Act365F); var atmFx = fxSurface.GetVolForAbsoluteStrike(fwdFx, t, fwdFx); var atmA = surface.GetVolForAbsoluteStrike(fwdAsset, t, fwdAsset); var compoFwd = fwdAsset * fwdFx; var atmCompo = Sqrt(atmFx * atmFx + atmA * atmA + 2.0 * correlation * atmA * atmFx); var lowK = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(compoFwd, -0.0001, 0, t, atmCompo); var hiK = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(compoFwd, -0.9999, 0, t, atmCompo); var lowKA = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwdAsset, -0.0001, 0, t, atmA); var hiKA = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwdAsset, -0.9999, 0, t, atmA); var x = new double[numSamples]; var y = new double[numSamples]; var k = lowK; var kStep = (hiK - lowK) / numSamples; var kStepA = (hiKA - lowKA) / numSamples; var kStepFx = (hiK / hiKA - lowK / lowKA) / numSamples; for (var i = 0; i < x.Length; i++) { x[i] = k; var kA = lowKA; var totalP = 0.0; for (var j = 0; j < numSamples; j++) { var kFx = k / kA; var volFx = fxSurface.GetVolForAbsoluteStrike(kFx, t, fwdFx); var volA = surface.GetVolForAbsoluteStrike(kA, t, fwdAsset); var volC = Sqrt(volFx * volFx + volA * volA + 2.0 * correlation * volA * volFx); var fxBucketLow = kFx - deltaKfx / 2.0; var fxBucketHi = kFx + deltaKfx / 2.0; var assetBucketLow = kA - deltaKa / 2.0; var assetBucketHi = kA + deltaKa / 2.0; var weight = 1.0; y[i] += weight * volC; totalP += weight; kA += kStepA; } y[i] /= totalP; k += kStep; } if (deltaStrikeOutput) { x = x.Select((q, ix) => - BlackFunctions.BlackDelta(compoFwd, q, 0.0, t, y[ix], OptionType.P)).ToArray(); } return(InterpolatorFactory.GetInterpolator(x, y, Interpolator1DType.LinearFlatExtrap)); }
public void LVMC_PathsGenerated() { var origin = DateTime.Now.Date; using var engine = new PathEngine(2.IntPow(IsCoverageOnly ? 6 : 12)) { Parallelize = false }; engine.AddPathProcess(new Random.MersenneTwister.MersenneTwister64() { UseNormalInverse = true, UseAnthithetic = true }); var tenorsStr = new[] { "1m", "2m", "3m", "6m" }; var tenors = tenorsStr.Select(x => new Frequency(x)); var expiries = tenors.Select(t => origin.AddPeriod(RollType.F, new Calendar(), t)).ToArray(); var deltaKs = new[] { 0.1, 0.25, 0.5, 0.75, 0.9 }; var smileVols = new[] { 0.32, 0.3, 0.29, 0.3, 0.32 }; var vols = Enumerable.Repeat(smileVols, expiries.Length).ToArray(); var tExp = (origin.AddMonths(6) - origin).TotalDays / 365.0; var volSurface = new GridVolSurface(origin, deltaKs, expiries, vols, StrikeType.ForwardDelta, Interpolator1DType.GaussianKernel, Interpolator1DType.LinearInVariance, DayCountBasis.Act365F); var fwdCurve = new Func <double, double>(t => { return(900 + 100 * t / tExp); }); var asset = new LVSingleAsset ( startDate: origin, expiryDate: origin.AddMonths(6), volSurface: volSurface, forwardCurve: fwdCurve, nTimeSteps: IsCoverageOnly ? 3 : 100, name: "TestAsset" ); engine.AddPathProcess(asset); var payoff = new EuropeanPut("TestAsset", 900, origin.AddMonths(6)); var payoff2 = new EuropeanCall("TestAsset", 0, origin.AddMonths(6)); engine.AddPathProcess(payoff); engine.AddPathProcess(payoff2); engine.SetupFeatures(); engine.RunProcess(); var pv = payoff.AverageResult; var blackVol = volSurface.GetVolForAbsoluteStrike(900, origin.AddMonths(6), fwdCurve(tExp)); var blackPv = BlackFunctions.BlackPV(fwdCurve(tExp), 900, 0, tExp, blackVol, OptionType.P); if (!IsCoverageOnly) { Assert.True(System.Math.Abs(blackPv / pv - 1.0) < 0.02); var fwd = payoff2.AverageResult; Assert.True(System.Math.Abs(fwdCurve(tExp) / fwd - 1.0) < 0.005); } }
public void AbsKFromDelta_Facts() { var vol = 0.32; var fwd = 100.0; var k = 115.0; var t = 1.5; var deltaK = Qwack.Options.BlackFunctions.BlackDelta(fwd, k, 0, t, vol, OptionType.C); Assert.Equal(k, (double)BlackFunctions.AbsoluteStrikeFromDelta(t, deltaK, fwd, vol, 0), 12); }
public void PVFacts() { var t = 1.0; var t2 = 2.0; var k = 0; var f = 100; var vol = 0.32; var rf = 0.05; var cp = OptionType.P; //expired is worthless var PV = TurnbullWakeman.PV(f, 0, vol, 0, t, 0, rf, OptionType.P); Assert.Equal(0, PV, 10); PV = TurnbullWakeman.PV(f, 0, vol, 0, t, 0, rf, OptionType.C); Assert.Equal(0, PV, 10); //zero strike put is worthless PV = TurnbullWakeman.PV(f, 0, vol, 0, t, t2, rf, cp); Assert.Equal(0, PV, 10); PV = TurnbullWakeman.PV(f, 50, vol, 0, -0.1, t2, rf, cp); Assert.Equal(0, PV, 10); //zero strike call is worth discounted fwd cp = OptionType.C; PV = TurnbullWakeman.PV(f, 0, vol, 0, t, t2, rf, cp); Assert.Equal(System.Math.Exp(-rf * t2) * f, PV, 2); //OTM option with zero vol is worthless vol = 0.0; k = f + 1; PV = TurnbullWakeman.PV(f, k, vol, 0, t, t2, rf, cp); Assert.Equal(0, PV, 10); //put-call parity at f==k k = f; vol = 0.32; var PVcall = TurnbullWakeman.PV(f, 0, vol, k, t, t2, rf, OptionType.C); var PVput = TurnbullWakeman.PV(f, 0, vol, k, t, t2, rf, OptionType.P); Assert.Equal(PVcall, PVput, 2); //independent fwds version var valDate = new DateTime(2019, 10, 24); var fixingStartDate = new DateTime(2019, 10, 01); var fixingEndDate = new DateTime(2019, 10, 25); var fixingDates = DateExtensions.BusinessDaysInPeriod(fixingStartDate, fixingEndDate, TestProviderHelper.CalendarProvider.Collection["NYC"]).ToArray(); var fwds = fixingDates.Select(x => 100.0).ToArray(); var sigmas = fixingDates.Select(x => 0.32).ToArray(); PV = TurnbullWakeman.PV(fwds, fixingDates, valDate, fixingEndDate, sigmas, 1, 0.0, OptionType.C, true); var blackPV = BlackFunctions.BlackPV(100.0, 1, 0.0, 1 / 365.0, 0.32, OptionType.C); Assert.Equal(blackPV, PV, 4); }
public void LVMC_PathsGenerated() { var origin = DateTime.Now.Date; var engine = new PathEngine(2.IntPow(17)); //engine.AddPathProcess(new Random.MersenneTwister.MersenneTwister64() //{ // UseNormalInverse = true, // UseAnthithetic = false //}); engine.AddPathProcess(new Random.Sobol.SobolShiftedPathGenerator(new Random.Sobol.SobolDirectionNumbers(s_directionNumbers), 0) { UseNormalInverse = true }); var tenorsStr = new[] { "1m", "2m", "3m", "6m", "9m", "1y" }; var tenors = tenorsStr.Select(x => new Frequency(x)); var expiries = tenors.Select(t => origin.AddPeriod(RollType.F, new Calendar(), t)).ToArray(); var deltaKs = new[] { -0.1, -0.25, -0.5, -0.75, -0.9 }; var smileVols = new[] { 0.32, 0.3, 0.29, 0.3, 0.32 }; var vols = Enumerable.Repeat(smileVols, expiries.Length).ToArray(); var volSurface = new GridVolSurface(origin, deltaKs, expiries, vols, Core.Basic.StrikeType.ForwardDelta, Interpolator1DType.LinearFlatExtrap, Interpolator1DType.LinearInVariance, DayCountBasis.Act365F); var fwdCurve = new Func <double, double>(t => { return(900 + 100 * t); }); var asset = new LVSingleAsset ( startDate: origin, expiryDate: origin.AddYears(1), volSurface: volSurface, forwardCurve: fwdCurve, nTimeSteps: 365, name: "TestAsset" ); engine.AddPathProcess(asset); var payoff = new EuropeanPut("TestAsset", 900, origin.AddYears(1)); var payoff2 = new EuropeanCall("TestAsset", 0, origin.AddYears(1)); engine.AddPathProcess(payoff); engine.AddPathProcess(payoff2); engine.SetupFeatures(); engine.RunProcess(); var pv = payoff.AverageResult; var blackVol = volSurface.GetVolForAbsoluteStrike(900, origin.AddYears(1), fwdCurve(1.0)); var blackPv = BlackFunctions.BlackPV(1000, 900, 0, 1, blackVol, OptionType.P); Assert.Equal(blackPv, pv, 0); var fwd = payoff2.AverageResult; Assert.True(System.Math.Abs(fwdCurve(1) / fwd - 1.0) < 0.001); //var output = new OutputPathsToImage(engine,2000,1000); }
public static double InverseCDFex(this IVolSurface surface, double t, double fwd, double p) { var deltaK = fwd * 1e-10; var lowGuess = fwd / 2; var highGuess = fwd * 2; var targetFunc = new Func <double, double>(k => { var volM = surface.GetVolForAbsoluteStrike(k - deltaK, t, fwd); var volP = surface.GetVolForAbsoluteStrike(k + deltaK, t, fwd); var pvM = BlackFunctions.BlackPV(fwd, k - deltaK, 0.0, t, volM, OptionType.P); var pvP = BlackFunctions.BlackPV(fwd, k + deltaK, 0.0, t, volP, OptionType.P); var digi = (pvP - pvM) / (2 * deltaK); //var digi = BlackFunctions.BlackDigitalPV(fwd, k, 0, t, surface.GetVolForAbsoluteStrike(k, t, fwd), OptionType.P); return(p - digi); }); var breakCount = 0; while (targetFunc(lowGuess) < 0) { // highGuess = lowGuess*2.0; lowGuess /= 2.0; breakCount++; if (breakCount == 10) { return(lowGuess); } } breakCount = 0; while (targetFunc(highGuess) > 0) { //lowGuess = highGuess/2.0; highGuess *= 2.0; breakCount++; if (breakCount == 10) { return(highGuess); } } var b = Math.Solvers.Brent.BrentsMethodSolve(targetFunc, lowGuess, highGuess, 1e-8); //var b = Math.Solvers.Newton1D.MethodSolve2(targetFunc, fwd, 1e-6, 1000, fwd * 0.00001); if (double.IsInfinity(b) || double.IsNaN(b)) { throw new Exception("Invalid strike found"); } //if (b==lowGuess || b==highGuess) // throw new Exception("Strike outside of bounds"); return(b); }
public static IInterpolator1D GenerateCDF(this IVolSurface surface, int numSamples, DateTime expiry, double fwd, bool returnInverse = false) { var deltaKLow = 0.0000001; var deltaKHi = 0.9999999; var kStepD = (deltaKHi - deltaKLow) / (numSamples + 3); var deltaKBump = deltaKLow / 10; var t = surface.OriginDate.CalculateYearFraction(expiry, DayCountBasis.Act365F); var x = new double[numSamples + 2]; var y = new double[numSamples + 2]; for (var i = 0; i < x.Length; i++) { var deltaKNew = deltaKLow + i * kStepD; var mStrike = deltaKNew - deltaKBump / 2; var pStrike = deltaKNew + deltaKBump / 2; var mStrikeVol = surface.GetVolForDeltaStrike(mStrike, t, fwd); var mk = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, -mStrike, 0, t, mStrikeVol); var pStrikeVol = surface.GetVolForDeltaStrike(pStrike, t, fwd); var pk = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, -pStrike, 0, t, pStrikeVol); if (i == 0) { x[0] = mk / 2.0; y[0] = 0; continue; } if (i == x.Length - 1) { x[i] = pk * 2; y[i] = 1; continue; } var dkAbs = (pk - mk); var pPut = BlackFunctions.BlackPV(fwd, pk, 0, t, pStrikeVol, OptionType.P); var mPut = BlackFunctions.BlackPV(fwd, mk, 0, t, mStrikeVol, OptionType.P); var digital = (pPut - mPut) / dkAbs; y[i] = digital; x[i] = (mk + pk) / 2.0; } return(returnInverse ? InterpolatorFactory.GetInterpolator(y, x, Interpolator1DType.LinearFlatExtrap) : InterpolatorFactory.GetInterpolator(x, y, Interpolator1DType.LinearFlatExtrap)); }
public void GridVolSurfaceAbsolute() { //flat surface var origin = new DateTime(2017, 02, 07); var strikes = new double[] { 1, 2 }; var maturities = new DateTime[] { new DateTime(2017, 04, 06), new DateTime(2017, 06, 07) }; var vols = new double[][] { new double[] { 0.32, 0.32 }, new double[] { 0.32, 0.32 } }; var surface = new Qwack.Options.VolSurfaces.GridVolSurface( origin, strikes, maturities, vols, Core.Basic.StrikeType.Absolute, Math.Interpolation.Interpolator1DType.Linear, Math.Interpolation.Interpolator1DType.Linear, Dates.DayCountBasis.Act_365F); var fwd = 1.5; Func <double, double> fwdCurve = (t => { return(fwd); }); Assert.Equal(vols[0][0], surface.GetVolForAbsoluteStrike(999, origin.AddDays(33), fwd), 12); Assert.Equal(vols[0][0], surface.GetVolForDeltaStrike(-0.3, origin.AddDays(303), fwd), 12); Assert.Equal(vols[0][0], surface.GetVolForAbsoluteStrike(4, 0.777, fwd), 12); Assert.Equal(vols[0][0], surface.GetVolForDeltaStrike(0.9, 0.123, fwd), 12); //with some shape vols = new double[][] { new double[] { 0.16, 0.32 }, new double[] { 0.32, 0.64 } }; surface = new Qwack.Options.VolSurfaces.GridVolSurface( origin, strikes, maturities, vols, Core.Basic.StrikeType.Absolute, Math.Interpolation.Interpolator1DType.Linear, Math.Interpolation.Interpolator1DType.Linear, Dates.DayCountBasis.Act_365F); Assert.Equal(vols[0].Average(), surface.GetVolForAbsoluteStrike(1.5, maturities[0], fwd), 12); var midPoint = maturities[0].AddDays(((maturities[1] - maturities[0]).TotalDays / 2.0)); Assert.Equal(vols.Select(x => x.Average()).Average(), surface.GetVolForAbsoluteStrike(1.5, midPoint, fwd), 12); //test delta-space function var vol = surface.GetVolForAbsoluteStrike(1.75, maturities[0].AddDays(15), fwd); var deltaK = BlackFunctions.BlackDelta(1.5, 1.75, 0, surface.ExpiriesDouble[0] + 15.0 / 365.0, vol, OptionType.C); var volForDeltaK = surface.GetVolForDeltaStrike(deltaK, maturities[0].AddDays(15), fwd); Assert.Equal(vol, volForDeltaK, 8); }
public void PVFacts() { var evalDate = DateTime.Today; var avgStart = evalDate.AddDays(365); var avgEnd = avgStart.AddDays(32); var t = (avgEnd - evalDate).TotalDays / 365.0; var fixCal = new Calendar(); var k = 0; var f = 100; var vol = 0.32; var rf = 0.05; //zero strike put is worthless var PV = LME_Clewlow.PV(f, 0, vol, 0.0, evalDate, avgStart, avgEnd, rf, OptionType.P, fixCal); Assert.Equal(0, PV, 10); //zero strike call is worth discounted fwd PV = LME_Clewlow.PV(f, 0, vol, 0.0, evalDate, avgStart, avgEnd, rf, OptionType.C, fixCal); Assert.Equal(System.Math.Exp(-rf * t) * f, PV, 2); //OTM option with zero vol is worthless vol = 0.0; k = f + 1; PV = LME_Clewlow.PV(f, 0, vol, 0.0, evalDate, avgStart, avgEnd, rf, OptionType.C, fixCal); Assert.Equal(0, PV, 10); //put-call parity at f==k k = f; vol = 0.32; var PVcall = LME_Clewlow.PV(f, 0, vol, k, evalDate, avgStart, avgEnd, rf, OptionType.C, fixCal); var PVput = LME_Clewlow.PV(f, 0, vol, k, evalDate, avgStart, avgEnd, rf, OptionType.P, fixCal); Assert.Equal(PVcall, PVput, 2); //bullet defaults to european avgStart = avgEnd; PV = LME_Clewlow.PV(f, 0.0, vol, k, evalDate, avgStart, avgEnd, rf, OptionType.P, fixCal); var blackPV = BlackFunctions.BlackPV(f, k, rf, t, vol, OptionType.P); Assert.Equal(blackPV, PV, 10); //on expiry its intrinsic evalDate = avgEnd; PV = LME_Clewlow.PV(f, f, vol, f + 10, evalDate, avgStart, avgEnd, rf, OptionType.P, fixCal); Assert.Equal(10.0, PV, 10); PV = LME_Clewlow.PV(f, f, vol, f - 10, evalDate, avgStart, avgEnd, rf, OptionType.C, fixCal); Assert.Equal(10.0, PV, 10); }
public static IInterpolator1D GeneratePDF(this IVolSurface surface, int numSamples, DateTime expiry, double fwd) { var deltaK = fwd * 0.0001; var t = surface.OriginDate.CalculateYearFraction(expiry, DayCountBasis.Act365F); var lowStrikeVol = surface.GetVolForDeltaStrike(0.0001, t, fwd); var lowStrike = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, -0.0001, 0, t, lowStrikeVol); var hiStrikeVol = surface.GetVolForDeltaStrike(0.9999, t, fwd); var hiStrike = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, -0.9999, 0, t, hiStrikeVol); var x = new double[numSamples + 2]; var y = new double[numSamples + 2]; var k = lowStrike; var kStep = (hiStrike - lowStrike) / numSamples; for (var i = 0; i < x.Length; i++) { if (i == 0) { x[0] = lowStrike / 2.0; y[0] = 0; continue; } if (i == x.Length - 1) { x[i] = k * 2; y[i] = 0; continue; } var volLow = surface.GetVolForAbsoluteStrike(k - deltaK, t, fwd); var callLow = BlackFunctions.BlackPV(fwd, k - deltaK, 0, t, volLow, OptionType.C); var volMid = surface.GetVolForAbsoluteStrike(k, t, fwd); var callMid = BlackFunctions.BlackPV(fwd, k, 0, t, volMid, OptionType.C); var volHi = surface.GetVolForAbsoluteStrike(k + deltaK, t, fwd); var callHi = BlackFunctions.BlackPV(fwd, k + deltaK, 0, t, volHi, OptionType.C); var digitalLo = (callLow - callMid) / deltaK; var digitalHi = (callMid - callHi) / deltaK; y[i] = (digitalLo - digitalHi) / deltaK; x[i] = k; k += kStep; } var firstPass = InterpolatorFactory.GetInterpolator(x, y, Interpolator1DType.LinearFlatExtrap); var totalY = ((IIntegrableInterpolator)firstPass).DefiniteIntegral(x.First(), x.Last()); y = y.Select(v => v / totalY).ToArray(); return(InterpolatorFactory.GetInterpolator(x, y, Interpolator1DType.LinearFlatExtrap)); }
public void ImpliedVolFacts() { var t = 1.0; var k = 120; var f = 100; var vol = 0.32; var rf = 0.05; var cp = OptionType.P; var PV = BlackFunctions.BlackPV(f, k, rf, t, vol, cp); var impliedVol = BlackFunctions.BlackImpliedVol(f, k, rf, t, PV, cp); Assert.Equal(vol, impliedVol, 10); }
public double GetVolForDeltaStrike(double deltaStrike, double maturity, double forward) { var cp = deltaStrike < 0 ? OptionType.Put : OptionType.Call; Func <double, double> testFunc = (absK => { var volTest = GetVolForAbsoluteStrike(absK, maturity, forward); var deltaK = BlackFunctions.BlackDelta(forward, absK, 0, maturity, volTest, cp); return(deltaK - System.Math.Abs(deltaStrike)); }); var solvedStrike = Math.Solvers.Brent.BrentsMethodSolve(testFunc, 0.000000001, 10 * forward, 1e-8); return(GetVolForAbsoluteStrike(solvedStrike, maturity, forward)); }
public void DeltaGammaFacts() { var t = 1.0; var k = 100; var f = 100; var vol = 0.32; var rf = 0.05; var cp = OptionType.P; //delta closely matches numerical estimate var PV1 = BlackFunctions.BlackPV(f + 0.000005, k, rf, t, vol, cp); var PV2 = BlackFunctions.BlackPV(f - 0.000005, k, rf, t, vol, cp); var deltaEst = (PV1 - PV2) / 0.00001; var delta = BlackFunctions.BlackDelta(f, k, rf, t, vol, cp); Assert.Equal(deltaEst, delta, 6); //all else the same, more time for OTM option == more delta k = 150; var deltaNear = BlackFunctions.BlackDelta(f, k, rf, t, vol, cp); var deltaFar = BlackFunctions.BlackDelta(f, k, rf, t * 2, vol, cp); Assert.True(deltaFar > deltaNear); //put-call parity var deltaCall = BlackFunctions.BlackDelta(f, k, rf, t, vol, OptionType.C); var deltaPut = BlackFunctions.BlackDelta(f, k, rf, t, vol, OptionType.P); var syntheticFwdDelta = deltaCall - deltaPut; Assert.Equal(System.Math.Exp(-rf * t), syntheticFwdDelta, 10); //gamma closely matches numerical estimate var delta1 = BlackFunctions.BlackDelta(f + 0.000005, k, rf, t, vol, cp); var delta2 = BlackFunctions.BlackDelta(f - 0.000005, k, rf, t, vol, cp); var gammaEst = (delta1 - delta2) / 0.00001; var gamma = BlackFunctions.BlackGamma(f, k, rf, t, vol); Assert.Equal(gammaEst, gamma, 6); //cases for zero delta / gamma delta = BlackFunctions.BlackDelta(f, 0, rf, t, vol, OptionType.P); Assert.Equal(0, delta, 8); delta = BlackFunctions.BlackDelta(f, 1e6, rf, t, vol, OptionType.C); Assert.Equal(0, delta, 8); gamma = BlackFunctions.BlackGamma(f, 0, rf, t, vol); Assert.Equal(0, gamma, 8); gamma = BlackFunctions.BlackGamma(f, 1e6, rf, t, vol); Assert.Equal(0, gamma, 8); }
public void PVFacts() { var t = 1.0; var k = 0; var f = 100; var vol = 0.32; var rf = 0.05; var cp = OptionType.P; var df = System.Math.Exp(-rf * t); //zero strike put is worthless var PV = BinomialTree.AmericanFutureOptionPV(f, k, rf, t, vol, cp); Assert.Equal(0, PV, 10); PV = TrinomialTree.AmericanFutureOptionPV(f, k, rf, t, vol, cp); Assert.Equal(0, PV, 10); //zero strike call is worth fwd with no discounting cp = OptionType.C; PV = BinomialTree.AmericanFutureOptionPV(f, k, rf, t, vol, cp); Assert.Equal(f, PV, 10); PV = TrinomialTree.AmericanFutureOptionPV(f, k, rf, t, vol, cp); Assert.Equal(f, PV, 10); //and has delta = 1.0 var greeks = TrinomialTree.AmericanFutureOption(f, k, rf, t, vol, cp); Assert.Equal(1.0, (double)greeks[1, 0], 10); greeks = BinomialTree.AmericanFutureOption(f, k, rf, t, vol, cp); Assert.Equal(1.0, (double)greeks[1, 0], 10); //OTM option with zero vol is worthless vol = 0.0; k = f + 1; PV = BinomialTree.AmericanFutureOptionPV(f, k, rf, t, vol, cp); Assert.Equal(0, PV, 10); PV = TrinomialTree.AmericanFutureOptionPV(f, k, rf, t, vol, cp); Assert.Equal(0, PV, 10); //option worth >= black in all cases for same inputs vol = 0.32; k = f + 20; var PVBlack = BlackFunctions.BlackPV(f, k, rf, t, vol, cp); PV = BinomialTree.AmericanFutureOptionPV(f, k, rf, t, vol, cp); Assert.True(PV >= PVBlack); PV = TrinomialTree.AmericanFutureOptionPV(f, k, rf, t, vol, cp); Assert.True(PV >= PVBlack); }
public void PremiumInterpolatorFacts() { var origin = new DateTime(2017, 02, 07); var expiry = origin.AddYears(1); var t = (expiry - origin).TotalDays / 365.0; var volAsset = 0.32; var fwd = 100.0; var surfaceAsset = new ConstantVolSurface(origin, volAsset); var premInterp = surfaceAsset.GeneratePremiumInterpolator(100, expiry, fwd, OptionType.P); var strike = fwd * 0.8; Assert.Equal(BlackFunctions.BlackPV(fwd, strike, 0.0, t, volAsset, OptionType.P), premInterp.Interpolate(strike), 2); }
private double GetAbsStrikeForDelta(double fwd, double deltaStrike, double maturity) { var cp = deltaStrike < 0 ? OptionType.Put : OptionType.Call; Func <double, double> testFunc = (absK => { var interpForStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, ExpiriesDouble.Select(e => GetVolForAbsoluteStrike(absK, e, fwd)).ToArray(), TimeInterpolatorType); var vol2 = interpForStrike.Interpolate(maturity); var deltaK = BlackFunctions.BlackDelta(fwd, absK, 0, maturity, vol2, cp); return(deltaK - deltaStrike); }); var solvedStrike = Math.Solvers.Brent.BrentsMethodSolve(testFunc, 0.000000001, 50 * fwd, 1e-8); return(solvedStrike); }
public void DeltaStrikeMappingFacts() { var t = 1.0; var k = 100; var f = 100; var vol = 0.32; var rf = 0.0; var cp = OptionType.P; //black forward delta matches var delta = BlackFunctions.BlackDelta(f, k, rf, t, vol, cp); var absolute = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(f, delta, rf, t, vol); Assert.Equal(k, absolute, 10); }
public double GetVolForDeltaStrike(double deltaStrike, double maturity, double forward) { if (deltaStrike > 1.0 || deltaStrike < -1.0) { throw new ArgumentOutOfRangeException($"Delta strike must be in range -1.0 < x < 1.0 - value was {deltaStrike}"); } var key = $"{deltaStrike:f6}~{maturity:f3}~{forward:f6}"; if (_allowCaching && _deltaVolCache.TryGetValue(key, out var vol)) { return(vol); } if (StrikeType == StrikeType.ForwardDelta) { var interpForStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(deltaStrike)).ToArray(), TimeInterpolatorType); vol = interpForStrike.Interpolate(maturity); } else { var fwd = forward; var cp = deltaStrike < 0 ? OptionType.Put : OptionType.Call; Func <double, double> testFunc = (absK => { var interpForStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(absK)).ToArray(), TimeInterpolatorType); var vol2 = interpForStrike.Interpolate(maturity); var deltaK = BlackFunctions.BlackDelta(fwd, absK, 0, maturity, vol2, cp); return(deltaK - Abs(deltaStrike)); }); var solvedStrike = Math.Solvers.Brent.BrentsMethodSolve(testFunc, 0.000000001, 10 * fwd, 1e-8); var interpForSolvedStrike = InterpolatorFactory.GetInterpolator(ExpiriesDouble, _interpolators.Select(x => x.Interpolate(solvedStrike)).ToArray(), TimeInterpolatorType); vol = interpForSolvedStrike.Interpolate(maturity); } if (_allowCaching) { _deltaVolCache[key] = vol; } return(vol); }
public static double GetVolForDeltaStrike(double deltaStrike, double maturity, double forward, Func <double, double> GetVolForAbsoluteStrike) { var fwd = forward; var cp = OptionType.Put; Func <double, double> testFunc = (absK => { var vol = GetVolForAbsoluteStrike(absK); var deltaK = System.Math.Abs(BlackFunctions.BlackDelta(fwd, absK, 0, maturity, vol, cp)); return(deltaK - System.Math.Abs(deltaStrike)); }); var solvedStrike = Math.Solvers.Brent.BrentsMethodSolve(testFunc, fwd / 10, 50 * fwd, 1e-8); return(GetVolForAbsoluteStrike(solvedStrike)); }