본문 바로가기
Finance

Introduction to Zipline and PyFolio

by 자동매매 2023. 3. 20.

8

Introduction to Zipline and PyFolio

In this chapter, you will learn about the Python libraries known as Zipline and PyFolio, which abstract away the complexities of the backtesting and performance/risk analysis aspects of algorithmic trading strategies. ey allow you to completely focus on the trading logic.

For this, we are going to cover the following main topics:

Introduction to Zipline and PyFolio

Installing Zipline and PyFolio

Importing market data into a Zipline/PyFolio backtesting system

Structuring Zipline/PyFolio backtesting modules

Reviewing the key Zipline API reference

Running Zipline backtesting from the command line

Introduction to the key risk management figures provided by PyFolio

Technical requirements

e Python code used in this chapter is available in the Chapter08/risk_management. ipynb notebook in the book’s code repository.

214 Introduction to Zipline and PyFolio

Introduction to Zipline and PyFolio

Backtesting is a computational method of assessing how well a trading strategy would have done if it had been applied to historical data. Ideally, this historical data should come from a period of time where there were similar market conditions, such as it having similar volatility to the present and the future.

Backtesting should include all relevant factors, such as slippage and trading costs.

Zipline is one of the most advanced open source Python libraries for algorithmic

trading backtesting engines. Its source code can be found at https://github.com/ quantopian/zipline. Zipline is a backtesting library ideal for daily trading (you can also backtest weekly, monthly, and so on). It is less suitable for backtesting high-frequency trading strategies.

PyFolio is an open source Python performance and risk analysis library consisting of financial portfolios that’s closely integrated with Zipline. You can find its documentation at https://github.com/quantopian/pyfolio.

Using these two libraries to backtest your trading strategy saves you an enormous amount of time.

e objective of this chapter is to describe the key functionality of these libraries and to

build your intuition. You are encouraged to debug the code in PyCharm or any other

Python IDE and study the contents of each result’s variables to make full use of the provided information. Once you become familiar with the contents of each resultant object, briefly study the source code of the libraries to see their full functionality.

Installing Zipline and PyFolio

We recommend setting up the development environment as described in Appendix A. Nevertheless, the detailed instructions are given in the following sections.

Installing Zipline

For performance reasons, Zipline is closely dependent on a particular version of Python and its related libraries. erefore, the best way to install it is to create a conda virtual environment and install Zipline there. We recommend using Anaconda Python for this.

Let’s create a virtual environment called zipline_env with Python 3.6 and install the zipline package:

conda create -n zipline_env python=3.6 conda activate zipline_env

conda install -c conda-forge zipline

Importing market data into a Zipline/PyFolio backtesting system 215

We will now install PyFolio.

Installing PyFolio

You can install the pyfolio package via pip:

pip install pyfolio

As we can see, installing PyFolio is also an easy task.

Importing market data into a Zipline/PyFolio backtesting system

Backtesting depends on us having an extensive market data database. Zipline introduces two market data-specific terms bundle and ingest:

A bundle is an interface for incrementally importing market data into Zipline’s

proprietary database from a custom source.

An ingest is the actual process of incrementally importing the custom source

market data into Zipline’s proprietary database; the data ingest is not automatically updated. Each time you need fresh data, you must re-ingest the bundle.

By default, Zipline supports these bundles:

Historical Quandl bundle (complimentary daily data for US equities up to 2018)

.csv files bundle

We will now learn how to import these two bundles in more detail.

Importing data from the historical Quandl bundle

First, in the activated zipline_env environment, set the QUANDL_API_KEY environment variable to your free (or paid) Quandl API key. en, ingest the quandl data.

For Windows, use the following code:

SET QUANDL_API_KEY=XXXXXXXX zipline ingest -b quandl

216 Introduction to Zipline and PyFolio

For Mac/Linux, use the following code:

export QUANDL_API_KEY=XXXXXXXX zipline ingest -b quandl

Note

Quandl stopped updating the complimentary bundle in 2018 but is still more than useful for the first few algorithmic trading steps.

It’s best to set QUANDL_API_KEY in Windows’ System Properties (press the Windows icon and type Environment Variables):

Figure 8.1 Locating the Edit the system environment variables dialog on Windows

Importing market data into a Zipline/PyFolio backtesting system 217

en, choose Edit the system environment variables.

Figure 8.2 e location of the Environment Variables dialog in System Properties on Windows

en, specify the variable in the Environment Variables... dialog.

On Mac/Linux, add the following command to ~/.bash_profile for user-based operations or ~/.bashrc for non-login interactive shells:

export QUANDL_API_KEY=xxxx

Now, let’s learn how to import data from the CSV files bundle.

218 Introduction to Zipline and PyFolio

Importing data from the CSV files bundle

e default CSV bundle requires the CSV file to be in open, high, low, close, volume (OHLCV) format with dates, dividends, and splits:

date,open,high,low,close,volume,dividend,split

is book’s GitHub repository contains one sample input CSV file. Its top few lines are as follows:

date,open,high,low,close,volume,dividend,split

2015-05- 15,18251.9707,18272.7207,18215.07031,18272.56055,108220000,0,0

2015-05- 18,18267.25,18325.53906,18244.25977,18298.88086,79080000,0,0

2015-05- 19,18300.48047,18351.35938,18261.34961,18312.39063,87200000,0,0

2015-05- 20,18315.06055,18350.13086,18272.56055,18285.40039,80190000,0,0

2015-05- 21,18285.86914,18314.89063,18249.90039,18285.74023,84270000,0,0

2015-05- 22,18286.86914,18286.86914,18217.14063,18232.01953,78890000,0,0

2015-05- 26,18229.75,18229.75,17990.01953,18041.53906,109440000,0,0

To use the custom CSV files bundle, follow these steps:

  1. Create a directory for CSV files, for example, C:\MarketData, with a subdirectory called Daily.
  2. Copy the CSV files to the created directory (for example, C:\MarketData\Daily).
  3. Edit the .py file extension in the C:\Users<username>.zipline\ extension.py directory on Windows or ~/.zipline/extension.py on Mac/Linux, as shown:

import pandas as pd

from zipline.data.bundles import register

from zipline.data.bundles.csvdir import csvdir_equities

register(

'packt-csvdir-bundle',

Importing market data into a Zipline/PyFolio backtesting system 219

csvdir_equities(

['daily'],

'c:/MarketData/',

),

calendar_name='NYSE',

start_session=pd.Timestamp('2015-5-15', tz='utc'), end_session=pd.Timestamp('2020-05-14', tz='utc')

)

Notice that we associate the market data with a trading calendar. In this case, we’re using NYSE, which corresponds to the US equities.

  1. Ingest the bundle, as follows:

zipline ingest -b packt-csvdir-bundle e output is as follows:

Figure 8.3 Output of the zipline ingest for packt-csvdir-bundle

is has created one asset with the A ticker.

Importing data from custom bundles

e historical Quandl bundle is most suitable for learning how to design and backtest

an algorithmic strategy. e CSV files bundle is most suitable for importing prices of assets with no public prices. However, for other purposes, you should purchase a market data subscription.

Importing data from Quandl's EOD US Stock Prices data

Quandl offers a subscription service for the End of Day US Stock Prices database (https://www.quandl.com/data/EOD-End-of-Day-US-Stock-Prices) at 49 USD per month, with discounts for quarterly or annual payments.

e advantages of this service, compared to others, are as follows:

Quandl is deeply integrated into Zipline and you can download the history of all the

stocks using one command.

ere is no hard limit in terms of the number of API calls you can make per month,

unlike other providers.

220 Introduction to Zipline and PyFolio

Installing the custom bundle is straightforward:

  1. Find the location of the bundles directory using the following command:

python -c "import zipline.data.bundles as bdl; )print(bdl.__path__)"

is results in the following output on my computer:

['d:\Anaconda3\envs\zipline_env\lib\site-packages\ zipline\data\bundles']

  1. Copy the quandl_eod.py file from this book’s GitHub repository into that directory. e file is a slight modification of the code from Zipline’s GitHub.
  2. In the same directory, modify the __init__.py file (add this line there): from . import quandl_eod # noqa

An example of the full __init__.py file is as follows:

  • These imports are necessary to force module-scope register calls to happen.

from . import quandl # noqa

from . import csvdir # noqa from . import quandl_eod # noqa

from .core import ( UnknownBundle,

bundles,

clean,

from_bundle_ingest_dirname, ingest, ingestions_for_bundle, load,

register, to_bundle_ingest_dirname, unregister,

)

__all__ = [

Importing market data into a Zipline/PyFolio backtesting system 221

'UnknownBundle', 'bundles',

'clean', 'from_bundle_ingest_dirname', 'ingest', 'ingestions_for_bundle', 'load',

'register', 'to_bundle_ingest_dirname', 'unregister',

]

Once you have set this up, ensure you have set the QUANDL_API_KEY environment variable to your API key and run the ingest command:

zipline ingest -b quandl_eod

e output is as follows:

Figure 8.4 Output of ingesting the quandl_eod bundle

222 Introduction to Zipline and PyFolio

e actual source code of quandl_eod.py is self-explanatory. e quandl_eod_ bundle function, which is annotated with @bundles.register("quandl_eod"), defines the download process:

@bundles.register("quandl_eod")

def quandl_eod_bundle(environ, asset_db_writer, minute_bar_writer, daily_bar_writer, adjustment_writer, calendar, start_session, end_session, cache, show_progress, output_dir): """

quandl_bundle builds a daily dataset using Quandl's WIKI Prices dataset.

For more information on Quandl's API and how to obtain an API key,

please visit https://docs.quandl.com/docs#section- authentication

"""

api_key = environ.get("QUANDL_API_KEY") if api_key is None:

raise ValueError(

"Please set your QUANDL_API_KEY environment variable and retry."

)

raw_data = fetch_data_table(

api_key, show_progress,

environ.get("QUANDL_DOWNLOAD_ATTEMPTS", 5) )

asset_metadata = \

gen_asset_metadata(raw_data[["symbol", "date"]],

Importing market data into a Zipline/PyFolio backtesting system 223

show_progress) asset_db_writer.write(asset_metadata)

symbol_map = asset_metadata.symbol

sessions = calendar.sessions_in_range(start_session, end_session)

raw_data.set_index(["date", "symbol"], inplace=True) daily_bar_writer.write(

parse_pricing_and_vol(raw_data, sessions, symbol_map), show_progress=show_progress,

)

raw_data.reset_index(inplace=True)

raw_data["symbol"] = \ raw_data["symbol"].astype("category")

raw_data["sid"] = raw_data.symbol.cat.codes adjustment_writer.write( splits=parse_splits(

raw_data[["sid", "date", "split_ratio"]].loc[raw_ data.split_ratio != 1],

show_progress=show_progress, ), dividends=parse_dividends(

raw_data[["sid", "date", "ex_dividend"]].loc[raw_ data.ex_dividend != 0],

show_progress=show_progress, ),

)

e steps that are involved in this process are as follows:

  1. Download all the EOD data.
  2. Generate the metadata.
  3. Apply the trading calendar.
  4. Apply the corporate events.

224 Introduction to Zipline and PyFolio

While Quandl’s commercial data source is deeply integrated with Zipline, there are alternative data sources.

Importing data from Yahoo Finance and IEX paid data

e project at https://github.com/hhatefi/zipline_bundles provides a Zipline bundle for Yahoo Finance and IEX. e package supports Zipline imports from a Yahoo Finance .csv file, Yahoo Finance directly, and from IEX. is book will only focus on directly importing from Yahoo Finance and IEX.

While the package does allow automatic installation, I do not recommend it since it requires an empty extension.py file in the C:\Users<username>.zipline\ extension.py directory on Windows or ~/.zipline/extension.py on Mac/Linux.

e installation steps are as follows:

  1. Download the repository from https://github.com/hhatefi/zipline_ bundles.
  2. Merge the repository’s \zipline_bundles-master\lib\extension.py file with C:\Users<username>.zipline\extension.py on Windows or ~/.zipline/extension.py on Mac/Linux. If the latter file does not exist, just copy and paste the file.
  3. Edit the start and end dates in the following code:

register('yahoo_direct', # bundle's name direct_ingester('YAHOO', every_min_bar=False,

symbol_list_env='YAHOO_SYM_LST',

  • the environment variable holding the comma separated list of assert names

downloader=yahoo.get_ downloader(start_date='2010-01-01',

end_date='2020-01-01' ), ), calendar_name='NYSE', )

Importing market data into a Zipline/PyFolio backtesting system 225

Do the same in the following code:

register('iex', # bundle's name

direct_ingester('IEX Cloud', every_min_bar=False,

symbol_list_env='IEX_SYM_LST',

  • the environemnt variable holding the comma separated list of assert names

downloader=iex.get_ downloader(start_date='2020-01-01',

end_date='2020-01-05' ),

filter_cb=lambda df: df[[cal. is_session(dt) for dt in df.index]]

), calendar_name='NYSE', )

e full file should look as follows:

#!/usr/bin/env python

  • -*- coding: utf-8 -*-

from pathlib import Path

from zipline.data.bundles import register

from zipline.data.bundles.ingester import csv_ingester

  • ingester.py need to be placed in zipline.data.bundles

_DEFAULT_PATH = str(Path.home()/'.zipline/csv/yahoo')

register(

'yahoo_csv',

csv_ingester('YAHOO', every_min_bar=False,

  • the price is daily

csvdir_env='YAHOO_CSVDIR', csvdir=_DEFAULT_PATH, index_column='Date',

226 Introduction to Zipline and PyFolio

column_mapper={'Open': 'open',

'High': 'high',

'Low': 'low',

'Close': 'close',

'Volume': 'volume',

'Adj Close': 'price', },

),

calendar_name='NYSE',

)

from zipline.data.bundles.ingester import direct_ingester

from zipline.data.bundles import yahoo register('yahoo_direct', # bundle's name direct_ingester('YAHOO', every_min_bar=False,

symbol_list_env='YAHOO_SYM_LST',

  • the environemnt variable holding the comma separated

list of assert names

downloader=yahoo.get_ downloader(start_date='2010-01-01',

end_date='2020-01-01' ), ), calendar_name='NYSE', )

from zipline.data.bundles import iex import trading_calendars as tc

cal=tc.get_calendar('NYSE')

register('iex', # bundle's name

direct_ingester('IEX Cloud', every_min_bar=False,

Importing market data into a Zipline/PyFolio backtesting system 227

symbol_list_env='IEX_SYM_LST', # the environemnt variable holding the comma separated list of assert names

downloader=iex.get_ downloader(start_date='2020-01-01',

end_date='2020-01-05' ),

filter_cb=lambda df: df[[cal. is_session(dt) for dt in df.index]]

), calendar_name='NYSE', )

  1. Find the location of the bundles directory using the following command:

python -c "import zipline.data.bundles as bdl; )print(bdl.__path__)"

is results in the following output on my computer:

['d:\Anaconda3\envs\zipline_env\lib\site-packages\ zipline\data\bundles']

  1. Copy the Copy \zipline_bundles-master\lib\iex.py, \zipline_ bundles-master\lib\ingester.py, and \zipline_bundles-master\ lib\yahoo.py repository files into your Zipline bundles directory; for example, d:\Anaconda3\envs\zipline_env\lib\site-packages\ zipline\data\bundles.
  2. Set the tickers of interest as environmental variables. For example, for Windows, use the following code:

set YAHOO_SYM_LST=GOOG,AAPL,GE,MSFT set IEX_SYM_LST=GOOG,AAPL,GE,MSFT

For Mac/Linux, use the following code:

export YAHOO_SYM_LST=GOOG,AAPL,GE,MSFT export IEX_SYM_LST=GOOG,AAPL,GE,MSFT

228 Introduction to Zipline and PyFolio

  1. Set an IEX token (it starts with sk_ ), if available, like so on Windows:

set IEX_TOKEN=xxx

For Mac/Linux, do the following:

export IEX_TOKEN=xxx

  1. Ingest the data:

zipline ingest -b yahoo_direct zipline ingest -b iex

is results in the following output in terms of the yahoo_direct bundle:

Figure 8.5 Output of ingesting the yahoo_direct bundle

Structuring Zipline/PyFolio backtesting modules 229

is also results in the following output, which is for the iex bundle:

Figure 8.6 Output of ingesting the iex bundle

Integrating with other data sources, such as a local MySQL database, is similar to the code in https://github.com/hhatefi/zipline_bundles. Some such bundles are available on github.com.

Structuring Zipline/PyFolio backtesting modules

Typical Zipline backtesting code defines three functions:

initialize: is method is called before any simulated trading happens; it’s used to enrich the context object with the definition of tickers and other key trading information. It also enables commission and slippage considerations.

handle_data: is method downloads the market data, calculates the trading signals, and places the trades. is is where you put the actual trading logic on entry/exit positions.

analyze: is method is called to perform trading analytics. In our code, we will use pyfolio’s standard analytics. Notice that the pf.utils.extract_rets_ pos_txn_from_zipline(perf) function returns any returns, positions, and transactions for custom analytics.

Finally, the code defines the start date and the end date and performs backtesting by calling the run_algorithm method. is method returns a comprehensive summary of all the trades to be persisted to a file, which can be analyzed later.

ere are a few typical patterns when it comes to Zipline’s code, depending on the use case.

230 Introduction to Zipline and PyFolio

Trading happens every day

Let’s refer to the handle_data method directly from the run_algorithm method:

from zipline import run_algorithm

from zipline.api import order_target_percent, symbol from datetime import datetime

import pytz

import matplotlib.pyplot as plt

import pandas as pd

import pyfolio as pf

from random import random

def initialize(context): pass

def handle_data(context, data): pass

def analyze(context, perf):

returns, positions, transactions = \ pf.utils.extract_rets_pos_txn_from_zipline(perf) pf.create_returns_tear_sheet(returns,

benchmark_rets = None)

start_date = pd.to_datetime('1996-1-1', utc=True) end_date = pd.to_datetime('2020-12-31', utc=True)

results = run_algorithm(start = start_date, end = end_date, initialize = initialize,

analyze = analyze,

handle_data = handle_data,

capital_base = 10000,

data_frequency = 'daily',

bundle ='quandl')

e handle_data method will be called for every single day between start_date and end_date.

Structuring Zipline/PyFolio backtesting modules 231

Trading happens on a custom schedule

We omit the references to the handle_data method in the run_algorithm method. Instead, we set the scheduler in the initialize method:

from zipline import run_algorithm

from zipline.api import order_target_percent, symbol, set_ commission, schedule_function, date_rules, time_rules from datetime import datetime

import pytz

import matplotlib.pyplot as plt import pandas as pd

import pyfolio as pf

from random import random

def initialize(context):

  • definition of the stocks and the trading parameters, e.g.

commission

schedule_function(handle_data, date_rules.month_end(), time_rules.market_open(hours=1))

def handle_data(context, data): pass

def analyze(context, perf):

returns, positions, transactions = \ pf.utils.extract_rets_pos_txn_from_zipline(perf) pf.create_returns_tear_sheet(returns,

benchmark_rets = None)

start_date = pd.to_datetime('1996-1-1', utc=True) end_date = pd.to_datetime('2020-12-31', utc=True)

results = run_algorithm(start = start_date, end = end_date, initialize = initialize,

analyze = analyze,

capital_base = 10000,

data_frequency = 'daily',

bundle ='quandl')

232 Introduction to Zipline and PyFolio

e handle_data method will be called for every single month_end with the prices 1 hour a er the market opens.

We can specify various date rules, as shown here:

Figure 8.7 Table containing various date rules

Similarly, we can specify time rules, as shown here:

Figure 8.8 Table containing various time rules

We will now learn how to review the key Zipline API reference.

Reviewing the key Zipline API reference 233

Reviewing the key Zipline API reference

In this section, we will outline the key features from https://www.zipline.io/ appendix.html.

For backtesting, the most important features are order types, commission models, and

slippage models. Let’s look at them in more detail. Types of orders

Zipline supports these types of orders:

Figure 8.9 Supported order types

e order-placing logic is typically placed in the handle_data method. e following is an example:

def handle_data(context, data):

price_hist = data.history(context.stock, "close",

context.rolling_window, "1d")

order_target_percent(context.stock, 1.0 if price_hist[-1] > price_hist.mean() else 0.0)

234 Introduction to Zipline and PyFolio

is example places an order so that we own 100% of the stock if the last daily price is above the average of the close prices.

Commission models

Commission is the fee that’s charged by a brokerage for selling or buying stocks. Zipline supports various types of commissions, as shown here:

Figure 8.10 Supported commission types

is logic is typically placed into the initialize method. e following is an example:

def initialize(context):

context.stock = symbol('AAPL')

context.rolling_window = 90 set_commission(PerTrade(cost=5))

In this example, we have defined a commission of 5 USD per trade.

Slippage models

Slippage is defined as the difference between the expected price and the executed price.

Running Zipline backtesting from the command line 235

Zipline offers these slippage models:

Figure 8.11 Supported slippage models

e slippage model should be placed in the initialize method. e following is an example:

def initialize(context):

context.stock = symbol('AAPL')

context.rolling_window = 90 set_commission(PerTrade(cost=5)) set_slippage(VolumeShareSlippage(volume_limit=0.025, price_impact=0.05))

In this example, we have chosen VolumeShareSlippage with a limit of 0.025 and a price impact of 0.05.

Running Zipline backtesting from the command line

For large backtesting jobs, it’s preferred to run backtesting from the command line. e following command runs the backtesting strategy defined in the job.py Python

script and saves the resulting DataFrame in the job_results.pickle pickle file:

zipline run -f job.py --start 2016-1-1 --end 2021-1-1 -o job_ results.pickle --no-benchmark

236 Introduction to Zipline and PyFolio

For example, you can set up a batch consisting of tens of Zipline command-line jobs to run overnight, with each storing the results in a pickle file for later analysis.

It’s a good practice to keep a journal and library of past backtesting pickle files for easy reference.

Introduction to risk management with PyFolio

Having a risk management system is a fundamental part of having a successful algorithmic trading system.

Various risks are involved in algorithmic trading:

Market risk: While all strategies lose money at some point in their life cycle,

quantifying risk measures and ensuring there are risk management systems in

place can mitigate strategy losses. In some cases, bad risk management can increase trading losses to an extreme and even shut down successful trading firms completely.

Regulatory risk: is is the risk that stems from either accidentally or intentionally

violating regulations. It is designed to enforce smooth and fair market functionality. Some well-known examples include spoofing, quote stuffing, and banging the close.

So ware implementation risk: So ware development is a complex process and

sophisticated algorithmic trading strategy systems are especially complex. Even seemingly minor so ware bugs can lead to malfunctioning algorithmic trading strategies and yield catastrophic outcomes.

Operational risk: is risk comes from deploying and operating these algorithmic

trading systems. Operations/trading personnel mistakes can also lead to disastrous outcomes. Perhaps the most well-known error in this category is the fat-finger error, which refers to accidentally sending huge orders and/or at unintentional prices.

e PyFolio library provides extensive market performance and risk reporting functionality.

Introduction to risk management with PyFolio 237

A typical PyFolio report looks as follows:

Figure 8.12 PyFolio’s standard output showing the backtesting summary and key risk statistics

e following text aims to explain the key statistics in this report; that is, Annual volatility, Sharpe ratio, and drawdown.

For the purpose of this chapter, let’s generate trades and returns from a hypothetical trading strategy.

238 Introduction to Zipline and PyFolio

e following code block generates hypothetical PnLs for a trading strategy with a slight positive bias and hypothetical positions with no bias:

dates = pd.date_range('1992-01-01', '2012-10-22') np.random.seed(1)

pnls = np.random.randint(-990, 1000, size=len(dates))

  • slight positive bias

pnls = pnls.cumsum()

positions = np.random.randint(-1, 2, size=len(dates)) positions = positions.cumsum()

strategy_performance = \

pd.DataFrame(index=dates,

data={'PnL': pnls, 'Position': positions})

strategy_performance

PnL Position 1992-01-01 71 0 1992-01-02 -684 0 1992-01-03 258 1 ... ... ... 2012-10-21 32100 -27 2012-10-22 32388 -26 7601 rows × 2 columns

Let’s review how the PnL varies over the course of 20 years:

strategy_performance['PnL'].plot(figsize=(12,6), color='black', )legend='PnL')

Introduction to risk management with PyFolio 239

Here’s the output:

Figure 8.13 Plot showing the synthetically generated PnLs with a slight positive bias

is plot confirms that the slight positive bias causes the strategy to be profitable in the long run.

Now, let’s explore some risk metrics of this hypothetical strategy’s performance.

Market volatility, PnL variance, and PnL standard deviation

Market volatility is defined as the standard deviation of prices. Generally, during more volatile market conditions, trading strategy PnLs also undergo increased swings in magnitude. is is because the same positions are susceptible to larger price moves, which means that the PnL moves.

PnL variance is used to measure the magnitude of volatility in the strategy’s performance/returns.

e code to compute the PnL’s standard deviation is identical to the code that’s used to compute the standard deviation of prices (market volatility).

240 Introduction to Zipline and PyFolio

Let’s compute the PnL standard deviation over a rolling 20-day period:

strategy_performance['PnLStdev'] = strategy_performance['PnL']. rolling(20).std().fillna(method='backfill')

strategy_performance['PnLStdev'].plot(figsize=(12,6), color='black', legend='PnLStdev')

e output is as follows:

Figure 8.14 Plot showing PnL standard deviations across a 20-day rolling period

is plot proves that, in this case, there is not a significant pattern it is a relatively random strategy.

Trade-level Sharpe ratio

e trade-level Sharpe ratio compares average PnLs (strategy returns) relative to PnL standard deviations (strategy volatility). Compared to the standard Sharpe ratio, the Trade Level Sharpe Ratio assumes that the risk-free rate is 0 since we don’t roll over positions, so there is no interest charge. is assumption is realistic for intraday or daily trading.

e advantage of this measure is that it’s a single number that takes all the relevant risk management factors into account, so we can easily compare the performance of different strategies. Nevertheless, it’s important to realize that the Sharpe ratio does not tell the whole story and that it’s critical to use it in combination with other risk measures.

Introduction to risk management with PyFolio 241

e Trade Level Sharpe Ratio is defined as follows:

Let’s generate the Sharpe ratio for our strategy’s returns. First, we’ll generate the daily PnLs:

daily_pnl_series = strategy_performance['PnL'].shift(-1) - strategy_performance['PnL']

daily_pnl_series.fillna(0, inplace=True) avg_daily_pnl = daily_pnl_series.mean() std_daily_pnl = daily_pnl_series.std() sharpe_ratio = avg_daily_pnl/std_daily_pnl sharpe_ratio

0.007417596376703097

Intuitively, this Sharpe ratio makes sense since the hypothetical strategy’s expected daily average performance was set to (1000-990)/2 = $5 and the daily standard deviation of PnLs was set to be roughly $1,000 based on this line:

pnls = np.random.randint(-990, 1000, size=len(dates))

  • slight positive bias

In practice, Sharpe ratios are o en annualized so that we can make comparisons between strategies with different horizons fairer. To annualize the Sharpe ratio computed over daily returns, we must multiply it by the square root of 252 (the number of trading dates in a year):

e code for this is as follows:

annualized_sharpe_ratio = sharpe_ratio * np.sqrt(252) annualized_sharpe_ratio

0.11775069203166105

242 Introduction to Zipline and PyFolio

Now, let’s interpret the Sharpe ratio:

A ratio of 3.0 or higher is excellent.

A ratio > 1.5 is very good.

A ratio > 1.0 is acceptable.

A ratio < 1.0 is considered sub-optimal.

We will now look at maximum drawdown.

Maximum drawdown

Maximum drawdown is the peak-to-trough decline in a trading strategy’s cumulative PnL over a period of time. In other words, it’s the longest streak of losing money compared to the last known maximum cumulative PnL.

is metric quantifies the worst-case decline in a trading account’s value based on historical results.

Let’s visually find the maximum drawdown in the hypothetical strategy’s performance:

strategy_performance['PnL'].plot(figsize=(12,6), color='black', legend='PnL')

plt.axhline(y=28000, color='darkgrey', linestyle='--', label='PeakPnLBeforeDrawdown')

plt.axhline(y=-15000, color='darkgrey', linestyle=':', label='TroughPnLAfterDrawdown')

plt.vlines(x='2000', ymin=-15000, ymax=28000,

label='MaxDrawdown', color='black', linestyle='-.') plt.legend()

Introduction to risk management with PyFolio 243

Here’s the output:

Figure 8.15 Plot showing the peak and trough PnLs and max drawdown

From this plot, we can assess that the biggest drawdown was $43K for this strategy, from the peak PnL of roughly $28K in 1996 to the trough PnL of roughly -$15K in 2001. If we had started this strategy in 1996, we would have experienced a loss of $43K in our account, which we need to be aware of and prepared for moving forward.

Strategy stop rule – stop loss/maximum loss

Before opening trades, it’s important to set a stop loss barrier, which is defined as the maximum number of losses that a strategy or portfolio (which is just a collection of strategies) can take before it is stopped.

e stop loss barrier can be set using historical maximum drawdown values. For our hypothetical strategy, we saw that over the course of 20 years, the maximum drawdown that was achieved was $43K. While historical results are not 100% representative of future results, you might wish to use a $43K stop loss value for this strategy and shut it down if it loses that much money in the future. In practice, setting stop losses is much more complex than described here, but this should help you build some intuition about stop losses.

Once a strategy is stopped, we can decide to shut down the strategy forever or just shut it down for a certain period of time, or even shut it down until certain market conditions change. is decision depends on the strategy’s behavior and its risk tolerance.

244 Introduction to Zipline and PyFolio

Summary

In this chapter, we learned how to install and set up a complete backtesting and risk/performance analysis system based on Zipline and PyFolio. We then imported market data into a Zipline/PyFolio backtesting protfolio and structured it and reviewed it. en,

we looked into how to manage risk with PyFolio and make a successful algorithmic trading system.

In the next chapter, we make full use of this setup and introduce several key trading strategies.

댓글