diff --git a/PKG-INFO b/PKG-INFO
index c3f692ae..20594945 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: tqsdk
-Version: 2.9.2
+Version: 2.9.3
Summary: TianQin SDK
Home-page: https://www.shinnytech.com/tqsdk
Author: TianQin
diff --git a/doc/conf.py b/doc/conf.py
index a2997077..4ee31fb0 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -48,9 +48,9 @@
# built documents.
#
# The short X.Y version.
-version = u'2.9.2'
+version = u'2.9.3'
# The full version, including alpha/beta/rc tags.
-release = u'2.9.2'
+release = u'2.9.3'
# 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 248fd75d..030b14aa 100644
--- a/doc/profession.rst
+++ b/doc/profession.rst
@@ -61,9 +61,9 @@ TqSdk 免费版本提供全部的期货、商品/金融期权和上证50、沪
其他相关函数
-------------------------------------------------
- :py:meth:`~tqsdk.api.TqApi.query_symbol_ranking`交易所每日成交持仓排名
+ :py:meth:`~tqsdk.api.TqApi.query_symbol_ranking` 交易所每日成交持仓排名
- :py:meth:`~tqsdk.api.TqApi.get_kline_data_series`以起始日期获取 Dataframe 格式的 kline 数据
+ :py:meth:`~tqsdk.api.TqApi.get_kline_data_series` 以起始日期获取 Dataframe 格式的 kline 数据
:py:meth:`~tqsdk.api.TqApi.get_trading_status` 获取指定合约的交易状态,帮助用户实现开盘/跨小节抢单
diff --git a/doc/quickstart.rst b/doc/quickstart.rst
index 970ba084..58f04694 100644
--- a/doc/quickstart.rst
+++ b/doc/quickstart.rst
@@ -269,7 +269,7 @@ klines是一个pandas.DataFrame对象. 跟 api.get_quote() 一样, api.get_kline
刚刚注册完成的信易账户的【手机号】/【邮箱地址】/【用户名】和【密码】可以作为 快期模拟 账号,通过 :py:class:`~tqsdk.api.TqKq` 对 auth 传入参数进行登录,这个 快期模拟 账户在快期APP、快期V3 pro 和天勤量化上是互通的
-快期模拟的资金可以通过快期APP、快期V3 pro的模拟银行进行出入金::
+快期模拟的资金可以通过快期APP、快期专业版的模拟银行进行出入金::
from tqsdk import TqApi, TqAuth, TqKq
diff --git a/doc/reference/index.rst b/doc/reference/index.rst
index 54c726f1..5627b8b0 100644
--- a/doc/reference/index.rst
+++ b/doc/reference/index.rst
@@ -17,5 +17,6 @@ TqSdk 模块参考
tqsdk.sim.rst
tqsdk.backtest.rst
tqsdk.algorithm.rst
+ tqsdk.risk_rule.rst
tqsdk.tools.download.rst
tqsdk.exceptions.rst
diff --git a/doc/reference/tqsdk.risk_rule.rst b/doc/reference/tqsdk.risk_rule.rst
new file mode 100644
index 00000000..4229271d
--- /dev/null
+++ b/doc/reference/tqsdk.risk_rule.rst
@@ -0,0 +1,6 @@
+.. _tqsdk.risk_rule:
+
+tqsdk.risk_rule - 风控类模块
+------------------------------------------------------------------
+.. automodule:: tqsdk.risk_rule
+ :members:
diff --git a/doc/usage/trade.rst b/doc/usage/trade.rst
index 3e926eb5..31b9d3b6 100644
--- a/doc/usage/trade.rst
+++ b/doc/usage/trade.rst
@@ -49,7 +49,7 @@ TqApi 创建成功即代表相应账户已登录成功. 如果在60秒内无法
----------------------------------------------------
如果您需要使用快期模拟账户进行测试,只需在创建TqApi时传入一个 :py:class:`~tqsdk.account.TqKq` 的实例,同时需要传入信易账户 :ref:`sim_trading`。
-此账户类型与快期 APP 、天勤官网论坛、快期V3使用相同的模拟账户系统::
+此账户类型与快期 APP 、天勤官网论坛、快期专业版使用相同的模拟账户系统::
from tqsdk import TqApi, TqAuth, TqKq
diff --git a/doc/version.rst b/doc/version.rst
index 84fc15af..169ee7d8 100644
--- a/doc/version.rst
+++ b/doc/version.rst
@@ -2,6 +2,13 @@
版本变更
=============================
+2.9.3 (2021/10/28)
+
+* 增加::py:class:`~tqsdk.risk_rule.TqRuleOpenCountsLimit`、:py:class:`~tqsdk.risk_rule.TqRuleOpenVolumesLimit` 类,
+ 以及 :py:meth:`~tqsdk.api.TqApi.add_risk_rule`、:py:meth:`~tqsdk.api.TqApi.delete_risk_rule` 接口,支持本地风控功能
+* 增加::py:class:`~tqsdk.exceptions.TqRiskRuleError` 错误类型,可以捕获风控触发的错误
+
+
2.9.2 (2021/10/20)
* 修复:实盘账户无法使用 :py:meth:`~tqsdk.api.TqApi.get_trading_status` 接口的问题
diff --git a/setup.py b/setup.py
index 4eb40327..9db144f1 100644
--- a/setup.py
+++ b/setup.py
@@ -36,7 +36,7 @@ def get_tag(self):
setuptools.setup(
name='tqsdk',
- version="2.9.2",
+ version="2.9.3",
description='TianQin SDK',
author='TianQin',
author_email='tianqincn@gmail.com',
diff --git a/tqsdk/__init__.py b/tqsdk/__init__.py
index 4e47d463..4f4cbdfc 100644
--- a/tqsdk/__init__.py
+++ b/tqsdk/__init__.py
@@ -8,7 +8,7 @@
from tqsdk.auth import TqAuth
from tqsdk.channel import TqChan
from tqsdk.backtest import TqBacktest, TqReplay
-from tqsdk.exceptions import BacktestFinished, TqTimeoutError
+from tqsdk.exceptions import BacktestFinished, TqTimeoutError, TqRiskRuleError
from tqsdk.lib import TargetPosScheduler, TargetPosTask, InsertOrderUntilAllTradedTask, InsertOrderTask, TqNotify
from tqsdk.sim import TqSim
from tqsdk.multiaccount import TqMultiAccount
diff --git a/tqsdk/__version__.py b/tqsdk/__version__.py
index c0d35b3f..4ef1be1f 100644
--- a/tqsdk/__version__.py
+++ b/tqsdk/__version__.py
@@ -1 +1 @@
-__version__ = '2.9.2'
+__version__ = '2.9.3'
diff --git a/tqsdk/api.py b/tqsdk/api.py
index 877163b4..a976fd01 100644
--- a/tqsdk/api.py
+++ b/tqsdk/api.py
@@ -74,6 +74,8 @@
from tqsdk.objs import SecurityAccount, SecurityOrder, SecurityTrade, SecurityPosition
from tqsdk.objs_not_entity import QuoteList, TqDataFrame, TqSymbolDataFrame, SymbolList, SymbolLevelList, \
TqSymbolRankingDataFrame, TqOptionGreeksDataFrame
+from tqsdk.risk_manager import TqRiskManager
+from tqsdk.risk_rule import TqRiskRule
from tqsdk.sim import TqSim
from tqsdk.symbols import TqSymbols
from tqsdk.trading_status import TqTradingStatus
@@ -243,6 +245,7 @@ def __init__(self, account: Union[TqMultiAccount, TqAccount, TqSim, None] = None
self._td_url = _td_url
# 内部关键数据
+ self._risk_manager = TqRiskManager()
self._requests = {
"trading_status": set(),
"quotes": set(),
@@ -376,13 +379,13 @@ def get_quote(self, symbol: str) -> Quote:
注意:
1. 在 tqsdk 还没有收到行情数据包时, 此对象中各项内容为 NaN 或 0
2. 天勤接口从0.8版本开始,合约代码格式变更为 交易所代码.合约代码 的格式. 可用的交易所代码如下:
- * CFFEX: 中金所
- * SHFE: 上期所
- * DCE: 大商所
- * CZCE: 郑商所
- * INE: 能源交易所(原油)
- * SSE: 上交所
- * SZSE: 深交所
+ * CFFEX: 中金所
+ * SHFE: 上期所
+ * DCE: 大商所
+ * CZCE: 郑商所
+ * INE: 能源交易所(原油)
+ * SSE: 上交所
+ * SZSE: 深交所
Example1::
@@ -443,13 +446,13 @@ def get_quote_list(self, symbols: List[str]) -> List[Quote]:
注意:
1. 在 tqsdk 还没有收到行情数据包时, 此对象中各项内容为 NaN 或 0
2. 天勤接口从0.8版本开始,合约代码格式变更为 交易所代码.合约代码 的格式. 可用的交易所代码如下:
- * CFFEX: 中金所
- * SHFE: 上期所
- * DCE: 大商所
- * CZCE: 郑商所
- * INE: 能源交易所(原油)
- * SSE: 上交所
- * SZSE: 深交所
+ * CFFEX: 中金所
+ * SHFE: 上期所
+ * DCE: 大商所
+ * CZCE: 郑商所
+ * INE: 能源交易所(原油)
+ * SSE: 上交所
+ * SZSE: 深交所
Example::
@@ -1082,6 +1085,37 @@ def query_his_cont_quotes(self, symbol: Union[str, List[str]], n: int = 200):
df.reset_index(inplace=True, drop=True)
return df
+ # ----------------------------------------------------------------------
+ def add_risk_rule(self, rule: TqRiskRule):
+ """
+ 添加一项风控规则实例,此接口为 TqSdk 专业版提供。
+
+ 如需使用此功能,可以点击 `天勤量化专业版 `_ 申请试用或购买
+
+ Args:
+ rule (TqRiskRule): 风控规则实例,必须是 TqRiskRule 的子类型
+
+ """
+ if not self._auth._has_feature("tq_lc_rk"):
+ raise Exception("本地风控功能仅限专业版用户使用,如需购买专业版或者申请试用,请访问 https://www.shinnytech.com/tqsdk_professional/")
+ if not isinstance(rule, TqRiskRule):
+ raise Exception("传入参数对象必须是 TqRiskRule 的类型")
+ self._risk_manager.append(rule)
+
+ def delete_risk_rule(self, rule: TqRiskRule):
+ """
+ 删除一项风控规则实例,此接口为 TqSdk 专业版提供。
+
+ 如需使用此功能,可以点击 `天勤量化专业版 `_ 申请试用或购买
+
+ Args:
+ rule (TqRiskRule): 风控规则实例,必须是 TqRiskRule 的子类型
+
+ """
+ if not isinstance(rule, TqRiskRule):
+ raise Exception("传入参数对象必须是 TqRiskRule 的类型")
+ self._risk_manager.remove(rule)
+
# ----------------------------------------------------------------------
def insert_order(self, symbol: str, direction: str, offset: str = "", volume: int = 0,
limit_price: Union[str, float, None] = None,
@@ -1322,7 +1356,8 @@ def _get_insert_order_pack(self, symbol, direction, offset, volume, limit_price,
msg["contingent_condition"] = "UNKNOWN"
msg["volume_condition"] = "UNKNOWN"
msg["time_condition"] = "UNKNOWN"
-
+ self._risk_manager._could_insert_order(msg)
+ self._risk_manager._on_insert_order(msg)
return msg
async def _insert_order_async(self, symbol, direction, offset, volume, limit_price, advanced, order_id,
@@ -2194,20 +2229,20 @@ def query_symbol_ranking(self, symbol: str, ranking_type: str, days: int = 1, st
Returns:
pandas.DataFrame: 本函数返回 pandas.DataFrame 实例。行数为 days * 20,每行为一条成交量/多头持仓量/空头持仓量的排名信息。返回值不会再更新。包含以下列:
- * datetime (查询日期)
- * symbol (合约代码,以交易所列出的期货公司名称为准)
- * exchange_id (交易所)
- * instrument_id (交易所内合约代码)
- * broker (期货公司)
- * volume (成交量)
- * volume_change (成交量变化)
- * volume_ranking (成交量排名)
- * long_oi (多头持仓量)
- * long_change (多头持仓增减量)
- * long_ranking (多头持仓量排名)
- * short_oi (空头持仓量)
- * short_change (空头持仓增减量)
- * short_ranking (空头持仓量排名)
+ * datetime (查询日期)
+ * symbol (合约代码,以交易所列出的期货公司名称为准)
+ * exchange_id (交易所)
+ * instrument_id (交易所内合约代码)
+ * broker (期货公司)
+ * volume (成交量)
+ * volume_change (成交量变化)
+ * volume_ranking (成交量排名)
+ * long_oi (多头持仓量)
+ * long_change (多头持仓增减量)
+ * long_ranking (多头持仓量排名)
+ * short_oi (空头持仓量)
+ * short_change (空头持仓增减量)
+ * short_ranking (空头持仓量排名)
注意:
1. 返回值中 datetime、symbol、exchange_id、instrument_id、broker 这几列一定为有效值。其他列会根据不同的 ranking_type 参数值,可能返回 nan:
diff --git a/tqsdk/exceptions.py b/tqsdk/exceptions.py
index 5ab9c1d1..9f2b8c90 100644
--- a/tqsdk/exceptions.py
+++ b/tqsdk/exceptions.py
@@ -105,3 +105,13 @@ class TqBacktestPermissionError(Exception):
def __init__(self, message):
super().__init__(message)
self.message = message
+
+
+class TqRiskRuleError(Exception):
+ """
+ 风控触发的报错
+ """
+
+ def __init__(self, message):
+ super().__init__(message)
+ self.message = message
diff --git a/tqsdk/risk_manager.py b/tqsdk/risk_manager.py
new file mode 100644
index 00000000..13faabed
--- /dev/null
+++ b/tqsdk/risk_manager.py
@@ -0,0 +1,30 @@
+#!usr/bin/env python3
+# -*- coding:utf-8 -*-
+
+__author__ = 'mayanqiong'
+
+from tqsdk.exceptions import TqRiskRuleError
+
+
+class TqRiskManager(list):
+
+ def append(self, rule):
+ if rule not in self:
+ super(TqRiskManager, self).append(rule)
+
+ def remove(self, rule):
+ if rule in self:
+ super(TqRiskManager, self).remove(rule)
+
+ def _could_insert_order(self, pack):
+ # 是否可以下单
+ for r in self:
+ is_valid, err_msg = r._could_insert_order(pack)
+ if not is_valid:
+ raise TqRiskRuleError(err_msg)
+ return True
+
+ def _on_insert_order(self, pack):
+ # 需要更新风控对象内部统计值
+ for r in self:
+ r._on_insert_order(pack)
diff --git a/tqsdk/risk_rule.py b/tqsdk/risk_rule.py
new file mode 100644
index 00000000..e14be96b
--- /dev/null
+++ b/tqsdk/risk_rule.py
@@ -0,0 +1,189 @@
+#!usr/bin/env python3
+# -*- coding:utf-8 -*-
+
+__author__ = 'mayanqiong'
+
+
+from abc import abstractmethod
+
+
+class TqRiskRule(object):
+
+ def __init__(self, api, account=None):
+ # 记录必要参数, 每个风控规则都需要继承这个基类
+ self._api = api
+ account = self._api._account._check_valid(account)
+ if account is None:
+ raise Exception(f"多账户模式下, 需要指定账户实例 account")
+ self._account = account
+ self._account_key = self._account._account_key
+
+ @abstractmethod
+ def _could_insert_order(self, pack) -> (bool, str): # 是否可以下单
+ pass
+
+ @abstractmethod
+ def _on_insert_order(self, pack):
+ pass
+
+
+class TqRuleOpenCountsLimit(TqRiskRule):
+ """
+ 风控规则类 - 交易日内开仓次数限制。
+
+ 此功能为 TqSdk 专业版提供,如需使用此功能,可以点击 `天勤量化专业版 `_ 申请试用或购买
+ """
+
+ def __init__(self, api, open_counts_limit, symbol, account=None):
+ """
+ Args:
+ api (TqApi): TqApi 实例
+
+ open_volumes_limit (int): 交易日内开仓手数上限
+
+ symbol (str/list of str): 负责限制的合约代码或合约代码列表.
+ * str: 一个合约代码
+ * list of str: 合约代码列表
+
+ account (TqAccount/TqKq/TqSim): [可选] 指定发送下单指令的账户实例, 多账户模式下,该参数必须指定
+
+ Example1::
+
+ from tqsdk import TqApi
+ from tqsdk.risk_rule import TqRuleOpenCountsLimit
+
+ api = TqApi(auth=TqAuth("信易账户", "账户密码"))
+
+ rule = TqRuleOpenCountsLimit(api, open_counts_limit=10, symbol="DCE.m2112") # 创建风控规则实例
+ api.add_risk_rule(rule) # 添加风控规则
+
+ quote = api.get_quote("DCE.m2112")
+ try:
+ # 每次最新价变动,下一笔订单,直到超过开仓次数风控限制
+ while True:
+ api.wait_update()
+ if api.is_changing(quote, ['last_price']):
+ order = api.insert_order(symbol="DCE.m2112", direction="BUY", offset="OPEN", volume=1)
+ while order.status != "FINISHED":
+ api.wait_update()
+ except TqRiskRuleError as e:
+ print('!!!', e)
+ api.close()
+
+ """
+ super(TqRuleOpenCountsLimit, self).__init__(api=api, account=account)
+ if open_counts_limit < 0:
+ raise Exception("参数 open_volumes_limit 必须大于 0 的数字")
+ self.open_counts_limit = open_counts_limit
+ self.symbol_list = [symbol] if isinstance(symbol, str) else symbol
+ self.data = {s: 0 for s in self.symbol_list}
+ for order_id, order in self._api._data.get('trade', {}).get(self._account_key, {}).get('orders', {}).items():
+ symbol = order["exchange_id"] + "." + order["instrument_id"]
+ if order["offset"] == "OPEN" and symbol in self.data:
+ self.data[symbol] += 1
+
+ def _could_insert_order(self, pack) -> {bool, str}:
+ if pack['account_key'] == self._account_key:
+ symbol = pack["exchange_id"] + "." + pack["instrument_id"]
+ if pack["offset"] == "OPEN" and symbol in self.symbol_list:
+ if self.data[symbol] + 1 > self.open_counts_limit:
+ return False, f"触发风控规则,合约 {symbol} 开仓到达交易日内开仓次数限制 {self.open_counts_limit}, " \
+ f"已下单次数 {self.data[symbol]}"
+ return True, ""
+
+ def _on_insert_order(self, pack):
+ if pack['account_key'] == self._account_key:
+ symbol = pack["exchange_id"] + "." + pack["instrument_id"]
+ if pack["offset"] == "OPEN" and symbol in self.symbol_list:
+ self.data[symbol] += 1
+
+
+class TqRuleOpenVolumesLimit(TqRiskRule):
+ """
+ 风控规则类 - 交易日内开仓手数限制
+
+ 此功能为 TqSdk 专业版提供,如需使用此功能,可以点击 `天勤量化专业版 `_ 申请试用或购买
+ """
+
+ def __init__(self, api, open_volumes_limit, symbol, account=None):
+ """
+ Args:
+ api (TqApi): TqApi 实例
+
+ open_volumes_limit (int): 交易日内开仓手数上限
+
+ symbol (str/list of str): 负责限制的合约代码或合约代码列表.
+ * str: 一个合约代码
+ * list of str: 合约代码列表
+
+ account (TqAccount/TqKq/TqSim): [可选] 指定发送下单指令的账户实例, 多账户模式下,该参数必须指定
+
+ Example1::
+
+ from tqsdk import TqApi
+ from tqsdk.risk_rule import TqRuleOpenVolumesLimit
+
+ api = TqApi(auth=TqAuth("信易账户", "账户密码"))
+
+ rule = TqRuleOpenVolumesLimit(api, open_volumes_limit=10, symbol="DCE.m2112") # 创建风控规则实例
+ api.add_risk_rule(rule) # 添加风控规则
+
+ # 下单 5 手,不会触发风控规则
+ order1 = api.insert_order(symbol="DCE.m2112", direction="BUY", offset="OPEN", volume=5)
+ while order1.status != "FINISHED":
+ api.wait_update()
+
+ # 继续下单 8 手,会触发风控规则
+ order2 = api.insert_order(symbol="DCE.m2112", direction="BUY", offset="OPEN", volume=8)
+ while order2.status != "FINISHED":
+ api.wait_update()
+ api.close()
+
+
+ Example2::
+
+
+ from tqsdk import TqApi, TqKq, TqRiskRuleError
+ from tqsdk.risk_rule import TqRuleOpenVolumesLimit
+
+ account = TqKq()
+ api = TqApi(account=account, auth=TqAuth("信易账户", "账户密码"))
+
+ rule = TqRuleOpenVolumesLimit(api, open_volumes_limit=10, symbol="DCE.m2112", account=account) # 创建风控规则实例
+ api.add_risk_rule(rule) # 添加风控规则
+
+ try:
+ # 下单 11 手,触发风控规则
+ order1 = api.insert_order(symbol="DCE.m2112", direction="BUY", offset="OPEN", volume=11)
+ while order1.status != "FINISHED":
+ api.wait_update()
+ except TqRiskRuleError as e:
+ print("!!!", e)
+
+ api.close()
+ """
+ super(TqRuleOpenVolumesLimit, self).__init__(api=api, account=account)
+ if open_volumes_limit < 0:
+ raise Exception("参数 open_volumes_limit 必须大于 0 的数字")
+ self.open_volumes_limit = open_volumes_limit
+ self.symbol_list = [symbol] if isinstance(symbol, str) else symbol
+ self.data = {s: 0 for s in self.symbol_list}
+ for order_id, order in self._api._data.get('trade', {}).get(self._account_key, {}).get('orders', {}).items():
+ symbol = order["exchange_id"] + "." + order["instrument_id"]
+ if order["offset"] == "OPEN" and symbol in self.data:
+ self.data[symbol] += order["volume"]
+
+ def _could_insert_order(self, pack) -> {bool, str}:
+ if pack['account_key'] == self._account_key:
+ symbol = pack["exchange_id"] + "." + pack["instrument_id"]
+ if pack["offset"] == "OPEN" and symbol in self.symbol_list:
+ if self.data[symbol] + pack["volume"] > self.open_volumes_limit:
+ return False, f"触发风控规则,合约 {symbol} 开仓到达交易日内开仓手数限制 {self.open_volumes_limit}, " \
+ f"已下单手数 {self.data[symbol]}, 即将下单手数 {pack['volume']}"
+ return True, ""
+
+ def _on_insert_order(self, pack):
+ if pack['account_key'] == self._account_key:
+ symbol = pack["exchange_id"] + "." + pack["instrument_id"]
+ if pack["offset"] == "OPEN" and symbol in self.symbol_list:
+ self.data[symbol] += pack["volume"]