Discount/Zero Curve Construction in F# – Part 4 (Core Math)
All that’s left to cover in this simplistic tour of discount curve construction is to fill in the implementations of the core mathematics underpinning the bootstrapping process.
computeDf simply calculates the next discount factor based on a previously computed one:
let computeDf fromDf toQuote = let dpDate, dpFactor = fromDf let qDate, qValue = toQuote (qDate, dpFactor * (1.0 / (1.0 + qValue * dayCountFraction { startDate = dpDate; endDate = qDate })))
Where for dayCountFraction we’ll assume an Actual/360 day count convention, but this could be generalized to pass the day counting method as a function parameter to computeDf:
let dayCountFraction period = double (period.endDate - period.startDate).Days / 360.0
findDf looks up a discount factor on the curve, for a given date, interpolating if necessary. Again, here tail recursion and pattern matching make this relatively clean:
let rec findDf interpolate sampleDate = function // exact match (dpDate:Date, dpFactor:double) :: tail when dpDate = sampleDate -> dpFactor // falls between two points - interpolate | (highDate:Date, highFactor:double) :: (lowDate:Date, lowFactor:double) :: tail when lowDate < sampleDate && sampleDate < highDate -> interpolate sampleDate (highDate, highFactor) (lowDate, lowFactor) // recurse | head :: tail -> findDf interpolate sampleDate tail // falls outside the curve | [] -> failwith "Outside the bounds of the discount curve"
logarithmic does logarithmic interpolation for a date that falls between two points on the discount curve. This function is passed by value as the interpolate parameter to findDf above:
let logarithmic (sampleDate:Date) highDp lowDp = let (lowDate:Date), lowFactor = lowDp let (highDate:Date), highFactor = highDp lowFactor * ((highFactor / lowFactor) ** (double (sampleDate - lowDate).Days / double (highDate - lowDate).Days))
Newton’s Method is quite straightforward in F#:
let newton f df (guess:double) = guess - f guess / df guess
To recursively solve using Newton’s Method to a given accuracy:
let rec solveNewton f df accuracy guess = let root = (newton f df guess) if abs(root - guess) < accuracy then root else solveNewton f df accuracy root
And all that remains are the functions that feed Newton; the price of the market swap and its first derivative – note that this is certainly not the most efficient way to do this, because to compute the derivative we recalculate the price in order to approximate the derivative with a finite-difference method:
let deriv f x = let dx = (x + max (1e-6 * x) 1e-12) let fv = f x let dfv = f dx if (dx <= x) then (dfv - fv) / 1e-12 else (dfv - fv) / (dx - x)
let computeSwapDf dayCount spotDate swapQuote discountCurve swapSchedule (guessDf:double) = let qDate, qQuote = swapQuote let guessDiscountCurve = (qDate, guessDf) :: discountCurve let spotDf = findDf logarithmic spotDate discountCurve let swapDf = findPeriodDf { startDate = spotDate; endDate = qDate } guessDiscountCurve let swapVal = let rec _computeSwapDf a spotDate qQuote guessDiscountCurve = function swapPeriod :: tail -> let couponDf = findPeriodDf { startDate = spotDate; endDate = swapPeriod.endDate } guessDiscountCurve _computeSwapDf (couponDf * (dayCount swapPeriod) * qQuote + a) spotDate qQuote guessDiscountCurve tail | [] -> a _computeSwapDf -1.0 spotDate qQuote guessDiscountCurve swapSchedule spotDf * (swapVal + swapDf)
And finally, the zero coupon rates can be found with something like the following direct mapping, on the basis that there are 365 days in a year:
let zeroCouponRates = discs |> Seq.map (fun (d, f) -> (d, 100.0 * -log(f) * 365.0 / double (d - curveDate).Days))
All in all, I think that a functional language like F# provides a much simpler means to code these types of calculation over a typical C or C++ implementation (notwithstanding performance considerations, which I’ve yet to study in any depth, but I have a hunch that with the advent of cloud computing and massively multicore hardware, that for this kind of work it’s going to become less and less about “straight-line” speed in clock cycles or operations, and more and more about the simplicity with which we can reason about such code in order to find inherent functional parallelism).
I’m also wondering about the benefits of functional programming when it comes to pricing certain derivative trades with payoff formulae that might be maintained by an end-user-trader or quantitative analyst without requiring anything we’d currently regard as “software engineering”. This question touches on the use of functional languages as Domain-Specific Languages (DSLs), and may be the topic of a future series of posts as time permits…