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 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 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 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 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 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)); }
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)); }
public static Dictionary <DateTime, IInterpolator1D> ToDeltaSmiles(this List <ListedOptionSettlementRecord> optionSettlements, Dictionary <string, double> futuresPrices, SmileOrderOfPrecedence orderOfP = SmileOrderOfPrecedence.UseOTM) { var output = new Dictionary <DateTime, IInterpolator1D>(); var byExpiry = optionSettlements.GroupBy(r => r.ExpiryDate).OrderBy(o => o.Key); foreach (var expGroup in byExpiry) { var expiry = expGroup.Key; if (expGroup.Select(x => x.UnderlyingFuturesCode).Distinct().Count() != 1) { throw new Exception($"Inconsistent underlying contracts for expiry {expiry}"); } if (expGroup.Select(x => x.ValDate).Distinct().Count() != 1) { throw new Exception($"Inconsistent value dates for expiry {expiry}"); } var t = expGroup.First().ValDate.CalculateYearFraction(expiry, DayCountBasis.ACT365F); var futCode = expGroup.First().UnderlyingFuturesCode; if (!futuresPrices.TryGetValue(futCode, out var fut)) { throw new Exception($"No future price found for contract {futCode}"); } var filtered = new List <SmilePoint>(); var byStrike = expGroup.GroupBy(e => e.Strike).OrderBy(o => o.Key); foreach (var strikeGrp in byStrike) { var k = strikeGrp.Key; if (strikeGrp.Count() > 2) { throw new Exception($"Did not expect more than two options at strike {k}"); } switch (orderOfP) { case SmileOrderOfPrecedence.Average: { var v = strikeGrp.Average(s => s.ImpliedVol); filtered.Add( new SmilePoint { Strike = -BlackFunctions.BlackDelta(fut, k, 0.0, t, v, OptionType.P), ImpliedVol = v }); break; } case SmileOrderOfPrecedence.UseOTM: { ListedOptionSettlementRecord r; if (fut > k) { r = strikeGrp.Where(sg => sg.CallPut == OptionType.P).SingleOrDefault(); } else { r = strikeGrp.Where(sg => sg.CallPut == OptionType.C).SingleOrDefault(); } if (r != null && r.ImpliedVol > 1e-8) { filtered.Add( new SmilePoint { Strike = -BlackFunctions.BlackDelta(fut, k, 0.0, t, r.ImpliedVol, OptionType.P), ImpliedVol = r.ImpliedVol }); } break; } } } //now we have filtered to a single vol per-strike... if (filtered.Any()) { output[expiry] = InterpolatorFactory.GetInterpolator(filtered.Select(f => f.Strike).ToArray(), filtered.Select(f => f.ImpliedVol).ToArray(), Interpolator1DType.CubicSpline); } } return(output); }
public static double Delta(double forward, double knownAverage, double sigma, double K, DateTime evalDate, DateTime avgStartDate, DateTime avgEndDate, double riskFree, OptionType callPut, Calendar fixingCalendar) { //Build Vector of Observation Dates var resetDates = avgStartDate.BusinessDaysInPeriod(avgEndDate, fixingCalendar); var RT = resetDates.Count; if (avgEndDate == evalDate) { if (callPut == OptionType.C) { return(knownAverage > K ? 1.0 / RT : 0); } else { return(knownAverage < K ? -1.0 / RT : 0); } } else if (avgEndDate < evalDate) { return(0); } else if (avgStartDate == avgEndDate) { var t = (avgEndDate - evalDate).TotalDays / 365.0; return(BlackFunctions.BlackDelta(forward, K, riskFree, t, sigma, callPut)); } //Initialise Variables double E1 = 0.0, E2 = 0.0, E3 = 0.0, E4 = 0.0, E5 = 0.0; double DeltaTPrime = 0.0, DeltaT = 0.0; var FBar = forward; var tvs = resetDates.Count(x => x < evalDate); if (tvs > 0) { DeltaTPrime = (resetDates[tvs + 1] - resetDates[tvs]).TotalDays / 365.0; } else { DeltaTPrime = ((resetDates[0] - evalDate).TotalDays) / 365.0; } if (tvs + 2 >= RT) { DeltaT = DeltaTPrime; } else { DeltaT = (avgEndDate - resetDates[tvs + 1]).TotalDays / 365.0 / (RT - tvs); } var Ak = knownAverage * tvs / RT; E5 = 2 * Ak * FBar * (RT - tvs) / RT + (Ak * Ak); if (tvs < RT) { E4 = (1 + Exp(sigma * sigma * DeltaT)) * (Exp((RT - tvs) * sigma * sigma * DeltaT) - 1); E4 += 2 * (RT - tvs) * (1 - Exp(sigma * sigma * DeltaT)); E4 /= (Pow(Exp(sigma * sigma * DeltaT) - 1, 2)); } else { E4 = 1; } E3 = Pow(FBar / RT, 2) * Exp(sigma * sigma * DeltaTPrime); E2 = E3 * E4 + E5; E1 = FBar * (RT - tvs) / RT + Ak; var EA = Ak + FBar * (RT - tvs) / RT; var b = Log(E2) - 2 * Log(E1); var d1 = (Log(EA / K) + 0.5 * b) / Sqrt(b); var df = Exp(-riskFree * (avgEndDate - evalDate).TotalDays / 365.0); return(df * ((callPut == OptionType.Put) ? Math.Statistics.NormSDist(d1) - 1 : Math.Statistics.NormSDist(d1))); }
public void BlackDelta_Facts() { Assert.Equal("Could not parse call or put flag - blah", BlackFunctions.BlackDelta(1.0, 1.0, 1, 1, 1, "blah")); Assert.Equal(0.0, BlackFunctions.BlackDelta(0.1, 1.0, 0.5, 0.0, 0.0, "C")); }
public static IInterpolator1D GenerateCompositeSmileB(this IVolSurface surface, IVolSurface fxSurface, int numSamples, DateTime expiry, double fwdAsset, double fwdFx, double correlation, bool strikesInDeltaSpace = false) { var t = surface.OriginDate.CalculateYearFraction(expiry, DayCountBasis.Act365F); var fxInv = new InverseFxSurface("fxInv", fxSurface as IATMVolSurface, null); var atmFx = fxSurface.GetVolForDeltaStrike(0.5, t, fwdFx); var atmA = surface.GetVolForDeltaStrike(0.5, t, fwdAsset); var compoFwd = fwdAsset * fwdFx; var atmCompo = Sqrt(atmFx * atmFx + atmA * atmA + 2.0 * correlation * atmA * atmFx); var lowK = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(compoFwd, -0.01, 0, t, atmCompo); var hiK = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(compoFwd, -0.99, 0, t, atmCompo); //var cdfInvFx = fxSurface.GenerateCDF2(numSamples * 10, expiry, fwdFx, true); //var cdfInvAsset = surface.GenerateCDF2(numSamples * 10, expiry, fwdAsset, true); //var yFx = new Func<double, double>(z => cdfInvFx.Interpolate(Statistics.NormSDist(z))); //var yAsset = new Func<double, double>(z => cdfInvAsset.Interpolate(Statistics.NormSDist(z))); var fxCDFCache = new Dictionary <double, double>(); var assetCDFCache = new Dictionary <double, double>(); var yFx = new Func <double, double>(z => { if (fxCDFCache.TryGetValue(z, out var K)) { return(K); } K = fxInv.InverseCDF(expiry, 1.0 / fwdFx, Statistics.NormSDist(z)); fxCDFCache.Add(z, K); return(K); }); var yAsset = new Func <double, double>(z => { if (assetCDFCache.TryGetValue(z, out var K)) { return(K); } K = surface.InverseCDF(expiry, fwdAsset, Statistics.NormSDist(z)); assetCDFCache.Add(z, K); return(K); }); //var fxCDFCache = new Dictionary<double, double>(); //var assetCDFCache = new Dictionary<double, double>(); //var putFx = fxInv.GeneratePremiumInterpolator(numSamples * 10, expiry, 1.0/fwdFx, OptionType.P); //var putAsset = surface.GeneratePremiumInterpolator(numSamples * 10, expiry, fwdAsset, OptionType.P); //var yFx = new Func<double, double>(z => //{ // if (fxCDFCache.TryGetValue(z, out var K)) return K; // K = InverseCDF(putFx, t, 1.0/fwdFx, Statistics.NormSDist(z)); // fxCDFCache.Add(z, K); // return K; //}); //var yAsset = new Func<double, double>(z => //{ // if (assetCDFCache.TryGetValue(z, out var K)) return K; // K = InverseCDF(putAsset, t, fwdAsset, Statistics.NormSDist(z)); // var kl = assetCDFCache.Keys.ToList(); // var closerIx = kl.BinarySearch(z); // var keyIx = ~closerIx; // if (closerIx < 0 && z < 0 && kl.Count > keyIx) // { // if (assetCDFCache[kl[keyIx]] < K) // K = assetCDFCache[kl[keyIx]]; // } // assetCDFCache.Add(z, K); // return K; //}); var payoff = new Func <double, double, double, double>((z1, z2, kQ) => Max(kQ * yFx(z2) - yAsset(z1), 0)); var integrand = new Func <double, double, double, double>((z1, z2, kQ) => payoff(z1, z2, kQ) * BivariateNormal.PDF(z1, z2, -correlation)); var kStep = (hiK - lowK) / numSamples; var ks = Enumerable.Range(0, numSamples).Select(kk => lowK + kk * kStep).ToArray(); var premiums = new double[ks.Length]; var vols = new double[ks.Length]; for (var i = 0; i < ks.Length; i++) { var ik = new Func <double, double, double>((z1, z2) => integrand(z1, z2, ks[i])); var pk = Integration.TwoDimensionalGaussLegendre(ik, -5, 5, -5, 5, 16); //var pk = Integration.TwoDimensionalSimpsons(ik, -5, 5, -5, 5, 100); pk *= fwdFx; var volK = BlackFunctions.BlackImpliedVol(compoFwd, ks[i], 0.0, t, pk, OptionType.P); vols[i] = volK; premiums[i] = pk; } if (strikesInDeltaSpace) { ks = ks.Select((ak, ix) => - BlackFunctions.BlackDelta(compoFwd, ak, 0.0, t, vols[ix], OptionType.P)).ToArray(); } return(InterpolatorFactory.GetInterpolator(ks, vols, Interpolator1DType.CubicSpline)); }
public static IInterpolator1D GenerateCompositeSmile(this IVolSurface surface, IVolSurface fxSurface, int numSamples, DateTime expiry, double fwdAsset, double fwdFx, double rho, bool strikesInDeltaSpace = false) { var t = surface.OriginDate.CalculateYearFraction(expiry, DayCountBasis.Act365F); var atmFx = fxSurface.GetVolForDeltaStrike(0.5, t, fwdFx); var atmA = surface.GetVolForDeltaStrike(0.5, t, fwdAsset); var compoFwd = fwdAsset / fwdFx; var atmCompo = Sqrt(atmFx * atmFx + atmA * atmA + 2.0 * rho * atmA * atmFx); var lowK = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(compoFwd, -0.01, 0, t, atmCompo); var hiK = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(compoFwd, -0.99, 0, t, atmCompo); var nuA = Sqrt(t) * atmA; var nuFx = Sqrt(t) * atmFx; var cdfFx = new Func <double, double>(k => fxSurface.CDF(expiry, fwdFx, Exp(k))); var cdfA = new Func <double, double>(k => surface.CDF(expiry, fwdAsset, Exp(k))); var fxCDFCache = new Dictionary <double, double>(); var assetCDFCache = new Dictionary <double, double>(); var yFx = new Func <double, double>(z => { if (fxCDFCache.TryGetValue(z, out var K)) { return(K); } K = Log(fxSurface.InverseCDF(expiry, fwdFx, Statistics.NormSDist(z))); fxCDFCache.Add(z, K); return(K); }); var yA = new Func <double, double>(z => { if (assetCDFCache.TryGetValue(z, out var K)) { return(K); } K = Log(surface.InverseCDF(expiry, fwdAsset, Statistics.NormSDist(z))); assetCDFCache.Add(z, K); return(K); }); //var zfxS = new Func<double, double, double>((zA, K) => Statistics.NormInv(Max(1e-18, Min(1.0 - 1e-18, cdfFx(yA(zA) - Log(K)))))); //var zAs = new Func<double, double, double>((zFx, K) => Statistics.NormInv(Max(1e-18, Min(1.0 - 1e-18, cdfA(yFx(zFx) + Log(K)))))); var zfxS = new Func <double, double, double>((zA, K) => Statistics.NormInv(cdfFx(yA(zA) - Log(K)))); var zAs = new Func <double, double, double>((zFx, K) => Statistics.NormInv(cdfA(yFx(zFx) + Log(K)))); var d = -1.0; var p2 = 1.0 / Sqrt(2.0 * PI); //var I1 = new Func<double, double, double>((zA, K) => //p2*Exp(yA(zA) - (nuA * zA - nuA * nuA / 2)) * Statistics.NormSDist(d * (zfxS(zA, K) - rho * zA) / Sqrt(1 - rho * rho)) * Exp(-(zA - nuA) * (zA - nuA) / 2.0) // ); //var I2 = new Func<double, double, double>((zFx, K) => //p2*Exp(yFx(zFx) - (nuFx * zFx - nuFx * nuFx / 2)) * Statistics.NormSDist(-d * (zAs(zFx, K) - rho * zFx) / Sqrt(1 - rho * rho)) * Exp(-(zFx - nuFx) * (zFx - nuFx) / 2.0) // ); var I1 = new Func <double, double, double>((zA, K) => p2 * Exp(yA(zA)) * Statistics.NormSDist(d * (zfxS(zA, K) - rho * zA) / Sqrt(1 - rho * rho)) * Exp(-(zA * zA) / 2.0) ); var I2 = new Func <double, double, double>((zFx, K) => p2 * Exp(yFx(zFx)) * Statistics.NormSDist(-d * (zAs(zFx, K) - rho * zFx) / Sqrt(1 - rho * rho)) * Exp(-(zFx * zFx) / 2.0) ); var kStep = (hiK - lowK) / numSamples; var ks = Enumerable.Range(0, numSamples).Select(kk => lowK + kk * kStep).ToArray(); var premiums = new double[ks.Length]; var vols = new double[ks.Length]; for (var i = 0; i < ks.Length; i++) { var I1k = new Func <double, double>(z => I1(z, ks[i])); var I2k = new Func <double, double>(z => I2(z, ks[i])); //var i1 = Integration.GaussLegendre(I1k, -5, 5, 16); //var i2 = Integration.GaussLegendre(I2k, -5, 5, 16); var i1 = Integration.SimpsonsRule(I1k, -5, 5, numSamples); var i2 = Integration.SimpsonsRule(I2k, -5, 5, numSamples); var pk = d * (i1 - ks[i] * i2); pk /= fwdFx; var volK = BlackFunctions.BlackImpliedVol(compoFwd, ks[i], 0.0, t, pk, OptionType.P); vols[i] = volK; premiums[i] = pk; } if (strikesInDeltaSpace) { ks = ks.Select((ak, ix) => - BlackFunctions.BlackDelta(compoFwd, ak, 0.0, t, vols[ix], OptionType.P)).ToArray(); } return(InterpolatorFactory.GetInterpolator(ks, vols, Interpolator1DType.CubicSpline)); }