From ec1fe8757953c222b8086ff5a1eb02ffcf10b22a Mon Sep 17 00:00:00 2001 From: Gavin Brennan Date: Fri, 10 May 2024 13:31:28 +0100 Subject: [PATCH] Fixes for RiskLadder calcs with MC models --- src/Qwack.Core/Cubes/CubeEx.cs | 55 +++++++++++++++++++++++++++ src/Qwack.Models/Risk/BasicMetrics.cs | 2 +- src/Qwack.Models/Risk/RiskLadder.cs | 17 ++++++++- version.props | 2 +- version.txt | 2 +- 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/Qwack.Core/Cubes/CubeEx.cs b/src/Qwack.Core/Cubes/CubeEx.cs index 4ded6c95..46c0d40f 100644 --- a/src/Qwack.Core/Cubes/CubeEx.cs +++ b/src/Qwack.Core/Cubes/CubeEx.cs @@ -50,6 +50,61 @@ public static ICube Difference(this ICube baseCube, ICube cubeToSubtract) return o; } + public static ICube Difference(this ICube baseCube, ICube cubeToSubtract, string[] matchFields) + { + var ixsBase = new List(); + var ixsSub = new List(); + for (var i = 0; i < matchFields.Length; i++) + { + ixsBase.Add(baseCube.GetColumnIndex(matchFields[i])); + ixsSub.Add(cubeToSubtract.GetColumnIndex(matchFields[i])); + } + + if (ixsBase.Any(x=>x<0) || ixsSub.Any(x=>x<0)) + throw new Exception("Cubes must contain all required fields"); + + var o = new ResultCube(); + o.Initialize(baseCube.DataTypes); + var baseRows = baseCube.GetAllRows().ToList(); + var subRows = cubeToSubtract.GetAllRows().ToList(); + foreach (var br in baseRows) + { + var rowFound = false; + foreach (var sr in subRows) + { + var thisRowMatched = true; + for(var i=0;i< matchFields.Length; i++) + { + if (br.MetaData[ixsBase[i]] != sr.MetaData[ixsSub[i]]) + { + thisRowMatched = false; + break; + } + } + if(thisRowMatched) + { + o.AddRow(br.MetaData, br.Value - sr.Value); + subRows.Remove(sr); + rowFound = true; + break; + } + } + + if (!rowFound) //zero to subtract + { + o.AddRow(br.MetaData, br.Value); + } + } + + //look at what is left in subrows + foreach (var sr in subRows) + { + o.AddRow(sr.MetaData, -sr.Value); + } + + return o; + } + /// /// Differences two cubes, assuming same number and same order of rows in both /// diff --git a/src/Qwack.Models/Risk/BasicMetrics.cs b/src/Qwack.Models/Risk/BasicMetrics.cs index cf3d581f..09dae425 100644 --- a/src/Qwack.Models/Risk/BasicMetrics.cs +++ b/src/Qwack.Models/Risk/BasicMetrics.cs @@ -537,7 +537,7 @@ public static ICube AssetDeltaSingleCurve(this IPvModel pvModel, string assetId, var lastDateInBook = subPortfolio.LastSensitivityDate; model.AttachPortfolio(subPortfolio); - var pvCube = model.PV(curveObj.Currency); + var pvCube = pvModel.PV(curveObj.Currency); var pvRows = pvCube.GetAllRows(); var tidIx = pvCube.GetColumnIndex(TradeId); diff --git a/src/Qwack.Models/Risk/RiskLadder.cs b/src/Qwack.Models/Risk/RiskLadder.cs index 5c2356b0..2e006d65 100644 --- a/src/Qwack.Models/Risk/RiskLadder.cs +++ b/src/Qwack.Models/Risk/RiskLadder.cs @@ -103,6 +103,13 @@ public Dictionary GenerateScenarios(IPvModel model) return o; } + private string[] GetCubeMatchingFields(RiskMetric riskMetric) => riskMetric switch + { + RiskMetric.AssetCurveDelta or RiskMetric.AssetVega or RiskMetric.PV01 => ["TradeId", "PointLabel", "AssetId"], + RiskMetric.PV => ["TradeId"], + _ => [], + }; + public ICube Generate(IPvModel model, Portfolio portfolio = null) { var o = new ResultCube(); @@ -112,6 +119,7 @@ public ICube Generate(IPvModel model, Portfolio portfolio = null) ICube baseRiskCube = null; + var matchingFields = GetCubeMatchingFields(Metric); if (ReturnDifferential) { var baseModel = model; @@ -138,7 +146,14 @@ public ICube Generate(IPvModel model, Portfolio portfolio = null) if (ReturnDifferential) { - result = result.Difference(baseRiskCube); + try + { + result = result.Difference(baseRiskCube, matchingFields); + } + catch (Exception ex) + { + + } } results[i] = result; diff --git a/version.props b/version.props index 9c4d9adc..5dde31e4 100644 --- a/version.props +++ b/version.props @@ -1,5 +1,5 @@ - 0.7.88 + 0.7.89 diff --git a/version.txt b/version.txt index e62d4dd2..7fecfd8c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.7.88 +0.7.89