In part one we build a couple of simple frameworks that allow the programmer to specify that a value should be cached until one of its dependencies changed. I wanted to put these frameworks though their paces in a semi realistic environment.

I decided to use the idea of pricing european options, since it’s similar to what we do at LexiFi, rather that work we the LexiFi pricer I took the simplest pricer I could find written in OCaml and adapted it to F# (this was hardly any work at all). I did this as I simply wanted the price to provide some work, I did not care about the accuracy of the process.

Note: Do not use the pricer used here to price real options, it is too simple to give an accurate price.

Our pricer exposes a nice and simple interface that consists of passing it a series of floats and an integer: simple_monte_carlo1 expiry strike spot vol r num_paths. I decided I want to change the spot and the volatility (vol) at random intervals while increasing the work load by increasing the number of paths.

To do this I wrote a testing framework that expected to receive a number of functions that would actually perform the work of doing the test. This code is shown below:

let runTest name f set_paths set_spot set_vol =/o:p

    let stopwatch = new Stopwatch();/o:p

    for paths in [100000.0 .. 100000.0 .. 1000000.0] do/o:p

        set_paths paths/o:p

        for odds in [0 .. 5] do/o:p

            let times = new ResizeArray<int64>()/o:p

            for index in indexes do/o:p

                let setValue name set get =/o:p

                    if rand.Next(odds) = 0 then/o:p

                        let spot = (get index) /o:p

                        if printAll then/o:p

                            printfn ”%s %s Changed to %f” name index spot/o:p

                        set index spot/o:p

                setValue “Spot” set_spot get_spot/o:p

                setValue “Vol” set_vol get_vol/o:p

                stopwatch.Reset()/o:p

                stopwatch.Start()/o:p

                let res = f index /o:p

                stopwatch.Stop()/o:p

                times.Add(stopwatch.ElapsedMilliseconds)/o:p

                if printAll then/o:p

                    let printer =/o:p

                        match outputFormat with/o:p

                        | Readable -> printfn “type = %s, result = %f, paths = %f, odds = %i, index = %s, time taken = %i” /o:p

                        | Csv -> printfn ”%s;%f;%f;%i;%s;%i”/o:p

                    printer name res paths odds index stopwatch.ElapsedMilliseconds/o:p

            let avgTime = (times |> Seq.fold (+) 0L) / (int64 indexes.Length)/o:p

            let printer =/o:p

                match outputFormat with/o:p

                | Readable -> printfn “type = %s, paths = %f, odds = %i, avg time = %i” /o:p

                | Csv -> printfn ”%s;%f;%i;%i”/o:p

            printer name paths odds avgTime/o:p

 /o:p

The parameter name is a name for recording the results, f is the function that does the work and set_paths, set_spot and set_vol control the parameters that the function f should use. Impmenting these frameworks is fairly straight forward, implementing the auto-enroll framework is shown below, the others are available for download as they are very similar:

let acal, aset_paths, aset_spot, aset_vol = /o:p

    let state = new AutoEnrollFramework.StateWrapper<float>()/o:p

    let paths = state.NewNode(AutoEnrollFramework.Value 100.0)/o:p

    let set_paths x =/o:p

        paths.Value <- AutoEnrollFramework.Value x/o:p

    let ftseSpot = state.NewNode(AutoEnrollFramework.Value ftseSpotBase)/o:p

    let cacSpot = state.NewNode(AutoEnrollFramework.Value cacSpotBase)/o:p

    let nasdaqSpot = state.NewNode(AutoEnrollFramework.Value nasdaqSpotBase)/o:p

    let set_spot index x =/o:p

        match index with/o:p

        | “FTSE_100” -> ftseSpot.Value <- AutoEnrollFramework.Value x/o:p

        | “CAC_40” -> cacSpot.Value <- AutoEnrollFramework.Value x/o:p

        | “NASDAQ_100” -> nasdaqSpot.Value <- AutoEnrollFramework.Value x/o:p

        | _ -> failwith “unknown index”/o:p

    let ftseVol = state.NewNode(AutoEnrollFramework.Value ftseVolBase)/o:p

    let cacVol = state.NewNode(AutoEnrollFramework.Value cacVolBase)/o:p

    let nasdaqVol = state.NewNode(AutoEnrollFramework.Value nasdaqVolBase)/o:p

    let set_vol index x =/o:p

        match index with/o:p

        | “FTSE_100” -> ftseVol.Value <- AutoEnrollFramework.Value x/o:p

        | “CAC_40” -> cacVol.Value <- AutoEnrollFramework.Value x/o:p

        | “NASDAQ_100” -> nasdaqVol.Value <- AutoEnrollFramework.Value x/o:p

        | _ -> failwith “unknown index”/o:p

    let ftseCal =/o:p

        state.NewNode(AutoEnrollFramework.Calculation (fun () -> /o:p

                MonteCarlo.simple_monte_carlo1 0.2 160.0 ftseSpot.Value ftseVol.Value 0.045 (int paths.Value)))/o:p

    let cacCal =/o:p

        state.NewNode(AutoEnrollFramework.Calculation (fun () -> /o:p

                MonteCarlo.simple_monte_carlo1 0.2 160.0 cacSpot.Value cacVol.Value 0.045 (int paths.Value)))/o:p

    let djCal =/o:p

        state.NewNode(AutoEnrollFramework.Calculation (fun () -> /o:p

                MonteCarlo.simple_monte_carlo1 0.2 160.0 cacSpot.Value cacVol.Value 0.045 (int paths.Value)))/o:p

    let cal index =/o:p

            match index with/o:p

            | “FTSE_100” -> ftseCal.Value/o:p

            | “CAC_40” -> cacCal.Value/o:p

            | “NASDAQ_100” -> djCal.Value/o:p

            | _ -> failwith “unknown index”/o:p

    cal, set_paths, set_spot, set_vol/o:p

So once the results have been correlated and loaded into excel we get the following picture:

This is pretty much what we expected for the no caching case we see a very strong correlation between the number of paths and the time take. With the other two case we see that the time taken dips as cached results are used with the time rising as when there is more chance of the value being recalculated. The worst cases of the caching scenarios are almost exactly equal to the results for the no caching case, showing that in this test very little over head is added by the framework. This because we remove our values from the cache before performing the work, we do not fetch the result from the cache at each computation. If we did fetch the calculation from cache each time, we might expect the worst case value to increase considerable for the caching scenarios – but then what I have implemented in the test is probably what you’d do in a real programming situation.

The full listing for this test and the results gathered are available for download here.

In the next it this series we’ll look at how we can use the frameworks to automatically visualise calculations and their results using reflection and WinForms.

Feedback:

Feedback was imported from my only blog engine, it’s no longer possible to post feedback here.

Robert on &quot;Recalculating Values Only When Dependencies Change&quot; - Don Syme’s WebLog on F# and Other Research Projects

Robert Pickering has just posted his second article on the topic of incremental evaluation in F# . His