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 void CanRunPV() { var sut = GetSut(); var zar = TestProviderHelper.CurrencyProvider["ZAR"]; var pvCube = sut.PV(zar); var expiryDates = sut.Portfolio.Instruments .Select(x => x as AsianOption) .OrderBy(x => x.AverageEndDate) .Select(x => x.AverageEndDate); var expiryTimes = expiryDates.Select(d => sut.OriginDate.CalculateYearFraction(d, DayCountBasis.Act365F)).ToArray(); var pvs = pvCube.GetAllRows().Select(x => x.Value).ToArray(); var vols = pvs.Select((x, ix) => BlackFunctions.BlackImpliedVol(1400, 1400, 0.0, expiryTimes[ix], x, OptionType.C)).ToArray(); var volsCl = expiryDates.Select(d => sut.VanillaModel.GetVolSurface("CL").GetVolForDeltaStrike(0.5, d, 100)).ToArray(); var volsFx = expiryDates.Select(d => sut.VanillaModel.GetVolSurface("USD/ZAR").GetVolForDeltaStrike(0.5, d, 100)).ToArray(); var expectedVols = volsCl.Select((x, ix) => System.Math.Sqrt(x * x + volsFx[ix] * volsFx[ix] + 2 * _correls[ix + 1] * volsFx[ix] * x)).ToArray(); for (var i = 0; i < expiryTimes.Length; i++) { Assert.Equal(expectedVols[i], vols[i], 1); } }
public static void ImplyVols(List <ListedOptionSettlementRecord> optionSettlements, Dictionary <string, double> futuresPrices, IIrCurve discountCurve = null) { var o = new Dictionary <DateTime, IInterpolator1D>(); foreach (var s in optionSettlements) { if (!futuresPrices.TryGetValue(s.UnderlyingFuturesCode, out var fut)) { throw new Exception($"No future price found for contract {s}"); } var t = s.ValDate.CalculateYearFraction(s.ExpiryDate, DayCountBasis.ACT365F); var r = 0.0; if (s.MarginType == OptionMarginingType.Regular) { if (discountCurve == null) { throw new Exception("To strip vols from options with regular margining, a discount curve must be supplied"); } r = discountCurve.GetRate(s.ExpiryDate); } if (s.ExerciseType == OptionExerciseType.European || s.MarginType == OptionMarginingType.FuturesStyle) { s.ImpliedVol = BlackFunctions.BlackImpliedVol(fut, s.Strike, r, t, s.PV, s.CallPut); } else if (s.ExerciseType == OptionExerciseType.American) { s.ImpliedVol = BinomialTree.AmericanFuturesOptionImpliedVol(fut, s.Strike, r, t, s.PV, s.CallPut); } else { throw new Exception("Unable to handle option in stripper"); } } }
public void CompositeSmimleFacts_LocalVol() { var origin = new DateTime(2017, 02, 07); var expiry = origin.AddMonths(2); var tExp = origin.CalculateYearFraction(expiry, DayCountBasis.Act365F); var fwdCurveAsset = new Func <double, double>(t => { return(100); }); var fwdCurveFx = new Func <double, double>(t => { return(15); }); var volAsset = 0.32; var volFx = 0.16; var correl = 0.25; var surfaceAsset = new RiskyFlySurface(origin, new[] { volAsset }, new[] { expiry }, new[] { 0.25, 0.1 }, new[] { new[] { 0.02, 0.03 } }, new[] { new[] { 0.005, 0.007 } }, new[] { 100.0 }, WingQuoteType.Arithmatic, AtmVolType.ZeroDeltaStraddle, Interpolator1DType.GaussianKernel, Interpolator1DType.Linear) { FlatDeltaSmileInExtreme = true }; var surfaceFx = new RiskyFlySurface(origin, new[] { volFx }, new[] { expiry }, new[] { 0.25, 0.1 }, new[] { new[] { 0.015, 0.025 } }, new[] { new[] { 0.005, 0.007 } }, new[] { 0.1 }, WingQuoteType.Arithmatic, AtmVolType.ZeroDeltaStraddle, Interpolator1DType.GaussianKernel, Interpolator1DType.Linear) { FlatDeltaSmileInExtreme = true }; //var surfaceAsset = new SabrVolSurface(origin, new[] { volAsset }, new[] { expiry }, new[] { 0.25, 0.1 }, new[] { new[] { 0.02, 0.03 } }, new[] { new[] { 0.005, 0.007 } }, new[] { 100.0 }, WingQuoteType.Arithmatic, AtmVolType.ZeroDeltaStraddle, Interpolator1DType.Linear); //var surfaceFx = new SabrVolSurface(origin, new[] { volFx }, new[] { expiry }, new[] { 0.25, 0.1 }, new[] { new[] { 0.015, 0.025 } }, new[] { new[] { 0.005, 0.007 } }, new[] { 0.1 }, WingQuoteType.Arithmatic, AtmVolType.ZeroDeltaStraddle, Interpolator1DType.Linear); //var surfaceAsset = new SVIVolSurface(origin, new[] { volAsset }, new[] { expiry }, new[] { 0.25, 0.1 }, new[] { new[] { 0.02, 0.03 } }, new[] { new[] { 0.005, 0.007 } }, new[] { 100.0 }, WingQuoteType.Arithmatic, AtmVolType.ZeroDeltaStraddle, Interpolator1DType.Linear); //var surfaceFx = new SVIVolSurface(origin, new[] { volFx }, new[] { expiry }, new[] { 0.25, 0.1 }, new[] { new[] { 0.015, 0.025 } }, new[] { new[] { 0.005, 0.007 } }, new[] { 0.1 }, WingQuoteType.Arithmatic, AtmVolType.ZeroDeltaStraddle, Interpolator1DType.Linear); var invFx = new InverseFxSurface("inv", surfaceFx, TestProviderHelper.CurrencyProvider); var surfaceCompo = surfaceAsset.GenerateCompositeSmile(invFx, 200, expiry, 100, 1.0 / 15, correl); //setup MC using var engine = new PathEngine(2.IntPow(IsCoverageOnly?5:15)); engine.AddPathProcess( new Qwack.Random.MersenneTwister.MersenneTwister64 { UseNormalInverse = true }); var correlMatrix = new double[][] { new double[] { 1.0, correl }, new double[] { correl, 1.0 }, }; engine.AddPathProcess(new Cholesky(correlMatrix)); var asset1 = new TurboSkewSingleAsset ( startDate: origin, expiryDate: expiry, volSurface: surfaceAsset, forwardCurve: fwdCurveAsset, nTimeSteps: 1, name: "Asset" ); var asset2 = new TurboSkewSingleAsset ( startDate: origin, expiryDate: expiry, volSurface: surfaceFx, forwardCurve: fwdCurveFx, nTimeSteps: 1, name: "USD/ZAR" ); engine.AddPathProcess(asset1); engine.AddPathProcess(asset2); var strike = 1500; var product = new EuropeanOption { AssetId = "Asset", CallPut = OptionType.C, ExpiryDate = expiry, PaymentCurrency = TestProviderHelper.CurrencyProvider["ZAR"], PaymentDate = expiry, Notional = 1.0, SpotLag = new Frequency("0b"), Strike = strike, FxConversionType = FxConversionType.ConvertThenAverage }; var productAsset = new EuropeanOption { AssetId = "Asset", CallPut = OptionType.C, ExpiryDate = expiry, PaymentCurrency = TestProviderHelper.CurrencyProvider["USD"], PaymentDate = expiry, Notional = 1.0, SpotLag = new Frequency("0b"), Strike = 100, FxConversionType = FxConversionType.None }; var productFx = new EuropeanOption { AssetId = "USD/ZAR", CallPut = OptionType.C, ExpiryDate = expiry, PaymentCurrency = TestProviderHelper.CurrencyProvider["ZAR"], PaymentDate = expiry, Notional = 1.0, SpotLag = new Frequency("0b"), Strike = 0.1, FxConversionType = FxConversionType.None }; var pathProduct = new AssetPathPayoff(product, TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider, TestProviderHelper.CurrencyProvider["ZAR"]); var pathProductAsset = new AssetPathPayoff(productAsset, TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider, TestProviderHelper.CurrencyProvider["ZAR"]); var pathProductFx = new AssetPathPayoff(productFx, TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider, TestProviderHelper.CurrencyProvider["ZAR"]); engine.AddPathProcess(pathProduct); engine.AddPathProcess(pathProductAsset); engine.AddPathProcess(pathProductFx); engine.SetupFeatures(); engine.RunProcess(); var q = pathProduct.ResultsByPath; var qq = q.Average(); var productIv = BlackFunctions.BlackImpliedVol(1500, strike, 0.0, tExp, pathProduct.AverageResult, OptionType.C); var productAssetIv = BlackFunctions.BlackImpliedVol(100, 100, 0.0, tExp, pathProductAsset.AverageResult, OptionType.C); var productFxIv = BlackFunctions.BlackImpliedVol(10, 10, 0.0, tExp, pathProductFx.AverageResult, OptionType.C); Assert.True(Abs(productIv - surfaceCompo.Interpolate(strike)) < 0.01); }
public void BlackImpliedVol_Facts() { Assert.Equal("Could not parse call or put flag - blah", BlackFunctions.BlackImpliedVol(1.0, 1.0, 1, 1, 1, "blah")); Assert.Equal(1e-9, BlackFunctions.BlackImpliedVol(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)); }