From dbfb9b6354a94d755c7b2b3396a239fe82915068 Mon Sep 17 00:00:00 2001 From: Gavin Brennan Date: Wed, 24 May 2023 17:43:54 +0200 Subject: [PATCH] Inflation refactor --- .../Curves/IRCurveFunctions.cs | 2 +- .../Curves/InflationCurveFunctions.cs | 10 +--- .../Instruments/FundingInstrumentFunctions.cs | 2 +- .../Qwack.Excel.Next/Risk/RiskFunctions.cs | 2 +- .../Qwack.Excel/Curves/IRCurveFunctions.cs | 2 +- .../Instruments/FundingInstrumentFunctions.cs | 2 +- clients/Qwack.Excel/Risk/RiskFunctions.cs | 2 +- ...ack - YeildCurveExample (Frozen Data).xlsx | Bin 0 -> 29689 bytes src/Qwack.Core/Curves/CPICurve.cs | 39 +++++++++------ src/Qwack.Core/Curves/IIrCurve.cs | 4 +- src/Qwack.Core/Curves/IrCurve.cs | 2 +- src/Qwack.Core/Curves/SvenssonCurve.cs | 8 ++- src/Qwack.Core/Instruments/Cashflow.cs | 4 +- .../Instruments/CashflowSchedule.cs | 6 +-- .../Funding/FundingInstrumentCollection.cs | 39 +++++++++++---- .../Funding/InflationPerformanceSwap.cs | 46 +++++++++++++++--- .../Instruments/Funding/InflationSwap.cs | 22 +++++++-- .../Instruments/Funding/InflationUtils.cs | 39 +++++++++++++++ .../Instruments/IIsInflationInstrument.cs | 10 ++++ src/Qwack.Core/Models/IFundingModel.cs | 5 ++ .../Calibrators/CMEModelBuilder.cs | 6 +-- .../Calibrators/COMEXModelBuilder.cs | 2 +- .../Calibrators/QuickFundingCurveStripper.cs | 2 +- src/Qwack.Models/Models/FundingModel.cs | 17 +++++++ src/Qwack.Models/Risk/BenchmarkCurveRisk.cs | 10 ++-- .../CurveSolving/EuroDollarFuturesFact.cs | 4 +- test/Qwack.Core.Tests/CurveSolving/OisFact.cs | 8 +-- .../CurveSolving/SelfDiscountingFact.cs | 2 +- .../Inflation/InflationCurveFacts.cs | 44 +++++++++++------ test/Qwack.Core.Tests/Instruments/FICFacts.cs | 6 +-- .../Risk/BenchmarkRiskFacts.cs | 4 +- .../SolvingOisBenchmark.cs | 2 +- 32 files changed, 257 insertions(+), 96 deletions(-) create mode 100644 examples/Qwack - YeildCurveExample (Frozen Data).xlsx create mode 100644 src/Qwack.Core/Instruments/Funding/InflationUtils.cs create mode 100644 src/Qwack.Core/Instruments/IIsInflationInstrument.cs diff --git a/clients/Qwack.Excel.Next/Curves/IRCurveFunctions.cs b/clients/Qwack.Excel.Next/Curves/IRCurveFunctions.cs index 99424566..72f445a3 100644 --- a/clients/Qwack.Excel.Next/Curves/IRCurveFunctions.cs +++ b/clients/Qwack.Excel.Next/Curves/IRCurveFunctions.cs @@ -230,7 +230,7 @@ public static object CreateFundingModel( fxMatrix = fxMatrixCache.GetObject((string)FxMatrix).Value; } - var emptyCurves = new Dictionary(); + var emptyCurves = new Dictionary(); if (fic != null) { emptyCurves = fic.Value.ImplyContainedCurves(BuildDate, Interpolator1DType.LinearFlatExtrap); diff --git a/clients/Qwack.Excel.Next/Curves/InflationCurveFunctions.cs b/clients/Qwack.Excel.Next/Curves/InflationCurveFunctions.cs index bcd9318a..2f49d0b7 100644 --- a/clients/Qwack.Excel.Next/Curves/InflationCurveFunctions.cs +++ b/clients/Qwack.Excel.Next/Curves/InflationCurveFunctions.cs @@ -32,7 +32,6 @@ public static object CreateCPICurveFromForecasts( [ExcelArgument(Description = "Array of CPI forecasts")] double[] CPIForecasts, [ExcelArgument(Description = "Type of interpolation")] object InterpolationType, [ExcelArgument(Description = "Inflation Index")] string InfIndex, - [ExcelArgument(Description = "Fixing Dictionary")] string FixingDict, [ExcelArgument(Description = "Collateral Spec - default LIBOR.3M")] object CollateralSpec) { return ExcelHelper.Execute(_logger, () => @@ -52,15 +51,8 @@ public static object CreateCPICurveFromForecasts( return $"Rate index {InfIndex} not found in cache"; } - var fixings = new Dictionary(); - if(ContainerStores.GetObjectCache().TryGetObject(FixingDict, out var fixDict)) - { - foreach(var kv in fixDict.Value) - fixings.Add(kv.Key, kv.Value); - } - var pDates = Pillars.ToDateTimeArray(); - var cObj = new CPICurve(BuildDate, pDates, CPIForecasts, rIndex.Value, fixings) + var cObj = new CPICurve(BuildDate, pDates, CPIForecasts, rIndex.Value) { Name = curveName, }; diff --git a/clients/Qwack.Excel.Next/Instruments/FundingInstrumentFunctions.cs b/clients/Qwack.Excel.Next/Instruments/FundingInstrumentFunctions.cs index a62362e4..a50e5b4f 100644 --- a/clients/Qwack.Excel.Next/Instruments/FundingInstrumentFunctions.cs +++ b/clients/Qwack.Excel.Next/Instruments/FundingInstrumentFunctions.cs @@ -533,7 +533,7 @@ public static object CreateFundingInstrumentCollection( var ficInstruments = Instruments.GetAnyFromCache() .SelectMany(s => s); - var fic = new FundingInstrumentCollection(ContainerStores.CurrencyProvider); + var fic = new FundingInstrumentCollection(ContainerStores.CurrencyProvider, ContainerStores.CalendarProvider); fic.AddRange(swaps); fic.AddRange(fras); fic.AddRange(futures); diff --git a/clients/Qwack.Excel.Next/Risk/RiskFunctions.cs b/clients/Qwack.Excel.Next/Risk/RiskFunctions.cs index 7a352250..d82057cd 100644 --- a/clients/Qwack.Excel.Next/Risk/RiskFunctions.cs +++ b/clients/Qwack.Excel.Next/Risk/RiskFunctions.cs @@ -240,7 +240,7 @@ public static object PortfolioIrBenchmarkDelta( var model = InstrumentFunctions.GetModelFromCache(ModelName, PortfolioName); var fic = ContainerStores.GetObjectCache().GetObjectOrThrow(FICName, $"FIC {FICName} not found in cache"); var ccy = ContainerStores.CurrencyProvider.GetCurrency(ReportingCcy); - var result = model.BenchmarkRisk(fic.Value, ContainerStores.CurrencyProvider, ccy); + var result = model.BenchmarkRisk(fic.Value, ContainerStores.CurrencyProvider, ContainerStores.CalendarProvider, ccy); return PushCubeToCache(result, ResultObjectName); }); } diff --git a/clients/Qwack.Excel/Curves/IRCurveFunctions.cs b/clients/Qwack.Excel/Curves/IRCurveFunctions.cs index 52d4f9a2..e1611cf8 100644 --- a/clients/Qwack.Excel/Curves/IRCurveFunctions.cs +++ b/clients/Qwack.Excel/Curves/IRCurveFunctions.cs @@ -230,7 +230,7 @@ public static object CreateFundingModel( fxMatrix = fxMatrixCache.GetObject((string)FxMatrix).Value; } - var emptyCurves = new Dictionary(); + var emptyCurves = new Dictionary(); if (fic != null) { emptyCurves = fic.Value.ImplyContainedCurves(BuildDate, Interpolator1DType.LinearFlatExtrap); diff --git a/clients/Qwack.Excel/Instruments/FundingInstrumentFunctions.cs b/clients/Qwack.Excel/Instruments/FundingInstrumentFunctions.cs index 71974ad0..4b6eed77 100644 --- a/clients/Qwack.Excel/Instruments/FundingInstrumentFunctions.cs +++ b/clients/Qwack.Excel/Instruments/FundingInstrumentFunctions.cs @@ -533,7 +533,7 @@ public static object CreateFundingInstrumentCollection( var ficInstruments = Instruments.GetAnyFromCache() .SelectMany(s => s); - var fic = new FundingInstrumentCollection(ContainerStores.CurrencyProvider); + var fic = new FundingInstrumentCollection(ContainerStores.CurrencyProvider, ContainerStores.CalendarProvider); fic.AddRange(swaps); fic.AddRange(fras); fic.AddRange(futures); diff --git a/clients/Qwack.Excel/Risk/RiskFunctions.cs b/clients/Qwack.Excel/Risk/RiskFunctions.cs index bc7ce233..49f0d20e 100644 --- a/clients/Qwack.Excel/Risk/RiskFunctions.cs +++ b/clients/Qwack.Excel/Risk/RiskFunctions.cs @@ -240,7 +240,7 @@ public static object PortfolioIrBenchmarkDelta( var model = InstrumentFunctions.GetModelFromCache(ModelName, PortfolioName); var fic = ContainerStores.GetObjectCache().GetObjectOrThrow(FICName, $"FIC {FICName} not found in cache"); var ccy = ContainerStores.CurrencyProvider.GetCurrency(ReportingCcy); - var result = model.BenchmarkRisk(fic.Value, ContainerStores.CurrencyProvider, ccy); + var result = model.BenchmarkRisk(fic.Value, ContainerStores.CurrencyProvider, ContainerStores.CalendarProvider, ccy); return PushCubeToCache(result, ResultObjectName); }); } diff --git a/examples/Qwack - YeildCurveExample (Frozen Data).xlsx b/examples/Qwack - YeildCurveExample (Frozen Data).xlsx new file mode 100644 index 0000000000000000000000000000000000000000..6e3ebc98a6ab006258b990b633621692ef08f3af GIT binary patch literal 29689 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 N$jIy0hGz4-^ cpiHistory = null) + public CPICurve(DateTime buildDate, DateTime[] pillars, double[] cpiRates, InflationIndex inflationIndex) { BuildDate = buildDate; Pillars = pillars; CpiRates = cpiRates; InflationIndex = inflationIndex; Basis = InflationIndex.DayCountBasis; - CpiHistory = cpiHistory ?? new Dictionary(); + + BuildInterpolator(); + } + + public CPICurve(DateTime buildDate, DateTime[] pillars, double[] cpiRates, DayCountBasis cpiBasis, Frequency cpiFixingLag, Calendar cpiCalendar) + { + BuildDate = buildDate; + Pillars = pillars; + CpiRates = cpiRates; + InflationIndex = new InflationIndex + { + DayCountBasis = cpiBasis, + RollConvention = RollType.P, + HolidayCalendars = cpiCalendar, + FixingLag = cpiFixingLag + }; + + Basis = cpiBasis; BuildInterpolator(); } private void BuildInterpolator() { - var allCpiPoints = new Dictionary(CpiHistory.Where(x => x.Key <= BuildDate).ToDictionary(x => x.Key, x => x.Value)); + var allCpiPoints = new Dictionary(); for (var i = 0; i < Pillars.Length; i++) { @@ -49,8 +66,6 @@ private void BuildInterpolator() public DateTime[] Pillars { get; set; } = Array.Empty(); public double[] CpiRates { get; set; } = Array.Empty(); - public Dictionary CpiHistory { get; set; } = new(); - public int NumberOfPillars => Pillars.Length; public InflationIndex InflationIndex { get; set; } @@ -58,6 +73,7 @@ private void BuildInterpolator() private IInterpolator1D _cpiInterp; public int SolveStage { get; set; } + public string CollateralSpec { get; set; } public IIrCurve BumpRate(int pillarIx, double delta, bool mutate) { @@ -69,7 +85,7 @@ 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, CpiHistory); + return new CPICurve(BuildDate, Pillars, CpiRates.Select((x, ix) => ix == pillarIx ? x + delta : x).ToArray(), InflationIndex); } } public IIrCurve BumpRateFlat(double delta, bool mutate) @@ -85,18 +101,13 @@ public IIrCurve BumpRateFlat(double delta, bool mutate) } else { - return new CPICurve(BuildDate, Pillars, CpiRates.Select(x => x + delta).ToArray(), InflationIndex, CpiHistory); + return new CPICurve(BuildDate, Pillars, CpiRates.Select(x => x + delta).ToArray(), InflationIndex); } } public Dictionary BumpScenarios(double delta, DateTime lastSensitivityDate) => throw new NotImplementedException(); - public double GetReturn(DateTime startDate, DateTime endDate) - { - var cpiStart = _cpiInterp.Interpolate(startDate.SubtractPeriod(InflationIndex.RollConvention, InflationIndex.HolidayCalendars, InflationIndex.FixingLag).ToOADate()); - var cpiEnd = _cpiInterp.Interpolate(endDate.SubtractPeriod(InflationIndex.RollConvention, InflationIndex.HolidayCalendars, InflationIndex.FixingLag).ToOADate()); - return cpiEnd / cpiStart - 1; - } + public double GetForecast(DateTime fixingDate, int fixingLagInMonths) => InflationUtils.InterpFixing(fixingDate, _cpiInterp, fixingLagInMonths); public double GetDf(DateTime startDate, DateTime endDate) { @@ -130,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, CpiHistory); + return new CPICurve(BuildDate, Pillars, 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 266d0742..cb82bb06 100644 --- a/src/Qwack.Core/Curves/IIrCurve.cs +++ b/src/Qwack.Core/Curves/IIrCurve.cs @@ -26,6 +26,8 @@ public interface IIrCurve IIrCurve RebaseDate(DateTime newAnchorDate); - int SolveStage { get; } + int SolveStage { get; set; } + + string CollateralSpec { get; set; } } } diff --git a/src/Qwack.Core/Curves/IrCurve.cs b/src/Qwack.Core/Curves/IrCurve.cs index 3ba535da..f765b3cd 100644 --- a/src/Qwack.Core/Curves/IrCurve.cs +++ b/src/Qwack.Core/Curves/IrCurve.cs @@ -65,7 +65,7 @@ public IrCurve(TO_IrCurve transportObject, ICurrencyProvider currencyProvider) public Currency Currency; - public string CollateralSpec { get; private set; } + public string CollateralSpec { get; set; } public FloatRateIndex RateIndex { get; private set; } public Dictionary Fixings { get; set; } diff --git a/src/Qwack.Core/Curves/SvenssonCurve.cs b/src/Qwack.Core/Curves/SvenssonCurve.cs index 491e576c..9a10996a 100644 --- a/src/Qwack.Core/Curves/SvenssonCurve.cs +++ b/src/Qwack.Core/Curves/SvenssonCurve.cs @@ -49,14 +49,18 @@ public SvenssonCurve(double beta0, double beta1, double beta2, double beta3, dou public double Beta3 { get; } public double Tau1 { get; } public double Tau2 { get; } - public string CollateralSpec { get; private set; } + public string CollateralSpec { get; set; } public FloatRateIndex RateIndex { get; private set; } public void SetCollateralSpec(string collateralSpec) => CollateralSpec = collateralSpec; public void SetRateIndex(FloatRateIndex rateIndex) => RateIndex = rateIndex; - public int SolveStage { get { throw new NotImplementedException(); } } + public int SolveStage + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } public double GetDf(DateTime startDate, DateTime endDate) { diff --git a/src/Qwack.Core/Instruments/Cashflow.cs b/src/Qwack.Core/Instruments/Cashflow.cs index f5e13794..12a15daf 100644 --- a/src/Qwack.Core/Instruments/Cashflow.cs +++ b/src/Qwack.Core/Instruments/Cashflow.cs @@ -27,6 +27,7 @@ public CashFlow() public double Dcf { get; set; } public Currency Currency { get; set; } public double FixedRateOrMargin { get; set; } + public int CpiFixingLagInMonths { get; set; } public FlowType FlowType { get; set; } public DayCountBasis Basis { get; set; } @@ -51,7 +52,8 @@ public CashFlow() FlowType = FlowType, Basis = Basis, RateIndex = RateIndex, - Dcf = Dcf + Dcf = Dcf, + CpiFixingLagInMonths = CpiFixingLagInMonths }; } diff --git a/src/Qwack.Core/Instruments/CashflowSchedule.cs b/src/Qwack.Core/Instruments/CashflowSchedule.cs index 13ac8aef..054730a3 100644 --- a/src/Qwack.Core/Instruments/CashflowSchedule.cs +++ b/src/Qwack.Core/Instruments/CashflowSchedule.cs @@ -26,7 +26,7 @@ public class CashFlowSchedule public static class CashFlowScheduleEx { - public static double PV(this CashFlowSchedule schedule, IIrCurve discountCurve, IIrCurve forecastCurve, bool updateState, bool updateDf, bool updateEstimate, DayCountBasis basisFloat, DateTime? filterDate) + public static double PV(this CashFlowSchedule schedule, IIrCurve discountCurve, IIrCurve forecastCurve, bool updateState, bool updateDf, bool updateEstimate, DayCountBasis basisFloat, DateTime? filterDate, double? initialCpiFixing = null) { double totalPv = 0; @@ -135,8 +135,8 @@ public static double PV(this CashFlowSchedule schedule, IIrCurve discountCurve, } var s = flow.AccrualPeriodStart; var e = flow.AccrualPeriodEnd; - var cpiStart = forecastCurve.GetRate(s); - var cpiEnd = forecastCurve.GetRate(e); + var cpiStart = initialCpiFixing ?? forecastCurve.GetRate(s); + var cpiEnd = infCurve.GetForecast(e, flow.CpiFixingLagInMonths); fv = cpiEnd / cpiStart * flow.Notional * flow.FixedRateOrMargin * flow.YearFraction; } else diff --git a/src/Qwack.Core/Instruments/Funding/FundingInstrumentCollection.cs b/src/Qwack.Core/Instruments/Funding/FundingInstrumentCollection.cs index 2865cbcb..fcb36293 100644 --- a/src/Qwack.Core/Instruments/Funding/FundingInstrumentCollection.cs +++ b/src/Qwack.Core/Instruments/Funding/FundingInstrumentCollection.cs @@ -4,6 +4,7 @@ using Qwack.Core.Basic; using Qwack.Core.Curves; using Qwack.Core.Models; +using Qwack.Dates; using Qwack.Math.Interpolation; using Qwack.Transport.BasicTypes; @@ -16,39 +17,57 @@ namespace Qwack.Core.Instruments.Funding public class FundingInstrumentCollection : List { private readonly ICurrencyProvider _currencyProvider; + private readonly ICalendarProvider _calendarProvider; public List SolveCurves => this.Select(x => x.SolveCurve).Distinct().ToList(); - public FundingInstrumentCollection(ICurrencyProvider currencyProvider) => _currencyProvider = currencyProvider; + public FundingInstrumentCollection(ICurrencyProvider currencyProvider, ICalendarProvider calendarProvider) + { + _currencyProvider = currencyProvider; + _calendarProvider = calendarProvider; + } public FundingInstrumentCollection Clone() { - var fic = new FundingInstrumentCollection(_currencyProvider); + var fic = new FundingInstrumentCollection(_currencyProvider, _calendarProvider); fic.AddRange(this.Select(x => x.Clone())); return fic; } - public Dictionary ImplyContainedCurves(DateTime buildDate, Interpolator1DType interpType) + public Dictionary ImplyContainedCurves(DateTime buildDate, Interpolator1DType interpType) { - var o = new Dictionary(); + var o = new Dictionary(); foreach (var curveName in SolveCurves) { - var pillars = this.Where(x => x.SolveCurve == curveName) - .Select(x => x.PillarDate) + var insInScope = this.Where(x => x.SolveCurve == curveName).ToList(); + var pillars = insInScope.Select(x => x.PillarDate) .OrderBy(x => x) .ToArray(); - if (pillars.Distinct().Count() != pillars.Count()) + if (pillars.Distinct().Count() != pillars.Length) throw new Exception($"More than one instrument has the same solve pillar on curve {curveName}"); - var dummyRates = pillars.Select(x => 0.05).ToArray(); var ccy = _currencyProvider.GetCurrency(curveName.Split('.')[0]); var colSpec = (curveName.Contains("[")) ? curveName.Split('[').Last().Trim("[]".ToCharArray()) : curveName.Substring(curveName.IndexOf('.') + 1); if (o.Values.Any(v => v.CollateralSpec == colSpec)) colSpec = colSpec + "_" + curveName; - var irCurve = new IrCurve(pillars, dummyRates, buildDate, curveName, interpType, ccy, colSpec); - o.Add(curveName, irCurve); + if (insInScope.All(i => i is IIsInflationInstrument)) + { + var dummyRates = pillars.Select(x => 100.0).ToArray(); + var irCurve = new CPICurve(buildDate, pillars, dummyRates, DayCountBasis.Act360, new Frequency("-3m"), _calendarProvider.GetCalendarSafe(ccy)) + { + Name = curveName, + CollateralSpec = colSpec, + }; + o.Add(curveName, irCurve); + } + else + { + var dummyRates = pillars.Select(x => 0.05).ToArray(); + var irCurve = new IrCurve(pillars, dummyRates, buildDate, curveName, interpType, ccy, colSpec); + o.Add(curveName, irCurve); + } } return o; } diff --git a/src/Qwack.Core/Instruments/Funding/InflationPerformanceSwap.cs b/src/Qwack.Core/Instruments/Funding/InflationPerformanceSwap.cs index 56434e73..337db28a 100644 --- a/src/Qwack.Core/Instruments/Funding/InflationPerformanceSwap.cs +++ b/src/Qwack.Core/Instruments/Funding/InflationPerformanceSwap.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.WebSockets; using Qwack.Core.Basic; using Qwack.Core.Curves; using Qwack.Core.Models; @@ -9,7 +10,7 @@ namespace Qwack.Core.Instruments.Funding { - public class InflationPerformanceSwap : IFundingInstrument, ISaCcrEnabledIR + public class InflationPerformanceSwap : IFundingInstrument, ISaCcrEnabledIR, IIsInflationInstrument { public Dictionary MetaData { get; set; } = new Dictionary(); public InflationPerformanceSwap() { } @@ -44,7 +45,7 @@ public InflationPerformanceSwap(DateTime startDate, Frequency swapTenor, Inflati public double ParRate { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } - public int NDates { get; set; } + public double InitialFixing { get; set; } public DateTime[] ResetDates { get; set; } public Currency Currency { get; set; } @@ -70,8 +71,18 @@ public double Pv(IFundingModel model, bool updateState) { var discountCurve = model.Curves[DiscountCurve]; var forecastCurveCpi = model.Curves[ForecastCurveCpi]; - - var cpiPerf = (forecastCurveCpi as CPICurve).GetReturn(StartDate, EndDate); + + if (InitialFixing == 0) + { + if (!model.TryGetFixingDictionary(ForecastCurveCpi, out var fixingDictionary)) + throw new Exception($"Fixing dictionary not found for inflation index {ForecastCurveCpi}"); + + InitialFixing = InflationUtils.InterpFixing(StartDate, fixingDictionary, RateIndex.FixingLag.PeriodCount); + } + + var forecast = (forecastCurveCpi as CPICurve).GetForecast(EndDate, RateIndex.FixingLag.PeriodCount); + + var cpiPerf = forecast / InitialFixing - 1; var cpiLegFv = cpiPerf * Notional * (SwapType == SwapPayReceiveType.Payer ? 1.0 : -1.0); var fixedLegFv = FixedFlow; @@ -89,10 +100,19 @@ public Dictionary> Sensitivities(IFundingMo public double CalculateParRate(IFundingModel model) { var forecastCurveCpi = model.Curves[ForecastCurveCpi]; + if (InitialFixing == 0) + { + if (!model.TryGetFixingDictionary(ForecastCurveCpi, out var fixingDictionary)) + throw new Exception($"Fixing dictionary not found for inflation index {ForecastCurveCpi}"); + + InitialFixing = InflationUtils.InterpFixing(StartDate, fixingDictionary, RateIndex.FixingLag.PeriodCount); + } - var cpiPerf = (forecastCurveCpi as CPICurve).GetReturn(StartDate, EndDate); + var forecast = (forecastCurveCpi as CPICurve).GetForecast(EndDate, RateIndex.FixingLag.PeriodCount); - return System.Math.Pow(cpiPerf + 1, 1 / T) - 1; + var cpiPerf = forecast / InitialFixing; + + return System.Math.Pow(cpiPerf, 1 / T) - 1; } public IFundingInstrument Clone() => new InflationPerformanceSwap @@ -105,7 +125,7 @@ public double CalculateParRate(IFundingModel model) EndDate = EndDate, FixedFlow = FixedFlow, ForecastCurveCpi = ForecastCurveCpi, - NDates = NDates, + InitialFixing = InitialFixing, Notional = Notional, ParRate = ParRate, PillarDate = PillarDate, @@ -147,7 +167,17 @@ public List ExpectedCashFlows(IAssetFxModel model) var discountCurve = model.FundingModel.Curves[DiscountCurve]; var forecastCurveCpi = model.FundingModel.Curves[ForecastCurveCpi]; - var cpiPerf = (forecastCurveCpi as CPICurve).GetReturn(StartDate, EndDate); + if (InitialFixing == 0) + { + if (!model.TryGetFixingDictionary(ForecastCurveCpi, out var fixingDictionary)) + throw new Exception($"Fixing dictionary not found for inflation index {ForecastCurveCpi}"); + + InitialFixing = InflationUtils.InterpFixing(StartDate, fixingDictionary, RateIndex.FixingLag.PeriodCount); + } + + var forecast = (forecastCurveCpi as CPICurve).GetForecast(EndDate, RateIndex.FixingLag.PeriodCount); + + var cpiPerf = forecast / InitialFixing; var cpiLegFv = cpiPerf * Notional * (SwapType == SwapPayReceiveType.Payer ? 1.0 : -1.0); var fixedLegFv = FixedFlow; diff --git a/src/Qwack.Core/Instruments/Funding/InflationSwap.cs b/src/Qwack.Core/Instruments/Funding/InflationSwap.cs index 193e5405..10dfb07d 100644 --- a/src/Qwack.Core/Instruments/Funding/InflationSwap.cs +++ b/src/Qwack.Core/Instruments/Funding/InflationSwap.cs @@ -2,13 +2,14 @@ using System.Collections.Generic; using System.Linq; using Qwack.Core.Basic; +using Qwack.Core.Curves; using Qwack.Core.Models; using Qwack.Dates; using Qwack.Transport.BasicTypes; namespace Qwack.Core.Instruments.Funding { - public class InflationSwap : IFundingInstrument, ISaCcrEnabledIR + public class InflationSwap : IFundingInstrument, ISaCcrEnabledIR, IIsInflationInstrument { public Dictionary MetaData { get; set; } = new Dictionary(); public InflationSwap() { } @@ -44,6 +45,10 @@ public InflationSwap(DateTime startDate, Frequency swapTenor, InflationIndex rat AccrualDCB = rateIndex.DayCountBasis }; FlowScheduleCpiLinked = CpiLeg.GenerateSchedule(); + foreach(var flow in FlowScheduleCpiLinked.Flows) + { + flow.CpiFixingLagInMonths = rateIndex.FixingLag.PeriodCount; + } FlowScheduleIrLinked = IrLeg.GenerateSchedule(); ResetDates = FlowScheduleIrLinked.Flows.Select(x => x.FixingDateStart).ToArray(); @@ -57,7 +62,7 @@ public InflationSwap(DateTime startDate, Frequency swapTenor, InflationIndex rat public double ParRate { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } - public int NDates { get; set; } + public double InitialFixing { get; set; } public DateTime[] ResetDates { get; set; } public Currency Currency { get; set; } public GenericSwapLeg CpiLeg { get; set; } @@ -78,6 +83,7 @@ public InflationSwap(DateTime startDate, Frequency swapTenor, InflationIndex rat public string Counterparty { get; set; } public InflationIndex RateIndex { get; set; } public string PortfolioName { get; set; } + public DateTime LastSensitivityDate => EndDate; @@ -91,7 +97,15 @@ public double Pv(IFundingModel model, bool updateState) var discountCurve = model.Curves[DiscountCurve]; var forecastCurveCpi = model.Curves[ForecastCurveCpi]; var forecastCurveIr = model.Curves[ForecastCurveIr]; - var cpiLegPv = FlowScheduleCpiLinked.PV(discountCurve, forecastCurveCpi, updateState, updateDf, updateEst, BasisFloat, null); + + if (InitialFixing == 0) + { + if (!model.TryGetFixingDictionary(ForecastCurveCpi, out var fixingDictionary)) + throw new Exception($"Fixing dictionary not found for inflation index {ForecastCurveCpi}"); + + InitialFixing = InflationUtils.InterpFixing(StartDate, fixingDictionary, RateIndex.FixingLag.PeriodCount); + } + var cpiLegPv = FlowScheduleCpiLinked.PV(discountCurve, forecastCurveCpi, updateState, updateDf, updateEst, BasisFloat, null, InitialFixing); var irLegPv = FlowScheduleIrLinked.PV(discountCurve, forecastCurveIr, updateState, updateDf, updateEst, BasisFloat, null); return cpiLegPv + irLegPv; @@ -173,7 +187,7 @@ public double CalculateParRate(IFundingModel model) FlowScheduleCpiLinked = FlowScheduleCpiLinked.Clone(), FlowScheduleIrLinked = FlowScheduleIrLinked.Clone(), ForecastCurveCpi = ForecastCurveCpi, - NDates = NDates, + InitialFixing = InitialFixing, Notional = Notional, ParRate = ParRate, PillarDate = PillarDate, diff --git a/src/Qwack.Core/Instruments/Funding/InflationUtils.cs b/src/Qwack.Core/Instruments/Funding/InflationUtils.cs new file mode 100644 index 00000000..c1d63e9f --- /dev/null +++ b/src/Qwack.Core/Instruments/Funding/InflationUtils.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Qwack.Core.Models; +using Qwack.Math; + +namespace Qwack.Core.Instruments.Funding +{ + public static class InflationUtils + { + public static double InterpFixing(DateTime fixingDate, double indexA, double indexB) + { + var d = fixingDate.Day; + var som = new DateTime(fixingDate.Year, fixingDate.Month, 1); + var D = som.AddMonths(1).Subtract(som).TotalDays; + + return indexA + d / D * (indexB - indexA); + } + + public static double InterpFixing(DateTime fixingDate, IInterpolator1D fixings, int fixingLagMonths) + { + var d0 = fixingDate.AddMonths(-System.Math.Abs(fixingLagMonths)); + var indexA = fixings.Interpolate(new DateTime(d0.Year, d0.Month, 1).ToOADate()); + var indexB = fixings.Interpolate(new DateTime(d0.Year, d0.Month, 1).AddMonths(1).ToOADate()); + return InterpFixing(fixingDate, indexA, indexB); + } + + public static double InterpFixing(DateTime fixingDate, IFixingDictionary fixings, int fixingLagMonths) + { + var d0 = fixingDate.AddMonths(-System.Math.Abs(fixingLagMonths)); + var som = new DateTime(d0.Year, d0.Month, 1); + var indexA = fixings[som]; + if (som == d0) + return indexA; + var indexB = fixings[som.AddMonths(1)]; + return InterpFixing(fixingDate, indexA, indexB); + } + } +} diff --git a/src/Qwack.Core/Instruments/IIsInflationInstrument.cs b/src/Qwack.Core/Instruments/IIsInflationInstrument.cs new file mode 100644 index 00000000..d5d1bbfa --- /dev/null +++ b/src/Qwack.Core/Instruments/IIsInflationInstrument.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Qwack.Core.Instruments +{ + public interface IIsInflationInstrument + { + } +} diff --git a/src/Qwack.Core/Models/IFundingModel.cs b/src/Qwack.Core/Models/IFundingModel.cs index 64616e60..9e54157d 100644 --- a/src/Qwack.Core/Models/IFundingModel.cs +++ b/src/Qwack.Core/Models/IFundingModel.cs @@ -39,5 +39,10 @@ public interface IFundingModel Dictionary CalibrationCurves { get; set; } TO_FundingModel GetTransportObject(); + + IFixingDictionary GetFixingDictionary(string name); + bool TryGetFixingDictionary(string name, out IFixingDictionary fixings); + void AddFixingDictionary(string name, IFixingDictionary fixings); + void RemoveFixingDictionary(string name); } } diff --git a/src/Qwack.Models/Calibrators/CMEModelBuilder.cs b/src/Qwack.Models/Calibrators/CMEModelBuilder.cs index d4e40142..42096927 100644 --- a/src/Qwack.Models/Calibrators/CMEModelBuilder.cs +++ b/src/Qwack.Models/Calibrators/CMEModelBuilder.cs @@ -37,7 +37,7 @@ public static IrCurve GetCurveForCode(IEnumerable parsed, string var origin = DateTime.ParseExact(parsed.First().BizDt, "yyyy-MM-dd", CultureInfo.InvariantCulture); var instruments = parsed.Select(p => ToQwackIns(p, qwackCode, futureSettingsProvider, currencyProvider, indices, curves)).ToList(); var pillars = instruments.Select(x => x.PillarDate).OrderBy(x => x).ToArray(); - var fic = new FundingInstrumentCollection(currencyProvider); + var fic = new FundingInstrumentCollection(currencyProvider, calendarProvider); fic.AddRange(instruments); var curve = new IrCurve(pillars, pillars.Select(p => 0.01).ToArray(), origin, curveName, Interpolator1DType.Linear, currencyProvider.GetCurrency("USD")) { @@ -144,7 +144,7 @@ public static IrCurve StripFxBasisCurve(string cmeFwdFileName, FxPair ccyPair, s ForeignDiscountCurve = ccyPair.Foreign == curveCcy ? curveName : baseCurve.Name, }); - var fic = new FundingInstrumentCollection(currencyProvider); + var fic = new FundingInstrumentCollection(currencyProvider, calendarProvider); fic.AddRange(fwdObjects); var pillars = fwds.Keys.OrderBy(x => x).ToArray(); var curve = new IrCurve(pillars, pillars.Select(p => 0.01).ToArray(), valDate, curveName, Interpolator1DType.Linear, curveCcy); @@ -190,7 +190,7 @@ public static IrCurve StripFxBasisCurve(Dictionary fwds, FxPai ForeignDiscountCurve = ccyPair.Foreign == curveCcy ? curveName : baseCurve.Name, }); - var fic = new FundingInstrumentCollection(currencyProvider); + var fic = new FundingInstrumentCollection(currencyProvider, calendarProvider); fic.AddRange(fwdObjects); var pillars = fwds.Keys.OrderBy(x => x).ToArray(); var curve = new IrCurve(pillars, pillars.Select(p => 0.01).ToArray(), valDate, curveName, Interpolator1DType.Linear, curveCcy); diff --git a/src/Qwack.Models/Calibrators/COMEXModelBuilder.cs b/src/Qwack.Models/Calibrators/COMEXModelBuilder.cs index d138358d..36f72a38 100644 --- a/src/Qwack.Models/Calibrators/COMEXModelBuilder.cs +++ b/src/Qwack.Models/Calibrators/COMEXModelBuilder.cs @@ -40,7 +40,7 @@ public static (IrCurve curve, double spotPrice) GetMetalCurveForCode(string cmxS ForeignDiscountCurve = baseCurve.Name, }); - var fic = new FundingInstrumentCollection(currencyProvider); + var fic = new FundingInstrumentCollection(currencyProvider, calendarProvider); fic.AddRange(fwdObjects); var pillars = fwds.Keys.OrderBy(x => x).ToArray(); var curve = new IrCurve(pillars, pillars.Select(p => 0.01).ToArray(), valDate, curveName, Interpolator1DType.Linear, curveCcy); diff --git a/src/Qwack.Models/Calibrators/QuickFundingCurveStripper.cs b/src/Qwack.Models/Calibrators/QuickFundingCurveStripper.cs index df5a41af..887adea2 100644 --- a/src/Qwack.Models/Calibrators/QuickFundingCurveStripper.cs +++ b/src/Qwack.Models/Calibrators/QuickFundingCurveStripper.cs @@ -19,7 +19,7 @@ public static IrCurve StripFlatSpread(string name, double spread, FloatRateIndex var instruments = pillars.Select(p => new FloatingRateLoanDepo(baseCurve.BuildDate, p, floatRateIndex, 1e6, spread, baseCurve.Name, name) //-spread hack { SolveCurve = name, PillarDate = p }).ToArray(); - var fic = new FundingInstrumentCollection(currencyProvider); + var fic = new FundingInstrumentCollection(currencyProvider, calendarProvider); fic.AddRange(instruments); var solver = new NewtonRaphsonMultiCurveSolverStaged(); var fm = new FundingModel(baseCurve.BuildDate, new[] { baseCurve, fCurve }, currencyProvider, calendarProvider); diff --git a/src/Qwack.Models/Models/FundingModel.cs b/src/Qwack.Models/Models/FundingModel.cs index 5f21372f..f3f95e46 100644 --- a/src/Qwack.Models/Models/FundingModel.cs +++ b/src/Qwack.Models/Models/FundingModel.cs @@ -97,6 +97,23 @@ public IIrCurve GetCurveByCCyAndSpec(Currency ccy, string collateralSpec) public Dictionary CalibrationCurves { get; set; } public void UpdateCurves(Dictionary updateCurves) => Curves = new Dictionary(updateCurves); + private readonly Dictionary _fixings = new(); + + public void AddFixingDictionary(string name, IFixingDictionary fixings) => _fixings[name] = fixings; + public void AddFixingDictionaries(Dictionary fixings) + { + foreach (var kv in fixings) + _fixings[kv.Key] = kv.Value; + } + public IFixingDictionary GetFixingDictionary(string name) + { + if (!_fixings.TryGetValue(name, out var dict)) + throw new Exception($"Fixing dictionary with name {name} not found"); + return dict; + } + public bool TryGetFixingDictionary(string name, out IFixingDictionary fixings) => _fixings.TryGetValue(name, out fixings); + public void RemoveFixingDictionary(string name) => _fixings.Remove(name); + public IFundingModel BumpCurve(string curveName, int pillarIx, double deltaBump, bool mutate) { var newModel = new FundingModel(BuildDate, Curves.Select(kv => diff --git a/src/Qwack.Models/Risk/BenchmarkCurveRisk.cs b/src/Qwack.Models/Risk/BenchmarkCurveRisk.cs index 8d624ff8..42c27866 100644 --- a/src/Qwack.Models/Risk/BenchmarkCurveRisk.cs +++ b/src/Qwack.Models/Risk/BenchmarkCurveRisk.cs @@ -17,7 +17,7 @@ namespace Qwack.Models.Risk { public static class BenchmarkCurveRisk { - public static ICube BenchmarkRiskWithReStrip(this IPvModel pvModel, FundingInstrumentCollection riskCollection, ICurrencyProvider currencyProvider, Currency reportingCcy) + public static ICube BenchmarkRiskWithReStrip(this IPvModel pvModel, FundingInstrumentCollection riskCollection, ICurrencyProvider currencyProvider, ICalendarProvider calendarProvider, Currency reportingCcy) { var insByCurve = riskCollection.GroupBy(x => x.SolveCurve); var curvesNeeded = insByCurve.Select(x => x.Key).ToArray(); @@ -42,10 +42,10 @@ public static ICube BenchmarkRiskWithReStrip(this IPvModel pvModel, FundingInstr sol.Solve(newFundingModel, riskCollection); var newModel = pvModel.VanillaModel.Clone(newFundingModel); - return newModel.BenchmarkRisk(riskCollection, currencyProvider, reportingCcy); + return newModel.BenchmarkRisk(riskCollection, currencyProvider, calendarProvider, reportingCcy); } - public static ICube BenchmarkRisk(this IPvModel pvModel, FundingInstrumentCollection riskCollection, ICurrencyProvider currencyProvider, Currency reportingCcy) + public static ICube BenchmarkRisk(this IPvModel pvModel, FundingInstrumentCollection riskCollection, ICurrencyProvider currencyProvider, ICalendarProvider calendarProvider, Currency reportingCcy) { var cube = new ResultCube(); var dataTypes = new Dictionary @@ -121,7 +121,7 @@ public static ICube BenchmarkRisk(this IPvModel pvModel, FundingInstrumentCollec var parRates = insToRisk.Select(x => x.CalculateParRate(pvModel.VanillaModel.FundingModel)).ToList(); var newIns = insToRisk.Select((x, ix) => x.SetParRate(parRates[ix])); - var newFic = new FundingInstrumentCollection(currencyProvider); + var newFic = new FundingInstrumentCollection(currencyProvider, calendarProvider); newFic.AddRange(newIns.OrderBy(x => x.SolveCurve).ThenBy(x => x.PillarDate)); var fModel = pvModel.VanillaModel.FundingModel.DeepClone(null); @@ -150,7 +150,7 @@ public static ICube BenchmarkRisk(this IPvModel pvModel, FundingInstrumentCollec var bumpSize = GetBumpSize(insToRisk[i]); var bumpedIns = newIns.Select((x, ix) => x.SetParRate(parRates[ix] + (ix == i ? bumpSize : 0.0))); - var newFicb = new FundingInstrumentCollection(currencyProvider); + var newFicb = new FundingInstrumentCollection(currencyProvider, calendarProvider); newFicb.AddRange(bumpedIns); var fModelb = fModel.DeepClone(null); diff --git a/test/Qwack.Core.Tests/CurveSolving/EuroDollarFuturesFact.cs b/test/Qwack.Core.Tests/CurveSolving/EuroDollarFuturesFact.cs index 05cc4dd5..174c7ea5 100644 --- a/test/Qwack.Core.Tests/CurveSolving/EuroDollarFuturesFact.cs +++ b/test/Qwack.Core.Tests/CurveSolving/EuroDollarFuturesFact.cs @@ -67,7 +67,7 @@ public void FuturesStripNoConvexity() currentDate = currentDate.AddMonths(3); } - var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider); + var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider); fic.AddRange(instruments); var curve = new IrCurve(pillars, new double[nContracts], startDate, "USD.LIBOR.3M", Interpolator1DType.LinearFlatExtrap, ccyUsd); @@ -138,7 +138,7 @@ public void FuturesStripWithConvexity() currentDate = currentDate.AddMonths(3); } - var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider); + var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider); fic.AddRange(instruments); var curve = new IrCurve(pillars, new double[nContracts], startDate, "USD.LIBOR.3M", Interpolator1DType.LinearFlatExtrap, ccyUsd); diff --git a/test/Qwack.Core.Tests/CurveSolving/OisFact.cs b/test/Qwack.Core.Tests/CurveSolving/OisFact.cs index 791cbbfc..532918f4 100644 --- a/test/Qwack.Core.Tests/CurveSolving/OisFact.cs +++ b/test/Qwack.Core.Tests/CurveSolving/OisFact.cs @@ -93,7 +93,7 @@ public void BasicOisCurveSolving() var oisSwaps = new IrBasisSwap[oisTenors.Length]; var FRAs = new ForwardRateAgreement[FRATenors.Length]; - var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider); + var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider); for (var i = 0; i < FRAs.Length; i++) { @@ -193,7 +193,7 @@ public void ComplexCurve() var USDFRAs = new ForwardRateAgreement[FRATenors.Length]; - var FIC = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider); + var FIC = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider); for (var i = 0; i < FRATenors.Length; i++) { @@ -352,7 +352,7 @@ public void ComplexerCurve() var fxForwards = new FxForward[fxForwardTenors.Length]; var xcySwaps = new XccyBasisSwap[xcySwapTenors.Length]; - var FIC = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider); + var FIC = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider); for (var i = 0; i < FRATenors.Length; i++) { @@ -481,7 +481,7 @@ public void LessComplexCurve() var ZARdepos = new IrSwap[depoTenors.Length]; var ZARFRAs = new ForwardRateAgreement[FRATenors.Length]; - var FIC = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider); + var FIC = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider); for (var i = 0; i < FRATenors.Length; i++) { diff --git a/test/Qwack.Core.Tests/CurveSolving/SelfDiscountingFact.cs b/test/Qwack.Core.Tests/CurveSolving/SelfDiscountingFact.cs index 56408b50..5489b472 100644 --- a/test/Qwack.Core.Tests/CurveSolving/SelfDiscountingFact.cs +++ b/test/Qwack.Core.Tests/CurveSolving/SelfDiscountingFact.cs @@ -46,7 +46,7 @@ public void BasicSelfDiscounting() var swap2 = new IrSwap(startDate, swapTenor2, zar3m, 0.06, SwapPayReceiveType.Payer, "ZAR.JIBAR.3M", "ZAR.JIBAR.3M"); var depo = new IrSwap(startDate, 3.Months(), zar3m, 0.06, SwapPayReceiveType.Payer, "ZAR.JIBAR.3M", "ZAR.JIBAR.3M"); - var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider) + var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider) { swap, swap2, diff --git a/test/Qwack.Core.Tests/Inflation/InflationCurveFacts.cs b/test/Qwack.Core.Tests/Inflation/InflationCurveFacts.cs index 1749e377..a4820c11 100644 --- a/test/Qwack.Core.Tests/Inflation/InflationCurveFacts.cs +++ b/test/Qwack.Core.Tests/Inflation/InflationCurveFacts.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Qwack.Core.Basic; using Qwack.Core.Curves; using Qwack.Core.Instruments; using Qwack.Core.Instruments.Funding; @@ -20,7 +21,7 @@ public void TestInflationCurve() { var vd = new DateTime(2023, 03, 01); var usd = TestProviderHelper.CurrencyProvider.GetCurrencySafe("USD"); - var usdIrCurve = new ConstantRateIrCurve(0.05, vd, "USD-CURVE", usd) + var usdIrCurve = new ConstantRateIrCurve(0.05, vd, "USD.CURVE", usd) { SolveStage = -1, }; @@ -35,16 +36,16 @@ public void TestInflationCurve() ResetFrequency = 1.Years() }; - var infSwap1y = new InflationSwap(vd, 1.Years(), infIx, 0.045, Core.Basic.SwapPayReceiveType.Pay, "USD-CPI", "USD-CURVE", "USD-CURVE") + var infSwap1y = new InflationSwap(vd, 1.Years(), infIx, 0.045, SwapPayReceiveType.Pay, "USD.CPI", "USD.CURVE", "USD.CURVE") { - SolveCurve = "USD-CPI" + SolveCurve = "USD.CPI" }; - var infSwap2y = new InflationSwap(vd, 2.Years(), infIx, 0.045, Core.Basic.SwapPayReceiveType.Pay, "USD-CPI", "USD-CURVE", "USD-CURVE") + var infSwap2y = new InflationSwap(vd, 2.Years(), infIx, 0.045, SwapPayReceiveType.Pay, "USD.CPI", "USD.CURVE", "USD.CURVE") { - SolveCurve = "USD-CPI" + SolveCurve = "USD.CPI" }; - var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider) + var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider) { infSwap1y, infSwap2y @@ -60,13 +61,14 @@ public void TestInflationCurve() var pillars = new[] { vd.AddYears(1), vd.AddYears(2) }; var rates = new[] { 120.0, 121.0 }; - var cpiCurve = new CPICurve(vd, pillars, rates, infIx, fixings) + var cpiCurve = new CPICurve(vd, pillars, rates, infIx) { - Name = "USD-CPI", + Name = "USD.CPI", SolveStage = 0, }; var model = new FundingModel(vd, new IIrCurve[] { usdIrCurve, cpiCurve }, TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider); + model.AddFixingDictionary("USD.CPI", new FixingDictionary(fixings)); var S = new NewtonRaphsonMultiCurveSolverStaged { @@ -85,7 +87,7 @@ public void TestInflationPerfSwap() { var vd = new DateTime(2023, 02, 01); var usd = TestProviderHelper.CurrencyProvider.GetCurrencySafe("USD"); - var usdIrCurve = new ConstantRateIrCurve(0.05, vd, "USD-CURVE", usd) + var usdIrCurve = new ConstantRateIrCurve(0.05, vd, "USD.CURVE", usd) { SolveStage = -1, }; @@ -102,19 +104,33 @@ public void TestInflationPerfSwap() var pillars = new DateTime[] { vd, vd.AddYears(1).AddMonths(-1) }; var cpiRates = new double[] { 100, 150 }; - var cpiCurve = new CPICurve(vd, pillars, cpiRates, infIx, new Dictionary { { vd.AddMonths(-1), 100.0 } }) { Name = "USD-CPI" }; + var cpiCurve = new CPICurve(vd, pillars, cpiRates, infIx) { Name = "USD.CPI" }; + var fixingDict = new Dictionary { { vd.AddMonths(-1), 100.0 } }; var fModel = new FundingModel(vd, new IIrCurve[] { usdIrCurve, cpiCurve }, TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider); - - var infSwap1y = new InflationPerformanceSwap(vd, 1.Years(), infIx, 0.045, 1e6, Core.Basic.SwapPayReceiveType.Pay, "USD-CPI", "USD-CURVE") + fModel.AddFixingDictionary("USD.CPI", new FixingDictionary(fixingDict)); + var infSwap1y = new InflationPerformanceSwap(vd, 1.Years(), infIx, 0.045, 1e6, SwapPayReceiveType.Pay, "USD.CPI", "USD.CURVE") { - SolveCurve = "USD-CPI", + SolveCurve = "USD.CPI", }; - Assert.Equal(0.5, infSwap1y.CalculateParRate(fModel), 3); + Assert.Equal(0.5, infSwap1y.CalculateParRate(fModel), 2); Assert.NotEqual(0, infSwap1y.Pv(fModel, false), 3); infSwap1y = infSwap1y.SetParRate(infSwap1y.CalculateParRate(fModel)) as InflationPerformanceSwap; Assert.Equal(0, infSwap1y.Pv(fModel, false), 8); } + + + [Fact] + public void TestInfInterpolation() + { + var indexA = 100; + var indexB = 110; + var fDate = new DateTime(2023, 6, 15); + + var sut = InflationUtils.InterpFixing(fDate, indexA, indexB); + + Assert.Equal(105, sut, 8); + } } } diff --git a/test/Qwack.Core.Tests/Instruments/FICFacts.cs b/test/Qwack.Core.Tests/Instruments/FICFacts.cs index c96132eb..fdda0b69 100644 --- a/test/Qwack.Core.Tests/Instruments/FICFacts.cs +++ b/test/Qwack.Core.Tests/Instruments/FICFacts.cs @@ -23,7 +23,7 @@ public class FICFacts [Fact] public void FundingInstrumentCollection() { - var f = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider) + var f = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider) { new FxForward { SolveCurve = "1.blah" }, new FxForward { SolveCurve = "1.blah" }, @@ -39,7 +39,7 @@ public void FundingInstrumentCollection() [Fact] public void PillarDatesTest() { - var f = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider) + var f = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider) { new FxForward { SolveCurve = "usd.1blah", PillarDate = DateTime.Today }, new FxForward { SolveCurve = "usd.2blah", PillarDate = DateTime.Today.AddDays(1) }, @@ -57,7 +57,7 @@ public void PillarDatesTest() [Fact] public void ImplySolveStagesTest() { - var f = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider) + var f = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider) { new IrSwap { diff --git a/test/Qwack.Models.Tests/Risk/BenchmarkRiskFacts.cs b/test/Qwack.Models.Tests/Risk/BenchmarkRiskFacts.cs index f516f98d..2e85248c 100644 --- a/test/Qwack.Models.Tests/Risk/BenchmarkRiskFacts.cs +++ b/test/Qwack.Models.Tests/Risk/BenchmarkRiskFacts.cs @@ -110,13 +110,13 @@ public void BasicRiskMatrixFacts() Price = 95, PillarDate = _originDate.AddDays(180), }; - var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider) + var fic = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider) { f1, f2 }; - var cube = model.BenchmarkRisk(fic, TestProviderHelper.CurrencyProvider, usd); + var cube = model.BenchmarkRisk(fic, TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider, usd); var riskSum = cube.SumOfAllRows; diff --git a/test/benchmark/Qwack.Curves.Benchmark/SolvingOisBenchmark.cs b/test/benchmark/Qwack.Curves.Benchmark/SolvingOisBenchmark.cs index 0157a0fb..628135a9 100644 --- a/test/benchmark/Qwack.Curves.Benchmark/SolvingOisBenchmark.cs +++ b/test/benchmark/Qwack.Curves.Benchmark/SolvingOisBenchmark.cs @@ -77,7 +77,7 @@ public static void Setup() var ccySwaps = new XccyBasisSwap[oisTenors.Length]; - _instruments = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider); + _instruments = new FundingInstrumentCollection(TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider); for (var i = 0; i < FRATenors.Length; i++) {