Skip to content

Commit

Permalink
Update Version 2.8.3
Browse files Browse the repository at this point in the history
  • Loading branch information
shinny-pack authored and shinny-mayanqiong committed Aug 31, 2021
1 parent 41d8f69 commit 5b6a1ae
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 108 deletions.
2 changes: 1 addition & 1 deletion PKG-INFO
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: tqsdk
Version: 2.8.2
Version: 2.8.3
Summary: TianQin SDK
Home-page: https://www.shinnytech.com/tqsdk
Author: TianQin
Expand Down
4 changes: 2 additions & 2 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
# built documents.
#
# The short X.Y version.
version = u'2.8.2'
version = u'2.8.3'
# The full version, including alpha/beta/rc tags.
release = u'2.8.2'
release = u'2.8.3'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
4 changes: 2 additions & 2 deletions doc/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

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

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

.. _quickstart_0:

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

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

.. figure:: images/web_gui_klines.png
.. figure:: images/web_gui_demo.png

具体请见 :ref:`web_gui`

Expand Down
2 changes: 1 addition & 1 deletion doc/usage/mddatas.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ SZSE 深圳证券交易所

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

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


.. image:
Expand Down
2 changes: 1 addition & 1 deletion doc/usage/option_trade.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ TqSdk 内提供了丰富的期权指标计算&序列计算函数,参考如下

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



Expand Down
9 changes: 9 additions & 0 deletions doc/version.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

版本变更
=============================
2.8.3 (2021/08/30)

* 增加:is_changing 接口增加对于委托单 :py:meth:`~tqsdk.objs.Order.is_dead`、:py:meth:`~tqsdk.objs.Order.is_online`、
:py:meth:`~tqsdk.objs.Order.is_error`、:py:meth:`~tqsdk.objs.Order.trade_price` 字段支持判断是否更新
* 修复: TqApi 初始化可能失败的问题
* 优化: 将已知下市合约直接打包在代码中,缩短 TqApi 初始化时间
* docs: 完善主力切换规则说明,将阿里源替换为清华源


2.8.2 (2021/08/17)

* 增加:is_changing 接口增加对于合约 :py:meth:`~tqsdk.objs.Quote.expire_rest_days`,持仓 :py:meth:`~tqsdk.objs.Position.pos_long`、
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def get_tag(self):

setuptools.setup(
name='tqsdk',
version="2.8.2",
version="2.8.3",
description='TianQin SDK',
author='TianQin',
author_email='[email protected]',
Expand Down
2 changes: 1 addition & 1 deletion tqsdk/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '2.8.2'
__version__ = '2.8.3'
40 changes: 18 additions & 22 deletions tqsdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import asyncio
import copy
import logging
import lzma
import json
import os
import platform
import re
Expand Down Expand Up @@ -116,15 +118,14 @@ def __init__(self, account: Union[TqMultiAccount, TqAccount, TqSim, None] = None
信易账户注册链接 https://www.shinnytech.com/register-intro/
url (str): [可选]指定服务器的地址
* 当 account 为 :py:class:`~tqsdk.account.TqAccount` 类型时, 可以通过该参数指定交易服务器地址, \
默认使用 wss://opentd.shinnytech.com/trade/user0, 行情始终使用 wss://openmd.shinnytech.com/t/md/front/mobile
* 当 account 为 :py:class:`~tqsdk.account.TqAccount`、:py:class:`~tqsdk.multiaccount.TqMultiAccount` 类型时, 可以通过该参数指定交易服务器地址,\
默认使用对应账户的交易服务地址,行情地址该信易账户对应的行情服务地址
* 当 account 为 :py:class:`~tqsdk.sim.TqSim` 类型时, 可以通过该参数指定行情服务器地址,\
默认使用 wss://openmd.shinnytech.com/t/md/front/mobile
* 当 account 为 :py:class:`~tqsdk.sim.TqSim` 类型时, 可以通过该参数指定行情服务器地址, 默认使用该信易账户对应的行情服务地址
backtest (TqBacktest/TqReplay): [可选] 进入时光机,此时强制要求 account 类型为 :py:class:`~tqsdk.sim.TqSim`
* :py:class:`~tqsdk.backtest.TqBacktest` : 传入 TqBacktest 对象,进入回测模式 \
在回测模式下, TqBacktest 连接 wss://openmd.shinnytech.com/t/md/front/mobile 接收行情数据, \
在回测模式下, TqBacktest 连接 wss://backtest.shinnytech.com/t/md/front/mobile 接收行情数据, \
由 TqBacktest 内部完成回测时间段内的行情推进和 K 线、Tick 更新.
* :py:class:`~tqsdk.backtest.TqReplay` : 传入 TqReplay 对象, 进入复盘模式 \
Expand Down Expand Up @@ -2790,29 +2791,22 @@ def _setup_connection(self):

if self._stock is False: # self._stock == False 需要旧版的合约服务文件
quotes = self._fetch_symbol_info(self._ins_url)
if isinstance(self._backtest, TqBacktest):
_quotes_add_night(quotes)
ws_md_recv_chan.send_nowait({
"aid": "rtn_data",
"data": [{
"quotes": quotes
}]
}) # 获取合约信息
else: # todo: self._stock == True 新版合约服务没有已下市合约
quotes = self._fetch_symbol_info(os.getenv("TQ_INS_URL", "https://openmd.shinnytech.com/t/md/symbols/2020-09-15.json"))
if isinstance(self._backtest, TqBacktest):
_quotes_add_night(quotes)
ws_md_recv_chan.send_nowait({
"aid": "rtn_data",
"data": [{
"quotes": {k: v for k, v in quotes.items() if v["expired"] is True}
}]
}) # 获取合约信息
dir_path = os.path.dirname(os.path.realpath(__file__))
with lzma.open(os.path.join(dir_path, "expired_quotes.json.lzma"), "rt", encoding="utf-8") as f:
quotes = json.loads(f.read())

if isinstance(self._backtest, TqBacktest):
_quotes_add_night(quotes)
# 期权增加了 exercise_year、exercise_month 在旧版合约服务中没有,需要添加,使用下市日期代替最后行权日
for quote in quotes.values():
if quote["ins_class"] == "FUTURE_OPTION":
quote["exercise_year"] = datetime.fromtimestamp(quote["expire_datetime"]).year
quote["exercise_month"] = datetime.fromtimestamp(quote["expire_datetime"]).month
ws_md_recv_chan.send_nowait({
"aid": "rtn_data",
"data": [{"quotes": quotes}]
}) # 获取合约信息

conn = TqConnect(md_logger)
self.create_task(conn._run(self, self._md_url, ws_md_send_chan, ws_md_recv_chan))
Expand Down Expand Up @@ -2879,6 +2873,8 @@ def _setup_connection(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._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")
Expand Down
122 changes: 98 additions & 24 deletions tqsdk/data_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from tqsdk.datetime import _get_expire_rest_days
from tqsdk.datetime_state import TqDatetimeState
from tqsdk.diff import _simple_merge_diff
from tqsdk.diff import _simple_merge_diff, _is_key_exist, _simple_merge_diff_and_collect_paths, _get_obj


class DataExtension(object):
Expand All @@ -25,6 +25,14 @@ class DataExtension(object):
'pos_short': int,
'pos': int
}
},
ordres: {
*: {
'is_dead': bool
'is_online': bool
'is_error': bool
'trade_price': float
}
}
}
}
Expand All @@ -35,6 +43,30 @@ def __init__(self, api):
self._api = api
self._data = {'trade': {}} # 数据截面, 现在的功能只需要记录 trade
self._diffs = []
self._diffs_paths = set()
self._prototype = {
"trade": {
"*": {
"orders": {
"*": {
"status": None,
"exchange_order_id": None
}
},
"trades": {
"*": None
},
"positions": {
"*": {
"pos_long_his": None,
"pos_long_today": None,
"pos_short_his": None,
"pos_short_today": None
}
}
}
}
}

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

async def _md_recv(self, pack):
"""将行情数据和交易数据合并至 self._data """
for d in pack.get("data", {}):
for d in pack.get("data", []):
self._datetime_state.update_state(d)
if d.get('trade', None):
_simple_merge_diff(self._data['trade'], d['trade'], reduce_diff=False)
_simple_merge_diff_and_collect_paths(
result=self._data['trade'],
diff=d['trade'],
path=('trade', ),
diff_paths=self._diffs_paths,
prototype=self._prototype['trade']
)
self._diffs.append(d)

def _generate_ext_diff(self):
Expand All @@ -86,43 +124,79 @@ def _generate_ext_diff(self):
此函数在 send_diff() 才会调用, self._datetime_state.data_ready 一定为 True,
调用 self._datetime_state.get_current_dt() 一定有正确的当前时间
"""
pend_diff = {}
for d in self._diffs:
if d.get('quotes', None):
_simple_merge_diff(pend_diff, self._update_quotes(d), reduce_diff=False)
if d.get('trade', None):
_simple_merge_diff(pend_diff, self._update_positions(d), reduce_diff=False)
self._update_quotes(d)
pend_diff = {}
_simple_merge_diff(pend_diff, self._get_positions_pend_diff(), reduce_diff=False)
orders_set = set() # 计算过委托单,is_dead、is_online、is_error
orders_price_set = set() # 根据成交计算哪些 order 需要重新计算平均成交价 trade_price
for path in self._diffs_paths:
if path[2] == 'orders':
_, account_key, _, order_id, _ = path
if (account_key, order_id) not in orders_set:
orders_set.add((account_key, order_id))
order = _get_obj(self._data, ['trade', account_key, 'orders', order_id])
if order:
pend_order = pend_diff.setdefault('trade', {}).setdefault(account_key, {}).setdefault('orders', {}).setdefault(order_id, {})
pend_order['is_dead'] = order['status'] == "FINISHED"
pend_order['is_online'] = order['exchange_order_id'] != "" and order['status'] == "ALIVE"
pend_order['is_error'] = order['exchange_order_id'] == "" and order['status'] == "FINISHED"
elif path[2] == 'trades':
_, account_key, _, trade_id = path
trade = _get_obj(self._data, path)
order_id = trade.get('order_id', '')
if order_id:
orders_price_set.add(('trade', account_key, 'orders', order_id))
for path in orders_price_set:
_, account_key, _, order_id = path
trade_price = self._get_trade_price(account_key, order_id)
if trade_price == trade_price:
pend_order = pend_diff.setdefault('trade', {}).setdefault(account_key, {}).setdefault('orders', {}).setdefault(order_id, {})
pend_order['trade_price'] = trade_price
self._diffs_paths = set()
return pend_diff

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

def _update_positions(self, diff):
def _get_positions_pend_diff(self):
pend_diff = {}
for account_key in diff['trade']:
for symbol in diff['trade'].get(account_key, {}).get('positions', {}):
pos = diff['trade'][account_key]['positions'][symbol]
if 'pos_long_his' in pos or 'pos_long_today' in pos or 'pos_short_his' in pos or 'pos_short_today' in pos:
data_pos = self._data['trade'][account_key]['positions'][symbol]
pos_long = data_pos['pos_long_his'] + data_pos['pos_long_today']
pos_short = data_pos['pos_short_his'] + data_pos['pos_short_today']
pend_diff.setdefault(account_key, {})
pend_diff[account_key].setdefault('positions', {})
for account_key in self._data['trade']:
positions = self._data['trade'][account_key].get('positions', {})
for symbol, pos in positions.items():
paths = [('trade', account_key, 'positions', symbol) + (key, )
for key in ['pos_long_his', 'pos_long_today', 'pos_short_his', 'pos_short_today']]
if any([p in self._diffs_paths for p in paths]):
pos_long = pos['pos_long_his'] + pos['pos_long_today']
pos_short = pos['pos_short_his'] + pos['pos_short_today']
pend_diff.setdefault(account_key, {}).setdefault('positions', {})
pend_diff[account_key]['positions'][symbol] = {
'pos_long': pos_long,
'pos_short': pos_short,
'pos': pos_long - pos_short
}
return {'trade': pend_diff} if pend_diff else {}

def _get_trade_price(self, account_key, order_id):
# 计算某个 order_id 对应的成交均价
trades = self._data['trade'][account_key]['trades']
trade_id_list = [t_id for t_id in trades.keys() if trades[t_id]['order_id'] == order_id]
sum_volume = sum([trades[t_id]['volume'] for t_id in trade_id_list])
if sum_volume == 0:
return float('nan')
else:
sum_amount = sum([trades[t_id]['volume'] * trades[t_id]['price'] for t_id in trade_id_list])
return sum_amount / sum_volume

async def _send_diff(self):
if self._datetime_state.data_ready and self._pending_peek and self._diffs:
# 生成增量业务截面, 该截面包含补充的字段,只在真正需要给下游发送数据时,才将需要发送的数据放在 _diffs 中
Expand Down
38 changes: 38 additions & 0 deletions tqsdk/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
__author__ = 'yanqiong'

import copy
from typing import Set, Union, Dict, Tuple

from tqsdk.entity import Entity

Expand Down Expand Up @@ -143,3 +144,40 @@ def _simple_merge_diff(result, diff, reduce_diff=True, persist=False):
del diff[key]
else:
result[key] = diff[key]


def _simple_merge_diff_and_collect_paths(result, diff, path: Tuple, diff_paths: Set, prototype: Union[Dict, None]):
"""
更新业务数据并收集指定节点的路径
默认行为 reduce_diff = True,表示函数运行完成后,diff 会更新为与 result 真正的有区别的字段。
这个函数只在 data_extension 中用到,diff 只使用一次,并且全部转发到 api,可以执行 reduce_diff = True 的行为。
:param result: 更新结果
:param diff: diff pack
:param path: 当前迭代 merge_diff 的节点路径
:param diff_paths: 收集指定节点的路径
:param prototype: 数据原型, 为 None 的节点路径会被记录在 diff_paths 集合中
:return:
"""
for key in list(diff.keys()):
if diff[key] is None:
result.pop(key, None)
if prototype and ('*' in prototype or key in prototype) and prototype['*' if '*' in prototype else key] is None:
diff_paths.add(path + (key, ))
elif isinstance(diff[key], dict):
target = result.setdefault(key, {})
sub_path = path + (key, )
sub_prototype = None
if prototype and ('*' in prototype or key in prototype):
sub_prototype = prototype['*' if '*' in prototype else key]
if sub_prototype is None:
diff_paths.add(sub_path)
_simple_merge_diff_and_collect_paths(target, diff[key], path=sub_path, prototype=sub_prototype, diff_paths=diff_paths)
if len(diff[key]) == 0:
del diff[key]
elif key in result and result[key] == diff[key]:
del diff[key]
else:
result[key] = diff[key]
# 只有确实有变更的字段,会出现在 diff_paths 里
if prototype and ('*' in prototype or key in prototype) and prototype['*' if '*' in prototype else key] is None:
diff_paths.add(path + (key, ))
Loading

0 comments on commit 5b6a1ae

Please sign in to comment.