Concepts

Concepts

Backtesting

Backtesting is the general method for seeing how well a strategy or model would have done ex-post. Backtesting assesses the viability of a trading strategy by discovering how it would play out using historical data. If backtesting works, traders and analysts may have the confidence to employ it going forward.

Strategy

Strategies is a stack of algos which defines what asset to hold or not hold, how to weight each asset in the portfolio, and when to rebalance the portfolio.

graph TD Strategy --> Selection Strategy --> Weights Strategy --> Rebalance style Strategy padding:5px

Evaluating Results

Here’s an example backtest result:

Stat                 above50sma
-------------------  ------------
Start                2010-01-03
End                  2017-02-22
Risk-free rate       0.00%

Total Return         82.61%
Daily Sharpe         0.56
Daily Sortino        0.68
CAGR                 8.80%
Max Drawdown         -31.96%
Calmar Ratio         0.28

MTD                  7.10%
3m                   13.58%
6m                   28.80%
YTD                  6.85%
1Y                   35.60%
3Y (ann.)            16.24%
5Y (ann.)            13.20%
10Y (ann.)           8.80%
Since Incep. (ann.)  8.80%

Daily Sharpe         0.56
Daily Sortino        0.68
Daily Mean (ann.)    10.09%
Daily Vol (ann.)     18.12%
Daily Skew           -0.54
Daily Kurt           4.49
Best Day             5.78%
Worst Day            -7.99%

Monthly Sharpe       0.51
Monthly Sortino      0.72
Monthly Mean (ann.)  10.74%
Monthly Vol (ann.)   20.97%
Monthly Skew         -0.50
Monthly Kurt         0.46
Best Month           13.64%
Worst Month          -16.03%

Yearly Sharpe        0.65
Yearly Sortino       2.03
Yearly Mean          10.91%
Yearly Vol           16.75%
Yearly Skew          -0.13
Yearly Kurt          -0.68
Best Year            34.85%
Worst Year           -13.42%

Avg. Drawdown        -3.51%
Avg. Drawdown Days   54.34
Avg. Up Month        4.92%
Avg. Down Month      -4.59%
Win Year %           71.43%
Win 12m %            68.00%

The most important metrics are the top 5.

  • Total returns over the backtest period.
  • Daily Sharpe ratio - the higher the better. The ratio incorporates returns and volatility in one number.
  • Daily Sortino similar Sharpe except it only accounts for negative volatility.
  • CAGR Compound annual growth rate is the rate of return that would be required for an investment to grow from its beginning balance to its ending balance.
  • Max Drawdown is the maximum loss from the the prior peak.

While the highest total returns is desired, volatility or drawndowns is equally as important. In live-trading, the drawndown incurred may not recover like the backtesting results which represent a risk that must be minimized.

Per Investopedia:

  • Usually, any Sharpe ratio greater than 1.0 is considered acceptable to good by investors.
  • A ratio higher than 2.0 is rated as very good.
  • A ratio of 3.0 or higher is considered excellent.
  • A ratio under 1.0 is considered sub-optimal.

Types of Algos

Algos (bt.algos) are the defines selection process, and weighting process of a strategy.

Selection Algos

This is the implementation of the selection (you don’t need to implement it). It takes in a signal in a form of a dataframe with each row as a date, and a column of either selected True or False.

class SelectWhere(bt.Algo):

    """
    Selects securities based on an indicator DataFrame.

    Selects securities where the value is True on the current date (target.now).

    Args:
        * signal (DataFrame): DataFrame containing the signal (boolean DataFrame)

    Sets:
        * selected

    """
    def __init__(self, signal):
        self.signal = signal

    def __call__(self, target):
        # get signal on target.now
        if target.now in self.signal.index:
            sig = self.signal.ix[target.now]

            # get indices where true as list
            selected = list(sig.index[sig])

            # save in temp - this will be used by the weighing algo
            target.temp['selected'] = selected

        # return True because we want to keep on moving down the stack
        return True

While you can implement most algos with SelectWhere, there are other select algos available such as select top N or bottom N, select momentum. See API.

Weight Algos

Weight algos determine how to weight the selected assets.

Pandas and DataFrames

Pandas is an open source library providing high performance, easy to use data structures and data analysis tools for Python. It is used extenstively to manipulate data and converts them into signals for the trading strategy.

Viewing

> data.head()
                        BTC/USDT											ETH/USDT
                        Open	High	Low	Close	Volume	Open	High	Low	Close	Volume
Timestamp										
2018-01-01	13715.65	13818.55	12750.00	13380.00	8609.915844	733.01	763.55	716.80	754.99	53909.25885
2018-01-02	13382.16	15473.49	12890.02	14675.11	20078.092111	754.99	899.50	749.06	855.28	113257.78355
2018-01-03	14690.00	15307.56	14150.00	14919.51	15905.667639	855.13	950.01	810.00	934.03	89041.45688
2018-01-04	14919.51	15280.00	13918.04	15059.54	21329.649574	934.03	1009.72	890.01	940.00	102894.47384
2018-01-05	15059.56	17176.24	14600.00	16960.39	23251.491125	940.00	1045.00	930.00	959.30	97374.01630

The platform returns data as a multi-level dataframe. Selecting all the columns for BTC.

> data['BTC/USDT'].head()
        Open	High	Low	Close	Volume
Timestamp					
2018-01-01	13715.65	13818.55	12750.00	13380.00	8609.915844
2018-01-02	13382.16	15473.49	12890.02	14675.11	20078.092111
2018-01-03	14690.00	15307.56	14150.00	14919.51	15905.667639
2018-01-04	14919.51	15280.00	13918.04	15059.54	21329.649574
2018-01-05	15059.56	17176.24	14600.00	16960.39	23251.491125

Getting all of the Close prices.

> data.xs('Close', level=1, axis=1)
                    BTC/USDT	ETH/USDT
Timestamp		
2018-01-01	13380.00	754.99
2018-01-02	14675.11	855.28
2018-01-03	14919.51	934.03
2018-01-04	15059.54	940.00
2018-01-05	16960.39	959.30

Operations