This is the last post of this series. Previously we have seen how to download market data from a public web based feed, and how to display them in graphical charts. Today we will see an example of technical strategy backtester (BT).
Background
When it comes to trading in the financial market, there are two major categories of criteria one can adopt to take operational decisions: Fundamental and Technical.
Fundamental criteria are those that derive from an analysis of the quality of the traded asset. The investor makes assumptions on the discrepancy between the price offered by the market for the asset and the "real" value she perceives based on the information she can gather about the asset.
Technical criteria, instead, ignore completely that fundamental information and concentrate on the price behavior in time. The technical trader takes decisions based on elements that can be determined only by the past prices of the asset under consideration. Behind technical trading there is the conviction that "Market price discounts everything".
Of course nothing forbids to combine the two kind of trading, and as a matter of fact probably most traders do so.
Artificial Intelligence and Machine Learning have demonstrated that computational architectures inspired to the structure of the brain (neural networks) manifest the astonishing capability to learn from experience properties that we wouldn't be able to codify analytically.
Understanding technical trading
In a technical trading strategy, we define conditions that, when verified, trigger a specific market operation. Let's see a few examples
- Buy/Sell when the price crosses its 20-periods simple moving average.
- Buy/Sell when the 20 and 50 period moving averages cross.
- Buy/Sell when the stochastic oscillator crosses from below to above the level of 20.
Backtesting is the fundamental tool for the technical trader. We go back in time and apply our strategy simulating actual trading on past data. Computers allow to backtest a strategy in a minuscule fraction of the time we would spend doing it manually.
Disclaimer
Please note that
- The scope of this post is to show how to write backtest software in F#, NOT how to find or recommend a profitable strategy
- None of the strategies indicated in this post have to be considered trad-able in live markets. They are only suitable examples to demonstrate the features of the backtest software presented
Understanding BT
BT is used as follows
- First of all we determine the (technical) strategy to be tested
- As a result, we list the market data we need; typically one or more instruments and zero or more indicators
- We write a function that BT will call as callback
- We call a backtest function passing both the market data and the callback
- We receive as returned result a backtest report containing the list of trading operations and the final financial result
MSFT SMA20/SMA50 crossover
1. Strategy
We trade the daily timeframe. We buy when the fast SMA (SMA20) crosses the slow one (SMA50) upwards; we sell when the cross occurs downwards. The chosen asset is Microsoft stock (symbol MSFT).
2. Data
let msft = getStock "MSFT" DAY
let geti = getIndi "MSFT" DAY
let sma20 = SMA (20, Close) |> geti
let sma50 = SMA (50, Close) |> geti3. Callback
The backtest type is defined as
type BackTest = {
Market : MarketData seq
Strategy : IBacktestStatus -> unit
}where the interface IBacktestStatus is
type IBacktestStatus =
abstract member GetInstrument : symbol:string * offset:int -> OHLCV option
abstract member GetOpen : symbol:string * offset:int -> float option
abstract member GetClose : symbol:string * offset:int -> float option
abstract member Trade : symbol:string * shares:int -> bool
abstract member GetIndicator : id:string * offset:int -> float option
abstract member GetTime : offset:int -> DateTime
abstract member GetPosition : symbol:string -> (int * float) optionBT calls the callback strategy function once for every period in the range of market data. The interface IBacktestStatus gives to the user a well-defined set of methods that the strategy can call. The Get* methods have an offset argument: 0 means today, -1 means yesterday and so on. The Get* methods return Option values because in general it is possible that some market data are missing.
4. Run backtest
let market2 = [msft; sma20; sma50]
{Market = market2; Strategy = strategy2} |> Backtest.backtest5. Display report
let report2 = {Market = market2; Strategy = strategy2} |> Backtest.backtest
show (report2, market2 |> List.toSeq)Additional notes
The full code can be seen in github.
Backtest iteration loop - tail recursion
module Backtest =
let backtest test =
let status = BacktestStatus(test)
let rec iterate() =
status :> IBacktestStatus |> test.Strategy
if status.Next() then iterate()
iterate()
status.FinalReportThe iterate function is tail recursive — its last instruction is the recursive call. Recognizing the function as tail recursive, an optimizing compiler will simply generate a jump to the beginning instead of a call+push, so that our expensive recursive loop becomes lean exactly as an imperative loop.
Aligning data with Deedle
In the BacktestStatus class type, we take all our market data and transform them in a Deedle Frame. A Frame in Deedle is a data structure with a number of useful features to treat in-memory data. Something useful happens when we merge all those single-column frames together in a single frame, because Deedle automatically aligns all the constituent frames: we can finally index rows with a time and get all the correspondent values for that period.
How BT manages Trade operations
The backtester implemented here has only demonstration purposes. It:
- Can manage portfolios with multiple instruments
- Maintains one single position per instrument
- Allows short selling
- Does not consider trading costs (commissions, borrowing fees, interest rate differences)
- Does not check margins
- Does not compensate for online market inefficiencies (slippages, liquidity, delayed execution)
- Returns a report containing only the list of performed operations, the cash balance and the portfolio at the end of the simulation
Conclusion
This post concludes this mini series regarding how to implement a technical trading backtester in F#. Algorithmic trading is an interesting software engineering application area and I hope this publication gives some clues on how F# and its ecosystem are well suited to have an important voice in future developments.
Please feel free to drop comments or ideas.
