In the previous post of this series we've seen how to download market data from Alpha Vantage. Today I'm gonna show a way to put this data into charts. In doing so, we will see some of the nicest features of F# and how to leverage on them to write powerful succinct generic code.
The next post will dive into technical strategy backtesting.
Charting in F#
There are several options available to the F# developer to create charts based on numerical data. We can simply rely on the Operating System (OS) graphics primitives, but there are libraries and frameworks publicly available that offer higher level abstractions over those primitives. Often they adhere to the Open Source Software (OSS) principles. OSS, for obvious reasons, is not always well documented or supported, but it offers undoubted advantages to the developer:
- We can start using the pre-built binaries directly as with any library/framework and quickly implement our first-versions. This is important because often being able to create live prototypes greatly streamlines the design phase.
- Having the source available allows us to "learn by example" much quicker than we would do using just the reference documents.
- Very often the licensing scheme for OSS software allows the user to grab, fork, embed, basically "do what you want" with the source code.
For our necessities here I am going to use XPlot. XPlot is an F# OSS graphics library that encapsulates popular graphics frameworks (Google.Charts and Plotly) and makes them easily usable from an F# codebase. XPlot is part of the excellent FsLab project. The Plotly flavor of XPlot will be used here.
MarketData charts
From the previous post, we have seen that our domain types for market data are
type PriceBarPart = | Open | High | Low | Close
type OHLCV = { O:float; H:float; L:float; C:float; V:int }
type InstrumentMarketDataElement = DateTime * OHLCV
type InstrumentMarketData = InstrumentMarketDataElement seq
type IndicatorMarketDataElement = DateTime * float
type IndicatorMarketData = IndicatorMarketDataElement seq
type IndicatorDefinitions =
| SMA of period:int * price:PriceBarPart
with
member this.Id =
match this with
| SMA (period, price) -> sprintf "SMA%d%A" period price
type Instrument = {
Symbol : string
MarketData : InstrumentMarketData
}
type Indicator = {
Instrument : string
Definition : IndicatorDefinitions
MarketData : IndicatorMarketData
}
with
member this.Id = sprintf "%s(%s)" this.Definition.Id this.Instrument
type MarketData =
| Instrument of Instrument
| Indicator of IndicatorFinancial charts use to display instrument OHLC timeframed data as bars or candlesticks. To convert our domain types we write this function
let private marketToTrace withCandlestick marketData =
match marketData with
| Instrument {Symbol = symbol; MarketData = series} ->
if withCandlestick then
Candlestick(
name = symbol,
x = (series |> Seq.map (fun (dt, _) -> dt)),
``open`` = (series |> Seq.map (fun (_, ohlcv) -> ohlcv.O)),
high = (series |> Seq.map (fun (_, ohlcv) -> ohlcv.H)),
low = (series |> Seq.map (fun (_, ohlcv) -> ohlcv.L)),
close = (series |> Seq.map (fun (_, ohlcv) -> ohlcv.C))
) :> Trace
else
Scatter(
name = symbol,
x = (series |> Seq.map (fun (dt, _) -> dt)),
y = (series |> Seq.map (fun (_, ohlcv) -> ohlcv.C))
) :> Trace
| Indicator {Definition = def; MarketData = series} ->
Scatter(
name = def.Id,
x = (series |> Seq.map (fun (dt, v) -> dt)),
y = (series |> Seq.map (fun (dt, v) -> v))
) :> Tracelet private marketsToTraces marketsData =
marketsData |> Seq.map (marketToTrace (marketsData |> Seq.length = 1))Generic show function
We want to create a generic show function that accepts various input types. We define the following helper type
type Shower =
static member ($) (_:Shower, m:seq<_>) = m |> Seq.map marketsToTraces |> Seq.map Chart.Plot |> Chart.ShowAll
static member ($) (_:Shower, r:BacktestReport) = reportToTrace r
static member ($) (_:Shower, (r:BacktestReport, m:seq<_>)) = seq { yield! marketsToTraces m; yield reportToTrace r } |> Chart.Plot |> Chart.Show
let inline show o = Unchecked.defaultof<Shower> $ oWhen the compiler encounters a use site, it will search for an operator $ and find the overloaded implementation. If the actual argument passed matches one of the overloads, a call to the chosen operator overload will be generated at the call site (inline). No runtime type resolution, no runtime exceptions.
Here is an example of usage
let gets symbol = getStock symbol DAY
let geti symbol = getIndi symbol DAY
let msft = gets "MSFT"
let aapl = gets "AAPL"
let sma8Msft = geti "MSFT" (SMA (8, Close))
let sma20Msft = geti "MSFT" (SMA (20, Close))
let sma50Msft = geti "MSFT" (SMA (50, Close))
let sma100Aapl = geti "AAPL" (SMA (100, Close))
let sma200Aapl = geti "AAPL" (SMA (200, Close))
show [[pickLast 50 msft]; [pickLast 100 aapl]]
show [[msft; sma8Msft; sma20Msft; sma50Msft]; [aapl; sma100Aapl; sma200Aapl]]Conclusion
We are now able to download market data from a free online datasource and show them in graphical charts. Plotly already offers nice built-in interactivity to work with the generated chart and data. Once displayed in the browser, our charts are equipped with a toolbar that we can use to operate on the chart in different ways.
The XPlot.Plotly Html APIs also return HTML or javascript markup that render the PlotlyChart instance they are called upon. We can then embed it in our custom page and serve it as we like.
The code in this post can be seen in github.
