Skip to content

Instantly share code, notes, and snippets.

@yhilpisch
Last active October 2, 2017 18:28
Show Gist options
  • Save yhilpisch/069fead6d3ed08e93a34b05f07fcd5c2 to your computer and use it in GitHub Desktop.
Save yhilpisch/069fead6d3ed08e93a34b05f07fcd5c2 to your computer and use it in GitHub Desktop.

Executive Program in Algorithmic Trading (QuantInsti)

Python Sessions by Dr. Yves J. Hilpisch | The Python Quants GmbH

Online, 03. & 04. June 2017

Resources

Slides

You find the introduction slides under http://hilpisch.com/epat.pdf

Python

Download and install Miniconda 2.7 from https://conda.io/miniconda.html

If you have either Miniconda or Anaconda already installed, there is not need to install anything new.

conda create -n epat python=2.7
(source) activate epat
conda install numpy pandas=0.19 scikit-learn matplotlib
conda install pandas-datareader pytables
conda install ipython jupyter
jupyter notebook

Management of environments: https://conda.io/docs/using/envs.html

Books

Good book covering object-oriented programming in Python: Fluent Python, O'Reilly

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"http://hilpisch.com/tpq_logo.png\" width=300px align=right>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Event-based Backtesting"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**EPAT Python Session 02**\n",
"\n",
"Dr. Yves J. Hilpisch\n",
"\n",
"Online, 04. June 2017"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"from pandas_datareader import data as web\n",
"from pylab import plt\n",
"# import cufflinks\n",
"plt.style.use('seaborn')\n",
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"class FinancialData(object):\n",
" def __init__(self, symbol):\n",
" self.symbol = symbol\n",
" self.get_data()\n",
" \n",
" def get_data(self):\n",
" self.data = web.DataReader(self.symbol, data_source='google')\n",
" \n",
" def plot_data(self, cols=['Close']):\n",
" self.data[cols].plot(figsize=(10, 6))"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"fd = FinancialData('AAPL')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Open</th>\n",
" <th>High</th>\n",
" <th>Low</th>\n",
" <th>Close</th>\n",
" <th>Volume</th>\n",
" </tr>\n",
" <tr>\n",
" <th>Date</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>2010-01-04</th>\n",
" <td>30.49</td>\n",
" <td>30.64</td>\n",
" <td>30.34</td>\n",
" <td>30.57</td>\n",
" <td>123432050</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2010-01-05</th>\n",
" <td>30.66</td>\n",
" <td>30.80</td>\n",
" <td>30.46</td>\n",
" <td>30.63</td>\n",
" <td>150476004</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2010-01-06</th>\n",
" <td>30.63</td>\n",
" <td>30.75</td>\n",
" <td>30.11</td>\n",
" <td>30.14</td>\n",
" <td>138039594</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2010-01-07</th>\n",
" <td>30.25</td>\n",
" <td>30.29</td>\n",
" <td>29.86</td>\n",
" <td>30.08</td>\n",
" <td>119282324</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2010-01-08</th>\n",
" <td>30.04</td>\n",
" <td>30.29</td>\n",
" <td>29.87</td>\n",
" <td>30.28</td>\n",
" <td>111969081</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Open High Low Close Volume\n",
"Date \n",
"2010-01-04 30.49 30.64 30.34 30.57 123432050\n",
"2010-01-05 30.66 30.80 30.46 30.63 150476004\n",
"2010-01-06 30.63 30.75 30.11 30.14 138039594\n",
"2010-01-07 30.25 30.29 29.86 30.08 119282324\n",
"2010-01-08 30.04 30.29 29.87 30.28 111969081"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fd.data.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Backtesting Base Class"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, we are going to implement a **base class** for the event-based backtesting.\n",
"\n",
"* `__init__`\n",
"* `get_data`\n",
"* `plot_data`\n",
"* `print_balance`\n",
"* `get_date_price` (for a \"bar\")\n",
"* `place_buy_order`\n",
"* `place_sell_order`\n",
"* `close_out`"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import math"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"class BacktestBase(FinancialData):\n",
" def __init__(self, symbol, start, end, amount, ftc=0.0, ptc=0.0):\n",
" FinancialData.__init__(self, symbol)\n",
" self.start = start\n",
" self.end = end\n",
" self.amount = amount\n",
" self.initial_amount = amount\n",
" self.ftc = ftc\n",
" self.ptc = ptc\n",
" self.units = 0\n",
" self.trades = 0\n",
" self.position = 0\n",
" \n",
" def print_balance(self, date=''):\n",
" print('%s | current cash balance %9.2f' % (date, self.amount))\n",
" \n",
" def get_date_price(self, bar):\n",
" date = str(self.run.index[bar])[:10]\n",
" price = self.run['Close'].ix[bar]\n",
" return date, price\n",
" \n",
" def place_buy_order(self, bar, units=None, amount=None):\n",
" date, price = self.get_date_price(bar)\n",
" if units is None:\n",
" units = math.floor(amount / price) # include ftc/ptc\n",
" self.amount -= (units * price) * (1 + self.ptc) + self.ftc\n",
" self.units += units\n",
" self.trades += 1\n",
" print('%s | buying %4d units at %8.2f' % (date, units, price))\n",
" self.print_balance(date)\n",
" \n",
" def place_sell_order(self, bar, units=None, amount=None):\n",
" date, price = self.get_date_price(bar)\n",
" if units is None:\n",
" units = math.floor(amount / price) # include ftc/ptc\n",
" self.amount += (units * price) * (1 - self.ptc) - self.ftc\n",
" self.units -= units\n",
" self.trades += 1\n",
" print('%s | selling %4d units at %8.2f' % (date, units, price))\n",
" self.print_balance(date)\n",
" \n",
" def close_out(self, bar):\n",
" date, price = self.get_date_price(bar)\n",
" self.amount += (self.units * price) # include ftc/ptc?!\n",
" print(50 * '=')\n",
" print('%s | buying/selling %d units at %7.2f' % (date, self.units, price))\n",
" print('Final balance [$]: %8.2f' % self.amount)\n",
" perf = (self.amount - self.initial_amount) / self.initial_amount * 100\n",
" print('Performance [%%]: %8.2f' % perf)\n",
" print('# of Trades : %8d' % self.trades)\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"bb = BacktestBase('AAPL', '2010-1-1', '2016-12-31', 10000)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'pandas.core.frame.DataFrame'>\n",
"DatetimeIndex: 1866 entries, 2010-01-04 to 2017-06-02\n",
"Data columns (total 5 columns):\n",
"Open 1866 non-null float64\n",
"High 1866 non-null float64\n",
"Low 1866 non-null float64\n",
"Close 1866 non-null float64\n",
"Volume 1866 non-null int64\n",
"dtypes: float64(4), int64(1)\n",
"memory usage: 87.5 KB\n"
]
}
],
"source": [
"bb.data.info()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\niIiISJT+D818hJNG8cxlAAAAAElFTkSuQmCC\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x10f033d90>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"bb.plot_data()"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"# bb.get_date_price(10)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" | current cash balance 10000.00\n"
]
}
],
"source": [
"bb.print_balance('')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Backtest Long Only"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"class BacktestLongOnly(BacktestBase):\n",
" \n",
" def run_sma_strategy(self, SMA1, SMA2):\n",
" msg = 'Running SMA Strategy for %s | SMA1 = %d | SMA2 = %d | ftc = %f | ptc = %f'\n",
" msg = msg % (self.symbol, SMA1, SMA2, self.ftc, self.ptc)\n",
" print(msg)\n",
" print(50 * '=')\n",
" # re-initialization\n",
" self.position = 0\n",
" self.amount = self.initial_amount\n",
" self.trades = 0\n",
" # data preparation\n",
" self.run = self.data[(self.data.index > self.start) &\n",
" (self.data.index < self.end)].copy()\n",
" self.run['SMA1'] = self.run['Close'].rolling(SMA1).mean()\n",
" self.run['SMA2'] = self.run['Close'].rolling(SMA2).mean()\n",
" self.run.dropna(inplace=True)\n",
" \n",
" for bar in range(len(self.run)):\n",
" if self.position == 0:\n",
" if self.run['SMA1'].ix[bar] > self.run['SMA2'].ix[bar]:\n",
" # self.place_buy_order(bar, amount=7500) # check for ftc/ptc?!\n",
" self.place_buy_order(bar, amount=self.amount) # check for ftc/ptc?!\n",
" self.position = 1 # long the market\n",
" elif self.position == 1:\n",
" if self.run['SMA1'].ix[bar] < self.run['SMA2'].ix[bar]:\n",
" self.place_sell_order(bar, units=self.units)\n",
" self.position = 0 # market neutral\n",
" self.close_out(bar)\n",
" \n",
" def run_momentum_strategy(self, momentum):\n",
" pass\n",
" #\n",
" # place\n",
" # trading code here\n",
" #\n",
" \n",
" def run_mean_reversion_strategy(self, SMA, threshold):\n",
" pass\n",
" #\n",
" # place\n",
" # trading code here\n",
" #"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"sma = BacktestLongOnly('AAPL', '2010-1-1', '2017-6-30', 10000)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Running SMA Strategy for AAPL | SMA1 = 42 | SMA2 = 252 | ftc = 0.000000 | ptc = 0.000000\n",
"==================================================\n",
"2011-01-03 | buying 212 units at 47.08\n",
"2011-01-03 | current cash balance 19.04\n",
"2012-12-18 | selling 212 units at 76.27\n",
"2012-12-18 | current cash balance 16188.28\n",
"2013-10-03 | buying 234 units at 69.06\n",
"2013-10-03 | current cash balance 28.24\n",
"2015-09-08 | selling 234 units at 112.31\n",
"2015-09-08 | current cash balance 26308.78\n",
"2016-09-09 | buying 255 units at 103.13\n",
"2016-09-09 | current cash balance 10.63\n",
"==================================================\n",
"2017-06-02 | buying/selling 255 units at 155.45\n",
"Final balance [$]: 39650.38\n",
"Performance [%]: 296.50\n",
"# of Trades : 5\n"
]
}
],
"source": [
"sma.run_sma_strategy(42, 252)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Open</th>\n",
" <th>High</th>\n",
" <th>Low</th>\n",
" <th>Close</th>\n",
" <th>Volume</th>\n",
" <th>SMA1</th>\n",
" <th>SMA2</th>\n",
" </tr>\n",
" <tr>\n",
" <th>Date</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>2011-01-03</th>\n",
" <td>46.52</td>\n",
" <td>47.18</td>\n",
" <td>46.41</td>\n",
" <td>47.08</td>\n",
" <td>111280407</td>\n",
" <td>45.347381</td>\n",
" <td>37.173651</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2011-01-04</th>\n",
" <td>47.49</td>\n",
" <td>47.50</td>\n",
" <td>46.88</td>\n",
" <td>47.33</td>\n",
" <td>77337001</td>\n",
" <td>45.410238</td>\n",
" <td>37.240159</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2011-01-05</th>\n",
" <td>47.08</td>\n",
" <td>47.76</td>\n",
" <td>47.07</td>\n",
" <td>47.71</td>\n",
" <td>63879193</td>\n",
" <td>45.463571</td>\n",
" <td>37.307937</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2011-01-06</th>\n",
" <td>47.82</td>\n",
" <td>47.89</td>\n",
" <td>47.56</td>\n",
" <td>47.68</td>\n",
" <td>75106626</td>\n",
" <td>45.520238</td>\n",
" <td>37.377540</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2011-01-07</th>\n",
" <td>47.71</td>\n",
" <td>48.05</td>\n",
" <td>47.41</td>\n",
" <td>48.02</td>\n",
" <td>77982212</td>\n",
" <td>45.579762</td>\n",
" <td>37.448730</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Open High Low Close Volume SMA1 SMA2\n",
"Date \n",
"2011-01-03 46.52 47.18 46.41 47.08 111280407 45.347381 37.173651\n",
"2011-01-04 47.49 47.50 46.88 47.33 77337001 45.410238 37.240159\n",
"2011-01-05 47.08 47.76 47.07 47.71 63879193 45.463571 37.307937\n",
"2011-01-06 47.82 47.89 47.56 47.68 75106626 45.520238 37.377540\n",
"2011-01-07 47.71 48.05 47.41 48.02 77982212 45.579762 37.448730"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sma.run.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"http://hilpisch.com/tpq_logo.png\" width=300px align=right>"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.13"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
#
# Tick Data Client
# with ZeroMQ
#
import zmq
import datetime
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect('tcp://127.0.0.1:5555')
socket.setsockopt(zmq.SUBSCRIBE, '')
while True:
msg = socket.recv_string()
t = datetime.datetime.now()
print(str(t) + ' | ' + msg)
#
# Tick Data Collector
# with ZeroMQ
#
import zmq
import datetime
import pandas as pd
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect('tcp://127.0.0.1:5555')
socket.setsockopt(zmq.SUBSCRIBE, '')
df = pd.DataFrame()
i = 0
while True:
i += 1
msg = socket.recv_string()
sym, value = msg.split()
t = datetime.datetime.now()
print(('%2d' % i) + ' | ' + str(t) + ' | ' + msg)
df = df.append(pd.DataFrame({sym: float(value)}, index=[t]))
if i % 5 == 0:
df['SMA1'] = df[sym].rolling(5).mean()
df['SMA2'] = df[sym].rolling(10).mean()
print(df.tail())
#
# Tick Data Server
# with ZeroMQ
#
import zmq
import random
import time
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind('tcp://127.0.0.1:5555')
AAPL = 100.
while True:
AAPL += random.gauss(0, 1) * 0.5
msg = 'AAPL %.3f' % AAPL
print(msg)
socket.send_string(msg)
time.sleep(random.random() * 2)
#
# Tick Data Collector
# with ZeroMQ
#
import zmq
import datetime
import pandas as pd
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect('tcp://127.0.0.1:5555')
socket.setsockopt(zmq.SUBSCRIBE, '')
df = pd.DataFrame()
i = 0
position = 0
while True:
i += 1
msg = socket.recv_string()
sym, value = msg.split()
t = datetime.datetime.now()
print(('%2d' % i) + ' | ' + str(t) + ' | ' + msg)
df = df.append(pd.DataFrame({sym: float(value)}, index=[t]))
if i >= 10 :
df['SMA1'] = df[sym].rolling(5).mean()
df['SMA2'] = df[sym].rolling(10).mean()
if position == 0:
if df['SMA1'].ix[-1] > df['SMA2'].ix[-1]:
print(50 * '=')
print('WE GO LONG THE MARKET.')
#
# code to connect to your broker
# to place a buy order
#
position = 1
print(df.ix[-1])
print('')
elif position == 1:
if df['SMA1'].ix[-1] < df['SMA2'].ix[-1]:
print(50 * '=')
print('WE EXIT THE MARKET.')
#
# code to connect to your broker
# to place a sell order
#
position = 0
print(df.ix[-1])
print('')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment