Visualising Black-Scholes option pricing using F# and WPF

The other day I was looking for an example of some code relevant to finance that I could use as a test for experimenting with Windows Presentation Foundation (WPF) and F#. I decided I could use a simple Black-Scholes option pricer combined with WPF to easily visualise how the option inputs affect the value and greeks. For example, given all the usual Black-Scholes assumptions, and a non-zero interest rate, you’d expect the value of an at-the-money call option to increase as time to expiry increases, whereas a put option would decrease in value.

Calculating option values using F#

The first thing you need is an approximation to the cumulative normal distribution. I used the Abromowitz and Stegun approximation, which should give us enough precision. It’s fairly concise to implement in F#:

let ncdf x =
  let b1 =  0.319381530
  let b2 = -0.356563782
  let b3 =  1.781477937
  let b4 = -1.821255978
  let b5 =  1.330274429
  let p  =  0.2316419
  let c  =  0.39894228
  match x with
  | x when x >= 0.0 ->
    let t = 1.0 / (1.0 + p * x)
    (1.0 - c * Math.Exp( -x * x / 2.0)* t * (t*(t*(t*(t*b5+b4)+b3)+b2)+b1))
  | _ ->
    let t = 1.0 / (1.0 - p * x)
    (c * Math.Exp( -x * x / 2.0)* t * (t*(t*(t*(t*b5+b4)+b3)+b2)+b1))

The CDF should be near 0 for low values, around 0.5 for zero, and near 1 for high values. Let’s check:

> [-5.0; 0.0; 5.0] |> ncdf;;
val it : float list = [2.871049992e-07; 0.500000001; 0.9999997129]

Looks good.

Now let’s put it to use by implementing the option pricing formula for call options:

let call strike spot (rate:float) (now:float) (expiry:float) (vol:float) =
    let exp = expiry-now
    let d1 = (Math.Log(spot/strike) + ((rate+(vol*vol)/2.0)*exp))/(vol * Math.Sqrt(exp))
    let d2 = d1 - (vol * (Math.Sqrt(exp)))
    (spot * ncdf d1) - (strike * Math.Pow(Math.E, -rate*exp)*(ncdf d2)), ncdf d1

Notice that the function returns a tuple of (value,delta). Give that the greeks are available analytically we may as well return them at the same time as the value.

We can poke this a bit to check that it’s doing the right thing. A call option that’s way in-the-money (i.e. spot > strike) near to expiry, has a lot of intrinsic value:

> call 10.0 100.0 0.0 0.0 0.1 0.5;;
val it : float * float = (90.0, 1.0)

An at-the-money call option should have a delta close to 0.5 at expiry:

> call 10.0 10.0 0.0 0.0 0.1 0.5;;
val it : float * float = (0.6301280626, 0.5315064031)

All seems fine.

So lets see what we can do about visualising these relationships.

Using WPF with F#

You’d be forgiven for thinking that the XAML markup language is the only way to construct user interfaces in WPF. Indeed, if you want to avoid writing code it’s the way to go. But unfortunately it’s not possible to use F# as the code-behind XAML files, so you’re forced to use C#. And it’s also not as dynamic or immediate as it could be; involving the usual write, compile, run iteration. We can do better in F# using scripts and F# interactive (FSI).

The key is to construct the WPF object model imperatively, rather than declaratively with XAML. Yeah, I know this is normally the opposite of what you’d want to do.

For instance, here’s some code to create a Canvas. Note the use of a construction expression to both create the object and set some of it’s properties. This is just syntactic sugar which helps to make the object look slightly less mutable than it actually is:

    let c = new Canvas(Name="Canvas", Width=250.0, Height=250.0)
    c.Background <- SolidColorBrush(Color.FromRgb(228uy,228uy,228uy))

We can use the WPF canvas panel to enable us to explicitly place points (or rather, Rectangles) on our plot, it uses absolute locations rather than the implied flow layout of the other containers (Grid, StackPanel etc). Putting this together, we can write a function which will take a parent Panel, and a list of (x,y) tuples and create a plot:

let plot (panel:Panel) (pts:(double * double) list) =
    let bgc = new Canvas(Name="Canvas", Width=300.0, Height=300.0)
    let c = new Canvas(Name="Canvas", Width=250.0, Height=250.0)
    c.SetValue(Canvas.LeftProperty, 25.0)
    c.SetValue(Canvas.TopProperty, 25.0)
    c.Background <- SolidColorBrush(Color.FromRgb(228uy,228uy,228uy))
    let x,y = pts |> List.unzip
    let xmin,xmax = x |> List.min, x |> List.max
    let ymin,ymax = y |> List.min, y |> List.max
    let xscale = c.Width / (xmax-xmin)
    let yscale = c.Height / (ymax-ymin)
    |> (fun pt ->
        let p = new Rectangle(Width=2.0,Height=2.0)
        p.Stroke <- new SolidColorBrush(Color.FromRgb(0uy,0uy,0uy))
        p.SetValue( Canvas.LeftProperty, (fst pt - xmin) * xscale)
        p.SetValue( Canvas.TopProperty, c.Height - (snd pt - ymin) * yscale)
    |> List.iter (fun rect ->
        ignore <| c.Children.Add rect)
    ignore <| bgc.Children.Add (fst (makeYScale ymin ymax))
    ignore <| bgc.Children.Add (fst (makeXScale xmin xmax))
    ignore <| bgc.Children.Add c
    ignore <| panel.Children.Add bgc

The function returns nothing (unit in F# parlance) and performs a side-effecting modify of the passed Panel. I've omitted the makeXScale and makeYScale functions, as they're a bit gnarly, and don't really serve to demonstrate anything in particular.

Now we need to create some data to plot. We can do this in many ways, but one thing I looked at was creating a set of call option values with varying time to expiry. I thought this could use a floating point sequence, but it generated a deprecation warning, so I decided to do it long hand instead. This function returns a list of (time,value) tuples, where time range from rfrom to rto in num steps and value is calculated using the passed function f.

let varyExpiry f num rfrom rto =
    List.init num (fun n ->
        let s = rfrom + ((float n/float num) * (rto-rfrom))
        float s, fst <| f 10.0 10.0 0.05 0.0 s 0.5 )

We can pass the resulting list directly to our plot function, but first we need to create a window to display it in:

let w = new Window(Name="Plot",Width=500.0,Height=500.0)
let wp = new WrapPanel()
w.Content <- wp
w.Visibility <- Visibility.Visible

Executing this code in FSI will display a blank window (probably behind your active window if you're within Visual Studio). Then we can add the plot to the graph:

plot wp (varyExpiry call 100 10.0 100.0)

Voila! Obviously the plot is very, very basic, but it's an interesting experiment, and it does makes it extremely easy to plot functions interactively in almost the same way as you can with applications like Excel and MatLab. Pretty powerful.

Call option values for varying expiries
Call option values for varying expiries
Call and put option values for varying expiries
Call and put option values for varying expiries

  • sandeep

    Hi Ian,

    Nice example!

    Are functional programming languages making an impact in Finance? Given that performance is a major criteria in most situations, how does F# stack up? Also, what attributes of functional PLs are most useful in finance related development?


  • ian

    Yes, in my experience they are. I’m aware of a few investment banks using functional (or mixed functional and imperative) programming languages such as Haskell, ML and F# to varying degrees.

    My opinion is that they’re more natural for maths-oriented quants than languages like C++. Less worrying about pointers and memory management, and more focus on algorithms.

  • Pingback: Generating and plotting random numbers()

  • soap

    good example, but pls note typo in black-scholes formula where you calculate d1, the term ln(S/K) also needs to be divided by sigma*sqrt(T), and a way-in-the-money call option should have a delta practically 1.

  • Thanks, you’re right, I need to add a bracket otherwise it associates wrongly. With that fixed the delta of the ITM call becomes 1.0 (rather than 0.99).

    I’ll update the article.

  • Pingback: Black-Scholes formula with PV and greeks – using DiffSharp in F# | sw2code()