rlquantdata 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rlquantdata might be problematic. Click here for more details.
- rlquantdata-0.1.0.dist-info/METADATA +24 -0
- rlquantdata-0.1.0.dist-info/RECORD +22 -0
- rlquantdata-0.1.0.dist-info/WHEEL +5 -0
- rlquantdata-0.1.0.dist-info/licenses/LICENSE +21 -0
- rlquantdata-0.1.0.dist-info/top_level.txt +1 -0
- src/__init__.py +0 -0
- src/rlquantdata/DateUtilities/Asserts.py +26 -0
- src/rlquantdata/DateUtilities/Calendar.py +282 -0
- src/rlquantdata/DateUtilities/Date.py +427 -0
- src/rlquantdata/DateUtilities/DateUtilities.py +86 -0
- src/rlquantdata/DateUtilities/Date_enum.py +47 -0
- src/rlquantdata/DateUtilities/DayCounter.py +141 -0
- src/rlquantdata/DateUtilities/Period.py +319 -0
- src/rlquantdata/DateUtilities/Schedule.py +309 -0
- src/rlquantdata/DateUtilities/Settings.py +39 -0
- src/rlquantdata/DateUtilities/__init__.py +24 -0
- src/rlquantdata/__init__.py +0 -0
- src/rlquantdata/client.py +665 -0
- src/rlquantdata/utilities/__init__.py +0 -0
- src/rlquantdata/utilities/trade_date_utils.py +65 -0
- src/rlquantdata/utilities/utils.py +197 -0
- src/rlquantdata/version.py +1 -0
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import datetime as dt
|
|
3
|
+
import gc
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from io import BytesIO
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pandas as pd
|
|
14
|
+
import requests
|
|
15
|
+
from pandas import Index
|
|
16
|
+
|
|
17
|
+
from src.rlquantdata.utilities import utils
|
|
18
|
+
|
|
19
|
+
exec('from numpy import nan')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DayBarStore():
|
|
23
|
+
"""规定一些标准数据加载模块,目前数据格式同因子平台数据,columns: code, index: time"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, f, include930, key, start_time='2015-01-01', end_time=None):
|
|
26
|
+
'''freq: 文件子目录'''
|
|
27
|
+
t1 = time.time()
|
|
28
|
+
freq, field = key.split('/')
|
|
29
|
+
if start_time is None:
|
|
30
|
+
start_time = '2007-01-01'
|
|
31
|
+
if freq == '01min':
|
|
32
|
+
start_time = '2009-01-01'
|
|
33
|
+
self.start_time = start_time
|
|
34
|
+
self.end_time = end_time
|
|
35
|
+
self._data = utils.get_feather(f, include930, key, start_time, end_time, stocks=None, freq='price' + freq)
|
|
36
|
+
self._data = self._data.sort_index(axis=1)
|
|
37
|
+
if end_time is None:
|
|
38
|
+
self.end_time = self._data.index[-1].strftime('%Y-%m-%d')
|
|
39
|
+
print('load {}/price{} from {} to {}, consume {:.4}s'.format(f, key,
|
|
40
|
+
start_time, end_time, time.time() - t1))
|
|
41
|
+
|
|
42
|
+
def get_datas(self, trading_dates, start_time, end_time, bar_counts, codes):
|
|
43
|
+
# 合并 trading_dates 和 self._data.index,防止类似于指数000001.fea数据,会提前录入
|
|
44
|
+
trading_dates = pd.to_datetime(trading_dates).union(self._data.index).sort_values()
|
|
45
|
+
days = utils.trade_time(trading_dates, start_time=start_time, end_time=end_time, bar_counts=bar_counts)
|
|
46
|
+
if len(days) == 0:
|
|
47
|
+
raise ValueError('days did not provide')
|
|
48
|
+
if isinstance(days, str):
|
|
49
|
+
days = pd.to_datetime(days)
|
|
50
|
+
elif isinstance(days, int):
|
|
51
|
+
days = pd.to_datetime(str(days))
|
|
52
|
+
elif isinstance(days, list):
|
|
53
|
+
days = [pd.to_datetime((day)) for day in days]
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError('days type:{} not match.'.format(type(days)))
|
|
56
|
+
|
|
57
|
+
if isinstance(codes, (list, np.ndarray)):
|
|
58
|
+
codes = [int(code) for code in codes]
|
|
59
|
+
return self._data.reindex(index=days, columns=codes)
|
|
60
|
+
elif isinstance(codes, Index):
|
|
61
|
+
if codes.dtype != 'int64':
|
|
62
|
+
raise ValueError('not support for codes type:{}'.format(codes.dtype))
|
|
63
|
+
return self._data.loc[start_time:end_time].reindex(columns=codes)
|
|
64
|
+
elif isinstance(codes, str):
|
|
65
|
+
codes = [int(codes)]
|
|
66
|
+
return self._data.reindex(index=days, columns=codes)
|
|
67
|
+
elif codes is None:
|
|
68
|
+
return self._data.reindex(index=days)
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError('not support for codes:{}'.format(codes))
|
|
71
|
+
|
|
72
|
+
def get_range_datas(self, start_time, end_time, codes):
|
|
73
|
+
if isinstance(codes, (list, np.ndarray)):
|
|
74
|
+
codes = [int(code) for code in codes]
|
|
75
|
+
return self._data.loc[start_time:end_time].reindex(columns=codes)
|
|
76
|
+
elif isinstance(codes, Index):
|
|
77
|
+
if codes.dtype != 'int64':
|
|
78
|
+
raise ValueError('not support for codes type:{}'.format(codes.dtype))
|
|
79
|
+
return self._data.loc[start_time:end_time].reindex(columns=codes)
|
|
80
|
+
elif isinstance(codes, str):
|
|
81
|
+
codes = [int(codes)]
|
|
82
|
+
return self._data.loc[start_time:end_time].reindex(columns=codes)
|
|
83
|
+
elif codes is None:
|
|
84
|
+
return self._data.loc[start_time:end_time, :]
|
|
85
|
+
else:
|
|
86
|
+
raise ValueError('not support for codes:{}'.format(codes))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_last_not_nan(df: pd.DataFrame):
|
|
90
|
+
sub_v = df.values
|
|
91
|
+
ix = len(df) - 1 - (~pd.isna(sub_v))[::-1].argmax(0)
|
|
92
|
+
return pd.Series(data=sub_v[ix, range(df.shape[1])], index=df.columns, dtype=object)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class BaseClient(object):
|
|
96
|
+
"""加载数据key格式统一:fre/field"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, path,
|
|
99
|
+
include930=True,
|
|
100
|
+
day_with_1500=False,
|
|
101
|
+
username=None,
|
|
102
|
+
password=None,
|
|
103
|
+
remote_servers='factorlib.irongliang.com',
|
|
104
|
+
https=True,
|
|
105
|
+
enable_remote=False,
|
|
106
|
+
format_column=None
|
|
107
|
+
):
|
|
108
|
+
if isinstance(path, str) or path is None:
|
|
109
|
+
self.path = {'public': path}
|
|
110
|
+
elif isinstance(path, dict):
|
|
111
|
+
self.path = path
|
|
112
|
+
else:
|
|
113
|
+
raise ValueError('not support for path:{}'.format(path))
|
|
114
|
+
self.include930 = include930
|
|
115
|
+
self.day_with_1500 = day_with_1500
|
|
116
|
+
self.format_column = format_column
|
|
117
|
+
self._datas = {}
|
|
118
|
+
for key, path in self.path.items():
|
|
119
|
+
if path != '' and path is not None:
|
|
120
|
+
if not os.path.exists(path):
|
|
121
|
+
raise RuntimeError('data path {} not exist'.format(os.path.abspath(path)))
|
|
122
|
+
self.load_time = 0
|
|
123
|
+
self.load_count = 0
|
|
124
|
+
self.get_datas_time = 0
|
|
125
|
+
self.get_datas_count = 0
|
|
126
|
+
|
|
127
|
+
now_time = datetime.datetime.now()
|
|
128
|
+
if now_time.hour < 22:
|
|
129
|
+
self.validate_time = datetime.datetime.combine(now_time - datetime.timedelta(days=1),
|
|
130
|
+
datetime.time(10, 0, 0))
|
|
131
|
+
else:
|
|
132
|
+
self.validate_time = datetime.datetime.combine(now_time, datetime.time(10, 0, 0))
|
|
133
|
+
# load bar
|
|
134
|
+
|
|
135
|
+
self.enable_remote = enable_remote
|
|
136
|
+
if enable_remote:
|
|
137
|
+
none_num = [username, password, remote_servers].count(None)
|
|
138
|
+
if 1 < none_num < 3:
|
|
139
|
+
raise Exception('开启远程请求,必须提供 remove_servers、username、password 参数')
|
|
140
|
+
self.username = username
|
|
141
|
+
self.password = hashlib.md5(password.encode('utf-8')).hexdigest()
|
|
142
|
+
self.__server_list = []
|
|
143
|
+
self.__lock = threading.Lock()
|
|
144
|
+
self.__valid_url_index = 0
|
|
145
|
+
self.__token = None
|
|
146
|
+
protocol = 'https' if https else 'http'
|
|
147
|
+
for remote_server in remote_servers.split(','):
|
|
148
|
+
split = remote_server.split(':')
|
|
149
|
+
if len(split) != 2 and len(split) != 1:
|
|
150
|
+
raise Exception(f'错误的服务列表 {remote_server}')
|
|
151
|
+
ip = split[0]
|
|
152
|
+
if len(split) == 2:
|
|
153
|
+
port = split[1]
|
|
154
|
+
else:
|
|
155
|
+
port = '443' if https else '80'
|
|
156
|
+
if not port.isdigit():
|
|
157
|
+
raise Exception(f'错误的端口 {port}')
|
|
158
|
+
self.__server_list.append(f'{protocol}://{ip}:{port}')
|
|
159
|
+
|
|
160
|
+
close_data = self.get_data('2007-01-01', dt.datetime.today().strftime('%Y-%m-%d'), None, '01d/close', None, 1,
|
|
161
|
+
format_column=False)
|
|
162
|
+
# 按close时间戳作为交易日列表
|
|
163
|
+
self.trading_dates = pd.to_datetime(close_data.index, unit='ns').values
|
|
164
|
+
|
|
165
|
+
self.full_ticks = {}
|
|
166
|
+
# # 初始化daily,分钟的后续取数时再添加
|
|
167
|
+
freq = '01d'
|
|
168
|
+
self.full_ticks[freq] = utils.get_full_trade_tick(self.trading_dates, freq, include930=include930)
|
|
169
|
+
|
|
170
|
+
if format_column is not None:
|
|
171
|
+
code_columns = self.get_data('2007-01-01', dt.datetime.today().strftime('%Y-%m-%d'), None,
|
|
172
|
+
f'01d/{format_column}', None, 1, format_column=False)
|
|
173
|
+
self.codes = code_columns.columns.astype(int).tolist()
|
|
174
|
+
else:
|
|
175
|
+
self.codes = close_data.columns.astype(int).tolist()
|
|
176
|
+
|
|
177
|
+
def set_include930(self, include930):
|
|
178
|
+
self.include930 = include930
|
|
179
|
+
|
|
180
|
+
def check_validata_time(self):
|
|
181
|
+
now_time = datetime.datetime.now()
|
|
182
|
+
if (now_time - self.validate_time).total_seconds() > 24 * 3600:
|
|
183
|
+
self._datas.clear()
|
|
184
|
+
self.validate_time = datetime.datetime.combine(now_time, datetime.time(10, 0, 0))
|
|
185
|
+
|
|
186
|
+
def preloading(self, start_time, end_time, subscribe_data):
|
|
187
|
+
for freq, fields in subscribe_data.items():
|
|
188
|
+
t1 = time.time()
|
|
189
|
+
for field_in in fields:
|
|
190
|
+
splite_field = field_in.split('.')
|
|
191
|
+
if len(splite_field) == 2:
|
|
192
|
+
custom_key = splite_field[0]
|
|
193
|
+
field = splite_field[1]
|
|
194
|
+
else:
|
|
195
|
+
custom_key = 'public'
|
|
196
|
+
field = field_in
|
|
197
|
+
data_key = '{}/{}'.format(freq, field)
|
|
198
|
+
bar_key = '{}/{}'.format(freq, field_in)
|
|
199
|
+
self._datas[bar_key] = DayBarStore(self.path[custom_key], self.include930, data_key, start_time,
|
|
200
|
+
end_time)
|
|
201
|
+
self.load_time += time.time() - t1
|
|
202
|
+
self.load_count += 1
|
|
203
|
+
|
|
204
|
+
def check_time_limit(self, start_time, end_time, bar_store):
|
|
205
|
+
if start_time is not None:
|
|
206
|
+
if pd.to_datetime(start_time) < pd.to_datetime(bar_store.start_time):
|
|
207
|
+
return True
|
|
208
|
+
if end_time is not None:
|
|
209
|
+
if pd.to_datetime(end_time) > pd.to_datetime(bar_store.end_time):
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
# if end_time is None:
|
|
213
|
+
# end_time = dt.datetime.today().strftime('%Y-%m-%d 15:00:00')
|
|
214
|
+
# else:
|
|
215
|
+
# end_time = pd.to_datetime(end_time).strftime('%Y-%m-%d 15:00:00')
|
|
216
|
+
# if start_time is None:
|
|
217
|
+
# start_time = '2007-01-01'
|
|
218
|
+
# if pd.to_datetime(start_time) < pd.to_datetime(bar_store.start_time):
|
|
219
|
+
# return True
|
|
220
|
+
# if pd.to_datetime(end_time) > pd.to_datetime(bar_store.end_time):
|
|
221
|
+
# return True
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
def get_fin_data(self, start_time, end_time, field, quarter_shift=0, quarter=None, interval=1, codes=None,
|
|
225
|
+
publish_merge=None, quarter_merge=None, format_column=True, universe=None):
|
|
226
|
+
universe = self.__apply_universe(start_time, end_time, codes, universe)
|
|
227
|
+
fields = field
|
|
228
|
+
file_list = []
|
|
229
|
+
if not isinstance(field, list):
|
|
230
|
+
fields = [field]
|
|
231
|
+
for field in fields:
|
|
232
|
+
file_exist = False
|
|
233
|
+
for _, msg_path in self.path.items():
|
|
234
|
+
filepath = "{}/{}/{}.fea".format(msg_path, 'finance', field)
|
|
235
|
+
if not os.path.exists(filepath):
|
|
236
|
+
try:
|
|
237
|
+
filepath = utils.search_files(msg_path, 'finance', '{}.fea'.format(field))
|
|
238
|
+
file_list.append((field, filepath))
|
|
239
|
+
file_exist = True
|
|
240
|
+
break
|
|
241
|
+
except FileNotFoundError:
|
|
242
|
+
pass
|
|
243
|
+
else:
|
|
244
|
+
file_list.append((field, filepath))
|
|
245
|
+
file_exist = True
|
|
246
|
+
break
|
|
247
|
+
if not file_exist:
|
|
248
|
+
if self.enable_remote:
|
|
249
|
+
for field in fields:
|
|
250
|
+
df = self.__get_remote_fin_data(start_time, end_time, field, quarter_shift, quarter,
|
|
251
|
+
interval)
|
|
252
|
+
file_list.append((df, None))
|
|
253
|
+
else:
|
|
254
|
+
raise FileNotFoundError('{}.fea'.format(field))
|
|
255
|
+
|
|
256
|
+
merge_publish_df = None
|
|
257
|
+
merge_quarter_df = None
|
|
258
|
+
if publish_merge is not None:
|
|
259
|
+
if isinstance(publish_merge, pd.DataFrame):
|
|
260
|
+
merge_publish_df = publish_merge
|
|
261
|
+
else:
|
|
262
|
+
merge_publish_df = self.get_data('1993-01-01', datetime.datetime.now().strftime('%Y-%m-%d'), codes=None,
|
|
263
|
+
field=f'01d/{publish_merge}')
|
|
264
|
+
merge_publish_df.index = merge_publish_df.index.strftime('%Y-%m-%d')
|
|
265
|
+
elif quarter_merge is not None:
|
|
266
|
+
if isinstance(quarter_merge, pd.DataFrame):
|
|
267
|
+
merge_quarter_df = quarter_merge
|
|
268
|
+
else:
|
|
269
|
+
merge_quarter_df = self.get_data('1993-01-31', datetime.datetime.now().strftime('%Y-%m-%d'), codes=None,
|
|
270
|
+
field=f'01d/{quarter_merge}')
|
|
271
|
+
merge_quarter_df.index = merge_quarter_df.index.strftime('%Y-%m-%d')
|
|
272
|
+
|
|
273
|
+
result = None
|
|
274
|
+
for file_tuple in file_list:
|
|
275
|
+
if file_tuple[1] is not None:
|
|
276
|
+
single_df = self._get_fin_data(file_tuple[0], file_tuple[1], quarter_shift, start_time, quarter,
|
|
277
|
+
merge_publish_df, merge_quarter_df)
|
|
278
|
+
else:
|
|
279
|
+
single_df = file_tuple[0]
|
|
280
|
+
if result is None:
|
|
281
|
+
result = single_df
|
|
282
|
+
else:
|
|
283
|
+
union_columns = result.columns.union(single_df.columns)
|
|
284
|
+
result = result.reindex(columns=union_columns)
|
|
285
|
+
single_df = single_df.reindex(columns=union_columns)
|
|
286
|
+
result = single_df.fillna(result)
|
|
287
|
+
result = result.ffill()
|
|
288
|
+
result = result.loc[start_time:end_time, :]
|
|
289
|
+
result = result.bfill()
|
|
290
|
+
if interval > 1:
|
|
291
|
+
result = result[::interval]
|
|
292
|
+
|
|
293
|
+
if merge_publish_df is not None:
|
|
294
|
+
merge_publish_df.index = pd.to_datetime(merge_publish_df.index)
|
|
295
|
+
elif merge_quarter_df is not None:
|
|
296
|
+
merge_quarter_df.index = pd.to_datetime(merge_quarter_df.index)
|
|
297
|
+
|
|
298
|
+
if universe is not None:
|
|
299
|
+
result = result.reindex(columns=universe.columns)
|
|
300
|
+
universe = universe.reindex(index=result.index)
|
|
301
|
+
return result * universe
|
|
302
|
+
elif format_column and self.format_column is not None and codes is None:
|
|
303
|
+
# 如果需要按照统一codes进行format,在此执行
|
|
304
|
+
return result.reindex(columns=self.codes)
|
|
305
|
+
|
|
306
|
+
if isinstance(codes, (list, np.ndarray)):
|
|
307
|
+
codes = [int(code) for code in codes]
|
|
308
|
+
elif isinstance(codes, Index):
|
|
309
|
+
if codes.dtype != 'int64':
|
|
310
|
+
raise ValueError('not support for codes type:{}'.format(codes.dtype))
|
|
311
|
+
pass
|
|
312
|
+
elif isinstance(codes, str):
|
|
313
|
+
codes = [int(codes)]
|
|
314
|
+
elif codes is None:
|
|
315
|
+
return result
|
|
316
|
+
else:
|
|
317
|
+
raise ValueError('not support for codes:{}'.format(codes))
|
|
318
|
+
|
|
319
|
+
return result.reindex(columns=codes)
|
|
320
|
+
|
|
321
|
+
def _get_fin_data(self, field, filepath, quarter_shift, start_time, quarter, merge_publish_df, merge_quarter_df):
|
|
322
|
+
if field in self._datas.keys():
|
|
323
|
+
df = self._datas[field]
|
|
324
|
+
else:
|
|
325
|
+
df: pd.DataFrame = pd.read_feather(filepath, use_threads=False).set_index('trade_date')
|
|
326
|
+
df.columns = df.columns.astype(int)
|
|
327
|
+
df = df.sort_index(axis=1)
|
|
328
|
+
df.columns.name = 'code'
|
|
329
|
+
self._datas[field] = df
|
|
330
|
+
|
|
331
|
+
values = df.tail(1).squeeze()
|
|
332
|
+
|
|
333
|
+
quarter_map = {1: '0331', 2: '0630', 3: '0930', 4: '1231'}
|
|
334
|
+
fin_date = quarter_map[quarter] if quarter in quarter_map else None
|
|
335
|
+
|
|
336
|
+
path = self.get_path()
|
|
337
|
+
if isinstance(path, str):
|
|
338
|
+
trade_date_path = f'{path}/price01d/trade_date.fea'
|
|
339
|
+
else:
|
|
340
|
+
trade_date_path = f'{path["public"]}/price01d/trade_date.fea'
|
|
341
|
+
from src.rlquantdata.utilities.trade_date_utils import TradeDateUtils
|
|
342
|
+
tdu = TradeDateUtils(trade_date_path, self)
|
|
343
|
+
|
|
344
|
+
def at_data(data_df, label, trade_date):
|
|
345
|
+
if label not in data_df.columns:
|
|
346
|
+
return np.nan
|
|
347
|
+
trade_date = trade_date[:4] + "-" + trade_date[4:6] + "-" + trade_date[6:]
|
|
348
|
+
if not tdu.isBizDay('china.sse', trade_date):
|
|
349
|
+
trade_date = tdu.advanceDateByCalendar('china.sse', trade_date, '-1b').strftime('%Y-%m-%d')
|
|
350
|
+
if trade_date in data_df.index:
|
|
351
|
+
return data_df.at[trade_date, label]
|
|
352
|
+
else:
|
|
353
|
+
return np.nan
|
|
354
|
+
|
|
355
|
+
def get_fin(vv, label, fin_date=None, shift=0):
|
|
356
|
+
split_array = vv.split(':')
|
|
357
|
+
for i in range(len(split_array), 0, -2):
|
|
358
|
+
if fin_date is None:
|
|
359
|
+
idx = i - 1 - shift * 2
|
|
360
|
+
if idx < 0:
|
|
361
|
+
return np.nan
|
|
362
|
+
if merge_quarter_df is not None:
|
|
363
|
+
return at_data(merge_quarter_df, label, split_array[idx - 1])
|
|
364
|
+
return float(split_array[idx]) if split_array[idx] != '' else np.nan
|
|
365
|
+
elif split_array[i - 2].endswith(fin_date):
|
|
366
|
+
idx = i - 1 - shift * 2 * 4
|
|
367
|
+
if idx < 0:
|
|
368
|
+
return np.nan
|
|
369
|
+
if merge_quarter_df is not None:
|
|
370
|
+
return at_data(merge_quarter_df, label, split_array[idx - 1])
|
|
371
|
+
return float(split_array[idx]) if split_array[idx] != '' else np.nan
|
|
372
|
+
|
|
373
|
+
start_time = start_time.replace('-', '')
|
|
374
|
+
main_dict = {}
|
|
375
|
+
for label, item in values.items():
|
|
376
|
+
if item is None:
|
|
377
|
+
main_dict[label] = {}
|
|
378
|
+
continue
|
|
379
|
+
|
|
380
|
+
array = item.split(',')
|
|
381
|
+
label_dict = {}
|
|
382
|
+
for i in range(len(array), 0, -2):
|
|
383
|
+
cur_date = array[i - 2]
|
|
384
|
+
if merge_publish_df is not None:
|
|
385
|
+
label_dict[cur_date] = at_data(merge_publish_df, label, cur_date)
|
|
386
|
+
else:
|
|
387
|
+
label_dict[cur_date] = get_fin(array[i - 1], label, fin_date, quarter_shift)
|
|
388
|
+
if cur_date < start_time and not pd.isna(label_dict[cur_date]):
|
|
389
|
+
break
|
|
390
|
+
|
|
391
|
+
main_dict[label] = label_dict
|
|
392
|
+
|
|
393
|
+
# 生成完整的 df,可能包含节假日
|
|
394
|
+
result = pd.DataFrame(main_dict)
|
|
395
|
+
result.index = pd.to_datetime(result.index)
|
|
396
|
+
union_index = result.index.union(df.index)
|
|
397
|
+
result = result.reindex(union_index, fill_value=pd.NA)
|
|
398
|
+
|
|
399
|
+
# 获取节假日的 index
|
|
400
|
+
hol_index = result.index.difference(df.index)
|
|
401
|
+
|
|
402
|
+
# 获取连续节假日的第一天,得到第一天的索引,由该索引获得连续节假日的第一天的上一个交易日
|
|
403
|
+
start_of_continuous_segments = hol_index[
|
|
404
|
+
result.index.difference(df.index).to_series().diff() > pd.Timedelta(days=1)]
|
|
405
|
+
start_of_continuous_segments = pd.DatetimeIndex([hol_index[0]]).union(start_of_continuous_segments)
|
|
406
|
+
position = union_index.searchsorted(start_of_continuous_segments)
|
|
407
|
+
head_hol_size = 0
|
|
408
|
+
# 移除连续节假日
|
|
409
|
+
while True:
|
|
410
|
+
if position[head_hol_size] == head_hol_size:
|
|
411
|
+
head_hol_size += 1
|
|
412
|
+
else:
|
|
413
|
+
break
|
|
414
|
+
position = position[head_hol_size:] - 1
|
|
415
|
+
pre_biz_index = union_index[position]
|
|
416
|
+
|
|
417
|
+
# 将连续节假日的数据,先 ffill,再取连续节假日的最后一天,使得后面节假日的数据优先级高于前面的节假日
|
|
418
|
+
hol_df = result.loc[hol_index]
|
|
419
|
+
index_diff = hol_df.index.to_series().diff().dt.days.ne(1).cumsum()
|
|
420
|
+
hol_df['index_diff'] = index_diff
|
|
421
|
+
hol_df = hol_df.groupby('index_diff').ffill()
|
|
422
|
+
hol_df['index_diff'] = index_diff
|
|
423
|
+
hol_df = hol_df.groupby('index_diff').tail(1)
|
|
424
|
+
hol_df = hol_df.drop('index_diff', axis=1)
|
|
425
|
+
|
|
426
|
+
if head_hol_size != 0:
|
|
427
|
+
# 头几天为节假日,移除后再合并
|
|
428
|
+
# 将合并的节假日数据,与交易日数据合并,并给与交易日的 index
|
|
429
|
+
head_hol_row_df = hol_df.head(head_hol_size)
|
|
430
|
+
hol_df = hol_df.drop(hol_df.index[:head_hol_size])
|
|
431
|
+
hol_df = pd.DataFrame(
|
|
432
|
+
np.where(pd.isna(hol_df), result.loc[pre_biz_index], hol_df),
|
|
433
|
+
index=pre_biz_index,
|
|
434
|
+
columns=hol_df.columns
|
|
435
|
+
)
|
|
436
|
+
result = pd.concat([head_hol_row_df, result.reindex(df.index)])
|
|
437
|
+
result.update(hol_df)
|
|
438
|
+
result.iloc[:head_hol_size + 1] = result.iloc[:head_hol_size + 1].ffill()
|
|
439
|
+
result = result.drop(result.index[:head_hol_size])
|
|
440
|
+
result.index.name = 'trade_date'
|
|
441
|
+
else:
|
|
442
|
+
hol_df = pd.DataFrame(
|
|
443
|
+
np.where(pd.isna(hol_df), result.loc[pre_biz_index], hol_df),
|
|
444
|
+
index=pre_biz_index,
|
|
445
|
+
columns=hol_df.columns
|
|
446
|
+
)
|
|
447
|
+
result = result.reindex(df.index)
|
|
448
|
+
result.update(hol_df)
|
|
449
|
+
|
|
450
|
+
return result
|
|
451
|
+
|
|
452
|
+
def get_data(self, start_time=None, end_time=None,
|
|
453
|
+
codes=None, field='01d/close', bar_counts=None, interval=1, format_column=True, universe=None):
|
|
454
|
+
universe = self.__apply_universe(start_time, end_time, codes, universe)
|
|
455
|
+
t1 = time.time()
|
|
456
|
+
freq, fld = field.split('/', 1)
|
|
457
|
+
fre = re.sub(r'[^a-zA-Z]', '', freq)
|
|
458
|
+
try:
|
|
459
|
+
if field not in self._datas.keys():
|
|
460
|
+
print('no {} data source, now load it'.format(field))
|
|
461
|
+
self.preloading(start_time, end_time, {freq: [fld]})
|
|
462
|
+
elif self.check_time_limit(start_time, end_time, self._datas[field]):
|
|
463
|
+
self.preloading(start_time, end_time, {freq: [fld]})
|
|
464
|
+
except FileNotFoundError as e:
|
|
465
|
+
if self.enable_remote:
|
|
466
|
+
return self.__get_remote_data(start_time, end_time, codes, field, bar_counts, interval)
|
|
467
|
+
raise e
|
|
468
|
+
|
|
469
|
+
start_time, end_time = utils.format_time(start_time, end_time, fre)
|
|
470
|
+
|
|
471
|
+
# if end_time == None:
|
|
472
|
+
# end_time = dt.datetime.today().strftime('%Y%m%d')
|
|
473
|
+
if (start_time is None) or (end_time is None):
|
|
474
|
+
if freq.endswith('min'):
|
|
475
|
+
bars = self._datas[field].get_datas([], start_time, end_time, bar_counts, codes)
|
|
476
|
+
else:
|
|
477
|
+
bars = self._datas[field].get_datas(self.trading_dates, start_time, end_time, bar_counts, codes)
|
|
478
|
+
else:
|
|
479
|
+
if freq == '01d':
|
|
480
|
+
start_time_int = pd.to_datetime(pd.to_datetime(start_time).strftime('%Y-%m-%d 00:00:00'))
|
|
481
|
+
end_time_int = pd.to_datetime(pd.to_datetime(end_time).strftime('%Y-%m-%d 00:00:00'))
|
|
482
|
+
else:
|
|
483
|
+
start_time_int = pd.to_datetime(start_time)
|
|
484
|
+
end_time_int = pd.to_datetime(end_time)
|
|
485
|
+
bars = self._datas[field].get_range_datas(start_time_int, end_time_int, codes)
|
|
486
|
+
self.get_datas_time += time.time() - t1
|
|
487
|
+
self.get_datas_count += 1
|
|
488
|
+
if bar_counts is None and interval > 1:
|
|
489
|
+
bars = bars[::interval]
|
|
490
|
+
|
|
491
|
+
if universe is not None:
|
|
492
|
+
bars = bars.reindex(columns=universe.columns)
|
|
493
|
+
universe = universe.reindex(index=bars.index)
|
|
494
|
+
return bars * universe
|
|
495
|
+
# 如果需要按照统一codes进行format,在此执行
|
|
496
|
+
elif format_column and self.format_column is not None and codes is None:
|
|
497
|
+
bars = bars.reindex(columns=self.codes)
|
|
498
|
+
return bars
|
|
499
|
+
|
|
500
|
+
def del_data(self, field):
|
|
501
|
+
if field in self._datas.keys():
|
|
502
|
+
del self._datas[field]
|
|
503
|
+
gc.collect()
|
|
504
|
+
|
|
505
|
+
def get_path(self):
|
|
506
|
+
return self.path
|
|
507
|
+
|
|
508
|
+
def get_remote_hol_dates(self, from_date=None):
|
|
509
|
+
if self.enable_remote:
|
|
510
|
+
return self.__get_remote_hol_dates(from_date)
|
|
511
|
+
else:
|
|
512
|
+
return None
|
|
513
|
+
|
|
514
|
+
def __get_remote_hol_dates(self, from_date, relogin=False):
|
|
515
|
+
token = self.__login(relogin)
|
|
516
|
+
if token is None:
|
|
517
|
+
return None
|
|
518
|
+
if from_date is None:
|
|
519
|
+
from_date = ''
|
|
520
|
+
response = self.__request(f'api/factor/data/get_hol_dates/{from_date}', None, headers={'Authorization': token})
|
|
521
|
+
if response is not None:
|
|
522
|
+
stream_data = BytesIO()
|
|
523
|
+
for chunk in response.iter_content(chunk_size=1024):
|
|
524
|
+
if chunk:
|
|
525
|
+
stream_data.write(chunk)
|
|
526
|
+
stream_data.seek(0)
|
|
527
|
+
result = json.loads(stream_data.getvalue())
|
|
528
|
+
if result.get('code', 0) == -1 and not relogin:
|
|
529
|
+
return self.__get_remote_hol_dates(from_date, True)
|
|
530
|
+
elif result.get('code', None) is None:
|
|
531
|
+
return result.get('value')
|
|
532
|
+
else:
|
|
533
|
+
raise Exception(f'get data error, message: {result["msg"]}')
|
|
534
|
+
return None
|
|
535
|
+
|
|
536
|
+
def __get_remote_data(self, start_time, end_time, codes, field, bar_counts, interval, relogin=False, universe=None):
|
|
537
|
+
token = self.__login(relogin)
|
|
538
|
+
if token is None:
|
|
539
|
+
return None
|
|
540
|
+
t1 = time.time()
|
|
541
|
+
data_json = {
|
|
542
|
+
'start_time': start_time, 'end_time': end_time, 'codes': codes, 'field': field,
|
|
543
|
+
'bar_counts': bar_counts, 'interval': interval,
|
|
544
|
+
}
|
|
545
|
+
if isinstance(universe, str):
|
|
546
|
+
data_json['universe'] = universe
|
|
547
|
+
response = self.__request('api/factor/data/get_data', data_json, {'Authorization': token}, True)
|
|
548
|
+
if response is not None:
|
|
549
|
+
with BytesIO() as stream_data:
|
|
550
|
+
for chunk in response.iter_content(chunk_size=1024):
|
|
551
|
+
# 处理数据块
|
|
552
|
+
if chunk:
|
|
553
|
+
# 在这里自定义处理数据块的逻辑
|
|
554
|
+
stream_data.write(chunk)
|
|
555
|
+
stream_data.seek(0)
|
|
556
|
+
if stream_data.getvalue()[0] == 65:
|
|
557
|
+
data = pd.read_feather(stream_data).set_index('trade_date')
|
|
558
|
+
data.columns = data.columns.astype(int)
|
|
559
|
+
print(
|
|
560
|
+
f'get field {field} from remote, start time {start_time}, end time {end_time}, cost {time.time() - t1}')
|
|
561
|
+
else:
|
|
562
|
+
result = json.loads(stream_data.getvalue())
|
|
563
|
+
if result.get('code', 0) == -1 and not relogin:
|
|
564
|
+
data = self.__get_remote_data(start_time, end_time, codes, field, bar_counts, interval, True,
|
|
565
|
+
universe)
|
|
566
|
+
else:
|
|
567
|
+
raise Exception(f'get data error, message: {result["msg"]}')
|
|
568
|
+
if universe is not None and not isinstance(universe, str):
|
|
569
|
+
if codes is not None:
|
|
570
|
+
universe = universe.reindex(columns=codes)
|
|
571
|
+
data = data.reindex(columns=universe.columns)
|
|
572
|
+
universe = universe.reindex(index=data.index)
|
|
573
|
+
return data * universe
|
|
574
|
+
return data
|
|
575
|
+
return None
|
|
576
|
+
|
|
577
|
+
def __get_remote_fin_data(self, start_time, end_time, field, quarter_shift, quarter, interval, relogin=False,
|
|
578
|
+
universe=None):
|
|
579
|
+
token = self.__login(relogin)
|
|
580
|
+
if token is None:
|
|
581
|
+
return None
|
|
582
|
+
t1 = time.time()
|
|
583
|
+
data_json = {
|
|
584
|
+
'start_time': start_time, 'end_time': end_time, 'field': field, 'quarter_shift': quarter_shift,
|
|
585
|
+
'quarter': quarter, 'interval': interval,
|
|
586
|
+
}
|
|
587
|
+
if isinstance(universe, str):
|
|
588
|
+
data_json['universe'] = universe
|
|
589
|
+
response = self.__request('api/factor/data/get_fin_data', data_json, {'Authorization': token})
|
|
590
|
+
if response is not None:
|
|
591
|
+
with BytesIO() as stream_data:
|
|
592
|
+
for chunk in response.iter_content(chunk_size=1024):
|
|
593
|
+
# 处理数据块
|
|
594
|
+
if chunk:
|
|
595
|
+
# 在这里自定义处理数据块的逻辑
|
|
596
|
+
stream_data.write(chunk)
|
|
597
|
+
stream_data.seek(0)
|
|
598
|
+
if stream_data.getvalue()[0] == 65:
|
|
599
|
+
data = pd.read_feather(stream_data).set_index('trade_date')
|
|
600
|
+
data.columns = data.columns.astype(int)
|
|
601
|
+
print(
|
|
602
|
+
f'get field {field} from remote, start time {start_time}, end time {end_time}, cost {time.time() - t1}')
|
|
603
|
+
else:
|
|
604
|
+
result = json.loads(stream_data.getvalue())
|
|
605
|
+
if result.get('code', 0) == -1 and not relogin:
|
|
606
|
+
data = self.__get_remote_fin_data(start_time, end_time, field, quarter_shift, quarter, interval,
|
|
607
|
+
True, universe)
|
|
608
|
+
else:
|
|
609
|
+
raise Exception(f'get fin data error, message: {result["msg"]}')
|
|
610
|
+
if universe is not None and not isinstance(universe, str):
|
|
611
|
+
data = data.reindex(columns=universe.columns)
|
|
612
|
+
universe = universe.reindex(index=data.index)
|
|
613
|
+
return data * universe
|
|
614
|
+
return None
|
|
615
|
+
|
|
616
|
+
def __login(self, relogin=False):
|
|
617
|
+
if self.__token is None or relogin:
|
|
618
|
+
result = self.__request('api/user/login', {
|
|
619
|
+
'username': self.username,
|
|
620
|
+
'password': self.password,
|
|
621
|
+
})
|
|
622
|
+
if result is not None:
|
|
623
|
+
result = json.loads(result.text)
|
|
624
|
+
if result['success']:
|
|
625
|
+
self.__token = result['value']['token']
|
|
626
|
+
print(f'login success, role : {result["value"]["role"]}')
|
|
627
|
+
else:
|
|
628
|
+
print(f'login error, message: {result["msg"]}')
|
|
629
|
+
|
|
630
|
+
return self.__token
|
|
631
|
+
|
|
632
|
+
def __request(self, url_suffix, json, headers=None, stream=False):
|
|
633
|
+
self.__lock.acquire()
|
|
634
|
+
size = len(self.__server_list)
|
|
635
|
+
firs_index = self.__valid_url_index
|
|
636
|
+
resp = None
|
|
637
|
+
while True:
|
|
638
|
+
url_prefix = self.__server_list[self.__valid_url_index]
|
|
639
|
+
try:
|
|
640
|
+
resp = requests.post(f'{url_prefix}' + f'/{url_suffix}'.replace('//', '/'), json=json, headers=headers,
|
|
641
|
+
stream=stream)
|
|
642
|
+
if resp.status_code == 200:
|
|
643
|
+
return resp
|
|
644
|
+
else:
|
|
645
|
+
print(f'request error, http status {resp.status_code}')
|
|
646
|
+
return None
|
|
647
|
+
except Exception as e:
|
|
648
|
+
print(f'{self.__server_list[self.__valid_url_index]} invalid')
|
|
649
|
+
if firs_index != (self.__valid_url_index + 1) % size:
|
|
650
|
+
self.__valid_url_index = (self.__valid_url_index + 1) % size
|
|
651
|
+
else:
|
|
652
|
+
break
|
|
653
|
+
finally:
|
|
654
|
+
self.__lock.release()
|
|
655
|
+
return resp
|
|
656
|
+
|
|
657
|
+
def __apply_universe(self, start_time, end_time, codes, universe):
|
|
658
|
+
if universe is None:
|
|
659
|
+
return universe
|
|
660
|
+
if isinstance(universe, pd.DataFrame):
|
|
661
|
+
if codes is not None:
|
|
662
|
+
universe = universe.reindex(columns=codes)
|
|
663
|
+
return universe
|
|
664
|
+
if isinstance(universe, str):
|
|
665
|
+
return self.get_data(start_time, end_time, field=universe)
|
|
File without changes
|