HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Discount curve from instant rate

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
curverateinstantdiscountfrom

Problem

This my first foray in F#, and I cannot think (yet) functional. I have implemented an interface for a discount curve and the implementation for a piecewise constant instant rate implementation.

I would like to have your opinion on how to make this more F#. Should I keep the second input of the constructor as an array or a sequence?

Also, as I pre-compute all possible values between the first point and the end_date, I could have done a tail recursion and lazily populate the cache, but I was wondering about efficiency: I did not have time to write the tail-recursion and do a timing comparison.

module public DiscountCurve = 
    type IDiscountCurve =
       abstract member Df : System.DateTime -> double

    type DfPieceWiseInstant(end_date:System.DateTime, points:(System.DateTime * double)[]) =
        let start_date = fst(points.[0])
        let end_date = end_date
        let cache : double array = Array.zeroCreate ((end_date - start_date).Days + 1)
        let points = points
        do
            cache.[0] <- 1.        
            for idx in { 0 .. (points.Length - 1) } do
                let cache_idx_start = max 1 (fst(points.[idx]) - start_date).Days
                let cache_idx_end = 
                    if idx+1 < points.Length then
                        (fst(points.[idx+1]) - start_date).Days - 1
                    else
                        (end_date - start_date).Days - 1
                let cache_idx_df = exp (-snd(points.[idx])/365.)
                for cache_date in seq { cache_idx_start .. cache_idx_end-1 } do
                    cache.[cache_date] <- cache.[cache_date-1] * cache_idx_df

        interface IDiscountCurve with 
            member this.Df(date) = 
                cache.[(date - start_date).Days]

Solution

I can't see how tail recursion comes into the picture exactly, but I would start with putting a stop watch around the do that populates the cache and checking how much work it actually needs to do, just to see whether you couldn't get away with what you have now. There's certainly some value in computing only the part of the curve you need, but I'd check how much that would gain you before making that code more complicated.

For starters, you certainly don't need let bindings for end_date and points. You already have them accessible anywhere within your type as they're constructor arguments. You can also drop the {} and seq {} in you for ... in ... do expressions.

A quick and simple way to delay populating of the cache until it's needed is to make it lazy.

let cache = 
     Lazy.Create 
        let cache = Array.zeroCreate ((end_date - start_date).Days + 1)
        (* the entirety of your 'do' goes here *)
        cache

interface IDiscountCurve with 
    member this.Df(date) = 
        cache.Value.[(date - start_date).Days]


This way it will only be populated once Df is called, and no work would be done on object's construction.

Code Snippets

let cache = 
     Lazy.Create <| fun () ->
        let cache = Array.zeroCreate ((end_date - start_date).Days + 1)
        (* the entirety of your 'do' goes here *)
        cache

interface IDiscountCurve with 
    member this.Df(date) = 
        cache.Value.[(date - start_date).Days]

Context

StackExchange Code Review Q#58323, answer score: 2

Revisions (0)

No revisions yet.