Home > F#, FSharpChart > Fun with F# Charting. Factoring out FSharpChart

Fun with F# Charting. Factoring out FSharpChart

February 18, 2012 Leave a comment Go to comments

FSharpChart wraps .NET 4.0 charting control. Here is Don’s entry about it to get started and download the assembly with examples.

However, FSharpChart is still a control that needs WinForms or WPF to work. What about some Matlab-esque type of functionality, where a simple function call would do the job without having to worry about much wiring?

An example (full sources available here) is a simple risk minimization model, where given a portfolio of assets and their expected performance, we must choose the weights for each asset so that standard deviation (risk) of the portfolio is minimal. The model and its solution are described, for instance, here. It is easy enough to implement it using Math.Net package.

module RiskMinimizationFormulation =
    
    open System
    open MathNet.Numerics.FSharp
    open MathNet.Numerics.LinearAlgebra.Double
    open modelling.shared

    type RiskMinimization(expected : Vector, correlations : Matrix, stdDeviations : Vector) =
        do
            if correlations.RowCount <> correlations.ColumnCount 
            then 
                invalidArg "variances" "expected square matrix" 

            if expected.Count <> correlations.RowCount
            then
                invalidArg "expected" "expectations vector must have the same length as variance matrix dimensions" 

        let variances = 
            correlations 
            |> Matrix.mapi 
                (fun i j value -> 
                    let mutable v = value
                    if correlations.[i,j] = 0.0 
                    then 
                        v <- correlations.[j, i]
                    stdDeviations.[i] * stdDeviations.[j] * v)
        let n = expected.Count
        let i = vector (List.init n (fun i -> 1.))

        let variancesInv = variances.Inverse()
        let a = i * variancesInv * i
        let b = i * variancesInv * expected
        let c = expected * variancesInv * expected
        let denom = 1. / (a * c - b*b)
        let g = denom * (variancesInv * (c * i - b * expected))
        let h = denom * (variancesInv * (a * expected - b * i))

        member rm.ComputeOptimal (expectation : float) =
            g + h * expectation

We compute all the auxiliary values at instantiation and the function of interest is ComputeOptimal, that given an expectation produces a vector of weights.

Suppose we were to chart weights of our portfolio for a range of expectations. Let’s say expectations are values 0.05..0.12..0.05 ([5%, 12%], with a step of 0.5%). Our charting function could look something like this:

        member rm.ChartOptimalWeights (expectations : float list) (names : string seq) =
            let data = matrix(expectations |> List.map (fun v -> (rm.ComputeOptimal v |> Vector.toList)))

            let plotData =  seq {for i in 0 .. data.ColumnCount - 1 -> data.Column(i).ToArray() |> Array.toList} 

            Charting.Plot(
                "Line", 
                plotData,
                plotX = (expectations |> List.map(fun v -> v * 100.)), 
                xTitle = "Expected Return",
                yTitle = "Asset Weight",
                xLimits = (5., 12.),
                yLimits = (-2.0, 3.0),
                seriesNames = names,
                title = "Risk Minimization Model")

We call ComputeOptimal for each of the values in range and get a vector in return. We then construct a matrix out of these vectors, making each vector a row of the new matrix. Then our series are in the columns of the resulting matrix. For instance, for a test case with the range of expectations described above and a portfolio of four assets, we get a 4 x 24 matrix, where each row represents a portfolio. We want to chart the dynamic of each asset weight as function of expectation, so our series actually are in the columns of this matrix.

The function of interest is Charting.Plot. This function wraps the necessary functionality of FSharpChart in a general way to produce a chart of given type:

    type Charting () =  

        static member private createChartOfType ((chartType : string), name, (y : seq<#IConvertible * #IConvertible>))  =
            let innerTps = FSharpType.GetTupleElements(y.GetType().GetGenericArguments().[0])
            findAndCreateChart chartType innerTps name y

        static member private createChartOfType ((chartType : string), name, (y : seq<#IConvertible>))  =
            let innerTp = y.GetType().GetGenericArguments().[0]
            findAndCreateChart chartType [|innerTp|] name y
  
        static member Plot 
            (
            chartType : string,
            plotY : #seq<#IConvertible> seq, 
            ? plotX : #seq<#IConvertible>,
            ? seriesNames : string seq,
            ? title : string,
            ? xTitle : string,
            ? yTitle : string, 
            ? xLimits : float * float, 
            ? yLimits : float * float, 
            ? margin : float32 * float32 * float32 * float32) =

            let marg = defaultArg margin (4.0f, 12.0f, 4.0f, 4.0f)
            let chartTitle = defaultArg title "Chart"
            let xTitle = defaultArg xTitle String.Empty
            let yTitle = defaultArg yTitle String.Empty
            let chartNames = defaultArg seriesNames (plotY |> Seq.mapi(fun i v -> "Series " + i.ToString()))
            if (chartNames |> Seq.length) <> (plotY |> Seq.length) then invalidArg "names" "not of the right length"
            
            // zip up the relevant information together
            // x-values go with every y-series values in a tuple
            // series names gets zipped with every sequence of (x, y) tuples: (name, seq(x, seq(y)))
            let mutable chart = 
                match plotX with
                |Some plotX ->
                    let plot = plotY |> Seq.map(fun s -> List.zip plotX s) |> Seq.zip chartNames
                    FSharpChart.Combine ([for p in plot -> Charting.createChartOfType (chartType, (fst p), (snd p))])

                | None -> 
                    let plot = plotY |> Seq.zip chartNames
                    FSharpChart.Combine ([for p in plot -> Charting.createChartOfType (chartType, (fst p), (snd p))])

            
            //add x and y limits
            chart <- 
                match xLimits with
                | Some (xMin, xMax) -> FSharpChart.WithArea.AxisX(Minimum = xMin, Maximum= xMax, MajorGrid = Grid(LineColor = Color.LightGray)) chart
                | None -> FSharpChart.WithArea.AxisX(MajorGrid = Grid(LineColor = Color.LightGray)) chart

            chart <-
                match yLimits with
                | Some (yMin, yMax) -> FSharpChart.WithArea.AxisY(Minimum = yMin, Maximum= yMax, MajorGrid = Grid(LineColor = Color.LightGray)) chart
                | None -> FSharpChart.WithArea.AxisY(MajorGrid = Grid(LineColor = Color.LightGray)) chart
                //... and margin
                |> FSharpChart.WithMargin marg

            //set the titles
            chart.Area.AxisX.Title <- xTitle
            chart.Area.AxisY.Title <- yTitle

            //add legend
            chart <- 
                FSharpChart.WithLegend(InsideArea = false, Alignment = StringAlignment.Center, Docking = Docking.Top) chart

            //add title
            chart.Title <- StyleHelper.Title(chartTitle, FontSize = 10.0f, FontStyle = FontStyle.Bold)

            //create the form
            createForm chart

There is not much going on – just setting some chart parameters. There are only two things of interest:

FSharpChart.Combine ([for p in plot -> Charting.createChartOfType (chartType, (fst p), (snd p))])

This call will create a combined chart (if necessary – the combination of only one series) of any type, by calling one of two createChartType overrides. One override looks for chart with X and Y series, the other – for just Y series. For instance:

static member private createChartOfType ((chartType : string), name, (y : seq<#IConvertible * #IConvertible>))  =
   let innerTps = FSharpType.GetTupleElements(y.GetType().GetGenericArguments().[0])
   findAndCreateChart chartType innerTps name y

This version of the function will look for something like

ChartTypes.type name<TX, TY>(IEnumerable<TX> xvalues, IEnumerable<TY> yvalues) where TX: IConvertible where TY: IConvertible

in FSharpType and then attempt to create it:

    let (|FindChartOfType|_|) numGenericArgs chartType =
        let mi = 
            (typeof<FSharpChart>.GetMethods() 
            |> Array.filter(
                fun v -> 
                    v.Name = chartType && v.GetParameters().Length = 1 && v.GetGenericArguments().Length = numGenericArgs)).[0]
        match mi with
        | x when x = Unchecked.defaultof<MethodInfo> -> None
        |_ -> Some mi

    let findAndCreateChart chartType (genericArgs : Type []) name y =
        let mi =
            match chartType with
            | FindChartOfType genericArgs.Length mi -> mi
            | _ -> invalidArg "chartType " ("Chart of type " + chartType + " not found")

        let chart = mi.GetGenericMethodDefinition().MakeGenericMethod(genericArgs).Invoke(null, [|y|]) :?> ChartTypes.GenericChart
        chart.Name <- name
        chart

Another function of interest is the one that puts it all together and displays the chart:

    let createForm (chart : ChartTypes.CombinedChart) =
            let chartForm = new ChartForm<ChartData.DataSourceCombined>(chart)
            chartForm.Text <- "Chart"
            chartForm.ClientSize <- new Size(600, 600)
            Application.EnableVisualStyles()
            Application.Run(chartForm :> Form)

MSDN.FSharp.Charting namespace ChartExtensions module provides the ChartForm class that is used to host the chart control and display the chart.

My test data represents a hypothetical portfolio, taken from a Frank Fabozzi’s book:

        [TestMethod]
        public void ChartWeights()
        {
            Vector expected =  new DenseVector (new double [] { 0.079, 0.079, 0.09, 0.071 });

            Matrix correlations = new DenseMatrix(new double[,] { { 1.0, 0F, 0F, 0F }, { 0.24, 1.0, 0F, 0F }, { 0.25, 0.47, 1.0, 0F }, { 0.22, 0.14, 0.25, 1.0 } });

            Vector stdDeviations = new DenseVector(new double[] { 0.195, 0.182, 0.183, 0.165 });
            RiskMinimizationFormulation.RiskMinimization model = new RiskMinimizationFormulation.RiskMinimization(expected, correlations, stdDeviations);
            
            var range = ListModule.OfSeq(Enumerable.Range(50, 120).Where(e => e % 5 == 0).Select(e => (double)e / 1000D));

            model.ChartOptimalWeights(range, new string [] {"Australia", "Austria", "Belgium", "Canada"});
        }

This produces the chart:

Categories: F#, FSharpChart Tags: ,
  1. June 3, 2013 at 4:18 pm | #1

    If it is possible, could you update this sample to use the new Fsharp.Charting (the next evolution of FSharpChart)? http://fsharp.github.io/FSharp.Charting/

    Thanks
    Don and Tomas

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: