Skip to content

Commit 311d2ea

Browse files
tmkontraflipdazed
andauthored
feat(feed): arbitrary parsed objects (pandas, dicts) feed support (#4)
* added generic input support for Pandas DataFrames and List Structures * feat(barfeed): separate customfeed into listfeed and pandasfeed - pandas feed no longer circuitously convert to string -> csv parser * feat(pandas): update to quantworks, add test cases * fix(travis): cache installed ta-lib * fix(travis): set -e * fix(travis): checking if ta-lib installed * fix(travis): caching ta-lib install (/usr/lib) * fix(travis): skip coveralls Co-authored-by: Alexander McFarlane <[email protected]>
1 parent 64aa35c commit 311d2ea

File tree

8 files changed

+588
-10
lines changed

8 files changed

+588
-10
lines changed

.travis.yml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
language: python
22
python:
33
- '3.7'
4-
4+
cache:
5+
- /usr/lib
6+
57
env:
68
global:
79
- secure: "SJPwuOKJDiGgOB+VyhJ/i/3Hb8z0leDo7hdgTp3X9+9dAdPQlwXSnCKoOEjLqpCz9gkr0pw6PCEnyDMcZo9ig2IBKt2y1r2M1Oc6c1d201EI+Fe6RGsRyG1StqU9iNSPZ9UEK+ld4Oa1FspNlFy1msd3N6sgpRRAa7HTENsCMKTl7Sq+wPwGtmm6qACCZN2Eqtth1ZyvwW5+64xRJPBNLcxNMnU2IJCPdn4j7MFsVpTufWAcV3kghvue0y73pKQRKnLrNGANt33uocU/PaKLYpGa/D0loEbRglgmG1ufnPCs4XyvRBgVN2iK2hLVHeXQvOXkfaH4joQzf+6PG/2UzP9EG/hRSSsI+Ul11mxxXrljwNm3a6N3E/JR7xhXWn1bGckLyoMm9tButedjs9zKhCRQWgh6f63l0JugFG86FLdAL1OpNjP77kpT+xwlrYaXvteJvr+TYu2H6QvsUJnT0QuRu9ydQD5UbaelVVZk4M8XK0cmD33HKEpBXNBAQjrvVcQu5VGJeyWgMYMmrDwjP4UsframpU8kAQa+cFu/ZaK624onROoxzD8/Am5sLdesJM6LWj1iSJSfHlGJT/KcfW+wM6+3JPuyR0X9Foyv1EkxzzqMMyOUZ04/hN8X681qBhEk9rnpPNphNnYOrmzS1JNXTNXt2/Boea5B58JNVDU="
@@ -10,10 +12,13 @@ sudo: required
1012

1113
before_install:
1214
- sudo apt-get update
13-
- sudo apt-get install build-essential wget
14-
- wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
15-
- tar -xvzf ta-lib-0.4.0-src.tar.gz
16-
- cd ta-lib/ && ./configure --prefix=/usr LDFLAGS="-lm" && make && sudo make install && cd .. && rm -r ta-lib
15+
- set -e
16+
- if [ ! -f /usr/lib/libta_lib.so ]; then
17+
sudo apt-get install build-essential wget;
18+
wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz;
19+
tar -xvzf ta-lib-0.4.0-src.tar.gz;
20+
cd ta-lib/ && ./configure --prefix=/usr LDFLAGS="-lm" && make && sudo make install && cd .. && rm -r ta-lib;
21+
fi
1722
- python -m pip install poetry
1823
- poetry install
1924

@@ -22,5 +27,5 @@ script:
2227
- export PYTHONPATH=.
2328
- poetry run tox -v
2429

25-
after_success:
26-
- poetry run coveralls
30+
# after_success:
31+
# - poetry run coveralls

poetry.lock

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ coveralls = "^1.9"
2626
pytest = "^5.3"
2727
tox = "^3.14"
2828
pytest-cov = "^2.8"
29+
pandas = "^0.25.3"
2930

3031
[tool.poetry.extras]
3132
TALib = ["cython", "TA-Lib"]

quantworks/barfeed/csvfeed.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ def __init__(self, columnNames, dateTimeFormat, dailyBarTime, frequency, timezon
158158
self.__adjCloseColName = columnNames["adj_close"]
159159
self.__columnNames = columnNames
160160

161-
def _parseDate(self, dateString):
162-
ret = datetime.datetime.strptime(dateString, self.__dateTimeFormat)
161+
def _parseDate(self, dateTime):
162+
ret = datetime.datetime.strptime(dateTime, self.__dateTimeFormat)
163163

164164
if self.__dailyBarTime is not None:
165165
ret = datetime.datetime.combine(ret, self.__dailyBarTime)

quantworks/barfeed/listfeed.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# QuantWorks
2+
#
3+
# Copyright 2019 Tyler M Kontra
4+
# Copyright 2011-2015 Gabriel Martin Becedillas Ruiz
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
"""
19+
.. moduleauthor:: Alex McFarlane <[email protected]>, Tyler Kontra <[email protected]>
20+
"""
21+
22+
from quantworks.utils import dt
23+
from quantworks.barfeed import membf
24+
from quantworks.barfeed import csvfeed
25+
from quantworks import bar
26+
27+
import datetime
28+
29+
30+
class BarFeed(membf.BarFeed):
31+
"""Base class for Iterable[Dict] based :class:`quantworks.barfeed.BarFeed`.
32+
33+
.. note::
34+
This is a base class and should not be used directly.
35+
"""
36+
37+
def __init__(self, frequency, maxLen=None):
38+
super(BarFeed, self).__init__(frequency, maxLen)
39+
40+
self.__barFilter = None
41+
self.__dailyTime = datetime.time(0, 0, 0)
42+
43+
def getDailyBarTime(self):
44+
return self.__dailyTime
45+
46+
def setDailyBarTime(self, time):
47+
self.__dailyTime = time
48+
49+
def getBarFilter(self):
50+
return self.__barFilter
51+
52+
def setBarFilter(self, barFilter):
53+
self.__barFilter = barFilter
54+
55+
def _addBarsFromListofDicts(self, instrument, loadedBars):
56+
loadedBars = filter(
57+
lambda bar_: (bar_ is not None) and
58+
(self.__barFilter is None or self.__barFilter.includeBar(bar_)),
59+
loadedBars
60+
)
61+
self.addBarsFromSequence(instrument, loadedBars)
62+
63+
64+
class Feed(BarFeed):
65+
"""A BarFeed that loads bars from a custom feed that has the following columns:
66+
::
67+
68+
Date Time Open Close High Low Volume Adj Close
69+
2015-08-14 09:06:00 0.00690 0.00690 0.00690 0.00690 1.346117 9567
70+
71+
72+
:param frequency: The frequency of the bars. Check :class:`quantworks.bar.Frequency`.
73+
:param timezone: The default timezone to use to localize bars. Check :mod:`quantworks.marketsession`.
74+
:type timezone: A pytz timezone.
75+
:param maxLen: The maximum number of values that the :class:`quantworks.dataseries.bards.BarDataSeries` will hold.
76+
Once a bounded length is full, when new items are added, a corresponding number of items are discarded from the
77+
opposite end. If None then dataseries.DEFAULT_MAX_LEN is used.
78+
:type maxLen: int.
79+
80+
.. note::
81+
* The data should be sampled across regular time points, you can
82+
regularlise (e.g. for 5min intervals) as::
83+
84+
df = df.set_index('Date Time').resample('s').interpolate().resample('5T').asfreq()
85+
df = df.dropna().reset_index()
86+
which is described in a SO [post](https://stackoverflow.com/a/39730730/4013571)
87+
* It is ok if the **Adj Close** column is empty.
88+
* When working with multiple instruments:
89+
90+
* If all the instruments loaded are in the same timezone, then the timezone parameter may not be specified.
91+
* If any of the instruments loaded are in different timezones, then the timezone parameter should be set.
92+
"""
93+
94+
def __init__(self, frequency, timezone=None, maxLen=None):
95+
super(Feed, self).__init__(frequency, maxLen)
96+
97+
self.__timezone = timezone
98+
# Assume bars don't have adjusted close. This will be set to True after
99+
# loading the first file if the adj_close column is there.
100+
self.__haveAdjClose = False
101+
102+
self.__barClass = bar.BasicBar
103+
104+
self.__dateTimeFormat = "%Y-%m-%d %H:%M:%S"
105+
self.__columnNames = {
106+
"datetime": "Date Time",
107+
"open": "Open",
108+
"high": "High",
109+
"low": "Low",
110+
"close": "Close",
111+
"volume": "Volume",
112+
"adj_close": "Adj Close",
113+
}
114+
# self.__dateTimeFormat expects time to be set so there is no need to
115+
# fix time.
116+
self.setDailyBarTime(None)
117+
118+
def barsHaveAdjClose(self):
119+
return self.__haveAdjClose
120+
121+
def setNoAdjClose(self):
122+
self.__columnNames["adj_close"] = None
123+
self.__haveAdjClose = False
124+
125+
def setColumnName(self, col, name):
126+
self.__columnNames[col] = name
127+
128+
def setDateTimeFormat(self, dateTimeFormat):
129+
self.__dateTimeFormat = dateTimeFormat
130+
131+
def setBarClass(self, barClass):
132+
self.__barClass = barClass
133+
134+
def __localize_dt(self, dateTime):
135+
# Localize the datetime if a timezone was given.
136+
if self.__timezone:
137+
return dt.localize(dateTime, self.__timezone)
138+
else:
139+
return dateTime
140+
141+
def addBarsFromListofDicts(self, instrument, list_of_dicts, timezone=None):
142+
"""Loads bars for a given instrument from a list of dictionaries.
143+
The instrument gets registered in the bar feed.
144+
145+
:param instrument: Instrument identifier.
146+
:type instrument: string.
147+
:param list_of_dicts: A list of dicts. First item should contain
148+
columns.
149+
:type list_of_dicts: list
150+
:param timezone: The timezone to use to localize bars. Check :mod:`quantworks.marketsession`.
151+
:type timezone: A pytz timezone.
152+
"""
153+
154+
if timezone is None:
155+
timezone = self.__timezone
156+
157+
if not isinstance(list_of_dicts, (list, tuple)):
158+
raise ValueError('This function only supports types: {list, tuple}')
159+
if not isinstance(list_of_dicts[0], dict):
160+
raise ValueError('List should only contain dicts')
161+
162+
dicts_have_adj_close = self.__columnNames['adj_close'] in list_of_dicts[0].keys()
163+
164+
# Convert dicts to Bar(s)
165+
loadedBars = map(
166+
lambda row: bar.BasicBar(
167+
self.__localize_dt(row[self.__columnNames['datetime']]),
168+
row[self.__columnNames['open']],
169+
row[self.__columnNames['high']],
170+
row[self.__columnNames['low']],
171+
row[self.__columnNames['close']],
172+
row[self.__columnNames['volume']],
173+
row[self.__columnNames['adj_close']],
174+
self.getFrequency(),
175+
extra = {key: row[key] for key in set(row.keys()).difference(self.__columnNames.values())}
176+
),
177+
list_of_dicts
178+
)
179+
180+
missing_columns = [
181+
col for col in self.__columnNames.values()
182+
if col not in list_of_dicts[0].keys()
183+
]
184+
if missing_columns:
185+
raise ValueError('Missing required columns: {}'.format(repr(missing_columns)))
186+
187+
super(Feed, self)._addBarsFromListofDicts(
188+
instrument, loadedBars)
189+
190+
if dicts_have_adj_close:
191+
self.__haveAdjClose = True
192+
elif self.__haveAdjClose:
193+
raise Exception("Previous bars had adjusted close and these ones don't have.")

0 commit comments

Comments
 (0)