Calculating Discount Margin on a floating rate bond using QuantLib

Going off Luigi's hint on this answer: Setting up Schedule for an amortizing floater in QuantLib

I was able to cobble something together but I'm unable to verify if it's correct. TLDR: I was able to generate this cashflow and got a Discount Margin value of 121.42091071060361 for the price input of 90.00. Any insights is appreciated.

First, let's set up the bond: It's a LIBOR+1.77 (vectors come from a third party).

• Issue Date: 03/06/1997
• Maturity: 03/06/2037
• Next payment: 03/05/2018
• Face: 12000000
• Floater: 1.77
• Reference Index: LIBOR_1MO (vectors: https://pastebin.com/HLYWsyux)

Setting up the bond: I use InterpolatedForwardCurve to setup my floating rates vectors and feed it to ForwardSpreadedTermStructure in order to accept a spread value. The basic idea is that since SimpleQuote, your input spread, is an observable, all changes to it will trigger recalculations in your bond class. I use this input in order to find the spread at which price exactly equals the current price (the price is an input; going from price to get DM).

DMFinder Function: My DMFinder inherits ISolver1d accepts a price as input. The value(x) implementation accepts a Spread value and tries to solve for v where the the resulting price exactly matches the original price; i.e. the spread value where the price exactly results in a null price at the current DM. As advised by @LuigiBallabio I'm also keeping an instance of the Bond class inside the DMFinder class to call cleanPrice() as the SimpleQuote value changes.

private class DMFinder : ISolver1d
{
private readonly FloatingRateBond bond_;
private SimpleQuote spread_;
private double price_;

public DMFinder(FloatingRateBond bond, double price, SimpleQuote spread)
{
this.bond_ = bond;
this.price_ = price;
this.spread_ = spread;
}

public override double value(double v)
{
this.spread.setValue(v);
var solvedPrice = bond.cleanPrice();
return this.price - solvedPrice;
}
}


Getting the DM through Solver1D:

public static double DM(FloatingRateBond bond, double price, SimpleQuote spread, double accuracy = 1.0e-10, int maxIterations = 100, double guess = 0.05)
{
var solver = new FiniteDifferenceNewtonSafe();
solver.SetMaxEvaluations(maxIterations);

var objFunction = new DMFinder(bond, price, spread);
var dm = solver.Solve(objFunction, accuracy, guess, guess / 10.0);
return dm;
}


Full bond setup: Full code below.

var settleDate = new Date(15, Month.Jan, 2018);                         // Settle date
var settlementDays = 3;                                                 // Settle day number, usually T+3
var faceAmount = 12000000.00;                                           // Current face
var issueDate = new Date(3, Month.Jun, 1997);                           // Issue Date
var maturity = new Date(3, Month.Jun, 2037);                            // Maturity Date

var thirty360 = new Thirty360();                                        // Day counter, using 30/360 convention.
var calendar = new UnitedStates(UnitedStates.Market.Settlement);        // Using USA settlement calendar for holidays/weekends detection
settleDate = calendar.adjust(settleDate);

// Evaluation must be a business day
var today = calendar.advance(settleDate, -settlementDays, TimeUnit.Days);
Settings.setEvaluationDate(today);      // Set business day to today

// An observable, holds the Spread argument
var spread = new SimpleQuote(0.0);

// Vectors are here: https://pastebin.com/HLYWsyux
var yieldCurve = new InterpolatedForwardCurve<Linear>(VECTOR_DATES, VECTORS, thirty360, calendar, new Linear());
yieldCurve.enableExtrapolation();
var spreadedYieldCurve = new ForwardSpreadedTermStructure(new Handle<YieldTermStructure>(yieldCurve), new Handle<Quote>(spread));
spreadedYieldCurve.enableExtrapolation();

var discountingTs = new Handle<YieldTermStructure>(spreadedYieldCurve);
var indexTs = new Handle<YieldTermStructure>(spreadedYieldCurve);

var index = new Libor("USD Libor",          // Family name
new Period(Frequency.Monthly),          // Frequency of of rates
2,                                      // Settlement days
new USDCurrency(),                      // Base currency
calendar,                               // Calendar used
thirty360,                              // Day counting convention
indexTs);                               // Class containing vectors and dates, as well as interpolation

// Generates the payment schedule, always start at issue date
var schedule = new Schedule(issueDate,
maturity,
new Period(Frequency.Quarterly),
calendar,
BusinessDayConvention.ModifiedFollowing,
BusinessDayConvention.ModifiedFollowing,
DateGeneration.Rule.Forward,
false);

// Setup the bond, issued 06/03/1997, libor+177, matures 06/03/2037
// Next payment: 03/05/2018
var bond = new FloatingRateBond(settlementDays,
faceAmount,
schedule,
index,
thirty360,
schedule.businessDayConvention(),
0,                                  // Fixing days
new List<double>() { 1, 1.0 },      // LIBOR multiplier (for example, 0.80 * libor)
new List<double>() { 1, 0.0177 },   // The spread, or "libor plus" => in this case,  x is 0.0177
new List<double?>() { },            // Caps
new List<double?>() { },            // Floors
true,                               // Index fixing in arrears?
100,                                // Percent of redemption at maturity
issueDate);                         // When bond was issued

var bondEngine = new DiscountingBondEngine(discountingTs);
bond.setPricingEngine(bondEngine);

// Sets the coupon pricer to the bond
var pricer = new BlackIborCouponPricer();
var vol = 0.0;
var volatility = new ConstantOptionletVolatility(3,
calendar,
BusinessDayConvention.ModifiedFollowing,
vol,
new Thirty360());

pricer.setCapletVolatility(new Handle<OptionletVolatilityStructure>(volatility));
HelperFunctions.setCouponPricer(bond.cashflows(), pricer);

// now calculate DM
var dm = Cashflows.DM(bond, 90, spread) * 100;

• What if you use yieldCurve instead of spreadedYieldCurve for discounting? Does the result make more sense? Commented Jan 18, 2018 at 17:11
• @LuigiBallabio It didn't work for me because it takes into account Spreads in the DM calculations. I needed a way to calculate DM without Spreads. See answer below.
– Kyle
Commented Feb 6, 2018 at 15:48

1 Answer

Update (2018-10-09):

This solution is more correct. It's a class that solves for the DM using the class ForwardSpreadedTermStructure.

public class DMFinder : ISolver1d
{
private readonly List<Cashflow> leg_;
private readonly double dm_;
private readonly DayCounter dayCounter_;
private readonly Compounding compounding_;
private readonly Frequency frequency_;
private readonly bool includeSettlementDateFlows_;
private readonly Date settlementDate_, npvDate_;
private readonly Handle<YieldTermStructure> discountCurve_;
private readonly ForwardSpreadedTermStructure forwardSpreadedTs_;
private readonly SimpleQuote spread_;

public DMFinder(List<Cashflow> leg,
double dm,
Handle<YieldTermStructure> discountCurve,
DayCounter dayCounter,
Compounding comp,
Frequency freq,
bool includeSettlementDateFlows,
Date settlementDate,
Date npvDate)
{
leg_ = leg;
dm_ = dm;
dayCounter_ = dayCounter;
compounding_ = comp;
frequency_ = freq;
includeSettlementDateFlows_ = includeSettlementDateFlows;
settlementDate_ = settlementDate;
npvDate_ = npvDate;
discountCurve_ = discountCurve;

if (settlementDate_ == null) settlementDate_ = Settings.EvaluationDate();
if (npvDate_ == null) npvDate_ = settlementDate_;

// Get the spread, starting at 0.0
// This will value will be what the solver will try to solve for
spread_ = new SimpleQuote(0.0);

// Declare a new Spreaded Discount Curve using the given curve and the spread
forwardSpreadedTs_ = new ForwardSpreadedTermStructure(discountCurve_, new Handle<Quote>(spread_));
forwardSpreadedTs_.EnableExtrapolation(discountCurve_.Instance.AllowsExtrapolation());
}

public override double Value(double v)
{
// The solver will set the value v continuously until a solution is found
this.spread_.SetValue(v);
var solvedDm = Cashflows.DM(leg_, forwardSpreadedTs_, includeSettlementDateFlows_, settlementDate_, npvDate_);

return dm_ - solvedDm;
}
}


This function discounts the cashflows given the spread:

/// <summary>
/// Given a discount curve, calculate the Discount Margin of the cashflows when
/// discounted against the given curve.
/// </summary>
/// <param name="leg">The schedule of cashflows.</param>
/// <param name="discountCurve">The discount curve that is spreaded via a simple quote.</param>
/// <param name="includeSettlementDateFlows">Specifies whether we want to include settlement date flows.</param>
/// <param name="settlementDate">the settlement date.</param>
/// <param name="npvDate">the npv date.</param>
/// <returns>The total calculated npv</returns>
public static double DM(Leg leg,
ForwardSpreadedTermStructure discountCurve,
bool includeSettlementDateFlows,
Date settlementDate = null,
Date npvDate = null)
{
if (leg.Empty()) return 0.0;

if (settlementDate == null)
settlementDate = Settings.EvaluationDate();

if (npvDate == null)
npvDate = settlementDate;

var iborCoupon = leg.FirstOrDefault(c => c is IborCoupon);
double cfSpread = 0.0;
if (iborCoupon != null)
cfSpread = (iborCoupon as IborCoupon).Spread();

// If the cashflow's spread is non-zero, take it away from the quote handler
// inside the ForwardSpreadedTermStruture
if(cfSpread > 0.0)
{
var dmSpreadHandler = discountCurve.Spread();
var dmSpread = dmSpreadHandler.Instance.Value();

// Take the cashflow's spread off of the  discount curve's spread
// By updating the value, we notify the ForwardSpreadedTermStructure to use the new value
(dmSpreadHandler.Instance as SimpleQuote).SetValue(dmSpread - cfSpread);
}

double totalDm = 0.0;

// Loop through the cashflows and calculate the total DM, based on the discount curve
foreach(var period in leg
.Where(l => !l.HasOccurred(settlementDate, includeSettlementDateFlows))
.GroupBy(l => l.Date())
.OrderBy(p => p.Key))
{
var date = period.Key;

// In each period, discount against the curve for each amount
foreach(var cashflow in period)
{
totalDm += cashflow.Amount() * discountCurve.Discount(date);
}
}

return totalDm / discountCurve.Discount(npvDate);
}


===

This is what I ended up implementing and has been verified to work by a colleague.

Quite similar to calculating yield, the DM function takes a cleanPrice and derives a dirtyPrice from the accrued amount.

public static double DM(Bond bond,
double cleanPrice,
DayCounter dayCounter,
Compounding compounding,
Frequency frequency,
Date settlementDate = null,
double accuracy = 1.0e-10,
int maxIterations = 100,
double guess = 0.05)
{
if (settlementDate == null)
settlementDate = bond.SettlementDate();

Thrower.AssertValid(BondFunctions.IsTradable(bond, settlementDate), () =>
\$"non tradable at {settlementDate} (maturity being {bond.MaturityDate()})");

double dirtyPrice = cleanPrice + bond.AccruedAmount(settlementDate);
dirtyPrice /= 100.0 / bond.Notional(settlementDate);

return Cashflows.DM(bond.Cashflows(), dirtyPrice,
dayCounter, compounding, frequency,
false, settlementDate, settlementDate,
accuracy, maxIterations, guess);
}


Once you have the target NPV (the dirtyPrice), you feed it to the next function, which does the actual calculations:

public static double DM(Leg leg,
double npv,
DayCounter dayCounter,
Compounding compounding,
Frequency frequency,
bool includeSettlementDateFlows,
Date settlementDate = null,
Date npvDate = null,
double accuracy = 1.0e-10,
int maxIterations = 100,
double guess = 0.05)
{
var solver = new FiniteDifferenceNewtonSafe();
solver.SetMaxEvaluations(maxIterations);
var objFunction = new DMFinder(leg,
npv,
dayCounter,
compounding,
frequency,
includeSettlementDateFlows,
settlementDate,
npvDate);

return solver.Solve(objFunction, accuracy, guess, guess / 10.0);
}


And this is the DMFinder function. It solves for DM by given a discountRate and stops solving when a given discountRate results in the original DM (NPV).

private class DMFinder : ISolver1d
{
private Leg leg;
private double dm;
private DayCounter dayCounter;
private Compounding compounding;
private Frequency frequency;
private bool includeSettlementDateFlows;
private Date settlementDate, npvDate;

public DMFinder(Leg leg,
double dm,
DayCounter dayCounter,
Compounding comp,
Frequency freq,
bool includeSettlementDateFlows,
Date settlementDate,
Date npvDate)
{
this.leg = leg;
this.dm = dm;
this.dayCounter = dayCounter;
this.compounding = comp;
this.frequency = freq;
this.includeSettlementDateFlows = includeSettlementDateFlows;
this.settlementDate = settlementDate;
this.npvDate = npvDate;

if (settlementDate == null)
settlementDate = Settings.EvaluationDate();

if (npvDate == null)
this.npvDate = this.settlementDate;
}

public override double Value(double v)
{
var discountRate = new InterestRate(v, this.dayCounter, this.compounding, this.frequency);
var solvedDm = Cashflows.DM(this.leg, discountRate, this.includeSettlementDateFlows, this.settlementDate, this.npvDate);
return this.dm - solvedDm;
}
}


And Finally, this is where the total DM calc happens. This is needed by the solver. It takes a given discountRate and calculates total DM. Note that it doesn't taken into account the given Spread (the floater margin):

/// <summary>
/// Gets the total DM given a discount rate.
/// </summary>
/// <param name="leg">The cashflows.</param>
/// <param name="dm">The discount rate.</param>
/// <param name="includeSettlementDateFlows">Indicates whether to includ cashflows that occurred on the settlement date.</param>
/// <param name="settlementDate">The settlement date.</param>
/// <param name="npvDate">The NPV date.</param>
/// <returns>The total DM.</returns>
public static double DM(Leg leg,
InterestRate dm,
bool includeSettlementDateFlows,
Date settlementDate = null,
Date npvDate = null)
{
if (leg.Empty()) return 0.0;

if (settlementDate == null)
settlementDate = Settings.EvaluationDate();

if (npvDate == null)
npvDate = settlementDate;

var totalDm = 0.0;
var discount = 1.0;
var lastRate = 0.0;
var lastSpread = 0.0;
var lastDate = npvDate;
Date refStartDate, refEndDate;

for(var i = 0; i< leg.Count; ++i)
{
if (leg[i].HasOccurred(settlementDate, includeSettlementDateFlows))
continue;

var couponDate = leg[i].Date();
var amount = leg[i].Amount();
if (leg[i].TradingExCoupon(settlementDate))
amount = 0.0;

var coupon = leg[i] as Coupon;
if(coupon != null)
{
refStartDate = coupon.ReferencePeriodStart;
refEndDate = coupon.ReferencePeriodEnd;
}
else
{
if (lastDate == npvDate)
{
// we don't have a previous coupon date,
// so we fake it
refStartDate = couponDate - new Period(1, TimeUnit.Years);
}
else
{
refStartDate = lastDate;
}
refEndDate = couponDate;
}

double l, h;
if (settlementDate > refStartDate && settlementDate < refEndDate)
l = dm.DayCounter().YearFraction(settlementDate, refEndDate, refStartDate, refEndDate);
else
{
l = dm.DayCounter().YearFraction(lastDate, couponDate, refStartDate, refEndDate);
// if (coupon != null) l = coupon.AccrualPeriod();
// else l = dm.DayCounter().YearFraction(lastDate, couponDate, refStartDate, refEndDate);
}

double rate;
if (coupon != null) rate = coupon.Rate();
else rate = lastRate; // Fake the last rate if the coupon doesn't come with a rate

var spread = 0.0;
if ((leg[i] as IborCoupon) != null) spread = (leg[i] as IborCoupon).Spread();
else spread = lastSpread;

h = rate + dm.Rate() - spread;
discount /= (1 + (l * h));

totalDm += discount * amount;

lastDate = couponDate;
lastRate = rate;
lastSpread = spread;
}

return totalDm;
}

• I see. So during the solving phase, you move the discounting curve but keep the coupon amounts fixed; my code was moving both. One doubt: as far as I can see, your solver seems to return the discount rate directly, much like what a yield calculation would do. Isn't the discount margin supposed to be a spread over libor? Commented Feb 6, 2018 at 16:19
• @LuigiBallabio See my edit, the Total DM calc. On the line h = rate + dm.Rate() - spread;, I needed to get the rate without the coupon's spread. I don't know why my coworker prefered this way... But he needed to see the DM without the floater margin.
– Kyle
Commented Feb 6, 2018 at 16:52
• That's because the rate minus the coupon spread is the libor. The rate you're using for discounting is libor + dm.Rate(), which makes dm.Rate() a spread over libor. This looks similar to what you would get from your original code if you wrote var discountingTs = new Handle<YieldTermStructure>(spreadedYieldCurve); and var indexTs = new Handle<YieldTermStructure>(yieldCurve); in the bond setup. Out of curiosity, may you try it and see if the figure you get is in the right ballpark? Commented Feb 6, 2018 at 17:11
• @LuigiBallabio Please see update. Turns out using the ForwardSpreadedTermStructure as you suggest is the correct way.
– Kyle
Commented Oct 9, 2018 at 14:22