- List processing
- Higher-order function
- Function currying
- Function pipelining
There are a few things to note about the code example:
- Normal random numbers are generated using Box-Muller transform.
- The number of simulation runs is 10,000,000.
- The time function is used to measure CPU time and absolute time.
- The seed to initiate System.Random is deliberately fixed as 1 so that I can get the same series of normal random numbers every time I run the code. If you don't want this, please remember to remove the seed.
- The underlying dynamics and European call option details are as follows:
- S0 (spot price) = 50.0
- vol (annualized volatility) = 20%
- risk free rate = 1%
- strike = 40.0
- expiration (in years) = 0.25
- As a sanity check, the closed-form theoretical price based on the parameters above is 10.11842.
option_price = 10.118465
CPU time = 5709ms
Absolute time = 7115ms
It took about 7 seconds to run 10 million simulations. And the simulated price agrees with the theoretical price up to 4 decimal places.
Finally, here is the code:
let N = 10000000
let rnd = new Random(1)
let randomPairs = List.init N (fun i -> (rnd.NextDouble(), rnd.NextDouble()))
let box_muller_transform (u1,u2) = sqrt(-2.0*log(u1)) * cos(2.0*Math.PI*u2)
let randomNormals = randomPairs |> List.map box_muller_transform
let call_payout K S = max (S-K) 0.0
let European_Option_Price (payout:float->float) S0 r vol T =
let S_T x = S0 * exp((r - 0.5*(vol**2.0))*T + vol*sqrt(T)*x)
randomNormals |> List.map S_T |> List.map payout |> List.average
|> (*) (exp(-r*T))
let S0 = 50.0
let K = 40.0
let r = 0.01
let vol = 0.2
let T = 0.25
let price() =
let price = European_Option_Price (call_payout K) S0 r vol T
printfn "option_price = %f" price
let time f =
let proc = Process.GetCurrentProcess()
let cpu_time_stamp = proc.TotalProcessorTime
let timer = new Stopwatch()
let cpu_time = (proc.TotalProcessorTime-cpu_time_stamp).TotalMilliseconds
printfn "CPU time = %dms" (int64 cpu_time)
printfn "Absolute time = %dms" timer.ElapsedMilliseconds