Discount/Zero Curve Construction in F# – Part 1 (The End)
I wanted to learn a little bit about F# by implementing something more interesting than the obligatory fractals or Fibonacci sequences, so I thought I’d see what yield curve construction would look like, and how much less code would be needed versus a standard C/C++ implementation. The simplicity with which it’s possible to tail recurse over lists in F# lends itself well to the bootstrapping techniques employed for constructing a zero-coupon rate curve.
F# seems to read better from right to left and from bottom to top, so we’ll start with what might be the final step in building an interest rate discount curve, and work back from there:
let curve = [ (curveDate, 1.0) ] |> bootstrap spotPoints |> bootstrapCash spotDate cashPoints |> bootstrapFutures futuresStartDate futuresPoints |> bootstrapSwaps spotDate USD swapPoints |> Seq.sortBy (fun (d, _) -> d)
This is a forward pipe, whereby the result of one expression or function in the pipeline is silently passed as the last argument to the next function, allowing composition in stages, but still quite concisely. We start with today’s money (each dollar of which is obviously worth one dollar today), as a tuple (Date * double), and separately bootstrap the short cash up to spot (typically 2 days from today in the US dollar market), longer cash out to a few months, several Eurodollar futures and some dollar swaps, and finally sort the sequence chronologically. The result is an ordered sequence of Date * double tuples that represent the full discount curve:
What goes in?
Let’s look at what feeds the process. The curve date would typically be today’s date:
let curveDate = Date.Today
Spot, in the US market is usually two business days. We’ll get to the details of business day rolling conventions later on, but for now:
let spotDate = rollBy 2 RollRule.Following USD curveDate
A collection of quotes would be something like the list below. For illustration purposes we’ll use cash deposits out to three months, a small range of futures and swaps for the rest of the curve out to thirty years:
let quotes = [ (Overnight, 0.045); (TomorrowNext, 0.045); (Cash (tenor "1W"), 0.0462); (Cash (tenor "2W"), 0.0464); (Cash (tenor "3W"), 0.0465); (Cash (tenor "1M"), 0.0467); (Cash (tenor "3M"), 0.0493); (Futures (contract "Jun2009"), 95.150); (Futures (contract "Sep2009"), 95.595); (Futures (contract "Dec2009"), 95.795); (Futures (contract "Mar2010"), 95.900); (Futures (contract "Jun2010"), 95.910); (Swap (tenor "2Y"), 0.04404); (Swap (tenor "3Y"), 0.04474); (Swap (tenor "4Y"), 0.04580); (Swap (tenor "5Y"), 0.04686); (Swap (tenor "6Y"), 0.04772); (Swap (tenor "7Y"), 0.04857); (Swap (tenor "8Y"), 0.04924); (Swap (tenor "9Y"), 0.04983); (Swap (tenor "10Y"), 0.0504); (Swap (tenor "12Y"), 0.05119); (Swap (tenor "15Y"), 0.05201); (Swap (tenor "20Y"), 0.05276); (Swap (tenor "25Y"), 0.05294); (Swap (tenor "30Y"), 0.05306) ]
All of these quotes are tuples, with a second value of type float, representing the market quote itself. The type of the first value in the tuple we make a discriminated union, to distinguish the different types of instrument:
type QuoteType = | Overnight // the overnight rate (one day period) | TomorrowNext // the one day period starting "tomorrow" | Cash of Tenor // cash deposit period in days, weeks, months | Futures of FuturesContract // year and month of futures contract expiry | Swap of Tenor // swap period in years
A futures contract we can model simply as a date, and a tenor is generically a combination of year, month and/or day offsets:
type Date = System.DateTime type FuturesContract = Date type Tenor = { years:int; months:int; days:int }
Date and FuturesContract amount to little more than aliases for the Framework Class Library type DateTime, providing us with a shorthand notation, or clarifying the code. To that end we also throw in some helper functions for creating instances by parsing text. For tenors we use a regular expression match that should cover all the bases:
let date d = System.DateTime.Parse(d) let contract d = date d let tenor t = let regex s = new Regex(s) let pattern = regex ("(?<weeks>[0-9]+)W" + "|(?<years>[0-9]+)Y(?<months>[0-9]+)M(?<days>[0-9]+)D" + "|(?<years>[0-9]+)Y(?<months>[0-9]+)M" + "|(?<months>[0-9]+)M(?<days>[0-9]+)D" + "|(?<years>[0-9]+)Y" + "|(?<months>[0-9]+)M" + "|(?<days>[0-9]+)D") let m = pattern.Match(t) if m.Success then { new Tenor with years = (if m.Groups.["years"].Success then int m.Groups.["years"].Value else 0) and months = (if m.Groups.["months"].Success then int m.Groups.["months"].Value else 0) and days = (if m.Groups.["days"].Success then int m.Groups.["days"].Value else if m.Groups.["weeks"].Success then int m.Groups.["weeks"].Value * 7 else 0) } else failwith "Invalid tenor format. Valid formats include 1Y 3M 7D 2W 1Y6M, etc"
In part 2 we’ll look into the details of preparing the raw quotes for bootstrapping, which will include converting quotes into dates and rolling those dates to account for weekends and holidays.