diff --git a/PKG-INFO b/PKG-INFO
index 4e5116d1..a93712f7 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: tqsdk
-Version: 2.8.6
+Version: 2.9.0
Summary: TianQin SDK
Home-page: https://www.shinnytech.com/tqsdk
Author: TianQin
diff --git a/doc/conf.py b/doc/conf.py
index fe93a914..76b4bff2 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -48,9 +48,9 @@
# built documents.
#
# The short X.Y version.
-version = u'2.8.6'
+version = u'2.9.0'
# The full version, including alpha/beta/rc tags.
-release = u'2.8.6'
+release = u'2.9.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/profession.rst b/doc/profession.rst
index 6e9ac065..7999fe1d 100644
--- a/doc/profession.rst
+++ b/doc/profession.rst
@@ -38,7 +38,7 @@ TqSdk 中大部分功能是供用户免费使用的, 同时我们也提供了 Tq
-------------------------------------------------
TqSdk 免费版本提供全部的期货、商品/金融期权和上证50、沪深300和中证500的实时行情
-其他股票类行情需购买或申请 TqSdk 专业版试用才可提供,TqSdk 中股票示例代码参考如下::
+购买或申请 TqSdk 专业版试用后可提供A股股票的实时和历史行情,TqSdk 中股票示例代码参考如下::
SSE.600000 - 上交所浦发银行股票编码
SZSE.000001 - 深交所平安银行股票编码
diff --git a/doc/reference/tqsdk.lib.rst b/doc/reference/tqsdk.lib.rst
index b90d8cc1..04505b34 100644
--- a/doc/reference/tqsdk.lib.rst
+++ b/doc/reference/tqsdk.lib.rst
@@ -4,6 +4,14 @@ tqsdk.lib - 业务工具库
------------------------------------------------------------------
+.. _tqsdk.lib.notify:
+
+tqsdk.lib.notify - 收集通知信息工具
+==================================================================
+.. automodule:: tqsdk.lib.notify
+ :members:
+
+
.. _tqsdk.lib.target_pos_task:
tqsdk.lib.target_pos_task - 目标持仓工具
diff --git a/doc/usage/backtest.rst b/doc/usage/backtest.rst
index 067eced0..76e9b6d8 100644
--- a/doc/usage/backtest.rst
+++ b/doc/usage/backtest.rst
@@ -59,6 +59,28 @@
.. figure:: ../images/web_gui_backtest.png
+.. _backtest_with_web_gui:
+
+回测结束在浏览器中查看绘图结果
+-------------------------------------------------
+
+要在回测结束时,如果依然需要在浏览器中查看绘图结果,同时又需要打印回测信息,您应该这样做::
+
+ from tqsdk import BacktestFinished
+
+ acc = TqSim()
+
+ try:
+ api = TqApi(acc, backtest=TqBacktest(start_dt=date(2018, 5, 1), end_dt=date(2018, 10, 1)), auth=TqAuth("信易账户", "账户密码"))
+ #策略代码在这里
+ #...
+ except BacktestFinished as e:
+ print(acc.tqsdk_stat) # 回测时间内账户交易信息统计结果,其中包含以下字段
+ # 由于需要在浏览器中查看绘图结果,因此程序不能退出
+ while True:
+ api.wait_update()
+
+
.. _backtest_underlying_symbol:
回测时获取主连合约标的
diff --git a/doc/usage/mddatas.rst b/doc/usage/mddatas.rst
index bfe9eac5..1e519885 100644
--- a/doc/usage/mddatas.rst
+++ b/doc/usage/mddatas.rst
@@ -9,7 +9,7 @@ TqSdk中的合约代码, 统一采用 交易所代码.交易所内品种代码
其中 TqSdk 免费版本提供全部的期货、商品/金融期权和上证50、沪深300和中证500的实时行情
-其他股票类行情需购买或申请 TqSdk 专业版才可提供,具体 TqSdk免费版和专业版的区别,请点击 `天勤量化专业版 `_
+购买或申请 TqSdk 专业版试用后可提供A股股票的实时和历史行情,具体免费版和专业版的区别,请点击 `天勤量化专业版 `_
目前 TqSdk 支持的交易所包括:
diff --git a/doc/version.rst b/doc/version.rst
index 5c6f4999..5297b6e6 100644
--- a/doc/version.rst
+++ b/doc/version.rst
@@ -2,6 +2,16 @@
版本变更
=============================
+2.9.0 (2021/09/29)
+
+* 增加::py:meth:`~tqsdk.api.TqApi.query_symbol_info` 接口返回值中增加 ``pre_open_interest``, ``pre_settlement``, ``pre_close`` 这三个字段
+* 优化:重构网络连接,增加多账户测试用例
+* 优化:简化回测结束后用户依然需要查看 web_gui 时的代码,详情参考 :ref:`backtest_with_web_gui`
+* 优化:网络连接失败时,优化对用户的提示信息
+* 优化:实盘账户实盘不支持主连和指数交易,提前抛错提示用户
+* docs:更新文档,专业版承诺提供A股股票行情
+
+
2.8.6 (2021/09/16)
* 增加:TqApi 增加 :py:meth:`~tqsdk.api.TqApi.query_his_cont_quotes` 接口,可以获取过去 n 个交易日的历史主连信息
diff --git a/setup.py b/setup.py
index 248b754c..4cafcd64 100644
--- a/setup.py
+++ b/setup.py
@@ -36,7 +36,7 @@ def get_tag(self):
setuptools.setup(
name='tqsdk',
- version="2.8.6",
+ version="2.9.0",
description='TianQin SDK',
author='TianQin',
author_email='tianqincn@gmail.com',
diff --git a/tqsdk/__version__.py b/tqsdk/__version__.py
index 7039ccb2..387cfacc 100644
--- a/tqsdk/__version__.py
+++ b/tqsdk/__version__.py
@@ -1 +1 @@
-__version__ = '2.8.6'
+__version__ = '2.9.0'
diff --git a/tqsdk/account.py b/tqsdk/account.py
index ef6f55f6..38ed4a3c 100644
--- a/tqsdk/account.py
+++ b/tqsdk/account.py
@@ -106,10 +106,11 @@ async def _run(self, api, api_send_chan, api_recv_chan, md_send_chan, md_recv_ch
"aid": "confirm_settlement"
}) # 自动发送确认结算单
self._pending_peek = False # 是否有下游收到未处理的 peek_message
- self._md_pending_peek = False # 是否有发给上游的 peek_message,未收到过回复
+ self._md_pending_peek = False # 是否有发给行情上游的 peek_message,未收到过回复
+ self._td_pending_peek = False # 是否有发给交易上游的 peek_message,未收到过回复
self._diffs = []
- md_task = api.create_task(self._md_handler(api_recv_chan, md_send_chan, md_recv_chan))
- td_task = api.create_task(self._td_handler(api_recv_chan, td_send_chan, td_recv_chan))
+ md_task = api.create_task(self._md_handler(api_recv_chan, md_recv_chan))
+ td_task = api.create_task(self._td_handler(api_recv_chan, td_recv_chan))
try:
async for pack in api_send_chan:
if pack["aid"] == "subscribe_quote" or pack["aid"] == "set_chart" or pack["aid"] == "ins_query":
@@ -125,9 +126,13 @@ async def _run(self, api, api_send_chan, api_recv_chan, md_send_chan, md_recv_ch
elif pack["aid"] == "peek_message":
self._pending_peek = True
await self._send_diff(api_recv_chan)
- if self._pending_peek and self._md_pending_peek is False: # 控制"peek_message"发送: 当没有新的事件需要用户处理时才推进到下一个行情
- await md_send_chan.send(pack)
- self._md_pending_peek = True
+ if self._pending_peek:
+ if self._md_pending_peek is False: # 控制"peek_message"发送: 当没有新的事件需要用户处理时才推进到下一个行情
+ await md_send_chan.send({"aid": "peek_message"})
+ self._md_pending_peek = True
+ if self._td_pending_peek is False: # 控制"peek_message"发送: 当没有新的事件需要用户处理时才推进到下一个行情
+ await td_send_chan.send({"aid": "peek_message"})
+ self._td_pending_peek = True
finally:
md_task.cancel()
td_task.cancel()
@@ -142,16 +147,16 @@ async def _send_diff(self, api_recv_chan):
self._pending_peek = False
await api_recv_chan.send(rtn_data)
- async def _md_handler(self, api_recv_chan, md_send_chan, md_recv_chan):
+ async def _md_handler(self, api_recv_chan, md_recv_chan):
async for pack in md_recv_chan:
if pack["aid"] == "rtn_data":
+ self._md_pending_peek = False
self._diffs.extend(pack.get('data', []))
+ await self._send_diff(api_recv_chan)
else:
await api_recv_chan.send(pack) # 有可能是另一个 account 的 rsp_login
- self._md_pending_peek = False
- await self._send_diff(api_recv_chan)
- async def _td_handler(self, api_recv_chan, td_send_chan, td_recv_chan):
+ async def _td_handler(self, api_recv_chan, td_recv_chan):
async for pack in td_recv_chan:
# OTG 返回业务信息截面 trade 中 account_key 为 user_id, 该值需要替换为 account_key
for _, slice_item in enumerate(pack["data"] if "data" in pack else []):
@@ -166,13 +171,11 @@ async def _td_handler(self, api_recv_chan, td_send_chan, td_recv_chan):
elif self._sub_account_id in slice_item["trade"]:
slice_item["trade"][self._account_key] = slice_item["trade"].pop(self._sub_account_id)
if pack["aid"] == "rtn_data":
+ self._td_pending_peek = False
self._diffs.extend(pack.get('data', []))
+ await self._send_diff(api_recv_chan)
else:
await api_recv_chan.send(pack)
- await td_send_chan.send({
- "aid": "peek_message"
- })
- await self._send_diff(api_recv_chan)
def _get_sub_account(self, trade):
""" 股票账户
diff --git a/tqsdk/api.py b/tqsdk/api.py
index f88e6692..893a27ab 100644
--- a/tqsdk/api.py
+++ b/tqsdk/api.py
@@ -1143,7 +1143,8 @@ def insert_order(self, symbol: str, direction: str, offset: str = "", volume: in
"""
(exchange_id, instrument_id) = symbol.split(".", 1)
- if not self._account._check_valid(account):
+ account = self._account._check_valid(account)
+ if account is None:
raise Exception(f"多账户模式下, 需要指定账户实例 account")
if direction not in ("BUY", "SELL"):
raise Exception("下单方向(direction) %s 错误, 请检查 direction 参数是否填写正确" % (direction))
@@ -1152,6 +1153,8 @@ def insert_order(self, symbol: str, direction: str, offset: str = "", volume: in
raise Exception("下单数量(volume) %s 错误, 请检查 volume 是否填写正确" % (volume))
if limit_price != limit_price:
raise Exception(f"limit_price 参数不支持设置为 {limit_price}。")
+ if isinstance(account, TqAccount) and exchange_id == "KQ":
+ raise Exception(f"账户 {account._broker_id}, {account._account_id} 不支持交易合约 {symbol}。")
# 股票下单时, 不支持 order_id 和 offset 参数
if exchange_id in ["SSE", "SZSE"] and self._account._is_stock_type(account):
if order_id:
@@ -1746,7 +1749,7 @@ def wait_update(self, deadline: Optional[float] = None, _task: Union[asyncio.Tas
"TqSdk 使用了 python3 的原生协程和异步通讯库 asyncio,您所使用的 IDE 不支持 asyncio, 请使用 pycharm 或其它支持 asyncio 的 IDE")
self._wait_timeout = False
# 先尝试执行各个task,再请求下个业务数据,可能用户的同步代码会在 chan 中 send 数据,需要先 run_tasks
- self._run_until_idle()
+ self._run_until_idle(async_run=False)
# 用户可能在同步或者异步代码中修改 klines 附加列的值
# 同步代码:此次调用 wait_update 之前应该已经修改执行
@@ -1754,7 +1757,7 @@ def wait_update(self, deadline: Optional[float] = None, _task: Union[asyncio.Tas
# 所以放在这里处理, 总会发送 serial_extra_array 数据,由 TqWebHelper 处理
for _, serial in self._serials.items():
self._process_serial_extra_array(serial)
- self._run_until_idle() # 这里 self._run_until_idle() 主要为了把上一步计算出得需要绘制的数据发送到 TqWebHelper
+ self._run_until_idle(async_run=False) # 这里 self._run_until_idle() 主要为了把上一步计算出得需要绘制的数据发送到 TqWebHelper
if _task is not None:
# 如果 _task 已经 done,则提前返回 True, False 代表超时会抛错
_tasks = _task if isinstance(_task, list) else [_task]
@@ -2595,6 +2598,9 @@ def query_symbol_info(self, symbol: Union[str, List[str]]):
* exercise_year: 期权最后行权日年份,只对期权品种有效。
* exercise_month: 期权最后行权日月份,只对期权品种有效。
* option_class: 期权方向
+ * pre_settlement: 昨结算
+ * pre_open_interest: 昨持仓
+ * pre_close: 昨收盘
Example1::
@@ -2981,7 +2987,14 @@ def _setup_connection(self):
# 连接合约和行情服务器
if self._md_url is None:
- self._md_url = self._auth._get_md_url(self._stock, backtest=isinstance(self._backtest, TqBacktest)) # 如果用户未指定行情地址,则使用名称服务获取行情地址
+ try:
+ self._md_url = self._auth._get_md_url(self._stock, backtest=isinstance(self._backtest, TqBacktest)) # 如果用户未指定行情地址,则使用名称服务获取行情地址
+ except Exception as e:
+ now = datetime.now()
+ if now.hour == 19 and 0 <= now.minute <= 30:
+ raise Exception(f"{e}, 每日 19:00-19:30 为日常运维时间,请稍后再试")
+ else:
+ raise
md_logger = ShinnyLoggerAdapter(self._logger.getChild("TqConnect"), url=self._md_url)
ws_md_send_chan = TqChan(self, chan_name="send to md", logger=md_logger)
ws_md_recv_chan = TqChan(self, chan_name="recv from md", logger=md_logger)
@@ -3071,9 +3084,9 @@ def _setup_connection(self):
data_extension = DataExtension(self)
data_extension_send_chan = TqChan(self, chan_name="send to data_extension")
data_extension_recv_chan = TqChan(self, chan_name="recv from data_extension")
- self.create_task(data_extension._run(data_extension_send_chan, data_extension_recv_chan, self._send_chan, self._recv_chan))
self._send_chan._logger_bind(chan_from="data_extension")
self._recv_chan._logger_bind(chan_to="data_extension")
+ self.create_task(data_extension._run(data_extension_send_chan, data_extension_recv_chan, self._send_chan, self._recv_chan))
self._send_chan, self._recv_chan = data_extension_send_chan, data_extension_recv_chan
self._send_chan._logger_bind(chan_from="api")
self._recv_chan._logger_bind(chan_to="api")
diff --git a/tqsdk/backtest.py b/tqsdk/backtest.py
index d5860273..af91bc27 100644
--- a/tqsdk/backtest.py
+++ b/tqsdk/backtest.py
@@ -336,6 +336,7 @@ async def _send_diff(self):
}
}]
})
+ await self._api._wait_until_idle()
raise BacktestFinished(self._api) from None
for ins, diff in quotes.items():
self._quotes[ins]["sended_init_quote"] = True
diff --git a/tqsdk/baseApi.py b/tqsdk/baseApi.py
index 406b9acd..99b51f19 100644
--- a/tqsdk/baseApi.py
+++ b/tqsdk/baseApi.py
@@ -6,6 +6,7 @@
import functools
import sys
import time
+from asyncio import Future
from typing import Optional, Coroutine
@@ -23,6 +24,7 @@ def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
"""
self._loop = asyncio.SelectorEventLoop() if loop is None else loop # 创建一个新的 ioloop, 避免和其他框架/环境产生干扰
self._event_rev, self._check_rev = 0, 0
+ self._wait_idle_list = [] # 所有等待 loop idle 的 Future
self._wait_timeout = False # wait_update 是否触发超时
self._tasks = set() # 由api维护的所有根task,不包含子task,子task由其父task维护
self._exceptions = [] # 由api维护的所有task抛出的例外
@@ -54,14 +56,27 @@ def _run_once(self):
if self._exceptions:
raise self._exceptions.pop(0)
- def _run_until_idle(self):
- """执行 ioloop 直到没有待执行任务"""
+ def _run_until_idle(self, async_run=False):
+ """执行 ioloop 直到没有待执行任务
+ async_run is True 会从 _wait_idle_list 中取出等待的异步任务,保证同步代码优先于异步代码执行,
+ 只有被 _run_until_task_done 调用(即 api 等待 fetch_msg)时,async_run 会为 True
+ """
while self._check_rev != self._event_rev:
check_handle = self._loop.call_soon(self._check_event, self._event_rev + 1)
try:
self._run_once()
finally:
check_handle.cancel()
+ if len(self._wait_idle_list) > 0 and async_run:
+ f = self._wait_idle_list.pop(0) # 取出 list 中的第一个 Future
+ f.set_result(None) # f 返回
+
+ async def _wait_until_idle(self):
+ """等待 ioloop 执行到空闲时,才从网络连接处收数据包,在 TqConnect 类中使用"""
+ f = Future()
+ self._wait_idle_list.append(f)
+ self._loop.stop()
+ await f
def _run_until_task_done(self, task: asyncio.Task, deadline=None):
try:
@@ -69,7 +84,10 @@ def _run_until_task_done(self, task: asyncio.Task, deadline=None):
if deadline is not None:
deadline_handle = self._loop.call_later(max(0, deadline - time.time()), self._set_wait_timeout)
while not self._wait_timeout and not task.done():
- self._run_once()
+ if len(self._wait_idle_list) == 0:
+ self._run_once()
+ else:
+ self._run_until_idle(async_run=True)
finally:
if deadline is not None:
deadline_handle.cancel()
@@ -101,7 +119,7 @@ async def _windows_patch(self):
await asyncio.sleep(1)
def _close(self) -> None:
- self._run_until_idle() # 由于有的处于 ready 状态 task 可能需要报撤单, 因此一直运行到没有 ready 状态的 task
+ self._run_until_idle(async_run=False) # 由于有的处于 ready 状态 task 可能需要报撤单, 因此一直运行到没有 ready 状态的 task
for task in self._tasks:
task.cancel()
while self._tasks: # 等待 task 执行完成
diff --git a/tqsdk/connect.py b/tqsdk/connect.py
index a8857be1..94f54c33 100644
--- a/tqsdk/connect.py
+++ b/tqsdk/connect.py
@@ -10,6 +10,8 @@
import time
import warnings
from abc import abstractmethod
+from asyncio import Future
+from datetime import datetime
from queue import Queue
import certifi
@@ -150,6 +152,7 @@ async def _run(self, api, url, send_chan, recv_chan):
else:
self._logger.debug("websocket connected")
# 发送网络连接建立的通知,code = 2019112901 or 2019112902,这里区分了第一次连接和重连
+ await self._api._wait_until_idle()
await recv_chan.send({
"aid": "rtn_data",
"data": [{
@@ -163,8 +166,9 @@ async def _run(self, api, url, send_chan, recv_chan):
send_task = self._api.create_task(self._send_handler(send_chan, client))
try:
async for msg in client:
- self._logger.debug("websocket received data", pack=msg)
pack = json.loads(msg)
+ await self._api._wait_until_idle()
+ self._logger.debug("websocket received data", pack=msg)
await recv_chan.send(pack)
finally:
if client.reader._start_read_message:
@@ -177,15 +181,18 @@ async def _run(self, api, url, send_chan, recv_chan):
except (websockets.exceptions.ConnectionClosed, websockets.exceptions.InvalidStatusCode,
websockets.exceptions.InvalidState, websockets.exceptions.ProtocolError, OSError,
TqBacktestPermissionError) as e:
+ in_ops_time = datetime.now().hour == 19 and 0 <= datetime.now().minute <= 30
# 发送网络连接断开的通知,code = 2019112911
notify_id = _generate_uuid()
notify = {
"type": "MESSAGE",
"level": "WARNING",
"code": 2019112911,
- "content": "与 %s 的网络连接断开,请检查客户端及网络是否正常" % url,
+ "content": f"与 {url} 的网络连接断开,请检查客户端及网络是否正常",
"url": url
}
+ if in_ops_time:
+ notify['content'] += ',每日 19:00-19:30 为日常运维时间,请稍后再试'
self._logger.debug("websocket connection closed", error=str(e))
await recv_chan.send({
"aid": "rtn_data",
@@ -198,6 +205,8 @@ async def _run(self, api, url, send_chan, recv_chan):
if isinstance(e, TqBacktestPermissionError):
# 如果错误类型是用户无回测权限,直接返回
raise
+ if self._first_connect and in_ops_time:
+ raise Exception(f'与 {url} 的连接失败,每日 19:00-19:30 为日常运维时间,请稍后再试')
finally:
if self._first_connect:
self._first_connect = False
diff --git a/tqsdk/data_extension.py b/tqsdk/data_extension.py
index 5910d281..c3a7edc9 100644
--- a/tqsdk/data_extension.py
+++ b/tqsdk/data_extension.py
@@ -81,9 +81,10 @@ async def _run(self, api_send_chan, api_recv_chan, md_send_chan, md_recv_chan):
try:
async for pack in api_send_chan:
if "_md_recv" in pack:
- self._pending_peek_md = False
- await self._md_recv(pack)
- await self._send_diff()
+ if pack['aid'] == 'rtn_data':
+ self._pending_peek_md = False
+ await self._md_recv(pack)
+ await self._send_diff()
if self._pending_peek and self._pending_peek_md is False:
self._pending_peek_md = True
await self._md_send_chan.send({"aid": "peek_message"})
diff --git a/tqsdk/multiaccount.py b/tqsdk/multiaccount.py
index 71f0217d..e0f4f70f 100644
--- a/tqsdk/multiaccount.py
+++ b/tqsdk/multiaccount.py
@@ -208,8 +208,8 @@ def _connect_td(self, api, account: Union[TqAccount, TqKq] = None, index: int =
conn = TqConnect(td_logger)
api.create_task(conn._run(api, account._td_url, ws_td_send_chan, ws_td_recv_chan))
- ws_td_send_chan._logger_bind(chan_from="td_reconn")
- ws_td_recv_chan._logger_bind(chan_to="td_reconn")
+ ws_td_send_chan._logger_bind(chan_from=f"td_reconn_{index}")
+ ws_td_recv_chan._logger_bind(chan_to=f"td_reconn_{index}")
td_handler_logger = self._format_logger("TdReconnect", account)
td_reconnect = TdReconnectHandler(td_handler_logger)
diff --git a/tqsdk/objs.py b/tqsdk/objs.py
index 0ce26008..a1d3a2a2 100644
--- a/tqsdk/objs.py
+++ b/tqsdk/objs.py
@@ -181,6 +181,11 @@ def _instance_entity(self, path):
@property
def underlying_quote(self):
+ """
+ 标的合约 underlying_symbol 所指定的合约对象,若没有标的合约则为 None
+
+ :return: 标的指定的 :py:class:`~tqsdk.objs.Quote` 对象
+ """
if self.underlying_symbol:
return _get_obj(self._api._data, ["quotes", self.underlying_symbol], self._api._prototype["quotes"]["#"])
return None
diff --git a/tqsdk/objs_not_entity.py b/tqsdk/objs_not_entity.py
index b81588b1..4ed31ee9 100644
--- a/tqsdk/objs_not_entity.py
+++ b/tqsdk/objs_not_entity.py
@@ -11,7 +11,7 @@
from tqsdk.objs import Quote
from tqsdk.diff import _get_obj
-from tqsdk.utils import _query_for_quote, query_all_fields, _generate_uuid
+from tqsdk.utils import _query_for_quote, query_all_fields, _generate_uuid, fragments
from tqsdk.tafunc import _get_t_series, get_impv, _get_d1, get_delta, get_theta, get_gamma, get_vega, get_rho
"""
@@ -228,7 +228,10 @@ def __init__(self, api, symbol_list, backtest_timestamp, *args, **kwargs):
"last_exercise_datetime",
"exercise_year",
"exercise_month",
- "option_class"
+ "option_class",
+ "pre_settlement",
+ "pre_open_interest",
+ "pre_close"
]
default_quote = Quote(None)
data = [{k: (s if k == "instrument_id" else default_quote[k]) for k in self.__dict__["_columns"]} for s in symbol_list]
@@ -241,10 +244,10 @@ async def async_update(self):
if self.__dict__["_backtest_timestamp"]:
variables["timestamp"] = self.__dict__["_backtest_timestamp"]
query = "query ($instrument_id:[String],$timestamp:Int64) {"
- query += "multi_symbol_info(instrument_id:$instrument_id,timestamp:$timestamp) {" + query_all_fields + "}}"
+ query += "multi_symbol_info(instrument_id:$instrument_id,timestamp:$timestamp) {" + query_all_fields + "}}" + fragments
else:
query = "query ($instrument_id:[String]) {"
- query += "multi_symbol_info(instrument_id:$instrument_id) {" + query_all_fields + "}}"
+ query += "multi_symbol_info(instrument_id:$instrument_id) {" + query_all_fields + "}}" + fragments
self.__dict__["_api"]._send_pack({
"aid": "ins_query",
"query_id": query_id,
diff --git a/tqsdk/symbols.py b/tqsdk/symbols.py
index 855c5ceb..16c72ab8 100644
--- a/tqsdk/symbols.py
+++ b/tqsdk/symbols.py
@@ -21,6 +21,8 @@ async def _run(self, api, sim_send_chan, sim_recv_chan, md_send_chan, md_recv_ch
self._md_recv_chan = md_recv_chan
self._quotes_all_keys = set(Quote(None).keys())
self._quotes_all_keys = self._quotes_all_keys.union({'margin', 'commission'})
+ # 以下字段合约服务也会请求,但是不应该记在 quotes 中,quotes 中的这些字段应该有行情服务负责
+ self._quotes_all_keys.difference_update({'pre_open_interest', 'pre_settlement', 'pre_close'})
sim_task = self._api.create_task(self._sim_handler())
try:
async for pack in self._md_recv_chan:
diff --git a/tqsdk/utils.py b/tqsdk/utils.py
index d318f88e..2508fb7f 100644
--- a/tqsdk/utils.py
+++ b/tqsdk/utils.py
@@ -17,33 +17,24 @@ def _generate_uuid(prefix=''):
return f"{prefix + '_' if prefix else ''}{RD.getrandbits(128):032x}"
-query_all_fields = """
- ... on basic{ class trading_time{day night} trading_day instrument_id instrument_name price_tick price_decs exchange_id english_name}
- ... on stock{ stock_dividend_ratio cash_dividend_ratio}
- ... on fund{ cash_dividend_ratio}
- ... on bond{ maturity_datetime }
- ... on tradeable{ volume_multiple quote_multiple}
- ... on index{ index_multiple}
- ... on securities{ currency face_value first_trading_datetime buy_volume_unit sell_volume_unit status public_float_share_quantity}
- ... on future{ expired product_id product_short_name delivery_year delivery_month expire_datetime settlement_price max_market_order_volume max_limit_order_volume margin commission mmsa}
- ... on option{ expired product_short_name expire_datetime last_exercise_datetime settlement_price max_market_order_volume max_limit_order_volume strike_price call_or_put exercise_type}
- ... on combine{ expired product_id expire_datetime max_market_order_volume max_limit_order_volume leg1{ ... on basic{instrument_id}} leg2{ ... on basic{instrument_id}} }
- ... on derivative{
- underlying{
- count edges{ underlying_multiple node{
- ... on basic{ class trading_time{day night} trading_day instrument_id instrument_name price_tick price_decs exchange_id english_name }
- ... on stock{ stock_dividend_ratio cash_dividend_ratio }
- ... on fund{ cash_dividend_ratio }
- ... on bond{ maturity_datetime }
- ... on tradeable{ volume_multiple quote_multiple}
- ... on index{ index_multiple}
- ... on securities{ currency face_value first_trading_datetime buy_volume_unit sell_volume_unit public_float_share_quantity }
- ... on future{ expired product_id product_short_name delivery_year delivery_month expire_datetime settlement_price max_market_order_volume max_limit_order_volume margin commission mmsa}
- }
- }
- }
+fragments = """\
+fragment basic on basic {class trading_time{day night} trading_day instrument_id instrument_name price_tick price_decs exchange_id english_name}
+fragment stock on stock{ stock_dividend_ratio cash_dividend_ratio}
+fragment fund on fund{ cash_dividend_ratio}
+fragment bond on bond{ maturity_datetime }
+fragment tradeable on tradeable{ pre_close volume_multiple quote_multiple}
+fragment index on index{ index_multiple}
+fragment securities on securities{ currency face_value first_trading_datetime buy_volume_unit sell_volume_unit status public_float_share_quantity}
+fragment future on future{ pre_open_interest expired product_id product_short_name delivery_year delivery_month expire_datetime settlement_price max_market_order_volume max_limit_order_volume margin commission mmsa}
+fragment option on option{ pre_open_interest expired product_short_name expire_datetime last_exercise_datetime settlement_price max_market_order_volume max_limit_order_volume strike_price call_or_put exercise_type}
+fragment combine on combine{ expired product_id expire_datetime max_market_order_volume max_limit_order_volume leg1{ ...on basic{instrument_id}} leg2{ ...on basic{instrument_id}} }
+fragment derivative on derivative{
+ underlying{
+ count edges{ underlying_multiple node{...basic...stock...fund...bond...tradeable...index...securities...future}}
}
-"""
+}"""
+
+query_all_fields = "...basic...stock...fund...bond...tradeable...index...securities...future...option...combine...derivative"
def _query_for_quote(symbol):
@@ -56,7 +47,7 @@ def _query_for_quote(symbol):
if any([s == "" for s in symbol_list]) or len(symbol_list) == 0:
raise Exception("发送的 ins_query 请求合约代码不支持空字符串、空列表或者列表中包括空字符串。")
query = "query ($instrument_id:[String]) {"
- query += "multi_symbol_info(instrument_id:$instrument_id) {" + query_all_fields + "}}"
+ query += "multi_symbol_info(instrument_id:$instrument_id) {" + query_all_fields + "}}" + fragments
return {
"aid": "ins_query",
"query_id": _generate_uuid(prefix='PYSDK_quote_'),
@@ -86,7 +77,7 @@ def _query_for_init():
todo: 为了兼容旧版提供给用户的 api._data["quote"].items() 类似用法,应该限制交易所 ["SHFE", "DCE", "CZCE", "INE", "CFFEX", "KQ"]
"""
query = "query ($class_list:[Class], $exchange_list:[String]) {"
- query += "multi_symbol_info(class:$class_list, exchange_id:$exchange_list) {" + query_all_fields + "}}"
+ query += "multi_symbol_info(class:$class_list, exchange_id:$exchange_list) {" + query_all_fields + "}}" + fragments
return query, {
"class_list": ["FUTURE", "INDEX", "OPTION", "COMBINE", "CONT"],
"exchange_list": ["SHFE", "DCE", "CZCE", "INE", "CFFEX", "KQ"]
diff --git a/tqsdk/utils_symbols.py b/tqsdk/utils_symbols.py
index b3c1e730..daae268d 100644
--- a/tqsdk/utils_symbols.py
+++ b/tqsdk/utils_symbols.py
@@ -64,6 +64,8 @@ def _convert_symbol_to_quote(symbol, keys):
quote["exercise_year"] = datetime.fromtimestamp(symbol["last_exercise_datetime"] / 1e9).year
elif key == "exercise_month" and symbol.get("last_exercise_datetime"):
quote["exercise_month"] = datetime.fromtimestamp(symbol["last_exercise_datetime"] / 1e9).month
+ elif key == "pre_settlement" and "settlement_price" in symbol:
+ quote["pre_settlement"] = symbol["settlement_price"]
elif key in symbol:
quote[key] = symbol[key]
return quote
\ No newline at end of file