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:
- Create a directory for CSV files, for example, C:\MarketData, with a subdirectory called Daily.
- Copy the CSV files to the created directory (for example, C:\MarketData\Daily).
- 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(
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.
- 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,
220 Introduction to Zipline and PyFolio
Installing the custom bundle is straightforward:
- 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']
- 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.
- 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:
- Download all the EOD data.
- Generate the metadata.
- Apply the trading calendar.
- 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:
- Download the repository from https://github.com/hhatefi/zipline_ bundles.
- 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.
- 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', )
- 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']
- 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.
- 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
- 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
- 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',
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
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.
'Finance' 카테고리의 다른 글
Fundamental Algorithmic Trading Strategies (0) | 2023.03.20 |
---|---|
Financial Market Data Access in Python (0) | 2023.03.20 |
Statistical Estimation, Inference, and Prediction (0) | 2023.03.20 |
Data Visualization Using Matplotlib (0) | 2023.03.20 |
Data Manipulation and Analysis with pandas (0) | 2023.03.20 |
댓글