lessCode.net
Links
AFFILIATES
Thursday
May142009

Discount/Zero Curve Construction in F# – Part 2 (Rolling Dates)

In part 1, we used the rollBy function to find the spot date from the curve date:

let spotDate = rollBy 2 RollRule.Following USD curveDate

Unsurprisingly, this rolls by two business days; according to the following roll rule; accounting for business holidays in the United States; the curve date. I guess there are several ways to implement this function – recursively is one way:

let rec rollBy n rule calendar (date:Date) =
    match n with
    | 0 -> date
    | x -> 
        match rule with
        | RollRule.Actual -> date.AddDays(float x)

        | RollRule.Following -> dayAfter date
                                |> roll rule calendar
                                |> rollBy (x - 1) rule calendar

        | _ -> failwith "Invalid RollRule"

Here there are two examples of pattern matching: firstly to determine when to end the recursion (when there are no more days by which we need to roll), and then to roll appropriately depending on the rule (just two rules are shown here for brevity). rollBy in turn makes use of the more basic functions dayAfter and roll:
 
let dayAfter (date:Date) = date.AddDays(1.0)
let rec roll rule calendar date =
    if isBusinessDay date calendar then
        date
    else
        match rule with
        | RollRule.Actual -> date
        | RollRule.Following -> dayAfter date |> roll rule calendar
        | _ -> failwith "Invalid RollRule"

Where dayAfter is not much more than an alias for readability, and roll is a function that rolls any day that happens to be a holiday or weekend. The real decision about the “goodness” of a business day is handled by the isBusinessDay function, a simple lookup into the calendar:
 
let isBusinessDay (date:Date) calendar = 
    not (calendar.weekendDays.Contains date.DayOfWeek || calendar.holidays.Contains date)

And to save space I’ll use a US holiday calendar for only 2009:
 
let USD = { weekendDays = Set [ System.DayOfWeek.Saturday; System.DayOfWeek.Sunday ]; 
            holidays = Set [ date "2009-01-01"; 
                             date "2009-01-19"; 
                             date "2009-02-16"; 
                             date "2009-05-25"; 
                             date "2009-07-03"; 
                             date "2009-09-07"; 
                             date "2009-10-12"; 
                             date "2009-11-11"; 
                             date "2009-11-26"; 
                             date "2009-12-25" ] }

Discount Dates

Now we have our quotes, and some routines to handle rolling to good business days, we can begin to establish the benchmark dates that will form the discount curve. First, we can pull out the quotes up to spot, and roll each quote by an appropriate number of days:
 
let spotPoints = quotes
                 |> List.choose (fun (t, q) -> 
                        match t with
                        | Overnight _ -> Some (rollBy 1 RollRule.Following USD curveDate, q)
                        | TomorrowNext _ -> Some (rollBy 2 RollRule.Following USD curveDate, q)
                        | _ -> None)
                 |> List.sortBy (fun (d, _) -> d)
This is our first look at list functions. Our quotes form a list, and List.choose is a function that allows us to iterate through the list, making a decision on each item as to whether or not we want to include it (the Some option), or exclude it (the None option) – exactly what we need to pull out the overnight and tomnext quotes, and roll the curve date appropriately in order to establish each discount point’s date:
 
image

Cash quotes get similar treatment, except that now, instead of rolling from the curve date, we’re offsetting from the spot date (and rolling if necessary):
 
let cashPoints = quotes
                 |> List.choose (fun (t, q) -> 
                        match t with
                        | Cash c -> Some (offset c spotDate |> roll RollRule.Following USD, q)
                        | _ -> None)
                 |> List.sortBy (fun (d, _) -> d)

Where offset simply adds the relevant number of days, months and/or years to a date:
 
let offset tenor (date:Date) = 
    date.AddDays(float tenor.days)
        .AddMonths(tenor.months)
        .AddYears(tenor.years)
 
image

Swaps – same story:
 
let swapPoints = quotes
                 |> List.choose (fun (t, q) -> 
                        match t with
                        | Swap s -> Some (offset s spotDate |> roll RollRule.Following USD, q)
                        | _ -> None)
                 |> List.sortBy (fun (d, _) -> d)
 
image

Futures – not nearly so simple. The June 2009 Eurodollar contract covers the three month period starting on the 3rd Wednesday in June, and ending on the 3rd Wednesday in September, so the quotes actually correspond to discount dates represented by the next futures contract in the schedule. This means we will interpolate the discount point for June (the start of the futures schedule), and add an extra point after the last quote (the end of the futures schedule):
let (sc, _) = List.hd futuresQuotes
let (ec, _) = futuresQuotes.[futuresQuotes.Length - 1]   
let futuresStartDate = findNthWeekDay 3 System.DayOfWeek.Wednesday sc 
                       |> roll RollRule.ModifiedFollowing USD
let futuresEndDate = (new Date(ec.Year, ec.Month, 1)).AddMonths(3)

// "invent" an additional contract to capture the end of the futures schedule
let endContract = (futuresEndDate, 0.0)
 
First we find the first and last futures quotes, and compute dates of the start and end of the futures schedule. Then we create the extra point covering the final contract. I’ve omitted findNthWeekDay, but it’s not complicated and fairly self-explanatory. Now for the real work:
 
let futuresPoints = Seq.append futuresQuotes [endContract]
                    |> Seq.pairwise
                    |> Seq.map (fun ((_, q1), (c2, _)) -> 
                        (findNthWeekDay 3 System.DayOfWeek.Wednesday c2 
                         |> roll RollRule.ModifiedFollowing USD, (100.0 - q1) / 100.0))
                    |> Seq.to_list
This pipeline operates on a sequence, which is the F# equivalent of IEnumerable<T>. Here we first append our extra contract, then use the Seq.pairwise function to iterate through the sequence two-by-two. This pairing allows us to take the value of the quote from the first of each pair, and combine it with the contract of the quote from the second of the pair. The effect is to “shift” the quote values down the schedule to their subsequent contract. At the same time, we find the correct contract date (and roll it if necessary), and convert the quote to a rate. At the end of this dance we have the following futures points ready for bootstrapping (in part 3).
 
image  
Tuesday
May052009

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:
 
image image

 

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.
Monday
Apr272009

To Begin With…

A few weeks ago at the Wintellect Devscovery conference in New York I was struck by a trio of coincidental insights. The first was from Scott Hanselman’s keynote, during which he suggested that there’s really very little reason why every developer shouldn’t have a personal blog. The second came from John Robbins’ session on debugging, where he reminded us that paper, pencil and our own brains are the best debugging tools we have.

I’ve gone through several methods of “thought management” over the years, ranging from the old Black n’ Red notebook to Microsoft OneNote. A while back one company I worked for required engineers (programming was engineering back then) to maintain a daily log of work and notes in an A4 Black n’ Red, and those books were ultimately the property of the company – how they expected to be able to assimilate or search that information I have no idea.

I like OneNote a lot, but it suffers slightly from the classic “fat client” problem, in that OneNote itself is an installed application that works primarily with local notebook files. It’s possible to put OneNote notebooks on a network share or a Live Mesh folder for sharing outside your LAN, but of course even then you need to have OneNote and/or Live Mesh installed on every machine from which you plan to access your notes. No kiosks need apply. And if you’d like to publish some of your more lucid thoughts…

So now, since I have some personal projects gathering source control dust, I’m going to try a personal blog for my notes. It’ll mean I’ll need to write up my notes a lot more coherently than I usually do, but as Robbins suggested for my third insightful reminder from that conference, oftentimes you solve a particularly gnarly problem after the first ten seconds of trying to explain it to someone else.

Page 1 ... 3 4 5 6 7