-
Notifications
You must be signed in to change notification settings - Fork 650
/
api.py
4155 lines (3455 loc) · 212 KB
/
api.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
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
天勤接口的PYTHON封装, 提供以下功能
* 连接行情和交易服务器, 接收行情及交易推送数据
* 在内存中存储管理一份完整的业务数据(行情+交易), 并在接收到新数据包时更新内存数据
* 通过一批函数接口, 支持用户代码访问业务数据
* 发送交易指令
* 提供本地的模拟交易账户,同时完成撮合成交
* 支持回测功能
* PYTHON SDK使用文档: https://doc.shinnytech.com/pysdk/latest/
* 天勤vscode插件使用文档: https://doc.shinnytech.com/pysdk/latest/devtools/vscode.html
* 天勤用户论坛: https://www.shinnytech.com/qa/
"""
__author__ = 'chengzhi'
import asyncio
import copy
import logging
import lzma
import json
import os
import platform
import re
import sys
import time
import warnings
from datetime import datetime, date, timedelta
from typing import Union, List, Any, Optional, Coroutine, Callable, Tuple, Dict
from asyncio.events import _get_running_loop, _set_running_loop
import numpy as np
import psutil
from sgqlc.operation import Operation
from shinny_structlog import ShinnyLoggerAdapter, JSONFormatter
import pandas as pd
import requests
from pandas import RangeIndex, Index
from pandas._libs.internals import BlockPlacement
if tuple(map(int, pd.__version__.split("."))) < (1, 3, 0):
from pandas.core.internals import FloatBlock
elif tuple(map(int, pd.__version__.split("."))) < (2, 1, 0):
from pandas.core.internals import NumericBlock as FloatBlock
else:
from pandas.core.internals.blocks import NumpyBlock as FloatBlock
from tqsdk.auth import TqAuth
from tqsdk.baseApi import TqBaseApi
from tqsdk.multiaccount import TqMultiAccount
from tqsdk.backtest import TqBacktest, TqReplay
from tqsdk.channel import TqChan
from tqsdk.connect import TqConnect, MdReconnectHandler, ReconnectTimer
from tqsdk.calendar import _get_trading_calendar, TqContCalendar, _init_chinese_rest_days
from tqsdk.constants import FUTURE_EXCHANGES
from tqsdk.data_extension import DataExtension
from tqsdk.data_series import DataSeries
from tqsdk.datetime import _get_trading_day_from_timestamp, _datetime_to_timestamp_nano, _timestamp_nano_to_datetime, \
_cst_now, _convert_user_input_to_nano
from tqsdk.diff import _merge_diff, _get_obj, _is_key_exist, _register_update_chan
from tqsdk.entity import Entity
from tqsdk.exceptions import TqTimeoutError
from tqsdk.log import _clear_logs, _get_log_name, _get_disk_free
from tqsdk.objs import Quote, TradingStatus, Kline, Tick, Account, Position, Order, Trade, QuotesEntity, RiskManagementRule, RiskManagementData
from tqsdk.objs import SecurityAccount, SecurityOrder, SecurityTrade, SecurityPosition
from tqsdk.objs_not_entity import QuoteList, TqDataFrame, TqSymbolDataFrame, SymbolList, SymbolLevelList, \
TqSymbolRankingDataFrame, TqOptionGreeksDataFrame, TqMdSettlementDataFrame
from tqsdk.risk_manager import TqRiskManager
from tqsdk.risk_rule import TqRiskRule
from tqsdk.ins_schema import ins_schema, basic, derivative, future, option
from tqsdk.symbols import TqSymbols
from tqsdk.tradeable import TqAccount, TqZq, TqKq, TqKqStock, TqSim, TqSimStock, BaseSim, BaseOtg, TqCtp, TqRohon, TqJees, TqYida
from tqsdk.trading_status import TqTradingStatus
from tqsdk.tqwebhelper import TqWebHelper
from tqsdk.utils import _generate_uuid, _query_for_quote, BlockManagerUnconsolidated, _quotes_add_night, _bisect_value, \
deprecated_chart_id
from tqsdk.utils_symbols import _symbols_to_quotes
from tqsdk.tafunc import get_dividend_df, get_dividend_factor
from .__version__ import __version__
UnionTradeable = Union[TqAccount, TqKq, TqZq, TqKqStock, TqSim, TqSimStock, TqCtp, TqRohon, TqJees, TqYida]
class TqApi(TqBaseApi):
"""
天勤接口及数据管理类
通常情况下, 一个线程中 **应该只有一个** TqApi的实例, 它负责维护网络连接, 接收行情及账户数据, 并在内存中维护业务数据截面
"""
def __init__(self, account: Optional[Union[TqMultiAccount, UnionTradeable]] = None,
auth: Union[TqAuth, str, None] = None,
url: Optional[str] = None, backtest: Union[TqBacktest, TqReplay, None] = None,
web_gui: Union[bool, str] = False, debug: Union[bool, str, None] = None,
loop: Optional[asyncio.AbstractEventLoop] = None, disable_print: bool = False, _stock: bool = True,
_ins_url=None, _md_url=None, _td_url=None) -> None:
"""
创建天勤接口实例
Args:
account (None/TqAccount/TqKq/TqKqStock/TqSim/TqSimStock/TqZq/TqMultiAccount): [可选]交易账号:
* None: 账号将根据环境变量决定, 默认为 :py:class:`~tqsdk.TqSim`
* :py:class:`~tqsdk.TqAccount` : 使用实盘账号, 直连行情和交易服务器, 需提供期货公司/帐号/密码
* :py:class:`~tqsdk.TqKq` : 使用快期账号登录,直连行情和快期模拟交易服务器
* :py:class:`~tqsdk.TqKqStock` : 使用快期账号登录,直连行情和快期股票模拟交易服务器
* :py:class:`~tqsdk.TqSim` : 使用 TqApi 自带的内部模拟账号
* :py:class:`~tqsdk.TqSimStock` : 使用 TqApi 自带的内部股票模拟账号
* :py:class:`~tqsdk.TqZq` : 使用众期账号
* :py:class:`~tqsdk.TqCtp` : 使用直连 CTP 账号
* :py:class:`~tqsdk.TqRohon` : 使用融航资管账号
* :py:class:`~tqsdk.TqJees` : 使用杰宜斯资管账号
* :py:class:`~tqsdk.TqYida` : 使用易达账号
* :py:class:`~tqsdk.TqMultiAccount` : 多账户列表,列表中支持 :py:class:`~tqsdk.TqAccount`、:py:class:`~tqsdk.TqKq`、:py:class:`~tqsdk.TqKqStock`、\
:py:class:`~tqsdk.TqSim`、:py:class:`~tqsdk.TqSimStock`、:py:class:`~tqsdk.TqZq`、:py:class:`~tqsdk.TqRohon`、:py:class:`~tqsdk.TqJees`、\
:py:class:`~tqsdk.TqYida` 和 :py:class:`~tqsdk.TqCtp` 中的 0 至 N 个或者组合
auth (TqAuth/str): [必填]用户快期账户:
* :py:class:`~tqsdk.TqAuth` : 添加快期账户类,例如:TqAuth("[email protected]", "123456")
* str: 用户权限认证对象为天勤用户论坛的邮箱和密码,中间以英文逗号分隔,例如: "[email protected],123456"\
快期账户注册链接 https://www.shinnytech.com/register-intro/
url (str): [可选]指定服务器的地址
* 当 account 为 :py:class:`~tqsdk.TqAccount`、:py:class:`~tqsdk.TqKq`、:py:class:`~tqsdk.TqKqStock` 类型时, 可以通过该参数指定交易服务器地址,\
默认使用对应账户的交易服务地址,行情地址使用快期账户对应的行情服务地址
* 当 account 为 :py:class:`~tqsdk.TqSim`、:py:class:`~tqsdk.TqSimStock` 类型时, 可以通过该参数指定行情服务器地址, 默认使用快期账户对应的行情服务地址
backtest (TqBacktest): [可选] 进入时光机,此时强制要求 account 类型为 :py:class:`~tqsdk.TqSim` 或 :py:class:`~tqsdk.TqSimStock`
* :py:class:`~tqsdk.TqBacktest` : 传入 TqBacktest 对象,进入回测模式 \
在回测模式下, TqBacktest 连接 wss://backtest.shinnytech.com/t/md/front/mobile 接收行情数据, \
由 TqBacktest 内部完成回测时间段内的行情推进和 K 线、Tick 更新.
debug(bool/str): [可选] 是否将调试信息输出到指定文件,默认值为 None。
* None [默认]: 根据账户情况不同,默认值的行为不同。
+ 仅有本地模拟账户 :py:class:`~tqsdk.TqSim`、:py:class:`~tqsdk.TqSimStock` 时,调试信息不输出。
+ 当有其他类型账户时,即 :py:class:`~tqsdk.TqAccount`、:py:class:`~tqsdk.TqKq`、:py:class:`~tqsdk.TqKqStock`,\
调试信息输出到指定文件夹 `~/.tqsdk/logs`(如果磁盘剩余空间不足 10G 则不会输出调试信息)。
* True: 调试信息会输出到指定文件夹 `~/.tqsdk/logs`。
* False: 不输出调试信息。
* str: 指定一个日志文件名, 调试信息输出到指定文件。
disable_print(bool): [可选] 是否隐藏提示信息,默认值为 False
* False [默认]: 打印提示信息。
* True: 隐藏提示信息。
+ 提示信息会输出到标准输出流,即 sys.stdout,标准输出流默认指向控制台。
loop(asyncio.AbstractEventLoop): [可选] 使用指定的 IOLoop, 默认创建一个新的.
web_gui(bool/str): [可选]是否启用图形化界面功能, 默认不启用.
* 启用图形化界面传入参数 web_gui=True 会每次以随机端口生成网页,也可以直接设置本机IP和端口 web_gui=[ip]:port 为网页地址,\
ip 可选,默认为 0.0.0.0,参考example 6
* 为了图形化界面能够接收到程序传输的数据并且刷新,在程序中,需要循环调用 api.wait_update的形式去更新和获取数据
* 推荐打开图形化界面的浏览器为Google Chrome 或 Firefox
Example1::
# 使用实盘帐号直连行情和交易服务器
from tqsdk import TqApi, TqAuth, TqAccount
api = TqApi(TqAccount("H海通期货", "022631", "123456"), auth=TqAuth("快期账户", "账户密码"))
Example2::
# 使用快期模拟帐号连接行情服务器
from tqsdk import TqApi, TqAuth, TqKq
api = TqApi(TqKq(), auth=TqAuth("快期账户", "账户密码")) # 根据填写的快期账户参数连接指定的快期模拟账户
Example3::
# 使用模拟帐号直连行情服务器
from tqsdk import TqApi, TqAuth, TqSim
api = TqApi(TqSim(), auth=TqAuth("快期账户", "账户密码")) # 不填写参数则默认为 TqSim() 模拟账号
Example4::
# 进行策略回测
from datetime import date
from tqsdk import TqApi, TqAuth, TqBacktest
api = TqApi(backtest=TqBacktest(start_dt=date(2018, 5, 1), end_dt=date(2018, 10, 1)), auth=TqAuth("快期账户", "账户密码"))
Example5::
# 进行策略复盘
from datetime import date
from tqsdk import TqApi, TqAuth, TqReplay
api = TqApi(backtest=TqReplay(replay_dt=date(2019, 12, 16)), auth=TqAuth("快期账户", "账户密码"))
Example6::
# 开启 web_gui 功能,使用默认参数True
from tqsdk import TqApi, TqAuth
api = TqApi(web_gui=True, auth=TqAuth("快期账户", "账户密码"))
Example7::
# 开启 web_gui 功能,使用本机IP端口固定网址生成
from tqsdk import TqApi, TqAuth
api = TqApi(web_gui=":9876", auth=TqAuth("快期账户", "账户密码")) # 等价于 api = TqApi(web_gui="0.0.0.0:9876", auth=TqAuth("快期账户", "账户密码"))
"""
# 初始化 logger
self._logger = logging.getLogger("TqApi")
self._logger.setLevel(logging.DEBUG)
self.disable_print = disable_print
# 创建一个新的 ioloop, 避免和其他框架/环境产生干扰
super(TqApi, self).__init__(loop=loop)
# 记录参数
self._debug = debug # 日志选项
if isinstance(auth, TqAuth):
self._auth = auth
elif isinstance(auth, str):
comma_index = auth.find(',')
if comma_index == -1:
raise Exception(f"不能正确解析 auth=\"{auth}\", 请填写正确的 auth 参数,以英文逗号分隔用户名和密码,例如:\"[email protected],123456\"。")
user_name, pwd = auth[:comma_index], auth[comma_index + 1:]
self._auth = TqAuth(user_name, pwd)
else:
self._auth = None
self._account = TqSim() if account is None else account
self._backtest = backtest
self._stock = False if isinstance(self._backtest, TqReplay) else _stock
self._ins_url = os.getenv("TQ_INS_URL", "https://openmd.shinnytech.com/t/md/symbols/latest.json")
self._md_url = os.getenv("TQ_MD_URL", None)
self._td_url = os.getenv("TQ_TD_URL", None)
if url and isinstance(self._account, BaseSim):
self._md_url = url
elif url and isinstance(self._account, BaseOtg):
self._td_url = url
elif url:
raise Exception("交易服务器地址需在创建账户实例时单独指定")
if _ins_url:
self._ins_url = _ins_url
if _md_url:
self._md_url = _md_url
if _td_url:
self._td_url = _td_url
# 内部关键数据
self._risk_manager = TqRiskManager()
self._requests = {
"trading_status": set(),
"quotes": set(),
"klines": {},
"ticks": {},
} # 记录已发出的请求
self._serials = {} # 记录所有数据序列
# 记录所有(若有多个serial 则仅data_length不同, right_id相同)合约、周期相同的多合约K线中最大的更新数据范围
# key:(主合约,(所有副合约),duration), value:(K线的新数据中主合约最小的id, 主合约的right_id)。用于is_changing()中新K线生成的判定
self._klines_update_range = {}
self._data = Entity() # 数据存储
self._data._instance_entity([])
self._data["quotes"] = QuotesEntity(self)
self._data["quotes"]._instance_entity(["quotes"])
self._diffs = [] # 自上次wait_update返回后收到更新数据的数组 (异步代码)
self._sync_diffs = [] # 自上次wait_update返回后收到更新数据的数组 (同步代码)
self._pending_diffs = [] # 从网络上收到的待处理的 diffs, 只在 wait_update 函数执行过程中才可能为非空
self._pending_peek = False # 是否有发出的 peek_message 还没收到数据回复
self._prototype = self._gen_prototype() # 各业务数据的原型, 用于决定默认值及将收到的数据转为特定的类型
self._security_prototype = self._gen_security_prototype() # 股票业务数据原型
self._dividend_cache = {} # 缓存合约对应的复权系数矩阵,每个合约只计算一次
self._send_chan, self._recv_chan = TqChan(self), TqChan(self) # 消息收发队列
self._ws_md_recv_chan = None # 记录 ws_md_recv_chan 引用
# slave模式的api不需要完整初始化流程
self._is_slave = isinstance(account, TqApi)
self._slaves = []
if self._is_slave:
self._master = account
if self._master._is_slave:
raise Exception("不可以为slave再创建slave")
self._master._slaves.append(self)
self._account = self._master._account
warnings.warn("TqSdk 暂不支持在多线程下使用")
self._web_gui = False # 如果是slave, _web_gui 一定是 False
return # 注: 如果是slave,则初始化到这里结束并返回,以下代码不执行
self._web_gui = web_gui
# 初始化
self.create_task(self._notify_watcher()) # 监控服务器发送的通知
self._reconnect_timer = ReconnectTimer() # 管理 ws 连接重连时间
self._setup_connection() # 初始化通讯连接
# 等待初始化完成
deadline = time.time() + 60
try:
# 多账户时,所有账户需要初始化完成
trade_more_data = True
while self._data.get("mdhis_more_data", True) or trade_more_data:
if not self.wait_update(deadline=deadline): # 等待连接成功并收取截面数据
raise TqTimeoutError("接收数据超时,请检查客户端及网络是否正常")
trade_more_data = self._account._get_trade_more_data(self._data)
except:
self.close()
raise
# 使用空 list, 使得 is_changing() 返回 false, 因为截面数据不算做更新数据
self._diffs = []
self._sync_diffs = []
def _print(self, msg: str = "", level: str = "INFO"):
if self.disable_print:
return
dt = "" if self._backtest else _cst_now().strftime('%Y-%m-%d %H:%M:%S')
level = level if isinstance(level, str) else logging.getLevelName(level)
print(f"{(dt + ' - ') if dt else ''}{level:>8} - {msg}")
@property
def _base_headers(self):
return self._auth._base_headers
# ----------------------------------------------------------------------
def copy(self) -> 'TqApi':
"""
创建当前TqApi的一个副本. 这个副本可以在另一个线程中使用
Returns:
:py:class:`~tqsdk.api.TqApi`: 返回当前TqApi的一个副本. 这个副本可以在另一个线程中使用
"""
slave_api = TqApi(self)
# 将当前api的_data值复制到_copy_diff中, 然后merge到副本api的_data里
_copy_diff = {}
TqApi._deep_copy_dict(self._data, _copy_diff)
slave_api._auth = self._auth
_merge_diff(slave_api._data, _copy_diff, slave_api._prototype, persist=False)
return slave_api
def close(self) -> None:
"""
关闭天勤接口实例并释放相应资源
Example::
# m1901开多3手
from tqsdk import TqApi, TqAuth
from contextlib import closing
with closing(TqApi(auth=TqAuth("快期账户", "账户密码")) as api:
api.insert_order(symbol="DCE.m1901", direction="BUY", offset="OPEN", volume=3)
"""
if self._loop.is_closed():
return
other_loop = None
try:
if self._loop.is_running():
raise Exception("不能在协程中调用 close, 如需关闭 api 实例需在 wait_update 返回后再关闭")
else:
other_loop = _get_running_loop()
if other_loop:
_set_running_loop(None)
# 总会发送 serial_extra_array 数据,由 TqWebHelper 处理
for _, serial in self._serials.items():
self._process_serial_extra_array(serial)
super(TqApi, self)._close()
mem = psutil.virtual_memory()
self._logger.debug("process end", mem_total=mem.total, mem_free=mem.free)
finally:
if other_loop:
_set_running_loop(other_loop)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
# ----------------------------------------------------------------------
def get_quote(self, symbol: str) -> Quote:
"""
获取指定合约的盘口行情.
Args:
symbol (str): 指定合约代码。
Returns:
:py:class:`~tqsdk.objs.Quote`: 返回一个盘口行情引用. 其内容将在 :py:meth:`~tqsdk.api.TqApi.wait_update` 时更新.
注意:
1. 在 tqsdk 还没有收到行情数据包时, 此对象中各项内容为 NaN 或 0
2. 天勤接口从0.8版本开始,合约代码格式变更为 交易所代码.合约代码 的格式. 可用的交易所代码如下:
* CFFEX: 中金所
* SHFE: 上期所
* DCE: 大商所
* CZCE: 郑商所
* INE: 能源交易所(原油)
* SSE: 上交所
* SZSE: 深交所
* GFEX: 广期所
Example1::
# 获取 SHFE.cu1812 合约的报价
from tqsdk import TqApi, TqAuth
api = TqApi(auth=TqAuth("快期账户", "账户密码"))
quote = api.get_quote("SHFE.cu1812")
print(quote.last_price)
while api.wait_update():
print(quote.last_price)
# 预计的输出是这样的:
nan
24575.0
24575.0
...
Example2::
# 协程示例,为每个合约创建 task
from tqsdk import TqApi, TqAuth
async def demo(SYMBOL):
quote = await api.get_quote(SYMBOL) # 支持 await 异步,这里会订阅合约,等到收到合约行情才返回
print(f"quote: {SYMBOL}", quote.datetime, quote.last_price) # 这一行就会打印出合约的最新行情
async with api.register_update_notify() as update_chan:
async for _ in update_chan:
if api.is_changing(quote):
print(SYMBOL, quote.datetime, quote.last_price)
# ... 策略代码 ...
api = TqApi(auth=TqAuth("快期账户", "账户密码"))
symbol_list = ["SHFE.rb2107", "DCE.m2109"] # 设置合约代码
for symbol in symbol_list:
api.create_task(demo(symbol)) # 为每个合约创建异步任务
while True:
api.wait_update()
"""
if symbol == "":
raise Exception(f"get_quote 中请求合约代码不能为空字符串")
return self.get_quote_list([symbol])[0]
# ----------------------------------------------------------------------
def get_quote_list(self, symbols: List[str]) -> QuoteList:
"""
获取指定合约列表的盘口行情.
Args:
symbols (list of str): 合约代码列表
Returns:
list of :py:class:`~tqsdk.objs.Quote`: 返回一个列表,每个元素为指定合约盘口行情引用。
注意:
1. 在 tqsdk 还没有收到行情数据包时, 此对象中各项内容为 NaN 或 0
2. 天勤接口从0.8版本开始,合约代码格式变更为 交易所代码.合约代码 的格式. 可用的交易所代码如下:
* CFFEX: 中金所
* SHFE: 上期所
* DCE: 大商所
* CZCE: 郑商所
* INE: 能源交易所(原油)
* SSE: 上交所
* SZSE: 深交所
* GFEX: 广期所
Example::
# 获取 SHFE.cu1812 合约的报价
from tqsdk import TqApi, TqAuth
api = TqApi(auth=TqAuth("快期账户", "账户密码"))
quote_list = api.get_quote_list(["SHFE.cu2105", "SHFE.cu2112"])
print(quote_list[0].last_price, quote_list[1].last_price)
while api.wait_update():
print(quote_list[0].last_price, quote_list[1].last_price)
# 预计的输出是这样的:
24575.0 24545.0
24575.0 24545.0
...
"""
if any([s == "" for s in symbols]):
raise Exception(f"get_quote_list 中请求合约代码不能为空字符串 {symbols}")
quote_list = QuoteList(self, [_get_obj(self._data, ["quotes", s], self._prototype["quotes"]["#"])
for s in symbols])
if not self._loop.is_running():
deadline = time.time() + 30
if isinstance(self._backtest, TqBacktest):
if len(quote_list) > 100:
raise Exception("get_quote_list 请求合约长度超过限制。回测中最多支持长度为 100。")
deadline = time.time() + 25 + 3 * len(quote_list) # 回测时的行情需要下载 klines,加长超时时间
while not quote_list._task.done():
if not self.wait_update(deadline=deadline, _task=quote_list._task):
raise TqTimeoutError(f"获取 {symbols} 的行情信息超时,请检查客户端及网络是否正常")
return quote_list
def _ensure_symbol(self, symbol: Union[str, List[str]]):
# 已经收到收到合约信息之后返回,同步
all_symbol_list = symbol if isinstance(symbol, list) else [symbol]
# self._data.quotes 下 price_tick 不大于 0,需要发送 ins_query 查询的合约
symbol_list = [symbol for symbol in all_symbol_list if
not self._data.get("quotes", {}).get(symbol, {}).get('price_tick', float('nan')) > 0]
if len(symbol_list) == 0:
return True
if self._stock is False:
raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % (symbol_list))
else:
task = self.create_task(self._ensure_symbol_async(symbol_list), _caller_api=True)
if not self._loop.is_running():
deadline = time.time() + 30
while not task.done():
if not self.wait_update(deadline=deadline, _task=task):
raise TqTimeoutError(f"获取 {symbol} 的合约信息超时,请检查客户端及网络是否正常,且合约代码填写正确")
async def _ensure_symbol_async(self, symbol: Union[str, List[str]]):
# 已经收到收到合约信息之后返回,异步
all_symbol_list = symbol if isinstance(symbol, list) else [symbol]
# self._data.quotes 下 price_tick 不大于 0,需要发送 ins_query 查询的合约
symbol_list = [symbol for symbol in all_symbol_list if
not self._data.get("quotes", {}).get(symbol, {}).get('price_tick', float('nan')) > 0]
if len(symbol_list) == 0:
return True
if self._stock is False:
raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % symbol_list)
else:
query_pack = _query_for_quote(symbol_list)
self._send_pack(query_pack)
async with self.register_update_notify() as update_chan:
async for _ in update_chan:
if all([self._data.get("quotes", {}).get(symbol, {}).get('price_tick', float('nan')) > 0 for symbol in symbol_list]):
break
# ----------------------------------------------------------------------
def get_trading_status(self, symbol: str) -> TradingStatus:
"""
获取指定合约的交易状态. 此接口为 TqSdk 专业版提供,便于实现开盘抢单功能。
如果想使用此功能,可以点击 `天勤量化专业版 <https://www.shinnytech.com/tqsdk-buy/>`_ 申请试用或购买
Args:
symbol (str): 合约代码
Returns:
:py:class:`~tqsdk.objs.TradingStatus`: 返回指定合约交易状态引用。
Example::
# 在集合竞价时下单
from tqsdk import TqApi, TqAuth
api = TqApi(auth=TqAuth("快期账户", "账户密码"))
ts = api.get_trading_status("SHFE.cu2201")
print(ts.trade_status)
while True:
api.wait_update()
if ts.trade_status == "AUCTIONORDERING":
order = api.insert_order("SHFE.cu2201","BUY","OPEN", 1, 71400)
break
# insert_order指令会在下一次wait_update()发出
api.wait_update()
api.close()
"""
if symbol == "":
raise Exception(f"get_trading_status 中合约代码不能为空字符串")
if not self._auth._has_feature('tq_trading_status'):
raise Exception(f"您的账户不支持查看交易状态信息,需要购买后才能使用。升级网址:https://www.shinnytech.com/tqsdk-buy/")
if self._backtest:
raise Exception('回测/复盘不支持查看交易状态信息')
ts = _get_obj(self._data, ['trading_status', symbol], self._prototype["trading_status"]["#"])
ts._task = self.create_task(self._handle_trading_status(symbol, ts), _caller_api=True)
if not self._loop.is_running():
deadline = time.time() + 30
while not ts._task.done():
if not self.wait_update(deadline=deadline, _task=ts._task):
raise TqTimeoutError(f"获取 {symbol} 的合约交易状态信息超时,请检查客户端及网络是否正常")
return ts
async def _handle_trading_status(self, symbol, ts):
if ts.trade_status != "":
return ts
if symbol not in self._requests["trading_status"]:
self._requests["trading_status"].add(symbol)
self._send_pack({
"aid": "subscribe_trading_status",
"ins_list": ",".join(self._requests["trading_status"])
})
async with self.register_update_notify(ts) as update_chan:
async for _ in update_chan:
if ts.trade_status != "":
return ts
@deprecated_chart_id("symbol", "duration_seconds", "data_length", "adj_type")
# ----------------------------------------------------------------------
def get_kline_serial(self, symbol: Union[str, List[str]], duration_seconds: int, data_length: int = 200,
adj_type: Union[str, None] = None, **kwargs) -> pd.DataFrame:
"""
获取k线序列数据
请求指定合约及周期的K线数据. 序列数据会随着时间推进自动更新
Args:
symbol (str/list of str): 指定合约代码或合约代码列表.
* str: 一个合约代码
* list of str: 合约代码列表 (一次提取多个合约的K线并根据相同的时间向第一个合约(主合约)对齐)
duration_seconds (int): K线数据周期, 以秒为单位。例如: 1分钟线为60,1小时线为3600,日线为86400。\
注意: 周期在日线以内时此参数可以任意填写, 在日线以上时只能是日线(86400)的整数倍, 最大为28天 (86400*28)。
data_length (int): 需要获取的序列长度。默认200根, 返回的K线序列数据是从当前最新一根K线开始往回取data_length根。\
每个序列最大支持请求 10000 个数据
adj_type (str/None): [可选]指定复权类型,默认为 None。adj_type 参数只对股票和基金类型合约有效。\
"F" 表示前复权;"B" 表示后复权;None 表示不做处理。
chart_id (str): [Deprecated] 由 api 自动生成,此参数不再使用
**获取后的 kline 对象请不要修改/重命名原始的 kline 序列,可能会导致后续的 kline 数据不更新 **
**注:关于传入合约代码列表 获取多合约K线的说明:**
1. 主合约的字段名为原始K线数据字段,从第一个副合约开始,字段名在原始字段后加数字,如第一个副合约的开盘价为 "open1" , 第二个副合约的收盘价为 "close2"。
2. 每条K线都包含了订阅的所有合约数据,即:如果任意一个合约(无论主、副)在某个时刻没有数据(即使其他合约在此时有数据),\
则不能对齐,此多合约K线在该时刻那条数据被跳过,现象表现为K线不连续(如主合约有夜盘,而副合约无夜盘,则生成的多合约K线无夜盘时间的数据)。
3. 若设置了较大的序列长度参数,而所有可对齐的数据并没有这么多,则序列前面部分数据为NaN(这与获取单合约K线且数据不足序列长度时情况相似)。
4. 若主合约与副合约的交易时间在所有合约数据中最晚一根K线时间开始往回的 10000*周期 时间段内完全不重合,则无法生成多合约K线,程序会报出获取数据超时异常。
5. datetime、duration是所有合约公用的字段,则未单独为每个副合约增加一份副本,这两个字段使用原始字段名(即没有数字后缀)。
6. **暂不支持复权** 获取多合约K线,若填入 adj_type,程序会报参数错误。
Returns:
pandas.DataFrame: 本函数总是返回一个 pandas.DataFrame 实例. 行数=data_length, 包含以下列:
* id: 1234 (k线序列号)
* datetime: 1501080715000000000 (K线起点时间(按北京时间),自unix epoch(1970-01-01 00:00:00 GMT)以来的纳秒数)
* open: 51450.0 (K线起始时刻的最新价)
* high: 51450.0 (K线时间范围内的最高价)
* low: 51450.0 (K线时间范围内的最低价)
* close: 51450.0 (K线结束时刻的最新价)
* volume: 11 (K线时间范围内的成交量)
* open_oi: 27354 (K线起始时刻的持仓量)
* close_oi: 27355 (K线结束时刻的持仓量)
Example1::
# 获取 SHFE.cu1812 的1分钟线
from tqsdk import TqApi, TqAuth
api = TqApi(auth=TqAuth("快期账户", "账户密码"))
klines = api.get_kline_serial("SHFE.cu1812", 60)
print(klines.iloc[-1].close)
while True:
api.wait_update()
print(klines.iloc[-1].close)
# 预计的输出是这样的:
50970.0
50970.0
50960.0
...
Example2::
# 获取按时间对齐的多合约K线
from tqsdk import TqApi, TqAuth
api = TqApi(auth=TqAuth("快期账户", "账户密码"))
# 获取 CFFEX.IF1912 按照K线时间向 SHFE.au2006 对齐的K线
klines = api.get_kline_serial(["SHFE.au2006", "CFFEX.IF2006"], 5, data_length=10)
print("多合约K线:", klines.iloc[-1])
while True:
api.wait_update()
if api.is_changing(klines.iloc[-1], ["close1", "close"]): # 判断任何一个收盘价是否有更新
dif = klines.close1 - klines.close # 使用对齐的K线直接计算价差等数据
print("价差序列:", dif)
Example3::
# 使用tqsdk自带的时间转换函数, 将最后一根K线的纳秒时间转换为 datetime.datetime 类型
from tqsdk import tafunc
...
klines = api.get_kline_serial("DCE.jd2001", 10)
kline_time = tafunc.time_to_datetime(klines.iloc[-1]["datetime"]) # datetime.datetime 类型值
print(type(kline_time), kline_time)
print(kline_time.year, kline_time.month, kline_time.day, kline_time.hour, kline_time.minute, kline_time.second)
...
"""
if not isinstance(symbol, list):
symbol = [symbol]
if any([s == "" for s in symbol]):
raise Exception("参数错误,合约代码不能为空字符串")
duration_seconds = int(duration_seconds) # 转成整数
if duration_seconds <= 0 or duration_seconds > 86400 and duration_seconds % 86400 != 0:
raise Exception("K线数据周期 %d 错误, 请检查K线数据周期值是否填写正确" % (duration_seconds))
data_length = int(data_length)
if data_length <= 0:
raise Exception("K线数据序列长度 %d 错误, 请检查序列长度是否填写正确" % (data_length))
if adj_type not in [None, "F", "B", "FORWARD", "BACK"]:
raise Exception("adj_type 参数只支持 None (不复权) | 'F' (前复权) | 'B' (后复权)")
adj_type = adj_type[0] if adj_type else adj_type
if adj_type and len(symbol) > 1:
raise Exception("参数错误,多合约 K 线序列不支持复权。")
if data_length > 10000:
data_length = 10000
dur_id = duration_seconds * 1000000000
request = (tuple(symbol), duration_seconds, data_length, adj_type) # request 中 symbols 为 tuple 序列
serial = self._requests["klines"].get(request, None)
pack = {
"aid": "set_chart",
"chart_id": _generate_uuid("PYSDK_realtime"),
"ins_list": ",".join(symbol),
"duration": dur_id,
"view_width": data_length if len(symbol) == 1 else 10000,
# 如果同时订阅了两个以上合约K线,初始化数据时默认获取 1w 根K线(初始化完成后修改指令为设定长度)
} if serial is None else {}
# 将数据权转移给TqChan时其所有权也随之转移,因pack还需要被用到,所以传入副本
task = self.create_task(self._get_serial_async(symbol, pack.copy()), _caller_api=True)
if serial is None:
serial = self._init_serial([_get_obj(self._data, ["klines", s, str(dur_id)]) for s in symbol],
data_length, self._prototype["klines"]["*"]["*"]["data"]["@"], adj_type)
serial["chart"] = _get_obj(self._data, ["charts", pack["chart_id"]]) # 保存chart信息
serial["chart"].update(pack)
self._requests["klines"][request] = serial
self._serials[id(serial["df"])] = serial
# 对于多合约Kline,超时的等待时间应该和需要下载的数据量成正比,根据合约数量判断下载的数据量
deadline = time.time() + 25 + 5 * len(symbol)
while not self._loop.is_running() and not serial["init"]:
if not self.wait_update(deadline=deadline, _task=[task, serial["df"].__dict__["_task"]]):
if len(symbol) > 1:
raise TqTimeoutError("获取 %s (%d) 的K线超时,请检查客户端及网络是否正常,或任一副合约在主合约行情的最后 %d 秒内无可对齐的K线" % (
symbol, duration_seconds, 10000 * duration_seconds))
else:
raise TqTimeoutError("获取 %s (%d) 的K线超时,请检查客户端及网络是否正常" % (symbol, duration_seconds))
return serial["df"]
async def _get_serial_async(self, symbol, pack):
await self._ensure_symbol_async(symbol)
self._auth._has_md_grants(symbol)
# 判断用户是否指定了 chart_id(参数), 如果指定了,则一定会发送新的请求。
if pack:
self._send_pack(pack)
# ----------------------------------------------------------------------
@deprecated_chart_id("symbol", "data_length", "adj_type")
def get_tick_serial(self, symbol: str, data_length: int = 200, adj_type: Union[str, None] = None,
**kwargs) -> pd.DataFrame:
"""
获取tick序列数据
请求指定合约的Tick序列数据. 序列数据会随着时间推进自动更新
Args:
symbol (str): 指定合约代码.
data_length (int): 需要获取的序列长度。每个序列最大支持请求 10000 个数据
adj_type (str/None): [可选]指定复权类型,默认为 None。adj_type 参数只对股票和基金类型合约有效。\
"F" 表示前复权;"B" 表示后复权;None 表示不做处理。
chart_id (str): [Deprecated] 由 api 自动生成,此参数不再使用
Returns:
pandas.DataFrame: 本函数总是返回一个 pandas.DataFrame 实例. 行数=data_length, 包含以下列:
* id: 12345 tick序列号
* datetime: 1501074872000000000 (tick从交易所发出的时间(按北京时间),自unix epoch(1970-01-01 00:00:00 GMT)以来的纳秒数)
* last_price: 3887.0 (最新价)
* average: 3820.0 (当日均价)
* highest: 3897.0 (当日最高价)
* lowest: 3806.0 (当日最低价)
* ask_price1: 3886.0 (卖一价)
* ask_volume1: 3 (卖一量)
* bid_price1: 3881.0 (买一价)
* bid_volume1: 18 (买一量)
* volume: 7823 (当日成交量)
* amount: 19237841.0 (成交额)
* open_interest: 1941 (持仓量)
Example::
# 获取 SHFE.cu1812 的Tick序列
from tqsdk import TqApi, TqAuth
api = TqApi(auth=TqAuth("快期账户", "账户密码"))
serial = api.get_tick_serial("SHFE.cu1812")
while True:
api.wait_update()
print(serial.iloc[-1].bid_price1, serial.iloc[-1].ask_price1)
# 预计的输出是这样的:
50860.0 51580.0
50860.0 51580.0
50820.0 51580.0
...
"""
if symbol == "":
raise Exception("参数错误,合约代码不能为空字符串")
data_length = int(data_length)
if data_length <= 0:
raise Exception("K线数据序列长度 %d 错误, 请检查序列长度是否填写正确" % (data_length))
if adj_type not in [None, "F", "B", "FORWARD", "BACK"]:
raise Exception("adj_type 参数只支持 None (不复权) | 'F' (前复权) | 'B' (后复权)")
adj_type = adj_type[0] if adj_type else adj_type
if data_length > 10000:
data_length = 10000
request = (symbol, data_length, adj_type)
serial = self._requests["ticks"].get(request, None)
pack = {
"aid": "set_chart",
"chart_id": _generate_uuid("PYSDK_realtime"),
"ins_list": symbol,
"duration": 0,
"view_width": data_length,
} if serial is None else {}
# pack 的副本数据和所有权转移给TqChan
task = self.create_task(self._get_serial_async(symbol, pack.copy()), _caller_api=True)
if serial is None:
serial = self._init_serial([_get_obj(self._data, ["ticks", symbol])], data_length,
self._prototype["ticks"]["*"]["data"]["@"], adj_type)
serial["chart"] = _get_obj(self._data, ["charts", pack["chart_id"]])
serial["chart"].update(pack)
self._requests["ticks"][request] = serial
self._serials[id(serial["df"])] = serial
deadline = time.time() + 30
while not self._loop.is_running() and not serial["init"]:
if not self.wait_update(deadline=deadline, _task=[task, serial["df"].__dict__["_task"]]):
raise TqTimeoutError("获取 %s 的Tick超时,请检查客户端及网络是否正常,且合约代码填写正确" % (symbol))
return serial["df"]
# ----------------------------------------------------------------------
def get_kline_data_series(self, symbol: Union[str, List[str]], duration_seconds: int,
start_dt: Union[date, datetime], end_dt: Union[date, datetime],
adj_type: Union[str, None] = None) -> pd.DataFrame:
"""
获取指定时间段内的 K 线序列,TqSdk 会缓存已经下载过的合约,提升代码执行效率、节约请求流量。
本接口仅限专业版用户使用,如需购买专业版或者申请试用,请访问 https://www.shinnytech.com/tqsdk-buy/。
该函数返回的对象不会更新,不建议在循环内调用该方法。
Args:
symbol (str): 指定合约代码。当前只支持单个合约。
duration_seconds (int): K 线数据周期, 以秒为单位。例如: 1 分钟线为 60,1 小时线为 3600,日线为 86400。\
注意: 周期在日线以内时此参数可以任意填写, 在日线以上时只能是日线(86400)的整数倍
start_dt (date/datetime): 起始时间
* date: 指的是交易日
* datetime: 指的是具体时间点,如果没有指定时区信息,则默认为北京时间
end_dt (date/datetime): 结束时间
* date: 指的是交易日
* datetime: 指的是具体时间点,如果没有指定时区信息,则默认为北京时间
adj_type (str/None): [可选]指定复权类型,默认为 None。adj_type 参数只对股票和基金类型合约有效。\
"F" 表示前复权;"B" 表示后复权;None 表示不做处理。
Returns:
pandas.DataFrame: 本函数总是返回一个 pandas.DataFrame 实例。包含以下列:
* id: 1234 (k线序列号)
* datetime: 1501080715000000000 (K线起点时间(按北京时间),自unix epoch(1970-01-01 00:00:00 GMT)以来的纳秒数)
* open: 51450.0 (K线起始时刻的最新价)
* high: 51450.0 (K线时间范围内的最高价)
* low: 51450.0 (K线时间范围内的最低价)
* close: 51450.0 (K线结束时刻的最新价)
* volume: 11 (K线时间范围内的成交量)
* open_oi: 27354 (K线起始时刻的持仓量)
* close_oi: 27355 (K线结束时刻的持仓量)
Example::
# 获取 SHFE.cu1805 合约 20180101-06:00:00 ~ 20180601-16:00:00 的 1 分钟线
from tqsdk import TqApi, TqAuth
from tqsdk.ta import MA, MACD
api = TqApi(auth=TqAuth("快期账户", "账户密码"))
kline_data = api.get_kline_data_series(symbol = "SHFE.cu1805", duration_seconds=60,
start_dt = datetime(2018, 1, 1, 6, 0, 0), end_dt = datetime(2018, 6, 1, 16, 0, 0))
print(kline_data)
ma = MA(kline_data, 30) # 计算 MA 指标
print(list(ma["ma"]))
macd = MACD(kline_data, 12, 26, 9) # 计算 MACD 指标
print(list(macd["diff"]))
print(list(macd["dea"]))
print(list(macd["bar"]))
api.close()
"""
if duration_seconds <= 0 or duration_seconds > 86400 and duration_seconds % 86400 != 0:
raise Exception("K线数据周期 %d 错误, 请检查K线数据周期值是否填写正确" % duration_seconds)
return self._get_data_series("get_kline_data_series", symbol, duration_seconds, start_dt, end_dt, adj_type)
# ----------------------------------------------------------------------
def get_tick_data_series(self, symbol: Union[str, List[str]], start_dt: Union[date, datetime],
end_dt: Union[date, datetime], adj_type: Union[str, None] = None) -> pd.DataFrame:
"""
获取指定时间段内的 tick 序列,TqSdk 会缓存已经下载过的合约,提升代码执行效率、节约请求流量。
本接口仅限专业版用户使用,如需购买专业版或者申请试用,请访问 https://www.shinnytech.com/tqsdk-buy/。
该函数返回的对象不会更新,不建议在循环内调用该方法。
Args:
symbol (str): 指定合约代码。当前只支持单个合约。
start_dt (date/datetime): 起始时间
* date: 指的是交易日
* datetime: 指的是具体时间点,如果没有指定时区信息,则默认为北京时间
end_dt (date/datetime): 结束时间
* date: 指的是交易日
* datetime: 指的是具体时间点,如果没有指定时区信息,则默认为北京时间
adj_type (str/None): [可选]指定复权类型,默认为 None。adj_type 参数只对股票和基金类型合约有效。\
"F" 表示前复权;"B" 表示后复权;None 表示不做处理。
Returns:
pandas.DataFrame: 本函数总是返回一个 pandas.DataFrame 实例。包含以下列:
* id: 12345 tick序列号
* datetime: 1501074872000000000 (tick从交易所发出的时间(按北京时间),自unix epoch(1970-01-01 00:00:00 GMT)以来的纳秒数)
* last_price: 3887.0 (最新价)
* average: 3820.0 (当日均价)
* highest: 3897.0 (当日最高价)
* lowest: 3806.0 (当日最低价)
* ask_price1: 3886.0 (卖一价)
* ask_volume1: 3 (卖一量)
* bid_price1: 3881.0 (买一价)
* bid_volume1: 18 (买一量)
* volume: 7823 (当日成交量)
* amount: 19237841.0 (成交额)
* open_interest: 1941 (持仓量)
Example::
# 获取 SHFE.cu1805 合约 20180201-06:00:00 ~ 20180301-16:00:00 的 tick 数据
from tqsdk import TqApi, TqAuth
api = TqApi(auth=TqAuth("快期账户", "账户密码"))
tick_data = api.get_tick_data_series(symbol = "SHFE.cu1805",
start_dt = datetime(2018, 2, 1, 6, 0, 0), end_dt = datetime(2018, 3, 1, 16, 0, 0))
print(tick_data)
ma = MA(tick_data, 30) # 计算 MA 指标
print(list(ma["ma"]))
api.close()
"""
return self._get_data_series("get_tick_data_series", symbol, 0, start_dt, end_dt, adj_type)
def _get_data_series(self, call_func: str, symbol_list: Union[str, List[str]], duration_seconds: int,
start_dt: Union[date, datetime], end_dt: Union[date, datetime],
adj_type: Union[str, None] = None) -> pd.DataFrame:
if self._loop.is_running():
raise Exception(f"不支持在协程中调用 {call_func} 接口")
if not self._auth._has_feature("tq_dl"):
raise Exception(
f"{call_func} 数据获取方式仅限专业版用户使用,如需购买专业版或者申请试用,请访问 https://www.shinnytech.com/tqsdk-buy/")
if self._backtest:
raise Exception(f"不支持在回测/复盘中调用 {call_func} 接口")
dur_nano = duration_seconds * 1000000000
symbol_list = symbol_list if isinstance(symbol_list, list) else [symbol_list]
if len(symbol_list) != 1:
raise Exception(f"{call_func} 数据获取方式暂不支持多合约请求")
self._ensure_symbol(symbol_list) # 检查合约代码是否存在
start_dt_nano, end_dt_nano = _convert_user_input_to_nano(start_dt, end_dt)