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