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 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); } }
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 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 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 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 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 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 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 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); }
public void PVFacts() { var t = 1.0; var k = 0.0; var f = 100.0; var vol = 0.32; var rf = 0.05; var cp = OptionType.P; //zero strike put is worthless var PV = TrinomialTree.AmericanAssetOptionPV(f, k, rf, f, t, vol, cp); Assert.Equal(0, PV, 10); PV = BinomialTree.AmericanPV(t, f, k, rf, vol, cp, rf, 100); Assert.Equal(0, PV, 10); var flatInterp = new DummyPointInterpolator(new[] { 1.0 }, new[] { rf }); var flatFwdInterp = new DummyPointInterpolator(new[] { 1.0 }, new[] { f }); PV = TrinomialTree.VanillaPV(t, f, k, flatInterp, vol, cp, flatFwdInterp, 100, true); Assert.Equal(0, PV, 10); //zero strike call is worth fwd with no discounting cp = OptionType.C; PV = TrinomialTree.AmericanAssetOptionPV(f, k, rf, f, t, vol, cp); Assert.Equal(f, PV, 10); PV = BinomialTree.AmericanPV(t, f, k, rf, vol, cp, rf, 100); Assert.Equal(f, PV, 10); //OTM option with zero vol is worthless vol = 0.0; k = f + 1.0; PV = BinomialTree.AmericanPV(t, f, k, rf, vol, cp, rf, 100); Assert.Equal(0, PV, 10); PV = TrinomialTree.AmericanAssetOptionPV(f, k, rf, f, 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.AmericanPV(t, f, k, rf, vol, cp, rf, 100); Assert.True(PV >= PVBlack); PV = TrinomialTree.AmericanAssetOptionPV(f, k, rf, f, t, vol, cp); Assert.True(PV >= PVBlack); }
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; //zero strike put is worthless var PV = BlackFunctions.BlackPV(f, k, rf, t, vol, cp); Assert.Equal(0, PV, 10); //zero strike call is worth discounted fwd cp = OptionType.C; PV = BlackFunctions.BlackPV(f, k, rf, t, vol, cp); Assert.Equal(Exp(-rf * t) * f, PV, 10); //OTM option with zero vol is worthless vol = 0.0; k = f + 1; PV = BlackFunctions.BlackPV(f, k, rf, t, vol, cp); Assert.Equal(0, PV, 10); //put-call parity at f==k k = f; vol = 0.32; var PVcall = BlackFunctions.BlackPV(f, k, rf, t, vol, OptionType.C); var PVput = BlackFunctions.BlackPV(f, k, rf, t, vol, OptionType.P); Assert.Equal(PVcall, PVput, 10); //forward price constant wrt total variance rf = 0; var variance = vol * vol * t; var t2 = t * 2.0; var vol2 = Sqrt(variance / t2); var PVnear = BlackFunctions.BlackPV(f, k, rf, t, vol, OptionType.C); var PVfar = BlackFunctions.BlackPV(f, k, rf, t2, vol2, OptionType.C); Assert.Equal(PVnear, PVfar, 10); }
public void DigitalFacts() { var t = 1.0; var f = 100; var vol = 0.32; var rf = 0.0; var cp = OptionType.C; var k = 110; var digiPV = BlackFunctions.BlackDigitalPV(f, k, rf, t, vol, cp); var spread = 0.0001; var expected = (BlackFunctions.BlackPV(f, k, rf, t, vol, cp) - BlackFunctions.BlackPV(f, k + spread, rf, t, vol, cp)) / spread; Assert.Equal(expected, digiPV, 6); var iv = BlackFunctions.BlackDigitalImpliedVol(f, k, rf, t, digiPV, cp); Assert.Equal(vol, iv, 6); }
public static IInterpolator1D GenerateCDF(this IInterpolator1D smile, int numSamples, double t, double fwd) { var deltaK = fwd * 0.0001; var atmVol = smile.Interpolate(fwd); var lowStrike = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, -0.0001, 0, t, atmVol); var hiStrike = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, -0.9999, 0, t, atmVol); 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] = k / 2.0; y[0] = 0; continue; } if (i == x.Length - 1) { x[i] = k * 2; y[i] = 1; continue; } var volLow = smile.Interpolate(k - deltaK / 2.0); var putLow = BlackFunctions.BlackPV(fwd, k - deltaK / 2.0, 0, t, volLow, OptionType.P); var volHi = smile.Interpolate(k + deltaK / 2.0); var putHi = BlackFunctions.BlackPV(fwd, k + deltaK / 2.0, 0, t, volHi, OptionType.P); var digital = (putHi - putLow) / deltaK; y[i] = digital; x[i] = k; k += kStep; } return(InterpolatorFactory.GetInterpolator(x, y, Interpolator1DType.Linear)); }
public static double PV(double forward, double knownAverage, double sigma, double K, double tAvgStart, double tExpiry, double riskFree, OptionType callPut) { if (tExpiry <= 0) //work out intrinsic { return(callPut == OptionType.Call ? Max(0, knownAverage - K) : Max(0, K - knownAverage)); } 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); //hack to fix deep ITM options have imaginary vol var sigma_a = tExpiry == 0 ? 0.0 : Sqrt(Log(M) / tExpiry); K = AsianUtils.AdjustedStrike(K, knownAverage, tExpiry, tAvgStart); if (K <= 0 && tAvgStart < 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); } var pv = BlackFunctions.BlackPV(forward, K, riskFree, tExpiry, sigma_a, callPut); if (tAvgStart < 0) { pv *= tExpiry / (tExpiry - tAvgStart); } return(pv); }
public void BlackMC_PathsGenerated() { var origin = DateTime.Now.Date; using var engine = new PathEngine(2.IntPow(IsCoverageOnly ? 6 : 15)); engine.AddPathProcess(new Random.MersenneTwister.MersenneTwister64() { UseNormalInverse = true, UseAnthithetic = false }); var volSurface = new ConstantVolSurface(origin, 0.32); var fwdCurve = new Func <double, double>(t => { return(900 + 100 * t); }); var asset = new BlackSingleAsset ( startDate: origin, expiryDate: origin.AddYears(1), volSurface: volSurface, forwardCurve: fwdCurve, nTimeSteps: IsCoverageOnly ? 1 : 10, 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 blackPv = BlackFunctions.BlackPV(1000, 900, 0, 1, 0.32, OptionType.P); if (!IsCoverageOnly) { Assert.True(System.Math.Abs(blackPv - pv) < 1.0); var fwd = payoff2.AverageResult; Assert.True(System.Math.Abs(fwdCurve(1) / fwd - 1.0) < 0.001); } }
public void EuropeanOnGridFacts() { var t = 1.0; var k = 120; var f = 100; var vol = 0.32; var rf = 0.05; var cp = OptionType.P; var PVBlack = BlackFunctions.BlackPV(f, k, rf, t, vol, cp); var PVbi = BinomialTree.EuropeanPV(t, f, k, rf, vol, cp, rf, 100); var PVtri = TrinomialTree.EuropeanPV(t, f, k, rf, vol, cp, rf, 100); Assert.Equal(PVBlack, PVbi, 1); Assert.Equal(PVBlack, PVtri, 1); PVbi = BinomialTree.EuropeanFuturePV(t, f, k, rf, vol, cp, 100); PVtri = TrinomialTree.EuropeanFuturePV(t, f, k, rf, vol, cp, 100); Assert.Equal(PVBlack, PVbi, 1); Assert.Equal(PVBlack, PVtri, 1); }
public static IInterpolator1D GeneratePremiumInterpolator(this IVolSurface surface, int numSamples, double t, double fwd, OptionType cp) { var lowStrikeVol = surface.GetVolForDeltaStrike(0.001, t, fwd); var lowStrike = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, -0.001, 0, t, lowStrikeVol); var hiStrikeVol = surface.GetVolForDeltaStrike(0.999, t, fwd); var hiStrike = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, -0.999, 0, t, hiStrikeVol); var x = new double[numSamples + 2]; var y = new double[numSamples + 2]; var k = lowStrike; var kStep = (hiStrike - lowStrike) / (numSamples + 1.0); var vol = surface.GetVolForAbsoluteStrike(k / 10, t, fwd); var call = BlackFunctions.BlackPV(fwd, k / 10, 0, t, vol, cp); y[0] = call; x[0] = k / 10; for (var i = 0; i < x.Length - 1; i++) { vol = surface.GetVolForAbsoluteStrike(k, t, fwd); call = BlackFunctions.BlackPV(fwd, k, 0, t, vol, cp); y[i + 1] = call; x[i + 1] = k; k += kStep; } vol = surface.GetVolForAbsoluteStrike(k * 10, t, fwd); call = BlackFunctions.BlackPV(fwd, k * 10, 0, t, vol, cp); y[x.Length - 1] = call; x[x.Length - 1] = k * 10; return(InterpolatorFactory.GetInterpolator(x, y, Interpolator1DType.MonotoneCubicSpline)); }
public static double PV(double forward, double knownAverage, double sigma, double K, DateTime evalDate, DateTime avgStartDate, DateTime avgEndDate, double riskFree, OptionType callPut, Calendar fixingCalendar) { if (avgEndDate == evalDate) { if (callPut == OptionType.C) { return(Max(knownAverage - K, 0)); } else { return(Max(K - knownAverage, 0)); } } else if (avgEndDate < evalDate) { return(0); } else if (avgStartDate == avgEndDate) { var t = (avgEndDate - evalDate).TotalDays / 365.0; return(BlackFunctions.BlackPV(forward, K, riskFree, t, sigma, callPut)); } //Build Vector of Observation Dates var resetDates = avgStartDate.BusinessDaysInPeriod(avgEndDate, fixingCalendar); var nResets = resetDates.Count; var nFixed = resetDates.Count(x => x < evalDate); var nFloat = nResets - nFixed; var DeltaTPrime = (nFixed > 0) ? (resetDates[nFixed + 1] - resetDates[nFixed]).TotalDays / 365.0 : (resetDates[0] - evalDate).TotalDays / 365.0; var DeltaT = (nFixed + 2 >= nResets) ? DeltaTPrime : (avgEndDate - resetDates[nFixed + 1]).TotalDays / 365.0 / nFloat; var Ak = knownAverage * nFixed / nResets; var E5 = 2 * Ak * forward * nFloat / nResets + (Ak * Ak); var E4 = 1.0; if (nFixed < nResets) { E4 = (1 + Exp(sigma * sigma * DeltaT)) * (Exp(nFloat * sigma * sigma * DeltaT) - 1); E4 += 2 * (nResets - nFixed) * (1 - Exp(sigma * sigma * DeltaT)); E4 /= Pow(Exp(sigma * sigma * DeltaT) - 1, 2); } var E3 = Pow(forward / nResets, 2) * Exp(sigma * sigma * DeltaTPrime); var E2 = E3 * E4 + E5; var E1 = forward * (nResets - nFixed) / nResets + Ak; var EA = Ak + forward * nFloat / nResets; var b = Log(E2) - 2 * Log(E1); var d1 = (Log(EA / K) + 0.5 * b) / Sqrt(b); var d2 = d1 - Sqrt(b); var df = Exp(-riskFree * (avgEndDate - evalDate).TotalDays / 365.0); //Main Option valuation if (callPut == OptionType.Call) { d1 = Math.Statistics.NormSDist(d1); d2 = Math.Statistics.NormSDist(d2); return((EA * d1 - K * d2) * df); } else { d1 = Math.Statistics.NormSDist(-d1); d2 = Math.Statistics.NormSDist(-d2); return((K * d2 - EA * d1) * df); } }
public void SolveSmileMarket() { var valDate = new DateTime(2018, 07, 28); var expDate = valDate.AddDays(365); var tExp = (expDate - valDate).TotalDays / 365.0; var fwd = 1000; double[] strikes = { 0.25, 0.5, 0.75 }; var atmConstraint = new ATMStraddleConstraint { ATMVolType = AtmVolType.ZeroDeltaStraddle, MarketVol = 0.32 }; var smile25d = new RRBFConstraint { Delta = 0.25, FlyVol = 0.01, RisykVol = 0.02, WingQuoteType = WingQuoteType.Market }; var s = new AssetSmileSolver(); if (IsCoverageOnly) { s.Tollerance = 1; } var smile = s.Solve(atmConstraint, new[] { smile25d }, valDate, expDate, fwd, strikes, Interpolator1DType.Linear); if (!IsCoverageOnly) { Assert.Equal(atmConstraint.MarketVol, smile[1], 8); } var surface = new GridVolSurface(valDate, strikes, new[] { expDate }, new[] { smile }, StrikeType.ForwardDelta, Interpolator1DType.Linear, Interpolator1DType.Linear, DayCountBasis.Act365F); //reprice market RR structrure off smile, premium must match var marketVolC = atmConstraint.MarketVol + smile25d.FlyVol + 0.5 * smile25d.RisykVol; var marketVolP = atmConstraint.MarketVol + smile25d.FlyVol - 0.5 * smile25d.RisykVol; var marketKC25 = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, 0.25, 0, tExp, marketVolC); var marketKP25 = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, -0.25, 0, tExp, marketVolP); var marketC25FV = BlackFunctions.BlackPV(fwd, marketKC25, 0, tExp, marketVolC, OptionType.C); var marketP25FV = BlackFunctions.BlackPV(fwd, marketKP25, 0, tExp, marketVolP, OptionType.P); var marketRR = marketC25FV - marketP25FV; var volC25d = surface.GetVolForAbsoluteStrike(marketKC25, expDate, fwd); var volP25d = surface.GetVolForAbsoluteStrike(marketKP25, expDate, fwd); var call25FV = BlackFunctions.BlackPV(fwd, marketKC25, 0, tExp, volC25d, OptionType.C); var put25FV = BlackFunctions.BlackPV(fwd, marketKP25, 0, tExp, volP25d, OptionType.P); var smileRR = call25FV - put25FV; if (!IsCoverageOnly) { Assert.Equal(marketRR, smileRR, 8); } //reprice market BF structrure off smile, premium must match var marketVolBF = atmConstraint.MarketVol + smile25d.FlyVol; var marketKBFC25 = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, 0.25, 0, tExp, marketVolBF); var marketKBFP25 = BlackFunctions.AbsoluteStrikefromDeltaKAnalytic(fwd, -0.25, 0, tExp, marketVolBF); var marketBFC25FV = BlackFunctions.BlackPV(fwd, marketKBFC25, 0, tExp, marketVolBF, OptionType.C); var marketBFP25FV = BlackFunctions.BlackPV(fwd, marketKBFP25, 0, tExp, marketVolBF, OptionType.P); var marketBF = marketBFC25FV + marketBFP25FV; var volCBF25d = surface.GetVolForAbsoluteStrike(marketKBFC25, expDate, fwd); var volPBF25d = surface.GetVolForAbsoluteStrike(marketKBFP25, expDate, fwd); var callBF25FV = BlackFunctions.BlackPV(fwd, marketKBFC25, 0, tExp, volCBF25d, OptionType.C); var putBF25FV = BlackFunctions.BlackPV(fwd, marketKBFP25, 0, tExp, volPBF25d, OptionType.P); var smileBF = callBF25FV + putBF25FV; if (!IsCoverageOnly) { Assert.Equal(marketBF, smileBF, 8); } }
public static double PV(double[] forwards, DateTime[] fixingDates, DateTime evalDate, DateTime payDate, double[] sigmas, double K, double riskFree, OptionType callPut, bool todayFixed = false) { if (payDate < evalDate) { return(0.0); } if (forwards.Length != fixingDates.Length || fixingDates.Length != sigmas.Length) { throw new DataMisalignedException(); } var nFixed = evalDate < fixingDates.First() ? 0 : fixingDates.Where(x => (todayFixed ? x <= evalDate : x < evalDate)).Count(); var nFloat = fixingDates.Length - nFixed; var m1 = forwards.Skip(nFixed).Average(); var wholeAverage = forwards.Average(); var tExpiry = evalDate.CalculateYearFraction(fixingDates.Last(), DayCountBasis.Act365F, false); var tPay = evalDate.CalculateYearFraction(payDate, DayCountBasis.Act365F, false); var df = Exp(-riskFree * tPay); if (tExpiry <= 0) //work out intrinsic { return(df * (callPut == OptionType.Call ? Max(0, wholeAverage - K) : Max(0, K - wholeAverage))); } var m2 = 0.0; var ts = fixingDates.Select(x => Max(0, evalDate.CalculateYearFraction(x, DayCountBasis.Act365F, false))).ToArray(); for (var i = nFixed; i < fixingDates.Length; i++) { for (var j = nFixed; j < fixingDates.Length; j++) { m2 += forwards[i] * forwards[j] * Exp(sigmas[i] * sigmas[j] * ts[Min(i, j)]); } } m2 /= nFloat * nFloat; var sigma_a = Sqrt(1 / tExpiry * Log(m2 / (m1 * m1))); var tAvgStart = evalDate.CalculateYearFraction(fixingDates.First(), DayCountBasis.Act365F, false); var knownAverage = nFixed == 0 ? 0.0 : forwards.Take(nFixed).Average(); var k0 = K; K = AsianUtils.AdjustedStrike(K, knownAverage, tExpiry, tAvgStart); if (K <= 0) { return((callPut == OptionType.P) ? 0.0 : df *Max(wholeAverage - k0, 0)); } var pv = BlackFunctions.BlackPV(m1, K, 0.0, tExpiry, sigma_a, callPut); if (tAvgStart < 0) { pv *= tExpiry / (tExpiry - tAvgStart); } return(df * pv); }
public void BlackPV_Facts() { Assert.Equal("Could not parse call or put flag - blah", BlackFunctions.BlackPV(1.0, 1.0, 1, 1, 1, "blah")); Assert.Equal(0.0, BlackFunctions.BlackPV(0.1, 1.0, 0.5, 0.0, 0.0, "C")); }