Skip to content

Commit

Permalink
Update Version 3.2.3
Browse files Browse the repository at this point in the history
  • Loading branch information
shinny-pack authored and shinny-mayanqiong committed Feb 17, 2022
1 parent 29c4aad commit e73586d
Show file tree
Hide file tree
Showing 17 changed files with 102 additions and 98 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: 3.2.2
Version: 3.2.3
Summary: TianQin SDK
Home-page: https://www.shinnytech.com/tqsdk
Author: TianQin
Expand Down
18 changes: 9 additions & 9 deletions doc/advanced/for_vnpy_user.rst
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
.. _for_vnpy_user:

TqSdk 与 VNPY 有哪些差别
TqSdk 与 vn.py 有哪些差别
=================================================
TqSdk 与 VNPY 有非常多的差别. 如果您是一位有经验的 VNPY 用户, 刚开始接触 TqSdk, 下面的信息将帮助您尽快理解 TqSdk.
TqSdk 与 vn.py 有非常多的差别. 如果您是一位有经验的 vn.py 用户, 刚开始接触 TqSdk, 下面的信息将帮助您尽快理解 TqSdk.


系统整体架构
-------------------------------------------------
VNPY 是一套 all-in-one 的结构, 在一个Python软件包中包含了数据库, 行情接收/存储, 交易接口, 图形界面等功能.
vn.py 是一套 all-in-one 的结构, 在一个Python软件包中包含了数据库, 行情接收/存储, 交易接口, 图形界面等功能.

TqSdk 则使用基于网络协作的组件设计. 如下图:

Expand All @@ -31,14 +31,14 @@ TqSdk 则使用基于网络协作的组件设计. 如下图:
* 交易相关接口被大幅度简化, 不再需要处理CTP接口的复杂回调, 也不需要发起任何查询请求


也有一些不如VNPY方便的地方:
也有一些不如vn.py方便的地方:

* 由于交易指令经交易网关转发, 用户无法直接指定CTP服务器地址. 用户如果需要连接到官方交易网关不支持的期货公司, 需要自行部署交易网关.


每个策略是一个单独运行的py文件
-------------------------------------------------
VNPY 中, 要实现一个策略程序, 通常是从 CtaTemplate 等基类派生一个子类, 像这样::
vn.py 中, 要实现一个策略程序, 通常是从 CtaTemplate 等基类派生一个子类, 像这样::

class DoubleMaStrategy(CtaTemplate):

Expand All @@ -54,7 +54,7 @@ TqSdk 则使用基于网络协作的组件设计. 如下图:
def on_bar(self, bar: BarData):
...

这个 DoubleMaStrategy 类写好以后, 由VNPY的策略管理器负责加载运行. 整个程序结构中, VNPY作为调用方, 用户代码作为被调用方, 结构图是这样的:
这个 DoubleMaStrategy 类写好以后, 由vn.py的策略管理器负责加载运行. 整个程序结构中, vn.py作为调用方, 用户代码作为被调用方, 结构图是这样的:

.. raw:: html

Expand Down Expand Up @@ -150,12 +150,12 @@ TqSdk将每个策略作为一个独立进程运行, 这样就可以:

K线数据与指标计算
-------------------------------------------------
使用VNPY时, K线是由VNPY接收实时行情, 并在用户电脑上生成K线, 存储于用户电脑上的数据库中.
使用vn.py时, K线是由vn.py接收实时行情, 并在用户电脑上生成K线, 存储于用户电脑上的数据库中.

而在TqSdk中, K线数据和其它行情数据一样是由行情网关生成并推送的. 这带来了一些差别:

* 用户不再需要维护K线数据库. 用户电脑实时行情中断后, 也不再需要补历史数据
* 行情服务器生成K线时, 采用了按K线时间严格补全对齐的算法. 这与VNPY或其它软件有明显区别, 详见 https://www.shinnytech.com/blog/why-our-kline-different/
* 行情服务器生成K线时, 采用了按K线时间严格补全对齐的算法. 这与vn.py或其它软件有明显区别, 详见 https://www.shinnytech.com/blog/why-our-kline-different/
* 行情数据只在每次程序运行时通过网络获取, 不在用户硬盘保存. 如果策略研究工作需要大量静态历史数据, 我们推荐使用数据下载工具, 另行下载csv文件使用.

TqSdk中的K线序列采用 pandas.DataFrame 格式. pandas 提供了 `非常丰富的数据处理函数 <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html>`_ , 使我们可以非常方便的进行数据处理, 例如::
Expand All @@ -177,7 +177,7 @@ TqSdk 也通过 :py:mod:`tqsdk.tafunc` 提供了一批行情分析中常用的

数据接收和更新
-------------------------------------------------
VNPY按照事件回调模型设计, 使用 CtaTemplate 的 on_xxx 回调函数进行行情数据和回单处理::
vn.py按照事件回调模型设计, 使用 CtaTemplate 的 on_xxx 回调函数进行行情数据和回单处理::

class DoubleMaStrategy(CtaTemplate):
def on_tick(self, tick: TickData):
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'3.2.2'
version = u'3.2.3'
# The full version, including alpha/beta/rc tags.
release = u'3.2.2'
release = u'3.2.3'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
2 changes: 1 addition & 1 deletion doc/usage/shinny_account.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

用信易账户来模拟交易
-------------------------------------------------
注册完成的信易账户的【手机号】/【邮箱地址】/【用户名】和【密码】可以作为 快期模拟 账号,通过 :py:class:`~tqsdk.api.TqKq` 对 auth 传入参数进行登录,这个 快期模拟 账户在快期APP、快期V3 pro 和天勤量化上是互通的::
注册完成的信易账户的【手机号】/【邮箱地址】/【用户名】和【密码】可以作为 快期模拟 账号,通过 :py:class:`~tqsdk.api.TqKq` 对 auth 传入参数进行登录,这个 快期模拟 账户在快期APP、快期专业版 和天勤量化上是互通的::

from tqsdk import TqApi, TqAuth, TqKq
api = TqApi(TqKq(), auth=TqAuth("信易账户", "账户密码"))
Expand Down
8 changes: 8 additions & 0 deletions doc/version.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

版本变更
=============================
3.2.3 (2022/02/16)

* 修复:query_all_level_options 接口查询 ETF 期权可能报错的问题
* 修复:提升程序在连续订阅 K 线时的运行速度
* 修复:使用快期模拟账户交易,在断线重连后程序可能报错的问题
* docs:修正文档


3.2.2 (2022/01/26)

* 增加:支持在回测中使用本地风控模块
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="3.2.2",
version="3.2.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__ = '3.2.2'
__version__ = '3.2.3'
95 changes: 50 additions & 45 deletions tqsdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import time
import warnings
from datetime import datetime, date, timedelta
from typing import Union, List, Any, Optional, Coroutine, Callable, Tuple
from typing import Union, List, Any, Optional, Coroutine, Callable, Tuple, Dict

import numpy as np
import psutil
Expand Down Expand Up @@ -320,7 +320,7 @@ def copy(self) -> 'TqApi':
_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, False)
_merge_diff(slave_api._data, _copy_diff, slave_api._prototype, persist=False)
return slave_api

def close(self) -> None:
Expand Down Expand Up @@ -1024,7 +1024,7 @@ def get_trading_calendar(self, start_dt: Union[date, datetime], end_dt: Union[da
# ----------------------------------------------------------------------
def query_his_cont_quotes(self, symbol: Union[str, List[str]], n: int = 200):
"""
获取指定的主连合约最近 n 天的标的,可以处理的范围为 2003-01-01 ~ 2021-12-31。
获取指定的主连合约最近 n 天的标的,可以处理的范围为 2003-01-01 ~ 2022-12-31。
Args:
symbol (str/list of str): 指定主连合约代码或主连合约代码列表.
Expand Down Expand Up @@ -1709,7 +1709,7 @@ def get_risk_management_rule(self, exchange_id: Optional[str] = None, account: O
from tqsdk import TqApi, TqAuth, TqAccount
api = TqApi(TqAccount("H海通期货", "022631", "123456"), auth=TqAuth("信易账户", "账户密码"))
rule = api.get_risk_management_rule(exchange_id="SSE")
print(exchange_id, rule['enable']")
print(exchange_id, rule['enable'])
print("自成交限制:", rule.self_trade)
print("频繁报撤单限制:", rule.frequent_cancellation)
print("成交持仓比限制:", rule.trade_position_ratio)
Expand Down Expand Up @@ -1900,17 +1900,18 @@ def wait_update(self, deadline: Optional[float] = None, _task: Union[asyncio.Tas
if "trade" in d:
for k, v in d.get('trade').items():
prototype = self._security_prototype if self._account._is_stock_type(k) else self._prototype
_merge_diff(self._data, {'trade': {k: v} }, prototype, False)
_merge_diff(self._data, {'trade': {k: v}}, prototype, persist=False, reduce_diff=True)
# 非交易数据均按照期货处理逻辑
diff_without_trade = {k : v for k, v in d.items() if k != "trade"}
if diff_without_trade:
_merge_diff(self._data, diff_without_trade, self._prototype, False)
_merge_diff(self._data, diff_without_trade, self._prototype, persist=False, reduce_diff=True)
self._risk_manager._on_recv_data(self._diffs)
for _, serial in self._serials.items():
# K线df的更新与原始数据、left_id、right_id、more_data、last_id相关,其中任何一个发生改变都应重新计算df
# 注:订阅某K线后再订阅合约代码、周期相同但长度更短的K线时, 服务器不会再发送已有数据到客户端,即chart发生改变但内存中原始数据未改变。
# 检测到K线数据或chart的任何字段发生改变则更新serial的数据
if self.is_changing(serial["df"]) or self.is_changing(serial["chart"]):
if self._is_obj_changing(serial["df"], diffs=self._diffs, key=[]) \
or self._is_obj_changing(serial["chart"], diffs=self._diffs, key=[]):
if len(serial["root"]) == 1: # 订阅单个合约
self._update_serial_single(serial)
else: # 订阅多个合约
Expand Down Expand Up @@ -1958,16 +1959,18 @@ def is_changing(self, obj: Any, key: Union[str, List[str], None] = None) -> bool
"""
if obj is None:
return False
# is_changing 区分同步 / 异步中,根据不同的 diffs 判断
diffs = self._diffs if self._loop.is_running() else self._sync_diffs
if not isinstance(key, list):
key = [key] if key else []
if isinstance(obj, list):
for o in obj:
if self._is_obj_changing(o, key):
if self._is_obj_changing(o, diffs=diffs, key=key):
return True
else:
return self._is_obj_changing(obj, key)
return self._is_obj_changing(obj, diffs=diffs, key=key)

def _is_obj_changing(self, obj: Any, key: Union[str, List[str], None] = None) -> bool:
if not isinstance(key, list):
key = [key] if key else []
def _is_obj_changing(self, obj: Any, diffs: List[Dict[str, Any]], key: List[str]) -> bool:
try:
if isinstance(obj, pd.DataFrame):
if id(obj) in self._serials:
Expand Down Expand Up @@ -2007,8 +2010,7 @@ def _is_obj_changing(self, obj: Any, key: Union[str, List[str], None] = None) ->
paths = [obj["_path"]]
except (KeyError, IndexError):
return False
# is_changing 区分同步 / 异步中,根据不同的 diffs 判断
for diff in (self._diffs if self._loop.is_running() else self._sync_diffs):
for diff in diffs:
# 如果传入key:生成一个dict(key:序号,value: 字段), 遍历这个dict并在_is_key_exist()判断key是否存在
if (isinstance(obj, pd.DataFrame) or isinstance(obj, pd.Series)) and len(key) != 0:
k_dict = {}
Expand Down Expand Up @@ -2668,19 +2670,20 @@ def query_atm_options(self, underlying_symbol, underlying_price, price_level, op

def filter(query_result):
options = self._convert_query_result_to_list(query_result)
if options:
options = self._get_options_filtered(options, option_class=option_class, exercise_year=exercise_year, exercise_month=exercise_month, has_A=has_A)
options, option_0_index = self._get_options_sorted(options, underlying_price, option_class)
rst_options = []
for pl in price_level:
option_index = option_0_index - pl
if 0 <= option_index < len(options):
rst_options.append(options[option_index]["instrument_id"])
else:
rst_options.append(None)
return rst_options
else:
if len(options) == 0:
return []
options = self._get_options_filtered(options, option_class=option_class, exercise_year=exercise_year, exercise_month=exercise_month, has_A=has_A)
if len(options) == 0:
return []
options, option_0_index = self._get_options_sorted(options, underlying_price, option_class)
rst_options = []
for pl in price_level:
option_index = option_0_index - pl
if 0 <= option_index < len(options):
rst_options.append(options[option_index]["instrument_id"])
else:
rst_options.append(None)
return rst_options

return self._get_symbol_list(query=query, filter=filter)

Expand Down Expand Up @@ -2833,18 +2836,19 @@ def query_all_level_options(self, underlying_symbol, underlying_price, option_cl

def filter(query_result):
options = self._convert_query_result_to_list(query_result)
if options:
options = self._get_options_filtered(options, option_class=option_class, exercise_year=exercise_year, exercise_month=exercise_month, has_A=has_A)
options, option_0_index = self._get_options_sorted(options, underlying_price, option_class)
# 实值期权
in_money_options = [o['instrument_id'] for o in options[:option_0_index]]
# 平值期权
at_money_options = [options[option_0_index]['instrument_id']]
# 虚值期权
out_of_money_options = [o['instrument_id'] for o in options[option_0_index+1:]]
return in_money_options, at_money_options, out_of_money_options
else:
if len(options) == 0:
return [], [], []
options = self._get_options_filtered(options, option_class=option_class, exercise_year=exercise_year, exercise_month=exercise_month, has_A=has_A)
if len(options) == 0:
return [], [], []
options, option_0_index = self._get_options_sorted(options, underlying_price, option_class)
# 实值期权
in_money_options = [o['instrument_id'] for o in options[:option_0_index]]
# 平值期权
at_money_options = [options[option_0_index]['instrument_id']]
# 虚值期权
out_of_money_options = [o['instrument_id'] for o in options[option_0_index + 1:]]
return in_money_options, at_money_options, out_of_money_options

return self._get_symbol_level_list(query=query, filter=filter)

Expand Down Expand Up @@ -2939,15 +2943,16 @@ def query_all_level_finance_options(self, underlying_symbol, underlying_price, o

def filter(query_result):
options = self._convert_query_result_to_list(query_result)
if options:
options = self._get_options_filtered(options, option_class=option_class, has_A=has_A, nearbys=nearbys)
options, option_0_index = self._get_options_sorted(options, underlying_price, option_class)
in_money_options = [o['instrument_id'] for o in options[:option_0_index]] # 实值期权
at_money_options = [options[option_0_index]['instrument_id']] # 平值期权
out_of_money_options = [o['instrument_id'] for o in options[option_0_index + 1:]] # 虚值期权
return in_money_options, at_money_options, out_of_money_options
else:
if len(options) == 0:
return [], [], []
options = self._get_options_filtered(options, option_class=option_class, has_A=has_A, nearbys=nearbys)
if len(options) == 0:
return [], [], []
options, option_0_index = self._get_options_sorted(options, underlying_price, option_class)
in_money_options = [o['instrument_id'] for o in options[:option_0_index]] # 实值期权
at_money_options = [options[option_0_index]['instrument_id']] # 平值期权
out_of_money_options = [o['instrument_id'] for o in options[option_0_index + 1:]] # 虚值期权
return in_money_options, at_money_options, out_of_money_options

return self._get_symbol_level_list(query=query, filter=filter)

Expand Down
6 changes: 3 additions & 3 deletions tqsdk/backtest/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import asyncio
import math
from datetime import date, datetime
from typing import Union, Any
from typing import Union, Any, List, Dict

from tqsdk.backtest.utils import TqBacktestContinuous, TqBacktestDividend
from tqsdk.channel import TqChan
Expand Down Expand Up @@ -139,7 +139,7 @@ async def _run(self, api, sim_send_chan, sim_recv_chan, md_send_chan, md_recv_ch
self._had_any_generator = False # 回测过程中是否有过 generator 对象
self._sim_recv_chan_send_count = 0 # 统计向下游发送的 diff 的次数,每 1w 次执行一次 gc
self._quotes = {} # 记录 min_duration 记录某一合约的最小duration; sended_init_quote 是否已经过这个合约的初始行情
self._diffs: list[dict[str, Any]] = []
self._diffs: List[Dict[str, Any]] = []
self._is_first_send = True
md_task = self._api.create_task(self._md_handler())
try:
Expand Down Expand Up @@ -201,7 +201,7 @@ async def _md_handler(self):
})
recv_quotes = False
for d in pack.get("data", []):
_merge_diff(self._data, d, self._prototype, False)
_merge_diff(self._data, d, self._prototype, persist=False, reduce_diff=False)
# 收到的 quotes 转发给下游
quotes = d.get("quotes", {})
if quotes:
Expand Down
4 changes: 2 additions & 2 deletions tqsdk/connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ async def _run(self, api, api_send_chan, api_recv_chan, ws_send_chan, ws_recv_ch
self._pending_diffs.extend(pack_data)
for d in pack_data:
# _merge_diff 之后, self._data 会用于判断是否接收到了完整截面数据
_merge_diff(self._data, d, self._api._prototype, False)
_merge_diff(self._data, d, self._api._prototype, persist=False, reduce_diff=False)
if self._is_all_received():
# 重连后收到完整数据截面
self._un_processed = False
Expand Down Expand Up @@ -298,7 +298,7 @@ async def _run(self, api, api_send_chan, api_recv_chan, ws_send_chan, ws_recv_ch
self._data = Entity()
self._data._instance_entity([])
for d in self._pending_diffs:
_merge_diff(self._data, d, self._api._prototype, False)
_merge_diff(self._data, d, self._api._prototype, persist=False, reduce_diff=False)
# 发送所有 resend_request
for msg in self._resend_request.values():
# 这里必须用 send_nowait 而不是 send,因为如果使用异步写法,在循环中,代码可能执行到 send_task, 可能会修改 _resend_request
Expand Down
Loading

0 comments on commit e73586d

Please sign in to comment.