Bounded Variation Building a Quant Finance Monte Carlo Engine in Haskell - Part 1 Project maintained by boundedvariation Hosted on GitHub Pages — Theme by mattgraham

Introduction

My background is in quant finance, mostly writing C++, Python, and (shudder) Matlab. Over the last few months I've been learning Haskell, so as an excuse to learn some more Haskell, I decided to try to solve a fundamental quantitative finance problem in Haskell. Quant finance tends to love tangled OOP, so I figured a functional programming style might be a new twist on the problem.

I was trying to think of a good problem to start with, and I came up with the idea of a simple (but powerful) Monte Carlo engine for Haskell. With that in mind, I started to think about architecture. I'm going to write this up in a few posts, but my rough plan is:

Today's post: specification of contingent claims

Implementation of the Monte Carlo engine and underlying models

Odds and ends (implementation of simple yield curves, volatility surfaces, etc) and future plans

The target audience for this blog post is folks who know a bit of Haskell and are curious how it might apply to this particular problem.

Quick Sidebar: Quant Finance Basics

If you know even a little bit about quant finance, you can skip this. For the rest of you, a few quick pointers:

A contingent claim is just a payout of cash that is dependent on something happening. The simplest contingent claim is a binary option. A binary option pays a fixed amount of cash immediately if some underlying (usually a stock) is above or below a certain amount. For instance, a binary call on XYZ stock with a fixed payout of $10 and a "strike price" of $50 will pay $10 if XYZ is above $50 at the option's expiration. A Monte Carlo engine uses an underlying model to evolve values. Models specify a way of creating a growth rate and an idea of how the value will move (in some cases including a distribution). For instance, the most basic model (Black-Scholes), assumes that its values grow at a fixed interest rate and that they will follow a lognormal distribution. Models get very byzantine in some cases.

The ContingentClaim Type

The first thing any Monte Carlo engine needs is a way to specify your payoffs. The great Simon Peyton-Jones himself has worked on this very problem. His solution is, of course, quite good, but it's actually too general for our purposes. There are many types of contracts that can be expressed in his system that cannot be easily priced in a Monte Carlo framework.

With that in mind, let's think about what a contingent claim really is, specifically from the standpoint of what a Monte Carlo engine needs to know about it.

The MC engine needs to know what to look at. Is the option on Apple's stock or Microsoft's stock? Or maybe the ten-year treasury rate? For my purposes, I'll refer to the value being looked at as the "observable". The engine will need to know how to combine the different observations to turn them into a cash flow. The engine will need to know when to stop evolving and check the value of the observable.

The third of these is pretty easy to represent. For this one, we'll just create a list of times when the model needs to stop and check an observable. Easy enough.

Now for 1 and 2, let's make this a little simpler. What if every time we stop the model, we simply put a structure containing all of the observables into a Map, keyed off of a Time? Then we can finish specifying our contingent claim with a function with this type:

Map Time Observables -> CashFlow

Where a CashFlow is just:

data CashFlow = CashFlow { cfTime :: Time , cfAmount :: Double }

For now let's just imagine Observables is just a list of Doubles. In practice we're doing something better than that (see the bottom of this post for some details), but let's keep it simple for now.

After some iterations on the design, I came up with the following types to represent contingent claims.

data CCProcessor = CCProcessor { monitorTime :: Time , payoutFunc :: [ Map Time Observables -> CashFlow ] } newtype ContingentClaim = ContingentClaim { unCC :: [ CCProcessor ] }

If you can think about this from the standpoint of the Monte Carlo engine, the engine can tick through each element of the list. At each element it will evolve the simulation through until the given Time and put the Observables into a running Map. Once this is done it will run any payoutFunc that is available (or maybe multiple ones) and determine the cash flows that need to be processed.

Monoid to the Rescue

One nifty thing about this is that it's easy to put together a bunch of different contingent claims simply by interleaving them based on the Time field.

-- | Combines two contingent claims into one. combine :: ContingentClaim -> ContingentClaim -> ContingentClaim combine ( ContingentClaim x) ( ContingentClaim y) = ContingentClaim $ combine' x y where combine' (cc1:ccs1) (cc2:ccs2) | monitorTime cc1 == monitorTime cc2 = let CCProcessor t mf = cc1 CCProcessor _ mf' = cc2 in CCProcessor t (mf++mf') : combine' ccs1 ccs2 | monitorTime cc1 > monitorTime cc2 = cc2 : combine' (cc1:ccs1) ccs2 | otherwise = cc1 : combine' ccs1 (cc2:ccs2) combine' cs1 cs2 = cs1 ++ cs2

But wait a sec, this is neat:

instance Monoid ContingentClaim where mempty = ContingentClaim [] mappend = combine

So now I can very easily compose a bunch of different options together. That seems handy.

Monads Enter the Picture

Now let's think about what a few simple options would look like in this framework. A binary call option with a payout of 50 with a strike of 100 on the 0th observable, paying out at time 1, would look like this:

binCall = ContingentClaim [ 1.0 , [\m -> if m ! 1.0 !! 0 > 100 then 50 else 0 ]]

Okay, that's a bit ugly. Now imagine if I want a payout that averages the observable's price over ten different times? Trust me, I've tried, it isn't pretty.

So what we need is a way to build up a ContingentClaim in a nice composable way. Now the answer to every question in Haskell is "Make a monad!", and this is no different. So what I did was combined two monads, the Reader and Writer monad. The Reader monad (which is really just the function monad) allows me to build up my payout function in a pretty nice and composable way. Meanwhile the Writer monad keeps track of the times I'm checking the observable.

type CCBuilder w r a = WriterT w ( Reader r) a

We'll also need something to pull our completed ContingentClaim out of the monad. The details of the implementation are beyond the scope of this (and tedious anyway), so just make note of the type:

specify :: CCBuilder ContingentClaim ( Map Time Observables ) CashFlow -> ContingentClaim

Now let's make a function that checks an observable's value at a certain time. We'll call it monitor, and it'll check the 0th observable.

monitor :: Time -> CCBuilder ContingentClaim ( Map Observables Double ) Double monitor t = do tell $ ContingentClaim [ CCProcessor t [] ] --adds to the list of Times m <- ask --grabs the Map of values return $ (m ! t) !! 0 --Yes, I know how ugly this is.

The details of the Reader and Writer monad are beyond the scope of this post, but suffice it to say that this adds to the running total of times to check the observable (tell), and while getting the value out of the map to use in the Reader monad.

Now we have all we need to build composable contingent claims. A few examples:

binCall strike t payout = specify $ do x <- monitor t let amt = if x > strike then payout else 0 return ( CashFlow t amt) vanillaCall strike t = specify $ do x <- monitor t let amt = max (x - strike) 0 return ( CashFlow t amt) --Calculates the average price on a series of times. averagePrice priceTimes t = specify $ do x <- mapM monitor priceTimes let val = sum x / fromIntegral ( length x) return $ CashFlow t val --We can even do insane things bizarre = specify $ do x <- monitor 0.5 y <- monitor 1.0 z <- monitor 2.0 let t = if sin x > 0 then 7.0 else 2.0 --why would we do this??? return $ CashFlow t ( cos (y + z)) --let's put a binary option together with a vanilla option! portfolio = binCall 100 1 50 <> vanillaCall 100 1

Additionally the library contains some helpful functions like multiplier, which scales up a ContingentClaim payout by a constant factor. And of course I can make a function short = multiplier (-1) . So a call spread could look like this:

callSpread lowStrike highStrike t = vanillaCall lowStrike t <> short (vanillaCall highStrike t)

Neat!

Wrapping Up a Loose End - The Observables Type

I mentioned earlier that the Observables type is a bit more sophisticated than I was letting on. The issue is that right now I can do this:

unsafeMonitor t = do m <- ask return (m ! t !! 3 )

But what if 3 is out of bounds? With that in mind, I created types that represent fixed length vectors. For instance:

--I'm leaving out the UNPACK pragma and strictness annotations, --but you get the idea. data Observables1 = Observables1 Double data Observables2 = Observables2 Double Double -- ... class Obs1 a where obs1 :: a -> Double class Obs2 a where obs2 :: a -> Double instance Obs1 Observables1 where obs1 ( Observables1 x) = x instance Obs1 Observables2 where obs1 ( Observables2 x _) = x instance Obs2 Observables2 where obs2 ( Observables2 _ x) = x -- ...

Enough of that, you get the idea. Now a quick rewrite of the unsafeMonitor function:

monitor t = do tell $ ContingentClaim [ CCProcessor t [] ] m <- ask return (obs4 $ m ! t)

One last change:

data CCProcessor a = CCProcessor { monitorTime :: Time , payoutFunc :: [ M . Map Time a -> CashFlow ] } newtype ContingentClaim a = ContingentClaim { unCC :: [ CCProcessor a] } --we'll need RankNTypes --contingent claims with one observable. type ContingentClaim1 = forall a . Obs1 a => ContingentClaim a --contingent claims with two observables. type ContingentClaim2 = forall a . Obs2 a => ContingentClaim a -- ...

And the type checker will now ensure that you're not accessing something that isn't being modeled. More on this when we get into the model implementation details.

Conclusion

You almost certainly haven't had as much fun reading this as I had writing the library. Haskell completely changes the way you solve problems, and while the abstractions are unfamiliar at first, they're remarkably powerful. It's still very much pre-pre-alpha but under heavy development.

In the mean time, check out the package on GitHub or Hackage.

Next time we'll cover a little bit about how we put together the engine itself and implemented models.