Tutorial - Backtesting

If you are completely new to TFire it is recommended to look at Tutorial - Basic Analysis of Time Series Data first.

Setting up Scoring

The decision of when to buy or sell an asset is done by Scoring the asset. To construct a Scoring we start with a slightly altered version of the setup from Tutorial - Basic Analysis of Time Series Data.

TFire> tickers = ["MMM", "AOS", "ABT", "ACN", "ATVI"];

TFire> spec = Specification(tickers, Date(2001,6,2),Date(2018,6,8); extend_back=600);

TFire> collection = setup_collection(spec);

Let's now cheat and filter out all all dates that have a return after 10 days larger than the average return after 10 days. First we find out what the average return is after 10 trading days

TFire> ret = compound_return(collection, 10;flat=true);

TFire> mean_return = mean(ret)
1.0078063862992992

Here, we use the compound_return function to calculate the return after 10 days. flat=true is used to flatten the returned values into a vector. Finally, we use the mean function to get the average return after 10 days.

Now we can filter out the dates that have a return larger than the average return after 10 days. First let's create a function that returns true if a value is larger than the average return after 10 days.

TFire> function higher_than_average(args)
       x = args[1]
       return x > 1.0078063862992992
       end

then filter a copy of the original collection

TFire> cret = compound_return(collection, 10);

TFire> collection_higher = filter_collection(collection, [cret], higher_than_average;action=:keep)

We can compare the number of samples in the original collection and the filtered collectio and notice that as expected about half of the samples had a higher return than the average sample.

TFire> collection
-| Collection |- (Continuous)
Tickers: 5, MMM AOS ABT ACN ATVI
Samples: 21373

TFire> collection_higher
-| Collection |- (Continuous)
Tickers: 5, MMM AOS ABT ACN ATVI
Samples: 10759

Now it's time to create the actual Scoring.

TFire> buy_scores = BuyScores(collection_higher, 1.);

This function creates a BuyScores (see Scoring) where every date contained in collection_higher, i.e. all dates that have higher than average return after 10 days, gets a score of 1.

Setting up an Initial Portfolio

TFire> from_date = DateTime(2002,1,7)

TFire> port = initialize_portfolio(collection, from_date);

Let's hold the assets for 10 days after they were bought.

TFire> position_scores = extend_scores(buy_scores, port; constant_ticks=10);

This creates a PositionScores object, which has a score of 1 for every date in ScoreBuy and 10 days forward.

Propagating the Portfolio

We can propagate the initial portfolio port using the position_scores to get a PortfolioHistory.

TFire> port_prop = propagate_portfolio(port, position_scores)
-| Portfolio History |-  4135 number of steps from 2002-01-07T00:00:00 to 2018-06-08T00:00:00

Analysing the Results

To evaluate the performance of our portfolio, we begin by plotting it against a portfolio where all of the stocks available for trade are bought for equal ammount at day 1 and then held to the final day, i.e. equal weighted buy and hold. We can get this portfolio by running reference_portfolio(port_prop).

TFire> plot_portfolio(port_prop, reference_portfolio(port_prop))

Portfolio Plot The cheating portfolio is of course significantly outperforming the buy and hold variant, with a 600x return vs 20x return over the time period.

We can add a trading fee of 1% on all trades and see what that does.

TFire> port_prop_fee = propagate_portfolio(port, position_scores; fee_type=:proportional, prop_fee=0.01)

TFire> plot_portfolio(port_prop_fee, reference_portfolio(port_prop_fee))

Portfolio With Fee Plot

That concludes this tutorial on the basics of backtesting in TFire.