diff --git a/clients/Qwack.Excel.Next/Curves/IRCurveFunctions.cs b/clients/Qwack.Excel.Next/Curves/IRCurveFunctions.cs index 72f445a3..e3d27b09 100644 --- a/clients/Qwack.Excel.Next/Curves/IRCurveFunctions.cs +++ b/clients/Qwack.Excel.Next/Curves/IRCurveFunctions.cs @@ -217,7 +217,8 @@ public static object CreateFundingModel( [ExcelArgument(Description = "Funding instrument collection")] string FundingInstrumentCollection, [ExcelArgument(Description = "Curve to solve stage mappings")] object SolveStages, [ExcelArgument(Description = "Fx matrix object")] object FxMatrix, - [ExcelArgument(Description = "Fx vol surfaces")] object FxVolSurfaces) + [ExcelArgument(Description = "Fx vol surfaces")] object FxVolSurfaces, + [ExcelArgument(Description = "Fixing dictionaries")] object[] Fixings) { return ExcelHelper.Execute(_logger, () => { @@ -251,9 +252,13 @@ public static object CreateFundingModel( } } } + var fixings = Fixings.GetAnyFromCache(); var model = new FundingModel(BuildDate, emptyCurves.Values.ToArray(), ContainerStores.CurrencyProvider, ContainerStores.CalendarProvider); + foreach (var f in fixings) + model.AddFixingDictionary(f.Name, f); + if (!(FxMatrix is ExcelMissing)) model.SetupFx(fxMatrix); diff --git a/clients/Qwack.Excel.Next/Curves/InflationCurveFunctions.cs b/clients/Qwack.Excel.Next/Curves/InflationCurveFunctions.cs index 2f49d0b7..bc707b16 100644 --- a/clients/Qwack.Excel.Next/Curves/InflationCurveFunctions.cs +++ b/clients/Qwack.Excel.Next/Curves/InflationCurveFunctions.cs @@ -62,5 +62,27 @@ public static object CreateCPICurveFromForecasts( return ObjectName + '¬' + cache.GetObject(ObjectName).Version; }); } + + [ExcelFunction(Description = "Gets a CPI forecast from a curve", Category = CategoryNames.Curves, Name = CategoryNames.Curves + "_" + nameof(GetCpiForecast), IsThreadSafe = false)] + public static object GetCpiForecast( + [ExcelArgument(Description = "Curve object name")] string ObjectName, + [ExcelArgument(Description = "Fixing Date")] DateTime FixingDate, + [ExcelArgument(Description = "Fixing lag in months")] int LagInMonths) + { + return ExcelHelper.Execute(_logger, () => + { + if (ContainerStores.GetObjectCache().TryGetObject(ObjectName, out var curve)) + { + if (curve.Value is CPICurve cpi) + { + return cpi.GetForecast(FixingDate, LagInMonths); + } + + return $"Curve {ObjectName} is not a CPI Curve"; + } + + return $"CPI curve {ObjectName} not found in cache"; + }); + } } } diff --git a/clients/Qwack.Excel.Next/Instruments/FundingInstrumentFunctions.cs b/clients/Qwack.Excel.Next/Instruments/FundingInstrumentFunctions.cs index a50e5b4f..c39d60fb 100644 --- a/clients/Qwack.Excel.Next/Instruments/FundingInstrumentFunctions.cs +++ b/clients/Qwack.Excel.Next/Instruments/FundingInstrumentFunctions.cs @@ -528,6 +528,7 @@ public static object CreateFundingInstrumentCollection( var loanDepos = Instruments.GetAnyFromCache(); var ctgoSwaps = Instruments.GetAnyFromCache(); var flrDepos = Instruments.GetAnyFromCache(); + var infSwaps = Instruments.GetAnyFromCache(); //allows merging of FICs into portfolios var ficInstruments = Instruments.GetAnyFromCache() @@ -546,6 +547,7 @@ public static object CreateFundingInstrumentCollection( fic.AddRange(loanDepos); fic.AddRange(ctgoSwaps); fic.AddRange(flrDepos); + fic.AddRange(infSwaps); return ExcelHelper.PushToCache(fic, ObjectName); }); diff --git a/clients/Qwack.Excel.Next/Instruments/InstrumentFunctions.cs b/clients/Qwack.Excel.Next/Instruments/InstrumentFunctions.cs index e149bfc6..8e8f42ba 100644 --- a/clients/Qwack.Excel.Next/Instruments/InstrumentFunctions.cs +++ b/clients/Qwack.Excel.Next/Instruments/InstrumentFunctions.cs @@ -1444,6 +1444,9 @@ public static Portfolio GetPortfolio(object[,] Instruments) var dntOpts = Instruments.GetAnyFromCache(); var fltDepos = Instruments.GetAnyFromCache(); + var infSwaps = Instruments.GetAnyFromCache(); + var infSwapsB = Instruments.GetAnyFromCache(); + //allows merging of FICs into portfolios var ficInstruments = Instruments.GetAnyFromCache() .SelectMany(s => s); @@ -1485,6 +1488,10 @@ public static Portfolio GetPortfolio(object[,] Instruments) pf.Instruments.AddRange(dntOpts); pf.Instruments.AddRange(fltDepos); + pf.Instruments.AddRange(infSwaps); + pf.Instruments.AddRange(infSwapsB); + + return pf; } } diff --git a/examples/Qwack - YeildCurveExample (Frozen Data).xlsx b/examples/Qwack - YeildCurveExample (Frozen Data).xlsx index 6e3ebc98..a3f95c24 100644 Binary files a/examples/Qwack - YeildCurveExample (Frozen Data).xlsx and b/examples/Qwack - YeildCurveExample (Frozen Data).xlsx differ diff --git a/src/Qwack.Core/Curves/CPICurve.cs b/src/Qwack.Core/Curves/CPICurve.cs index 3561b814..5f173ece 100644 --- a/src/Qwack.Core/Curves/CPICurve.cs +++ b/src/Qwack.Core/Curves/CPICurve.cs @@ -17,7 +17,7 @@ public CPICurve() { } public CPICurve(DateTime buildDate, DateTime[] pillars, double[] cpiRates, InflationIndex inflationIndex) { BuildDate = buildDate; - Pillars = pillars; + PillarDates = pillars; CpiRates = cpiRates; InflationIndex = inflationIndex; Basis = InflationIndex.DayCountBasis; @@ -28,7 +28,7 @@ public CPICurve(DateTime buildDate, DateTime[] pillars, double[] cpiRates, Infla public CPICurve(DateTime buildDate, DateTime[] pillars, double[] cpiRates, DayCountBasis cpiBasis, Frequency cpiFixingLag, Calendar cpiCalendar) { BuildDate = buildDate; - Pillars = pillars; + PillarDates = pillars; CpiRates = cpiRates; InflationIndex = new InflationIndex { @@ -47,9 +47,9 @@ private void BuildInterpolator() { var allCpiPoints = new Dictionary(); - for (var i = 0; i < Pillars.Length; i++) + for (var i = 0; i < PillarDates.Length; i++) { - allCpiPoints[Pillars[i]] = CpiRates[i]; + allCpiPoints[PillarDates[i]] = CpiRates[i]; } var x = allCpiPoints.OrderBy(x => x.Key).Select(x => x.Key.ToOADate()).ToArray(); var y = allCpiPoints.OrderBy(x => x.Key).Select(x => x.Value).ToArray(); @@ -63,10 +63,10 @@ private void BuildInterpolator() public string Name { get; set; } - public DateTime[] Pillars { get; set; } = Array.Empty(); + public DateTime[] PillarDates { get; set; } = Array.Empty(); public double[] CpiRates { get; set; } = Array.Empty(); - public int NumberOfPillars => Pillars.Length; + public int NumberOfPillars => PillarDates.Length; public InflationIndex InflationIndex { get; set; } @@ -85,14 +85,14 @@ public IIrCurve BumpRate(int pillarIx, double delta, bool mutate) } else { - return new CPICurve(BuildDate, Pillars, CpiRates.Select((x, ix) => ix == pillarIx ? x + delta : x).ToArray(), InflationIndex); + return new CPICurve(BuildDate, PillarDates, CpiRates.Select((x, ix) => ix == pillarIx ? x + delta : x).ToArray(), InflationIndex); } } public IIrCurve BumpRateFlat(double delta, bool mutate) { if (mutate) { - for (var i = 0; i < Pillars.Length; i++) + for (var i = 0; i < PillarDates.Length; i++) { CpiRates[i] += delta; } @@ -101,7 +101,7 @@ public IIrCurve BumpRateFlat(double delta, bool mutate) } else { - return new CPICurve(BuildDate, Pillars, CpiRates.Select(x => x + delta).ToArray(), InflationIndex); + return new CPICurve(BuildDate, PillarDates, CpiRates.Select(x => x + delta).ToArray(), InflationIndex); } } public Dictionary BumpScenarios(double delta, DateTime lastSensitivityDate) => throw new NotImplementedException(); @@ -141,7 +141,7 @@ public IIrCurve SetRate(int pillarIx, double rate, bool mutate) } else { - return new CPICurve(BuildDate, Pillars, CpiRates.Select((x, ix) => ix == pillarIx ? rate : x).ToArray(), InflationIndex); + return new CPICurve(BuildDate, PillarDates, CpiRates.Select((x, ix) => ix == pillarIx ? rate : x).ToArray(), InflationIndex); } } diff --git a/src/Qwack.Core/Curves/IIrCurve.cs b/src/Qwack.Core/Curves/IIrCurve.cs index cb82bb06..c7bda899 100644 --- a/src/Qwack.Core/Curves/IIrCurve.cs +++ b/src/Qwack.Core/Curves/IIrCurve.cs @@ -27,7 +27,7 @@ public interface IIrCurve IIrCurve RebaseDate(DateTime newAnchorDate); int SolveStage { get; set; } - string CollateralSpec { get; set; } + DateTime[] PillarDates { get; } } } diff --git a/src/Qwack.Core/Instruments/Funding/InflationPerformanceSwap.cs b/src/Qwack.Core/Instruments/Funding/InflationPerformanceSwap.cs index 337db28a..21a01ec3 100644 --- a/src/Qwack.Core/Instruments/Funding/InflationPerformanceSwap.cs +++ b/src/Qwack.Core/Instruments/Funding/InflationPerformanceSwap.cs @@ -57,7 +57,16 @@ public InflationPerformanceSwap(DateTime startDate, Frequency swapTenor, Inflati public string ForecastCurveCpi { get; set; } public string DiscountCurve { get; set; } public string SolveCurve { get; set; } - public DateTime PillarDate { get; set; } + + private DateTime? _pillarDate; + public DateTime PillarDate + { + get => _pillarDate ?? EndDate; + set + { + _pillarDate = value; + } + } public string TradeId { get; set; } public string Counterparty { get; set; } public InflationIndex RateIndex { get; set; } @@ -169,7 +178,7 @@ public List ExpectedCashFlows(IAssetFxModel model) if (InitialFixing == 0) { - if (!model.TryGetFixingDictionary(ForecastCurveCpi, out var fixingDictionary)) + if (!model.FundingModel.TryGetFixingDictionary(ForecastCurveCpi, out var fixingDictionary)) throw new Exception($"Fixing dictionary not found for inflation index {ForecastCurveCpi}"); InitialFixing = InflationUtils.InterpFixing(StartDate, fixingDictionary, RateIndex.FixingLag.PeriodCount); @@ -177,7 +186,7 @@ public List ExpectedCashFlows(IAssetFxModel model) var forecast = (forecastCurveCpi as CPICurve).GetForecast(EndDate, RateIndex.FixingLag.PeriodCount); - var cpiPerf = forecast / InitialFixing; + var cpiPerf = forecast / InitialFixing - 1; var cpiLegFv = cpiPerf * Notional * (SwapType == SwapPayReceiveType.Payer ? 1.0 : -1.0); var fixedLegFv = FixedFlow; diff --git a/src/Qwack.Models/Calibrators/NewtonRaphsonMultiCurveSolverStaged.cs b/src/Qwack.Models/Calibrators/NewtonRaphsonMultiCurveSolverStaged.cs index e9b438c7..88da2f2f 100644 --- a/src/Qwack.Models/Calibrators/NewtonRaphsonMultiCurveSolverStaged.cs +++ b/src/Qwack.Models/Calibrators/NewtonRaphsonMultiCurveSolverStaged.cs @@ -58,7 +58,7 @@ public void Solve(IFundingModel fundingModel, FundingInstrumentCollection instru var points = insForCurve.ToDictionary(x => x.PillarDate, x => x.SuggestPillarValue(fundingModel)); for (var i = 0; i < kv.Value.NumberOfPillars; i++) { - kv.Value.SetRate(i, points[(kv.Value as IrCurve).PillarDates[i]], true); + kv.Value.SetRate(i, points[kv.Value.PillarDates[i]], true); } } } diff --git a/src/Qwack.Models/Models/AssetProductEx.cs b/src/Qwack.Models/Models/AssetProductEx.cs index db51e726..aa020d43 100644 --- a/src/Qwack.Models/Models/AssetProductEx.cs +++ b/src/Qwack.Models/Models/AssetProductEx.cs @@ -1254,6 +1254,8 @@ public static string TradeType(this IInstrument ins) OISFuture => "OISFuture", IrSwap => "IRSwap", IrBasisSwap => "IRBasisSwap", + InflationPerformanceSwap => "InfPerfSwap", + InflationSwap => "InfSwap", CashWrapper wrapper => TradeType(wrapper.UnderlyingInstrument), _ => throw new Exception($"Unable to handle product of type {ins.GetType()}"), }; diff --git a/src/Qwack.Models/Models/FundingModel.cs b/src/Qwack.Models/Models/FundingModel.cs index f3f95e46..db41fdd6 100644 --- a/src/Qwack.Models/Models/FundingModel.cs +++ b/src/Qwack.Models/Models/FundingModel.cs @@ -47,6 +47,7 @@ public FundingModel(TO_FundingModel transportObject, ICurrencyProvider currencyP { if (transportObject.VolSurfaces != null) VolSurfaces = transportObject.VolSurfaces.ToDictionary(x => x.Key, x => x.Value.GetVolSurface(currencyProvider)); + _fixings = transportObject.Fixings?.ToDictionary(x => x.Key, x => (IFixingDictionary)new FixingDictionary(x.Value)) ?? new Dictionary(); SetupFx(new FxMatrix(transportObject.FxMatrix, currencyProvider, calendarProvider)); } @@ -138,8 +139,12 @@ public IFundingModel Clone() var returnValue = new FundingModel(BuildDate, Curves.Values.ToArray(), _currencyProvider, _calendarProvider) { FxMatrix = FxMatrix, - VolSurfaces = VolSurfaces + VolSurfaces = VolSurfaces, }; + + foreach (var kv in _fixings) + returnValue.AddFixingDictionary(kv.Key, kv.Value); + return returnValue; } @@ -152,6 +157,10 @@ public IFundingModel DeepClone(DateTime? newBuildDate = null) if (FxMatrix != null) returnValue.SetupFx(FxMatrix.Clone()); + + foreach (var kv in _fixings) + returnValue.AddFixingDictionary(kv.Key, kv.Value); + return returnValue; } @@ -287,7 +296,8 @@ public TO_FundingModel GetTransportObject() => BuildDate = BuildDate, VolSurfaces = VolSurfaces.ToDictionary(x => x.Key, x => x.Value.GetTransportObject()), Curves = Curves.ToDictionary(x => x.Key, x => (x.Value as IrCurve).GetTransportObject()), - FxMatrix = ((FxMatrix)FxMatrix).GetTransportObject() + FxMatrix = ((FxMatrix)FxMatrix).GetTransportObject(), + Fixings = _fixings?.ToDictionary(x => x.Key, x => x.Value.GetTransportObject()), }; } diff --git a/src/Qwack.Transport/TransportObjects/MarketData/Models/TO_FundingModel.cs b/src/Qwack.Transport/TransportObjects/MarketData/Models/TO_FundingModel.cs index 956280d4..4ca0365c 100644 --- a/src/Qwack.Transport/TransportObjects/MarketData/Models/TO_FundingModel.cs +++ b/src/Qwack.Transport/TransportObjects/MarketData/Models/TO_FundingModel.cs @@ -17,6 +17,8 @@ public class TO_FundingModel public DateTime BuildDate { get; set; } [ProtoMember(7)] public TO_FxMatrix FxMatrix { get; set; } + [ProtoMember(8)] + public Dictionary Fixings { get; set; } } } diff --git a/test/Qwack.Core.Tests/Inflation/InflationCurveFacts.cs b/test/Qwack.Core.Tests/Inflation/InflationCurveFacts.cs index a4820c11..f79d20b4 100644 --- a/test/Qwack.Core.Tests/Inflation/InflationCurveFacts.cs +++ b/test/Qwack.Core.Tests/Inflation/InflationCurveFacts.cs @@ -82,6 +82,89 @@ public void TestInflationCurve() Assert.DoesNotContain(rates, double.IsNaN); } + [Fact] + public void TestInflationCurve2() + { + var vd = new DateTime(2023, 03, 01); + var usd = TestProviderHelper.CurrencyProvider.GetCurrencySafe("USD"); + var usdIrCurve = new ConstantRateIrCurve(0.05, vd, "USD.CURVE", usd) + { + SolveStage = -1, + }; + + var infIx = new InflationIndex + { + Currency = usd, + DayCountBasis = DayCountBasis.Act360, + DayCountBasisFixed = DayCountBasis.Act360, + FixingInterpolation = Interpolator1DType.Linear, + FixingLag = 1.Months(), + ResetFrequency = 1.Years() + }; + + var infSwap6m = new InflationPerformanceSwap(vd, 6.Months(), infIx, 0.045, 1e6, SwapPayReceiveType.Pay, "USD.CPI", "USD.CURVE") + { + SolveCurve = "USD.CPI" + }; + var infSwap1y = new InflationPerformanceSwap(vd, 1.Years(), infIx, 0.05, 1e6, SwapPayReceiveType.Pay, "USD.CPI", "USD.CURVE") + { + SolveCurve = "USD.CPI" + }; + var infSwap2y = new InflationPerformanceSwap(vd, 2.Years(), infIx, 0.055, 1e6, SwapPayReceiveType.Pay, "USD.CPI", "USD.CURVE") + { + SolveCurve = "USD.CPI" + }; + var infSwap3y = new InflationPerformanceSwap(vd, 3.Years(), infIx, 0.06, 1e6, SwapPayReceiveType.Pay, "USD.CPI", "USD.CURVE") + { + SolveCurve = "USD.CPI" + }; + var infSwap5y = new InflationPerformanceSwap(vd, 5.Years(), infIx, 0.08, 1e6, SwapPayReceiveType.Pay, "USD.CPI", "USD.CURVE") + { + SolveCurve = "USD.CPI" + }; + + + var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider) + { + infSwap6m, + infSwap1y, + infSwap2y, + infSwap3y, + infSwap5y, + }; + + var curves = fic.ImplyContainedCurves(vd, Interpolator1DType.Linear); + curves.Add("USD.CURVE",usdIrCurve); + + var fixings = new Dictionary() + { + { vd.AddMonths(-1), 110 }, + { vd.AddMonths(-2), 100 }, + }; + + var pillars = new[] { vd.AddYears(1), vd.AddYears(2) }; + var rates = new[] { 120.0, 121.0 }; + + var model = new FundingModel(vd, curves.Values.ToArray(), TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider); + model.AddFixingDictionary("USD.CPI", new FixingDictionary(fixings)); + + var S = new NewtonRaphsonMultiCurveSolverStaged + { + JacobianBump = 0.01, + Tollerance = 0.00000001, + MaxItterations = 100, + }; + + S.Solve(model, fic); + + var cpiCurve = curves["USD.CPI"] as CPICurve; + Assert.DoesNotContain(cpiCurve.CpiRates, double.IsNaN); + foreach(var i in fic) + { + Assert.Equal(0, i.Pv(model, false), 8); + } + } + [Fact] public void TestInflationPerfSwap() {