Skip to content

Commit

Permalink
More inflation derivative support
Browse files Browse the repository at this point in the history
Better SOFR support via OIS futures
  • Loading branch information
gavbrennan committed Apr 21, 2023
1 parent 981bc6b commit 19c6f1d
Show file tree
Hide file tree
Showing 7 changed files with 336 additions and 6 deletions.
4 changes: 2 additions & 2 deletions src/Qwack.Core/Curves/CPICurve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ public IIrCurve BumpRateFlat(double delta, bool mutate)

public double GetReturn(DateTime startDate, DateTime endDate)
{
var cpiStart = _cpiInterp.Interpolate(startDate.ToOADate());
var cpiEnd = _cpiInterp.Interpolate(endDate.ToOADate());
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;
}

Expand Down
164 changes: 164 additions & 0 deletions src/Qwack.Core/Instruments/Funding/InflationPerformanceSwap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
using System;
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 InflationPerformanceSwap : IFundingInstrument, ISaCcrEnabledIR
{
public Dictionary<string, string> MetaData { get; set; } = new Dictionary<string, string>();
public InflationPerformanceSwap() { }

public InflationPerformanceSwap(DateTime startDate, Frequency swapTenor, InflationIndex rateIndex, double parRate, double notional,
SwapPayReceiveType swapType, string forecastCurveCpi, string discountCurve)
{
SwapTenor = swapTenor;
ResetFrequency = rateIndex.ResetFrequency;
StartDate = startDate;
EndDate = StartDate.AddPeriod(rateIndex.RollConvention, rateIndex.HolidayCalendars, SwapTenor);
ParRate = parRate;
BasisFloat = rateIndex.DayCountBasis;
BasisFixed = rateIndex.DayCountBasisFixed;
SwapType = swapType;
RateIndex = rateIndex;
Currency = rateIndex.Currency;
Notional = notional;
ResetDates = new[] { StartDate, EndDate };

ForecastCurveCpi = forecastCurveCpi;
DiscountCurve = discountCurve;

T = StartDate.CalculateYearFraction(EndDate, BasisFixed);
FixedFlow = (System.Math.Pow(1.0 + parRate, T) - 1.0) * Notional * (swapType == SwapPayReceiveType.Payer ? -1.0 : 1.0);
}

public double FixedFlow { get; set; }
public double T { get; set; }

public double Notional { get; set; }
public double ParRate { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int NDates { get; set; }
public DateTime[] ResetDates { get; set; }
public Currency Currency { get; set; }

public DayCountBasis BasisFixed { get; set; }
public DayCountBasis BasisFloat { get; set; }
public Frequency ResetFrequency { get; set; }
public Frequency SwapTenor { get; set; }
public SwapPayReceiveType SwapType { get; set; }
public string ForecastCurveCpi { get; set; }
public string DiscountCurve { get; set; }
public string SolveCurve { get; set; }
public DateTime PillarDate { get; set; }
public string TradeId { get; set; }
public string Counterparty { get; set; }
public InflationIndex RateIndex { get; set; }
public string PortfolioName { get; set; }

public DateTime LastSensitivityDate => EndDate;

public List<string> Dependencies(IFxMatrix matrix) => (new[] { DiscountCurve, ForecastCurveCpi }).Distinct().Where(x => x != SolveCurve).ToList();

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);

var cpiLegFv = cpiPerf * Notional * (SwapType == SwapPayReceiveType.Payer ? 1.0 : -1.0);
var fixedLegFv = FixedFlow;

var df = discountCurve.GetDf(model.BuildDate, EndDate);

return (cpiLegFv + fixedLegFv) * df;
}

public Dictionary<string, Dictionary<DateTime, double>> Sensitivities(IFundingModel model)
{
throw new NotImplementedException();
}

public double CalculateParRate(IFundingModel model)
{
var forecastCurveCpi = model.Curves[ForecastCurveCpi];

var cpiPerf = (forecastCurveCpi as CPICurve).GetReturn(StartDate, EndDate);

return System.Math.Pow(cpiPerf + 1, 1 / T) - 1;
}

public IFundingInstrument Clone() => new InflationPerformanceSwap
{
BasisFixed = BasisFixed,
BasisFloat = BasisFloat,
Currency = Currency,
Counterparty = Counterparty,
DiscountCurve = DiscountCurve,
EndDate = EndDate,
FixedFlow = FixedFlow,
ForecastCurveCpi = ForecastCurveCpi,
NDates = NDates,
Notional = Notional,
ParRate = ParRate,
PillarDate = PillarDate,
ResetDates = ResetDates,
ResetFrequency = ResetFrequency,
SolveCurve = SolveCurve,
StartDate = StartDate,
SwapTenor = SwapTenor,
SwapType = SwapType,
TradeId = TradeId,
RateIndex = RateIndex,
PortfolioName = PortfolioName,
HedgingSet = HedgingSet
};

public IFundingInstrument SetParRate(double parRate) => new InflationPerformanceSwap(StartDate, SwapTenor, RateIndex, parRate, Notional, SwapType, ForecastCurveCpi, DiscountCurve)
{
TradeId = TradeId,
SolveCurve = SolveCurve,
PillarDate = PillarDate,
RateIndex = RateIndex,
PortfolioName = PortfolioName,
HedgingSet = HedgingSet
};

public double TradeNotional => System.Math.Abs(Notional);
public virtual double EffectiveNotional(IAssetFxModel model, double? MPOR = null) => SupervisoryDelta(model) * AdjustedNotional(model) * MaturityFactor(model.BuildDate, MPOR);
public double AdjustedNotional(IAssetFxModel model) => TradeNotional * SupervisoryDuration(model.BuildDate);
private double tStart(DateTime today) => today.CalculateYearFraction(StartDate, DayCountBasis.Act365F);
private double tEnd(DateTime today) => today.CalculateYearFraction(EndDate, DayCountBasis.Act365F);
public double SupervisoryDuration(DateTime today) => SaCcrUtils.SupervisoryDuration(tStart(today), tEnd(today));
public virtual double SupervisoryDelta(IAssetFxModel model) => (SwapType == SwapPayReceiveType.Pay ? 1.0 : -1.0) * System.Math.Sign(Notional);
public double MaturityFactor(DateTime today, double? MPOR = null) => MPOR.HasValue ? SaCcrUtils.MfMargined(MPOR.Value) : SaCcrUtils.MfUnmargined(tEnd(today));
public string HedgingSet { get; set; }
public int MaturityBucket(DateTime today) => tEnd(today) <= 1.0 ? 1 : tEnd(today) <= 5.0 ? 2 : 3;

public List<CashFlow> ExpectedCashFlows(IAssetFxModel model)
{
var discountCurve = model.FundingModel.Curves[DiscountCurve];
var forecastCurveCpi = model.FundingModel.Curves[ForecastCurveCpi];

var cpiPerf = (forecastCurveCpi as CPICurve).GetReturn(StartDate, EndDate);

var cpiLegFv = cpiPerf * Notional * (SwapType == SwapPayReceiveType.Payer ? 1.0 : -1.0);
var fixedLegFv = FixedFlow;

return new List<CashFlow>
{
new CashFlow { Fv = cpiLegFv, Currency = Currency, SettleDate = EndDate },
new CashFlow { Fv = fixedLegFv, Currency = Currency, SettleDate = EndDate },
};
}

public double SuggestPillarValue(IFundingModel model) => ParRate;
}
}
5 changes: 4 additions & 1 deletion src/Qwack.Core/Instruments/Funding/OISFuture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ public double Pv(IFundingModel Model, bool updateState)
return PV;
}

private List<DateTime> _avgDates;
public double CalculateParRate(IFundingModel Model)
{
var forecastCurve = Model.Curves[ForecastCurve];
var cal = Index?.HolidayCalendars ?? Currency.SettlementCalendar;
double fwdRate = 0;
if (Model?.BuildDate > AverageStartDate && forecastCurve is IrCurve fc && fc.Fixings!=null)
{
var avgDates = AverageStartDate.BusinessDaysInPeriod(AverageEndDate, cal);
var avgDates = _avgDates ?? AverageStartDate.BusinessDaysInPeriod(AverageEndDate, cal);
var rates = avgDates.Select(d => fc.Fixings.TryGetValue(d, out var x) ? x : 0).ToArray();
var index = 1.0;
for(var i=0;i< avgDates.Count-1; i++)
Expand All @@ -62,6 +63,8 @@ public double CalculateParRate(IFundingModel Model)
}
var bigD = AverageEndDate.Subtract(AverageStartDate).TotalDays;
fwdRate = (index - 1) * 360.0 / bigD;

_avgDates = avgDates;
}
else
fwdRate = forecastCurve.GetForwardRate(AverageStartDate, AverageEndDate, RateType.Linear, Index.DayCountBasis);
Expand Down
39 changes: 38 additions & 1 deletion test/Qwack.Core.Tests/Inflation/InflationCurveFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,50 @@ public void TestInflationCurve()
var S = new NewtonRaphsonMultiCurveSolverStaged
{
JacobianBump = 0.01,
Tollerance = 0.00000001,
Tollerance = 0.00000001,
MaxItterations = 100,
};

S.Solve(model, fic);

Assert.DoesNotContain(rates, double.IsNaN);
}

[Fact]
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)
{
SolveStage = -1,
};

var infIx = new InflationIndex
{
Currency = usd,
DayCountBasis = DayCountBasis.Act_Act,
DayCountBasisFixed = DayCountBasis.Act_Act,
FixingInterpolation = Interpolator1DType.Linear,
FixingLag = 1.Months(),
ResetFrequency = 1.Years()
};

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<DateTime, double> { { vd.AddMonths(-1), 100.0 } }) { Name = "USD-CPI" };

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")
{
SolveCurve = "USD-CPI",
};

Assert.Equal(0.5, infSwap1y.CalculateParRate(fModel), 3);
Assert.NotEqual(0, infSwap1y.Pv(fModel, false), 3);
infSwap1y = infSwap1y.SetParRate(infSwap1y.CalculateParRate(fModel)) as InflationPerformanceSwap;
Assert.Equal(0, infSwap1y.Pv(fModel, false), 8);
}
}
}
126 changes: 126 additions & 0 deletions test/Qwack.Core.Tests/Instruments/OISFutureFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,131 @@ public void OISFuturePastFixings()
Assert.Equal(expectedPar, par, 8);
}

[Fact]
public void OISFutureRealSofrFixings()
{
var bd = new DateTime(2023, 04, 20);
var pillars = new[] { bd, bd.AddDays(1000) };
var flatRate = 0.05;
var rates = pillars.Select(p => flatRate).ToArray();
CalendarProvider.Collection.TryGetCalendar("LON", out var cal);
var usd = TestProviderHelper.CurrencyProvider["USD"];

var curve = new IrCurve(pillars, rates, bd, "USD.BLAH", Interpolator1DType.Linear, usd);

var ix = new FloatRateIndex
{
Currency = usd,
DayCountBasis = DayCountBasis.ACT360,
FixingOffset = 0.Bd(),
HolidayCalendars = cal,
ResetTenor = 3.Months(),
RollConvention = RollType.MF
};

var maturity = bd.AddDays(-1);
var accrualStart = maturity.SubtractPeriod(RollType.F, ix.HolidayCalendars, 3.Months()).ThirdWednesday();
var accrualEnd = maturity;
var dcf = accrualStart.CalculateYearFraction(accrualEnd, DayCountBasis.ACT360);
var fModel = new FundingModel(bd, new[] { curve }, TestProviderHelper.CurrencyProvider, TestProviderHelper.CalendarProvider);

var sut = new OISFuture()
{
Currency = usd,
ContractSize = 1e6,
Position = 1,
DCF = dcf,
AverageStartDate = accrualStart,
AverageEndDate = accrualEnd,
ForecastCurve = "USD.BLAH",
Price = 100,
Index = ix
};

var fixings = _sofrFixings.ToDictionary(x => DateTime.Parse(x.Key), x => x.Value/100.0);
curve.Fixings = fixings;

var par = sut.CalculateParRate(fModel);
var edsp = 95.3850;
Assert.Equal(edsp, par, 4);
}

readonly Dictionary<string, double> _sofrFixings = new Dictionary<string, double>()
{
{"2023-04-19", 4.8},
{"2023-04-18", 4.8},
{"2023-04-17", 4.8},
{"2023-04-14", 4.8},
{"2023-04-13", 4.8},
{"2023-04-12", 4.8},
{"2023-04-11", 4.8},
{"2023-04-10", 4.81},
{"2023-04-06", 4.81},
{"2023-04-05", 4.81},
{"2023-04-04", 4.83},
{"2023-04-03", 4.84},
{"2023-03-31", 4.87},
{"2023-03-30", 4.82},
{"2023-03-29", 4.83},
{"2023-03-28", 4.84},
{"2023-03-27", 4.81},
{"2023-03-24", 4.8},
{"2023-03-23", 4.8},
{"2023-03-22", 4.55},
{"2023-03-21", 4.55},
{"2023-03-20", 4.55},
{"2023-03-17", 4.55},
{"2023-03-16", 4.57},
{"2023-03-15", 4.58},
{"2023-03-14", 4.55},
{"2023-03-13", 4.55},
{"2023-03-10", 4.55},
{"2023-03-09", 4.55},
{"2023-03-08", 4.55},
{"2023-03-07", 4.55},
{"2023-03-06", 4.55},
{"2023-03-03", 4.55},
{"2023-03-02", 4.55},
{"2023-03-01", 4.55},
{"2023-02-28", 4.55},
{"2023-02-27", 4.55},
{"2023-02-24", 4.55},
{"2023-02-23", 4.55},
{"2023-02-22", 4.55},
{"2023-02-21", 4.55},
{"2023-02-17", 4.55},
{"2023-02-16", 4.55},
{"2023-02-15", 4.55},
{"2023-02-14", 4.55},
{"2023-02-13", 4.55},
{"2023-02-10", 4.55},
{"2023-02-09", 4.55},
{"2023-02-08", 4.55},
{"2023-02-07", 4.55},
{"2023-02-06", 4.55},
{"2023-02-03", 4.55},
{"2023-02-02", 4.56},
{"2023-02-01", 4.31},
{"2023-01-31", 4.31},
{"2023-01-30", 4.3},
{"2023-01-27", 4.3},
{"2023-01-26", 4.3},
{"2023-01-25", 4.31},
{"2023-01-24", 4.3},
{"2023-01-23", 4.3},
{"2023-01-20", 4.3},
{"2023-01-19", 4.31},
{"2023-01-18", 4.3},
{"2023-01-17", 4.31},
{"2023-01-13", 4.3},
{"2023-01-12", 4.3},
{"2023-01-11", 4.3},
{"2023-01-10", 4.31},
{"2023-01-09", 4.31},
{"2023-01-06", 4.31},
{"2023-01-05", 4.31},
{"2023-01-04", 4.3},
{"2023-01-03", 4.31},
};
}
}
2 changes: 1 addition & 1 deletion version.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project>
<PropertyGroup>
<VersionPrefix>0.7.14</VersionPrefix>
<VersionPrefix>0.7.15</VersionPrefix>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.7.14
0.7.15

0 comments on commit 19c6f1d

Please sign in to comment.