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? – Luigi Ballabio Jan 18 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 Feb 6 at 15:48

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? – Luigi Ballabio Feb 6 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 Feb 6 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? – Luigi Ballabio Feb 6 at 17:11
  • @LuigiBallabio Please see update. Turns out using the ForwardSpreadedTermStructure as you suggest is the correct way. – Kyle Oct 9 at 14:22

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.