/
base_utils.py
318 lines (253 loc) · 7.91 KB
/
base_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
import numpy as np
import validations
import math_func_utils as utils
import Error_collector
from datetime import datetime as dt
import pickle
ALLOWED_RAWDATA_MARKET_VEHICLES = {
'GENERAL': ['GENERAL'],
'FOREX': ['EURUSD', 'USDJPY']
}
ERRORKEY_RD = 'RAWDATA'
ERRORMSG_RD_INITFAIL = 'Failed Initialization. Minimum requirement of valid data value.'
ERRORMSG_RD_BADMARKET = 'Unexpected Market.'
ERRORMSG_RD_BADVEHICLE = 'Unexpected Vehicle.'
ERRORMSG_RD_INVDATE = "Invalid Date."
ERRORMSG_RD_INVTIME = "Invalid Time."
class RawData(object):
"""Data structure for incoming raw data."""
NUMERIC_ONLY = True
DEFAULT_MARKET = 'GENERAL'
DEFAULT_VEHICLE = 'GENERAL'
def __init__(self, value=None, market=None, vehicle=None, date_stamp=None, time_stamp=None):
self.valid_data = False
self.value = value
self.market = market
self.vehicle = vehicle
self.date_stamp = date_stamp
self.time_stamp = time_stamp
self.initError()
self.insertDefaults()
self.valid_data = self.dataCheck()
def to_dict(self):
return{
'value': self.value,
'market': self.market,
'vehicle': self.vehicle,
'date_stamp': self.date_stamp,
'time_stamp': self.time_stamp
}
def insertDefaults(self):
if self.market is None:
self.market = self.DEFAULT_MARKET
if self.vehicle is None and self.market == self.DEFAULT_MARKET:
self.vehicle = self.DEFAULT_VEHICLE
if self.date_stamp is None:
self.date_stamp = dt.now().date()
if self.time_stamp is None:
self.time_stamp = dt.now().time()
def valueCheck(self):
return not((self.NUMERIC_ONLY and not validations.isNumeric(self.value)) or self.value is None)
def initError(self):
self._Error = Error_collector.Errors()
def dataCheck(self):
all_pass = True
self.initError()
if not self.valueCheck():
self._Error.Add(ERRORKEY_RD, ERRORMSG_RD_INITFAIL)
all_pass = False
valid_market, valid_vehicle = False, False
for market, vehicle_list in ALLOWED_RAWDATA_MARKET_VEHICLES.iteritems():
if self.market == market:
valid_market = True
for vehicle in vehicle_list:
if self.vehicle == vehicle:
valid_vehicle = True
if not valid_market:
self._Error.Add(ERRORKEY_RD, ERRORMSG_RD_BADMARKET)
all_pass = False
if not valid_vehicle:
self._Error.Add(ERRORKEY_RD, ERRORMSG_RD_BADVEHICLE)
all_pass = False
if not issubclass(type(self.date_stamp), type(dt.now().date())):
self._Error.Add(ERRORKEY_RD, ERRORMSG_RD_INVDATE)
all_pass = False
if not issubclass(type(self.time_stamp), type(dt.now().time())):
self._Error.Add(ERRORKEY_RD, ERRORMSG_RD_INVTIME)
all_pass = False
return all_pass
def __repr__(self):
vals = {
'value': self.value,
'market': self.market,
'vehicle': self.vehicle,
'date_stamp': self.date_stamp,
'time_stamp': self.time_stamp
}
return '<RawData {0}>'.format(vals)
@property
def isValid(self):
self.valid_data = self.dataCheck()
return self.valid_data
class RollingList(object):
"""A List that has a maximum number of indexes.
Newest additions are added to the end of the list until list_limit value
is reached. At that point, the zero-index is removed as new values continue
to be added to the end.
"""
_DEFAULT_MAX_LIST_LENGTH = 50
def __init__(self, input_data=None, err=(None, None)):
self.error = err[0]
self.base_error_key = err[1]
self._rollinglist = []
self._listlimit = self._DEFAULT_MAX_LIST_LENGTH
self._only_numerical = True
self.periods = 0
self._high = None
self._low = None
self._range = None
# Accept Default values and add initial value/value-set
if input_data:
self.Add(input_data)
def __len__(self):
return len(self._rollinglist)
@property
def get(self):
if len(self):
return self._rollinglist[-1]
else:
return None
@property
def limit(self):
return self._listlimit
@property
def list(self):
return self._rollinglist
@property
def high(self):
if self._only_numerical:
return self._high
else:
return None
@property
def low(self):
if self._only_numerical:
return self._low
else:
return None
@property
def range(self):
if self._only_numerical:
return self._range
else:
return None
@property
def high_curr(self):
if len(self) and self._only_numerical:
return np.max(self._rollinglist)
else:
return None
@property
def low_curr(self):
if len(self) and self._only_numerical:
return np.min(self._rollinglist)
else:
return None
@property
def range_curr(self):
if len(self) and self._only_numerical:
return self.high_curr - self.low_curr
def _Add(self, val):
self._rollinglist.append(val)
self.periods += 1
if self._only_numerical and validations.isNumeric(val):
self._high = val if val > self._high or self._high is None else self._high
self._low = val if val < self._low or self._low is None else self._low
self._range = self.high - self.low if self.high and self.low else None
if len(self) > self._listlimit:
self._Delete(0)
def _Delete(self, idx):
if validations.isNumeric(idx):
if idx < len(self):
self._rollinglist.pop(idx)
def AllowAllValues(self):
self._only_numerical = False
self._high = None
self._low = None
self._range = None
def AllowOnlyNum(self, cleanse = True):
self._only_numerical = True
if validations.isNumeric(self._rollinglist):
if cleanse:
for i, val in enumerate(self._rollinglist):
if not validations.isNumeric(val):
self._rollinglist.pop(i)
self._high = np.max(self._rollinglist)
self._low = np.max(self._rollinglist)
self._range = self.high - self.low
else:
self._high = None
self._low = None
self._range = None
def SetLimit(self, val):
if validations.isNumeric(val):
self._listlimit = val
def Add(self, val):
if self._only_numerical and validations.isNumeric(val):
if validations.isList(val):
for x in val:
self._Add(x)
else:
self._Add(val)
elif not self._only_numerical:
self._Add(val)
def Reset(self):
self._rollinglist = []
def ResetLV(self):
self._high = None
self._low = None
self._range = None
def ResetAll(self):
self.Reset()
self.ResetLV()
class MovingAverage(RollingList):
"""Specialized RollingList object used for Moving Averages."""
_DEFAULT_SCOPES = {
'9-period MA':9,
'15-period MA':15,
'26-period MA': 26
}
def __init__(self, error_key=None):
super(MovingAverage, self).__init__()
self._scopes = self._DEFAULT_SCOPES
self._only_numerical = False
self.base_error_key = error_key
@property
def maxscope(self):
return np.max(self._scopes.values())
def AddScope(self, label=None, value=None):
if label and validations.isNumeric(value):
self._scopes[label] = value
else:
pass # Error exception handling here
def DelScope(self, val):
if validations.isNumeric(val):
for k, v in self._scopes.iteritems():
if val == v:
self._scopes.pop(k, None)
elif isinstance(val, str):
self._scopes.pop(val, None)
def AddData(self, RL_obj):
"""Receives RollingList object to calculate trailing MA values as per
scope definitions.
"""
if issubclass(type(RL_obj), RollingList):
data_set = RL_obj.list
new_data = []
for label, scope_factor in self._scopes.iteritems():
if len(data_set) >= scope_factor and validations.isNumeric(data_set):
avg = utils.numberFormat(np.mean(data_set[-1 * scope_factor:]), 4)
else:
avg = 'NA'
new_data.append((label, scope_factor, avg))
self.Add(tuple(new_data))