-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added CreditDefaultSwap Valuation Analytics
- Loading branch information
Showing
6 changed files
with
250 additions
and
89 deletions.
There are no files selected for viewing
42 changes: 42 additions & 0 deletions
42
ProjectX.AnalyticsLib.Tests/CreditDefaultSwapFunctionsTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using QLNet; | ||
|
||
namespace ProjectX.AnalyticsLib.Tests; | ||
|
||
public class CreditDefaultSwapFunctionsTest | ||
{ | ||
[Test] | ||
public void WhenValuingCdsPVUsingFlatInterestCurveItShouldReturnValidPV() | ||
{ | ||
var evalDate = new DateTime(2009, 6, 15); | ||
var effectiveDate = new DateTime(2009, 3, 20); | ||
var maturityDate = new DateTime(2014, 6, 20); | ||
var spreadsInBps = new int[] { 210 }; | ||
var tenors = new string[] { "5Y" }; | ||
var recoveryRate = 0.4; | ||
var couponInBps = 100; | ||
var notional = 10_000; | ||
Protection.Side protectionSide = Protection.Side.Buyer; | ||
var interestRate = 0.15; | ||
var actual = CreditDefaultSwapFunctions.PV( | ||
evalDate, | ||
effectiveDate, | ||
maturityDate, | ||
spreadsInBps, | ||
tenors, | ||
recoveryRate, | ||
couponInBps, | ||
notional, | ||
protectionSide, | ||
interestRate); | ||
Assert.That(actual.SurvivalProbabilityPercentage, Is.EqualTo(82).Within(1)); | ||
Assert.That(actual.DefaultProbabilityPercentage, Is.EqualTo(17).Within(1)); | ||
Assert.That(actual.HazardRatePercentage, Is.EqualTo(3).Within(1)); | ||
Assert.That(actual.PV, Is.EqualTo(405).Within(1), "PV must be equal to expected value within tolerance"); | ||
Assert.That(actual.FairSpread, Is.EqualTo(224).Within(1), "Fair spread must be equal to expected value within tolerance"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using System.Text; | ||
using QLNet; | ||
|
||
namespace ProjectX.AnalyticsLib; | ||
|
||
public class CreditDefaultSwapFunctions | ||
{ | ||
public static CreditDefaultSwapPVResult PV( | ||
DateTime evaluationDate, | ||
DateTime effectiveDate, | ||
DateTime maturityDate, | ||
int[] spreadsInBps, | ||
string[] tenors, | ||
double recoveryRate, | ||
int couponInBps, | ||
int notional, | ||
Protection.Side protectionSide, | ||
double flatInterestRate) | ||
{ | ||
Calendar calendar = new TARGET(); | ||
var evalDate = calendar.adjust(evaluationDate.ToQuantLibDate()); | ||
var settlementDate = calendar.advance(evalDate, new Period(2, TimeUnit.Days)); | ||
var maturity = maturityDate.ToQuantLibDate(); | ||
var flatInterestRateQuote = new Handle<Quote>(new SimpleQuote(flatInterestRate)); | ||
var curve = new RelinkableHandle<YieldTermStructure>(); | ||
var flatCurve = new FlatForward(evalDate, flatInterestRateQuote, new Actual365Fixed()); | ||
curve.linkTo(flatCurve); | ||
|
||
Settings.setEvaluationDate(evalDate); | ||
|
||
// compute hazard rates based on spreads (basis points) and corresponding tenors | ||
List<Date> dates = [evalDate]; | ||
List<double> hazardRates = [0.0]; | ||
for (int i = 0; i < tenors.Length; i++) | ||
{ | ||
var period = tenors[i].ToPeriod(); | ||
var spread = spreadsInBps[i] / 10_000.0; | ||
var date = evalDate + period; | ||
|
||
// implied hazard rates by building a cds | ||
var cdsValuation = new MakeCreditDefaultSwap(period, spread) | ||
.withNominal(10_000_000.0) | ||
.value(); | ||
var hazardRate = cdsValuation.impliedHazardRate( | ||
0.0, | ||
curve, | ||
new Actual365Fixed(), | ||
recoveryRate, | ||
1E-10, | ||
PricingModel.Midpoint | ||
); | ||
dates.Add(date); | ||
hazardRates.Add(hazardRate); | ||
} | ||
hazardRates[0] = hazardRates[1]; | ||
|
||
// bootstrap hazard rates | ||
RelinkableHandle<DefaultProbabilityTermStructure> piecewiseFlatHazardRate = new RelinkableHandle<DefaultProbabilityTermStructure>(); | ||
var hazardRateCurve = new InterpolatedHazardRateCurve<Linear>(dates, hazardRates, new Actual365Fixed()); | ||
piecewiseFlatHazardRate.linkTo(hazardRateCurve); | ||
piecewiseFlatHazardRate.link.enableExtrapolation(); | ||
|
||
// build instrument | ||
var schedule = new Schedule(effectiveDate.ToQuantLibDate(), maturity, new Period(Frequency.Annual), calendar, BusinessDayConvention.Following, BusinessDayConvention.Following, DateGeneration.Rule.Twentieth, false); | ||
CreditDefaultSwap cds = new CreditDefaultSwap(protectionSide, notional, couponInBps / 10_000.0, schedule, BusinessDayConvention.ModifiedFollowing, new Actual365Fixed()); | ||
cds.setPricingEngine(new MidPointCdsEngine(piecewiseFlatHazardRate, recoveryRate, curve)); | ||
cds.recalculate(); | ||
|
||
// results | ||
var pv = cds.NPV(); | ||
var fairSpread = 10000.0 * cds.fairSpread(); | ||
var survivalProbabilityPercentage = 100.0 * piecewiseFlatHazardRate.link.survivalProbability(maturity); | ||
var hazardRatePercentage = 100.0 * piecewiseFlatHazardRate.link.hazardRate(maturity); | ||
var defaultProbabilityPercentage = 100.0 * piecewiseFlatHazardRate.link.defaultProbability(maturity); | ||
|
||
return (pv, fairSpread, survivalProbabilityPercentage, hazardRatePercentage, defaultProbabilityPercentage); | ||
} | ||
} | ||
|
||
public record struct CreditDefaultSwapPVResult(double PV, double FairSpread, double SurvivalProbabilityPercentage, double HazardRatePercentage, double DefaultProbabilityPercentage) | ||
{ | ||
public static implicit operator (double pv, double fairSpread, double survivalProbabilityPercentage, double hazardRatePercenatge, double defaultProbabilityPercentage)(CreditDefaultSwapPVResult value) | ||
{ | ||
return (value.PV, value.FairSpread, value.SurvivalProbabilityPercentage, value.HazardRatePercentage, value.DefaultProbabilityPercentage); | ||
} | ||
|
||
public static implicit operator CreditDefaultSwapPVResult((double pv, double fairSpread, double survivalProbabilityPercentage, double hazardRatePercenatge, double defaultProbabilityPercentage) value) | ||
{ | ||
return new CreditDefaultSwapPVResult(value.pv, value.fairSpread, value.survivalProbabilityPercentage, value.hazardRatePercenatge, value.defaultProbabilityPercentage); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using QLNet; | ||
|
||
namespace ProjectX.AnalyticsLib; | ||
|
||
public static class Extensions | ||
{ | ||
public static QLNet.Date ToQuantLibDate(this DateTime dt) => new QLNet.Date((int)dt.ToOADate()); | ||
public static DateTime ToDatetime(this QLNet.Date date) => Convert.ToDateTime(date.month() + " " + date.Day.ToString() + ", " + date.year().ToString()); | ||
public static Period[] ToPeriods(this string[] tenors) => tenors.Select(t => t.ToPeriod()).ToArray(); | ||
public static Period ToPeriod(this string tenor) => new(int.Parse(tenor[..^1]), ToTimeUnits(tenor[^1])); | ||
private static TimeUnit ToTimeUnits(char timeUnit) | ||
{ | ||
var u = Char.ToLower(timeUnit); | ||
switch (u) | ||
{ | ||
case 'm': return TimeUnit.Months; | ||
case 'y': return TimeUnit.Years; | ||
default: | ||
throw new NotSupportedException($"{timeUnit} conversion not supported"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters