Hello and welcome to part 4 of the zipline local tutorial series. Up to this point, we've covered installing Zipline, using it locally, and even incorporating your own data to some degree, but, in this tutorial, we're going to dive a bit deeper with customizing the trading calendar.
With the Zipline defaults, trading occurs Monday through Friday (except for various holidays), and between 9:30am and 4pm EST. What if you wanted to trade a different market? Maybe different times, different timezones, or maybe you have different holidays? If you wanted to trade the Japanese markets, or India's markets, the NYSE calendar is obviously not acceptable! What about with something like Cryptocurrencies, where the markets are never closed?
In this tutorial, I will be showing you how to do just that!
To do this, let us bop on over to our zipline installation directory
and go into utils/calendars
. For me, that's: C:\Python35\Lib\site-packages\zipline\utils\calendars
. Not sure where Zipline is installed? No big deal. Run python in your command line, import zipline, then do zipline.__file__
, the output of this will be where it's located.
For example:
C:\Users\H\Desktop\asfa>C:/Python35/python Python 3.5.0 (v3.5.0:374f501f4567, Sep 13 2015, 02:27:37) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import zipline >>> zipline.__file__ 'C:\\Python35\\lib\\site-packages\\zipline\\__init__.py'
So, I am not going to mess around with creating a custom calendar, I am just going to modify the NYSE
one. First, it would be wise to make a backup/copy though, so do that. I just made a copy in the calendars directory.
Now, create a new file called C:\Python35\Lib\site-packages\zipline\utils\calendars\exchange_calendar_twentyfourhr.py
becomes:
from datetime import time from zipline.utils.memoize import lazyval from pandas.tseries.offsets import CustomBusinessDay from pytz import timezone from .trading_calendar import TradingCalendar class TwentyFourHR(TradingCalendar): """ Exchange calendar for 24/7 trading. Open Time: 12am, UTC Close Time: 11:59pm, UTC """ @property def name(self): return "twentyfourhr" @property def tz(self): return timezone("UTC") @property def open_time(self): return time(0, 0) @property def close_time(self): return time(23, 59) @lazyval def day(self): return CustomBusinessDay( weekmask='Mon Tue Wed Thu Fri Sat Sun', )
Very simple calendar to read. Opens at midnight, closes 1 minute before midnight, and repeat! Timezone is shifted to UTC as well, to match our data. This is the default calendar anyway, so that's all we need to do to change that. Next, we need some data. I have hosted BTC-USD.csv, which is minute-data for the USD value of Bitcoin from the GDAX exchange. Not interested in bitcoin? That's fine, feel free to replace with whatever data you want!
Once you have that file, assuming you're in your notebook:
%load_ext zipline
Now, for our data, I am going to do the following:
data = OrderedDict() data['BTC'] = pd.read_csv("BTC-USD.csv") data['BTC']['date'] = pd.to_datetime(data['BTC']['time'], unit='s', utc=True) data['BTC'].set_index('date', inplace=True) data['BTC'].drop('time', axis=1, inplace=True) data['BTC'] = data['BTC'].resample("1min").mean() data['BTC'].fillna(method="ffill", inplace=True) data['BTC'] = data['BTC'][["low","high","open","close","volume"]] print(data['BTC'].head()) panel = pd.Panel(data) panel.minor_axis = ["low","high","open","close","volume"] panel.major_axis = panel.major_axis.tz_localize(pytz.utc) print(panel)
Notice that we convert from unix time to datetime, but also we do a 1 minute resample, then a forward fill. What's up with that? Well, this data is coming from the GDAX API, coming in real time. There might be minutes where the exchange goes down, the API stops responding, or maybe my server collecting the data goes down. There will be gaps. We need to be 100% certain there are no gaps, so I resample by minute to be 100% certain we have data by the minute. Now, if there were long enough gaps, this means there could be NaN data. To handle for this, we'll just do a forward fill.
Next, let's cover our strategy:
def initialize(context): set_benchmark(symbol("BTC")) def handle_data(context, data): slowma = data.history(symbol("BTC"), fields='price', bar_count=50, frequency='1m').mean() fastma = data.history(symbol("BTC"), fields='price', bar_count=10, frequency='1m').mean() if fastma < slowma: if symbol("BTC") not in get_open_orders(): order_target_percent(symbol("BTC"), 0.04) if fastma > slowma: if symbol("BTC") not in get_open_orders(): order_target_percent(symbol("BTC"), 0.96) record(BTC=data.current(symbol('BTC'), fields='price'))
Pretty simple strategy. We have a fast and slow ma. We buy when the fast MA is above the slow, and sell when it's below.
Now, we want to import our new calendar:
from zipline.utils.calendars.exchange_calendar_twentyfourhr import TwentyFourHR
Now in the zipline.run_algorithm
, we can use the trading_calendar
parameter with: trading_calendar=TwentyFourHR(),
, so this block would be:
perf = zipline.run_algorithm(start=datetime(2018, 3, 25, 0, 0, 0, 0, pytz.utc), end=datetime(2018, 3, 26, 0, 0, 0, 0, pytz.utc), initialize=initialize, trading_calendar=TwentyFourHR(), capital_base=10000, handle_data=handle_data, data_frequency ='minute', data=panel)
Full script up to this point:
import pandas as pd from collections import OrderedDict import pytz from zipline.api import order, record, symbol, set_benchmark, order_target_percent, get_open_orders from zipline.utils.calendars.exchange_calendar_twentyfourhr import TwentyFourHR import zipline import matplotlib.pyplot as plt from datetime import datetime def initialize(context): set_benchmark(symbol("BTC")) def handle_data(context, data): slowma = data.history(symbol("BTC"), fields='price', bar_count=50, frequency='1m').mean() fastma = data.history(symbol("BTC"), fields='price', bar_count=10, frequency='1m').mean() if fastma < slowma: if symbol("BTC") not in get_open_orders(): order_target_percent(symbol("BTC"), 0.04) if fastma > slowma: if symbol("BTC") not in get_open_orders(): order_target_percent(symbol("BTC"), 0.96) record(BTC=data.current(symbol('BTC'), fields='price')) data = OrderedDict() data['BTC'] = pd.read_csv("BTC-USD.csv") data['BTC']['date'] = pd.to_datetime(data['BTC']['time'], unit='s', utc=True) data['BTC'].set_index('date', inplace=True) data['BTC'].drop('time', axis=1, inplace=True) data['BTC'] = data['BTC'].resample("1min").mean() data['BTC'].fillna(method="ffill", inplace=True) data['BTC'] = data['BTC'][["low","high","open","close","volume"]] print(data['BTC'].head()) panel = pd.Panel(data) panel.minor_axis = ["low","high","open","close","volume"] panel.major_axis = panel.major_axis.tz_localize(pytz.utc) print(panel) perf = zipline.run_algorithm(start=datetime(2018, 2, 7, 0, 0, 0, 0, pytz.utc), end=datetime(2018, 3, 26, 0, 0, 0, 0, pytz.utc), initialize=initialize, trading_calendar=TwentyFourHR(), capital_base=10000, handle_data=handle_data, data_frequency ='minute', data=panel)
Now we can check out the results when this is done with:
perf.head()
Graphing with:
import matplotlib.pyplot as plt from matplotlib import style style.use("ggplot") perf.portfolio_value.pct_change().fillna(0).add(1).cumprod().sub(1).plot(label='portfolio') perf.BTC.pct_change().fillna(0).add(1).cumprod().sub(1).plot(label='benchmark') plt.legend(loc=2) plt.show()
There you have it! At this point, you're good to go! If you haven't check out the Quantopian API docs, which also have a Zipline-specific section. In most cases, if you can do something in the algorithms section of Quantopian, you can do it in Zipline too. You just need to import the functions you plan to use, rather than just using them (often on Quantopian, if you want to use some API function, you don't need to import anything).