Tracking spread trades in F# (and WPF MVVM) – Part II

I wanted to experiment with MVVM and WPF in F#, so I decided to create a little graphical interface for the csv file that drives the spread tracking application. When I started I thought I needed some kind of a grid with Submit/Cancel buttons, but the more I thought about it, the more I realized that I wouldn’t need them.

See, I’ve always be one to complain about our current paradigm of Open File / Close File / Save File arguing that the user shouldn’t know about an entity called ‘file’. He shouldn’t be exposed to the fact that the application is just an in-memory copy of an hard disk artifact. His mental model should simply be: I open a document, I work on it, I close it, if needed I can make a copy; if I have problems I can revert to a previous version of the same document; If I make an error I can use ‘undo’ to revert it. There are no files/save/submit/cancel in such paradigm. There is no file system.

On the technical side I wanted to experiment with MVVM, even if in this case, the paradigm is overkilled (can really use this word?), given the simplicity of the application.

In any case, the ViewModel is in F#. It uses two utility classes:

// TODO: refactor to remove code repetition below
[<AbstractClass>]
type ViewModelBase () =
    let propertyChanged = new Event<PropertyChangedEventHandler, PropertyChangedEventArgs>()
    interface INotifyPropertyChanged with
        [<CLIEvent>]
        member this.PropertyChanged = propertyChanged.Publish
    member internal this.RaisePropertyChangedEvent(propertyName:string) =
        if not(propertyName = null) then
            let e = new PropertyChangedEventArgs(propertyName)
            let i = this :> INotifyPropertyChanged
            propertyChanged.Trigger(this, e)

type ObservableCollectionWithChanges<'a when 'a :> INotifyPropertyChanged> () =
    inherit ObservableCollection<'a> ()
    let propertyChanged = new Event<PropertyChangedEventHandler, PropertyChangedEventArgs>()
    member c.PropertyChanged = propertyChanged.Publish
    member private c.RaisePropertyChangedEvent(propertyName:string) =
        if not(propertyName = null) then
            let e = new PropertyChangedEventArgs(propertyName)
            let i = c :> INotifyPropertyChanged
            propertyChanged.Trigger(c, e)

    member c.Add(o) =
        base.Add(o)
        o.PropertyChanged.Add(fun x -> c.RaisePropertyChangedEvent(""))

The first one is used as a base for all the viewmodel entities in the application, the second one serves as the base for all the collections. They both define the customary PropertyChanged event. The latter adds itself as an observer to each object added to the collection so that, whenever one changes, it gets notified and can notify its own observers. Look at the c.Add method. A lot of repetitive code here, I would heed the advice of the comment on top if this were production code.

Each line in the csv file is represented as a ResultViewModel, hence the following:

type ResultViewModel (d:DateTime, sLong, sShort, tStop) =
    inherit ViewModelBase ()
    let mutable date = d
    let mutable stockLong = sLong
    let mutable stockShort = sShort
    let mutable trailingStop = tStop

    new () = new ResultViewModel(DateTime.Today, "", "", 0)       

    member r.Date with get() = date
                       and set newValue =
                            date <- newValue
                            base.RaisePropertyChangedEvent("Date")
        
    member r.StockLong with get() = stockLong
                       and set newValue =
                            stockLong <- newValue
                            base.RaisePropertyChangedEvent("StockLong")
        
    member r.StockShort with get() = stockShort
                        and set newValue =
                            stockShort <- newValue
                            base.RaisePropertyChangedEvent("StockShort")

    member r.TrailingStop with get() =
                                trailingStop
                          and set newValue =
                                trailingStop <- newValue                                
                                base.RaisePropertyChangedEvent("TrailingStop")
                                    
    member r.IsThereAnError = r.TrailingStop < 0 || r.TrailingStop > 100

I need the empty constructor to be able to hook up to the DataGrid add-new capability. There might be an event I could use instead, but this is simple enough (even if a bit goofy).

The main view model class then looks like the following:

type MainViewModel (fileName:string) as self =
    inherit ViewModelBase ()

    let mutable results = new ObservableCollectionWithChanges<ResultViewModel>()

    let loadResults () =
        parseFile fileName
        |> Array.iter (fun (d,sl, ss, ts) ->
                        results.Add(new ResultViewModel(d, sl, ss, ts)))
    do
        loadResults ()
        results.CollectionChanged.Add(fun e -> self.WriteResults())
        results.PropertyChanged.Add(fun e -> self.WriteResults())
    
    member m.Results with get() = results
                     and set newValue =
                        results <- newValue
                        base.RaisePropertyChangedEvent("Results")

    member m.WriteResults () =
        let rs = results
                 |> Seq.map (fun r -> r.Date, r.StockLong, r.StockShort, r.TrailingStop)
        let thereAreErrors = results |> Seq.exists (fun r -> r.IsThereAnError)
        if not thereAreErrors then
            writeFile fileName rs

Things here are more interesting. First of all, in the constructor I load the results calling my model (which I created in Part I of this series). I then subscribe to both the events fired by the collection of results. The former is triggered when an object is added/removed, the latter is triggered when an object changes one of its properties. When one of them fires, I simply write the new state back to the file. This allows me to get rid of Submit/Cancel buttons. What the user sees on the screen is synchronized with the disk at all times. The user doesn’t need to know about the file system.

If this were real, I would also implement an undo/redo mechanism. In such case, my reliance on object events might be unwise. I would probably route all the user changes through a command mechanism, so that they can be undo more easily.

That’s it for the modelview. The View itself is as follows:

<Window x:Class="SpreadTradingWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:spreadTrading="clr-namespace:SpreadTradingWPF"
        Title="Spread Trading" Height="350" Width="525" SizeToContent="WidthAndHeight">
        <Window.Resources>

            <spreadTrading:DateToShortStringConverter x:Key="DateToShortStringC" />
 
            
            <LinearGradientBrush x:Key="BlueLightGradientBrush" StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Offset="0" Color="#FFEAF3FF"/>
                    <GradientStop Offset="0.654" Color="#FFC0DEFF"/>
                    <GradientStop Offset="1" Color="#FFC0D9FB"/>
            </LinearGradientBrush>

            <Style TargetType="{x:Type DataGrid}">
                <Setter Property="Margin" Value="5" />
                <Setter Property="Background" Value="{StaticResource BlueLightGradientBrush}" />
                <Setter Property="BorderBrush" Value="#FFA6CCF2" />
                <Setter Property="RowBackground" Value="White" />
                <Setter Property="AlternatingRowBackground" Value="#FDFFD0" />
                <Setter Property="HorizontalGridLinesBrush" Value="Transparent" />
                <Setter Property="VerticalGridLinesBrush" Value="#FFD3D0" />
                <Setter Property="RowHeaderWidth" Value="20" />
            </Style>
            

    </Window.Resources>
    
        <StackPanel HorizontalAlignment="Center" Name="stackPanel1" VerticalAlignment="Top" Margin="20">
            <TextBlock Text="Spread Trading" Width="135" HorizontalAlignment="Center" FontSize="18" FontWeight="Bold" FontStretch="ExtraExpanded" />
            <DataGrid Height="Auto" Width="Auto" Margin="5" ItemsSource="{Binding Results}" CanUserAddRows ="True" CanUserDeleteRows="True" AutoGenerateColumns="False">
                <DataGrid.RowValidationRules>
                    <spreadTrading:ResultValidationRule ValidationStep="UpdatedValue"/>
                </DataGrid.RowValidationRules>
                <DataGrid.RowValidationErrorTemplate>
                    <ControlTemplate>
                        <Grid Margin="0,-2,0,-2"
                              ToolTip="{Binding RelativeSource={RelativeSource
                              FindAncestor, AncestorType={x:Type DataGridRow}},
                              Path=(Validation.Errors)[0].ErrorContent}">
                            <Ellipse StrokeThickness="0" Fill="Red" 
                                Width="{TemplateBinding FontSize}" 
                                Height="{TemplateBinding FontSize}" />
                            <TextBlock Text="!" FontSize="{TemplateBinding FontSize}" 
                                FontWeight="Bold" Foreground="White" 
                                HorizontalAlignment="Center"  />
                        </Grid>
                    </ControlTemplate>
                </DataGrid.RowValidationErrorTemplate>

            <DataGrid.Columns>
                    <DataGridTextColumn Header="Date" Binding="{Binding Date, Converter= {StaticResource DateToShortStringC}}"  IsReadOnly="false"/>
                    <DataGridTextColumn Header="Long" Binding="{Binding StockLong}"/>
                    <DataGridTextColumn Header="Short" Binding="{Binding StockShort}" />
                    <DataGridTextColumn Header="Stop" Binding="{Binding TrailingStop}" />
            </DataGrid.Columns>
            </DataGrid>
        </StackPanel>
</Window>

Notice that I styled the grid and I used the right incantations to get validation errors and to bind things properly. The DateToShortString converter used for the date field might be mildly interesting. It’s in the Utilities.cs file together with a validation rule that just delegates to the IsThereAnError method on each entity. In a bigger application, you could write this code in a much more reusable way.

[ValueConversion(typeof (DateTime), typeof (string))]
public class DateToShortStringConverter : IValueConverter
{
public Object Convert(
        Object value,
        Type targetType,
        Object parameter,
        CultureInfo culture)
{
    var date = (DateTime) value;
    return date.ToShortDateString();
}

public object ConvertBack(
    object value,
    Type targetType,
    object parameter,
    CultureInfo culture)
    {
        string strValue = value as string;
        DateTime resultDateTime;
        if (DateTime.TryParse(strValue, out resultDateTime))
        {
            return resultDateTime;
        }
        return DependencyProperty.UnsetValue;
    }
}

public class ResultValidationRule : ValidationRule
{

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        var result = (value as BindingGroup).Items[0] as ResultViewModel;
        if(result.IsThereAnError)
            return new ValidationResult(false, "TrailingStop must be between 0 and 100");
        else
            return ValidationResult.ValidResult;
    }
}

After all these niceties, this is what you get.

image

Tracking spread trades in F# (and hooking up XUnit and FsCheck) – Part 1

I have a bunch of spread trades open. Spread trades are trades where you buy something and you sell something else generally in the same amount. You hope to profit from the widening of the spread between the price of the two instruments.

I place stop loss orders or trailing stops for all my trades. I have various tool that automatically notify me when a stop loss or trailing stop is hit. For spread trades I don’t have such a tool, hence I decided to build it.

I defined maximum adverse excursion for a spread trade as the percentage difference between the current value of ‘long price’ / ‘short price’ and its maximum value from the point the trade was placed (aka Current(‘long price’ / ‘short price’) / max (‘long price’ / ‘short price’) – 1 ). This goes from 0 to 100. If the maximum adverse excursion is larger than the trailing stop (using closing prices only), then I want to be notified by email.

I decided to create a simple exe and use Task Scheduler to run it at the end of the trading day. The program reads a file with all the open spread trades, downloads their prices, calculates maximum adverse excursion and sends an email if it is larger than the trailing stop. I also built a little WPF veneer to manipulate the configuration file.

Here is what my common.fs file looks like.

namespace Spread
module internal Common =
    open System

    let internal isValidDate s =
        let v, _ = DateTime.TryParse(s) 
        v
    let internal isValidTrailingStop s =
        let v1, n = Int32.TryParse(s)
        if not(v1) then
            false
        else
            n >= 0 && n <= 100
    let internal isValidTicker (t:string) = not(t.Contains(","))
    let internal isValidLine (l:string) = l.Split([|','|]).Length = 4

    let internal elseThrow message expression = if not(expression) then raise message
    let internal elseThrowi i message expression = if not(expression) then failwith (sprintf "On line %i : %s" i message)

 

Notice the isValidTicker function. Yep, I’m using a CSV file to store the list of spread trades. Also I often end up using the little elseThrow  functions that I originally used in the Excel functions library to check preconditions.

Here is an example of using them for the parseLine function:

// parse a line in the csv config file, assumes valid csv, dates and trailing stop in [0,100]
let internal parseLine lineNumber line =
    isValidLine line                |> elseThrowi lineNumber "badly formatted line"
    let values = line.Split([|','|])
    isValidDate values.[0]          |> elseThrowi lineNumber "badly formatted date"
    isValidTicker values.[1]        |> elseThrowi lineNumber "long ticker has a comma in it"
    isValidTicker values.[2]        |> elseThrowi lineNumber "short ticker has a comma in it"
    isValidTrailingStop values.[3]  |> elseThrowi lineNumber "trailing stop has to be between 0 and 100 included"

    DateTime.Parse(values.[0]), values.[1].Trim(), values.[2].Trim(), int values.[3]

 

As you can see, the csv format is (dateOfTrade, longTicker, shortTicker, trailingStop). Let’s now look and the FsCheck testcase for this function.

let writeLine (date:DateTime) (tickerLong:string) (tickerShort:string) (trailingStopValue:int) =
    sprintf "%s,%s,%s,%i" (date.ToShortDateString()) tickerLong tickerShort trailingStopValue

[<Fact;Category("Fast Tests")>]
let can_parse_valid_lines () =
    let  prop_parseLine (lineNumber:int) date tickerLong tickerShort trailingStopValue =
        let line = writeLine date tickerLong tickerShort trailingStopValue
        let values = line.Split([|','|])
        (isValidLine(line) && isValidDate values.[0] && isValidTicker values.[1] && isValidTicker values.[2]
                                                                                        && isValidTrailingStop values.[3])
            ==> lazy
                let actual = parseLine lineNumber line
                (date, tickerLong.Trim(), tickerShort.Trim(), trailingStopValue) = actual
    check config prop_parseLine

In FsCheck you state properties of your functions and FsCheck generates random values to test them. In this case I’m asserting that, given a date, tickerLong, tickerShort, trailingStopValue, I can write them to a string, read them back and I get the same values. Frankly, I was skeptical on the utility of such exercise, but I was wrong. That’s how I discovered that tickers cannot have commas in them (among other things).

To hook up FsCheck and XUnit (aka to run FsCheck property checking as normal testcases), you need to write the below black magic code.

let xUnitRunner = 
    { new IRunner with 
        member x.OnArguments(_,_,_) = ()  
        member x.OnShrink(_,_) = ()
        member x.OnFinished(name, result) = 
            match result with 
                | True data -> Assert.True(true)
                | _ -> failwith (testFinishedToString name result)
    } 

let config = {quick with Runner = xUnitRunner}

Also, to run XUnit with your brand new .net 4.0 project, you need to add xunit.gui.exe.config to the XUnit directory with the following content:

<configuration>

<startup>

<requiredRuntime version=”v4.0.20506″ safemode=”true”/>

</startup>

</configuration>

While we are talking about such trivialities, I compile my testcases as executable, so that I can easily run them under debug. I also add the InternalsVisibleTo attribute, so that I can test internal stuff. Many of my algorithms are in internal functions and I want to test them in isolation.

    [<assembly:InternalsVisibleTo("SpreadTrackingTests")>]  
    do

 

Given the previous function, I can then parse text and files with the following:

let internal parseText (lines:string) = lines.Trim().Split([|'\n'|]) |> Array.mapi parseLine
let public parseFile fileName = File.ReadAllText fileName |> parseText

I need to load closing prices. I’m using my own library to load prices. That library is pretty badly designed. Also, the function below should be factorized in several sub-functions. It kind of shows how you can write spaghetti code in a beautiful functional language as F# if you really try hard. But let’s not worry about such subtleties for now …

let internal loadClosingPrices (endDate:DateTime) tickersStartDate  =
    // format parameters to conform to loadTickersAsync
    let tickersLong, tickersShort =
        tickersStartDate
        |> Array.map (fun (startDate:DateTime, ticker1:string, ticker2:string, _) ->
                (ticker1, {Start = startDate; End = endDate}), (ticker2, {Start = startDate; End = endDate}))
        |> Array.unzip
    let prices = tickersShort
                 |> Array.append tickersLong
                 |> Array.toList
                 |> loadTickersAsync
                 |> Async.RunSynchronously
                 |> Array.map (fun (ticker, span, obs) -> ticker, obs (*|> asHappened 1. |> adjusted adjStart*))
    let len = tickersLong.Length
    let longObs = Array.sub prices 0 len
    let shortObs = Array.sub prices len len
    // removes divs and splits
    let choosePrices observation = match observation.Event with Price(pr) -> Some(observation) | _ -> None
    let combineOverTickerObservations f tickerObservations =
        tickerObservations
        |> Array.map (fun (ticker, observations) ->
                                            ticker,
                                            observations |> List.choose f |> List.rev)
    let longPrices = combineOverTickerObservations choosePrices longObs
    let shortPrices = combineOverTickerObservations choosePrices shortObs
    longPrices, shortPrices

In the above, tickerStartDate is an array of (trade date * long ticker * short ticker * trailingStop) which is what is produced by our parseLine function. The function first separates out long tickers from short ones.

let tickersLong, tickersShort =
    tickersStartDate
    |> Array.map (fun (startDate:DateTime, ticker1:string, ticker2:string, _) ->
            (ticker1, {Start = startDate; End = endDate}), (ticker2, {Start = startDate; End = endDate}))
    |> Array.unzip

It then puts them together again in a single Array, to be able to pass it to the loadTickerAsync functions. It runs the function, waits for the results and then returns an array of (ticker * observations).

let prices = tickersShort
             |> Array.append tickersLong
             |> Array.toList
             |> loadTickersAsync
             |> Async.RunSynchronously
             |> Array.map (fun (ticker, span, obs) -> ticker, obs |> asHappened 1. |> adjusted adjStart)

 

The data is downloaded as it comes from Yahoo, which is a mix of adjusted and not adjusted data. asHappened transforms it so that everything is as it really happened, adjusted then adjusts it for the effect of dividends and splits. Think of this two function as ‘make the data right’.

We then split them again to get the long and short series. The point of merging them and splitting them is to call loadTickersAsync just once instead of twice. There are better ways to do it.

        let len = tickersLong.Length
        let longObs = Array.sub prices 0 len
        let shortObs = Array.sub prices len len

At this point we remove the observations that represents dividends or splits, as we are interested just in prices and we return the resulting observations.

let choosePrices observation = match observation.Event with Price(pr) -> Some(observation) | _ -> None
let combineOverTickerObservations f tickerObservations =
    tickerObservations
    |> Array.map (fun (ticker, observations) ->
                                        ticker,
                                        observations |> List.choose f |> List.rev)
let longPrices = combineOverTickerObservations choosePrices longObs
let shortPrices = combineOverTickerObservations choosePrices shortObs
longPrices, shortPrices

The List.rev at the end is interesting. Somewhere in the loadTickerAsync/asHappened/adjusted triad of functions I end up reversing the list. I should fix the bug instead of workaround it, but this is just a blog post, not production code, so I’ll let it slip.

Now that we have our price observations, we need to extract the price values and calculate the sequence of ratios (long price / short price).

let internal calcRatioSeries longPrices shortPrices =
    let extractPrice obs = match obs.Event with Price(pr) -> pr.Close | _ -> failwith "At this point divs and splits should have been removed"
    let longValues = longPrices |>  List.map extractPrice 
    let shortValues = shortPrices |> List.map extractPrice 
    shortValues |> List.map2 (/) longValues

Having this ratio series, we can calculate the maximum adverse excursion, incorrectly called trailing stop below.

let internal calcTrailingStop ratioSeries = List.head ratioSeries / List.max ratioSeries - 1.

We then create a function that puts it all together.

type public Result = {RatioName:string; CurrentTrailing:int; TrailingStop:int} with
    override x.ToString() = x.RatioName + "\t\t" + x.CurrentTrailing.ToString() + "\t\t" + x.TrailingStop.ToString()

// reads a csv file (startDate, longTicker, shortTicker, trailingStop) and returns an array of results
let public processFile fileName endDate =
    let fileInfo = parseFile fileName
    let longPrices, shortPrices = loadClosingPrices endDate fileInfo
    let ratioSeries = Array.map2 (fun l s -> fst l + "/" + fst s, calcRatioSeries (snd l) (snd s)) longPrices shortPrices
    ratioSeries |> Array.mapi (fun i (name, series) ->
                    let (_,_,_,ts) = fileInfo.[i]
                    {RatioName = name; CurrentTrailing = - int (Math.Round (calcTrailingStop series * 100., 0));
                                                                                                       TrailingStop = ts})

The function takes a fileName and an endDate, the latter parameter is for the sake of testcases that has to work in the past, so that the data doesn’t change on them.

Now we need to send an email. The code below works for me:

let sendEmail smtpServer port fromField toField subject body (user:string) (password:string) =
    let client = new SmtpClient(smtpServer, port)
    client.Credentials <- new NetworkCredential(user, password)
    client.EnableSsl <- true
    client.Send(fromField, toField, subject, body)

// gets the password from a file under C: so that when I post it on my blog I don't forget to delete it
let getPassword () =
    File.ReadAllText(@"D:\Documents and Settings\Luca\My Documents\config.txt")

Almost done, in the main part of the program, we gather the data, create the content of the email and send it out:

do
    let file = "spreads.csv"
    let spreads = processFile file DateTime.Today
    let mutable builder = new System.Text.StringBuilder()
    builder <- builder.AppendLine("Name\t\tCurrent\t\tStop")
    for s in spreads do
        builder <- builder.AppendLine(s.ToString())
    let password = getPassword()
    sendEmail "smtp.gmail.com" 587 "***@***.com" "***@***.com" "Alert Trigger Spread" (builder.ToString())
                                                                                           "lucabolg@gmail.com" password;;

Next stop, the WPF veneer on top of the file.