36

The easiest way to evaluate the performance of trading strategies in Python

 4 years ago
source link: https://www.tuicool.com/articles/VNBVZ37
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Learn how to generate comprehensive performance reports with one line of Python code!

Oct 7 ·8min read

This is the third part of a series of articles on backtesting trading strategies in Python. The previous ones described the following topics:

  • introducing the zipline framework and presenting how to test basic strategies (link)
  • importing custom data to use with zipline ( link )

This time, the goal of the article is to show how to quickly and efficiently evaluate the performance of our strategies using a library called pyfolio (developed by Quantopian, the creators of zipline ). pyfolio can be used as a standalone library and provide performance results based only on a provided series of returns. However, it works efficiently with zipline and I present this combination in this article.

Simple Moving Average Strategy

In this article, I only show a basic strategy, as the main focus is on evaluating the performance. For that purpose, I chose a strategy based on the simple moving average (SMA). The logic of the strategy can be summarized by the following:

  • when the price crosses the 20-day SMA upwards — buy 20 shares
  • when the price crosses the 20-day SMA downwards — sell all the shares
  • we can only have a maximum of 20 shares at any given time
  • there is no short-selling in the strategy

I run a backtest of the strategy using IBM’s stock over the years 2016–2017. For details on how to create such a strategy and what do the particular components actually do, please refer to myprevious article.

I start by loading the required libraries:

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import zipline
import warnings
import pyfolio as pf

Then, I run the strategy:

zipline automatically creates a performance DataFrame, which you can also see in the output of the code. For convenience, I stored the output in a pickle file called simple_moving_average.pkl . To make the analysis as smooth as possible, we can use a utility function provided by pyfolio and load the 3 most important elements of the performance DataFrame — the returns, positions, and transactions. To do so, I use pf.utils.extract_rets_pos_txn_from_zipline .

sma_results = pd.read_pickle('simple_moving_average.pkl')
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(sma_results)

We can now inspect the extracted elements:

returns.head()

r6FJnqj.png!web

There could be no returns over the first 20 trading days of 2016, as it is still the warm-up period for the moving average.

positions.head()

jueEfez.png!web

The positions DataFrame contains entries for each day when we do have a position in the considered assets and shows the capital split between equities and cash. We can see that by buying 20 IBM shares we still keep the majority of the capital in cash. For more advanced strategies, we can allocate a percentage of the capital to each order by using order_percent .

transactions.head()

YVnAbaj.png!web

The transactions DataFrame contains all the transactions executed by the trading strategy — we see both buy and sell orders.

Simple tear sheet

To evaluate the performance of strategies, portfolios or even single assets, we use pyfolio to create a tear sheet. A tear sheet is a concise document (often a single-paged one) that contains the most important information — such as financial metrics — regarding a public company. pyfolio provides much more functionalities than can be contained on a single sheet of paper, but for simplicity, we start with a simple tear sheet, which contains only the most vital information.

To create it, we run the following:

pf.create_simple_tear_sheet(returns)

What is truly remarkable about ` pyfolio ` is that it generates so much information with a single line of code!

iaAn6bA.png!web

The first table we see presents the dates of the test, how many months it lasted and a lot of financial metrics such as:

  • annualized returns/standard deviation
  • skewness — the third moment describes how skewed is the distribution
  • kurtosis — the fourth moment indicates if there is more mass in the tails of the distribution
  • Sharpe ratio — a very popular risk metric. It indicates the amount of excess return (over the risk-free rate) per unit of risk (measured by standard deviation).
  • Sortino ratio — a modified version of the Sharpe ratio, where the standard deviation is replaced by downside deviation. Downside deviation measures only the negative volatility of the series, strictly speaking below a predefined level called minimum acceptable return.
  • Omega ratio — another kind of risk-return performance metric. Its biggest advantage over the Sharpe ratio is that — by construction — it takes into account all statistical moments, while the Sharpe ratio only considers the first two.
  • Maximum drawdown — indicates the largest (expressed in %) drop between a peak and a valley
  • daily Value-at-Risk — another very popular risk metric. In this case, it indicates that in 95% of the cases, we will not lose more than 0.5% by keeping the position/portfolio for 1 more day.

The first 3 bullets are connected to the stylized facts of asset returns, which I described in one of theprevious articles.

fmAZZji.png!web

The next chart presents the cumulative returns on our strategy — we can observe the evolution of our portfolio’s worth over the two years of running this strategy. The flat periods represent periods when we held no assets.

URbAJbJ.png!web

We can also see the rolling Sharpe ratio, which provides more insight into the stability of our strategy. We see that the overall reported Sharpe ratio is 0.84, however, when we look at the rolling one we see how volatile it was over time (calculated using rolling 6 months of data, not entire sample!).

The last chart — the underwater plot — shows the investment from a pessimistic point of view. By pessimistic, I mean that it focuses on losses. It depicts drawdowns and shows how long it took for the portfolio’s value to recover to the previous peak, after suffering a loss. This chart makes it easy to distinguish between normal and extended periods of losses.

ui22myR.png!web

We created all these plots by passing only the returns object to the function. We can also pass the previously extracted positions and transactions, to automatically receive even more information on the simple tear sheet! Additionally, we can also specify a live_start_date , which splits the periods into the backtest and live-trading ones ( zipline offers such a possibility). To do so, we simply run:

pf.create_simple_tear_sheet(returns, positions, transactions, live_start_date='2017-01-01')

In the table below we immediately see the changes:

  • there are two separate periods for analysis
  • providing positions and transactions related data resulted in extra metrics such as gross leverage and extra turnover

The simple tear sheet also contains more plots related to positions and transactions, however, I do not show them for brevity.

RNFzmuy.png!web

Full Tear Sheet

In the previous part, we only created a simple tear sheet, which gave a concise overview of the strategy’s performance. It is possible to easily obtain much more information, by creating a full tear sheet (it’s level of detail also depends on the provided information — here we only use returns).

pf.create_full_tear_sheet(returns)

I present a selection of new tables/charts included in the full tear sheet:

  • A table presenting top 5 worst drawdowns, together with information such as peak/valley date and the duration

Ejaq22n.png!web

  • Top 5 drawdown periods visualized on top of the cumulative returns. In our case, the drawdown periods cover almost the entire investment horizon. One of the reasons is that for extended periods of time we had no open positions, so the cumulative returns did not change, thus extending the recovery time.

zEFnE3e.png!web

  • A simple plot showing the daily returns over time. The flat periods reflect the times when we had no position in the underlying asset.

MRB7Vz3.png!web

  • A selection of plots summarizing the strategy’s returns by breaking them down into: monthly returns (separately per each year), annual returns per each year, a histogram presenting the distribution of monthly returns and finally quantiles of returns expressed in different frequencies.

iMnmaar.png!web

Conclusions

In this article, I showed how to use pyfolio to easily obtain a plethora of financial metrics and plots describing the performance of an asset/strategy/portfolio. This post gives a high-level introduction, while there are still many aspects to cover.

Some of the other functionalities of pyfolio include:

pf.plot_drawdown_periods(returns, top=5)

Quantopian also released a library called empyrical , which is used to calculate all the risk metrics used in pyfolio . One possible advantage of using it is when we are purely interested in calculating one metric, such as the Omega ratio or the Sortino ratio. If that is the case, I certainly recommend empyrical for the task.

Kudos to Quantopian for creating such comprehensive libraries as zipline , pyfolio and empyrical !

As always, any constructive feedback is welcome. You can reach out to me on Twitter or in the comments. You can find the code used for this article on my GitHub .

References


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK