Skip to content

Commit 0e0d5e2

Browse files
committed
init file add
0 parents  commit 0e0d5e2

File tree

12 files changed

+1637
-0
lines changed

12 files changed

+1637
-0
lines changed

backtest.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# backtest.py
2+
3+
__version__ = '0.1.2'
4+
5+
from abc import ABCMeta, abstractmethod
6+
7+
class Strategy(object):
8+
"""Strategy is an abstract base class providing an interface for
9+
all subsequent (inherited) trading strategies.
10+
11+
The goal of a (derived) Strategy object is to output a list of signals,
12+
which has the form of a time series indexed pandas DataFrame.
13+
14+
In this instance only a single symbol/instrument is supported."""
15+
16+
__metaclass__ = ABCMeta
17+
18+
@abstractmethod
19+
def generate_signals(self):
20+
"""An implementation is required to return the DataFrame of symbols
21+
containing the signals to go long, short or hold (1, -1 or 0)."""
22+
raise NotImplementedError("Should implement generate_signals()!")
23+
24+
class Portfolio(object):
25+
"""An abstract base class representing a portfolio of
26+
positions (including both instruments and cash), determined
27+
on the basis of a set of signals provided by a Strategy."""
28+
29+
__metaclass__ = ABCMeta
30+
31+
@abstractmethod
32+
def generate_positions(self):
33+
"""Provides the logic to determine how the portfolio
34+
positions are allocated on the basis of forecasting
35+
signals and available cash."""
36+
raise NotImplementedError("Should implement generate_positions()!")
37+
38+
@abstractmethod
39+
def backtest_portfolio(self):
40+
"""Provides the logic to generate the trading orders
41+
and subsequent equity curve (i.e. growth of total equity),
42+
as a sum of holdings and cash, and the bar-period returns
43+
associated with this curve based on the 'positions' DataFrame.
44+
45+
Produces a portfolio object that can be examined by
46+
other classes/functions."""
47+
raise NotImplementedError("Should implement backtest_portfolio()!")

data.py

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import datetime
2+
import os, os.path
3+
import pandas as pd
4+
import pgdb
5+
import sql_legacy as iosql
6+
7+
from abc import ABCMeta, abstractmethod
8+
9+
from event import MarketEvent
10+
db = pgdb.database('django.xml', 'rw')
11+
(conn, cursor) = db.getConnCursor()
12+
13+
14+
class DataHandler(object):
15+
"""
16+
DataHandler is an abstract base class providing an interface for
17+
all subsequent (inherited) data handlers (both live and historic).
18+
19+
The goal of a (derived) DataHandler object is to output a generated
20+
set of bars (OLHCVI) for each symbol requested.
21+
22+
This will replicate how a live strategy would function as current
23+
market data would be sent "down the pipe". Thus a historic and live
24+
system will be treated identically by the rest of the backtesting suite.
25+
"""
26+
27+
__metaclass__ = ABCMeta
28+
29+
@abstractmethod
30+
def get_latest_bars(self, symbol, N=1):
31+
"""
32+
Returns the last N bars from the latest_symbol list,
33+
or fewer if less bars are available.
34+
"""
35+
raise NotImplementedError("Should implement get_latest_bars()")
36+
37+
@abstractmethod
38+
def update_bars(self):
39+
"""
40+
Pushes the latest bar to the latest symbol structure
41+
for all symbols in the symbol list.
42+
"""
43+
raise NotImplementedError("Should implement update_bars()")
44+
45+
class HistoricDBDataHandler(DataHandler):
46+
"""
47+
HistoricDataHandler is designed to read the database quotes table for
48+
each requested symbol from disk and provide an interface
49+
to obtain the "latest" bar in a manner identical to a live
50+
trading interface.
51+
"""
52+
53+
def __init__(self, events, symbol_list):
54+
"""
55+
Initialises the historic data handler by requesting
56+
getting the events queue and a list of symbols.
57+
58+
Parameters:
59+
events - The Event Queue.
60+
symbol_list - A list of symbol strings.
61+
"""
62+
self.events = events
63+
self.symbol_list = symbol_list
64+
65+
self.symbol_data = {}
66+
self.latest_symbol_data = {}
67+
self.continue_backtest = True
68+
69+
self._open_convert_db_quotes()
70+
self.bar_columns = ('asof', 'ticker', 'exchange', 'open', 'high', 'low', 'close', 'adjclose', 'volume')
71+
72+
def _open_convert_db_quotes(self):
73+
"""
74+
Query the database quotes table and converts
75+
them into pandas DataFrames within a symbol dictionary.
76+
"""
77+
comb_index = None
78+
for s in self.symbol_list:
79+
sql = "select ticker, exchange, asof, open, high, low, close, adjclose, volume from signals.dailyquotes where ticker=%s and exchange in ('XNYS', 'XASE', 'XNAS', 'XOTC') order by asof asc"
80+
args = [s]
81+
self.symbol_data[s] = iosql.read_frame(sql, con=conn, index_col='asof', params=args)
82+
83+
# Combine the index to pad forward values
84+
if comb_index is None:
85+
comb_index = self.symbol_data[s].index
86+
else:
87+
comb_index.union(self.symbol_data[s].index)
88+
89+
# Set the latest symbol_data to None
90+
self.latest_symbol_data[s] = []
91+
92+
# Reindex the dataframes
93+
for s in self.symbol_list:
94+
self.symbol_data[s] = self.symbol_data[s].reindex(index=comb_index, method='pad').iterrows()
95+
96+
def _get_new_bar(self, symbol):
97+
"""
98+
Returns the latest bar from the data feed as a tuple of
99+
(asof, ticker, exchange, open, high, low, close, adjclose, volume)
100+
"""
101+
for b in self.symbol_data[symbol]:
102+
yield tuple([b[0], b[1][0], b[1][1], b[1][2], b[1][3], b[1][4], b[1][5], b[1][6], b[1][7]])
103+
104+
def get_latest_bars(self, symbol, N=1):
105+
"""
106+
Returns the last N bars from the latest_symbol list,
107+
as a tuple of (asof, ticker, exchange, open, high, low, close, adjclose, volume)
108+
or N-k if less available.
109+
"""
110+
try:
111+
bars_list = self.latest_symbol_data[symbol]
112+
except KeyError:
113+
print "That symbol is not available in the historical data set."
114+
else:
115+
return bars_list[-N:]
116+
117+
def update_bars(self):
118+
"""
119+
Pushes the latest bar to the latest_symbol_data structure
120+
for all symbols in the symbol list.
121+
"""
122+
for s in self.symbol_list:
123+
try:
124+
bar = self._get_new_bar(s).next()
125+
except StopIteration:
126+
self.continue_backtest = False
127+
else:
128+
if bar is not None:
129+
self.latest_symbol_data[s].append(bar)
130+
self.events.put(MarketEvent())
131+
132+
class HistoricCSVDataHandler(DataHandler):
133+
"""
134+
HistoricCSVDataHandler is designed to read CSV files for
135+
each requested symbol from disk and provide an interface
136+
to obtain the "latest" bar in a manner identical to a live
137+
trading interface.
138+
"""
139+
140+
def __init__(self, events, csv_dir, symbol_list):
141+
"""
142+
Initialises the historic data handler by requesting
143+
the location of the CSV files and a list of symbols.
144+
145+
It will be assumed that all files are of the form
146+
'symbol.csv', where symbol is a string in the list.
147+
148+
Parameters:
149+
events - The Event Queue.
150+
csv_dir - Absolute directory path to the CSV files.
151+
symbol_list - A list of symbol strings.
152+
"""
153+
self.events = events
154+
self.csv_dir = csv_dir
155+
self.symbol_list = symbol_list
156+
157+
self.symbol_data = {}
158+
self.latest_symbol_data = {}
159+
self.continue_backtest = True
160+
161+
self._open_convert_csv_files()
162+
163+
def _open_convert_csv_files(self):
164+
"""
165+
Opens the CSV files from the data directory, converting
166+
them into pandas DataFrames within a symbol dictionary.
167+
168+
For this handler it will be assumed that the data is
169+
taken from DTN IQFeed. Thus its format will be respected.
170+
"""
171+
comb_index = None
172+
for s in self.symbol_list:
173+
# Load the CSV file with no header information, indexed on date
174+
self.symbol_data[s] = pd.io.parsers.read_csv(
175+
os.path.join(self.csv_dir, '%s.csv' % s),
176+
header=0, index_col=0,
177+
names=['datetime','open','low','high','close','volume','oi']
178+
)
179+
180+
# Combine the index to pad forward values
181+
if comb_index is None:
182+
comb_index = self.symbol_data[s].index
183+
else:
184+
comb_index.union(self.symbol_data[s].index)
185+
186+
# Set the latest symbol_data to None
187+
self.latest_symbol_data[s] = []
188+
189+
# Reindex the dataframes
190+
for s in self.symbol_list:
191+
self.symbol_data[s] = self.symbol_data[s].reindex(index=comb_index, method='pad').iterrows()
192+
193+
def _get_new_bar(self, symbol):
194+
"""
195+
Returns the latest bar from the data feed as a tuple of
196+
(datetime, symbol, open, low, high, close, volume).
197+
"""
198+
for b in self.symbol_data[symbol]:
199+
yield tuple([datetime.datetime.strptime(b[0], '%Y-%m-%d %H:%M:%S'),
200+
symbol, b[1][0], b[1][1], b[1][2], b[1][3], b[1][4]])
201+
202+
def get_latest_bars(self, symbol, N=1):
203+
"""
204+
Returns the last N bars from the latest_symbol list,
205+
or N-k if less available.
206+
"""
207+
try:
208+
bars_list = self.latest_symbol_data[symbol]
209+
except KeyError:
210+
print "That symbol is not available in the historical data set."
211+
else:
212+
return bars_list[-N:]
213+
214+
def update_bars(self):
215+
"""
216+
Pushes the latest bar to the latest_symbol_data structure
217+
for all symbols in the symbol list.
218+
"""
219+
for s in self.symbol_list:
220+
try:
221+
bar = self._get_new_bar(s).next()
222+
except StopIteration:
223+
self.continue_backtest = False
224+
else:
225+
if bar is not None:
226+
self.latest_symbol_data[s].append(bar)
227+
self.events.put(MarketEvent())

0 commit comments

Comments
 (0)