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

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

Luca -

☕ 5 min. read

I have a bunch of spread trades open. Spread trades are trades where you buy some­thing and you sell some­thing else gen­er­ally in the same amount. You hope to profit from the widen­ing of the spread be­tween the price of the two in­stru­ments.

I place stop loss or­ders or trail­ing stops for all my trades. I have var­i­ous tool that au­to­mat­i­cally no­tify me when a stop loss or trail­ing stop is hit. For spread trades I don’t have such a tool, hence I de­cided to build it.

I de­fined max­i­mum ad­verse ex­cur­sion for a spread trade as the per­cent­age dif­fer­ence be­tween the cur­rent value of long price’ / short price’ and its max­i­mum 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 max­i­mum ad­verse ex­cur­sion is larger than the trail­ing stop (using clos­ing prices only), then I want to be no­ti­fied by email.

I de­cided to cre­ate a sim­ple exe and use Task Scheduler to run it at the end of the trad­ing day. The pro­gram reads a file with all the open spread trades, down­loads their prices, cal­cu­lates max­i­mum ad­verse ex­cur­sion and sends an email if it is larger than the trail­ing stop. I also built a lit­tle WPF ve­neer to ma­nip­u­late the con­fig­u­ra­tion file.

Here is what my com­mon.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 is­ValidTicker func­tion. Yep, I’m us­ing a CSV file to store the list of spread trades. Also I of­ten end up us­ing the lit­tle _elseThrow_� func­tions that I orig­i­nally used in the Excel func­tions li­brary to check pre­con­di­tions.

Here is an ex­am­ple of us­ing them for the parse­Line func­tion:

// 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 for­mat is (dateOfTrade, longTicker, short­Ticker, trail­ingStop). Let’s now look and the FsCheck test­case for this func­tion.

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 prop­er­ties of your func­tions and FsCheck gen­er­ates ran­dom val­ues to test them. In this case I’m as­sert­ing that, given a date, tick­er­Long, tick­er­Short, trail­ingStop­Value, I can write them to a string, read them back and I get the same val­ues. Frankly, I was skep­ti­cal on the util­ity of such ex­er­cise, but I was wrong. That’s how I dis­cov­ered that tick­ers can­not have com­mas in them (among other things).

To hook up FsCheck and XUnit (aka to run FsCheck prop­erty check­ing as nor­mal test­cases), you need to write the be­low 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 pro­ject, you need to add xu­nit.gui.exe.con­fig to the XUnit di­rec­tory with the fol­low­ing con­tent:

While we are talk­ing about such triv­i­al­i­ties, I com­pile my test­cases as ex­e­cutable, so that I can eas­ily run them un­der de­bug. I also add the InternalsVisibleTo at­tribute, so that I can test in­ter­nal stuff. Many of my al­go­rithms are in in­ter­nal func­tions and I want to test them in iso­la­tion.

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

 

Given the pre­vi­ous func­tion, I can then parse text and files with the fol­low­ing:

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

I need to load clos­ing prices. I’m us­ing my own li­brary to load prices. That li­brary is pretty badly de­signed. Also, the func­tion be­low should be fac­tor­ized in sev­eral sub-func­tions. It kind of shows how you can write spaghetti code in a beau­ti­ful func­tional lan­guage as F# if you re­ally try hard. But let’s not worry about such sub­tleties 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, tick­er­Start­Date is an ar­ray of (trade date * long ticker * short ticker * trail­ingStop) which is what is pro­duced by our parse­Line func­tion. The func­tion first sep­a­rates out long tick­ers 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 to­gether again in a sin­gle Array, to be able to pass it to the loadTick­erA­sync func­tions. It runs the func­tion, waits for the re­sults and then re­turns an ar­ray of (ticker * ob­ser­va­tions).

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 down­loaded as it comes from Yahoo, which is a mix of ad­justed and not ad­justed data. asHap­pened trans­forms it so that every­thing is as it re­ally hap­pened, ad­justed then ad­justs it for the ef­fect of div­i­dends and splits. Think of this two func­tion as make the data right’.

We then split them again to get the long and short se­ries. The point of merg­ing them and split­ting them is to call loadTick­er­sAsync just once in­stead of twice. There are bet­ter 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 re­move the ob­ser­va­tions that rep­re­sents div­i­dends or splits, as we are in­ter­ested just in prices and we re­turn the re­sult­ing ob­ser­va­tions.

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 in­ter­est­ing. Somewhere in the loadTick­erA­sync/​asHap­pened/​ad­justed triad of func­tions I end up re­vers­ing the list. I should fix the bug in­stead of workaround it, but this is just a blog post, not pro­duc­tion code, so I’ll let it slip.

Now that we have our price ob­ser­va­tions, we need to ex­tract the price val­ues and cal­cu­late the se­quence of ra­tios (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 ra­tio se­ries, we can cal­cu­late the max­i­mum ad­verse ex­cur­sion, in­cor­rectly called trail­ing stop be­low.

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

We then cre­ate a func­tion that puts it all to­gether.

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 func­tion takes a file­Name and an end­Date, the lat­ter pa­ra­me­ter is for the sake of test­cases that has to work in the past, so that the data does­n’t change on them.

Now we need to send an email. The code be­low 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 pro­gram, we gather the data, cre­ate the con­tent 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 ve­neer on top of the file.

0 Webmentions

These are webmentions via the IndieWeb and webmention.io.