public void TSMC_PathsGenerated() { var origin = new DateTime(2019, 06, 28); var engine = new PathEngine(2.IntPow(IsCoverageOnly ? 6 : 17)); engine.AddPathProcess(new Random.MersenneTwister.MersenneTwister64() { UseNormalInverse = true, UseAnthithetic = 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, StrikeType.ForwardDelta, Interpolator1DType.GaussianKernel, Interpolator1DType.LinearInVariance, DayCountBasis.Act365F); var fwdCurve = new Func <double, double>(t => { return(900 + 100 * t); }); var asset = new TurboSkewSingleAsset ( startDate: origin, expiryDate: origin.AddYears(1), volSurface: volSurface, forwardCurve: fwdCurve, nTimeSteps: 1, 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); if (!IsCoverageOnly) { Assert.Equal(blackPv, pv, 0); var fwd = payoff2.AverageResult; Assert.True(System.Math.Abs(fwdCurve(1) / fwd - 1.0) < 0.002); } }
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 AssetFxMCModel(DateTime originDate, Portfolio portfolio, IAssetFxModel model, McSettings settings, ICurrencyProvider currencyProvider, IFutureSettingsProvider futureSettingsProvider, ICalendarProvider calendarProvider) { if (settings.CompactMemoryMode && settings.AveragePathCorrection) { throw new Exception("Can't use both CompactMemoryMode and PathCorrection"); } _currencyProvider = currencyProvider; _futureSettingsProvider = futureSettingsProvider; _calendarProvider = calendarProvider; Engine = new PathEngine(settings.NumberOfPaths) { Parallelize = settings.Parallelize, CompactMemoryMode = settings.CompactMemoryMode }; OriginDate = originDate; Portfolio = portfolio; Model = model; Settings = settings; switch (settings.Generator) { case RandomGeneratorType.MersenneTwister: Engine.AddPathProcess(new Random.MersenneTwister.MersenneTwister64() { UseNormalInverse = true, UseAnthithetic = false }); break; case RandomGeneratorType.Sobol: var directionNumers = new Random.Sobol.SobolDirectionNumbers(GetSobolFilename()); Engine.AddPathProcess(new Random.Sobol.SobolPathGenerator(directionNumers, 1000) { UseNormalInverse = true }); break; case RandomGeneratorType.Constant: Engine.AddPathProcess(new Random.Constant.Constant() { UseNormalInverse = true, }); break; case RandomGeneratorType.FlipFlop: Engine.AddPathProcess(new Random.Constant.FlipFlop(settings.CreditSettings.ConfidenceInterval, true)); break; } Engine.IncrementDepth(); if (model.CorrelationMatrix != null) { if (settings.LocalCorrelation) { Engine.AddPathProcess(new CholeskyWithTime(model.CorrelationMatrix, model)); } else { Engine.AddPathProcess(new Cholesky(model.CorrelationMatrix)); } Engine.IncrementDepth(); } var lastDate = portfolio.LastSensitivityDate; var assetIds = portfolio.AssetIds(); var assetInstruments = portfolio.Instruments .Where(x => x is IAssetInstrument) .Select(x => x as IAssetInstrument); var fixingsNeeded = new Dictionary <string, List <DateTime> >(); foreach (var ins in assetInstruments) { var fixingsForIns = ins.PastFixingDates(originDate); if (fixingsForIns.Any()) { foreach (var kv in fixingsForIns) { if (!fixingsNeeded.ContainsKey(kv.Key)) { fixingsNeeded.Add(kv.Key, new List <DateTime>()); } fixingsNeeded[kv.Key] = fixingsNeeded[kv.Key].Concat(kv.Value).Distinct().ToList(); } } } //asset processes var fxAssetsToAdd = new List <string>(); var corrections = new Dictionary <string, SimpleAveragePathCorrector>(); foreach (var assetId in assetIds) { if (assetId.Length == 7 && assetId[3] == '/') { fxAssetsToAdd.Add(assetId); continue; } if (!(model.GetVolSurface(assetId) is IATMVolSurface surface)) { throw new Exception($"Vol surface for asset {assetId} could not be cast to IATMVolSurface"); } var fixingDict = fixingsNeeded.ContainsKey(assetId) ? model.GetFixingDictionary(assetId) : null; var fixings = fixingDict != null ? fixingsNeeded[assetId].ToDictionary(x => x, x => fixingDict.GetFixing(x)) : new Dictionary <DateTime, double>(); var futuresSim = settings.ExpensiveFuturesSimulation && (model.GetPriceCurve(assetId).CurveType == PriceCurveType.ICE || model.GetPriceCurve(assetId).CurveType == PriceCurveType.NYMEX); if (futuresSim) { var fwdCurve = new Func <DateTime, double>(t => { return(model.GetPriceCurve(assetId).GetPriceForDate(t)); }); var asset = new BlackFuturesCurve ( startDate: originDate, expiryDate: lastDate, volSurface: surface, forwardCurve: fwdCurve, nTimeSteps: settings.NumberOfTimesteps, name: Settings.FuturesMappingTable[assetId], pastFixings: fixings, futureSettingsProvider: _futureSettingsProvider ); Engine.AddPathProcess(asset); } else { var fwdCurve = new Func <double, double>(t => { var c = model.GetPriceCurve(assetId); var d = originDate.AddYearFraction(t, DayCountBasis.ACT365F); if (c is PriceCurve pc) { d = d.AddPeriod(RollType.F, pc.SpotCalendar, pc.SpotLag); } else if (c is ContangoPriceCurve cc) { d = d.AddPeriod(RollType.F, cc.SpotCalendar, cc.SpotLag); } return(c.GetPriceForDate(d)); }); IATMVolSurface adjSurface = null; var correlation = 0.0; if (settings.ReportingCurrency != model.GetPriceCurve(assetId).Currency) { var fxAdjPair = settings.ReportingCurrency + "/" + model.GetPriceCurve(assetId).Currency; var fxAdjPairInv = model.GetPriceCurve(assetId).Currency + "/" + settings.ReportingCurrency; if (!(model.FundingModel.GetVolSurface(fxAdjPair) is IATMVolSurface adjSurface2)) { throw new Exception($"Vol surface for fx pair {fxAdjPair} could not be cast to IATMVolSurface"); } adjSurface = adjSurface2; if (model.CorrelationMatrix != null) { if (model.CorrelationMatrix.TryGetCorrelation(fxAdjPair, assetId, out var correl)) { correlation = correl; } else if (model.CorrelationMatrix.TryGetCorrelation(fxAdjPairInv, assetId, out var correl2)) { correlation = -correl2; } } } if (settings.McModelType == McModelType.LocalVol) { var asset = new LVSingleAsset ( startDate: originDate, expiryDate: lastDate, volSurface: surface, forwardCurve: fwdCurve, nTimeSteps: settings.NumberOfTimesteps, name: assetId, pastFixings: fixings, fxAdjustSurface: adjSurface, fxAssetCorrelation: correlation ); Engine.AddPathProcess(asset); } else if (settings.McModelType == McModelType.TurboSkew) { var asset = new TurboSkewSingleAsset ( startDate: originDate, expiryDate: lastDate, volSurface: surface, forwardCurve: fwdCurve, nTimeSteps: settings.NumberOfTimesteps, name: assetId, pastFixings: fixings, fxAdjustSurface: adjSurface, fxAssetCorrelation: correlation ); Engine.AddPathProcess(asset); } else { var asset = new BlackSingleAsset ( startDate: originDate, expiryDate: lastDate, volSurface: surface, forwardCurve: fwdCurve, nTimeSteps: settings.NumberOfTimesteps, name: assetId, pastFixings: fixings, fxAdjustSurface: adjSurface, fxAssetCorrelation: correlation ); Engine.AddPathProcess(asset); } if (settings.AveragePathCorrection) { corrections.Add(assetId, new SimpleAveragePathCorrector(new SimpleAveragePathCalculator(assetId) { CompactMode = settings.CompactMemoryMode }, surface, fwdCurve, assetId, fixings, adjSurface, correlation)); } } } //fx pairs var pairsAdded = new List <string>(); var fxPairs = portfolio.FxPairs(model).Concat(fxAssetsToAdd); var payoutCcys = portfolio.Instruments.Select(i => i.Currency); if (payoutCcys.Any(p => p != settings.ReportingCurrency)) { var ccysToAdd = payoutCcys.Where(p => p != settings.ReportingCurrency).Distinct(); var pairsToAdd = ccysToAdd.Select(c => $"{c.Ccy}/{settings.ReportingCurrency}"); fxPairs = fxPairs.Concat(pairsToAdd).Distinct(); } foreach (var fxPair in fxPairs) { var fxPairName = fxPair; var pair = fxPairName.FxPairFromString(_currencyProvider, _calendarProvider); if (pairsAdded.Contains(pair.ToString())) { continue; } if (!(model.FundingModel.VolSurfaces[fxPairName] is IATMVolSurface surface)) { throw new Exception($"Vol surface for fx pair {fxPairName} could not be cast to IATMVolSurface"); } var fwdCurve = new Func <double, double>(t => { var date = originDate.AddYearFraction(t, DayCountBasis.ACT365F); var spotDate = pair.SpotDate(date); return(model.FundingModel.GetFxRate(spotDate, fxPairName)); }); pairsAdded.Add(pair.ToString()); if (settings.McModelType == McModelType.LocalVol) { var asset = new LVSingleAsset ( startDate: originDate, expiryDate: lastDate, volSurface: surface, forwardCurve: fwdCurve, nTimeSteps: settings.NumberOfTimesteps, name: fxPairName ); Engine.AddPathProcess(asset); if (settings.AveragePathCorrection) { corrections.Add(fxPairName, new SimpleAveragePathCorrector(new SimpleAveragePathCalculator(fxPairName) { CompactMode = settings.CompactMemoryMode }, surface, fwdCurve, fxPairName)); } } else if (settings.McModelType == McModelType.TurboSkew) { if (fxPairName.Substring(fxPairName.Length - 3, 3) != settings.ReportingCurrency) {//needs to be drift-adjusted var fxAdjPair = settings.ReportingCurrency + "/" + fxPairName.Substring(fxPairName.Length - 3, 3); if (!(model.FundingModel.VolSurfaces[fxAdjPair] is IATMVolSurface adjSurface)) { throw new Exception($"Vol surface for fx pair {fxAdjPair} could not be cast to IATMVolSurface"); } var correlation = fxPair == fxAdjPair ? -1.0 : 0.0; if (correlation != -1.0 && model.CorrelationMatrix != null) { if (model.CorrelationMatrix.TryGetCorrelation(fxAdjPair, fxPair, out var correl)) { correlation = correl; } } var asset = new TurboSkewSingleAsset ( startDate: originDate, expiryDate: lastDate, volSurface: surface, forwardCurve: fwdCurve, nTimeSteps: settings.NumberOfTimesteps, name: fxPairName, fxAdjustSurface: adjSurface, fxAssetCorrelation: correlation ); Engine.AddPathProcess(asset); if (settings.AveragePathCorrection) { corrections.Add(fxPairName, new SimpleAveragePathCorrector(new SimpleAveragePathCalculator(fxPairName) { CompactMode = settings.CompactMemoryMode }, surface, fwdCurve, fxPairName, null, adjSurface, correlation)); } } else { var asset = new TurboSkewSingleAsset ( startDate: originDate, expiryDate: lastDate, volSurface: surface, forwardCurve: fwdCurve, nTimeSteps: settings.NumberOfTimesteps, name: fxPairName ); Engine.AddPathProcess(asset); if (settings.AveragePathCorrection) { corrections.Add(fxPairName, new SimpleAveragePathCorrector(new SimpleAveragePathCalculator(fxPairName) { CompactMode = settings.CompactMemoryMode }, surface, fwdCurve, fxPairName)); } } } else { if (fxPairName.Substring(fxPairName.Length - 3, 3) != settings.ReportingCurrency) {//needs to be drift-adjusted var fxAdjPair = settings.ReportingCurrency + "/" + fxPairName.Substring(fxPairName.Length - 3, 3); if (!(model.FundingModel.VolSurfaces[fxAdjPair] is IATMVolSurface adjSurface)) { throw new Exception($"Vol surface for fx pair {fxAdjPair} could not be cast to IATMVolSurface"); } var correlation = fxPair == fxAdjPair ? -1.0 : 0.0; if (correlation != -1.0 && model.CorrelationMatrix != null) { if (model.CorrelationMatrix.TryGetCorrelation(fxAdjPair, fxPair, out var correl)) { correlation = correl; } } var asset = new BlackSingleAsset ( startDate: originDate, expiryDate: lastDate, volSurface: surface, forwardCurve: fwdCurve, nTimeSteps: settings.NumberOfTimesteps, name: fxPairName, fxAdjustSurface: adjSurface, fxAssetCorrelation: correlation ); Engine.AddPathProcess(asset); if (settings.AveragePathCorrection) { corrections.Add(fxPairName, new SimpleAveragePathCorrector(new SimpleAveragePathCalculator(fxPairName) { CompactMode = settings.CompactMemoryMode }, surface, fwdCurve, fxPairName, null, adjSurface, correlation)); } } else { var asset = new BlackSingleAsset ( startDate: originDate, expiryDate: lastDate, volSurface: surface, forwardCurve: fwdCurve, nTimeSteps: settings.NumberOfTimesteps, name: fxPairName ); Engine.AddPathProcess(asset); if (settings.AveragePathCorrection) { corrections.Add(fxPairName, new SimpleAveragePathCorrector(new SimpleAveragePathCalculator(fxPairName) { CompactMode = settings.CompactMemoryMode }, surface, fwdCurve, fxPairName)); } } } } //apply path correctin if (settings.AveragePathCorrection && corrections.Any()) { Engine.IncrementDepth(); foreach (var pc in corrections) { Engine.AddPathProcess(pc.Value.PathCalc); } Engine.IncrementDepth(); foreach (var pc in corrections) { Engine.AddPathProcess(pc.Value); } } //payoffs Engine.IncrementDepth(); _payoffs = assetInstruments.ToDictionary(x => x.TradeId, y => new AssetPathPayoff(y, _currencyProvider, _calendarProvider, settings.ReportingCurrency)); if (!settings.AvoidRegressionForBackPricing && _payoffs.Any(x => x.Value.Regressors != null)) { var regressorsToAdd = _payoffs.Where(x => x.Value.Regressors != null) .SelectMany(x => x.Value.Regressors) .Distinct(); foreach (var regressor in regressorsToAdd) { Engine.AddPathProcess(regressor); foreach (var payoff in _payoffs.Where(x => x.Value.Regressors != null)) { if (payoff.Value.Regressors.Any(x => x == regressor)) { payoff.Value.SetRegressor(regressor); } } } Engine.IncrementDepth(); } foreach (var product in _payoffs) { if (settings.AvoidRegressionForBackPricing && (product.Value.AssetInstrument is BackPricingOption || product.Value.AssetInstrument is MultiPeriodBackpricingOption)) { product.Value.VanillaModel = VanillaModel; } Engine.AddPathProcess(product.Value); } var metricsNeedRegression = new[] { BaseMetric.PFE, BaseMetric.KVA, BaseMetric.CVA, BaseMetric.FVA, BaseMetric.EPE }; //Need to calculate PFE if (settings.CreditSettings != null && settings.CreditSettings.ExposureDates != null && settings.ReportingCurrency != null && metricsNeedRegression.Contains(settings.CreditSettings.Metric))//setup for PFE, etc { Engine.IncrementDepth(); switch (settings.CreditSettings.PfeRegressorType) { case PFERegressorType.MultiLinear: _regressor = new LinearPortfolioValueRegressor(settings.CreditSettings.ExposureDates, _payoffs.Values.ToArray(), settings); break; case PFERegressorType.MonoLinear: _regressor = new MonoIndexRegressor(settings.CreditSettings.ExposureDates, _payoffs.Values.ToArray(), settings, true); break; } Engine.AddPathProcess(_regressor); } //Need to calculate expected capital if (settings.CreditSettings != null && settings.CreditSettings.ExposureDates != null && settings.ReportingCurrency != null && settings.CreditSettings.Metric == BaseMetric.ExpectedCapital) { Engine.IncrementDepth(); _capitalCalc = new ExpectedCapitalCalculator(Portfolio, settings.CreditSettings.CounterpartyRiskWeighting, settings.CreditSettings.AssetIdToHedgeGroupMap, settings.ReportingCurrency, VanillaModel, settings.CreditSettings.ExposureDates); Engine.AddPathProcess(_capitalCalc); } Engine.SetupFeatures(); }