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(VECTOR_DATES, VECTORS, thirty360, calendar, new Linear());
yieldCurve.enableExtrapolation();
var spreadedYieldCurve = new ForwardSpreadedTermStructure(new Handle(yieldCurve), new Handle(spread));
spreadedYieldCurve.enableExtrapolation();
var discountingTs = new Handle(spreadedYieldCurve);
var indexTs = new Handle(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() { 1, 1.0 }, // LIBOR multiplier (for example, 0.80 * libor)
new List() { 1, 0.0177 }, // The spread, or "libor plus" => in this case, x is 0.0177
new List() { }, // Caps
new List() { }, // 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(volatility));
HelperFunctions.setCouponPricer(bond.cashflows(), pricer);
// now calculate DM
var dm = Cashflows.DM(bond, 90, spread) * 100;
No comments:
Post a Comment