dataquant 1.1.6__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.
- dataquant/__init__.py +150 -0
- dataquant/apis/__init__.py +1 -0
- dataquant/apis/base/__init__.py +2 -0
- dataquant/apis/base/api.py +77 -0
- dataquant/apis/info/__init__.py +1 -0
- dataquant/apis/info/api.py +2300 -0
- dataquant/apis/quote/__init__.py +1 -0
- dataquant/apis/quote/api.py +1744 -0
- dataquant/cd/__init__.py +1 -0
- dataquant/cd/api.py +746 -0
- dataquant/hk/__init__.py +1 -0
- dataquant/hk/api.py +1110 -0
- dataquant/ic/__init__.py +1 -0
- dataquant/ic/api.py +158 -0
- dataquant/intl/__init__.py +1 -0
- dataquant/intl/api.py +137 -0
- dataquant/pof/__init__.py +1 -0
- dataquant/pof/api.py +1963 -0
- dataquant/sql/__init__.py +1 -0
- dataquant/sql/api.py +61 -0
- dataquant/utils/__init__.py +5 -0
- dataquant/utils/client.py +192 -0
- dataquant/utils/common.py +23 -0
- dataquant/utils/config.py +29 -0
- dataquant/utils/config.yml +30 -0
- dataquant/utils/connection.py +502 -0
- dataquant/utils/connection_pool.py +67 -0
- dataquant/utils/consts.py +1 -0
- dataquant/utils/convert.py +54 -0
- dataquant/utils/datetime_func.py +77 -0
- dataquant/utils/decorators.py +125 -0
- dataquant/utils/error.py +159 -0
- dataquant/utils/parallel.py +48 -0
- dataquant-1.1.6.dist-info/METADATA +30 -0
- dataquant-1.1.6.dist-info/RECORD +38 -0
- dataquant-1.1.6.dist-info/WHEEL +5 -0
- dataquant-1.1.6.dist-info/entry_points.txt +3 -0
- dataquant-1.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import io
|
|
3
|
+
import time
|
|
4
|
+
import six
|
|
5
|
+
import abc
|
|
6
|
+
import warnings
|
|
7
|
+
import requests
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import json
|
|
11
|
+
import urllib3
|
|
12
|
+
import pyarrow as pa
|
|
13
|
+
import pyarrow.feather as feather
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from dataquant.utils.common import (
|
|
17
|
+
ConnectionStatus,
|
|
18
|
+
ERROR_NO,
|
|
19
|
+
ERROR_INFO,
|
|
20
|
+
DATA
|
|
21
|
+
)
|
|
22
|
+
from dataquant.utils.error import (
|
|
23
|
+
ConnectionException,
|
|
24
|
+
ConnectionTimeOut,
|
|
25
|
+
RequestTimeOut,
|
|
26
|
+
RequestException,
|
|
27
|
+
GatewayException,
|
|
28
|
+
ServerException,
|
|
29
|
+
GATEWAY_ERROR_DICT,
|
|
30
|
+
SERVER_ERROR_DICT,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AbstractConnection(six.with_metaclass(abc.ABCMeta)):
|
|
35
|
+
"""
|
|
36
|
+
连接抽象类
|
|
37
|
+
"""
|
|
38
|
+
def create(self, *args, **kwargs):
|
|
39
|
+
"""
|
|
40
|
+
创建连接对象
|
|
41
|
+
:param args:
|
|
42
|
+
:param kwargs:
|
|
43
|
+
:return:
|
|
44
|
+
"""
|
|
45
|
+
raise NotImplementedError
|
|
46
|
+
|
|
47
|
+
def connect(self, timeout=5):
|
|
48
|
+
"""
|
|
49
|
+
启动连接
|
|
50
|
+
:param timeout:
|
|
51
|
+
:return:
|
|
52
|
+
"""
|
|
53
|
+
raise NotImplementedError
|
|
54
|
+
|
|
55
|
+
def set_timeout(self, timeout):
|
|
56
|
+
"""
|
|
57
|
+
设置超时时间
|
|
58
|
+
:param timeout:
|
|
59
|
+
:return:
|
|
60
|
+
"""
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
|
|
63
|
+
def send(self, *args, **kwargs):
|
|
64
|
+
"""
|
|
65
|
+
同步发送数据
|
|
66
|
+
:param args:
|
|
67
|
+
:param kwargs:
|
|
68
|
+
:return:
|
|
69
|
+
"""
|
|
70
|
+
raise NotImplementedError
|
|
71
|
+
|
|
72
|
+
def async_send(self, *args, **kwargs):
|
|
73
|
+
"""
|
|
74
|
+
异步发送数据
|
|
75
|
+
:param args:
|
|
76
|
+
:param kwargs:
|
|
77
|
+
:return:
|
|
78
|
+
"""
|
|
79
|
+
raise NotImplementedError
|
|
80
|
+
|
|
81
|
+
def receive(self, *args, **kwargs):
|
|
82
|
+
"""
|
|
83
|
+
接收数据
|
|
84
|
+
:param args:
|
|
85
|
+
:param kwargs:
|
|
86
|
+
:return:
|
|
87
|
+
"""
|
|
88
|
+
raise NotImplementedError
|
|
89
|
+
|
|
90
|
+
def status(self):
|
|
91
|
+
"""
|
|
92
|
+
连接状态
|
|
93
|
+
:return:
|
|
94
|
+
"""
|
|
95
|
+
raise NotImplementedError
|
|
96
|
+
|
|
97
|
+
def check(self):
|
|
98
|
+
"""
|
|
99
|
+
检查连接是否可用
|
|
100
|
+
:return:
|
|
101
|
+
"""
|
|
102
|
+
raise NotImplementedError
|
|
103
|
+
|
|
104
|
+
def error(self):
|
|
105
|
+
"""
|
|
106
|
+
连接错误信息
|
|
107
|
+
:return:
|
|
108
|
+
"""
|
|
109
|
+
raise NotImplementedError
|
|
110
|
+
|
|
111
|
+
def close(self):
|
|
112
|
+
"""
|
|
113
|
+
关闭链接
|
|
114
|
+
:return:
|
|
115
|
+
"""
|
|
116
|
+
raise NotImplementedError
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Connection(object):
|
|
120
|
+
"""
|
|
121
|
+
连接工厂类
|
|
122
|
+
"""
|
|
123
|
+
@classmethod
|
|
124
|
+
def get_connection(cls, config):
|
|
125
|
+
from dataquant.utils.common import Protocol
|
|
126
|
+
protocol = config.get("protocol")
|
|
127
|
+
conn_timeout = config.get("connect_timeout")
|
|
128
|
+
try:
|
|
129
|
+
with ConnectionContext(conn_timeout):
|
|
130
|
+
return globals()[Protocol[protocol].value]().create(config)
|
|
131
|
+
except KeyError:
|
|
132
|
+
raise RuntimeError("创建链接对象失败,请检查配置信息是否正确?config={}".format(config))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class HttpConnection(AbstractConnection):
|
|
136
|
+
"""
|
|
137
|
+
HTTP连接对象类
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def create(self, config):
|
|
141
|
+
"""
|
|
142
|
+
创建连接对象
|
|
143
|
+
:param config:
|
|
144
|
+
:return:
|
|
145
|
+
"""
|
|
146
|
+
# 解析config文件中的信息
|
|
147
|
+
url = config.get("url")
|
|
148
|
+
auth = config.get("auth")
|
|
149
|
+
|
|
150
|
+
assert url
|
|
151
|
+
assert auth
|
|
152
|
+
|
|
153
|
+
self._url = url
|
|
154
|
+
self._user = auth.get("username")
|
|
155
|
+
self._pwd = auth.get("password")
|
|
156
|
+
self._timeout = config.get("request_timeout")
|
|
157
|
+
self._page_size = config.get("page_size")
|
|
158
|
+
|
|
159
|
+
assert self._user
|
|
160
|
+
assert self._pwd
|
|
161
|
+
|
|
162
|
+
self._conn = requests.Session()
|
|
163
|
+
self._status = ConnectionStatus.Connected
|
|
164
|
+
self._conn_u3 = urllib3.PoolManager()
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
def set_timeout(self, timeout):
|
|
168
|
+
"""
|
|
169
|
+
设置超时时间
|
|
170
|
+
:param timeout:
|
|
171
|
+
:return:
|
|
172
|
+
"""
|
|
173
|
+
self._timeout = timeout
|
|
174
|
+
|
|
175
|
+
def _build_data(self, **kwargs):
|
|
176
|
+
api_type = kwargs.get("api_type", "normal")
|
|
177
|
+
if api_type in {"normal"}:
|
|
178
|
+
data = {
|
|
179
|
+
"params": kwargs,
|
|
180
|
+
"page_no": kwargs.get("page_no", "1"),
|
|
181
|
+
"page_size": kwargs.get("page_size", self._page_size),
|
|
182
|
+
}
|
|
183
|
+
elif api_type in {"quote", "kline", "summary"}:
|
|
184
|
+
data = {
|
|
185
|
+
"params": kwargs
|
|
186
|
+
}
|
|
187
|
+
elif api_type in {"sql"}:
|
|
188
|
+
data = {
|
|
189
|
+
"params": kwargs
|
|
190
|
+
}
|
|
191
|
+
else:
|
|
192
|
+
raise RuntimeError("未知的API类型,当前仅支持normal/quote/kline")
|
|
193
|
+
return data
|
|
194
|
+
|
|
195
|
+
def _build_ret(self, rsp, **kwargs):
|
|
196
|
+
api_type = kwargs.get("api_type", "normal")
|
|
197
|
+
ret = {}
|
|
198
|
+
# 拆卸其他返回参数,处理DF,常规处理模式
|
|
199
|
+
if api_type == "normal":
|
|
200
|
+
df = pd.DataFrame()
|
|
201
|
+
for k, v in rsp.items():
|
|
202
|
+
if k == "data":
|
|
203
|
+
if isinstance(rsp[k], dict):
|
|
204
|
+
for dk, dv in rsp[k].items():
|
|
205
|
+
if dk not in {"rows"}:
|
|
206
|
+
ret[dk] = dv
|
|
207
|
+
|
|
208
|
+
else:
|
|
209
|
+
ret[k] = v
|
|
210
|
+
if rsp.get("data"):
|
|
211
|
+
df = pd.DataFrame(rsp["data"]["rows"])
|
|
212
|
+
self._mod_ret_df(df, kwargs, ret)
|
|
213
|
+
# 行情接口处理模式
|
|
214
|
+
elif api_type in ("quote", "kline", "summary"):
|
|
215
|
+
df = pd.DataFrame()
|
|
216
|
+
if rsp['data_type'] == 'data':
|
|
217
|
+
ret['page_num'] = rsp['page_num']
|
|
218
|
+
data_bin = rsp['content']
|
|
219
|
+
format_type = kwargs.get('format', 'Arrow')
|
|
220
|
+
|
|
221
|
+
if api_type == "kline":
|
|
222
|
+
# 不复权Kline数据,返回格式为Parquet/Arrow格式
|
|
223
|
+
# 前复权Kline数据,返回格式为CSV格式
|
|
224
|
+
# 后复权Kline数据,1d以上返回Parquet/Arrow格式,其余返回CSV格式
|
|
225
|
+
if kwargs.get('candle_mode', '0') == '0':
|
|
226
|
+
pass
|
|
227
|
+
elif kwargs.get('candle_mode', '0') == '1':
|
|
228
|
+
format_type = 'CSVWithNames'
|
|
229
|
+
elif kwargs.get('candle_mode', '0') == '2':
|
|
230
|
+
if kwargs.get('candle_period', '6') in ('6', '7', '8', '9'):
|
|
231
|
+
pass
|
|
232
|
+
else:
|
|
233
|
+
format_type = 'CSVWithNames'
|
|
234
|
+
elif api_type == "summary":
|
|
235
|
+
format_type = 'TSVWithNames'
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
if format_type == 'CSVWithNames':
|
|
239
|
+
df = pd.read_csv(io.StringIO(data_bin.decode('utf8')), sep=',')
|
|
240
|
+
elif format_type == 'Parquet':
|
|
241
|
+
df = pd.read_parquet(io.BytesIO(data_bin))
|
|
242
|
+
elif format_type == 'Arrow':
|
|
243
|
+
df = feather.read_feather(pa.py_buffer(data_bin))
|
|
244
|
+
elif format_type == 'TSVWithNames':
|
|
245
|
+
df = pd.read_csv(io.StringIO(data_bin.decode('utf8')), sep='\t', names=kwargs['fix_cols'])
|
|
246
|
+
pass
|
|
247
|
+
except Exception as e:
|
|
248
|
+
warn_str = "数据转换异常,错误说明={},数据长度={}".format(str(e),len(data_bin))
|
|
249
|
+
warnings.warn(warn_str)
|
|
250
|
+
pass
|
|
251
|
+
else:
|
|
252
|
+
format_type = 'json'
|
|
253
|
+
for k, v in rsp.items():
|
|
254
|
+
if k == "data":
|
|
255
|
+
if isinstance(rsp[k], dict):
|
|
256
|
+
for dk, dv in rsp[k].items():
|
|
257
|
+
if dk not in {"cols", "items"}:
|
|
258
|
+
ret[dk] = dv
|
|
259
|
+
else:
|
|
260
|
+
ret[k] = v
|
|
261
|
+
|
|
262
|
+
# 20220806 行情单独处理
|
|
263
|
+
self._mod_ret_df_quote(df, kwargs, ret,format_type)
|
|
264
|
+
# SQL接口处理模式
|
|
265
|
+
elif api_type == "sql":
|
|
266
|
+
ret = dict()
|
|
267
|
+
ret['result_code'] = rsp['result_code']
|
|
268
|
+
ret['result_msg'] = rsp['result_msg']
|
|
269
|
+
tmp_rsp = rsp['data'].replace('null', '\"\"')
|
|
270
|
+
ret['data'] = pd.DataFrame(eval(tmp_rsp))
|
|
271
|
+
|
|
272
|
+
return ret
|
|
273
|
+
|
|
274
|
+
def _mod_ret_df(self, df, kwargs, ret):
|
|
275
|
+
# DF后期加工
|
|
276
|
+
df.columns = df.columns.map(lambda x: x.lower())
|
|
277
|
+
if not df.empty:
|
|
278
|
+
if isinstance(kwargs['cols'], list):
|
|
279
|
+
df = df[kwargs['cols']]
|
|
280
|
+
elif isinstance(kwargs['cols'], str):
|
|
281
|
+
cols = kwargs['cols'].replace(' ', '')
|
|
282
|
+
df = df[cols.split(',')]
|
|
283
|
+
|
|
284
|
+
for int_type in kwargs.get("int_param"):
|
|
285
|
+
if int_type in df.columns:
|
|
286
|
+
df[int_type] = pd.Series(df[int_type], dtype='int64')
|
|
287
|
+
for float_type in kwargs.get("float_param"):
|
|
288
|
+
if float_type in df.columns:
|
|
289
|
+
df[float_type] = pd.Series(df[float_type], dtype='float64')
|
|
290
|
+
else:
|
|
291
|
+
ret["data"] = None
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
if not(kwargs.get("rslt_type", 0)):
|
|
295
|
+
ret["data"] = df
|
|
296
|
+
elif kwargs.get("rslt_type", 0) == 1:
|
|
297
|
+
ret["data"] = np.array(df.to_records(index=False))
|
|
298
|
+
else:
|
|
299
|
+
raise ValueError("rslt_type must in (0,1)")
|
|
300
|
+
|
|
301
|
+
def _mod_ret_df_quote(self, df, kwargs, ret, format_type):
|
|
302
|
+
if (not df.empty) and len(df) > 0:
|
|
303
|
+
# 20220818 前复权数据类型规整,但本质是tsv,csv需要
|
|
304
|
+
if format_type == 'CSVWithNames':
|
|
305
|
+
for int_type in kwargs.get("int_param"):
|
|
306
|
+
if int_type in df.columns:
|
|
307
|
+
df[int_type] = pd.Series(df[int_type], dtype='float64')
|
|
308
|
+
for float_type in kwargs.get("float_param"):
|
|
309
|
+
if float_type in df.columns:
|
|
310
|
+
df[float_type] = pd.Series(df[float_type], dtype='float64')
|
|
311
|
+
pass
|
|
312
|
+
else:
|
|
313
|
+
df = None
|
|
314
|
+
ret["data"] = df
|
|
315
|
+
return
|
|
316
|
+
|
|
317
|
+
def send(self, method, **kwargs):
|
|
318
|
+
"""
|
|
319
|
+
同步发送数据
|
|
320
|
+
:param method:
|
|
321
|
+
:param kwargs:
|
|
322
|
+
:return:
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
api_type = kwargs.get("api_type", "normal")
|
|
326
|
+
# 已连接状态时
|
|
327
|
+
if self.check():
|
|
328
|
+
if self._user == "license":
|
|
329
|
+
url = "".join((self._url, "/", method, "?app_key=", self._pwd))
|
|
330
|
+
else:
|
|
331
|
+
raise RuntimeError("当前只支持license登录方式")
|
|
332
|
+
|
|
333
|
+
data = self._build_data(**kwargs)
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
response_u3 = self._conn_u3.request(
|
|
337
|
+
'POST',
|
|
338
|
+
url,
|
|
339
|
+
body=json.dumps(data),
|
|
340
|
+
headers={'Content-Type': 'application/json', 'Accept-Encoding': 'br,gzip,deflate'},
|
|
341
|
+
timeout=self._timeout)
|
|
342
|
+
response = ResponseU3(response_u3)
|
|
343
|
+
except requests.exceptions.ConnectTimeout as ex:
|
|
344
|
+
raise RequestTimeOut(ex)
|
|
345
|
+
# 状态码200表示请求成功
|
|
346
|
+
if response.status_code == 200:
|
|
347
|
+
# requests获取后直接调用json()方法转变为python字典请求获取HttpResponse对象
|
|
348
|
+
if api_type in {'quote', 'kline', 'summary'}:
|
|
349
|
+
rsp = {
|
|
350
|
+
'data_type':None,
|
|
351
|
+
'content': response.content,
|
|
352
|
+
'result_code': '0',
|
|
353
|
+
'page_num': 1,
|
|
354
|
+
}
|
|
355
|
+
if 'page_num' in kwargs:
|
|
356
|
+
rsp['page_num'] = kwargs['page_num']
|
|
357
|
+
if response.content[0:1] == b'{':
|
|
358
|
+
rsp = response.json()
|
|
359
|
+
rsp['data_type'] = 'json'
|
|
360
|
+
if "result_code" not in rsp:
|
|
361
|
+
if "resultCode" in rsp:
|
|
362
|
+
rsp['result_code'] = rsp["resultCode"]
|
|
363
|
+
else:
|
|
364
|
+
rsp['result_code'] = '0'
|
|
365
|
+
if "result_msg" not in rsp:
|
|
366
|
+
if "resultMsg" in rsp:
|
|
367
|
+
rsp['result_msg'] = rsp["resultMsg"]
|
|
368
|
+
else:
|
|
369
|
+
rsp['result_msg'] = ''
|
|
370
|
+
else:
|
|
371
|
+
rsp['data_type'] = 'data'
|
|
372
|
+
|
|
373
|
+
else:
|
|
374
|
+
rsp = response.json() # 180ms
|
|
375
|
+
# 从数据服务返回的数据
|
|
376
|
+
if "result_code" in rsp:
|
|
377
|
+
if int(rsp["result_code"]) == 0:
|
|
378
|
+
ret = self._build_ret(rsp, **kwargs)
|
|
379
|
+
return ret
|
|
380
|
+
# 获取error中异常信息
|
|
381
|
+
if SERVER_ERROR_DICT.get(int(rsp["result_code"]), None):
|
|
382
|
+
# warnings.warn("app_key权限认证失败,请联系管理员,error_info={}".format(rsp["resultMsg"]))
|
|
383
|
+
# warnings.warn("app_key权限已过期,请重新申请,申请站点:{}".format(rsp["resultMsg"]))
|
|
384
|
+
warn_str = "请求异常,异常信息:{}".format(rsp["result_msg"])
|
|
385
|
+
warnings.warn(warn_str)
|
|
386
|
+
raise RequestException(rsp["result_msg"])
|
|
387
|
+
else:
|
|
388
|
+
warn_str = "获取数据异常,path={},params={},error_info={}".format(
|
|
389
|
+
method, kwargs, rsp["result_msg"])
|
|
390
|
+
warnings.warn(warn_str)
|
|
391
|
+
raise RequestException("请求发生未定义错误,错误信息:{}".format(rsp["result_msg"]))
|
|
392
|
+
elif 400 <= response.status_code < 500:
|
|
393
|
+
if api_type == 'quote': # 行情接口
|
|
394
|
+
warn_str = "服务异常,异常信息:{}".format(response.text)
|
|
395
|
+
warnings.warn(warn_str)
|
|
396
|
+
raise ServerException(response.text)
|
|
397
|
+
else:
|
|
398
|
+
rsp = response.json()
|
|
399
|
+
# 从新版网关返回异常
|
|
400
|
+
if ERROR_NO in rsp:
|
|
401
|
+
# 获取错误代码
|
|
402
|
+
if GATEWAY_ERROR_DICT.get(rsp[ERROR_NO], None):
|
|
403
|
+
warnings.warn("网关返回错误,错误信息:{}".format(rsp[ERROR_INFO]))
|
|
404
|
+
raise GatewayException(rsp[ERROR_INFO])
|
|
405
|
+
else:
|
|
406
|
+
raise GatewayException("网关发生未定义错误,错误信息:{}".format(rsp[ERROR_INFO]))
|
|
407
|
+
# 从老版网关返回异常
|
|
408
|
+
elif DATA in rsp:
|
|
409
|
+
if isinstance(rsp[DATA], list):
|
|
410
|
+
error_dict = rsp[DATA][0]
|
|
411
|
+
else:
|
|
412
|
+
error_dict = rsp[DATA]
|
|
413
|
+
|
|
414
|
+
if GATEWAY_ERROR_DICT.get(error_dict[ERROR_NO], None):
|
|
415
|
+
warnings.warn("网关返回错误,错误信息:{}".format(error_dict[ERROR_INFO]))
|
|
416
|
+
raise GatewayException(error_dict[ERROR_INFO])
|
|
417
|
+
else:
|
|
418
|
+
raise GatewayException("网关发生未定义错误,错误信息:{}".format(error_dict[ERROR_INFO]))
|
|
419
|
+
else:
|
|
420
|
+
warnings.warn("网关返回未定义异常,异常信息:{}".format(response.text))
|
|
421
|
+
raise GatewayException(response.text)
|
|
422
|
+
# 服务错误
|
|
423
|
+
elif 500 <= response.status_code < 601:
|
|
424
|
+
warnings.warn("服务异常,异常信息:{}".format(response.text))
|
|
425
|
+
raise ServerException(response.text)
|
|
426
|
+
# 其他流程错误
|
|
427
|
+
else:
|
|
428
|
+
warnings.warn("异常流程,返回信息:{}".format(response.text))
|
|
429
|
+
raise Exception(response.text)
|
|
430
|
+
# 如果没连接则raise连接异常
|
|
431
|
+
raise ConnectionException("连接已关闭,无法获取数据")
|
|
432
|
+
|
|
433
|
+
def async_send(self, *args, **kwargs):
|
|
434
|
+
"""
|
|
435
|
+
异步发送数据
|
|
436
|
+
:param args:
|
|
437
|
+
:param kwargs:
|
|
438
|
+
:return:
|
|
439
|
+
"""
|
|
440
|
+
raise NotImplementedError
|
|
441
|
+
|
|
442
|
+
def receive(self, *args, **kwargs):
|
|
443
|
+
"""
|
|
444
|
+
接收数据
|
|
445
|
+
:param args:
|
|
446
|
+
:param kwargs:
|
|
447
|
+
:return:
|
|
448
|
+
"""
|
|
449
|
+
raise NotImplementedError
|
|
450
|
+
|
|
451
|
+
def status(self):
|
|
452
|
+
"""
|
|
453
|
+
连接状态
|
|
454
|
+
:return:
|
|
455
|
+
"""
|
|
456
|
+
return self._status
|
|
457
|
+
|
|
458
|
+
def check(self):
|
|
459
|
+
return self._status in [ConnectionStatus.Connected, ConnectionStatus.SafeConnected]
|
|
460
|
+
|
|
461
|
+
def error(self):
|
|
462
|
+
"""
|
|
463
|
+
连接错误信息
|
|
464
|
+
:return:
|
|
465
|
+
"""
|
|
466
|
+
raise NotImplementedError
|
|
467
|
+
|
|
468
|
+
def close(self):
|
|
469
|
+
"""
|
|
470
|
+
关闭链接
|
|
471
|
+
:return:
|
|
472
|
+
"""
|
|
473
|
+
self._conn.close()
|
|
474
|
+
self._status = ConnectionStatus.Disconnected
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
class ConnectionContext(object):
|
|
478
|
+
def __init__(self, timeout=5):
|
|
479
|
+
assert timeout
|
|
480
|
+
self.timeout = timeout
|
|
481
|
+
self.start = None
|
|
482
|
+
|
|
483
|
+
def __enter__(self):
|
|
484
|
+
self.start = time.time()
|
|
485
|
+
|
|
486
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
487
|
+
if time.time() - self.start > self.timeout:
|
|
488
|
+
raise ConnectionTimeOut
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
class ResponseU3(object):
|
|
492
|
+
def __init__(self,resp_u3):
|
|
493
|
+
self.status_code = resp_u3.status
|
|
494
|
+
self.content = resp_u3.data
|
|
495
|
+
self.ori_resp = resp_u3
|
|
496
|
+
|
|
497
|
+
@property
|
|
498
|
+
def text(self):
|
|
499
|
+
return self.content.decode(errors='backslashreplace')
|
|
500
|
+
|
|
501
|
+
def json(self):
|
|
502
|
+
return json.loads(self.text)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
from threading import Lock, Semaphore #Semaphore 是用于控制进入数量的锁,控制同时进行的线程
|
|
5
|
+
from contextlib import contextmanager #上下文管理
|
|
6
|
+
|
|
7
|
+
from dataquant.utils.connection import Connection
|
|
8
|
+
from dataquant.utils.decorators import retry
|
|
9
|
+
from dataquant.utils.config import read_config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
CONFIG = read_config()['system']
|
|
13
|
+
PARALLEL_MODE = CONFIG.get("parallel_mode")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConnectionPool(object):
|
|
17
|
+
def __init__(self, config):
|
|
18
|
+
self._config = config.copy()
|
|
19
|
+
self._lock = Lock()
|
|
20
|
+
self._connections = deque()
|
|
21
|
+
self._connect_timeout = config.pop("connect_timeout")
|
|
22
|
+
self._signal = Semaphore(config.pop("pool_size"))
|
|
23
|
+
self._send_deque = deque()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def config(self):
|
|
28
|
+
return self._config
|
|
29
|
+
|
|
30
|
+
@retry(3, exp_name="RequestTimeOut")
|
|
31
|
+
def send(self, method, **kwargs):
|
|
32
|
+
with self._signal:
|
|
33
|
+
with self._yield_conn() as conn:
|
|
34
|
+
return conn.send(method, **kwargs)
|
|
35
|
+
|
|
36
|
+
@contextmanager
|
|
37
|
+
def _yield_conn(self):
|
|
38
|
+
conn = self._get_connection()
|
|
39
|
+
try:
|
|
40
|
+
yield conn #返回连接
|
|
41
|
+
except Exception as ex:
|
|
42
|
+
conn.close() #连接异常时关闭连接
|
|
43
|
+
raise ex
|
|
44
|
+
else:
|
|
45
|
+
with self._lock:
|
|
46
|
+
self._connections.append(conn) #若已加锁则将当前连接加入连接队列
|
|
47
|
+
|
|
48
|
+
def _get_connection(self):
|
|
49
|
+
with self._lock:
|
|
50
|
+
while self._connections:
|
|
51
|
+
conn = self._connections.popleft() #从队列左边取出一个连接
|
|
52
|
+
if conn.check(): #检查连接是否可用
|
|
53
|
+
return conn
|
|
54
|
+
else:
|
|
55
|
+
conn.close()
|
|
56
|
+
|
|
57
|
+
return self._cretate_connection()
|
|
58
|
+
|
|
59
|
+
@retry(5, exp_name="ConnectTimeout")
|
|
60
|
+
def _cretate_connection(self):
|
|
61
|
+
return Connection.get_connection(self._config) #创建连接
|
|
62
|
+
|
|
63
|
+
def close(self):
|
|
64
|
+
with self._lock:
|
|
65
|
+
for conn in self._connections: #逐个关闭连接
|
|
66
|
+
conn.close()
|
|
67
|
+
self._connections.clear() #清空队列
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import io
|
|
3
|
+
from dataquant.utils.parallel import parallel_df
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from itertools import groupby
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def convert_param(_type, param):
|
|
9
|
+
if param is None:
|
|
10
|
+
return _type() #返回该类型空值
|
|
11
|
+
|
|
12
|
+
return param
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def convert_fields(field):
|
|
16
|
+
if field is None:
|
|
17
|
+
return []
|
|
18
|
+
|
|
19
|
+
if isinstance(field, list):
|
|
20
|
+
return field
|
|
21
|
+
|
|
22
|
+
if isinstance(field, str):
|
|
23
|
+
return [field]
|
|
24
|
+
|
|
25
|
+
raise ValueError("字段[{}]类型转换失败,支持[None, str, list]类型,实参类型为[{}]".format(field, type(field)))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def sort_merge_df(df_list, sort_cols, ignore_index=True):
|
|
29
|
+
df = None
|
|
30
|
+
if len(df_list) <= 0:
|
|
31
|
+
return df
|
|
32
|
+
if len(df_list) == 1:
|
|
33
|
+
return df_list[0] # 节省10ms左右
|
|
34
|
+
|
|
35
|
+
sorted_dfs = sorted(df_list, key=lambda df: '|'.join(df.iloc[0][sort_cols].astype(str)))
|
|
36
|
+
df = pd.concat(sorted_dfs, ignore_index=ignore_index)
|
|
37
|
+
return df
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def sort_merge_df_dic(df_dic, ignore_index=True):
|
|
41
|
+
df = None
|
|
42
|
+
if len(df_dic) <= 0:
|
|
43
|
+
return df
|
|
44
|
+
sorted_dfs = list(zip(*sorted(df_dic.items(),key=lambda kv:kv[0])))[1]
|
|
45
|
+
if len(df_dic) == 1:
|
|
46
|
+
return sorted_dfs[0] # 节省10ms左右
|
|
47
|
+
df = pd.concat(sorted_dfs, ignore_index=ignore_index)
|
|
48
|
+
return df
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def split_df(df: pd.DataFrame):
|
|
52
|
+
new_str = df.to_csv(sep='|', header=False, index=False) # 47ms
|
|
53
|
+
df = pd.read_csv(io.StringIO(new_str.replace('"', '')), header=None, sep='|', dtype='float64') # 38ms
|
|
54
|
+
return df
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def convert_date(dt, now=True):
|
|
6
|
+
# if dt == "": #时间为空时返回当前时间或19900101
|
|
7
|
+
# if now:
|
|
8
|
+
# return datetime.datetime.now().date().strftime("%Y%m%d")
|
|
9
|
+
# else:
|
|
10
|
+
# return "19900101""19900101"
|
|
11
|
+
if dt == None:
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
if isinstance(dt, datetime.datetime): #datetime类型数据进行数据转换成固定格式
|
|
15
|
+
return dt.date().strftime("%Y%m%d")
|
|
16
|
+
|
|
17
|
+
if isinstance(dt, datetime.date): #datetime.date数据类型转换为固定格式
|
|
18
|
+
return dt.strftime("%Y%m%d")
|
|
19
|
+
|
|
20
|
+
if isinstance(dt, str):
|
|
21
|
+
if len(dt) == 0:
|
|
22
|
+
return dt
|
|
23
|
+
else:
|
|
24
|
+
from dateutil.parser import parse
|
|
25
|
+
return parse(dt).date().strftime("%Y%m%d") #用parse进行格式转换,变成数据类型
|
|
26
|
+
|
|
27
|
+
if hasattr(dt, "to_pydatetime"): #判断一个对象里面是否有"to_pydatetime"属性或者"to_pydatetime"方法
|
|
28
|
+
return dt.to_pydatetime().date().strftime("%Y%m%d")
|
|
29
|
+
|
|
30
|
+
if hasattr(dt, "dtype") and dt.dtype.char == "M": #M表示带分钟的dtype类型
|
|
31
|
+
from dateutil.parser import parse
|
|
32
|
+
return parse(dt).date().strftime("%Y%m%d")
|
|
33
|
+
|
|
34
|
+
raise ValueError("转换日期格式异常,实参为[{}]".format(dt)) #不在上述所列类型抛出异常
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def convert_date_mins(dt, end=False, now=True):
|
|
38
|
+
if dt == None:
|
|
39
|
+
return
|
|
40
|
+
count = 0
|
|
41
|
+
if isinstance(dt, datetime.datetime):
|
|
42
|
+
pass
|
|
43
|
+
elif isinstance(dt, datetime.date):
|
|
44
|
+
count = 8
|
|
45
|
+
elif isinstance(dt, str):
|
|
46
|
+
from dateutil.parser import parse
|
|
47
|
+
count = count_num(dt)
|
|
48
|
+
dt = parse(dt)
|
|
49
|
+
elif hasattr(dt, "to_pydatetime"):
|
|
50
|
+
dt = dt.to_pydatetime()
|
|
51
|
+
elif hasattr(dt, "dtype") and dt.dtype.char == "M":
|
|
52
|
+
from dateutil.parser import parse
|
|
53
|
+
dt = parse(dt)
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError("转换日期格式异常,实参为[{}]".format(dt))
|
|
56
|
+
|
|
57
|
+
if end and count == 8:
|
|
58
|
+
dt = datetime.datetime.combine(dt, datetime.time.max)
|
|
59
|
+
|
|
60
|
+
return dt.strftime("%Y%m%d %H:%M:%S")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def count_num(str_dt):
|
|
64
|
+
dig_sum = 0
|
|
65
|
+
for strs in str_dt:
|
|
66
|
+
if strs.isdigit():
|
|
67
|
+
dig_sum += 1
|
|
68
|
+
return dig_sum
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_current_date():
|
|
72
|
+
"""
|
|
73
|
+
获取当前日期
|
|
74
|
+
:return:
|
|
75
|
+
"""
|
|
76
|
+
return datetime.datetime.now().strftime('%Y%m%d')
|
|
77
|
+
|