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/hk/api.py ADDED
@@ -0,0 +1,1110 @@
1
+ # -*- coding: UTF-8 -*-
2
+
3
+ import re
4
+ import os
5
+ import time
6
+ import mmap
7
+ import gevent
8
+ import platform
9
+ import warnings
10
+ import pandas as pd
11
+ from functools import wraps
12
+ import traceback
13
+ import socket
14
+ from concurrent.futures import as_completed, ThreadPoolExecutor
15
+
16
+ from dataquant.apis.base import get_data, get_qdata
17
+ from dataquant.utils.convert import convert_fields, sort_merge_df, sort_merge_df_dic
18
+ from dataquant.utils.datetime_func import get_current_date
19
+ from dataquant.utils.config import read_config
20
+ from dataquant.utils.error import MaxTryExceed, RunTimeError
21
+
22
+ __all__ = [
23
+ # 新增港股新表查询接口;20240606 shenfc
24
+ "get_tick",#快照
25
+ "get_deal",#逐笔成交
26
+ "get_oddlot_order",#碎股
27
+ "get_broker_queue",#经纪商队列
28
+ "get_index_tick",#指数快照
29
+ "get_security_info", #获取港股基本信息
30
+ "get_stk_rstr_fctr" #获取港股复权因子信息
31
+ ]
32
+
33
+
34
+ # 请求服务端URL
35
+
36
+ #新增港股请求服务端URL
37
+ URL_GET_HKSTK_TICK = 'get_hkstock_snapshot'#快照
38
+ URL_GET_HKSTK_DEAL = 'get_hkstock_tick'#逐笔成交
39
+ URL_GET_HKSTK_ODD = 'get_hkstock_odd'#碎股
40
+ URL_GET_HKSTK_BROKER = 'get_hkstock_broker'#经纪商队列
41
+ URL_GET_HKSTK_INDEX = 'get_hkstock_index'#指数快照
42
+
43
+
44
+
45
+
46
+ # 代码与证券类型映射
47
+ CODE_SECURITY_MAP = {}
48
+
49
+
50
+ global CONFIG_PAGE_SIZE, PARALLEL_NUM, PARALLEL_MODE, EXECUTOR, EXECUTOR_INNER, \
51
+ HF_SORT_COLS, KLINED_SORT_COLS, KLINEM_SORT_COLS, PROCESS_MMAP
52
+
53
+ if platform.system().lower() == 'windows':
54
+ PROCESS_MMAP = mmap.mmap(-1, 1, tagname='dataquant_sdk_pid')
55
+ else:
56
+ MMAP_FILE = open('/dev/shm/dataquant_sdk_pid', 'w+b')
57
+ MMAP_FILE.write(b"\x00")
58
+ MMAP_FILE.flush()
59
+ PROCESS_MMAP = mmap.mmap(MMAP_FILE.fileno(), 1, flags=mmap.MAP_SHARED, prot=mmap.PROT_READ | mmap.PROT_WRITE)
60
+
61
+ PROCESS_MMAP.seek(0)
62
+ if PROCESS_MMAP.read() != b'\x31':
63
+ PROCESS_MMAP.seek(0)
64
+ PROCESS_MMAP.write(b"\x00")
65
+ PROCESS_MMAP.flush()
66
+
67
+
68
+ def reset():
69
+ PROCESS_MMAP.seek(0)
70
+ PROCESS_MMAP.write(b"\x00")
71
+ PROCESS_MMAP.flush()
72
+
73
+
74
+ def load_basic_info():
75
+ int_param = []
76
+ float_param = []
77
+
78
+ params = {
79
+ "mkt_code_list": ['XHKG'],
80
+ "cols": ['scr_num', 'scr_name', 'scr_abbr', 'scr_code', 'mkt_code', 'scr_clas', 'list_date'],
81
+ "int_param": int_param,
82
+ "float_param": float_param
83
+ }
84
+ result = get_data("get_scr_basc_info", **params)
85
+ global CODE_SECURITY_MAP
86
+ CODE_SECURITY_MAP = result.set_index('scr_num')['scr_clas'].to_dict()
87
+
88
+
89
+ reset()
90
+
91
+
92
+ def load_conf():
93
+ global CONFIG_PAGE_SIZE, PARALLEL_NUM, PARALLEL_MODE, EXECUTOR, EXECUTOR_INNER, \
94
+ HF_SORT_COLS, KLINED_SORT_COLS, KLINEM_SORT_COLS
95
+ CONFIG = read_config()['system']
96
+ CONFIG_PAGE_SIZE = CONFIG.get('page_size')
97
+ PARALLEL_NUM = CONFIG.get("quote_parallel")
98
+ PARALLEL_MODE = CONFIG.get("parallel_mode")
99
+ if PARALLEL_MODE == 'Thread':
100
+ EXECUTOR = ThreadPoolExecutor(max_workers=PARALLEL_NUM)
101
+ EXECUTOR_INNER = ThreadPoolExecutor(max_workers=PARALLEL_NUM)
102
+ elif PARALLEL_MODE == 'Coroutine':
103
+ from gevent import monkey
104
+ monkey.patch_all()
105
+ from gevent.pool import Pool as gPool
106
+ EXECUTOR = gPool(PARALLEL_NUM)
107
+ EXECUTOR_INNER = gPool(PARALLEL_NUM)
108
+ EXECUTOR.submit = EXECUTOR.spawn
109
+ EXECUTOR_INNER.submit = EXECUTOR_INNER.spawn
110
+ HF_SORT_COLS = {
111
+ 'hkstock': list(CONFIG.get('quote_hf_hkstock_sort').split(',')),
112
+ }
113
+ KLINED_SORT_COLS = {
114
+
115
+ }
116
+ KLINEM_SORT_COLS = {
117
+
118
+ }
119
+
120
+
121
+ load_conf()
122
+ _config = read_config()['system']
123
+
124
+ TWO_STEP_QUERY = False # 20220812 行情请求是否使用分页模式(第一步获取总条数,第二步实际查询)
125
+ ONE_STEP_NUM = 9999999 # 20220812 一步请求的最大请求数量需要很大以覆盖所有情况
126
+
127
+ KLINE_CODE_GROUP = 10
128
+ HF_DAY_DELTA = 1
129
+ HF_CODE_GROUP = 1
130
+ KLINE_DAY_DELTA = 90
131
+
132
+ KLINE_DAY_DELTA_CANDLE_PERIOD = {
133
+ '1m', '1',
134
+ '5m', '2',
135
+ '15m', '3',
136
+ '30m', '4',
137
+ '1h', '5',
138
+ '1d', '6',
139
+ # '1w': '7',
140
+ # '1M': '8',
141
+ # '1y': '9',
142
+ '2h', '11',
143
+ '3h', '12',
144
+ '4h', '13'
145
+ }
146
+
147
+
148
+ CANDLE_PERIOD_MAP = {
149
+ '1m': '1',
150
+ '5m': '2',
151
+ '15m': '3',
152
+ '30m': '4',
153
+ '1h': '5',
154
+ '1d': '6',
155
+ '1w': '7',
156
+ '1M': '8',
157
+ '1y': '9',
158
+ '2h': '11',
159
+ '3h': '12',
160
+ '4h': '13'
161
+ }
162
+
163
+
164
+ CANDLE_MODE_MAP = {
165
+ None: '0',
166
+ 'pre': '1',
167
+ 'post': '2'
168
+ }
169
+
170
+
171
+
172
+ def bind(_socket=None):
173
+ if _socket is None:
174
+ PROCESS_MMAP.seek(0)
175
+ # 共享区域没有数据,则写入对应数据
176
+ if PROCESS_MMAP.read() == b'\x00':
177
+ # 执行绑定操作
178
+ PROCESS_MMAP.seek(0)
179
+ PROCESS_MMAP.write(b'\x31')
180
+ PROCESS_MMAP.flush()
181
+
182
+ # 检验绑定是否成功
183
+ PROCESS_MMAP.seek(0)
184
+ if PROCESS_MMAP.read() == b'\x31':
185
+ return True
186
+ else:
187
+ return False
188
+
189
+ else:
190
+ return False
191
+ else:
192
+ try:
193
+ _socket.bind(('127.0.0.1', _config["bind_port"]))
194
+ return True
195
+ except Exception as ex:
196
+ return False
197
+
198
+
199
+ def unbind(_socket=None):
200
+ if _socket is None:
201
+ PROCESS_MMAP.seek(0)
202
+ PROCESS_MMAP.write(b'\x00')
203
+ PROCESS_MMAP.flush()
204
+ else:
205
+ _socket.close()
206
+
207
+
208
+ def wait_until_bind(count=10000, time_delta=1.0):
209
+ def decorate(func):
210
+ @wraps(func)
211
+ def wrap(*args, **kwargs):
212
+ # 尝试进行绑定
213
+ bind_method = _config.get("bind_method", "socket")
214
+ if bind_method == 'socket':
215
+ _socket = socket.socket()
216
+ else:
217
+ _socket = None
218
+ cnt = 0
219
+ while True:
220
+ if bind(_socket):
221
+ try:
222
+ return func(*args, **kwargs)
223
+ except:
224
+ print(f"进程{os.getpid()}运行函数报错")
225
+ print(traceback.format_exc())
226
+ raise RunTimeError("运行时报错")
227
+ finally:
228
+ unbind(_socket)
229
+
230
+ else:
231
+ cnt += 1
232
+ if cnt > count:
233
+ raise MaxTryExceed(f"进程{os.getpid()}等待次数超过上线")
234
+ if cnt % 60 == 0:
235
+ print(f"进程{os.getpid()}正常排队查询")
236
+ time.sleep(time_delta)
237
+ return wrap
238
+ return decorate
239
+
240
+
241
+ def quote_single_part(method, params, two_step: bool = TWO_STEP_QUERY):
242
+ init_count = 0 # 初次查询已获取的条数
243
+ init_page = 0 # 初次查询已获取的页数
244
+ total_pages = 0
245
+ df_dic = {}
246
+
247
+ # 两步模式下第一步获取总条数以取得分页
248
+ if two_step:
249
+ # 约定使用0作为条数查询
250
+ params['total_num'] = 0
251
+ init_result = get_qdata(method, **params)
252
+ if str(init_result.get('result_code', '0')) != '0':
253
+ raise Exception('异常代码[%s], 异常信息[%s]' % (init_result['result_code'], init_result['result_msg']))
254
+ if init_result['data'] is not None:
255
+ init_count = len(init_result['data'])
256
+ df_dic[0] = init_result['data']
257
+ total_pages = (init_result['total_num'] + params['page_size'] - 1 - init_count) // params['page_size']
258
+ # 只可能是1或0,表示初次查询是否返回值,否则有问题
259
+ init_page = init_count // params['page_size']
260
+
261
+ if two_step and total_pages > init_page:
262
+ _parts = []
263
+ for i in range(init_page + 1, total_pages + 1):
264
+ params['total_num'] = init_result['total_num']
265
+ params['page_num'] = i
266
+ _part = EXECUTOR_INNER.submit(get_qdata, method=method, **params)
267
+ _parts.append(_part)
268
+
269
+ for _p in as_completed(_parts):
270
+ try:
271
+ if _p is None:
272
+ continue
273
+ elif _p.result() is None:
274
+ continue
275
+
276
+ result_data = _p.result()
277
+
278
+ df_part = result_data['data']
279
+ if len(df_part) > 0:
280
+ df_part.index = df_part.index + (result_data['page_num'] - 1) * params['page_size']
281
+ df_dic[result_data['page_num']] = df_part
282
+
283
+ except Exception as ex:
284
+ raise Exception("并发处理异常,异常函数[%s]" % method)
285
+ df = sort_merge_df_dic(df_dic)
286
+ else:
287
+ # 一步模式请求所有数据
288
+ params['total_num'] = ONE_STEP_NUM
289
+ params['page_size'] = ONE_STEP_NUM
290
+ result = get_qdata(method, **params)
291
+ if str(result.get('result_code', '0')) != '0':
292
+ raise Exception('异常代码[%s], 异常信息[%s]' % (result['result_code'], result['result_msg']))
293
+ df = result['data']
294
+ ret = {'method': method, 'params': params, 'data': df, }
295
+ return ret
296
+
297
+
298
+ def get_exchange_calendar(mkt_code, strt_date='19900101', end_date=None, trdy_flag=None, cols=None, rslt_type=0):
299
+ """
300
+ 获取交易所交易日日历,包括:上海证券交易所,深圳证券交易所等。
301
+
302
+ """
303
+
304
+ int_param = []
305
+ float_param = []
306
+ if cols:
307
+ int_param = list(set([]).intersection(set(convert_fields(cols))))
308
+ float_param = list(set([]).intersection(set(convert_fields(cols))))
309
+ else:
310
+ cols = ['mkt_code', 'busi_date', 'trdy_flag']
311
+
312
+ if mkt_code:
313
+ params = {
314
+ "mkt_code": mkt_code,
315
+ "strt_date": strt_date,
316
+ "end_date": end_date,
317
+ "trdy_flag": trdy_flag,
318
+ "cols": cols,
319
+ "rslt_type": rslt_type,
320
+ "int_param": int_param,
321
+ "float_param": float_param
322
+ }
323
+ return get_data("get_exch_trd_cldr", **params)
324
+ else:
325
+ warnings.warn("函数[get_exchange_calendar]的参数(mkt_code)为必填项")
326
+ return None
327
+
328
+
329
+
330
+
331
+ def _async_get_hf_data(func, scr_num_list, start_time, end_time, columns, code_type):
332
+ """
333
+ 异步获取高频数据
334
+ """
335
+ # future和hkstock默认用的单日起止时间
336
+ day_end_time = '235959'
337
+ day_start_time = '000000'
338
+
339
+ scr_num_list = regroup_scr_nums(scr_num_list, HF_CODE_GROUP)
340
+
341
+ if HF_DAY_DELTA > 0:
342
+ # 获取交易日
343
+ if start_time[: 8] == end_time[: 8]:
344
+ trading_day = [start_time[: 8]]
345
+ else:
346
+ #20240606 新增港股交易日判斷
347
+ if code_type == "hkstock" :
348
+ df = get_exchange_calendar('XHKG', start_time[: 8], end_time[: 8], trdy_flag=1)
349
+ if df is None:
350
+ df = get_exchange_calendar('XHKG', trdy_flag=1).tail(1)
351
+ else:
352
+ df = get_exchange_calendar('XSHG', start_time[: 8], end_time[: 8], trdy_flag=1)
353
+ if df is None:
354
+ df = get_exchange_calendar('XSHG', trdy_flag=1).tail(1)
355
+ trading_day = df['busi_date'].tolist()
356
+
357
+ params_list = []
358
+ _futures = []
359
+ if HF_DAY_DELTA > 0:
360
+ day_delta = HF_DAY_DELTA if HF_DAY_DELTA < len(trading_day) else len(trading_day)
361
+ _start_date_list = trading_day[::day_delta]
362
+ _end_date_list = trading_day[day_delta - 1::day_delta]
363
+ if len(_start_date_list) != len(_end_date_list):
364
+ _end_date_list.append(trading_day[-1])
365
+
366
+ for _idx in range(len(_start_date_list)):
367
+ _start_time = _start_date_list[_idx]
368
+ _end_time = _end_date_list[_idx]
369
+ if _idx == 0 and _start_time == start_time[: 8]:
370
+ _start_time = start_time
371
+ if _idx == len(trading_day) - 1 and _end_time == end_time[: 8]:
372
+ _end_time = end_time
373
+ if len(end_time) == 8:
374
+ end_time = '%s%s' % (end_time, day_end_time)
375
+
376
+ for _s in scr_num_list:
377
+ params = {
378
+ "scr_num": _s,
379
+ "strt_time": _start_time,
380
+ "end_time": _end_time,
381
+ "cols": columns,
382
+ }
383
+ params_list.append(params)
384
+
385
+ else: # if SPILIT_DAY > 0
386
+ _start_time = start_time
387
+ _end_time = end_time
388
+ for _s in scr_num_list:
389
+ params = {
390
+ "scr_num": _s,
391
+ "strt_time": _start_time,
392
+ "end_time": _end_time,
393
+ "cols": columns,
394
+ }
395
+ params_list.append(params)
396
+
397
+ for params in params_list:
398
+ _future = EXECUTOR.submit(func, **params)
399
+ _futures.append(_future)
400
+
401
+ dfs = collect_dfs_parallel(func, _futures)
402
+
403
+ global HF_SORT_COLS
404
+ result = sort_merge_df(dfs, HF_SORT_COLS[code_type])
405
+ return result
406
+
407
+
408
+ def regroup_scr_nums(scr_num_list, group_num):
409
+ scr_num_list.sort() # 否则合并时可能排序顺序不对
410
+ new_list = list([','.join(scr_num_list[i:i+group_num]) for i in range(0, len(scr_num_list), group_num)])
411
+ return new_list
412
+
413
+
414
+ def convert_result_data(df, rslt_type):
415
+ rslt = None
416
+ if df is not None:
417
+ if rslt_type == 0:
418
+ rslt = df
419
+ else:
420
+ rslt = df.values
421
+ return rslt
422
+
423
+ def collect_dfs_parallel(func, _futures):
424
+ dfs = []
425
+ if PARALLEL_MODE in {'Coroutine'}:
426
+ gevent.joinall(_futures)
427
+ for _f in _futures:
428
+ try:
429
+ if _f is None:
430
+ continue
431
+ elif _f.value is None:
432
+ continue
433
+ elif _f.value['data'] is None:
434
+ continue
435
+
436
+ if len(_f.value['data']) > 0:
437
+ dfs.append(_f.value['data'])
438
+ except Exception as ex:
439
+ warn_str = "并发处理异常,异常函数[%s]" % func
440
+ warnings.warn(warn_str)
441
+ # raise Exception("并发处理异常,异常函数[%s]" % func)
442
+ else:
443
+ for _f in as_completed(_futures):
444
+ try:
445
+ if _f is None:
446
+ continue
447
+ elif _f.result() is None:
448
+ continue
449
+ elif _f.result()['data'] is None:
450
+ continue
451
+
452
+ if len(_f.result()['data']) > 0:
453
+ dfs.append(_f.result()['data'])
454
+ except Exception as ex:
455
+ import traceback
456
+ warn_str = "并发处理异常,异常函数[%s],异常信息[%s]" % (func, traceback.format_exc())
457
+ warnings.warn(warn_str)
458
+ return dfs
459
+
460
+ #20240628 add 港股基本信息接口
461
+ def get_security_info(scr_num_list=None, scr_type_list=None, cols=None):
462
+ """
463
+ 获取港股的基本信息
464
+
465
+ """
466
+ int_param = []
467
+ float_param = []
468
+ if cols:
469
+ int_param = list(set(int_param).intersection(set(convert_fields(cols))))
470
+ float_param = list(set(float_param).intersection(set(convert_fields(cols))))
471
+ else:
472
+ cols = [
473
+ 'scr_code',
474
+ 'scr_num',
475
+ 'scr_abbr',
476
+ 'scr_name',
477
+ 'mkt_code',
478
+ 'list_stat',
479
+ 'list_date',
480
+ 'delt_date',
481
+ 'scr_type',
482
+ 'scr_boar_type_code',
483
+ 'ofer_crrc_code',
484
+ 'trd_unit',
485
+ 'isin_num'
486
+ ]
487
+
488
+ params = {
489
+ "scr_num_list": scr_num_list,
490
+ "scr_type_list": scr_type_list,
491
+ "cols": cols,
492
+ "int_param": int_param,
493
+ "float_param": float_param
494
+ }
495
+
496
+ return get_data("get_hkscr_basc_info", **params)
497
+
498
+ #20240628 add 港股复权因子接口
499
+ def get_stk_rstr_fctr(scr_num_list=None, strt_date=None, end_date=None, cols=None):
500
+ """
501
+ 获取港股复权因子信息
502
+
503
+ """
504
+ int_param = []
505
+ float_param = ['accu_rstr_cnst', 'aacu_rstr_fctr', 'aggr_accu_rstr_cnst', 'aggr_accu_rstr_fctr']
506
+ if cols:
507
+ int_param = list(set(int_param).intersection(set(convert_fields(cols))))
508
+ float_param = list(set(float_param).intersection(set(convert_fields(cols))))
509
+ else:
510
+ cols = [
511
+ 'scr_num',
512
+ 'scr_code',
513
+ 'dr_day',
514
+ 'accu_rstr_cnst',
515
+ 'accu_rstr_fctr',
516
+ 'aggr_accu_rstr_cnst',
517
+ 'aggr_accu_rstr_fctr',
518
+ 'aggr_rati_rstr_fctr',
519
+ 'info_mine',
520
+ 'exd_if_susp',
521
+ 'next_resup_date',
522
+ ]
523
+
524
+ params = {
525
+ "scr_num_list": scr_num_list,
526
+ "strt_date": strt_date,
527
+ "end_date": end_date,
528
+ "cols": cols,
529
+ "int_param": int_param,
530
+ "float_param": float_param
531
+ }
532
+
533
+ return get_data("get_hkstk_rstr_fctr", **params)
534
+
535
+
536
+
537
+
538
+ #20240606 新增港股查询api
539
+ @wait_until_bind()
540
+ def get_deal(scr_num_list, strt_time, end_time, cols=None, rslt_type=0):
541
+ """
542
+ 获取港股逐笔成交数据
543
+ """
544
+
545
+ if scr_num_list is None or strt_time is None or end_time is None:
546
+ warnings.warn("函数[hk.get_deal]的参数(scr_num_list, strt_time, end_time)为必填项")
547
+ return None
548
+
549
+ if isinstance(scr_num_list, str):
550
+ scr_num_list = scr_num_list.split(',')
551
+
552
+ stk_list = []
553
+
554
+ global CODE_SECURITY_MAP
555
+ if len(CODE_SECURITY_MAP) == 0:
556
+ load_basic_info()
557
+
558
+ scr_code_list = \
559
+ [
560
+ _code for _code in scr_num_list if _code.split('.')[1] in ['XHKG']
561
+ ]
562
+
563
+
564
+ noexist_code_list = set(scr_code_list) - set(list(CODE_SECURITY_MAP.keys()))
565
+ if len(noexist_code_list):
566
+ warnings.warn("函数[scr_num_list]中指定代码不存在[%s]" % ''.join(noexist_code_list))
567
+ return None
568
+
569
+
570
+
571
+ try:
572
+
573
+ stk_list = [_scr.replace('.XHKG', '.HK') for _scr in scr_num_list]
574
+
575
+ except IndexError:
576
+ raise Exception('scr_num_list输入格式异常[%s]' % scr_num_list)
577
+
578
+ stk_result = None
579
+ if len(stk_list) > 0:
580
+ stk_result = _async_get_hf_data(
581
+ get_hkstock_deal, stk_list, strt_time, end_time,
582
+ cols, 'hkstock'
583
+ )
584
+
585
+ final_result = None
586
+ concat_list = []
587
+ if stk_result is not None:
588
+ concat_list.append(stk_result)
589
+
590
+ if len(concat_list) > 1:
591
+ final_result = pd.concat(
592
+ concat_list,
593
+ axis=0, sort=False, ignore_index=True
594
+ )
595
+ elif len(concat_list) == 1:
596
+ final_result = concat_list[0]
597
+
598
+ final_result = convert_result_data(final_result, rslt_type)
599
+
600
+ return final_result
601
+
602
+ def get_hkstock_deal(scr_num, strt_time=None, end_time=None, cols=None, rslt_type=0):
603
+ """
604
+ 批量获取港股逐笔成交数据
605
+
606
+ """
607
+
608
+ int_param = [
609
+ 'total_num', 'page_num', 'total_pages',
610
+ 't_id', 'qty'
611
+ ]
612
+ float_param = [
613
+ 'price'
614
+ ]
615
+ if cols:
616
+ fix_cols = \
617
+ ['scr_num', 'date_time']
618
+ if isinstance(cols, str):
619
+ cols = cols.split(',')
620
+ tmp_cols = fix_cols + cols
621
+ cols = list(set(tmp_cols))
622
+ cols.sort(key=tmp_cols.index)
623
+
624
+ int_param = list(set(int_param).intersection(set(cols)))
625
+ float_param = list(set(float_param).intersection(set(cols)))
626
+
627
+ if isinstance(cols, list):
628
+ cols = ','.join(cols)
629
+
630
+ if scr_num:
631
+ params = {
632
+ "scr_num": scr_num,
633
+ "strt_time": strt_time,
634
+ "end_time": end_time,
635
+ "cols": cols,
636
+ "page_num": 1,
637
+ "page_size": CONFIG_PAGE_SIZE,
638
+ "rslt_type": rslt_type,
639
+ "api_type": "quote",
640
+ "int_param": int_param,
641
+ "float_param": float_param
642
+ }
643
+
644
+ # 20220812 统一改为与期货快照一致
645
+ quote_data = quote_single_part(URL_GET_HKSTK_DEAL, params)
646
+ return quote_data
647
+
648
+ else:
649
+ warnings.warn("函数[get_hkstock_deal]的参数(scr_num)为必填项")
650
+ return None
651
+
652
+
653
+ @wait_until_bind()
654
+ def get_tick(scr_num_list, strt_time, end_time, cols=None, rslt_type=0):
655
+ """
656
+ 获取港股快照数据
657
+ """
658
+
659
+ if scr_num_list is None or strt_time is None or end_time is None:
660
+ warnings.warn("函数[get_tick]的参数(scr_num_list, strt_time, end_time)为必填项")
661
+ return None
662
+
663
+ if isinstance(scr_num_list, str):
664
+ scr_num_list = scr_num_list.split(',')
665
+
666
+ stk_list = []
667
+
668
+ global CODE_SECURITY_MAP
669
+ if len(CODE_SECURITY_MAP) == 0:
670
+ load_basic_info()
671
+
672
+ scr_code_list = \
673
+ [
674
+ _code for _code in scr_num_list if _code.split('.')[1] in ['XHKG']
675
+ ]
676
+
677
+
678
+ noexist_code_list = set(scr_code_list) - set(list(CODE_SECURITY_MAP.keys()))
679
+ if len(noexist_code_list):
680
+ warnings.warn("函数[scr_num_list]中指定代码不存在[%s]" % ''.join(noexist_code_list))
681
+ return None
682
+
683
+ try:
684
+ stk_list = [_scr.replace('.XHKG', '.HK') for _scr in scr_num_list]
685
+ except IndexError:
686
+ raise Exception('scr_num_list输入格式异常[%s]' % scr_num_list)
687
+
688
+ stk_result = None
689
+ if len(stk_list) > 0:
690
+ stk_result = _async_get_hf_data(
691
+ get_hkstock_tick, stk_list, strt_time, end_time,
692
+ cols, 'hkstock'
693
+ )
694
+
695
+ final_result = None
696
+ concat_list = []
697
+ if stk_result is not None:
698
+ concat_list.append(stk_result)
699
+
700
+ if len(concat_list) > 1:
701
+ final_result = pd.concat(
702
+ concat_list,
703
+ axis=0, sort=False, ignore_index=True
704
+ )
705
+ elif len(concat_list) == 1:
706
+ final_result = concat_list[0]
707
+
708
+ final_result = convert_result_data(final_result, rslt_type)
709
+
710
+ return final_result
711
+
712
+ def get_hkstock_tick(scr_num, strt_time=None, end_time=None, cols=None, rslt_type=0):
713
+ """
714
+ 批量获取股票代码快照数据
715
+
716
+ """
717
+
718
+ int_param = [
719
+ 'total_num', 'page_num', 'total_pages',
720
+ 'volume', 'shortsell_volume', 'ie_volume', 'noliquidity_providers', 'lpb_no',
721
+ 'bid_size1', 'bid_size2', 'bid_size3', 'bid_size4', 'bid_size5',
722
+ 'bid_size6', 'bid_size7', 'bid_size8', 'bid_size9', 'bid_size10',
723
+ 'bid_order1', 'bid_order2', 'bid_order3', 'bid_order4', 'bid_order5',
724
+ 'bid_order6', 'bid_order7', 'bid_order8', 'bid_order9', 'bid_order10',
725
+ 'offer_size1', 'offer_size2', 'offer_size3', 'offer_size4', 'offer_size5',
726
+ 'offer_size6', 'offer_size7', 'offer_size8', 'offer_size9', 'offer_size10',
727
+ 'offer_order1', 'offer_order2', 'offer_order3', 'offer_order4', 'offer_order5',
728
+ 'offer_order6', 'offer_order7', 'offer_order8', 'offer_order9', 'offer_order10'
729
+ ]
730
+ float_param = [
731
+ 'turnover', 'high_px', 'low_px', 'last_px', 'shortsell_turnover', 'nominal_px', 'close_px',
732
+ 'ie_price', 'refer_price', 'lower', 'upper', 'order_imbal_quantity', 'yield', 'open_px', 'preclose_px',
733
+ 'bid_price1', 'bid_price2', 'bid_price3', 'bid_price4', 'bid_price5',
734
+ 'bid_price6', 'bid_price7', 'bid_price8', 'bid_price9', 'bid_price10',
735
+ 'offer_price1', 'offer_price2', 'offer_price3', 'offer_price4', 'offer_price5',
736
+ 'offer_price6', 'offer_price7', 'offer_price8', 'offer_price9', 'offer_price10'
737
+
738
+ ]
739
+ if cols:
740
+ fix_cols = \
741
+ ['scr_num', 'date_time']
742
+ if isinstance(cols, str):
743
+ cols = cols.split(',')
744
+ tmp_cols = fix_cols + cols
745
+ cols = list(set(tmp_cols))
746
+ cols.sort(key=tmp_cols.index)
747
+
748
+ int_param = list(set(int_param).intersection(set(cols)))
749
+ float_param = list(set(float_param).intersection(set(cols)))
750
+
751
+ if isinstance(cols, list):
752
+ cols = ','.join(cols)
753
+
754
+ if scr_num:
755
+ params = {
756
+ "scr_num": scr_num,
757
+ "strt_time": strt_time,
758
+ "end_time": end_time,
759
+ "cols": cols,
760
+ "page_num": 1,
761
+ "page_size": CONFIG_PAGE_SIZE,
762
+ "rslt_type": rslt_type,
763
+ "api_type": "quote",
764
+ "int_param": int_param,
765
+ "float_param": float_param
766
+ }
767
+
768
+ # 20220812 统一改为与期货快照一致
769
+ quote_data = quote_single_part(URL_GET_HKSTK_TICK, params)
770
+ return quote_data
771
+
772
+ else:
773
+ warnings.warn("函数[get_hkstock_tick]的参数(scr_num)为必填项")
774
+ return None
775
+
776
+ @wait_until_bind()
777
+ def get_oddlot_order(scr_num_list, strt_time, end_time, cols=None, rslt_type=0):
778
+ """
779
+ 获取港股碎股数据
780
+ """
781
+
782
+ if scr_num_list is None or strt_time is None or end_time is None:
783
+ warnings.warn("函数[get_odd]的参数(scr_num_list, strt_time, end_time)为必填项")
784
+ return None
785
+
786
+ if isinstance(scr_num_list, str):
787
+ scr_num_list = scr_num_list.split(',')
788
+
789
+ stk_list = []
790
+
791
+ global CODE_SECURITY_MAP
792
+ if len(CODE_SECURITY_MAP) == 0:
793
+ load_basic_info()
794
+
795
+ scr_code_list = \
796
+ [
797
+ _code for _code in scr_num_list if _code.split('.')[1] in ['XHKG']
798
+ ]
799
+
800
+ noexist_code_list = set(scr_code_list) - set(list(CODE_SECURITY_MAP.keys()))
801
+ if len(noexist_code_list):
802
+ warnings.warn("函数[scr_num_list]中指定代码不存在[%s]" % ''.join(noexist_code_list))
803
+ return None
804
+
805
+ try:
806
+ stk_list = [_scr.replace('.XHKG', '.HK') for _scr in scr_num_list]
807
+
808
+ except IndexError:
809
+ raise Exception('scr_num_list输入格式异常[%s]' % scr_num_list)
810
+
811
+ stk_result = None
812
+ if len(stk_list) > 0:
813
+ stk_result = _async_get_hf_data(
814
+ get_hkstock_odd, stk_list, strt_time, end_time,
815
+ cols, 'hkstock'
816
+ )
817
+
818
+ final_result = None
819
+ concat_list = []
820
+ if stk_result is not None:
821
+ concat_list.append(stk_result)
822
+
823
+ if len(concat_list) > 1:
824
+ final_result = pd.concat(
825
+ concat_list,
826
+ axis=0, sort=False, ignore_index=True
827
+ )
828
+ elif len(concat_list) == 1:
829
+ final_result = concat_list[0]
830
+
831
+ final_result = convert_result_data(final_result, rslt_type)
832
+
833
+ return final_result
834
+
835
+ def get_hkstock_odd(scr_num, strt_time=None, end_time=None, cols=None, rslt_type=0):
836
+ """
837
+ 批量获取港股碎股数据
838
+
839
+ """
840
+
841
+ int_param = [
842
+ 'total_num', 'page_num', 'total_pages',
843
+ 'order_id', 'qty', 'broker_id'
844
+ ]
845
+ float_param = [
846
+ 'price'
847
+ ]
848
+ if cols:
849
+ fix_cols = \
850
+ ['scr_num', 'date_time']
851
+ if isinstance(cols, str):
852
+ cols = cols.split(',')
853
+ tmp_cols = fix_cols + cols
854
+ cols = list(set(tmp_cols))
855
+ cols.sort(key=tmp_cols.index)
856
+
857
+ int_param = list(set(int_param).intersection(set(cols)))
858
+ float_param = list(set(float_param).intersection(set(cols)))
859
+
860
+ if isinstance(cols, list):
861
+ cols = ','.join(cols)
862
+
863
+ if scr_num:
864
+ params = {
865
+ "scr_num": scr_num,
866
+ "strt_time": strt_time,
867
+ "end_time": end_time,
868
+ "cols": cols,
869
+ "page_num": 1,
870
+ "page_size": CONFIG_PAGE_SIZE,
871
+ "rslt_type": rslt_type,
872
+ "api_type": "quote",
873
+ "int_param": int_param,
874
+ "float_param": float_param
875
+ }
876
+
877
+ # 20220812 统一改为与期货快照一致
878
+ quote_data = quote_single_part(URL_GET_HKSTK_ODD, params)
879
+ return quote_data
880
+
881
+ else:
882
+ warnings.warn("函数[get_hkstock_odd]的参数(scr_num)为必填项")
883
+ return None
884
+
885
+ @wait_until_bind()
886
+ def get_broker_queue(scr_num_list, strt_time, end_time, cols=None, rslt_type=0):
887
+ """
888
+ 获取港股经纪商队列数据
889
+ """
890
+
891
+ if scr_num_list is None or strt_time is None or end_time is None:
892
+ warnings.warn("函数[get_broker]的参数(scr_num_list, strt_time, end_time)为必填项")
893
+ return None
894
+
895
+ if isinstance(scr_num_list, str):
896
+ scr_num_list = scr_num_list.split(',')
897
+
898
+ stk_list = []
899
+
900
+ global CODE_SECURITY_MAP
901
+ if len(CODE_SECURITY_MAP) == 0:
902
+ load_basic_info()
903
+
904
+ scr_code_list = \
905
+ [
906
+ _code for _code in scr_num_list if _code.split('.')[1] in ['XHKG']
907
+ ]
908
+
909
+
910
+ noexist_code_list = set(scr_code_list) - set(list(CODE_SECURITY_MAP.keys()))
911
+ if len(noexist_code_list):
912
+ warnings.warn("函数[scr_num_list]中指定代码不存在[%s]" % ''.join(noexist_code_list))
913
+ return None
914
+
915
+ try:
916
+
917
+ stk_list = [_scr.replace('.XHKG', '.HK') for _scr in scr_num_list]
918
+
919
+ except IndexError:
920
+ raise Exception('scr_num_list输入格式异常[%s]' % scr_num_list)
921
+
922
+ stk_result = None
923
+ if len(stk_list) > 0:
924
+ stk_result = _async_get_hf_data(
925
+ get_hkstock_broker, stk_list, strt_time, end_time,
926
+ cols, 'hkstock'
927
+ )
928
+
929
+ final_result = None
930
+ concat_list = []
931
+ if stk_result is not None:
932
+ concat_list.append(stk_result)
933
+
934
+ if len(concat_list) > 1:
935
+ final_result = pd.concat(
936
+ concat_list,
937
+ axis=0, sort=False, ignore_index=True
938
+ )
939
+ elif len(concat_list) == 1:
940
+ final_result = concat_list[0]
941
+
942
+ final_result = convert_result_data(final_result, rslt_type)
943
+
944
+ return final_result
945
+
946
+ def get_hkstock_broker(scr_num, strt_time=None, end_time=None, cols=None, rslt_type=0):
947
+ """
948
+ 批量获取港股经纪商队列数据
949
+
950
+ """
951
+
952
+ int_param = [
953
+ 'total_num', 'page_num', 'total_pages',
954
+ 'side', 'count'
955
+ ]
956
+ float_param = []
957
+ if cols:
958
+ fix_cols = \
959
+ ['scr_num', 'date_time']
960
+ if isinstance(cols, str):
961
+ cols = cols.split(',')
962
+ tmp_cols = fix_cols + cols
963
+ cols = list(set(tmp_cols))
964
+ cols.sort(key=tmp_cols.index)
965
+
966
+ int_param = list(set(int_param).intersection(set(cols)))
967
+ float_param = list(set(float_param).intersection(set(cols)))
968
+
969
+ if isinstance(cols, list):
970
+ cols = ','.join(cols)
971
+
972
+ if scr_num:
973
+ params = {
974
+ "scr_num": scr_num,
975
+ "strt_time": strt_time,
976
+ "end_time": end_time,
977
+ "cols": cols,
978
+ "page_num": 1,
979
+ "page_size": CONFIG_PAGE_SIZE,
980
+ "rslt_type": rslt_type,
981
+ "api_type": "quote",
982
+ "int_param": int_param,
983
+ "float_param": float_param
984
+ }
985
+
986
+ # 20220812 统一改为与期货快照一致
987
+ quote_data = quote_single_part(URL_GET_HKSTK_BROKER, params)
988
+ return quote_data
989
+
990
+ else:
991
+ warnings.warn("函数[get_hkstock_broker]的参数(scr_num)为必填项")
992
+ return None
993
+
994
+ @wait_until_bind()
995
+ def get_index_tick(scr_num_list, strt_time, end_time, cols=None, rslt_type=0):
996
+ """
997
+ 获取港股指数快照数据
998
+ """
999
+
1000
+ if scr_num_list is None or strt_time is None or end_time is None:
1001
+ warnings.warn("函数[get_index_tick]的参数(scr_num_list, strt_time, end_time)为必填项")
1002
+ return None
1003
+
1004
+ if isinstance(scr_num_list, str):
1005
+ scr_num_list = scr_num_list.split(',')
1006
+
1007
+ stk_list = []
1008
+
1009
+ global CODE_SECURITY_MAP
1010
+ if len(CODE_SECURITY_MAP) == 0:
1011
+ load_basic_info()
1012
+
1013
+ scr_code_list = \
1014
+ [
1015
+ _code for _code in scr_num_list if _code.split('.')[1] in ['XHKG']
1016
+ ]
1017
+
1018
+ # 等待港股代码服务完成后开放
1019
+ # noexist_code_list = set(scr_code_list) - set(list(CODE_SECURITY_MAP.keys()))
1020
+ # if len(noexist_code_list):
1021
+ # warnings.warn("函数[scr_num_list]中指定代码不存在[%s]" % ''.join(noexist_code_list))
1022
+ # return None
1023
+
1024
+ try:
1025
+
1026
+ stk_list = [_scr.replace('.XHKG', '.HK') for _scr in scr_num_list]
1027
+
1028
+ except IndexError:
1029
+ raise Exception('scr_num_list输入格式异常[%s]' % scr_num_list)
1030
+
1031
+ stk_result = None
1032
+ if len(stk_list) > 0:
1033
+ stk_result = _async_get_hf_data(
1034
+ get_hkstock_index, stk_list, strt_time, end_time,
1035
+ cols, 'hkstock'
1036
+ )
1037
+
1038
+ final_result = None
1039
+ concat_list = []
1040
+ if stk_result is not None:
1041
+ concat_list.append(stk_result)
1042
+
1043
+ if len(concat_list) > 1:
1044
+ final_result = pd.concat(
1045
+ concat_list,
1046
+ axis=0, sort=False, ignore_index=True
1047
+ )
1048
+ elif len(concat_list) == 1:
1049
+ final_result = concat_list[0]
1050
+
1051
+ #因index原始数据存在重复,对index_tick返回数据进行去重
1052
+
1053
+ if final_result is not None:
1054
+ final_result.drop_duplicates(subset=['scr_num', 'date_time'], keep='first', inplace=True)
1055
+ final_result = convert_result_data(final_result, rslt_type)
1056
+
1057
+ return final_result
1058
+
1059
+ def get_hkstock_index(scr_num, strt_time=None, end_time=None, cols=None, rslt_type=0):
1060
+ """
1061
+ 批量获取港股指数快照数据
1062
+
1063
+ """
1064
+
1065
+ int_param = [
1066
+ 'total_num', 'page_num', 'total_pages',
1067
+ 'index_volume'
1068
+ ]
1069
+ float_param = [
1070
+ 'index_value', 'netchg_prevday', 'high', 'low', 'eas_value', 'index_turnover',
1071
+ 'open', 'close', 'prevses_close', 'netchg_prevday_pct'
1072
+ ]
1073
+ if cols:
1074
+ fix_cols = \
1075
+ ['scr_num', 'date_time']
1076
+ if isinstance(cols, str):
1077
+ cols = cols.split(',')
1078
+ tmp_cols = fix_cols + cols
1079
+ cols = list(set(tmp_cols))
1080
+ cols.sort(key=tmp_cols.index)
1081
+
1082
+ int_param = list(set(int_param).intersection(set(cols)))
1083
+ float_param = list(set(float_param).intersection(set(cols)))
1084
+
1085
+ if isinstance(cols, list):
1086
+ cols = ','.join(cols)
1087
+
1088
+ if scr_num:
1089
+ params = {
1090
+ "scr_num": scr_num,
1091
+ "strt_time": strt_time,
1092
+ "end_time": end_time,
1093
+ "cols": cols,
1094
+ "page_num": 1,
1095
+ "page_size": CONFIG_PAGE_SIZE,
1096
+ "rslt_type": rslt_type,
1097
+ "api_type": "quote",
1098
+ "int_param": int_param,
1099
+ "float_param": float_param
1100
+ }
1101
+
1102
+ # 20220812 统一改为与期货快照一致
1103
+ quote_data = quote_single_part(URL_GET_HKSTK_INDEX, params)
1104
+ return quote_data
1105
+
1106
+ else:
1107
+ warnings.warn("函数[get_hkstock_index]的参数(scr_num)为必填项")
1108
+ return None
1109
+
1110
+