From 6f2e54aafad234ad477f4374740fac3f0abbabb7 Mon Sep 17 00:00:00 2001 From: Gavin Brennan Date: Wed, 30 Nov 2022 08:33:56 +0200 Subject: [PATCH] VaR clean up --- src/Qwack.Models/Risk/VaR/McVaRCalculator.cs | 144 +++++++++++++ .../Risk/{ => VaR}/VaRCalculator.cs | 197 ++---------------- .../{McVaRCalculator.cs => VaR/VaREngine.cs} | 188 +++++------------ version.props | 2 +- version.txt | 2 +- 5 files changed, 214 insertions(+), 319 deletions(-) create mode 100644 src/Qwack.Models/Risk/VaR/McVaRCalculator.cs rename src/Qwack.Models/Risk/{ => VaR}/VaRCalculator.cs (53%) rename src/Qwack.Models/Risk/{McVaRCalculator.cs => VaR/VaREngine.cs} (54%) diff --git a/src/Qwack.Models/Risk/VaR/McVaRCalculator.cs b/src/Qwack.Models/Risk/VaR/McVaRCalculator.cs new file mode 100644 index 00000000..31efd798 --- /dev/null +++ b/src/Qwack.Models/Risk/VaR/McVaRCalculator.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using Qwack.Core.Basic; +using Qwack.Core.Cubes; +using Qwack.Core.Instruments; +using Qwack.Core.Models; +using Qwack.Dates; +using Qwack.Futures; +using Qwack.Models.MCModels; +using Qwack.Models.Risk.Mutators; +using Qwack.Options.VolSurfaces; +using Qwack.Transport.Results; + +namespace Qwack.Models.Risk.VaR +{ + public class McVaRCalculator + { + private readonly IAssetFxModel _model; + private readonly Portfolio _portfolio; + private ICube _basePvCube; + private readonly ILogger _logger; + private readonly ICurrencyProvider _currencyProvider; + private readonly ICalendarProvider _calendarProvider; + private readonly IFutureSettingsProvider _futureSettingsProvider; + private readonly McModelType _modelType; + private readonly List _bumpedModels = new(); + private readonly Dictionary _spotFactors = new(); + private readonly Dictionary _returns = new(); + private int _ciIx = 0; + private VaREngine _varEngine; + + public int CIIX => _ciIx; + + public McVaRCalculator(IAssetFxModel model, Portfolio portfolio, ILogger logger, ICurrencyProvider currencyProvider, + ICalendarProvider calendarProvider, IFutureSettingsProvider futureSettingsProvider, McModelType modelType) + { + _model = model.Clone(); + _portfolio = portfolio; + _logger = logger; + _currencyProvider = currencyProvider; + _calendarProvider = calendarProvider; + _futureSettingsProvider = futureSettingsProvider; + _modelType = modelType; + } + + public void AddSpotFactor(string assetId, double vol) + { + _spotFactors[assetId] = vol; + } + + public void AddReturns(string assetId, double[] returns) + { + _returns[assetId] = returns; + } + + public string[] GetSpotFactors() => _spotFactors.Keys.OrderBy(x => x).ToArray(); + + public void SetCorrelationMatrix(ICorrelationMatrix matrix) => _model.CorrelationMatrix = matrix; + + public void CalculateModels() + { + //var allAssetIds = _portfolio.AssetIds().Concat(_portfolio.Instruments.Select(x => x.Currency.Ccy).Where(x => x != "USD").Select(x =>$"USD/{x}")).ToArray(); + var allAssetIds = _model.CurveNames.Concat(_portfolio.Instruments.Select(x => x.Currency.Ccy).Where(x => x != "USD").Select(x => $"USD/{x}")).ToArray(); + var simulatedIds = allAssetIds.Intersect(_spotFactors.Keys).ToArray(); + + foreach (var simulatedId in simulatedIds) + { + var surf = new ConstantVolSurface(_model.BuildDate, _spotFactors[simulatedId]) { AssetId = simulatedId }; + if (_returns.TryGetValue(simulatedId, out var returns)) + surf.Returns = returns; + _model.AddVolSurface(simulatedId, surf); + if (simulatedId.Length == 6 && simulatedId[3] == '/') + { + _model.FundingModel.VolSurfaces.Add(simulatedId, surf); + } + } + + _logger.LogInformation("Simulating {nFac} spot factors", simulatedIds.Length); + + var mcSettings = new McSettings + { + McModelType = _modelType, + Generator = RandomGeneratorType.MersenneTwister, + NumberOfPaths = 2048, + NumberOfTimesteps = 2, + ReportingCurrency = _currencyProvider.GetCurrencySafe("USD") + }; + + var vd = _model.BuildDate.AddDays(1); + var fp = new FactorReturnPayoff(simulatedIds, new DateTime[] { _model.BuildDate, vd }); + + var mcModel = new AssetFxMCModel(_model.BuildDate, fp, _model, mcSettings, _currencyProvider, _futureSettingsProvider, _calendarProvider); + mcModel.Engine.RunProcess(); + + var dix = fp.DateIndices[vd]; + for (var p = 0; p < mcSettings.NumberOfPaths; p++) + { + var pModel = _model.Clone(); + for (var a = 0; a < simulatedIds.Length; a++) + { + var price0 = fp.ResultsByPath[a][p][0]; + var price1 = fp.ResultsByPath[a][p][dix]; + var bump = price1 / price0 - 1.0; + + if (IsFx(simulatedIds[a])) + pModel = RelativeShiftMutator.FxSpotShift(_currencyProvider.GetCurrencySafe(simulatedIds[a].Split('/').Last()), bump, pModel); + else + pModel = RelativeShiftMutator.AssetCurveShift(simulatedIds[a], bump, pModel); + } + + _bumpedModels.Add(pModel); + } + + _varEngine = new VaREngine(_logger, _model, _portfolio, _bumpedModels.Select((x, ix) => (ix, x)).ToDictionary(kv => kv.ix.ToString(), kv => kv.x)); + } + + private static bool IsFx(string assetId) => assetId.Length == 7 && assetId[3] == '/'; + + public (double VaR, string ScenarioId, double cVaR) CalculateVaR(double ci, Currency ccy, string[] excludeTradeIds) + => _varEngine.CalculateVaR(ci, ccy, excludeTradeIds); + + public (double VaR, string ScenarioId, double cVaR) CalculateVaRInc(double ci, Currency ccy, string[] includeTradeIds) + => _varEngine.CalculateVaRInc(ci, ccy, includeTradeIds); + + public double[] CalculateVaRRange(double[] cis) => _varEngine.CalculateVaRRange(cis); + + public Dictionary GetBaseValuations() => _varEngine.GetBaseValuations(); + + public Dictionary GetContributions(int ix) => _varEngine.GetContributions(ix.ToString()); + + public decimal ComputeStress(string insId, decimal shockSize, int? nNearestSamples = null) + => _varEngine.ComputeStress(insId, shockSize, nNearestSamples); + + public StressTestResult ComputeStressObject(string insId, decimal shockSize, int? nNearestSamples = null) + => _varEngine.ComputeStressObject(insId, shockSize, nNearestSamples); + + public (double VaR, string ScenarioId, double cVaR) CalculateVaR(double ci, Currency ccy) => CalculateVaR(ci, ccy, _portfolio); + + public (double VaR, string ScenarioId, double cVaR) CalculateVaR(double ci, Currency ccy, Portfolio pf, bool parallelize = true) + => _varEngine.CalculateVaR(ci, ccy, pf, parallelize); + } +} diff --git a/src/Qwack.Models/Risk/VaRCalculator.cs b/src/Qwack.Models/Risk/VaR/VaRCalculator.cs similarity index 53% rename from src/Qwack.Models/Risk/VaRCalculator.cs rename to src/Qwack.Models/Risk/VaR/VaRCalculator.cs index f78f6ba8..1f32d6a3 100644 --- a/src/Qwack.Models/Risk/VaRCalculator.cs +++ b/src/Qwack.Models/Risk/VaR/VaRCalculator.cs @@ -16,7 +16,7 @@ using Qwack.Transport.Results; using Qwack.Utils.Parallel; -namespace Qwack.Models.Risk +namespace Qwack.Models.Risk.VaR { public class VaRCalculator { @@ -28,6 +28,7 @@ public class VaRCalculator private readonly Dictionary _curveTypeBumps = new(); private readonly Dictionary _surfaceTypeBumps = new(); private readonly Dictionary _bumpedModels = new(); + private VaREngine _varEngine; public VaRCalculator(IAssetFxModel model, Portfolio portfolio, ILogger logger) { @@ -131,7 +132,7 @@ public void CalculateModels() if (_spotTypeBumps.Any()) { - foreach(var d in _spotTypeBumps.First().Value.Bumps.Keys) + foreach (var d in _spotTypeBumps.First().Value.Bumps.Keys) { allDatesSet.Add(d); } @@ -212,67 +213,24 @@ public void CalculateModels() foreach (var kv in bumpedModels) _bumpedModels[kv.Key] = kv.Value; - } - public (double VaR, DateTime ScenarioDate, double cVaR) CalculateVaR(double ci, Currency ccy, string[] excludeTradeIds) - { - if (!_resultsCache.Any()) - { - var pf = _portfolio.Clone(); - pf.Instruments.RemoveAll(i => excludeTradeIds.Contains(i.TradeId)); - return CalculateVaR(ci, ccy, pf); - } - else - { - var filterDict = excludeTradeIds.Select(x => new KeyValuePair("TradeId", (object)x)).ToList(); - var results = _resultsCache.ToDictionary(x => x.Key, x => x.Value.Filter(filterDict, true).SumOfAllRows); - var sortedResults = results.OrderBy(kv => kv.Value).ToList(); - var ixCi = (int)System.Math.Floor(sortedResults.Count() * (1.0 - ci)); - var ciResult = sortedResults[ixCi]; - var cVaR = sortedResults.Take(System.Math.Max(ixCi - 1, 0)).Average(x => x.Value); - var basePvForSet = _basePvCube.Filter(filterDict, true).SumOfAllRows; - return (ciResult.Value - basePvForSet, ciResult.Key, cVaR - basePvForSet); - } - } - public (double VaR, DateTime ScenarioDate, double cVaR) CalculateVaRInc(double ci, Currency ccy, string[] includeTradeIds) - { - if (!_resultsCache.Any()) - { - var pf = _portfolio.Clone(); - pf.Instruments.RemoveAll(i => !includeTradeIds.Contains(i.TradeId)); - return CalculateVaR(ci, ccy, pf); - } - else - { - var filterDict = includeTradeIds.Select(x => new KeyValuePair("TradeId", (object)x)).ToList(); - var results = _resultsCache.ToDictionary(x => x.Key, x => x.Value.Filter(filterDict, false).SumOfAllRows); - var sortedResults = results.OrderBy(kv => kv.Value).ToList(); - var ixCi = (int)System.Math.Floor(sortedResults.Count() * (1.0 - ci)); - ixCi = System.Math.Min(System.Math.Max(ixCi, 0), sortedResults.Count - 1); - var ciResult = sortedResults[ixCi]; - var cVaR = sortedResults.Take(System.Math.Max(ixCi - 1, 0)).Average(x => x.Value); - var basePvForSet = _basePvCube.Filter(filterDict, false).SumOfAllRows; - return (ciResult.Value - basePvForSet, ciResult.Key, cVaR - basePvForSet); - } + _varEngine = new VaREngine(_logger, _model, _portfolio, _bumpedModels.ToDictionary(kv => kv.Key.ToString(), kv => kv.Value)); } + public (double VaR, string ScenarioId, double cVaR) CalculateVaR(double ci, Currency ccy, string[] excludeTradeIds) + => _varEngine.CalculateVaR(ci, ccy, excludeTradeIds); - public double[] CalculateVaRRange(double[] cis) - { - var results = _resultsCache.ToDictionary(x => x.Key, x => x.Value.SumOfAllRows); - var sortedResults = results.OrderBy(kv => kv.Value).ToList(); - var ixCis = cis.Select(ci => (int)System.Math.Floor(sortedResults.Count() * (1.0 - ci))).ToArray(); - var ciResults = ixCis.Select(ixCi => sortedResults[System.Math.Min(System.Math.Max(ixCi, 0), sortedResults.Count - 1)]); - var basePvForSet = _basePvCube.SumOfAllRows; - return ciResults.Select(x => x.Value - basePvForSet).ToArray(); - } + public (double VaR, string ScenarioId, double cVaR) CalculateVaRInc(double ci, Currency ccy, string[] includeTradeIds) + => _varEngine.CalculateVaRInc(ci, ccy, includeTradeIds); + + public double[] CalculateVaRRange(double[] cis) => _varEngine.CalculateVaRRange(cis); public BetaAnalysisResult ComputeBeta(double referenceNav, Dictionary benchmarkPrices, DateTime? earliestDate = null) { if (!earliestDate.HasValue) earliestDate = DateTime.MinValue; - var basePv = _basePvCube.SumOfAllRows; - var results = _resultsCache.ToDictionary(x => x.Key, x => x.Value.SumOfAllRows - basePv + referenceNav); + var basePv = _varEngine.BasePvCube.SumOfAllRows; + var results = _varEngine.ResultsCache.ToDictionary(x => DateTime.Parse(x.Key), x => x.Value.SumOfAllRows - basePv + referenceNav); var intersectingDates = results.Keys.Intersect(benchmarkPrices.Keys).Where(x => x > earliestDate).OrderBy(x => x).ToArray(); var pfReturns = new List(); var benchmarkReturns = new List(); @@ -284,7 +242,7 @@ public BetaAnalysisResult ComputeBeta(double referenceNav, Dictionary (x.Value.SumOfAllRows, _bumpedModels[x.Key].GetPriceCurve(insId).GetPriceForFixingDate(_model.BuildDate))) - .OrderBy(x=>x.Item2) - .ToList(); - - LinearRegressionResult lr; - - if (nNearestSamples.HasValue) - { - var shockDbl = Convert.ToDouble(shockSize); - var subset = allScenarios - .OrderBy(x => System.Math.Abs(x.Item2 - shockDbl)) - .Take(nNearestSamples.Value) - .OrderBy(x => x.Item2) - .ToArray(); - lr = LinearRegression.LinearRegressionNoVector(subset.Select(x => x.Item2).ToArray(), subset.Select(x => x.SumOfAllRows).ToArray(), false); - } - else - lr = LinearRegression.LinearRegressionNoVector(allScenarios.Select(x => x.Item2).ToArray(), allScenarios.Select(x => x.SumOfAllRows).ToArray(), false); + public Dictionary GetBaseValuations() => _varEngine.GetBaseValuations(); - var interp = lr.Alpha + lr.Beta * shockedLevel; - - return Convert.ToDecimal(interp - basePv); - } + public Dictionary GetContributions(DateTime scenarioDate) => _varEngine.GetContributions(scenarioDate.ToString()); + public decimal ComputeStress(string insId, decimal shockSize, int? nNearestSamples = null) + => _varEngine.ComputeStress(insId, shockSize, nNearestSamples); public StressTestResult ComputeStressObject(string insId, decimal shockSize, int? nNearestSamples = null) - { - var basePv = _basePvCube.SumOfAllRows; - var baseLevel = _model.GetPriceCurve(insId).GetPriceForFixingDate(_model.BuildDate); - var shockedLevel = baseLevel * Convert.ToDouble(1 + shockSize); + => _varEngine.ComputeStressObject(insId, shockSize, nNearestSamples); - var allScenarios = _resultsCache - .Select(x => (x.Value.SumOfAllRows, _bumpedModels[x.Key].GetPriceCurve(insId).GetPriceForFixingDate(_model.BuildDate))) - .OrderBy(x => x.Item2) - .ToList(); + public (double VaR, string ScenarioId, double cVaR) CalculateVaR(double ci, Currency ccy) => CalculateVaR(ci, ccy, _portfolio); - LinearRegressionResult lr; - - if (nNearestSamples.HasValue) - { - var shockDbl = Convert.ToDouble(shockSize); - var subset = allScenarios - .OrderBy(x => System.Math.Abs(x.Item2 - shockDbl)) - .Take(nNearestSamples.Value) - .OrderBy(x => x.Item2) - .ToArray(); - lr = LinearRegression.LinearRegressionNoVector(subset.Select(x => x.Item2).ToArray(), subset.Select(x => x.SumOfAllRows).ToArray(), false); - } - else - lr = LinearRegression.LinearRegressionNoVector(allScenarios.Select(x => x.Item2).ToArray(), allScenarios.Select(x => x.SumOfAllRows).ToArray(), false); - - var interp = lr.Alpha + lr.Beta * shockedLevel; - - var scenarioPoints = new Dictionary(); - foreach(var kv in allScenarios) - { - scenarioPoints[kv.Item2] = kv.SumOfAllRows - basePv; - } - - return new StressTestResult - { - Id = insId, - StressSize = Convert.ToDouble(shockSize), - LR = lr, - ScenarioPoints = scenarioPoints, - StressPvChange = Convert.ToDecimal(interp - basePv) - }; - } - - public Dictionary GetBaseValuations() => _basePvCube.Pivot("TradeId", AggregationAction.Sum).ToDictionary("TradeId").ToDictionary(x => x.Key as string, x => x.Value.Sum(r => r.Value)); - - public Dictionary GetContributions(DateTime scenarioDate) - { - var cube = _resultsCache[scenarioDate]; - var diff = cube.Difference(_basePvCube); - - return diff.Pivot("TradeId", AggregationAction.Sum).ToDictionary("TradeId").ToDictionary(x => x.Key as string, x => x.Value.Sum(r => r.Value)); - } - - public (double VaR, DateTime ScenarioDate, double cVaR) CalculateVaR(double ci, Currency ccy) => CalculateVaR(ci, ccy, _portfolio); - - private readonly ConcurrentDictionary _resultsCache = new(); - private ICube _basePvCube; - public (double VaR, DateTime ScenarioDate, double cVaR) CalculateVaR(double ci, Currency ccy, Portfolio pf, bool parallelize = true) - { - _basePvCube = pf.PV(_model, ccy); - var basePv = _basePvCube.SumOfAllRows; - _resultsCache.Clear(); - var results = new ConcurrentDictionary(); - var varFunc = new Action((d, m) => - { - var cube = pf.PV(m, ccy, false); - var scenarioPv = cube.SumOfAllRows; - _resultsCache[d] = cube; - results[d] = scenarioPv - basePv; - }); - if (parallelize) - { - Parallel.ForEach(_bumpedModels, kv => - { - varFunc(kv.Key, kv.Value); - }); - } - else - { - foreach (var kv in _bumpedModels) - { - varFunc(kv.Key, kv.Value); - } - } - - var sortedResults = results.OrderBy(kv => kv.Value).ToList(); - var ixCi = (int)System.Math.Floor(sortedResults.Count() * (1.0 - ci)); - ixCi = System.Math.Min(System.Math.Max(ixCi, 0), sortedResults.Count - 1); - var ciResult = sortedResults[ixCi]; - var cVaR = sortedResults.Take(System.Math.Max(ixCi - 1, 0)).Average(x => x.Value); - return (ciResult.Value, ciResult.Key, cVaR); - } + public (double VaR, string ScenarioId, double cVaR) CalculateVaR(double ci, Currency ccy, Portfolio pf, bool parallelize = true) + => _varEngine.CalculateVaR(ci, ccy, pf, parallelize); internal class VaRSpotScenarios { @@ -435,6 +282,4 @@ internal class VaRCurveScenarios public Dictionary Bumps { get; set; } } } - - } diff --git a/src/Qwack.Models/Risk/McVaRCalculator.cs b/src/Qwack.Models/Risk/VaR/VaREngine.cs similarity index 54% rename from src/Qwack.Models/Risk/McVaRCalculator.cs rename to src/Qwack.Models/Risk/VaR/VaREngine.cs index ed97be72..3622bac4 100644 --- a/src/Qwack.Models/Risk/McVaRCalculator.cs +++ b/src/Qwack.Models/Risk/VaR/VaREngine.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers.Text; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -8,118 +9,37 @@ using Qwack.Core.Cubes; using Qwack.Core.Instruments; using Qwack.Core.Models; -using Qwack.Dates; -using Qwack.Futures; using Qwack.Math; -using Qwack.Models.MCModels; +using Qwack.Math.Interpolation; using Qwack.Models.Models; using Qwack.Models.Risk.Mutators; -using Qwack.Options.VolSurfaces; using Qwack.Transport.Results; using Qwack.Utils.Parallel; namespace Qwack.Models.Risk { - public class McVaRCalculator + public class VaREngine { + + private readonly ILogger _logger; private readonly IAssetFxModel _model; private readonly Portfolio _portfolio; - private readonly ILogger _logger; - private readonly ICurrencyProvider _currencyProvider; - private readonly ICalendarProvider _calendarProvider; - private readonly IFutureSettingsProvider _futureSettingsProvider; - private readonly McModelType _modelType; - private readonly List _bumpedModels = new(); - private readonly Dictionary _spotFactors = new(); - private readonly Dictionary _returns = new(); - private int _ciIx = 0; - - public int CIIX => _ciIx; + private readonly Dictionary _bumpedModels = new(); + private readonly ConcurrentDictionary _resultsCache = new(); + private ICube _basePvCube; - public McVaRCalculator(IAssetFxModel model, Portfolio portfolio, ILogger logger, ICurrencyProvider currencyProvider, - ICalendarProvider calendarProvider, IFutureSettingsProvider futureSettingsProvider, McModelType modelType) + public VaREngine(ILogger logger, IAssetFxModel baseModel, Portfolio portfolio, Dictionary bumpedModels) { - _model = model.Clone(); - _portfolio = portfolio; _logger = logger; - _currencyProvider = currencyProvider; - _calendarProvider = calendarProvider; - _futureSettingsProvider = futureSettingsProvider; - _modelType = modelType; - } - - public void AddSpotFactor(string assetId, double vol) - { - _spotFactors[assetId] = vol; - } - - public void AddReturns(string assetId, double[] returns) - { - _returns[assetId] = returns; - } - - public string[] GetSpotFactors() => _spotFactors.Keys.OrderBy(x => x).ToArray(); - - public void SetCorrelationMatrix(ICorrelationMatrix matrix) => _model.CorrelationMatrix = matrix; - - public void CalculateModels() - { - //var allAssetIds = _portfolio.AssetIds().Concat(_portfolio.Instruments.Select(x => x.Currency.Ccy).Where(x => x != "USD").Select(x =>$"USD/{x}")).ToArray(); - var allAssetIds = _model.CurveNames.Concat(_portfolio.Instruments.Select(x => x.Currency.Ccy).Where(x => x != "USD").Select(x => $"USD/{x}")).ToArray(); - var simulatedIds = allAssetIds.Intersect(_spotFactors.Keys).ToArray(); - - foreach(var simulatedId in simulatedIds) - { - var surf = new ConstantVolSurface(_model.BuildDate, _spotFactors[simulatedId]) { AssetId = simulatedId }; - if (_returns.TryGetValue(simulatedId, out var returns)) - surf.Returns = returns; - _model.AddVolSurface(simulatedId, surf); - if(simulatedId.Length==6 && simulatedId[3] == '/') - { - _model.FundingModel.VolSurfaces.Add(simulatedId, surf); - } - } - - _logger.LogInformation("Simulating {nFac} spot factors", simulatedIds.Length); - - var mcSettings = new McSettings - { - McModelType= _modelType, - Generator=RandomGeneratorType.MersenneTwister, - NumberOfPaths=2048, - NumberOfTimesteps=2, - ReportingCurrency=_currencyProvider.GetCurrencySafe("USD") - }; - - var vd = _model.BuildDate.AddDays(1); - var fp = new FactorReturnPayoff(simulatedIds, new DateTime[] { _model.BuildDate, vd }); - - var mcModel = new AssetFxMCModel(_model.BuildDate, fp, _model, mcSettings, _currencyProvider, _futureSettingsProvider, _calendarProvider); - mcModel.Engine.RunProcess(); - - var dix = fp.DateIndices[vd]; - for (var p = 0; p < mcSettings.NumberOfPaths; p++) - { - var pModel = _model.Clone(); - for (var a = 0; a < simulatedIds.Length; a++) - { - var price0 = fp.ResultsByPath[a][p][0]; - var price1 = fp.ResultsByPath[a][p][dix]; - var bump = price1 / price0 - 1.0; - - if (IsFx(simulatedIds[a])) - pModel = RelativeShiftMutator.FxSpotShift(_currencyProvider.GetCurrencySafe(simulatedIds[a].Split('/').Last()), bump, pModel); - else - pModel = RelativeShiftMutator.AssetCurveShift(simulatedIds[a], bump, pModel); - } - - _bumpedModels.Add(pModel); - } + _model = baseModel; + _portfolio = portfolio; + _bumpedModels = bumpedModels; } - private static bool IsFx(string assetId) => assetId.Length == 7 && assetId[3] == '/'; + public Dictionary ResultsCache => _resultsCache.ToDictionary(x => x.Key, x => x.Value); + public ICube BasePvCube => _basePvCube; - public double CalculateVaR(double ci, Currency ccy, string[] excludeTradeIds) + public (double VaR, string ScenarioId, double cVaR) CalculateVaR(double ci, Currency ccy, string[] excludeTradeIds) { if (!_resultsCache.Any()) { @@ -133,14 +53,14 @@ public double CalculateVaR(double ci, Currency ccy, string[] excludeTradeIds) var results = _resultsCache.ToDictionary(x => x.Key, x => x.Value.Filter(filterDict, true).SumOfAllRows); var sortedResults = results.OrderBy(kv => kv.Value).ToList(); var ixCi = (int)System.Math.Floor(sortedResults.Count() * (1.0 - ci)); - _ciIx = ixCi; - var ciResult = sortedResults[System.Math.Min(System.Math.Max(ixCi, 0), sortedResults.Count - 1)]; + var ciResult = sortedResults[ixCi]; + var cVaR = sortedResults.Take(System.Math.Max(ixCi - 1, 0)).Average(x => x.Value); var basePvForSet = _basePvCube.Filter(filterDict, true).SumOfAllRows; - return ciResult.Value - basePvForSet; + return (ciResult.Value - basePvForSet, ciResult.Key, cVaR - basePvForSet); } } - public double CalculateVaRInc(double ci, Currency ccy, string[] includeTradeIds) + public (double VaR, string ScenarioId, double cVaR) CalculateVaRInc(double ci, Currency ccy, string[] includeTradeIds) { if (!_resultsCache.Any()) { @@ -154,9 +74,11 @@ public double CalculateVaRInc(double ci, Currency ccy, string[] includeTradeIds) var results = _resultsCache.ToDictionary(x => x.Key, x => x.Value.Filter(filterDict, false).SumOfAllRows); var sortedResults = results.OrderBy(kv => kv.Value).ToList(); var ixCi = (int)System.Math.Floor(sortedResults.Count() * (1.0 - ci)); - var ciResult = sortedResults[System.Math.Min(System.Math.Max(ixCi, 0), sortedResults.Count - 1)]; + ixCi = System.Math.Min(System.Math.Max(ixCi, 0), sortedResults.Count - 1); + var ciResult = sortedResults[ixCi]; + var cVaR = sortedResults.Take(System.Math.Max(ixCi - 1, 0)).Average(x => x.Value); var basePvForSet = _basePvCube.Filter(filterDict, false).SumOfAllRows; - return ciResult.Value - basePvForSet; + return (ciResult.Value - basePvForSet, ciResult.Key, cVaR - basePvForSet); } } @@ -170,16 +92,6 @@ public double[] CalculateVaRRange(double[] cis) return ciResults.Select(x => x.Value - basePvForSet).ToArray(); } - public Dictionary GetBaseValuations() => _basePvCube.Pivot("TradeId", AggregationAction.Sum).ToDictionary("TradeId").ToDictionary(x => x.Key as string, x => x.Value.Sum(r => r.Value)); - - public Dictionary GetContributions(int ix) - { - var cube = _resultsCache[ix]; - var diff = cube.Difference(_basePvCube); - - return diff.Pivot("TradeId", AggregationAction.Sum).ToDictionary("TradeId").ToDictionary(x => x.Key as string, x => x.Value.Sum(r => r.Value)); - } - public decimal ComputeStress(string insId, decimal shockSize, int? nNearestSamples = null) { var basePv = _basePvCube.SumOfAllRows; @@ -188,7 +100,7 @@ public decimal ComputeStress(string insId, decimal shockSize, int? nNearestSampl var allScenarios = _resultsCache .Select(x => (x.Value.SumOfAllRows, _bumpedModels[x.Key].GetPriceCurve(insId).GetPriceForFixingDate(_model.BuildDate))) - .OrderBy(x => x.Item2) + .OrderBy(x=>x.Item2) .ToList(); LinearRegressionResult lr; @@ -240,7 +152,7 @@ public StressTestResult ComputeStressObject(string insId, decimal shockSize, int var interp = lr.Alpha + lr.Beta * shockedLevel; var scenarioPoints = new Dictionary(); - foreach (var kv in allScenarios) + foreach(var kv in allScenarios) { scenarioPoints[kv.Item2] = kv.SumOfAllRows - basePv; } @@ -255,17 +167,25 @@ public StressTestResult ComputeStressObject(string insId, decimal shockSize, int }; } - public double CalculateVaR(double ci, Currency ccy) => CalculateVaR(ci, ccy, _portfolio); + public Dictionary GetBaseValuations() => _basePvCube.Pivot("TradeId", AggregationAction.Sum).ToDictionary("TradeId").ToDictionary(x => x.Key as string, x => x.Value.Sum(r => r.Value)); - private readonly ConcurrentDictionary _resultsCache = new(); - private ICube _basePvCube; - public double CalculateVaR(double ci, Currency ccy, Portfolio pf, bool parallelize = true) + public Dictionary GetContributions(string scenarioId) + { + var cube = _resultsCache[scenarioId]; + var diff = cube.Difference(_basePvCube); + + return diff.Pivot("TradeId", AggregationAction.Sum).ToDictionary("TradeId").ToDictionary(x => x.Key as string, x => x.Value.Sum(r => r.Value)); + } + + public (double VaR, string ScenarioId, double cVaR) CalculateVaR(double ci, Currency ccy) => CalculateVaR(ci, ccy, _portfolio); + + public (double VaR, string ScenarioId, double cVaR) CalculateVaR(double ci, Currency ccy, Portfolio pf, bool parallelize = true) { _basePvCube = pf.PV(_model, ccy); var basePv = _basePvCube.SumOfAllRows; _resultsCache.Clear(); - var results = new ConcurrentDictionary(); - var varFunc = new Action((d, m) => + var results = new ConcurrentDictionary(); + var varFunc = new Action((d, m) => { var cube = pf.PV(m, ccy, false); var scenarioPv = cube.SumOfAllRows; @@ -274,39 +194,25 @@ public double CalculateVaR(double ci, Currency ccy, Portfolio pf, bool paralleli }); if (parallelize) { - Parallel.For(0, _bumpedModels.Count, ix => + Parallel.ForEach(_bumpedModels, kv => { - varFunc(ix, _bumpedModels[ix]); + varFunc(kv.Key, kv.Value); }); } else { - for (var ix=0; ix<_bumpedModels.Count; ix++) + foreach (var kv in _bumpedModels) { - varFunc(ix, _bumpedModels[ix]); + varFunc(kv.Key, kv.Value); } } var sortedResults = results.OrderBy(kv => kv.Value).ToList(); var ixCi = (int)System.Math.Floor(sortedResults.Count() * (1.0 - ci)); - var ciResult = sortedResults[System.Math.Min(System.Math.Max(ixCi, 0), sortedResults.Count - 1)]; - return ciResult.Value; - } - - internal class VaRSpotScenarios - { - public bool IsRelativeBump { get; set; } - public string AssetId { get; set; } - public Dictionary Bumps { get; set; } - } - - internal class VaRCurveScenarios - { - public bool IsRelativeBump { get; set; } - public string AssetId { get; set; } - public Dictionary Bumps { get; set; } + ixCi = System.Math.Min(System.Math.Max(ixCi, 0), sortedResults.Count - 1); + var ciResult = sortedResults[ixCi]; + var cVaR = sortedResults.Take(System.Math.Max(ixCi - 1, 0)).Average(x => x.Value); + return (ciResult.Value, ciResult.Key, cVaR); } } - - } diff --git a/version.props b/version.props index 4d7da761..069a3e6e 100644 --- a/version.props +++ b/version.props @@ -1,5 +1,5 @@ - 0.7.12 + 0.7.13 diff --git a/version.txt b/version.txt index 6f30e954..ff70965e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.7.12 \ No newline at end of file +0.7.13 \ No newline at end of file