Skip to content

Commit 5b6a1ae

Browse files
shinny-packshinny-mayanqiong
authored andcommitted
Update Version 2.8.3
1 parent 41d8f69 commit 5b6a1ae

File tree

14 files changed

+184
-108
lines changed

14 files changed

+184
-108
lines changed

PKG-INFO

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Metadata-Version: 2.1
22
Name: tqsdk
3-
Version: 2.8.2
3+
Version: 2.8.3
44
Summary: TianQin SDK
55
Home-page: https://www.shinnytech.com/tqsdk
66
Author: TianQin

doc/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@
4848
# built documents.
4949
#
5050
# The short X.Y version.
51-
version = u'2.8.2'
51+
version = u'2.8.3'
5252
# The full version, including alpha/beta/rc tags.
53-
release = u'2.8.2'
53+
release = u'2.8.3'
5454

5555
# The language for content autogenerated by Sphinx. Refer to documentation
5656
# for a list of supported languages.

doc/quickstart.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
但是由于 `pip` 使用的是国外的服务器,普通用户往往下载速度过慢或不稳定,对于使用 `pip` 命令下载速度较慢的用户,我们推荐采用切换国内源的方式安装/升级 TqSdk::
3636

37-
pip install tqsdk -U -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host=mirrors.aliyun.com
37+
pip install tqsdk -U -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host=pypi.tuna.tsinghua.edu.cn
3838

3939
.. _quickstart_0:
4040

@@ -138,7 +138,7 @@ klines是一个pandas.DataFrame对象. 跟 api.get_quote() 一样, api.get_kline
138138

139139
点击生成的地址,即可访问订阅的K线图形
140140

141-
.. figure:: images/web_gui_klines.png
141+
.. figure:: images/web_gui_demo.png
142142

143143
具体请见 :ref:`web_gui`
144144

doc/usage/mddatas.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ SZSE 深圳证券交易所
6868

6969
天勤指数的计算方式为根据在市期货合约的昨持仓量加权平均
7070

71-
天勤主力的选定标准为该合约持仓量和成交量均为最大后盘后切换,且一旦切换之后不会再切回
71+
天勤主力的选定标准为该合约持仓量和成交量均为最大后,在下一个交易日开盘后进行切换,且切换后不会再切换到之前的合约
7272

7373

7474
.. image:

doc/usage/option_trade.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ TqSdk 内提供了丰富的期权指标计算&序列计算函数,参考如下
4242

4343
期权查询函数
4444
----------------------------------------------------
45-
TqSdk 内提供了完善的期权查询函数 :py:meth:`~tqsdk.api.TqApi.query_options` 和对应平值虚值期权查询函数 :py:meth:`~tqsdk.api.TqApi.query_atm_options` ,供用户搜索符合自己需求的期权,**需要注意 query 系列函数暂时不支持在回测中使用**::
45+
TqSdk 内提供了完善的期权查询函数 :py:meth:`~tqsdk.api.TqApi.query_options` 和对应平值虚值期权查询函数 :py:meth:`~tqsdk.api.TqApi.query_atm_options` ,供用户搜索符合自己需求的期权::
4646

4747

4848

doc/version.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
版本变更
44
=============================
5+
2.8.3 (2021/08/30)
6+
7+
* 增加:is_changing 接口增加对于委托单 :py:meth:`~tqsdk.objs.Order.is_dead`、:py:meth:`~tqsdk.objs.Order.is_online`、
8+
:py:meth:`~tqsdk.objs.Order.is_error`、:py:meth:`~tqsdk.objs.Order.trade_price` 字段支持判断是否更新
9+
* 修复: TqApi 初始化可能失败的问题
10+
* 优化: 将已知下市合约直接打包在代码中,缩短 TqApi 初始化时间
11+
* docs: 完善主力切换规则说明,将阿里源替换为清华源
12+
13+
514
2.8.2 (2021/08/17)
615

716
* 增加:is_changing 接口增加对于合约 :py:meth:`~tqsdk.objs.Quote.expire_rest_days`,持仓 :py:meth:`~tqsdk.objs.Position.pos_long`、

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def get_tag(self):
3636

3737
setuptools.setup(
3838
name='tqsdk',
39-
version="2.8.2",
39+
version="2.8.3",
4040
description='TianQin SDK',
4141
author='TianQin',
4242
author_email='tianqincn@gmail.com',

tqsdk/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.8.2'
1+
__version__ = '2.8.3'

tqsdk/api.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import asyncio
2121
import copy
2222
import logging
23+
import lzma
24+
import json
2325
import os
2426
import platform
2527
import re
@@ -116,15 +118,14 @@ def __init__(self, account: Union[TqMultiAccount, TqAccount, TqSim, None] = None
116118
信易账户注册链接 https://www.shinnytech.com/register-intro/
117119
118120
url (str): [可选]指定服务器的地址
119-
* 当 account 为 :py:class:`~tqsdk.account.TqAccount` 类型时, 可以通过该参数指定交易服务器地址, \
120-
默认使用 wss://opentd.shinnytech.com/trade/user0, 行情始终使用 wss://openmd.shinnytech.com/t/md/front/mobile
121+
* 当 account 为 :py:class:`~tqsdk.account.TqAccount`、:py:class:`~tqsdk.multiaccount.TqMultiAccount` 类型时, 可以通过该参数指定交易服务器地址,\
122+
默认使用对应账户的交易服务地址,行情地址该信易账户对应的行情服务地址
121123
122-
* 当 account 为 :py:class:`~tqsdk.sim.TqSim` 类型时, 可以通过该参数指定行情服务器地址,\
123-
默认使用 wss://openmd.shinnytech.com/t/md/front/mobile
124+
* 当 account 为 :py:class:`~tqsdk.sim.TqSim` 类型时, 可以通过该参数指定行情服务器地址, 默认使用该信易账户对应的行情服务地址
124125
125126
backtest (TqBacktest/TqReplay): [可选] 进入时光机,此时强制要求 account 类型为 :py:class:`~tqsdk.sim.TqSim`
126127
* :py:class:`~tqsdk.backtest.TqBacktest` : 传入 TqBacktest 对象,进入回测模式 \
127-
在回测模式下, TqBacktest 连接 wss://openmd.shinnytech.com/t/md/front/mobile 接收行情数据, \
128+
在回测模式下, TqBacktest 连接 wss://backtest.shinnytech.com/t/md/front/mobile 接收行情数据, \
128129
由 TqBacktest 内部完成回测时间段内的行情推进和 K 线、Tick 更新.
129130
130131
* :py:class:`~tqsdk.backtest.TqReplay` : 传入 TqReplay 对象, 进入复盘模式 \
@@ -2790,29 +2791,22 @@ def _setup_connection(self):
27902791

27912792
if self._stock is False: # self._stock == False 需要旧版的合约服务文件
27922793
quotes = self._fetch_symbol_info(self._ins_url)
2793-
if isinstance(self._backtest, TqBacktest):
2794-
_quotes_add_night(quotes)
2795-
ws_md_recv_chan.send_nowait({
2796-
"aid": "rtn_data",
2797-
"data": [{
2798-
"quotes": quotes
2799-
}]
2800-
}) # 获取合约信息
28012794
else: # todo: self._stock == True 新版合约服务没有已下市合约
2802-
quotes = self._fetch_symbol_info(os.getenv("TQ_INS_URL", "https://openmd.shinnytech.com/t/md/symbols/2020-09-15.json"))
2803-
if isinstance(self._backtest, TqBacktest):
2804-
_quotes_add_night(quotes)
2805-
ws_md_recv_chan.send_nowait({
2806-
"aid": "rtn_data",
2807-
"data": [{
2808-
"quotes": {k: v for k, v in quotes.items() if v["expired"] is True}
2809-
}]
2810-
}) # 获取合约信息
2795+
dir_path = os.path.dirname(os.path.realpath(__file__))
2796+
with lzma.open(os.path.join(dir_path, "expired_quotes.json.lzma"), "rt", encoding="utf-8") as f:
2797+
quotes = json.loads(f.read())
2798+
2799+
if isinstance(self._backtest, TqBacktest):
2800+
_quotes_add_night(quotes)
28112801
# 期权增加了 exercise_year、exercise_month 在旧版合约服务中没有,需要添加,使用下市日期代替最后行权日
28122802
for quote in quotes.values():
28132803
if quote["ins_class"] == "FUTURE_OPTION":
28142804
quote["exercise_year"] = datetime.fromtimestamp(quote["expire_datetime"]).year
28152805
quote["exercise_month"] = datetime.fromtimestamp(quote["expire_datetime"]).month
2806+
ws_md_recv_chan.send_nowait({
2807+
"aid": "rtn_data",
2808+
"data": [{"quotes": quotes}]
2809+
}) # 获取合约信息
28162810

28172811
conn = TqConnect(md_logger)
28182812
self.create_task(conn._run(self, self._md_url, ws_md_send_chan, ws_md_recv_chan))
@@ -2879,6 +2873,8 @@ def _setup_connection(self):
28792873
data_extension_send_chan = TqChan(self, chan_name="send to data_extension")
28802874
data_extension_recv_chan = TqChan(self, chan_name="recv from data_extension")
28812875
self.create_task(data_extension._run(data_extension_send_chan, data_extension_recv_chan, self._send_chan, self._recv_chan))
2876+
self._send_chan._logger_bind(chan_from="data_extension")
2877+
self._recv_chan._logger_bind(chan_to="data_extension")
28822878
self._send_chan, self._recv_chan = data_extension_send_chan, data_extension_recv_chan
28832879
self._send_chan._logger_bind(chan_from="api")
28842880
self._recv_chan._logger_bind(chan_to="api")

tqsdk/data_extension.py

Lines changed: 98 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from tqsdk.datetime import _get_expire_rest_days
77
from tqsdk.datetime_state import TqDatetimeState
8-
from tqsdk.diff import _simple_merge_diff
8+
from tqsdk.diff import _simple_merge_diff, _is_key_exist, _simple_merge_diff_and_collect_paths, _get_obj
99

1010

1111
class DataExtension(object):
@@ -25,6 +25,14 @@ class DataExtension(object):
2525
'pos_short': int,
2626
'pos': int
2727
}
28+
},
29+
ordres: {
30+
*: {
31+
'is_dead': bool
32+
'is_online': bool
33+
'is_error': bool
34+
'trade_price': float
35+
}
2836
}
2937
}
3038
}
@@ -35,6 +43,30 @@ def __init__(self, api):
3543
self._api = api
3644
self._data = {'trade': {}} # 数据截面, 现在的功能只需要记录 trade
3745
self._diffs = []
46+
self._diffs_paths = set()
47+
self._prototype = {
48+
"trade": {
49+
"*": {
50+
"orders": {
51+
"*": {
52+
"status": None,
53+
"exchange_order_id": None
54+
}
55+
},
56+
"trades": {
57+
"*": None
58+
},
59+
"positions": {
60+
"*": {
61+
"pos_long_his": None,
62+
"pos_long_today": None,
63+
"pos_short_his": None,
64+
"pos_short_today": None
65+
}
66+
}
67+
}
68+
}
69+
}
3870

3971
async def _run(self, api_send_chan, api_recv_chan, md_send_chan, md_recv_chan):
4072
self._logger = self._api._logger.getChild("DataExtension")
@@ -74,10 +106,16 @@ async def _md_handler(self):
74106

75107
async def _md_recv(self, pack):
76108
"""将行情数据和交易数据合并至 self._data """
77-
for d in pack.get("data", {}):
109+
for d in pack.get("data", []):
78110
self._datetime_state.update_state(d)
79111
if d.get('trade', None):
80-
_simple_merge_diff(self._data['trade'], d['trade'], reduce_diff=False)
112+
_simple_merge_diff_and_collect_paths(
113+
result=self._data['trade'],
114+
diff=d['trade'],
115+
path=('trade', ),
116+
diff_paths=self._diffs_paths,
117+
prototype=self._prototype['trade']
118+
)
81119
self._diffs.append(d)
82120

83121
def _generate_ext_diff(self):
@@ -86,43 +124,79 @@ def _generate_ext_diff(self):
86124
此函数在 send_diff() 才会调用, self._datetime_state.data_ready 一定为 True,
87125
调用 self._datetime_state.get_current_dt() 一定有正确的当前时间
88126
"""
89-
pend_diff = {}
90127
for d in self._diffs:
91128
if d.get('quotes', None):
92-
_simple_merge_diff(pend_diff, self._update_quotes(d), reduce_diff=False)
93-
if d.get('trade', None):
94-
_simple_merge_diff(pend_diff, self._update_positions(d), reduce_diff=False)
129+
self._update_quotes(d)
130+
pend_diff = {}
131+
_simple_merge_diff(pend_diff, self._get_positions_pend_diff(), reduce_diff=False)
132+
orders_set = set() # 计算过委托单,is_dead、is_online、is_error
133+
orders_price_set = set() # 根据成交计算哪些 order 需要重新计算平均成交价 trade_price
134+
for path in self._diffs_paths:
135+
if path[2] == 'orders':
136+
_, account_key, _, order_id, _ = path
137+
if (account_key, order_id) not in orders_set:
138+
orders_set.add((account_key, order_id))
139+
order = _get_obj(self._data, ['trade', account_key, 'orders', order_id])
140+
if order:
141+
pend_order = pend_diff.setdefault('trade', {}).setdefault(account_key, {}).setdefault('orders', {}).setdefault(order_id, {})
142+
pend_order['is_dead'] = order['status'] == "FINISHED"
143+
pend_order['is_online'] = order['exchange_order_id'] != "" and order['status'] == "ALIVE"
144+
pend_order['is_error'] = order['exchange_order_id'] == "" and order['status'] == "FINISHED"
145+
elif path[2] == 'trades':
146+
_, account_key, _, trade_id = path
147+
trade = _get_obj(self._data, path)
148+
order_id = trade.get('order_id', '')
149+
if order_id:
150+
orders_price_set.add(('trade', account_key, 'orders', order_id))
151+
for path in orders_price_set:
152+
_, account_key, _, order_id = path
153+
trade_price = self._get_trade_price(account_key, order_id)
154+
if trade_price == trade_price:
155+
pend_order = pend_diff.setdefault('trade', {}).setdefault(account_key, {}).setdefault('orders', {}).setdefault(order_id, {})
156+
pend_order['trade_price'] = trade_price
157+
self._diffs_paths = set()
95158
return pend_diff
96159

97160
def _update_quotes(self, diff):
98-
pend_diff = {}
99161
for symbol in diff['quotes']:
100-
expire_datetime = diff['quotes'].get(symbol, {}).get('expire_datetime', float('nan'))
101-
if expire_datetime == expire_datetime:
102-
# expire_rest_days 距离到期日的剩余天数(自然日天数)
103-
# 正数表示距离到期日的剩余天数,0表示到期日当天,负数表示距离到期日已经过去的天数
162+
if not _is_key_exist(diff, path=['quotes', symbol], key=['expire_datetime']):
163+
continue
164+
expire_datetime = diff['quotes'][symbol]['expire_datetime']
165+
if expire_datetime and expire_datetime == expire_datetime: # 排除 None 和 nan
166+
# expire_rest_days 距离到期日的剩余天数(自然日天数),正数表示距离到期日的剩余天数,0表示到期日当天,负数表示距离到期日已经过去的天数
167+
# 直接修改在 diff 里面的数据,当 diffs 里有多个对同个合约的修改时,保持数据截面的一致
104168
expire_rest_days = _get_expire_rest_days(expire_datetime, self._datetime_state.get_current_dt() / 1e9)
105-
pend_diff[symbol] = {'expire_rest_days': expire_rest_days}
106-
return {'quotes': pend_diff} if pend_diff else {}
169+
diff['quotes'][symbol]['expire_rest_days'] = expire_rest_days
107170

108-
def _update_positions(self, diff):
171+
def _get_positions_pend_diff(self):
109172
pend_diff = {}
110-
for account_key in diff['trade']:
111-
for symbol in diff['trade'].get(account_key, {}).get('positions', {}):
112-
pos = diff['trade'][account_key]['positions'][symbol]
113-
if 'pos_long_his' in pos or 'pos_long_today' in pos or 'pos_short_his' in pos or 'pos_short_today' in pos:
114-
data_pos = self._data['trade'][account_key]['positions'][symbol]
115-
pos_long = data_pos['pos_long_his'] + data_pos['pos_long_today']
116-
pos_short = data_pos['pos_short_his'] + data_pos['pos_short_today']
117-
pend_diff.setdefault(account_key, {})
118-
pend_diff[account_key].setdefault('positions', {})
173+
for account_key in self._data['trade']:
174+
positions = self._data['trade'][account_key].get('positions', {})
175+
for symbol, pos in positions.items():
176+
paths = [('trade', account_key, 'positions', symbol) + (key, )
177+
for key in ['pos_long_his', 'pos_long_today', 'pos_short_his', 'pos_short_today']]
178+
if any([p in self._diffs_paths for p in paths]):
179+
pos_long = pos['pos_long_his'] + pos['pos_long_today']
180+
pos_short = pos['pos_short_his'] + pos['pos_short_today']
181+
pend_diff.setdefault(account_key, {}).setdefault('positions', {})
119182
pend_diff[account_key]['positions'][symbol] = {
120183
'pos_long': pos_long,
121184
'pos_short': pos_short,
122185
'pos': pos_long - pos_short
123186
}
124187
return {'trade': pend_diff} if pend_diff else {}
125188

189+
def _get_trade_price(self, account_key, order_id):
190+
# 计算某个 order_id 对应的成交均价
191+
trades = self._data['trade'][account_key]['trades']
192+
trade_id_list = [t_id for t_id in trades.keys() if trades[t_id]['order_id'] == order_id]
193+
sum_volume = sum([trades[t_id]['volume'] for t_id in trade_id_list])
194+
if sum_volume == 0:
195+
return float('nan')
196+
else:
197+
sum_amount = sum([trades[t_id]['volume'] * trades[t_id]['price'] for t_id in trade_id_list])
198+
return sum_amount / sum_volume
199+
126200
async def _send_diff(self):
127201
if self._datetime_state.data_ready and self._pending_peek and self._diffs:
128202
# 生成增量业务截面, 该截面包含补充的字段,只在真正需要给下游发送数据时,才将需要发送的数据放在 _diffs 中

0 commit comments

Comments
 (0)