diff --git a/PKG-INFO b/PKG-INFO index 316056d9..4aa2a0a6 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: tqsdk -Version: 3.9.9 +Version: 3.10.1 Summary: TianQin SDK Home-page: https://www.shinnytech.com/tqsdk Author: TianQin @@ -10,7 +10,7 @@ Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent -Requires-Python: >=3.8 +Requires-Python: >=3.9 Description-Content-Type: text/markdown License-File: LICENSE @@ -20,7 +20,7 @@ License-File: LICENSE

- +

@@ -37,23 +37,23 @@ TqSdk 支持用户使用极少的代码量构建各种类型的量化交易策 from tqsdk import TqApi, TqAuth, TqAccount, TargetPosTask api = TqApi(TqAccount("H海通期货", "4003242", "123456"), auth=TqAuth("快期账户", "账户密码")) # 创建 TqApi 实例, 指定交易账户 -q_2309 = api.get_quote("SHFE.rb2309") # 订阅近月合约行情 -t_2309 = TargetPosTask(api, "SHFE.rb2309") # 创建近月合约调仓工具 -q_2401 = api.get_quote("SHFE.rb2401") # 订阅远月合约行情 -t_2401 = TargetPosTask(api, "SHFE.rb2401") # 创建远月合约调仓工具 +q_2610 = api.get_quote("SHFE.rb2610") # 订阅近月合约行情 +t_2610 = TargetPosTask(api, "SHFE.rb2610") # 创建近月合约调仓工具 +q_2701 = api.get_quote("SHFE.rb2701") # 订阅远月合约行情 +t_2701 = TargetPosTask(api, "SHFE.rb2701") # 创建远月合约调仓工具 while True: api.wait_update() # 等待数据更新 - spread = q_2309["last_price"] - q_2401["last_price"] # 计算近月合约-远月合约价差 + spread = q_2610["last_price"] - q_2701["last_price"] # 计算近月合约-远月合约价差 print("当前价差:", spread) if spread > 250: print("价差过高: 空近月,多远月") - t_2309.set_target_volume(-1) # 要求把2309合约调整为空头1手 - t_2401.set_target_volume(1) # 要求把2401合约调整为多头1手 + t_2610.set_target_volume(-1) # 要求把近月合约调整为空头1手 + t_2701.set_target_volume(1) # 要求把远月合约调整为多头1手 elif spread < 200: - print("价差回复: 清空持仓") # 要求把 2309 和 2401合约都调整为不持仓 - t_2309.set_target_volume(0) - t_2401.set_target_volume(0) + print("价差回复: 清空持仓") # 要求把近月和远月合约都调整为不持仓 + t_2610.set_target_volume(0) + t_2701.set_target_volume(0) ``` 要快速了解如何使用TqSdk,请访问我们的 [十分钟快速入门指南](https://doc.shinnytech.com/tqsdk/latest/quickstart.html)。 @@ -102,7 +102,7 @@ TqSdk提供的功能可以支持从简单到复杂的各类策略程序: ## 安装方法 -TqSdk 仅支持 Python 3.8 及更高版本。要安装 TqSdk,可使用 pip: +TqSdk 仅支持 Python 3.9 及更高版本。要安装 TqSdk,可使用 pip: ```bash pip install tqsdk diff --git a/README.md b/README.md index cbb1f1fc..771c0c3e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

- +

@@ -21,23 +21,23 @@ TqSdk 支持用户使用极少的代码量构建各种类型的量化交易策 from tqsdk import TqApi, TqAuth, TqAccount, TargetPosTask api = TqApi(TqAccount("H海通期货", "4003242", "123456"), auth=TqAuth("快期账户", "账户密码")) # 创建 TqApi 实例, 指定交易账户 -q_2309 = api.get_quote("SHFE.rb2309") # 订阅近月合约行情 -t_2309 = TargetPosTask(api, "SHFE.rb2309") # 创建近月合约调仓工具 -q_2401 = api.get_quote("SHFE.rb2401") # 订阅远月合约行情 -t_2401 = TargetPosTask(api, "SHFE.rb2401") # 创建远月合约调仓工具 +q_2610 = api.get_quote("SHFE.rb2610") # 订阅近月合约行情 +t_2610 = TargetPosTask(api, "SHFE.rb2610") # 创建近月合约调仓工具 +q_2701 = api.get_quote("SHFE.rb2701") # 订阅远月合约行情 +t_2701 = TargetPosTask(api, "SHFE.rb2701") # 创建远月合约调仓工具 while True: api.wait_update() # 等待数据更新 - spread = q_2309["last_price"] - q_2401["last_price"] # 计算近月合约-远月合约价差 + spread = q_2610["last_price"] - q_2701["last_price"] # 计算近月合约-远月合约价差 print("当前价差:", spread) if spread > 250: print("价差过高: 空近月,多远月") - t_2309.set_target_volume(-1) # 要求把2309合约调整为空头1手 - t_2401.set_target_volume(1) # 要求把2401合约调整为多头1手 + t_2610.set_target_volume(-1) # 要求把近月合约调整为空头1手 + t_2701.set_target_volume(1) # 要求把远月合约调整为多头1手 elif spread < 200: - print("价差回复: 清空持仓") # 要求把 2309 和 2401合约都调整为不持仓 - t_2309.set_target_volume(0) - t_2401.set_target_volume(0) + print("价差回复: 清空持仓") # 要求把近月和远月合约都调整为不持仓 + t_2610.set_target_volume(0) + t_2701.set_target_volume(0) ``` 要快速了解如何使用TqSdk,请访问我们的 [十分钟快速入门指南](https://doc.shinnytech.com/tqsdk/latest/quickstart.html)。 @@ -86,7 +86,7 @@ TqSdk提供的功能可以支持从简单到复杂的各类策略程序: ## 安装方法 -TqSdk 仅支持 Python 3.8 及更高版本。要安装 TqSdk,可使用 pip: +TqSdk 仅支持 Python 3.9 及更高版本。要安装 TqSdk,可使用 pip: ```bash pip install tqsdk diff --git a/doc/_includes/tqsdk_ai_agent_intro.rst b/doc/_includes/tqsdk_ai_agent_intro.rst index 9c634a41..68c7bea0 100644 --- a/doc/_includes/tqsdk_ai_agent_intro.rst +++ b/doc/_includes/tqsdk_ai_agent_intro.rst @@ -4,6 +4,12 @@ 请先下载并解压 TqSdk 技能包:https://doc.shinnytech.com/tqsdk/latest/ai_editor/skills/tqsdk-trading-and-data.zip,然后先阅读其中的 SKILL.md,并按里面的说明再回答任何 TqSdk 问题或编写任何 TqSdk 代码。 +推荐的 AI Agent 工具下载: + +* Trae:https://www.trae.cn/ide/download +* CodeBuddy:https://www.codebuddy.cn/ide/ +* Codex:https://openai.com/codex/ + .. tqsdk_skills_ref_marker 详细说明请见 :ref:`tqsdk_skills` 。 diff --git a/doc/_static/search_zh_dict.txt b/doc/_static/search_zh_dict.txt index 783082f7..5723f30c 100644 --- a/doc/_static/search_zh_dict.txt +++ b/doc/_static/search_zh_dict.txt @@ -8,7 +8,6 @@ TqKq 100000 TqKqStock 100000 TqMultiAccount 100000 TqNotify 100000 -TqReplay 100000 TqRiskRule 100000 TqRiskRuleError 100000 TqSim 100000 diff --git a/doc/advanced/backtest.rst b/doc/advanced/backtest.rst index ab0f25a4..39399812 100644 --- a/doc/advanced/backtest.rst +++ b/doc/advanced/backtest.rst @@ -13,12 +13,12 @@ TqSdk 并不提供专门的参数优化机制. 您可以按照自己的需求, from datetime import date LONG = 60 - SYMBOL = "SHFE.cu1907" + SYMBOL = "SHFE.cu2607" for SHORT in range(20, 40): # 短周期参数从20-40分别做回测 acc = TqSim() # 每次回测都创建一个新的模拟账户 try: - api = TqApi(acc, backtest=TqBacktest(start_dt=date(2019, 5, 6), end_dt=date(2019, 5, 10)), auth=TqAuth("快期账户", "账户密码")) + api = TqApi(acc, backtest=TqBacktest(start_dt=date(2026, 5, 18), end_dt=date(2026, 5, 22)), auth=TqAuth("快期账户", "账户密码")) account = api.get_account() klines = api.get_kline_serial(SYMBOL, duration_seconds=60, data_length=LONG + 2) target_pos = TargetPosTask(api, SYMBOL) @@ -48,10 +48,10 @@ TqSdk 并不提供专门的参数优化机制. 您可以按照自己的需求, def MyStrategy(SHORT): LONG = 60 - SYMBOL = "SHFE.cu1907" + SYMBOL = "SHFE.cu2607" acc = TqSim() try: - api = TqApi(acc, backtest=TqBacktest(start_dt=date(2019, 5, 6), end_dt=date(2019, 5, 10)), auth=TqAuth("快期账户", "账户密码")) + api = TqApi(acc, backtest=TqBacktest(start_dt=date(2026, 5, 18), end_dt=date(2026, 5, 22)), auth=TqAuth("快期账户", "账户密码")) data_length = LONG + 2 klines = api.get_kline_serial(SYMBOL, duration_seconds=60, data_length=data_length) target_pos = TargetPosTask(api, SYMBOL) diff --git a/doc/advanced/dingding.rst b/doc/advanced/dingding.rst index df79431d..cc169217 100644 --- a/doc/advanced/dingding.rst +++ b/doc/advanced/dingding.rst @@ -25,8 +25,8 @@ TqSdk 并不提供专门的服务器来推送消息,但是你可以通过其 api = TqApi(auth=TqAuth("快期账户", "账户密码")) - quote = api.get_quote("SHFE.rb2109") - target_pos = TargetPosTask(api, "SHFE.rb2110") + quote = api.get_quote("SHFE.rb2610") + target_pos = TargetPosTask(api, "SHFE.rb2701") send_msg("策略开始运行") a = 0 while True: diff --git a/doc/advanced/for_ctp_user.rst b/doc/advanced/for_ctp_user.rst index e50fd667..c2fb2edc 100644 --- a/doc/advanced/for_ctp_user.rst +++ b/doc/advanced/for_ctp_user.rst @@ -50,7 +50,7 @@ Ctp接口不提供K线数据. TqSdk中的K线序列采用 pandas.DataFrame 格式. pandas 提供了 `非常丰富的数据处理函数 `_ , 使我们可以非常方便的进行数据处理, 例如:: - ks = api.get_kline_serial("SHFE.cu1901", 60) + ks = api.get_kline_serial("SHFE.cu2607", 60) print(ks.iloc[-1]) # <- 最后一根K线 print(ks.close) # <- 收盘价序列 ks.high - ks.high.shift(1) # <- 每根K线最高价-前一根K线最高价, 形成一个新序列 @@ -59,7 +59,7 @@ TqSdk中的K线序列采用 pandas.DataFrame 格式. pandas 提供了 `非常丰 TqSdk 也通过 :py:mod:`tqsdk.tafunc` 提供了一批行情分析中常用的计算函数, 例如:: from tqsdk import tafunc - ks = api.get_kline_serial("SHFE.cu1901", 60) + ks = api.get_kline_serial("SHFE.cu2607", 60) ms = tafunc.max(ks.open, ks.close) # <- 取每根K线开盘价和收盘价的高者构建一个新序列 median3 = tafunc.median(ks.close, 100) # <- 求最近100根K线收盘价的中间值 ss = tafunc.std(ks.close, 5) # <- 每5根K线的收盘价标准差 @@ -85,8 +85,10 @@ Ctp接口按照事件回调模型设计, 使用 CThostFtdcTraderSpi 的 OnXXX TqSdk则不使用事件回调机制. :py:meth:`~tqsdk.TqApi.wait_update` 函数设计用来获取任意数据更新, 像这样:: + from tqsdk import TqApi, TqAuth + api = TqApi(auth=TqAuth("快期账户", "账户密码")) - x = api.insert_order("SHFE.cu1901", direction="BUY", offset="OPEN", volume=1, limit_price=50000) + x = api.insert_order("SHFE.cu2607", direction="BUY", offset="OPEN", volume=1, limit_price=50000) while True: api.wait_update() # <- 这个 wait_update 将尝试更新所有数据. 如果没有任何新信息, 程序会阻塞在这一句. 一旦有任意数据被更新, 程序会继续往下执行 @@ -95,10 +97,12 @@ TqSdk则不使用事件回调机制. :py:meth:`~tqsdk.TqApi.wait_update` 函数 一次 wait_update 可能更新多个实体, 在这种情况下, :py:meth:`~tqsdk.TqApi.is_changing` 被用来判断某个实体是否有变更:: + from tqsdk import TqApi, TqAuth + api = TqApi(auth=TqAuth("快期账户", "账户密码")) - q = api.get_quote("SHFE.cu1901") - ks = api.get_kline_serial("SHFE.cu1901", 60) - x = api.insert_order("SHFE.cu1901", direction="BUY", offset="OPEN", volume=1, limit_price=50000) + q = api.get_quote("SHFE.cu2607") + ks = api.get_kline_serial("SHFE.cu2607", 60) + x = api.insert_order("SHFE.cu2607", direction="BUY", offset="OPEN", volume=1, limit_price=50000) while True: api.wait_update() # <- 这个 wait_update 将尝试更新所有数据. 如果没有任何新信息, 程序会阻塞在这一句. 一旦有任意数据被更新, 程序会继续往下执行 diff --git a/doc/advanced/for_vnpy_user.rst b/doc/advanced/for_vnpy_user.rst index 50a695ea..2188f297 100644 --- a/doc/advanced/for_vnpy_user.rst +++ b/doc/advanced/for_vnpy_user.rst @@ -71,7 +71,7 @@ TqSdk 则使用基于网络协作的组件设计. 如下图: SHORT = 30 LONG = 60 - SYMBOL = "SHFE.bu1912" + SYMBOL = "SHFE.bu2609" api = TqApi(auth=TqAuth("快期账户", "账户密码")) @@ -122,13 +122,15 @@ TqSdk将每个策略作为一个独立进程运行, 这样就可以: 当近月-远月的价差大于200时做空近月,做多远月 当价差小于150时平仓 ''' + from tqsdk import TqApi, TqAuth, TargetPosTask + api = TqApi(auth=TqAuth("快期账户", "账户密码")) - quote_near = api.get_quote("SHFE.rb1910") - quote_deferred = api.get_quote("SHFE.rb2001") - # 创建 rb1910 的目标持仓 task,该 task 负责调整 rb1910 的仓位到指定的目标仓位 - target_pos_near = TargetPosTask(api, "SHFE.rb1910") - # 创建 rb2001 的目标持仓 task,该 task 负责调整 rb2001 的仓位到指定的目标仓位 - target_pos_deferred = TargetPosTask(api, "SHFE.rb2001") + quote_near = api.get_quote("SHFE.rb2610") + quote_deferred = api.get_quote("SHFE.rb2701") + # 创建 rb2610 的目标持仓 task,该 task 负责调整 rb2610 的仓位到指定的目标仓位 + target_pos_near = TargetPosTask(api, "SHFE.rb2610") + # 创建 rb2701 的目标持仓 task,该 task 负责调整 rb2701 的仓位到指定的目标仓位 + target_pos_deferred = TargetPosTask(api, "SHFE.rb2701") while True: api.wait_update() @@ -160,7 +162,7 @@ K线数据与指标计算 TqSdk中的K线序列采用 pandas.DataFrame 格式. pandas 提供了 `非常丰富的数据处理函数 `_ , 使我们可以非常方便的进行数据处理, 例如:: - ks = api.get_kline_serial("SHFE.cu1901", 60) + ks = api.get_kline_serial("SHFE.cu2607", 60) print(ks.iloc[-1]) # <- 最后一根K线 print(ks.close) # <- 收盘价序列 ks.high - ks.high.shift(1) # <- 每根K线最高价-前一根K线最高价, 形成一个新序列 @@ -169,7 +171,7 @@ TqSdk中的K线序列采用 pandas.DataFrame 格式. pandas 提供了 `非常丰 TqSdk 也通过 :py:mod:`tqsdk.tafunc` 提供了一批行情分析中常用的计算函数, 例如:: from tqsdk import tafunc - ks = api.get_kline_serial("SHFE.cu1901", 60) + ks = api.get_kline_serial("SHFE.cu2607", 60) ms = tafunc.max(ks.open, ks.close) # <- 取每根K线开盘价和收盘价的高者构建一个新序列 median3 = tafunc.median(ks.close, 100) # <- 求最近100根K线收盘价的中间值 ss = tafunc.std(ks.close, 5) # <- 每5根K线的收盘价标准差 @@ -192,8 +194,10 @@ vn.py按照事件回调模型设计, 使用 CtaTemplate 的 on_xxx 回调函数 TqSdk则不使用事件回调机制. :py:meth:`~tqsdk.TqApi.wait_update` 函数设计用来获取任意数据更新, 像这样:: + from tqsdk import TqApi, TqAuth + api = TqApi(auth=TqAuth("快期账户", "账户密码")) - ks = api.get_kline_serial("SHFE.cu1901", 60) + ks = api.get_kline_serial("SHFE.cu2607", 60) while True: api.wait_update() # <- 这个 wait_update 将尝试更新所有数据. 如果没有任何新信息, 程序会阻塞在这一句. 一旦有任意数据被更新, 程序会继续往下执行 @@ -202,10 +206,12 @@ TqSdk则不使用事件回调机制. :py:meth:`~tqsdk.TqApi.wait_update` 函数 一次 wait_update 可能更新多个实体, 在这种情况下, :py:meth:`~tqsdk.TqApi.is_changing` 被用来判断某个实体是否有变更:: + from tqsdk import TqApi, TqAuth + api = TqApi(auth=TqAuth("快期账户", "账户密码")) - q = api.get_quote("SHFE.cu1901") - ks = api.get_kline_serial("SHFE.cu1901", 60) - x = api.insert_order("SHFE.cu1901", direction="BUY", offset="OPEN", volume=1, limit_price=50000) + q = api.get_quote("SHFE.cu2607") + ks = api.get_kline_serial("SHFE.cu2607", 60) + x = api.insert_order("SHFE.cu2607", direction="BUY", offset="OPEN", volume=1, limit_price=50000) while True: api.wait_update() # <- 这个 wait_update 将尝试更新所有数据. 如果没有任何新信息, 程序会阻塞在这一句. 一旦有任意数据被更新, 程序会继续往下执行 @@ -235,12 +241,14 @@ TqSdk 提供 :ref:`web_gui` 来供有图形化需求的用户使用: TqSdk配合web_gui使用时, 还支持自定义绘制行情图表, 像这样:: + from tqsdk import TqApi, TqAuth + api = TqApi(auth=TqAuth("快期账户","账户密码"), web_gui=True) - # 获取 cu1905 和 cu1906 的日线数据 - klines = api.get_kline_serial("SHFE.cu1905", 86400) - klines2 = api.get_kline_serial("SHFE.cu1906", 86400) + # 获取 cu2607 和 cu2608 的日线数据 + klines = api.get_kline_serial("SHFE.cu2607", 86400) + klines2 = api.get_kline_serial("SHFE.cu2608", 86400) - # 算出 cu1906 - cu1905 的价差,并以折线型态显示在副图 + # 算出 cu2608 - cu2607 的价差,并以折线型态显示在副图 klines["dif"] = klines2["close"] - klines["close"] klines["dif.board"] = "DIF" klines["dif.color"] = 0xFF00FF00 diff --git a/doc/advanced/multi_strategy.rst b/doc/advanced/multi_strategy.rst index cd43fa9c..59595cea 100644 --- a/doc/advanced/multi_strategy.rst +++ b/doc/advanced/multi_strategy.rst @@ -6,7 +6,10 @@ 以简单的双均线策略为例. 一个简单的双均线策略代码大致是这样:: - SYMBOL = "SHFE.bu1912" # 合约代码 + from tqsdk import TqApi, TqAuth, TargetPosTask + from tqsdk.tafunc import ma + + SYMBOL = "SHFE.bu2609" # 合约代码 SHORT = 30 # 短周期 LONG = 60 # 长周期 @@ -40,8 +43,11 @@ TqSdk 为这类需求提供两种解决方案, 您可任意选择一种. 在函数文件 mylib.py 中: - def ma(SYMBOL, SHORT, LONG): - api = TqApi(TqSim()) + from tqsdk import TqApi, TqAuth, TargetPosTask + from tqsdk.tafunc import ma + + def run_ma(SYMBOL, SHORT, LONG): + api = TqApi(auth=TqAuth("快期账户", "账户密码")) klines = api.get_kline_serial(SYMBOL, duration_seconds=60, data_length=LONG + 2) target_pos = TargetPosTask(api, SYMBOL) @@ -61,26 +67,29 @@ TqSdk 为这类需求提供两种解决方案, 您可任意选择一种. -------------------------------------------------- 在策略文件 ma-股指.py 中: - from mylib import ma - ma("CFFEX.IF1906", 30, 60) + from mylib import run_ma + run_ma("CFFEX.IF2606", 30, 60) -------------------------------------------------- 在策略文件 ma-玉米.py 中: - from mylib import ma - ma("DCE.c1906", 10, 20) + from mylib import run_ma + run_ma("DCE.c2609", 10, 20) 习惯使用命令行的同学也可以做命令行参数:: import argparse + from tqsdk import TqApi, TqAuth, TargetPosTask + from tqsdk.tafunc import ma + parser = argparse.ArgumentParser() parser.add_argument('--SYMBOL') - parser.add_argument('--SHORT') - parser.add_argument('--LONG') + parser.add_argument('--SHORT', type=int) + parser.add_argument('--LONG', type=int) args = parser.parse_args() - api = TqApi(TqSim()) + api = TqApi(auth=TqAuth("快期账户", "账户密码")) klines = api.get_kline_serial(args.SYMBOL, duration_seconds=60, data_length=args.LONG + 2) target_pos = TargetPosTask(api, args.SYMBOL) while True: @@ -97,8 +106,8 @@ TqSdk 为这类需求提供两种解决方案, 您可任意选择一种. 使用时在命令行挂参数:: - python ma.py --SYMBOL=SHFE.cu1901 --LONG=30 --SHORT=20 - python ma.py --SYMBOL=SHFE.rb1901 --LONG=50 --SHORT=10 + python ma.py --SYMBOL=SHFE.cu2607 --LONG=30 --SHORT=20 + python ma.py --SYMBOL=SHFE.rb2610 --LONG=50 --SHORT=10 优点: @@ -119,12 +128,12 @@ TqSdk 内核支持以异步方式实现多任务。 如果用户策略代码实 TqSdk(2.6.1 版本)对几个常用接口 :py:meth:`~tqsdk.TqApi.get_quote`, :py:meth:`~tqsdk.TqApi.get_quote_list`, :py:meth:`~tqsdk.TqApi.get_kline_serial`, :py:meth:`~tqsdk.TqApi.get_tick_serial` 支持协程中调用。 -对于 :py:meth:`~tqsdk.TqApi.get_quote` 接口,在异步代码中可以写为 ``await api.get_quote('SHFE.cu2110')``,代码更加紧凑,可读性更好。 +对于 :py:meth:`~tqsdk.TqApi.get_quote` 接口,在异步代码中可以写为 ``await api.get_quote('SHFE.cu2607')``,代码更加紧凑,可读性更好。 示例代码如下:: # 协程示例,为每个合约创建 task - from tqsdk import TqApi + from tqsdk import TqApi, TqAuth async def demo(SYMBOL): quote = await api.get_quote(SYMBOL) # 支持 await 异步,这里会订阅合约,等到收到合约行情才返回 @@ -150,8 +159,8 @@ TqSdk(2.6.1 版本)对几个常用接口 :py:meth:`~tqsdk.TqApi.get_quote`, api = TqApi(auth=TqAuth("快期账户", "账户密码")) # 为每个合约创建异步任务 - api.create_task(demo("SHFE.rb2107")) - api.create_task(demo("DCE.m2109")) + api.create_task(demo("SHFE.rb2610")) + api.create_task(demo("DCE.m2609")) while True: api.wait_update() @@ -160,7 +169,8 @@ TqSdk(2.6.1 版本)对几个常用接口 :py:meth:`~tqsdk.TqApi.get_quote`, 下面是一个更完整的示例,用异步方式实现为每个合约创建双均线策略,示例代码如下:: # 协程示例,为每个合约创建 task - from tqsdk import TqApi + from tqsdk import TqApi, TqAuth, TargetPosTask + from tqsdk.tafunc import ma api = TqApi(auth=TqAuth("快期账户", "账户密码")) # 构造 api 实例 @@ -185,9 +195,9 @@ TqSdk(2.6.1 版本)对几个常用接口 :py:meth:`~tqsdk.TqApi.get_quote`, print("均线上穿,做多") # 为每个合约创建异步任务 - api.create_task(demo("SHFE.rb2107", 30, 60)) - api.create_task(demo("DCE.m2109", 30, 60)) - api.create_task(demo("DCE.jd2109", 30, 60)) + api.create_task(demo("SHFE.rb2610", 30, 60)) + api.create_task(demo("DCE.m2609", 30, 60)) + api.create_task(demo("DCE.jd2609", 30, 60)) while True: api.wait_update() diff --git a/doc/advanced/order.rst b/doc/advanced/order.rst index fd0a8ef4..6ef4cc28 100644 --- a/doc/advanced/order.rst +++ b/doc/advanced/order.rst @@ -33,21 +33,21 @@ FIVELEVEL FAK 最优五档即时成交剩余撤销指令 api = TqApi(auth=TqAuth("快期账户", "账户密码")) # 当日有效限价单 - api.insert_order("SHFE.cu2009", "BUY", "OPEN", 3, limit_price=14200) + api.insert_order("SHFE.cu2607", "BUY", "OPEN", 3, limit_price=14200) # FAK 限价单 - api.insert_order("SHFE.cu2009", "BUY", "OPEN", 3, limit_price=14200, advanced="FAK") + api.insert_order("SHFE.cu2607", "BUY", "OPEN", 3, limit_price=14200, advanced="FAK") # FOK 限价单 - api.insert_order("SHFE.cu2009", "BUY", "OPEN", 3, limit_price=14200, advanced="FOK") + api.insert_order("SHFE.cu2607", "BUY", "OPEN", 3, limit_price=14200, advanced="FOK") # 市价单 - api.insert_order("DCE.m2009", "BUY", "OPEN", 3) + api.insert_order("DCE.m2609", "BUY", "OPEN", 3) # FOK 市价单 - api.insert_order("DCE.m2009", "BUY", "OPEN", 3, advanced="FOK") + api.insert_order("DCE.m2609", "BUY", "OPEN", 3, advanced="FOK") # BEST - api.insert_order("CFFEX.T2003", "BUY", "OPEN", 3, limit_price="BEST") + api.insert_order("CFFEX.T2609", "BUY", "OPEN", 3, limit_price="BEST") # FIVELEVEL - api.insert_order("CFFEX.T2003", "BUY", "OPEN", 3, limit_price="FIVELEVEL") + api.insert_order("CFFEX.T2609", "BUY", "OPEN", 3, limit_price="FIVELEVEL") 不同交易所支持的高级指令参数组合: diff --git a/doc/advanced/scheduler.rst b/doc/advanced/scheduler.rst index 9ef6ddcd..902db448 100644 --- a/doc/advanced/scheduler.rst +++ b/doc/advanced/scheduler.rst @@ -38,7 +38,7 @@ TargetPosScheduler 执行目标持仓任务列表 :py:class:`~tqsdk.TargetPosScheduler` 类创建 target_pos_scheduler 实例,首先会将 ``time_table`` 中 ``interval`` 间隔时间列转为 ``deadline``,即这项任务结束时间的纳秒数。 -然后,依次为 ``time_table`` 中的每一项任务创建 :py:class:`~tqsdk.TargetPosTask` 实例,调整目标持仓,并在到达 ``deadline`` 时退出。每一项未完成的目标持仓都会留都下一项任务中。 +然后,依次为 ``time_table`` 中的每一项任务创建 :py:class:`~tqsdk.TargetPosTask` 实例,调整目标持仓,并在到达 ``deadline`` 时退出。每一项未完成的目标持仓都会留到下一项任务中。 需要注意的是,最后一项任务,是以手数达到目标的,会按照当前项参数,调整到目标持仓再退出。如果最后一项 ``price`` 参数为 ``None`` (表示不下单),由于无法调整持仓,那么会立即退出。 @@ -48,22 +48,26 @@ TargetPosScheduler 执行目标持仓任务列表 简单示例及说明如下:: + from pandas import DataFrame + from tqsdk import TqApi, TqAuth, TargetPosScheduler + + api = TqApi(auth=TqAuth("快期账户", "账户密码")) time_table = DataFrame([ - [25, 10, "PASSIVE"] - [5, 10, "ACTIVE"] - [30, 18, "PASSIVE"] + [25, 10, "PASSIVE"], + [5, 10, "ACTIVE"], + [30, 18, "PASSIVE"], [5, 18, "ACTIVE"] ], columns=['interval', 'target_pos', 'price']) - target_pos_scheduler = TargetPosScheduler(api, "SHFE.cu2112", time_table) + target_pos_scheduler = TargetPosScheduler(api, "SHFE.cu2607", time_table) # 这个 time_table 表示的下单策略依次是: - # 1. 使用排队价下单,调整 "SHFE.cu2112" 到 10 手,到达 25s 时退出(无论目标手数是否达到,都不会继续下单) - # 2. 使用对价下单,调整 "SHFE.cu2112" 到 10 手,到达 5s 时退出 + # 1. 使用排队价下单,调整 "SHFE.cu2607" 到 10 手,到达 25s 时退出(无论目标手数是否达到,都不会继续下单) + # 2. 使用对价下单,调整 "SHFE.cu2607" 到 10 手,到达 5s 时退出 # 如果上一步结束时目标持仓已经达到 10 手,这一步什么都不会做,等待 5s 到下一步; # 如果上一步结束时目标持仓没有达到 10 手,这一步会继续调整目标持仓到 10 手 - # 3. 使用排队价下单,调整 "SHFE.cu2112" 到 18 手,到达 30s 时退出(无论目标手数是否达到,都不会继续下单) - # 4. 使用对价下单,调整 "SHFE.cu2112" 到 18 手 + # 3. 使用排队价下单,调整 "SHFE.cu2607" 到 18 手,到达 30s 时退出(无论目标手数是否达到,都不会继续下单) + # 4. 使用对价下单,调整 "SHFE.cu2607" 到 18 手 # 如果上一步结束时目标持仓已经达到 18 手,这一步什么都不会做,立即退出; # 如果上一步结束时目标持仓没有达到 18 手,这一步会继续调整目标持仓到 18 手后退出 @@ -82,14 +86,15 @@ TargetPosScheduler 执行目标持仓任务列表 一个完整的 twap 策略示例:: - from tqsdk import TqApi, TargetPosScheduler + import pandas + from pandas import DataFrame + from tqsdk import TqApi, TqAuth, TargetPosScheduler from tqsdk.algorithm import twap_table - api = TqApi(auth="快期账户,用户密码") - quote = api.get_quote("CZCE.MA109") + api = TqApi(auth=TqAuth("快期账户", "账户密码")) # 设置 twap 任务参数, - time_table = twap_table(api, "CZCE.MA105", -100, 600, 1, 5) # 目标持仓 -100 手,600s 内完成 + time_table = twap_table(api, "CZCE.MA609", -100, 600, 1, 5) # 目标持仓 -100 手,600s 内完成 # 定制化调整 time_table,例如希望第一项任务延迟 10s 再开始下单 # 可以在 time_table 的头部加一行 @@ -98,7 +103,7 @@ TargetPosScheduler 执行目标持仓任务列表 time_table ], ignore_index=True) - target_pos_sch = TargetPosScheduler(api, "CZCE.MA105", time_table) + target_pos_sch = TargetPosScheduler(api, "CZCE.MA609", time_table) while not target_pos_sch.is_finished(): api.wait_update() @@ -106,6 +111,6 @@ TargetPosScheduler 执行目标持仓任务列表 print(target_pos_sch.trades_df) # 利用成交列表,您可以计算出策略的各种表现指标,例如: - average_trade_price = sum(scheduler.trades_df['price'] * scheduler.trades_df['volume']) / sum(scheduler.trades_df['volume']) + average_trade_price = sum(target_pos_sch.trades_df['price'] * target_pos_sch.trades_df['volume']) / sum(target_pos_sch.trades_df['volume']) print("成交均价:", average_trade_price) api.close() diff --git a/doc/advanced/targetpostask2.rst b/doc/advanced/targetpostask2.rst index 5813446e..d28aa66a 100644 --- a/doc/advanced/targetpostask2.rst +++ b/doc/advanced/targetpostask2.rst @@ -23,17 +23,17 @@ TargetPosTask 高级功能 :py:class:`~tqsdk.TargetPosTask` 类提供了 :py:meth:`~tqsdk.TargetPosTask.cancel` 和 :py:meth:`~tqsdk.TargetPosTask.is_finished` 方法。 -+ :py:meth:`~tqsdk.TargetPosTask.cancel` 方法会取消当前 :py:class:`~tqsdk.TargetPosTask` 实例,会将该实例已经发出但还未成交的委托单撤单此实例的 set_target_volume 函数不会再生效,并且此实例的 set_target_volume 函数不会再生效。 ++ :py:meth:`~tqsdk.TargetPosTask.cancel` 方法会取消当前 :py:class:`~tqsdk.TargetPosTask` 实例,并撤销该实例已经发出但还未成交的委托单。取消后,此实例的 set_target_volume 函数不会再生效。 + :py:meth:`~tqsdk.TargetPosTask.is_finished` 方法可以获取当前 :py:class:`~tqsdk.TargetPosTask` 实例是否已经结束。已经结束实例的 set_target_volume 函数不会再接受参数,此实例不会再下单或者撤单。 下面是一个例子:: from datetime import datetime, time - from tqsdk import TqApi, TargetPosTask + from tqsdk import TqApi, TqAuth, TargetPosTask api = TqApi(auth=TqAuth("快期账户", "账户密码")) - quote = api.get_quote("SHFE.rb2110") - target_pos_passive = TargetPosTask(api, "SHFE.rb2110", price="PASSIVE") + quote = api.get_quote("SHFE.rb2701") + target_pos_passive = TargetPosTask(api, "SHFE.rb2701", price="PASSIVE") while datetime.strptime(quote.datetime, "%Y-%m-%d %H:%M:%S.%f").time() < time(14, 50): api.wait_update() @@ -46,7 +46,7 @@ TargetPosTask 高级功能 api.wait_update() # 调用wait_update(),会对已经发出但还是未成交的委托单撤单 # 创建新的 TargetPosTask 实例 - target_pos_active = TargetPosTask(api, "SHFE.rb2110", price="ACTIVE") + target_pos_active = TargetPosTask(api, "SHFE.rb2701", price="ACTIVE") target_pos_active.set_target_volume(0) # 平所有仓位 while True: diff --git a/doc/advanced/timer.rst b/doc/advanced/timer.rst index 06017047..79e07f0e 100644 --- a/doc/advanced/timer.rst +++ b/doc/advanced/timer.rst @@ -14,8 +14,8 @@ from tqsdk import TqApi, TqAuth, TargetPosTask api = TqApi(auth=TqAuth("快期账户", "账户密码")) - quote = api.get_quote("SHFE.rb2405") - target_pos = TargetPosTask(api, "SHFE.rb2405") + quote = api.get_quote("SHFE.rb2610") + target_pos = TargetPosTask(api, "SHFE.rb2610") close_hour, close_minute = 14, 50 while True: @@ -38,7 +38,7 @@ from tqsdk import TqApi, TqAuth api = TqApi(auth=TqAuth("快期账户", "账户密码")) - quote = api.get_quote("SHFE.rb2405") + quote = api.get_quote("SHFE.rb2610") while True: api.wait_update() diff --git a/doc/advanced/tq_trading_unit.rst b/doc/advanced/tq_trading_unit.rst index dd92a437..1952a379 100644 --- a/doc/advanced/tq_trading_unit.rst +++ b/doc/advanced/tq_trading_unit.rst @@ -16,7 +16,7 @@ TqSdk 多策略使用手册 ============ - **Windows**: >= Windows 10 - **Linux**: >= Ubuntu 22.04 -- **tqsdk**: >= 3.8.0 +- **tqsdk**: >= 3.9 - **快期专业版**: >= 2504.659 安装 @@ -106,22 +106,18 @@ TqSdk 多策略使用手册 ========= 如果您在使用过程中遇到问题,您可以把日志发给我们来帮助您查询 -导出日志前,需要先关闭 zq_server 和 zq_server_history_go 两个进程,然后打包日志目录并发送给我们 +点击管理页面左下角日志导出按钮,导出日志,然后打包日志目录并发送给我们 **Linux**: .. code-block:: bash - pkill -f zq_server_history_go - pkill -f zq_server tar -czf ~/tqsdk_zq_log.tar.gz -C ~ .tqsdk/zq/log **Windows** (PowerShell): .. code-block:: powershell - taskkill /F /IM zq_server_history_go.exe 2>$null - taskkill /F /IM zq_server.exe 2>$null cd $env:USERPROFILE tar -czf tqsdk_zq_log.tar.gz .tqsdk/zq/log @@ -136,7 +132,7 @@ TqSdk 多策略使用手册 .. code-block:: bash pkill -f zq_server_history_go - pkill -f zq_server + pkill -f service_daemon pkill -f sock-proxy pkill -f postgres rm -rf ~/.tqsdk/zq @@ -146,7 +142,7 @@ TqSdk 多策略使用手册 .. code-block:: powershell taskkill /F /IM zq_server_history_go.exe 2>$null - taskkill /F /IM zq_server.exe 2>$null + taskkill /F /IM service_daemon.exe 2>$null taskkill /F /IM sock-proxy.exe 2>$null taskkill /F /IM postgres.exe 2>$null Remove-Item -Recurse -Force $env:USERPROFILE\.tqsdk\zq @@ -164,6 +160,17 @@ TqSdk 多策略使用手册 ========= 使用 `pip install -U --upgrade-strategy eager tqsdk-zq` 更新多策略功能所有依赖包 +.. line-block:: + **2026/05/26** + tqsdk-zq: 1.1.0 + tqsdk-zq-server: 1.1.0 + tqsdk-zq-history: 1.0.1 + tqsdk-zq-pgserver: 1.0.0 + tqsdk-zq-proxy: 1.0.0 + +* 新增:自动切换交易日功能 +* 新增:日志导出功能 + .. line-block:: **2025/12/10** tqsdk-zq: 1.0.3 diff --git a/doc/advanced/unanttended.rst b/doc/advanced/unanttended.rst index 84cacc66..62d2f8e7 100644 --- a/doc/advanced/unanttended.rst +++ b/doc/advanced/unanttended.rst @@ -8,7 +8,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TqSdk可以在windows/linux或macosx环境下运行. 无论您选择使用windows或linux系统, 请确保 -* 已经装有 Python 3.7+ +* 已经装有 Python 3.9+ * 安装 :ref:`TqSdk ` 创建一个目录, 放置你所有的策略文件. @@ -18,6 +18,8 @@ TqSdk可以在windows/linux或macosx环境下运行. 无论您选择使用window ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 将每个策略程序配置为独立直连实盘账号. 在创建 TqApi 时, 传入TqAccount实例. 注意期货公司名称需要与天勤中的名称一致:: + from tqsdk import TqApi, TqAccount, TqAuth + api = TqApi(TqAccount("H宏源期货", "022631", "123456"), auth=TqAuth("快期账户", "账户密码")) @@ -27,7 +29,7 @@ TqSdk可以在windows/linux或macosx环境下运行. 无论您选择使用window * 使用 python 的 logging 模块输出日志信息到文件, 不要使用 print 打印日志信息 * 策略代码退出时记得调用 api.close() 函数, 或者用 with closing(api) 的格式确保退出时自动关闭 -* 目前api在运行过程中抛出的异常, 默认处理都是整个策略进程直接退出. 如无特殊需求, 不要使用 expect: 的方式捕获异常并阻止程序退出, 这种情况如果没有正确处理, 可能产生难以预测的后果. +* 目前api在运行过程中抛出的异常, 默认处理都是整个策略进程直接退出. 如无特殊需求, 不要使用 except: 的方式捕获异常并阻止程序退出, 这种情况如果没有正确处理, 可能产生难以预测的后果. 在 windows 环境下配置策略的定时启动/停止 @@ -65,22 +67,22 @@ TqSdk可以在windows/linux或macosx环境下运行. 无论您选择使用window # -*- coding: utf-8 -*- - from tqsdk import TqApi, TqAccount + from tqsdk import TqApi, TqAccount, TqAuth api = TqApi(TqAccount("H宏源期货", "0330203", "123456"), auth=TqAuth("快期账户", "账户密码")) # 开仓两手并等待完成 - order = api.insert_order(symbol="SHFE.rb1901", direction="BUY", offset="OPEN", limit_price=4310,volume=2) + order = api.insert_order(symbol="SHFE.rb2610", direction="BUY", offset="OPEN", limit_price=4310,volume=2) while order.status != "FINISHED": api.wait_update() print("已开仓") -上面的代码中固定了账户及合约代码 SHFE.rb1901. 我们可以利用 python 的 argparse 模块为这个程序添加一些参数:: +上面的代码中固定了账户及合约代码 SHFE.rb2610. 我们可以利用 python 的 argparse 模块为这个程序添加一些参数:: # -*- coding: utf-8 -*- import argparse - from tqsdk import TqApi, TqSim, TqAccount + from tqsdk import TqApi, TqSim, TqAccount, TqAuth #解析命令行参数 parser = argparse.ArgumentParser() @@ -100,7 +102,7 @@ TqSdk可以在windows/linux或macosx环境下运行. 无论您选择使用window 要通过命令行运行此策略, 可以输入:: - python args.py --broker=H宏源期货 --user_name=0330203 --password=123456 --symbol=SHFE.cu1901 + python args.py --broker=H宏源期货 --user_name=0330203 --password=123456 --symbol=SHFE.cu2607 要在 PyCharm 中同时执行此程序的多种参数版本, 可以通过 PyCharm 的 Run Configuration 实现. diff --git a/doc/ai_editor/nl_research.rst b/doc/ai_editor/nl_research.rst index e9103dbb..9b800860 100644 --- a/doc/ai_editor/nl_research.rst +++ b/doc/ai_editor/nl_research.rst @@ -17,7 +17,7 @@ -------------------------------- - `股指期货收益来源可视化分析 `_ -- `ag2604 au2604 价差曲线分析 `_ +- `ag2608 au2608 价差曲线分析 `_ - `黄金2604策略逻辑及收益曲线绘制 `_ @@ -105,7 +105,7 @@ .. code-block:: text - 我已经添加了天勤 EDB 数据 SKILL,请获取 SHFE.ag2604 与 SHFE.au2604 的日线 close,计算价差 ag-au 并画出价差曲线; + 我已经添加了天勤 EDB 数据 SKILL,请获取 SHFE.ag2608 与 SHFE.au2608 的日线 close,计算价差 ag-au 并画出价差曲线; 额外输出:价差的滚动均值/标准差(例如 20 日),并给出异常波动区间的解释思路。 示例 3:策略回测脚本生成 @@ -113,7 +113,7 @@ .. code-block:: text - 我已经添加了天勤 EDB 数据 SKILL,请获取 period=60 的 SHFE.au2604 历史数据,并做一个简单策略回测: + 我已经添加了天勤 EDB 数据 SKILL,请获取 period=60 的 SHFE.au2608 历史数据,并做一个简单策略回测: - 指标:20/60 均线金叉死叉 - 交易规则:金叉开多、死叉平仓 - 输出:权益曲线、最大回撤、年化收益 diff --git a/doc/ai_editor/skills/tianqin-edb-api/references/api-reference.md b/doc/ai_editor/skills/tianqin-edb-api/references/api-reference.md index b1dd2ac1..c1a4f1ba 100644 --- a/doc/ai_editor/skills/tianqin-edb-api/references/api-reference.md +++ b/doc/ai_editor/skills/tianqin-edb-api/references/api-reference.md @@ -10,7 +10,7 @@ 常用参数: - `period`:`60`(1 分钟)或 `86400`(日线,按交易日) -- `symbol`:如 `SHFE.rb2401`、`KQ.m@CFFEX.IF` +- `symbol`:如 `SHFE.rb2701`、`KQ.m@CFFEX.IF` - `start_time` / `end_time`:`YYYY-MM-DD HH:MM:SS` - `col`:逗号分隔列名(可选):`open,high,low,close,volume,open_oi,close_oi` - `token`:可通过 query 传入(不推荐)或用 Header 传入(推荐) diff --git a/doc/ai_editor/skills/tqsdk-trading-and-data/SKILL.md b/doc/ai_editor/skills/tqsdk-trading-and-data/SKILL.md index af86febc..f747f084 100644 --- a/doc/ai_editor/skills/tqsdk-trading-and-data/SKILL.md +++ b/doc/ai_editor/skills/tqsdk-trading-and-data/SKILL.md @@ -1,6 +1,6 @@ --- name: tqsdk-trading-and-data -description: "Explain, implement, or debug TqSdk Python workflows for wait_update or is_changing update loops, market data retrieval, historical download, account type selection, funds or positions or orders or trades, field meanings, order placement or cancelation, target-position tools, TqScenario margin trials, real-account margin-rate lookup, margin or risk-ratio what-if analysis, simulation, backtest, and common TqSdk errors. Use when a request mentions TqSdk, TqApi, TqAuth, TqAccount, TqKq, TqKqStock, TqSim, TqSimStock, TqMultiAccount, TqBacktest, TqScenario, TargetPosTask, TargetPosScheduler, DataDownloader, margin rate, margin calculation, risk ratio, scenario trial, market data, K-line, tick, historical data, positions, trades, orders, account data, order placement, cancelation, position adjustment, field meanings, wait_update, debugging, \u884c\u60c5, K\u7ebf, \u5386\u53f2\u6570\u636e, \u4fdd\u8bc1\u91d1\u7387, \u4fdd\u8bc1\u91d1, \u98ce\u9669\u5ea6, \u573a\u666f\u8bd5\u7b97, \u6301\u4ed3, \u6210\u4ea4, \u59d4\u6258, \u8d26\u6237, \u4e0b\u5355, \u64a4\u5355, \u8c03\u4ed3, \u5b57\u6bb5\u542b\u4e49, or \u62a5\u9519." +description: "Explain, implement, or debug TqSdk Python workflows for wait_update or is_changing update loops, market data retrieval, historical download, account type selection, funds or positions or orders or trades, field meanings, order placement or cancellation, target-position tools, TqScenario margin trials, real-account margin-rate lookup, margin or risk-ratio what-if analysis, simulation, backtest, multi-strategy, and common TqSdk errors. Use when a request mentions TqSdk, TqApi, TqAuth, TqAccount, TqKq, TqKqStock, TqSim, TqSimStock, TqMultiAccount, TqTradingUnit, TqBacktest, TqScenario, TargetPosTask, TargetPosScheduler, Twap, DataDownloader, margin rate, margin calculation, risk ratio, scenario trial, market data, K-line, tick, historical data, positions, trades, orders, account data, order placement, cancellation, position adjustment, field meanings, wait_update, debugging, \u884c\u60c5, K\u7ebf, \u5386\u53f2\u6570\u636e, \u4fdd\u8bc1\u91d1\u7387, \u4fdd\u8bc1\u91d1, \u98ce\u9669\u5ea6, \u573a\u666f\u8bd5\u7b97, \u6301\u4ed3, \u6210\u4ea4, \u59d4\u6258, \u8d26\u6237, \u4e0b\u5355, \u64a4\u5355, \u8c03\u4ed3, \u591a\u7b56\u7565, \u5b57\u6bb5\u542b\u4e49, or \u62a5\u9519." --- # TqSdk Trading and Data @@ -13,15 +13,24 @@ Read only the references needed for the user's question. 1. Read [references/wait-update-and-update-loop.md](references/wait-update-and-update-loop.md) for `wait_update`, `is_changing`, `deadline`, async update notifications, Jupyter caveats, or backtest progression questions. 2. Read [references/market-data.md](references/market-data.md) for `get_quote`, `get_kline_serial`, `get_tick_serial`, contract discovery, symbol metadata, and `DataDownloader`. -3. Read [references/account-type-matrix.md](references/account-type-matrix.md) for `TqAccount`, `TqKq`, `TqKqStock`, `TqSim`, `TqSimStock`, OTG account classes, and `TqMultiAccount`. +3. Read [references/account-type-matrix.md](references/account-type-matrix.md) for `TqAccount`, `TqKq`, `TqKqStock`, `TqSim`, `TqSimStock`, `TqTradingUnit`, OTG account classes, and `TqMultiAccount`. 4. Read [references/accounts-and-trading.md](references/accounts-and-trading.md) for account, position, order, and trade getters plus multi-account getter patterns. 5. Read [references/scenario-and-margin.md](references/scenario-and-margin.md) for `TqScenario`, real-account margin-rate lookup, margin occupancy calculation, risk-ratio what-if analysis, and limited built-in margin discount rules. -6. Read [references/order-functions-and-position-tools.md](references/order-functions-and-position-tools.md) for `insert_order`, `cancel_order`, `TargetPosTask`, `support_open_min_volume`, `TargetPosScheduler`, and advanced execution helpers. +6. Read [references/order-functions-and-position-tools.md](references/order-functions-and-position-tools.md) for `insert_order`, `cancel_order`, `TargetPosTask`, `support_open_min_volume`, `TargetPosScheduler`, `Twap`, and advanced execution helpers. 7. Read [references/object-fields.md](references/object-fields.md) when the user asks what fields mean on `Quote`, K-line or tick rows, `Account`, `Position`, `Order`, `Trade`, or their stock variants. 8. Read [references/simulation-and-backtest.md](references/simulation-and-backtest.md) for local sim, Quick sim, stock sim, backtest, and cross-account backtest limits. 9. Read [references/error-faq.md](references/error-faq.md) when the user asks about common TqSdk failures, confusing behavior, or exception messages. 10. Read [references/example-map.md](references/example-map.md) when you want a repository-backed example or doc page to imitate. +## Before Writing Code + +1. Identify the task mode first: market data only, local sim, Quick sim, real account, stock trading, futures trading, backtest, or margin trial. +2. If an example needs a current futures or option contract, verify it with `query_quotes(..., expired=False)`, `query_options(..., expired=False)`, or a main-contract symbol before using it. +3. Prefer main-contract symbols such as `KQ.m@SHFE.rb` for market-data and historical-download examples when the user does not need a tradable delivery month. +4. Use a concrete non-expired delivery-month contract for order, position, `TargetPosTask`, and margin examples. +5. Do not expose real account names, passwords, tokens, or customer credentials. Use placeholders or environment variables. +6. When writing or changing code, run the smallest practical verification, such as import checks, syntax checks, a short quote subscription, or a narrow backtest. + ## Core Rules 1. Treat `get_*` results as live references, not snapshots. Explain that they refresh during `wait_update()`. @@ -39,6 +48,8 @@ Read only the references needed for the user's question. - `TargetPosTask` for target net position. - `TargetPosTask(..., support_open_min_volume=True)` only for contracts with exchange minimum opening size rules when approximate completion is acceptable. - `TargetPosScheduler` plus `twap_table` or `vwap_table` from `tqsdk.algorithm` for scheduled execution. + - `TargetPosScheduler(..., support_open_min_volume=True)` when the scheduled execution targets a contract with exchange minimum opening size rules. + - Direct `tqsdk.algorithm.Twap(..., support_open_min_volume=True)` only when the user asks for the older/direct TWAP helper; note that it is not for backtest. - Mention `InsertOrderTask` and `InsertOrderUntilAllTradedTask` as internal or advanced helpers, not the default answer. 10. Use `TqScenario` for synchronous what-if margin and risk trials, not for live order placement. It is futures-only, single-account, and updates the trial snapshot immediately after each call. 11. Explain the margin-rate source in every `TqScenario` answer: diff --git a/doc/ai_editor/skills/tqsdk-trading-and-data/references/account-type-matrix.md b/doc/ai_editor/skills/tqsdk-trading-and-data/references/account-type-matrix.md index 4e539f85..a0728dc8 100644 --- a/doc/ai_editor/skills/tqsdk-trading-and-data/references/account-type-matrix.md +++ b/doc/ai_editor/skills/tqsdk-trading-and-data/references/account-type-matrix.md @@ -3,7 +3,7 @@ ## Use This Reference For - Choosing the right account mode before writing code -- Explaining the difference among `TqAccount`, `TqKq`, `TqKqStock`, `TqSim`, `TqSimStock`, and `TqMultiAccount` +- Explaining the difference among `TqAccount`, `TqKq`, `TqKqStock`, `TqSim`, `TqSimStock`, `TqTradingUnit`, enterprise OTG accounts, and `TqMultiAccount` - Answering "which account class should I use?" ## Quick Selection @@ -16,6 +16,7 @@ Use the smallest account type that satisfies the request. | `TqSim()` | Local simulation, strategy development, futures backtest | Futures | Usually yes for data | Local simulated account | | `TqKq()` | Quick simulated trading that should match Quick clients | Futures | Yes | Remote simulated account in the Quick ecosystem | | `TqAccount(...)` | Broker live trading | Futures | Yes | Real trading account | +| `TqTradingUnit(...)` | Local multi-strategy front account | Futures | Yes | Use after local multi-strategy setup with `tqsdk-zq` | | `TqSimStock()` | Local stock simulation or stock backtest | Stock | Usually yes for data | Stock-only simulated account | | `TqKqStock()` | Quick stock simulation | Stock | Yes | Remote stock sim | | `TqMultiAccount([...])` | One API driving multiple accounts | Mixed | Yes | Pass `account=` for account-sensitive operations | @@ -29,7 +30,6 @@ These are valid when the user explicitly asks about them or already uses them: - `TqJees` - `TqYida` - `TqZq` -- `TqTradingUnit` Do not lead with these unless the user needs that exact connectivity. @@ -53,6 +53,12 @@ Do not lead with these unless the user needs that exact connectivity. - It is enough for data questions and defaults to a local simulated account. - Use `TqAccount(...)` only when the user wants live trading through a supported broker. +### `TqTradingUnit` And Local Multi-Strategy + +- Use `TqTradingUnit(account_id="front_account")` when the user wants TqSdk local multi-strategy: one backend real account split into multiple front accounts. +- The local multi-strategy system is initialized and managed by `tqsdk-zq`, not only by Python strategy code. +- If the user asks about log export, automatic trading-day switching, abnormal order assignment, reset, or web management, point to `doc/advanced/tq_trading_unit.rst` instead of inventing code-only answers. + ### `TqMultiAccount` Use this when one strategy must operate more than one account at once. @@ -71,12 +77,15 @@ Rules: - Stock local sim or stock backtest: `TqSimStock()` - Stock Quick sim: `TqKqStock()` - Broker live trading: `TqAccount(...)` +- Local multi-strategy front account: `TqTradingUnit(...)` - One API, many accounts: `TqMultiAccount([...])` ## Repository Sources - `tqsdk/__init__.py` - `tqsdk/tradeable/__init__.py` +- `tqsdk/tradeable/otg/tqtradingunit.py` +- `doc/advanced/tq_trading_unit.rst` - `doc/usage/framework.rst` - `doc/usage/shinny_account.rst` - `doc/reference/tqsdk.tqkq.rst` diff --git a/doc/ai_editor/skills/tqsdk-trading-and-data/references/accounts-and-trading.md b/doc/ai_editor/skills/tqsdk-trading-and-data/references/accounts-and-trading.md index a758753b..e3e7f29a 100644 --- a/doc/ai_editor/skills/tqsdk-trading-and-data/references/accounts-and-trading.md +++ b/doc/ai_editor/skills/tqsdk-trading-and-data/references/accounts-and-trading.md @@ -55,7 +55,7 @@ from tqsdk import TqApi, TqAuth api = TqApi(auth=TqAuth("快期账户", "账户密码")) account = api.get_account() -position = api.get_position("DCE.m2505") +position = api.get_position("DCE.m2609") orders = api.get_order() trades = api.get_trade() @@ -70,6 +70,17 @@ while True: For generic examples, `TqApi(auth=...)` is enough and defaults to a local `TqSim()` account. Switch to `TqKq()`, `TqAccount(...)`, or another explicit account class only when the user needs that exact account mode. +Local multi-strategy front-account pattern: + +```python +from tqsdk import TqApi, TqAuth, TqTradingUnit + +account = TqTradingUnit(account_id="前端账户号") +api = TqApi(account, auth=TqAuth("快期账户", "账户密码")) +``` + +Use this pattern only after the local multi-strategy system has been initialized and the front account has been created in `tqsdk-zq`. + Important collection behavior: - `get_order()` without `order_id` returns a dict-like collection keyed by order id @@ -113,7 +124,7 @@ sim_acc = TqKq() api = TqApi(TqMultiAccount([real_acc, sim_acc]), auth=TqAuth("快期账户", "账户密码")) account_info = api.get_account(account=sim_acc) -position = api.get_position("DCE.m2505", account=sim_acc) +position = api.get_position("DCE.m2609", account=sim_acc) orders = api.get_order(account=sim_acc) trades = api.get_trade(account=sim_acc) ``` @@ -122,7 +133,7 @@ Account-object pattern: ```python account_info = sim_acc.get_account() -position = sim_acc.get_position("DCE.m2505") +position = sim_acc.get_position("DCE.m2609") orders = sim_acc.get_order() trades = sim_acc.get_trade() ``` @@ -131,6 +142,8 @@ trades = sim_acc.get_trade() - `tqsdk/api.py` - `tqsdk/tradeable/mixin.py` +- `tqsdk/tradeable/otg/tqtradingunit.py` +- `doc/advanced/tq_trading_unit.rst` - `tqsdk/demo/tutorial/t40.py` - `tqsdk/demo/download_orders.py` - `tqsdk/demo/multiaccount.py` diff --git a/doc/ai_editor/skills/tqsdk-trading-and-data/references/error-faq.md b/doc/ai_editor/skills/tqsdk-trading-and-data/references/error-faq.md index 31ebc043..f3b9bf40 100644 --- a/doc/ai_editor/skills/tqsdk-trading-and-data/references/error-faq.md +++ b/doc/ai_editor/skills/tqsdk-trading-and-data/references/error-faq.md @@ -15,6 +15,7 @@ - `TargetPosTask` does not act - `TargetPosTask` and minimum open volume contracts - Multi-account errors +- Multi-strategy setup errors - `offset` errors - Stock and futures mixed up - `wait_update()` blocks too long @@ -88,6 +89,22 @@ Fix: - pass `account=` to account-sensitive APIs - or switch to `account_obj.get_account()`, `account_obj.get_position()`, `account_obj.get_order()`, `account_obj.get_trade()` +## Multi-Strategy Setup Errors + +Typical causes: + +- the local multi-strategy system has not been initialized with `tqsdk-zq` +- the front account was not created in the multi-strategy management page +- the strategy used a backend account directly instead of `TqTradingUnit(account_id=...)` +- the user is asking about log export, automatic trading-day switching, or abnormal order assignment as if it were normal strategy code + +Fix: + +- initialize or update the local multi-strategy environment with `tqsdk-zq` +- create and fund the front account in the management page first +- in Python strategy code, use `TqTradingUnit(account_id="前端账户号")` +- for log export, automatic trading-day switching, reset, web management, and abnormal order assignment, point to `doc/advanced/tq_trading_unit.rst` + ## `offset` Errors Typical causes: @@ -191,6 +208,8 @@ When debugging, check in this order: - `tqsdk/lib/target_pos_scheduler.py` - `tqsdk/tools/downloader.py` - `tqsdk/tradeable/mixin.py` +- `tqsdk/tradeable/otg/tqtradingunit.py` +- `doc/advanced/tq_trading_unit.rst` - `tqsdk/exceptions.py` - `doc/usage/framework.rst` - `doc/usage/backtest.rst` diff --git a/doc/ai_editor/skills/tqsdk-trading-and-data/references/example-map.md b/doc/ai_editor/skills/tqsdk-trading-and-data/references/example-map.md index 2cd1e028..ca9fd8b0 100644 --- a/doc/ai_editor/skills/tqsdk-trading-and-data/references/example-map.md +++ b/doc/ai_editor/skills/tqsdk-trading-and-data/references/example-map.md @@ -50,6 +50,8 @@ Use this file when you need a concrete repository example or doc page before wri - `cancel()` and `is_finished()` - `doc/advanced/scheduler.rst` - `TargetPosScheduler` +- `tqsdk/algorithm/twap.py` + - direct `Twap` behavior, including `support_open_min_volume` - `design/target_pos_task_support_open_min_limit.md` - design notes for contracts with minimum opening volume rules - `tqsdk/test/target_pos_task/test_open_min_volume.py` @@ -80,6 +82,8 @@ Use this file when you need a concrete repository example or doc page before wri - `TqKq` and `TqKqStock` - `doc/reference/tqsdk.sim.rst` - `TqSim` and `TqSimStock` +- `doc/advanced/tq_trading_unit.rst` + - local multi-strategy setup, `TqTradingUnit`, log export, and trading-day switching ## If The User Asks About Backtest diff --git a/doc/ai_editor/skills/tqsdk-trading-and-data/references/market-data.md b/doc/ai_editor/skills/tqsdk-trading-and-data/references/market-data.md index 140e2553..d5b83c7f 100644 --- a/doc/ai_editor/skills/tqsdk-trading-and-data/references/market-data.md +++ b/doc/ai_editor/skills/tqsdk-trading-and-data/references/market-data.md @@ -38,7 +38,7 @@ Notes: from tqsdk import TqApi, TqAuth api = TqApi(auth=TqAuth("快期账户", "账户密码")) -quote = api.get_quote("SHFE.au2504") +quote = api.get_quote("SHFE.au2608") while True: api.wait_update() @@ -58,12 +58,21 @@ Recommended quote fields: If the user wants "current price", do not hardcode expired delivery months. +When an answer needs a fixed current delivery-month example, query it first: + +```python +contracts = api.query_quotes(ins_class="FUTURE", exchange_id="SHFE", product_id="rb", expired=False) +print(contracts) +``` + +For market-data examples where tradability is not required, prefer `KQ.m@...` main-contract symbols so the example does not expire quickly. For trading, positions, `TargetPosTask`, and margin examples, use a real non-expired delivery-month contract from the query result. + ## K-Line And Tick Series K-line: ```python -klines = api.get_kline_serial("DCE.i2505", 60, data_length=200) +klines = api.get_kline_serial("DCE.i2609", 60, data_length=200) while True: api.wait_update() @@ -74,7 +83,7 @@ while True: Tick: ```python -ticks = api.get_tick_serial("DCE.i2505", data_length=200) +ticks = api.get_tick_serial("DCE.i2609", data_length=200) while True: api.wait_update() diff --git a/doc/ai_editor/skills/tqsdk-trading-and-data/references/order-functions-and-position-tools.md b/doc/ai_editor/skills/tqsdk-trading-and-data/references/order-functions-and-position-tools.md index b6378dce..c6bf9a24 100644 --- a/doc/ai_editor/skills/tqsdk-trading-and-data/references/order-functions-and-position-tools.md +++ b/doc/ai_editor/skills/tqsdk-trading-and-data/references/order-functions-and-position-tools.md @@ -6,6 +6,7 @@ - `cancel_order` - `TargetPosTask` - `TargetPosScheduler` +- `Twap` - Choosing between manual order control, target-position control, and `TqScenario` ## Table Of Contents @@ -16,6 +17,7 @@ - `TargetPosTask` - `TargetPosTask` with exchange minimum open volume - `TargetPosScheduler` +- Direct `Twap` - `TqScenario` versus live execution tools - Advanced helpers beyond the default answer - Stock limitations @@ -46,9 +48,9 @@ Basic futures example: from tqsdk import TqApi, TqAuth api = TqApi(auth=TqAuth("快期账户", "账户密码")) -quote = api.get_quote("SHFE.au2504") +quote = api.get_quote("SHFE.au2608") order = api.insert_order( - symbol="SHFE.au2504", + symbol="SHFE.au2608", direction="BUY", offset="OPEN", volume=1, @@ -99,7 +101,7 @@ Use `TargetPosTask` when the user thinks in target net position, not individual from tqsdk import TqApi, TqAuth, TargetPosTask api = TqApi(auth=TqAuth("快期账户", "账户密码")) -target_pos = TargetPosTask(api, "DCE.m2505") +target_pos = TargetPosTask(api, "DCE.m2609") target_pos.set_target_volume(5) while True: @@ -138,6 +140,8 @@ Important limits: - when the remaining opening chase volume is below `quote.open_min_limit_order_volume`, that opening chase stops instead of sending another order - if the task can only open positions and the calculated opening size is already below `quote.open_min_limit_order_volume`, no order is sent and the task ends - `support_open_min_volume` is part of the singleton construction parameters; cancel the old task before recreating the same account and symbol with a different value +- `TargetPosScheduler` passes `support_open_min_volume`, `min_volume`, and `max_volume` through to `TargetPosTask`, so these same limits apply to scheduled execution +- direct `Twap` has its own `support_open_min_volume` parameter and checks `min_volume_each_order` against `quote.open_min_limit_order_volume` Example completion check: @@ -180,6 +184,42 @@ from tqsdk.algorithm import twap_table, vwap_table It still depends on continuous `wait_update()` calls and must not be mixed with `TargetPosTask` or manual `insert_order()` for the same workflow. +For contracts with exchange minimum opening size rules, pass `support_open_min_volume=True` to `TargetPosScheduler` as well. If split execution is enabled, `min_volume` and `max_volume` still need to satisfy the minimum opening volume rules from `TargetPosTask`. + +The last scheduled item exits after the target is reached; with `support_open_min_volume=True`, it may also finish when the current target-position round ends because the remaining open volume is below the exchange minimum. + +## Direct `Twap` + +Prefer `TargetPosScheduler` plus `twap_table(...)` for new scheduled target-position examples. + +Use direct `tqsdk.algorithm.Twap` only when the user is already using it or asks for direct TWAP order execution. + +Important direct `Twap` rules: + +- it does not support backtest +- it still needs continuous `wait_update()` calls +- `duration` may cross non-trading pauses but not a trading day boundary +- for open-min contracts, pass `support_open_min_volume=True` +- when `support_open_min_volume=True`, `min_volume_each_order` must be at least `quote.open_min_limit_order_volume` +- final filled volume can be smaller than the requested volume when the remaining open volume is below `quote.open_min_limit_order_volume` + +Minimal pattern: + +```python +from tqsdk import TqApi, TqAuth +from tqsdk.algorithm import Twap + +api = TqApi(auth=TqAuth("快期账户", "账户密码")) +twap = Twap(api, "CZCE.MA609", "BUY", "OPEN", 200, 300, 5, 10, support_open_min_volume=True) + +while True: + api.wait_update() + if twap.is_finished(): + break + +api.close() +``` + ## `TqScenario` Versus Live Execution Tools Use `TqScenario` when the user wants synchronous trial calculation for futures margin or risk changes without sending live orders. @@ -198,7 +238,6 @@ These exist, but should not be the first answer unless the user is already using - `InsertOrderTask` - `InsertOrderUntilAllTradedTask` -- `tqsdk.algorithm.Twap` Explain them as advanced or specialized execution helpers, not as the default recommendation. diff --git a/doc/ai_editor/skills/tqsdk-trading-and-data/references/simulation-and-backtest.md b/doc/ai_editor/skills/tqsdk-trading-and-data/references/simulation-and-backtest.md index 64a5ecda..ea691086 100644 --- a/doc/ai_editor/skills/tqsdk-trading-and-data/references/simulation-and-backtest.md +++ b/doc/ai_editor/skills/tqsdk-trading-and-data/references/simulation-and-backtest.md @@ -78,8 +78,8 @@ api = TqApi( auth=TqAuth("快期账户", "账户密码"), ) -klines = api.get_kline_serial("DCE.m2505", 60, data_length=200) -target_pos = TargetPosTask(api, "DCE.m2505") +klines = api.get_kline_serial("DCE.m2609", 60, data_length=200) +target_pos = TargetPosTask(api, "DCE.m2609") while True: api.wait_update() diff --git a/doc/ai_editor/skills/tqsdk-trading-and-data/references/wait-update-and-update-loop.md b/doc/ai_editor/skills/tqsdk-trading-and-data/references/wait-update-and-update-loop.md index 7b582363..a02e7e97 100644 --- a/doc/ai_editor/skills/tqsdk-trading-and-data/references/wait-update-and-update-loop.md +++ b/doc/ai_editor/skills/tqsdk-trading-and-data/references/wait-update-and-update-loop.md @@ -48,7 +48,7 @@ Streaming quote: from tqsdk import TqApi, TqAuth api = TqApi(auth=TqAuth("快期账户", "账户密码")) -quote = api.get_quote("SHFE.au2504") +quote = api.get_quote("SHFE.au2608") while True: if not api.wait_update(): @@ -60,7 +60,7 @@ while True: New K-line only: ```python -klines = api.get_kline_serial("DCE.i2505", 60, data_length=200) +klines = api.get_kline_serial("DCE.i2609", 60, data_length=200) while True: api.wait_update() @@ -71,7 +71,7 @@ while True: Order state tracking: ```python -order = api.insert_order("DCE.m2505", "BUY", offset="OPEN", volume=1) +order = api.insert_order("DCE.m2609", "BUY", offset="OPEN", volume=1) while order.status != "FINISHED": api.wait_update() diff --git a/doc/ai_editor/tqsdk_codebuddy.rst b/doc/ai_editor/tqsdk_codebuddy.rst index 8409088d..77725a1b 100644 --- a/doc/ai_editor/tqsdk_codebuddy.rst +++ b/doc/ai_editor/tqsdk_codebuddy.rst @@ -163,7 +163,7 @@ CodeBuddy 常见使用方式包括: **基础概念与用法:** * “TqSdk 中 ``TqApi`` 与 ``TqAccount`` 的关系是什么?请结合一个最小示例解释。” -* “如何订阅 ``SHFE.rb2410`` 的 1 分钟 K 线?请给出可运行代码。” +* “如何订阅 ``SHFE.rb2610`` 的 1 分钟 K 线?请给出可运行代码。” * “``TargetPosTask`` 和直接调用 ``insert_order`` 的区别是什么?” **结合源码提问:** @@ -185,7 +185,7 @@ CodeBuddy 常见使用方式包括: ----------------------------------------- * **生成最小示例**: - * “写一个最小脚本,订阅 ``SHFE.rb2410`` 的 quote 并打印最新价。” + * “写一个最小脚本,订阅 ``SHFE.rb2610`` 的 quote 并打印最新价。” * “写一个只使用 ``TqSim`` 的模拟交易示例,不连接实盘账户。” * **修改现有策略**: * “只在当前文件中增加参数校验,不改变原有开平仓逻辑。” @@ -263,7 +263,7 @@ CodeBuddy 常见使用方式包括: .. code-block:: text 请在当前项目里写一个最小 TqSdk 示例。 - 目标:订阅 SHFE.rb2410 的 1 分钟 K 线并打印最后一根 K 线。 + 目标:订阅 SHFE.rb2610 的 1 分钟 K 线并打印最后一根 K 线。 只新增一个 demo 文件,不修改其他文件。 写完后请运行语法检查,并说明如何运行。 diff --git a/doc/ai_editor/tqsdk_trae.rst b/doc/ai_editor/tqsdk_trae.rst index 600ae6e0..79ec118f 100644 --- a/doc/ai_editor/tqsdk_trae.rst +++ b/doc/ai_editor/tqsdk_trae.rst @@ -114,7 +114,7 @@ Trae 官网:`https://www.trae.cn/ `_ **基础概念与用法:** * “TqSdk 中 ``TqApi`` 与 ``TqAccount`` 的关系与区别是什么?” -* “如何获取 ``SHFE.rb2410`` 的 1 分钟 K 线?请给完整示例。” +* “如何获取 ``SHFE.rb2610`` 的 1 分钟 K 线?请给完整示例。” * “回测 ``TqBacktest`` 的 ``start_dt`` 和 ``end_dt`` 该如何设置?” * “``insert_order`` 的 ``limit_price`` 与 ``offset`` 参数如何使用?” diff --git a/doc/conf.py b/doc/conf.py index 0436acbd..3d1c5a80 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -54,9 +54,9 @@ # built documents. # # The short X.Y version. -version = u'3.9.9' +version = u'3.10.1' # The full version, including alpha/beta/rc tags. -release = u'3.9.9' +release = u'3.10.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/dev_framework.rst b/doc/dev_framework.rst index b9eb1485..960d9cc8 100644 --- a/doc/dev_framework.rst +++ b/doc/dev_framework.rst @@ -68,7 +68,7 @@ TqSdk中以数据流的方式连接各组件。TqChan(本质是一个asyncio.Que .. raw:: html -
/
/
quotes
quotes
SHFE.cu1901
SHFE.cu1901
last_price
last_price
volume
volume
SHFE.cu1902
SHFE.cu1902
klines
klines
ticks
ticks
trade
trade
user1
user1
positions
positions
SHFE.cu1901
SHFE.cu1901
pos_long
pos_long
pos_short
pos_short
+
/
/
quotes
quotes
SHFE.cu2607
SHFE.cu2607
last_price
last_price
volume
volume
SHFE.cu2608
SHFE.cu2608
klines
klines
ticks
ticks
trade
trade
user1
user1
positions
positions
SHFE.cu2607
SHFE.cu2607
pos_long
pos_long
pos_short
pos_short
在每次收到数据包时,TqApi都会将数据包内容合并到 TqApi._data 中. 具体的代码流程如下: diff --git a/doc/dev_general.rst b/doc/dev_general.rst index b1d2ff75..c8ba4423 100644 --- a/doc/dev_general.rst +++ b/doc/dev_general.rst @@ -26,7 +26,7 @@ TqSdk设计原则 * 不使用多线程, 避免用户处理线程同步问题 * 不使用回调模型, 避免用户维护状态机状态变量 * 提供专门的调仓工具 -* 实盘/模拟/回测/复盘 几种不同运行模式切换, 只需要在代码中做单点修改 +* 实盘/模拟/回测 几种不同运行模式切换, 只需要在代码中做单点修改 行为可验证 diff --git a/doc/enterprise.rst b/doc/enterprise.rst index 5969be1c..beaed0a5 100644 --- a/doc/enterprise.rst +++ b/doc/enterprise.rst @@ -37,7 +37,7 @@ TqSdk 连接平台功能 ------------------------------------------------- TqSdk 提供了资管平台的对接支持,支持用户连接到指定资管平台,例如杰宜斯或者融航资管系统等 -以连融航的模拟服务器为例:: +以连接融航模拟服务器为例:: from tqsdk import TqApi, TqRohon, TqAuth diff --git a/doc/intro.rst b/doc/intro.rst index bd5ab8f4..1a356d34 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -19,23 +19,23 @@ TqSdk 是一个由 `信易科技 `_ 发起并贡献 from tqsdk import TqApi, TqAuth, TqAccount, TargetPosTask api = TqApi(TqAccount("H宏源期货", "4003242", "123456"), auth=TqAuth("快期账户", "账户密码")) # 创建 TqApi 实例, 指定交易账户 - q_1910 = api.get_quote("SHFE.rb1910") # 订阅近月合约行情 - t_1910 = TargetPosTask(api, "SHFE.rb1910") # 创建近月合约调仓工具 - q_2001 = api.get_quote("SHFE.rb2001") # 订阅远月合约行情 - t_2001 = TargetPosTask(api, "SHFE.rb2001") # 创建远月合约调仓工具 + q_2610 = api.get_quote("SHFE.rb2610") # 订阅近月合约行情 + t_2610 = TargetPosTask(api, "SHFE.rb2610") # 创建近月合约调仓工具 + q_2701 = api.get_quote("SHFE.rb2701") # 订阅远月合约行情 + t_2701 = TargetPosTask(api, "SHFE.rb2701") # 创建远月合约调仓工具 while True: api.wait_update() # 等待数据更新 - spread = q_1910.last_price - q_2001.last_price # 计算近月合约-远月合约价差 + spread = q_2610.last_price - q_2701.last_price # 计算近月合约-远月合约价差 print("当前价差:", spread) if spread > 250: print("价差过高: 空近月,多远月") - t_1910.set_target_volume(-1) # 要求把1910合约调整为空头1手 - t_2001.set_target_volume(1) # 要求把2001合约调整为多头1手 + t_2610.set_target_volume(-1) # 要求把近月合约调整为空头1手 + t_2701.set_target_volume(1) # 要求把远月合约调整为多头1手 elif spread < 200: - print("价差回复: 清空持仓") # 要求把 1910 和 2001合约都调整为不持仓 - t_1910.set_target_volume(0) - t_2001.set_target_volume(0) + print("价差回复: 清空持仓") # 要求把近月和远月合约都调整为不持仓 + t_2610.set_target_volume(0) + t_2701.set_target_volume(0) 要快速了解如何使用TqSdk, 可以访问我们的 :ref:`quickstart` diff --git a/doc/profession.rst b/doc/profession.rst index c5edf580..36cf0b68 100644 --- a/doc/profession.rst +++ b/doc/profession.rst @@ -60,9 +60,9 @@ TqSdk 免费版本提供全部的期货、商品/金融期权和上证50、沪 SSE.510050 - 上交所上证50etf SSE.510300 - 上交所沪深300etf SZSE.159919 - 深交所沪深300etf - SSE.10002513 - 上交所上证50etf期权 - SSE.10002504 - 上交所沪深300etf期权 - SZSE.90000097 - 深交所沪深300etf期权 + SSE.10010303 - 上交所上证50etf期权 + SSE.10010936 - 上交所沪深300etf期权 + SZSE.90007432 - 深交所沪深300etf期权 .. _profession_tqkqstock: @@ -106,7 +106,7 @@ EDB Server 数据服务 ------------------------------------------------- 拥有 TqSdk 专业版权限后,还可以通过 EDB server 的数据服务获取更丰富的数据能力,包括: -- 行情历史服务:获取自 2021 年 1 月 1 日(含)以来的期货分钟线数据(以及日线数据),用于回测、复盘与数据分析等场景。 +- 行情历史服务:获取自 2021 年 1 月 1 日(含)以来的期货分钟线数据(以及日线数据),用于回测与数据分析等场景。 - 非价量数据服务:获取基本面/宏观等 EDB 指标数据(如库存、交割量、现货、宏观经济指标等),便于研究、因子开发与报表看板构建。 具体接口说明与使用方式可参考 `EDB 数据服务产品文档 `_ (Token 服务、行情历史服务、非价量数据服务)。 diff --git a/doc/quickstart.rst b/doc/quickstart.rst index 60cfec72..401fdef4 100644 --- a/doc/quickstart.rst +++ b/doc/quickstart.rst @@ -23,7 +23,7 @@ ------------------------------------------------- 天勤量化的核心是TqSdk开发包, 在安装天勤量化 (TqSdk) 前, 你需要先准备适当的环境和Python包管理工具, 包括: -* Python >= 3.8 版本 +* Python >= 3.9 版本 * Windows 7 以上版本, macOS, 或 Linux @@ -48,7 +48,7 @@ from tqsdk import TqApi, TqAuth api = TqApi(auth=TqAuth("快期账户", "账户密码")) - quote = api.get_quote("SHFE.ni2206") + quote = api.get_quote("SHFE.ni2607") while True: api.wait_update() @@ -70,11 +70,11 @@ api = TqApi(auth=TqAuth("快期账户", "账户密码")) -获得上期所 ni2206 合约的行情引用:: +获得上期所 ni2607 合约的行情引用:: - quote = api.get_quote("SHFE.ni2206") + quote = api.get_quote("SHFE.ni2607") -现在, 我们获得了一个对象 quote. 这个对象总是指向 SHFE.ni2206 合约的最新行情. 我们可以通过 quote 的各个字段访问行情数据:: +现在, 我们获得了一个对象 quote. 这个对象总是指向 SHFE.ni2607 合约的最新行情. 我们可以通过 quote 的各个字段访问行情数据:: print (quote.last_price, quote.volume) @@ -103,9 +103,9 @@ 使用K线数据 ------------------------------------------------- -你很可能会需要合约的K线数据. 在TqSdk中, 你可以很方便的获得K线数据. 我们来请求 ni2206 合约的10秒线:: +你很可能会需要合约的K线数据. 在TqSdk中, 你可以很方便的获得K线数据. 我们来请求 ni2607 合约的10秒线:: - klines = api.get_kline_serial("SHFE.ni2206", 10) + klines = api.get_kline_serial("SHFE.ni2607", 10) klines是一个pandas.DataFrame对象. 跟 api.get_quote() 一样, api.get_kline_serial() 也是返回K线序列的引用对象. K线序列数据也会跟实时行情一起同步自动更新. 你也同样需要用 api.wait_update() 等待数据刷新. @@ -130,7 +130,7 @@ klines是一个pandas.DataFrame对象. 跟 api.get_quote() 一样, api.get_kline 要获得你交易账户中某个合约的持仓情况, 可以请求一个持仓引用对象:: - position = api.get_position("DCE.m1901") + position = api.get_position("DCE.m2609") 与行情数据一样, 它们也通过 api.wait_update() 获得更新, 你也同样可以访问它们的成员变量:: @@ -139,7 +139,7 @@ klines是一个pandas.DataFrame对象. 跟 api.get_quote() 一样, api.get_kline 要在交易账户中发出一个委托单, 使用 api.insert_order() 函数:: - order = api.insert_order(symbol="DCE.m2105", direction="BUY", offset="OPEN", volume=5, limit_price=3000) + order = api.insert_order(symbol="DCE.m2609", direction="BUY", offset="OPEN", volume=5, limit_price=3000) 这个函数调用后会立即返回, order 是一个指向此委托单的引用对象, 你总是可以通过它的成员变量来了解委托单的最新状态:: @@ -163,7 +163,7 @@ klines是一个pandas.DataFrame对象. 跟 api.get_quote() 一样, api.get_kline ------------------------------------------------- 在这一节中, 我们将创建一个简单的自动交易程序: 每当行情最新价高于最近15分钟均价时, 开仓买进. 这个程序是这样的:: - klines = api.get_kline_serial("DCE.m2105", 60) + klines = api.get_kline_serial("DCE.m2609", 60) while True: api.wait_update() if api.is_changing(klines): @@ -171,7 +171,7 @@ klines是一个pandas.DataFrame对象. 跟 api.get_quote() 一样, api.get_kline print("最新价", klines.close.iloc[-1], "MA", ma) if klines.close.iloc[-1] > ma: print("最新价大于MA: 市价开仓") - api.insert_order(symbol="DCE.m2105", direction="BUY", offset="OPEN", volume=5) + api.insert_order(symbol="DCE.m2609", direction="BUY", offset="OPEN", volume=5) 上面的代码中出现了一个新函数 api.is_changing(). 这个函数用于判定指定对象是否在最近一次 wait_update 中被更新. @@ -187,14 +187,14 @@ klines是一个pandas.DataFrame对象. 跟 api.get_quote() 一样, api.get_kline from tqsdk import TqApi, TqAuth, TargetPosTask api = TqApi(auth=TqAuth("快期账户", "账户密码")) - quote_near = api.get_quote("SHFE.ni2010") - quote_deferred = api.get_quote("SHFE.ni2011") + quote_near = api.get_quote("SHFE.ni2607") + quote_deferred = api.get_quote("SHFE.ni2608") - # 创建 ni2010 的目标持仓 task,该 task 负责调整 ni2010 的仓位到指定的目标仓位 - target_pos_near = TargetPosTask(api, "SHFE.ni2010") - # 创建 ni2011 的目标持仓 task,该 task 负责调整 ni2011 的仓位到指定的目标仓位 - target_pos_deferred = TargetPosTask(api, "SHFE.ni2011") + # 创建 ni2607 的目标持仓 task,该 task 负责调整 ni2607 的仓位到指定的目标仓位 + target_pos_near = TargetPosTask(api, "SHFE.ni2607") + # 创建 ni2608 的目标持仓 task,该 task 负责调整 ni2608 的仓位到指定的目标仓位 + target_pos_deferred = TargetPosTask(api, "SHFE.ni2608") while True: api.wait_update() diff --git a/doc/usage/backtest.rst b/doc/usage/backtest.rst index f4e01e0a..31709545 100644 --- a/doc/usage/backtest.rst +++ b/doc/usage/backtest.rst @@ -24,7 +24,8 @@ ------------------------------------------------- 要在回测结束时调用您自己写的代码, 可以使用 try/except 机制捕获回测结束信号 BacktestFinished, 像这样:: - from tqsdk import BacktestFinished + from datetime import date + from tqsdk import BacktestFinished, TqApi, TqAuth, TqBacktest, TqSim acc = TqSim() @@ -64,7 +65,8 @@ 要在回测结束时,如果依然需要在浏览器中查看绘图结果,同时又需要打印回测信息,您应该这样做:: - from tqsdk import BacktestFinished + from datetime import date + from tqsdk import BacktestFinished, TqApi, TqAuth, TqBacktest, TqSim acc = TqSim() @@ -90,7 +92,7 @@ from datetime import date from tqsdk import TqApi, TqAuth, TqBacktest, BacktestFinished - api = TqApi(backtest=TqBacktest(start_dt=date(2020, 1, 1), end_dt=date(2020, 10, 1)), auth=TqAuth("快期账户", "账户密码")) + api = TqApi(backtest=TqBacktest(start_dt=date(2026, 5, 18), end_dt=date(2026, 5, 22)), auth=TqAuth("快期账户", "账户密码")) quote = api.get_quote("KQ.m@CFFEX.T") print(quote.datetime, quote.underlying_symbol) @@ -103,10 +105,7 @@ api.close() # 预期输出: - # 2019-12-31 15:14:59.999999 CFFEX.T2003 - # 2020-02-19 09:15:00.000000 CFFEX.T2006 - # 2020-05-14 09:15:00.000000 CFFEX.T2009 - # 2020-08-19 09:30:00.000000 CFFEX.T2012 + # 2026-05-18 09:30:00.000000 CFFEX.T2606 .. _backtest_rule: @@ -128,11 +127,11 @@ 回测框架的规则是当没有新的事件需要用户处理时才推进到下一个行情, 也就是这样:: - q = api.get_quote("SHFE.cu1901") + q = api.get_quote("SHFE.cu2607") api.wait_update() # 这个 wait_update 更新了行情 - api.insert_order("SHFE.cu1901", ...) # 程序下单 + api.insert_order("SHFE.cu2607", ...) # 程序下单 api.wait_update() # 这个 wait_update 只会更新委托单状态, 行情还是停在原处 - api.insert_order("SHFE.cu1901", ...) # 如果又下了一个单 + api.insert_order("SHFE.cu2607", ...) # 如果又下了一个单 api.wait_update() # 这个 wait_update 还是只会更新委托单状态, 行情还是停在原处 api.wait_update() # 这个 wait_update 更新了行情 @@ -157,7 +156,7 @@ TqSdk 在 3.2.0 版本后支持了对股票合约进行回测功能,在回测 api = TqApi(account=TqMultiAccount([tqsim_future, tqsim_stock]), auth=TqAuth("快期账户", "账户密码")) # 多账户下单,需要指定下单账户 - order1 = api.insert_order(symbol="SHFE.cu2112", direction="BUY", offset="OPEN", volume=10, limit_price=72250.0, account=tqsim_future) + order1 = api.insert_order(symbol="SHFE.cu2607", direction="BUY", offset="OPEN", volume=10, limit_price=72250.0, account=tqsim_future) order2 = api.insert_order(symbol="SSE.603666", direction="BUY", volume=300, account=tqsim_stock) while order1.status != 'FINISHED' or order2.status != 'FINISHED': api.wait_update() @@ -173,25 +172,25 @@ TqSdk 在 3.2.0 版本后支持了对股票合约进行回测功能,在回测 TqSdk 允许一个策略程序中使用多个行情序列, 比如这样:: #... 策略程序代码 - ka1 = api.get_kline_serial("SHFE.cu1901", 60) - ka2 = api.get_kline_serial("SHFE.cu1901", 3600) - kb = api.get_kline_serial("CFFEX.IF1901", 3600) - tsa = api.get_tick_serial("CFFEX.IF1901") - qa = api.get_quote("DCE.a1901") + ka1 = api.get_kline_serial("SHFE.cu2607", 60) + ka2 = api.get_kline_serial("SHFE.cu2607", 3600) + kb = api.get_kline_serial("CFFEX.IF2606", 3600) + tsa = api.get_tick_serial("CFFEX.IF2606") + qa = api.get_quote("DCE.a2609") #... 策略程序代码 TqSdk回测框架使用一套复杂的规则来推进行情: 规则1: tick 序列(例如上面例子中的tsa) 总是按逐 tick 推进:: - tsa = api.get_tick_serial("CFFEX.IF1901") + tsa = api.get_tick_serial("CFFEX.IF2606") print(tsa.datetime.iloc[-1]) # 2018/01/01 09:30:00.000 api.wait_update() # 推进一个tick print(tsa.datetime.iloc[-1]) # 2018/01/01 09:30:00.500 规则2: K线序列 (例如上面例子中的ka1, ka2) 总是按周期推进. 每根K线在创建时和结束时各更新一次:: - ka2 = api.get_kline_serial("SHFE.cu1901", 3600) # 请求小时线 + ka2 = api.get_kline_serial("SHFE.cu2607", 3600) # 请求小时线 print(ka2.iloc[-1]) # 2018/01/01 09:00:00.000, O=35000, H=35000, L=35000, C=35000 小时线刚创建 api.wait_update() # 推进1小时, 前面一个小时线结束, 新开一根小时线 print(ka2.iloc[-2]) # 2018/01/01 09:00:00.000, O=35000, H=35400, L=34700, C=34900 9点这根小时线完成了 @@ -211,8 +210,8 @@ TqSdk回测框架使用一套复杂的规则来推进行情: 规则4: 策略程序中的多个序列的更新, 按时间顺序合并推进. 每次 wait_update 时, 优先处理用户事件, 当没有用户事件时, 从各序列中选择下一次更新时间最近的, 更新到这个时间:: - ka = api.get_kline_serial("SHFE.cu1901", 10) # 请求一个10秒线 - kb = api.get_kline_serial("SHFE.cu1902", 15) # 请求一个15秒线 + ka = api.get_kline_serial("SHFE.cu2607", 10) # 请求一个10秒线 + kb = api.get_kline_serial("SHFE.cu2608", 15) # 请求一个15秒线 print(ka.iloc[-1].datetime, kb.iloc[-1].datetime) # 2018/01/01 09:00:00, 2018/01/01 09:00:00 api.wait_update() # 推进一步, ka先更新了, 时间推到 09:00:10 print(ka.iloc[-1].datetime, kb.iloc[-1].datetime) # 2018/01/01 09:00:10, 2018/01/01 09:00:00 diff --git a/doc/usage/framework.rst b/doc/usage/framework.rst index 929555b8..5bbab950 100644 --- a/doc/usage/framework.rst +++ b/doc/usage/framework.rst @@ -81,20 +81,20 @@ TqApi 实例内存中保存了一份完整业务数据截面, 包括行情/K线 from tqsdk import TqApi, TqAuth, TqSim, TargetPosTask api = TqApi(auth=TqAuth("快期账户", "账户密码")) - klines = api.get_kline_serial("SHFE.rb1901", 60) - position = api.get_position("SHFE.rb1901") - target_pos = TargetPosTask(api, "SHFE.rb1901") + klines = api.get_kline_serial("SHFE.rb2610", 60) + position = api.get_position("SHFE.rb2610") + target_pos = TargetPosTask(api, "SHFE.rb2610") while True: #判断开仓条件的主循环 api.wait_update() #等待业务数据更新 if 开仓条件: - target_pos.set_target_volume(1) #如果触发了,则通过 target_pos 将 SHFE.rb1901 的目标持仓设置为多头 1 手,具体的调仓工作则由 target_pos 在后台完成 + target_pos.set_target_volume(1) #如果触发了,则通过 target_pos 将 SHFE.rb2610 的目标持仓设置为多头 1 手,具体的调仓工作则由 target_pos 在后台完成 break #跳出开仓循环,进入下面的平仓循环 while True: #判断平仓条件的主循环 api.wait_update() if 平仓条件: - target_pos.set_target_volume(0) #如果触发了,则通过 target_pos 将 SHFE.rb1901 的目标持仓设置为0手(即空仓) + target_pos.set_target_volume(0) #如果触发了,则通过 target_pos 将 SHFE.rb2610 的目标持仓设置为0手(即空仓) if position.pos == 0: #如果已经将仓位平掉则跳出循环 break api.close() #注意:程序结束运行前需调用此函数以关闭天勤接口实例并释放相应资源,同时此函数会包含发送最后一次wait_update信息传输 diff --git a/doc/usage/kqd_symbol.rst b/doc/usage/kqd_symbol.rst index 6b886bf9..09c95a5a 100644 --- a/doc/usage/kqd_symbol.rst +++ b/doc/usage/kqd_symbol.rst @@ -10,7 +10,7 @@ .. figure:: /images/foreign01.png -在 TqSdk 中,您可以使用 :py:class:`~tqsdk.TqApi.query_quotes` 函数来获取对应的外盘合约列表,然后再安装自己的需求获取对应合约 +在 TqSdk 中,您可以使用 :py:class:`~tqsdk.TqApi.query_quotes` 函数来获取对应的外盘合约列表,然后再按照自己的需求获取对应合约 合约展示 diff --git a/doc/usage/mddatas.rst b/doc/usage/mddatas.rst index 899b18a0..0e3ec0df 100644 --- a/doc/usage/mddatas.rst +++ b/doc/usage/mddatas.rst @@ -37,23 +37,23 @@ GFEX 广州期货交易所 一些合约代码示例:: - SHFE.cu1901 - 上期所 cu1901 期货合约 - DCE.m1901 - 大商所 m1901 期货合约 - CZCE.SR901 - 郑商所 SR901 期货合约 - CFFEX.IF1901 - 中金所 IF1901 期货合约 - INE.sc2109 - 上期能源 sc2109 期货合约 - GFEX.si2301 - 广期所 si2301 期货合约 + SHFE.cu2607 - 上期所 cu2607 期货合约 + DCE.m2609 - 大商所 m2609 期货合约 + CZCE.SR609 - 郑商所 SR609 期货合约 + CFFEX.IF2606 - 中金所 IF2606 期货合约 + INE.sc2607 - 上期能源 sc2607 期货合约 + GFEX.si2607 - 广期所 si2607 期货合约 - CZCE.SPD SR901&SR903 - 郑商所 SR901&SR903 跨期合约 - DCE.SP a1709&a1801 - 大商所 a1709&a1801 跨期合约 - GFEX.SP si2308&si2309 - 广期所 si2308&si2309 跨期组合 + CZCE.SPD SR609&SR701 - 郑商所 SR609&SR701 跨期合约 + DCE.SP a2609&a2705 - 大商所 a2609&a2705 跨期合约 + GFEX.SP si2607&si2609 - 广期所 si2607&si2609 跨期组合 - DCE.m1807-C-2450 - 大商所豆粕期权 - CZCE.CF003C11000 - 郑商所棉花期权 - SHFE.au2004C308 - 上期所黄金期权 - CFFEX.IO2002-C-3550 - 中金所沪深300股指期权 - INE.sc2109C450 - 上期能源原油期权 - GFEX.si2308-C-5800 - 广期所硅期权 + DCE.m2609-C-2700 - 大商所豆粕期权 + CZCE.CF609C13400 - 郑商所棉花期权 + SHFE.au2608C944 - 上期所黄金期权 + CFFEX.IO2606-C-4650 - 中金所沪深300股指期权 + INE.sc2607C540 - 上期能源原油期权 + GFEX.si2607-C-8500 - 广期所硅期权 KQ.m@CFFEX.IF - 中金所IF品种主连合约 @@ -75,15 +75,15 @@ GFEX 广州期货交易所 SSE.510050 - 上交所上证50ETF SSE.510300 - 上交所沪深300ETF SZSE.159919 - 深交所沪深300ETF - SSE.10002513 - 上交所上证50ETF期权 - SSE.10002504 - 上交所沪深300ETF期权 - SZSE.90000097 - 深交所沪深300ETF期权 + SSE.10010303 - 上交所上证50ETF期权 + SSE.10010936 - 上交所沪深300ETF期权 + SZSE.90007432 - 深交所沪深300ETF期权 SZSE.159915 - 易方达创业板ETF - SZSE.90001277 - 创业板ETF期权 + SZSE.90007481 - 创业板ETF期权 SZSE.159922 - 深交所中证500ETF - SZSE.90001355 - 深交所中证500ETF期权 + SZSE.90007447 - 深交所中证500ETF期权 SSE.510500 - 上交所中证500ETF - SSE.10004497 - 上交所中证500ETF期权 + SSE.10011720 - 上交所中证500ETF期权 SZSE.159901 - 深交所100ETF @@ -100,7 +100,7 @@ GFEX 广州期货交易所 ---------------------------------------------------- :py:meth:`~tqsdk.TqApi.get_quote` 函数提供实时行情和合约信息:: - q = api.get_quote("SHFE.cu2201") + q = api.get_quote("SHFE.cu2607") 返回值为一个dict, 结构如下:: @@ -151,8 +151,8 @@ GFEX 广州期货交易所 "underlying_symbol": "", # 标的合约 "strike_price": NaN, # 行权价 "ins_class": "FUTURE", # 合约类型 - "instrument_id": "SHFE.cu2201", # 合约代码 - "instrument_name": "沪铜2201", # 合约中文名 + "instrument_id": "SHFE.cu2607", # 合约代码 + "instrument_name": "沪铜2607", # 合约中文名 "exchange_id": "SHFE", # 交易所代码 "expired": false, # 合约是否已下市 "trading_time": "{'day': [['09:00:00', '10:15:00'], ['10:30:00', '11:30:00'], ['13:30:00', '15:00:00']], 'night': [['21:00:00', '25:00:00']]}", # 交易时间段 @@ -176,7 +176,7 @@ GFEX 广州期货交易所 对于每个合约, 只需要调用一次 get_quote 函数. 如果需要监控数据更新, 可以使用 :py:meth:`~tqsdk.TqApi.wait_update`:: - q = api.get_quote("SHFE.cu1812") # 获取SHFE.cu1812合约的行情 + q = api.get_quote("SHFE.cu2607") # 获取SHFE.cu2607合约的行情 while api.wait_update(): print(q.last_price) # 收到新行情时都会执行这行 @@ -186,11 +186,11 @@ K线数据 ---------------------------------------------------- :py:meth:`~tqsdk.TqApi.get_kline_serial` 函数获取指定合约和周期的K线序列数据:: - klines = api.get_kline_serial("SHFE.cu1812", 10) # 获取SHFE.cu1812合约的10秒K线 + klines = api.get_kline_serial("SHFE.cu2607", 10) # 获取SHFE.cu2607合约的10秒K线 获取按照时间对齐的多合约K线:: - klines = api.get_kline_serial(["SHFE.au1912", "SHFE.au2006"], 5) # 获取SHFE.au2006向SHFE.au1912对齐的K线 + klines = api.get_kline_serial(["SHFE.au2608", "SHFE.au2610"], 5) # 获取SHFE.au2610向SHFE.au2608对齐的K线 详细使用方法及说明请见 :py:meth:`~tqsdk.TqApi.get_kline_serial` 函数使用说明。 @@ -213,15 +213,15 @@ K线数据 TqSdk中, K线周期以秒数表示,支持不超过1日的任意周期K线,例如:: - api.get_kline_serial("SHFE.cu1901", 70) # 70秒线 - api.get_kline_serial("SHFE.cu1901", 86400) # 86400秒线, 即日线 - api.get_kline_serial("SHFE.cu1901", 86500) # 86500秒线, 超过1日,无效 + api.get_kline_serial("SHFE.cu2607", 70) # 70秒线 + api.get_kline_serial("SHFE.cu2607", 86400) # 86400秒线, 即日线 + api.get_kline_serial("SHFE.cu2607", 86500) # 86500秒线, 超过1日,无效 TqSdk中最多可以获取每个K线序列的最后8000根K线,无论哪个周期。也就是说,你如果提取小时线,最多可以提取最后8000根小时线,如果提取分钟线,最多也是可以提取最后8000根分钟线。 对于每个K线序列, 只需要调用一次 :py:meth:`~tqsdk.TqApi.get_kline_serial` . 如果需要监控数据更新, 可以使用 :py:meth:`~tqsdk.TqApi.wait_update` :: - klines = api.get_kline_serial("SHFE.cu1812", 10) # 获取SHFE.cu1812合约的10秒K线 + klines = api.get_kline_serial("SHFE.cu2607", 10) # 获取SHFE.cu2607合约的10秒K线 while api.wait_update(): print(klines.iloc[-1]) # K线数据有任何变动时都会执行这行 @@ -229,7 +229,7 @@ TqSdk中最多可以获取每个K线序列的最后8000根K线,无论哪个周 如果只想在新K线出现时收到信号, 可以配合使用 :py:meth:`~tqsdk.TqApi.is_changing`:: - klines = api.get_kline_serial("SHFE.cu1812", 10) # 获取SHFE.cu1812合约的10秒K线 + klines = api.get_kline_serial("SHFE.cu2607", 10) # 获取SHFE.cu2607合约的10秒K线 while api.wait_update(): if api.is_changing(klines.iloc[-1], "datetime"): # 判定最后一根K线的时间是否有变化 @@ -240,7 +240,7 @@ Tick序列 ---------------------------------------------------- :py:meth:`~tqsdk.TqApi.get_tick_serial` 函数获取指定合约的Tick序列数据:: - ticks = api.get_tick_serial("SHFE.cu1812") # 获取SHFE.cu1812合约的Tick序列 + ticks = api.get_tick_serial("SHFE.cu2607") # 获取SHFE.cu2607合约的Tick序列 :py:meth:`~tqsdk.TqApi.get_tick_serial` 的返回值是一个 pandas.DataFrame, 常见用法示例如下:: @@ -256,10 +256,10 @@ tick序列的更新监控, 与K线序列采用同样的方式. TqSdk可以订阅任意多个行情和K线, 并在一个wait_update中等待更新. 像这样:: - q1 = api.get_quote("SHFE.cu1901") - q2 = api.get_quote("SHFE.cu1902") - k1 = api.get_kline_serial("SHFE.cu1901", 60) - k2 = api.get_kline_serial("SHFE.cu1902", 60) + q1 = api.get_quote("SHFE.cu2607") + q2 = api.get_quote("SHFE.cu2608") + k1 = api.get_kline_serial("SHFE.cu2607", 60) + k2 = api.get_kline_serial("SHFE.cu2608", 60) while api.wait_update(): print("收到数据了") # 上面4项中的任意一项有变化, 都会到这一句. 具体是哪个或哪几个变了, 用 is_changing 判断 diff --git a/doc/usage/option_trade.rst b/doc/usage/option_trade.rst index f611e315..8a566244 100644 --- a/doc/usage/option_trade.rst +++ b/doc/usage/option_trade.rst @@ -2,17 +2,17 @@ 期权交易 & 交易所官方组合 ==================================================== -TqSdk 中期权合和交易所官方组合的约代码格式参考如下:: +TqSdk 中期权和交易所官方组合的合约代码格式参考如下:: - DCE.m1807-C-2450 - 大商所豆粕期权 - CZCE.CF003C11000 - 郑商所棉花期权 - SHFE.au2004C308 - 上期所黄金期权 - CFFEX.IO2002-C-3550 - 中金所沪深300股指期权 - SSE.10002513 - 上交所上证50etf期权 - SSE.10002504 - 上交所沪深300etf期权 - SZSE.90000097 - 深交所沪深300etf期权 - CZCE.SPD SR901&SR903 - 郑商所 SR901&SR903 跨期合约 - DCE.SP a1709&a1801 - 大商所 a1709&a1801 跨期合约 + DCE.m2609-C-2700 - 大商所豆粕期权 + CZCE.CF609C13400 - 郑商所棉花期权 + SHFE.au2608C944 - 上期所黄金期权 + CFFEX.IO2606-C-4650 - 中金所沪深300股指期权 + SSE.10010303 - 上交所上证50etf期权 + SSE.10010936 - 上交所沪深300etf期权 + SZSE.90007432 - 深交所沪深300etf期权 + CZCE.SPD SR609&SR701 - 郑商所 SR609&SR701 跨期合约 + DCE.SP a2609&a2705 - 大商所 a2609&a2705 跨期合约 @@ -45,17 +45,17 @@ TqSdk 内提供了完善的期权查询函数 :py:meth:`~tqsdk.TqApi.query_optio from tqsdk import TqApi, TqAuth api = TqApi(auth=TqAuth("快期账户", "账户密码")) - ls = api.query_options("SHFE.au2012") - print(ls) # 标的为 "SHFE.au2012" 的所有期权 + ls = api.query_options("SHFE.au2608") + print(ls) # 标的为 "SHFE.au2608" 的所有期权 - ls = api.query_options("SHFE.au2012", option_class="PUT") - print(ls) # 标的为 "SHFE.au2012" 的看跌期权 + ls = api.query_options("SHFE.au2608", option_class="PUT") + print(ls) # 标的为 "SHFE.au2608" 的看跌期权 - ls = api.query_options("SHFE.au2012", option_class="PUT", expired=False) - print(ls) # 标的为 "SHFE.au2012" 的看跌期权, 未下市的 + ls = api.query_options("SHFE.au2608", option_class="PUT", expired=False) + print(ls) # 标的为 "SHFE.au2608" 的看跌期权, 未下市的 - ls = api.query_options("SHFE.au2012", strike_price=340) - print(ls) # 标的为 "SHFE.au2012" 、行权价为 340 的期权 + ls = api.query_options("SHFE.au2608", strike_price=944) + print(ls) # 标的为 "SHFE.au2608" 、行权价为 944 的期权 ls = api.query_options("SSE.510300") print(ls) # 中金所沪深300股指期权 diff --git a/doc/usage/ta.rst b/doc/usage/ta.rst index 05d12b4a..124d3a60 100644 --- a/doc/usage/ta.rst +++ b/doc/usage/ta.rst @@ -9,7 +9,7 @@ tqsdk.ta 模块中包含了大量技术指标. 每个技术指标是一个函数 from tqsdk.ta import MACD - klines = api.get_kline_serial("SHFE.cu1812", 60) # 提取SHFE.cu1812的分钟线 + klines = api.get_kline_serial("SHFE.cu2607", 60) # 提取SHFE.cu2607的分钟线 result = MACD(klines, 12, 26, 9) # 计算MACD指标 print(result["diff"]) # MACD指标中的diff序列 @@ -24,7 +24,7 @@ tqsdk.tafunc 模块中包含了一批序列计算函数. 它们是构成技术 from tqsdk.tafunc import ma - klines = api.get_kline_serial("SHFE.cu1812", 60) # 提取SHFE.cu1812的分钟线 + klines = api.get_kline_serial("SHFE.cu2607", 60) # 提取SHFE.cu2607的分钟线 result = ma(klines.high, 9) # 按K线的最高价序列做9分钟的移动平均 print(result) # 移动平均结果 diff --git a/doc/usage/targetpostask.rst b/doc/usage/targetpostask.rst index 3087ce9c..a9255b50 100644 --- a/doc/usage/targetpostask.rst +++ b/doc/usage/targetpostask.rst @@ -7,7 +7,7 @@ 使用示例如下:: - target_pos = TargetPosTask(api, "SHFE.rb1901") #创建一个自动调仓工具, 负责调整SHFE.rb1901的持仓 + target_pos = TargetPosTask(api, "SHFE.rb2610") #创建一个自动调仓工具, 负责调整SHFE.rb2610的持仓 target_pos.set_target_volume(5) #要求自动调仓工具将持仓调整到5手 do_something_else() #现在你可以做别的事了, 自动调仓工具将会在后台自动下单/撤单/跟单, 直到持仓手数达到5手为止 @@ -16,12 +16,12 @@ from tqsdk import TqApi, TqAuth, TargetPosTask api = TqApi(auth=TqAuth("快期账户", "账户密码")) - quote_near = api.get_quote("SHFE.rb1810") - quote_deferred = api.get_quote("SHFE.rb1901") - # 创建 rb1810 的目标持仓 task,该 task 负责调整 rb1810 的仓位到指定的目标仓位 - target_pos_near = TargetPosTask(api, "SHFE.rb1810") - # 创建 rb1901 的目标持仓 task,该 task 负责调整 rb1901 的仓位到指定的目标仓位 - target_pos_deferred = TargetPosTask(api, "SHFE.rb1901") + quote_near = api.get_quote("SHFE.rb2610") + quote_deferred = api.get_quote("SHFE.rb2701") + # 创建 rb2610 的目标持仓 task,该 task 负责调整 rb2610 的仓位到指定的目标仓位 + target_pos_near = TargetPosTask(api, "SHFE.rb2610") + # 创建 rb2701 的目标持仓 task,该 task 负责调整 rb2701 的仓位到指定的目标仓位 + target_pos_deferred = TargetPosTask(api, "SHFE.rb2701") while True: api.wait_update() @@ -47,7 +47,7 @@ from tqsdk import TqApi, TqAuth, TargetPosTask api = TqApi(auth=TqAuth("快期账户", "账户密码")) - target_pos = TargetPosTask(api, "SHFE.rb2001") + target_pos = TargetPosTask(api, "SHFE.rb2701") # 设定目标净持仓为空头1手 target_pos.set_target_volume(-1) # 目标净持仓由空头1手改为多头1手 diff --git a/doc/usage/trade.rst b/doc/usage/trade.rst index c8978b05..0b441008 100644 --- a/doc/usage/trade.rst +++ b/doc/usage/trade.rst @@ -86,7 +86,7 @@ TqApi 提供以下函数来获取交易账户相关信息: ---------------------------------------------------- 要在交易账户中发出一个委托单, 使用 :py:meth:`~tqsdk.TqApi.insert_order` 函数:: - order = api.insert_order(symbol="SHFE.rb1901", direction="BUY", offset="OPEN", limit_price=4310, volume=2) + order = api.insert_order(symbol="SHFE.rb2610", direction="BUY", offset="OPEN", limit_price=4310, volume=2) print(order) 这个函数调用后会立即返回一个指向此委托单的对象引用,你可以通过它的字段查看最新状态。常见字段如下:: @@ -95,7 +95,7 @@ TqApi 提供以下函数来获取交易账户相关信息: "order_id": "", # "123" (委托单ID, 对于一个用户的所有委托单,这个ID都是不重复的) "exchange_order_id": "", # "1928341" (交易所单号) "exchange_id": "", # "SHFE" (交易所) - "instrument_id": "", # "rb1901" (交易所内的合约代码) + "instrument_id": "", # "rb2610" (交易所内的合约代码) "direction": "", # "BUY" (下单方向, BUY=买, SELL=卖) "offset": "", # "OPEN" (开平标志, OPEN=开仓, CLOSE=平仓, CLOSETODAY=平今) "volume_orign": 0, # 10 (总报单手数) @@ -111,7 +111,7 @@ TqApi 提供以下函数来获取交易账户相关信息: 与其它所有数据一样, 委托单的信息也会在 api.wait_update() 时被自动更新:: - order = api.insert_order(symbol="SHFE.rb1901", direction="BUY", offset="OPEN", limit_price=4310,volume=2) + order = api.insert_order(symbol="SHFE.rb2610", direction="BUY", offset="OPEN", limit_price=4310,volume=2) while order.status != "FINISHED": api.wait_update() print("委托单状态: %s, 未成交手数: %d 手" % (order.status, order.volume_left)) diff --git a/doc/usage/web_gui.rst b/doc/usage/web_gui.rst index 55eee9a4..286f25d4 100644 --- a/doc/usage/web_gui.rst +++ b/doc/usage/web_gui.rst @@ -15,8 +15,8 @@ from tqsdk import TqApi, TqAuth # 创建api实例,设置web_gui=True生成图形化界面 api = TqApi(web_gui=True, auth=TqAuth("快期账户", "账户密码")) - # 订阅 cu2002 合约的10秒线 - klines = api.get_kline_serial("SHFE.cu2002", 10) + # 订阅 cu2607 合约的10秒线 + klines = api.get_kline_serial("SHFE.cu2607", 10) while True: # 通过wait_update刷新数据 api.wait_update() @@ -38,11 +38,11 @@ from datetime import date from tqsdk import TqApi, TqAuth, TqBacktest, TargetPosTask # 在创建 api 实例时传入 TqBacktest 就会进入回测模式,设置web_gui=True开启图形化界面 - api = TqApi(backtest=TqBacktest(start_dt=date(2018, 5, 2), end_dt=date(2018, 6, 2)),web_gui=True, auth=TqAuth("快期账户", "账户密码")) - # 获得 m1901 5分钟K线的引用 - klines = api.get_kline_serial("DCE.m1901", 5 * 60, data_length=15) - # 创建 m1901 的目标持仓 task,该 task 负责调整 m1901 的仓位到指定的目标仓位 - target_pos = TargetPosTask(api, "DCE.m1901") + api = TqApi(backtest=TqBacktest(start_dt=date(2026, 5, 18), end_dt=date(2026, 5, 22)),web_gui=True, auth=TqAuth("快期账户", "账户密码")) + # 获得 m2609 5分钟K线的引用 + klines = api.get_kline_serial("DCE.m2609", 5 * 60, data_length=15) + # 创建 m2609 的目标持仓 task,该 task 负责调整 m2609 的仓位到指定的目标仓位 + target_pos = TargetPosTask(api, "DCE.m2609") while True: api.wait_update() if api.is_changing(klines): diff --git a/doc/version.rst b/doc/version.rst index b731b5b5..12bace0f 100644 --- a/doc/version.rst +++ b/doc/version.rst @@ -2,6 +2,20 @@ 版本变更 ============================= +3.10.1 (2026/06/11) + +* 优化: ::py:class:`~tqsdk.TqAuth` 增加本地账号密码判空 +* docs: 更新 TqSdk Skills 调仓与多策略说明 + + +3.10.0 (2026/05/26) + +* 新增: :py:class:`~tqsdk.TargetPosScheduler` 支持有最小开仓手数限制的合约 +* 新增: :py:class:`~tqsdk.algorithm.twap` 支持有最小开仓手数限制的合约 +* 优化: 多策略支持日志导出和自动切换交易日功能,详情见 :ref:`tq_trading_unit` +* docs: 优化文档 + + 3.9.9 (2026/05/19) * 优化: 迅投直连需指定后端账户进行登录 diff --git a/setup.py b/setup.py index 3507cffb..0564c57d 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name='tqsdk', - version="3.9.9", + version="3.10.1", description='TianQin SDK', author='TianQin', author_email='tianqincn@gmail.com', @@ -16,7 +16,7 @@ long_description_content_type="text/markdown", url='https://www.shinnytech.com/tqsdk', packages=setuptools.find_packages(exclude=["tqsdk.test", "tqsdk.test.*"]), - python_requires='>=3.8', + python_requires='>=3.9', install_requires=["websockets>=10.1", "requests", "numpy", "pandas>=1.1.0", "scipy", "simplejson", "aiohttp", "certifi", "pyjwt", "psutil>=5.9.6", "shinny_structlog", "sgqlc", "filelock", "tqsdk_ctpse", "tqsdk_sm", "packaging"], diff --git a/tqsdk/__init__.py b/tqsdk/__init__.py index e6aeb3c3..5976cb85 100644 --- a/tqsdk/__init__.py +++ b/tqsdk/__init__.py @@ -4,10 +4,10 @@ name = "tqsdk" from tqsdk.api import TqApi -from tqsdk.tradeable import TqAccount, TqZq, TqKq, TqKqStock, TqSim, TqSimStock, TqCtp, TqRohon, TqJees, TqYida, TqO32, O32Account, TqXuntou, TqTradingUnit +from tqsdk.tradeable import TqAccount, TqZq, TqKq, TqKqStock, TqSim, TqSimStock, TqCtp, TqRohon, TqJees, TqYida, TqO32, O32Account, TqIS, ISAccount, TqXuntou, TqTradingUnit from tqsdk.auth import TqAuth from tqsdk.channel import TqChan -from tqsdk.backtest import TqBacktest, TqReplay +from tqsdk.backtest import TqBacktest from tqsdk.exceptions import BacktestFinished, TqBacktestPermissionError, TqTimeoutError, TqRiskRuleError from tqsdk.lib import TargetPosScheduler, TargetPosTask, InsertOrderUntilAllTradedTask, InsertOrderTask, TqNotify from tqsdk.multiaccount import TqMultiAccount diff --git a/tqsdk/__version__.py b/tqsdk/__version__.py index 611851fe..e88098e3 100644 --- a/tqsdk/__version__.py +++ b/tqsdk/__version__.py @@ -1 +1 @@ -__version__ = '3.9.9' +__version__ = '3.10.1' diff --git a/tqsdk/algorithm/time_table_generater.py b/tqsdk/algorithm/time_table_generater.py index ec3e1695..d4889b53 100644 --- a/tqsdk/algorithm/time_table_generater.py +++ b/tqsdk/algorithm/time_table_generater.py @@ -48,11 +48,10 @@ def twap_table(api: TqApi, symbol: str, target_pos: int, duration: int, min_volu Example1:: - from tqsdk import TqApi, TargetPosScheduler + from tqsdk import TqApi, TqAuth, TargetPosScheduler from tqsdk.algorithm import twap_table - api = TqApi(auth="快期账户,用户密码") - quote = api.get_quote("CZCE.MA109") + api = TqApi(auth=TqAuth("快期账户", "账户密码")) # 设置twap任务参数 time_table = twap_table(api, "CZCE.MA109", -100, 600, 1, 5) # 目标持仓 -100 手,600s 内完成 @@ -67,11 +66,12 @@ def twap_table(api: TqApi, symbol: str, target_pos: int, duration: int, min_volu Example2:: - from tqsdk import TqApi, TargetPosScheduler + import pandas + from pandas import DataFrame + from tqsdk import TqApi, TqAuth, TargetPosScheduler from tqsdk.algorithm import twap_table - api = TqApi(auth="快期账户,用户密码") - quote = api.get_quote("CZCE.MA109") + api = TqApi(auth=TqAuth("快期账户", "账户密码")) # 设置 twap 任务参数, time_table = twap_table(api, "CZCE.MA109", -100, 600, 1, 5) # 目标持仓 -100 手,600s 内完成 @@ -91,7 +91,7 @@ def twap_table(api: TqApi, symbol: str, target_pos: int, duration: int, min_volu print(target_pos_sch.trades_df) # 利用成交列表,您可以计算出策略的各种表现指标,例如: - average_trade_price = sum(scheduler.trades_df['price'] * scheduler.trades_df['volume']) / sum(scheduler.trades_df['volume']) + average_trade_price = sum(target_pos_sch.trades_df['price'] * target_pos_sch.trades_df['volume']) / sum(target_pos_sch.trades_df['volume']) print("成交均价:", average_trade_price) api.close() @@ -178,11 +178,10 @@ def vwap_table(api: TqApi, symbol: str, target_pos: int, duration: float, Example1:: - from tqsdk import TqApi, TargetPosScheduler + from tqsdk import TqApi, TqAuth, TargetPosScheduler from tqsdk.algorithm import vwap_table - api = TqApi(auth="快期账户,用户密码") - quote = api.get_quote("CZCE.MA109") + api = TqApi(auth=TqAuth("快期账户", "账户密码")) # 设置 vwap 任务参数 time_table = vwap_table(api, "CZCE.MA109", -100, 600) # 目标持仓 -100 手,600s 内完成 @@ -285,7 +284,7 @@ def _gen_random_list(sum_val: int, min_val: int, max_val: int, length: int = Non :return: 整型列表,满足 sum(list) = sum_val, len(list) == length, min_val < any_item(list) < max_val """ if length is None: - length = sum_val * 2 // (min_val + max_val) # 先确定 ist 长度,interval 大小,再生成 volume_list 随机列表 + length = sum_val * 2 // (min_val + max_val) # 先确定 list 长度,interval 大小,再生成 volume_list 随机列表 # 例如:sum = 16 min_val = 11 max_val = 15,不满足 min_val * length <= sum_val <= max_val * length assert min_val * length <= sum_val <= max_val * length + min_val else: diff --git a/tqsdk/algorithm/twap.py b/tqsdk/algorithm/twap.py index 335a395a..4ec1f7f9 100644 --- a/tqsdk/algorithm/twap.py +++ b/tqsdk/algorithm/twap.py @@ -2,8 +2,6 @@ # -*- coding: utf-8 -*- __author__ = 'yanqiong' - -import asyncio from typing import Optional, Union from tqsdk.algorithm.time_table_generater import _gen_random_list @@ -50,7 +48,8 @@ class Twap(object): def __init__(self, api: TqApi, symbol: str, direction: str, offset: str, volume: int, duration: float, min_volume_each_order: int, max_volume_each_order: int, - account: Optional[Union[TqAccount, TqKq, TqSim]] = None): + account: Optional[Union[TqAccount, TqKq, TqSim]] = None, + support_open_min_volume: bool = False): """ 创建 Twap 实例 @@ -74,6 +73,9 @@ def __init__(self, api: TqApi, symbol: str, direction: str, offset: str, volume: account (TqAccount/TqKq/TqSim): [可选]指定发送下单指令的账户实例, 多账户模式下,该参数必须指定 + support_open_min_volume (bool): [可选]是否支持有最小开仓手数限制的合约,默认 False。 + 为 True 时,当剩余开仓手数小于 quote.open_min_limit_order_volume 时会结束当前追单,不再继续报单。 + Example1:: from tqsdk import TqApi @@ -116,6 +118,20 @@ def __init__(self, api: TqApi, symbol: str, direction: str, offset: str, volume: print(target_twap.trades) print(target_twap.average_trade_price) api.close() + + Example3:: + + from tqsdk import TqApi + from tqsdk.algorithm import Twap + + api = TqApi(auth="快期账户,用户密码") + target_twap = Twap(api, "CZCE.MA609", "BUY", "OPEN", 200, 300, 5, 10, support_open_min_volume=True) + + while True: + api.wait_update() + if target_twap.is_finished(): + break + api.close() """ self._api = api self._account = api._account._check_valid(account) @@ -128,6 +144,7 @@ def __init__(self, api: TqApi, symbol: str, direction: str, offset: str, volume: self._duration = duration self._min_volume_each_order = int(min_volume_each_order) self._max_volume_each_order = int(max_volume_each_order) + self._support_open_min_volume = bool(support_open_min_volume) if self._max_volume_each_order <= 0 or self._min_volume_each_order <= 0: raise Exception("请调整参数, min_volume_each_order、max_volume_each_order 必须是大于 0 的整数。") if self._min_volume_each_order > self._max_volume_each_order: @@ -154,17 +171,33 @@ def average_trade_price(self): async def _run(self, volume_list, interval_list): self._quote = await self._api.get_quote(self._symbol) # 判断最小下单手数是否大于1 - if self._quote.open_min_market_order_volume > 1 or self._quote.open_min_limit_order_volume > 1: - raise Exception( - f"交易所规定 {self._symbol} 的最小市价开仓手数 ({self._quote.open_min_market_order_volume})" - f" 或最小限价开仓手数 ({self._quote.open_min_limit_order_volume}) 大于 1,targetpostask、twap、vwap 这些函数还未支持该规则!" + open_min_limit_volume = self._quote.open_min_limit_order_volume + has_open_min_limit = self._offset == "OPEN" and open_min_limit_volume > 1 + if has_open_min_limit: + if not self._support_open_min_volume: + raise Exception( + f"交易所规定 {self._symbol} 的最小限价开仓手数 ({open_min_limit_volume}) 大于 1," + f"Twap 默认不支持有最小开仓手数限制的合约,如果需要支持,请设置 support_open_min_volume=True。" + ) + if self._min_volume_each_order < open_min_limit_volume: + raise Exception( + f"交易所规定 {self._symbol} 的最小限价开仓手数 ({open_min_limit_volume}) 大于 1," + f"当前 min_volume_each_order ({self._min_volume_each_order}) 小于最小限价开仓手数," + f"可能导致第一次拆单就开仓失败。请调整 min_volume_each_order 参数,使之大于等于最小限价开仓手数。" + ) + self._api._print( + f"交易所规定 {self._symbol} 最小限价开仓手数 ({open_min_limit_volume}) 大于 1。" + f"已启用 support_open_min_volume=True,Twap 最终成交手数可能小于目标手数," + f"剩余手数会小于 {open_min_limit_volume}。", + level="WARNING" ) # 计算得到时间序列,每个时间段快要结束的时间点,此时应该从被动价格切换为主动价格 deadline_timestamp_list, strict_deadline_timestamp_list = self._get_deadline_timestamp(interval_list) + carry_volume = 0 for i in range(len(volume_list)): exit_immediately = (i + 1 == len(volume_list)) # 如果是最后一个时间段,需要全部成交后立即退出 - await self._insert_order(volume_list[i], deadline_timestamp_list[i], strict_deadline_timestamp_list[i], - exit_immediately) + carry_volume = await self._insert_order(volume_list[i] + carry_volume, deadline_timestamp_list[i], + strict_deadline_timestamp_list[i], exit_immediately) async def _trade_recv(self): try: @@ -200,11 +233,19 @@ def _get_deadline_timestamp(self, interval_list): return deadline_timestamp_list, strict_deadline_timestamp_list async def _insert_order(self, volume, end_time, strict_end_time, exit_immediately): + if self._check_open_min_volume_stop(volume): + return volume volume_left = volume try: trade_chan = TqChan(self._api) + volume_limit = ( + (self._min_volume_each_order, self._max_volume_each_order) + if volume > self._max_volume_each_order else (None, None) + ) self._order_task = InsertOrderUntilAllTradedTask(self._api, self._symbol, self._direction, self._offset, - volume=volume, price="PASSIVE", trade_chan=trade_chan, + volume=volume, min_volume=volume_limit[0], + max_volume=volume_limit[1], price="PASSIVE", + trade_chan=trade_chan, trade_objs_chan=self._trade_objs_chan, account=self._account) async with self._api.register_update_notify() as update_chan: @@ -217,6 +258,8 @@ async def _insert_order(self, volume, end_time, strict_end_time, exit_immediatel volume_left = volume_left - (v if self._direction == "BUY" else -v) if exit_immediately and volume_left == 0: break + if self._order_task._task.done() and self._check_open_min_volume_stop(volume_left, False): + break finally: await self._api._cancel_task(self._order_task._task) while not trade_chan.empty(): @@ -224,22 +267,44 @@ async def _insert_order(self, volume, end_time, strict_end_time, exit_immediatel volume_left = volume_left - (v if self._direction == "BUY" else -v) await trade_chan.close() if volume_left > 0: - await self._insert_order_active(volume_left) + volume_left = await self._insert_order_active(volume_left) + return volume_left async def _insert_order_active(self, volume): + if self._check_open_min_volume_stop(volume): + return volume try: trade_chan = TqChan(self._api) + volume_limit = ( + (self._min_volume_each_order, self._max_volume_each_order) + if volume > self._max_volume_each_order else (None, None) + ) self._order_task = InsertOrderUntilAllTradedTask(self._api, self._symbol, self._direction, self._offset, - volume=volume, price="ACTIVE", trade_chan=trade_chan, + volume=volume, min_volume=volume_limit[0], + max_volume=volume_limit[1], price="ACTIVE", + trade_chan=trade_chan, trade_objs_chan=self._trade_objs_chan, account=self._account) - async for v in trade_chan: - volume = volume - (v if self._direction == "BUY" else -v) - if volume == 0: - break + await self._order_task._task finally: + while not trade_chan.empty(): + v = await trade_chan.recv() + volume = volume - (v if self._direction == "BUY" else -v) await trade_chan.close() await self._api._cancel_task(self._order_task._task) + return volume + + def _check_open_min_volume_stop(self, volume: int, print_warning: bool = True) -> bool: + open_min_limit_volume = self._quote.open_min_limit_order_volume + if self._support_open_min_volume and self._offset == "OPEN" and open_min_limit_volume > 1 and 0 < volume < open_min_limit_volume: + if print_warning: + self._api._print( + f"合约 {self._symbol} ({self._direction} 方向), 剩余开仓手数 {volume} 小于最小开仓手数 " + f"{open_min_limit_volume},不进行开仓,本轮追单任务结束", + level="WARNING" + ) + return True + return False def _get_volume_list(self): if self._volume < self._max_volume_each_order: diff --git a/tqsdk/api.py b/tqsdk/api.py index 7aa2f363..26f6fae1 100644 --- a/tqsdk/api.py +++ b/tqsdk/api.py @@ -51,7 +51,7 @@ from tqsdk.auth import TqAuth from tqsdk.baseApi import TqBaseApi from tqsdk.multiaccount import TqMultiAccount -from tqsdk.backtest import TqBacktest, TqReplay +from tqsdk.backtest import TqBacktest from tqsdk.channel import TqChan from tqsdk.connect import TqConnect, MdReconnectHandler, ReconnectTimer from tqsdk.calendar import _get_trading_calendar, TqContCalendar, _init_chinese_rest_days @@ -72,7 +72,7 @@ from tqsdk.risk_rule import TqRiskRule from tqsdk.ins_schema import ins_schema, basic, derivative, future, option from tqsdk.symbols import TqSymbols -from tqsdk.tradeable import TqAccount, TqZq, TqKq, TqKqStock, TqSim, TqSimStock, BaseSim, BaseOtg, TqCtp, TqRohon, TqJees, TqYida, TqO32, TqXuntou, TqTradingUnit +from tqsdk.tradeable import TqAccount, TqZq, TqKq, TqKqStock, TqSim, TqSimStock, BaseSim, BaseOtg, TqCtp, TqRohon, TqJees, TqYida, TqO32, TqIS, TqXuntou, TqTradingUnit from tqsdk.trading_status import TqTradingStatus from tqsdk.tqwebhelper import TqWebHelper from tqsdk.utils import _generate_uuid, _query_for_quote, BlockManagerUnconsolidated, _quotes_add_night, _bisect_value, \ @@ -86,7 +86,7 @@ # 在 python 文档中对 type alias 的定义有多种:TypeAliasType, TypeAlias 以及 simple assignment https://docs.python.org/3.13/library/typing.html#type-aliases # Union 类型支持嵌套 Union 类型,但是不支持嵌套 Union TypeAliasType 类型:https://docs.python.org/3.13/library/typing.html#typing.Union # 但是 Union 文档没有明说是否支持嵌套 Union simple assignment 类型,从实现上看,目前所有版本都支持(最新 3.13) -UnionTradeable = Union[TqAccount, TqKq, TqZq, TqKqStock, TqSim, TqSimStock, TqCtp, TqRohon, TqJees, TqYida, TqO32, TqXuntou, TqTradingUnit] +UnionTradeable = Union[TqAccount, TqKq, TqZq, TqKqStock, TqSim, TqSimStock, TqCtp, TqRohon, TqJees, TqYida, TqO32, TqIS, TqXuntou, TqTradingUnit] class TqApi(TqBaseApi): @@ -98,7 +98,7 @@ class TqApi(TqBaseApi): def __init__(self, account: Optional[Union[TqMultiAccount, UnionTradeable]] = None, auth: Union[TqAuth, str, None] = None, - url: Optional[str] = None, backtest: Union[TqBacktest, TqReplay, None] = None, + url: Optional[str] = None, backtest: Union[TqBacktest, None] = None, web_gui: Union[bool, str] = False, debug: Union[bool, str, None] = None, loop: Optional[asyncio.AbstractEventLoop] = None, disable_print: bool = False, _stock: bool = True, _ins_url=None, _md_url=None, _td_url=None) -> None: @@ -106,7 +106,7 @@ def __init__(self, account: Optional[Union[TqMultiAccount, UnionTradeable]] = No 创建天勤接口实例 Args: - account (None/TqAccount/TqKq/TqKqStock/TqSim/TqSimStock/TqZq/TqCtp/TqRohon/TqJees/TqYida/TqO32/TqXuntou/TqTradingUnit/TqMultiAccount): [可选]交易账号: + account (None/TqAccount/TqKq/TqKqStock/TqSim/TqSimStock/TqZq/TqCtp/TqRohon/TqJees/TqYida/TqO32/TqIS/TqXuntou/TqTradingUnit/TqMultiAccount): [可选]交易账号: * None: 账号将根据环境变量决定, 默认为 :py:class:`~tqsdk.TqSim` * :py:class:`~tqsdk.TqAccount` : 使用实盘账号, 直连行情和交易服务器, 需提供期货公司/帐号/密码 @@ -131,6 +131,8 @@ def __init__(self, account: Optional[Union[TqMultiAccount, UnionTradeable]] = No * :py:class:`~tqsdk.TqO32` : 使用恒生 O32 账号 + * :py:class:`~tqsdk.TqIS` : 使用 IS 柜台账号 + * :py:class:`~tqsdk.TqTradingUnit` : 使用交易单元账号 * :py:class:`~tqsdk.TqXuntou` : 使用迅投账号 @@ -139,7 +141,7 @@ def __init__(self, account: Optional[Union[TqMultiAccount, UnionTradeable]] = No :py:class:`~tqsdk.TqAccount`、:py:class:`~tqsdk.TqKq`、:py:class:`~tqsdk.TqKqStock`、 :py:class:`~tqsdk.TqSim`、:py:class:`~tqsdk.TqSimStock`、:py:class:`~tqsdk.TqZq`、 :py:class:`~tqsdk.TqRohon`、:py:class:`~tqsdk.TqJees`、:py:class:`~tqsdk.TqYida`、 - :py:class:`~tqsdk.TqO32`、:py:class:`~tqsdk.TqXuntou`、 + :py:class:`~tqsdk.TqO32`、:py:class:`~tqsdk.TqIS`、:py:class:`~tqsdk.TqXuntou`、 :py:class:`~tqsdk.TqTradingUnit` 和 :py:class:`~tqsdk.TqCtp` 中的 0 至 N 个或者组合 auth (TqAuth/str): [必填]用户快期账户: @@ -238,7 +240,7 @@ def __init__(self, account: Optional[Union[TqMultiAccount, UnionTradeable]] = No self._auth = None self._account = TqSim() if account is None else account self._backtest = backtest - self._stock = False if isinstance(self._backtest, TqReplay) else _stock + self._stock = _stock self._ins_url = os.getenv("TQ_INS_URL", "https://openmd.shinnytech.com/t/md/symbols/latest.json") self._md_url = os.getenv("TQ_MD_URL", None) self._td_url = os.getenv("TQ_TD_URL", None) @@ -360,7 +362,7 @@ def close(self) -> None: from tqsdk import TqApi, TqAuth from contextlib import closing - with closing(TqApi(auth=TqAuth("快期账户", "账户密码")) as api: + with closing(TqApi(auth=TqAuth("快期账户", "账户密码"))) as api: api.insert_order(symbol="DCE.m1901", direction="BUY", offset="OPEN", volume=3) """ if self._loop.is_closed(): @@ -592,7 +594,7 @@ def get_trading_status(self, symbol: str) -> TradingStatus: if not self._auth._has_feature('tq_trading_status'): raise Exception(f"您的账户不支持查看交易状态信息,需要购买后才能使用。升级网址:https://www.shinnytech.com/tqsdk-buy/") if self._backtest: - raise Exception('回测/复盘不支持查看交易状态信息') + raise Exception('回测不支持查看交易状态信息') ts = _get_obj(self._data, ['trading_status', symbol], self._prototype["trading_status"]["#"]) ts._task = self.create_task(self._handle_trading_status(symbol, ts), _caller_api=True) if not self._loop.is_running(): @@ -998,7 +1000,7 @@ def _get_data_series(self, call_func: str, symbol_list: Union[str, List[str]], d raise Exception( f"{call_func} 数据获取方式仅限专业版用户使用,如需购买专业版或者申请试用,请访问 https://www.shinnytech.com/tqsdk-buy/") if self._backtest: - raise Exception(f"不支持在回测/复盘中调用 {call_func} 接口") + raise Exception(f"不支持在回测中调用 {call_func} 接口") dur_nano = duration_seconds * 1000000000 symbol_list = symbol_list if isinstance(symbol_list, list) else [symbol_list] if len(symbol_list) != 1: @@ -1245,7 +1247,7 @@ def insert_order(self, symbol: str, direction: str, offset: str = "", volume: in Example4:: # 多账户模式下, 分别获取各账户的成交记录 - from tqsdk import TqApi, TqAuth, TqMultiAccount + from tqsdk import TqApi, TqAuth, TqAccount, TqMultiAccount account1 = TqAccount("H海通期货", "123456", "123456") account2 = TqAccount("H宏源期货", "123456", "123456") @@ -3560,16 +3562,6 @@ def _setup_connection(self): if not self._check_account_auth(acc): raise Exception(f"您的账户不支持 {type(acc)},需要购买后才能使用。升级网址:https://www.shinnytech.com/tqsdk-buy/") - # 等待复盘服务器启动 - if isinstance(self._backtest, TqReplay): - sim = None # 复盘时如果用户传入的 TqSim 实例,则使用用户传入的参数 - for acc in self._account._account_list: - if isinstance(acc, TqSim): - sim = acc - break - self._account = TqMultiAccount([sim if sim else TqSim()]) - self._ins_url, self._md_url = self._backtest._create_server(self) - # 连接合约和行情服务器 if self._md_url is None: try: @@ -3631,17 +3623,6 @@ def _setup_connection(self): tq_symbols._run(self, tq_symbols_send_chan, tq_symbols_recv_chan, ws_md_send_chan, ws_md_recv_chan)) ws_md_send_chan, ws_md_recv_chan = tq_symbols_send_chan, tq_symbols_recv_chan - # 复盘模式,定时发送心跳包, 并将复盘日期发在行情的 recv_chan - if isinstance(self._backtest, TqReplay): - ws_md_recv_chan.send_nowait({ - "aid": "rtn_data", - "data": [{ - "_tqsdk_replay": { - "replay_dt": _datetime_to_timestamp_nano(datetime.combine(self._backtest._replay_dt, datetime.min.time()))} - }] - }) - self.create_task(self._backtest._run()) - # 如果处于回测模式,则将行情连接对接到 backtest 上 if isinstance(self._backtest, TqBacktest): ws_md_send_chan._logger_bind(chan_from="backtest") @@ -4388,5 +4369,5 @@ def _get_current_datetime(self): print("在使用天勤量化之前,默认您已经知晓并同意以下免责条款,如果不同意请立即停止使用:https://www.shinnytech.com/blog/disclaimer/", file=sys.stderr) -if platform.python_version().startswith('3.8.'): - warnings.warn("TqSdk 计划在 20260601 之后放弃支持 Python 3.8 版本,请尽快升级 Python 版本。", FutureWarning, stacklevel=1) +if platform.python_version().startswith('3.9.'): + warnings.warn("TqSdk 计划在 20270601 之后放弃支持 Python 3.9 版本,请尽快升级 Python 版本。", FutureWarning, stacklevel=1) diff --git a/tqsdk/auth.py b/tqsdk/auth.py index 0c04099b..8d7a89e5 100644 --- a/tqsdk/auth.py +++ b/tqsdk/auth.py @@ -35,6 +35,14 @@ def __init__(self, user_name: str, password: str): api = TqApi(TqAccount("H海通期货", "022631", "123456"), auth=TqAuth("快期账户", "账户密码")) """ + if not isinstance(user_name, str): + raise Exception("快期账户必须是字符串") + if not user_name.strip(): + raise Exception("快期账户不能为空") + if not isinstance(password, str): + raise Exception("快期账户密码必须是字符串") + if not password.strip(): + raise Exception("快期账户密码不能为空") self._user_name = user_name self._password = password self._auth_url = os.getenv("TQ_AUTH_URL", "https://auth.shinnytech.com") diff --git a/tqsdk/backtest/__init__.py b/tqsdk/backtest/__init__.py index 844369c8..fc6805b7 100644 --- a/tqsdk/backtest/__init__.py +++ b/tqsdk/backtest/__init__.py @@ -3,5 +3,4 @@ __author__ = 'mayanqiong' -from tqsdk.backtest.backtest import TqBacktest -from tqsdk.backtest.replay import TqReplay \ No newline at end of file +from tqsdk.backtest.backtest import TqBacktest \ No newline at end of file diff --git a/tqsdk/data_series.py b/tqsdk/data_series.py index 976ef8e1..a5284d26 100644 --- a/tqsdk/data_series.py +++ b/tqsdk/data_series.py @@ -37,7 +37,7 @@ class DataSeries: **功能限制说明** * 该接口返回的 df 不会随着行情更新 * 暂不支持多合约 Kline 缓存 - * 不支持用户回测/复盘使用。get_data_series() 是直接连接行情服务器,是会下载到未来数据的。 + * 不支持用户回测使用。get_data_series() 是直接连接行情服务器,是会下载到未来数据的。 * 不支持多进程/线程/协程。每个合约+周期只能在同一个线程/进程里下载,因为需要写/读/修改文件,多个线程/进程会造成冲突。 """ diff --git a/tqsdk/lib/target_pos_scheduler.py b/tqsdk/lib/target_pos_scheduler.py index de862fa6..9fde4215 100644 --- a/tqsdk/lib/target_pos_scheduler.py +++ b/tqsdk/lib/target_pos_scheduler.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- __author__ = 'mayanqiong' -import asyncio from typing import Optional, Union from pandas import DataFrame @@ -21,7 +20,8 @@ class TargetPosScheduler(object): def __init__(self, api: TqApi, symbol: str, time_table: DataFrame, offset_priority: str = "今昨,开", min_volume: Optional[int] = None, max_volume: Optional[int] = None, trade_chan: Optional[TqChan] = None, - trade_objs_chan: Optional[TqChan] = None, account: Optional[Union[TqAccount, TqKq, TqSim]] = None) -> None: + trade_objs_chan: Optional[TqChan] = None, account: Optional[Union[TqAccount, TqKq, TqSim]] = None, + support_open_min_volume: bool = False) -> None: """ 创建算法执行引擎实例,根据设定的目标持仓任务列表,调用 TargetPosTask 来调整指定合约到目标头寸。 @@ -30,7 +30,7 @@ def __init__(self, api: TqApi, symbol: str, time_table: DataFrame, offset_priori 2. 请勿同时使用 TargetPosScheduler、TargetPosTask、insert_order() 函数, 否则将导致报错或错误下单。 - 3. `symbol`,`offset_priority`,`min_volume`,`max_volume`,`trade_chan`,`trade_objs_chan`,`account` 这几个参数会直接传给 TargetPosTask,请按照 TargetPosTask 的说明设置参数。 + 3. `symbol`,`offset_priority`,`min_volume`,`max_volume`,`trade_chan`,`trade_objs_chan`,`account`,`support_open_min_volume` 这几个参数会直接传给 TargetPosTask,请按照 TargetPosTask 的说明设置参数。 Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 @@ -68,10 +68,13 @@ def __init__(self, api: TqApi, symbol: str, time_table: DataFrame, offset_priori account (TqAccount/TqKq/TqSim): [可选]指定发送下单指令的账户实例, 多账户模式下,该参数必须指定 + support_open_min_volume (bool): [可选]是否支持有最小开仓手数限制的合约,默认 False,具体说明参考 TargetPosTask 的同名参数。 + 为 True 时,最后一项任务会在目标持仓达到或当前 TargetPosTask 调仓轮次结束后自动结束。 + Example:: from pandas import DataFrame - from tqsdk import TqApi, TargetPosScheduler + from tqsdk import TqApi, TqAuth, TargetPosScheduler api = TqApi(auth=TqAuth("快期账户", "账户密码")) time_table = DataFrame([ @@ -92,6 +95,26 @@ def __init__(self, api: TqApi, symbol: str, time_table: DataFrame, offset_priori average_trade_price = sum(scheduler.trades_df['price'] * scheduler.trades_df['volume']) / sum(scheduler.trades_df['volume']) print("成交均价:", average_trade_price) api.close() + + Example2:: + + from pandas import DataFrame + from tqsdk import TqApi, TqAuth, TargetPosScheduler + + api = TqApi(auth=TqAuth("快期账户", "账户密码")) + time_table = DataFrame([ + [25, 10, "PASSIVE"], + [5, 10, "ACTIVE"], + [25, 20, "PASSIVE"], + [5, 20, "ACTIVE"], + ], columns=['interval', 'target_pos', 'price']) + + scheduler = TargetPosScheduler(api, 'CZCE.MA609', time_table=time_table, support_open_min_volume=True) + while True: + api.wait_update() + if scheduler.is_finished(): + break + api.close() """ self._api = api if isinstance(time_table, TqTimeTable): @@ -104,6 +127,7 @@ def __init__(self, api: TqApi, symbol: str, time_table: DataFrame, offset_priori self._min_volume = min_volume self._max_volume = max_volume self._trade_chan = trade_chan + self._support_open_min_volume = support_open_min_volume self._trade_objs_chan = trade_objs_chan if trade_objs_chan else TqChan(self._api) self._time_table = _check_time_table(time_table) @@ -118,31 +142,36 @@ async def _run(self): quote = await self._api.get_quote(self._symbol) self._time_table['deadline'] = _get_deadline_from_interval(quote, self._time_table['interval']) target_pos_task = None + target_volume_chan = None try: _index = 0 # _index 表示下标 for index, row in self._time_table.iterrows(): if row['price'] is None: target_pos_task = None + target_volume_chan = None else: + target_pos = int(row['target_pos']) + # target_volume_chan 的关闭由 TargetPosTask 负责 + target_volume_chan = TqChan(self._api) target_pos_task = TargetPosTask(api=self._api, symbol=self._symbol, price=row['price'], offset_priority=self._offset_priority, min_volume=self._min_volume, max_volume=self._max_volume, trade_chan=self._trade_chan, trade_objs_chan=self._trade_objs_chan, - account=self._account) - target_pos_task.set_target_volume(row['target_pos']) + account=self._account, + support_open_min_volume=self._support_open_min_volume, + target_volume_chan=target_volume_chan) + target_pos_task.set_target_volume(target_pos) if _index < self._time_table.shape[0] - 1: # 非最后一项 async for _ in self._api.register_update_notify(quote): if _get_trade_timestamp(quote.datetime, float('nan')) > row['deadline']: if target_pos_task: await self._api._cancel_task(target_pos_task._task) break - elif target_pos_task: # 最后一项,如果有 target_pos_task 等待持仓调整完成,否则直接退出 - position = self._account.get_position(self._symbol) - if position.pos != row['target_pos']: - async for _ in self._api.register_update_notify(position): - if position.pos == row['target_pos']: - break + elif target_pos_task and target_volume_chan: # 最后一项,如果有 target_pos_task 等待持仓调整完成,否则直接退出 + async for finished_target_pos in target_volume_chan: + if finished_target_pos == target_pos: + break _index = _index + 1 finally: if target_pos_task: @@ -161,7 +190,7 @@ def cancel(self): Example:: from pandas import DataFrame - from tqsdk import TqApi, TargetPosScheduler + from tqsdk import TqApi, TqAuth, TargetPosScheduler api = TqApi(auth=TqAuth("快期账户", "账户密码")) time_table = DataFrame([ diff --git a/tqsdk/lib/target_pos_task.py b/tqsdk/lib/target_pos_task.py index ec99b1ff..339c8776 100644 --- a/tqsdk/lib/target_pos_task.py +++ b/tqsdk/lib/target_pos_task.py @@ -36,7 +36,7 @@ class TargetPosTaskSingleton(type): def __call__(cls, api, symbol, price="ACTIVE", offset_priority="今昨,开", min_volume=None, max_volume=None, trade_chan=None, trade_objs_chan=None, account: Optional[Union[TqAccount, TqKq, TqSim]]=None, - support_open_min_volume: bool = False, *args, **kwargs): + support_open_min_volume: bool = False, target_volume_chan: Optional[TqChan] = None, *args, **kwargs): target_account = api._account._check_valid(account) if target_account is None: raise Exception(f"多账户模式下, 需要指定账户实例 account") @@ -49,6 +49,7 @@ def __call__(cls, api, symbol, price="ACTIVE", offset_priority="今昨,开", min trade_objs_chan, target_account, support_open_min_volume, + target_volume_chan, *args, **kwargs) else: instance = TargetPosTaskSingleton._instances[key] @@ -76,6 +77,11 @@ def __call__(cls, api, symbol, price="ACTIVE", offset_priority="今昨,开", min f"您试图用不同的 support_open_min_volume 参数创建两个 {symbol} 调仓任务, " f"support_open_min_volume 参数原为 {instance._support_open_min_volume}, 现为 {support_open_min_volume}" ) + if instance._target_volume_chan is not target_volume_chan: + raise Exception( + f"您试图用不同的 target_volume_chan 参数创建两个 {symbol} 调仓任务, " + f"target_volume_chan 原对象 id 为 {id(instance._target_volume_chan)}, 现为 {id(target_volume_chan)}" + ) return TargetPosTaskSingleton._instances[key] @@ -86,7 +92,7 @@ def __init__(self, api: TqApi, symbol: str, price: Union[str, Callable[[str], Un offset_priority: str = "今昨,开", min_volume: Optional[int] = None, max_volume: Optional[int] = None, trade_chan: Optional[TqChan] = None, trade_objs_chan: Optional[TqChan] = None, account: Optional[Union[TqAccount, TqKq, TqSim]] = None, - support_open_min_volume: bool = False) -> None: + support_open_min_volume: bool = False, target_volume_chan: Optional[TqChan] = None) -> None: """ 创建目标持仓task实例,负责调整归属于该task的持仓 **(默认为整个账户的该合约净持仓)**. @@ -150,6 +156,9 @@ def __init__(self, api: TqApi, symbol: str, price: Union[str, Callable[[str], Un 详细说明参考 :ref:`最小开仓手数限制合约 `。 + target_volume_chan (TqChan): [可选]高级用法,仅供 TargetPosScheduler 内部使用,外部不应直接使用。 + 当本轮 set_target_volume 对应的调仓任务结束时,通过该 channel 发送目标持仓手数,用于通知 TargetPosScheduler 继续后续调度。 + **注意** 当 price 参数为函数类型时,该函数应该返回一个有效的价格值,应该避免返回 nan。以下为 price 参数是函数类型时的示例。 @@ -238,6 +247,7 @@ def get_price(direction): self._pos_chan = TqChan(self._api, last_only=True) self._trade_chan = trade_chan self._trade_objs_chan = trade_objs_chan + self._target_volume_chan = target_volume_chan self._support_open_min_volume = bool(support_open_min_volume) self._task = self._api.create_task(self._target_pos_task()) self._time_update_task = self._api.create_task(self._update_time_from_md()) # 监听行情更新并记录当时本地时间的task @@ -284,6 +294,8 @@ async def _exit_task(self): # self._account 类型为 TqSim/TqKq/TqAccount,都包括 _account_key 变量 TargetPosTaskSingleton._instances.pop(self._account._account_key + "#" + self._symbol, None) await self._pos_chan.close() + if self._target_volume_chan is not None: + await self._target_volume_chan.close() await self._api._cancel_task(self._time_update_task) self._wait_task_finished.set_result(True) @@ -312,7 +324,7 @@ def set_target_volume(self, volume: int) -> None: Example2:: # 多账户模式下使用 TargetPosTask - from tqsdk import TqApi, TqMultiAccount, TqAuth, TargetPosTask + from tqsdk import TqApi, TqAccount, TqMultiAccount, TqAuth, TargetPosTask account1 = TqAccount("H海通期货", "123456", "123456") account2 = TqAccount("H宏源期货", "654321", "123456") @@ -460,6 +472,9 @@ async def _target_pos_task(self): account=self._account) all_tasks.append(order_task) delta_volume -= order_volume if order_dir == "BUY" else -order_volume + + if self._target_volume_chan is not None: + self._target_volume_chan.send_nowait(target_pos) finally: await asyncio.gather(*[t._task for t in all_tasks], return_exceptions=True) @@ -474,7 +489,7 @@ def cancel(self): Example1:: from datetime import datetime, time - from tqsdk import TqApi, TargetPosTask + from tqsdk import TqApi, TqAuth, TargetPosTask api = TqApi(auth=TqAuth("快期账户", "账户密码")) quote = api.get_quote("SHFE.rb2110") @@ -505,7 +520,7 @@ def cancel(self): # 在异步代码中使用 from datetime import datetime, time - from tqsdk import TqApi, TargetPosTask + from tqsdk import TqApi, TqAuth, TargetPosTask api = TqApi(auth=TqAuth("快期账户", "账户密码")) quote = api.get_quote("SHFE.rb2110") @@ -517,12 +532,13 @@ async def demo(SYMBOL): async for _ in update_chan: if datetime.strptime(quote.datetime, "%Y-%m-%d %H:%M:%S.%f").time() < time(14, 50): # ... 策略代码 ... + pass else: target_pos_passive.cancel() # 取消 TargetPosTask 实例 await target_pos_passive # 等待 target_pos_passive 处理 cancel 结束 break - target_pos_active = TargetPosTask(api, "SHFE.rb2110", price="ACTIVE") + target_pos_active = TargetPosTask(api, SYMBOL, price="ACTIVE") target_pos_active.set_target_volume(0) # 平所有仓位 pos = await api.get_position(SYMBOL) async with api.register_update_notify() as update_chan: diff --git a/tqsdk/multiaccount.py b/tqsdk/multiaccount.py index 6589a577..82640b5f 100644 --- a/tqsdk/multiaccount.py +++ b/tqsdk/multiaccount.py @@ -35,11 +35,11 @@ def __init__(self, accounts: Optional[List['UnionTradeable']] = None): 创建 TqMultiAccount 实例 Args: - accounts (List[Union[TqAccount, TqKq, TqKqStock, TqSim, TqSimStock, TqZq, TqCtp, TqRohon, TqJees, TqYida, TqO32, TqXuntou, TqTradingUnit]]): [可选] 多账户列表, 若未指定任何账户, 则为 [TqSim()] + accounts (List[Union[TqAccount, TqKq, TqKqStock, TqSim, TqSimStock, TqZq, TqCtp, TqRohon, TqJees, TqYida, TqO32, TqIS, TqXuntou, TqTradingUnit]]): [可选] 多账户列表, 若未指定任何账户, 则为 [TqSim()] Example1:: - from tqsdk import TqApi, TqAccount, TqMultiAccount + from tqsdk import TqApi, TqAuth, TqAccount, TqMultiAccount account1 = TqAccount("H海通期货", "123456", "123456") account2 = TqAccount("H宏源期货", "654321", "123456") diff --git a/tqsdk/risk_rule.py b/tqsdk/risk_rule.py index daacba89..4f74ed6f 100644 --- a/tqsdk/risk_rule.py +++ b/tqsdk/risk_rule.py @@ -77,11 +77,11 @@ def __init__(self, api: 'TqApi', open_counts_limit: int, symbol: Union[str, List * str: 一个合约代码 * list of str: 合约代码列表 - account (TqAccount/TqKq/TqZq/TqKqStock/TqSim/TqSimStock/TqCtp/TqRohon/TqJees/TqYida/TqO32/TqXuntou/TqTradingUnit): [可选] 指定发送下单指令的账户实例, 多账户模式下,该参数必须指定 + account (TqAccount/TqKq/TqZq/TqKqStock/TqSim/TqSimStock/TqCtp/TqRohon/TqJees/TqYida/TqO32/TqIS/TqXuntou/TqTradingUnit): [可选] 指定发送下单指令的账户实例, 多账户模式下,该参数必须指定 Example1:: - from tqsdk import TqApi + from tqsdk import TqApi, TqAuth, TqRiskRuleError from tqsdk.risk_rule import TqRuleOpenCountsLimit api = TqApi(auth=TqAuth("快期账户", "账户密码")) @@ -151,11 +151,11 @@ def __init__(self, api: 'TqApi', open_volumes_limit: int, symbol: Union[str, Lis * str: 一个合约代码 * list of str: 合约代码列表 - account (TqAccount/TqKq/TqZq/TqKqStock/TqSim/TqSimStock/TqCtp/TqRohon/TqJees/TqYida/TqO32/TqXuntou/TqTradingUnit): [可选] 指定发送下单指令的账户实例, 多账户模式下,该参数必须指定 + account (TqAccount/TqKq/TqZq/TqKqStock/TqSim/TqSimStock/TqCtp/TqRohon/TqJees/TqYida/TqO32/TqIS/TqXuntou/TqTradingUnit): [可选] 指定发送下单指令的账户实例, 多账户模式下,该参数必须指定 Example1:: - from tqsdk import TqApi + from tqsdk import TqApi, TqAuth, TqRiskRuleError from tqsdk.risk_rule import TqRuleOpenVolumesLimit api = TqApi(auth=TqAuth("快期账户", "账户密码")) @@ -178,7 +178,7 @@ def __init__(self, api: 'TqApi', open_volumes_limit: int, symbol: Union[str, Lis Example2:: - from tqsdk import TqApi, TqKq, TqRiskRuleError + from tqsdk import TqApi, TqAuth, TqKq, TqRiskRuleError from tqsdk.risk_rule import TqRuleOpenVolumesLimit account = TqKq() @@ -248,11 +248,11 @@ def __init__(self, api: 'TqApi', open_volumes_limit: int, symbol: Union[str, Lis * str: 一个合约代码 * list of str: 合约代码列表 - account (TqAccount/TqKq/TqZq/TqKqStock/TqSim/TqSimStock/TqCtp/TqRohon/TqJees/TqYida/TqO32/TqXuntou/TqTradingUnit): [可选] 指定发送下单指令的账户实例, 多账户模式下,该参数必须指定 + account (TqAccount/TqKq/TqZq/TqKqStock/TqSim/TqSimStock/TqCtp/TqRohon/TqJees/TqYida/TqO32/TqIS/TqXuntou/TqTradingUnit): [可选] 指定发送下单指令的账户实例, 多账户模式下,该参数必须指定 Example:: - from tqsdk import TqApi, TqKq, TqRiskRuleError + from tqsdk import TqApi, TqAuth, TqKq, TqRiskRuleError from tqsdk.risk_rule import TqRuleAccOpenVolumesLimit account = TqKq() @@ -328,11 +328,11 @@ def __init__(self, api: 'TqApi', limit_per_second: int, exchange_id: Union[str, * str: 指定交易所代码,如 "SHFE", "DCE", "CZCE", "CFFEX" 等 * list of str: 交易所代码列表,如 ["DCE", "SHFE"],每个交易所分别限制 - account (TqAccount/TqKq/TqZq/TqKqStock/TqSim/TqSimStock/TqCtp/TqRohon/TqJees/TqYida/TqO32/TqXuntou/TqTradingUnit): [可选] 指定发送下单指令的账户实例, 多账户模式下,该参数必须指定 + account (TqAccount/TqKq/TqZq/TqKqStock/TqSim/TqSimStock/TqCtp/TqRohon/TqJees/TqYida/TqO32/TqIS/TqXuntou/TqTradingUnit): [可选] 指定发送下单指令的账户实例, 多账户模式下,该参数必须指定 Example1:: - from tqsdk import TqApi + from tqsdk import TqApi, TqAuth, TqRiskRuleError from tqsdk.risk_rule import TqRuleOrderRateLimit api = TqApi(auth=TqAuth("快期账户", "账户密码")) @@ -352,7 +352,7 @@ def __init__(self, api: 'TqApi', limit_per_second: int, exchange_id: Union[str, Example2:: - from tqsdk import TqApi, TqKq + from tqsdk import TqApi, TqAuth, TqKq, TqRiskRuleError from tqsdk.risk_rule import TqRuleOrderRateLimit account = TqKq() @@ -373,7 +373,7 @@ def __init__(self, api: 'TqApi', limit_per_second: int, exchange_id: Union[str, Example3:: - from tqsdk import TqApi, TqKq + from tqsdk import TqApi, TqAuth, TqKq, TqRiskRuleError from tqsdk.risk_rule import TqRuleOrderRateLimit account = TqKq() diff --git a/tqsdk/ta.py b/tqsdk/ta.py index db578133..91ee1948 100644 --- a/tqsdk/ta.py +++ b/tqsdk/ta.py @@ -1512,7 +1512,7 @@ def PSY(df, n, m): Example:: # 获取 CFFEX.IF1903 合约的心理线 - from tqsdk import TqApi, TqSim + from tqsdk import TqApi, TqAuth, TqSim from tqsdk.ta import PSY api = TqApi(auth=TqAuth("快期账户", "账户密码")) @@ -1545,7 +1545,7 @@ def QHLSR(df): Example:: # 获取 CFFEX.IF1903 合约的阻力指标 - from tqsdk import TqApi, TqSim + from tqsdk import TqApi, TqAuth, TqSim from tqsdk.ta import QHLSR api = TqApi(auth=TqAuth("快期账户", "账户密码")) @@ -1589,7 +1589,7 @@ def RC(df, n): Example:: # 获取 CFFEX.IF1903 合约的变化率指数 - from tqsdk import TqApi, TqSim + from tqsdk import TqApi, TqAuth, TqSim from tqsdk.ta import RC api = TqApi(auth=TqAuth("快期账户", "账户密码")) @@ -1625,7 +1625,7 @@ def RCCD(df, n, n1, n2): Example:: # 获取 CFFEX.IF1903 合约的异同离差变化率指数 - from tqsdk import TqApi, TqSim + from tqsdk import TqApi, TqAuth, TqSim from tqsdk.ta import RCCD api = TqApi(auth=TqAuth("快期账户", "账户密码")) diff --git a/tqsdk/tools/downloader.py b/tqsdk/tools/downloader.py index 20e151b5..6a08109a 100644 --- a/tqsdk/tools/downloader.py +++ b/tqsdk/tools/downloader.py @@ -161,7 +161,7 @@ def _get_data_series(self) -> Optional[pandas.DataFrame]: Example:: from datetime import datetime, date - rom tqsdk import TqApi, TqAuth + from tqsdk import TqApi, TqAuth from contextlib import closing from tqsdk.tools import DataDownloader diff --git a/tqsdk/tqwebhelper.py b/tqsdk/tqwebhelper.py index 37519d99..2b8062b7 100644 --- a/tqsdk/tqwebhelper.py +++ b/tqsdk/tqwebhelper.py @@ -15,9 +15,9 @@ from tqsdk.tradeable.sim.basesim import BaseSim from tqsdk.auth import TqAuth -from tqsdk.backtest import TqBacktest, TqReplay +from tqsdk.backtest import TqBacktest from tqsdk.channel import TqChan -from tqsdk.datetime import _get_trading_day_start_time, _datetime_to_timestamp_nano +from tqsdk.datetime import _datetime_to_timestamp_nano from tqsdk.diff import _simple_merge_diff from tqsdk.tradeable import TqAccount, TqKq, TqSim @@ -56,9 +56,6 @@ def __init__(self, api): self._api._backtest = TqBacktest(start_dt=datetime.strptime(args["_start_dt"], '%Y%m%d'), end_dt=datetime.strptime(args["_end_dt"], '%Y%m%d')) self._api._print(f"当前回测区间 {args['_start_dt']} - {args['_end_dt']}。") - elif args["_action"] == "replay": - self._api._backtest = TqReplay(datetime.strptime(args["_replay_dt"], '%Y%m%d')) - self._api._print(f"当前复盘日期 {args['_replay_dt']}。") if args["_auth"]: comma_index = args["_auth"].find(',') user_name, pwd = args["_auth"][:comma_index], args["_auth"][comma_index + 1:] @@ -97,7 +94,7 @@ async def _run(self, api_send_chan, api_recv_chan, web_send_chan, web_recv_chan) accounts_info[acc._account_key].update(acc._account_info) self._data = { "action": { - "mode": "replay" if isinstance(self._api._backtest, TqReplay) else "backtest" if isinstance(self._api._backtest, TqBacktest) else "run", + "mode": "backtest" if isinstance(self._api._backtest, TqBacktest) else "run", "md_url_status": '-', "user_name": self._api._auth._user_name, "file_path": file_path[0].upper() + file_path[1:], @@ -188,8 +185,8 @@ async def _data_handler(self, api_recv_chan, web_recv_chan): account_changed = True _simple_merge_diff(self._data["trade"], trade) web_diffs.append({"trade": trade}) - # 处理 backtest replay - if d.get("_tqsdk_backtest") or d.get("_tqsdk_replay"): + # 处理 backtest + if d.get("_tqsdk_backtest"): _simple_merge_diff(self._data, d) web_diffs.append(d) # 处理通知,行情和交易连接的状态 @@ -239,17 +236,10 @@ def send_to_conn_chan(self, chan, diffs): chan.send_nowait(last_diff) def dt_func (self): - # 回测和复盘模式,用 _api._account 一定是 TqSim, 使用 TqSim _get_current_timestamp() 提供的时间 + # 回测模式,用 _api._account 一定是 TqSim, 使用 TqSim _get_current_timestamp() 提供的时间 # todo: 使用 TqSim.EPOCH if self._data["action"]["mode"] == "backtest": return self._data['_tqsdk_backtest']['current_dt'] - elif self._data["action"]["mode"] == "replay": - tqsim_current_timestamp = self._api._account._account_list[0]._get_current_timestamp() - if tqsim_current_timestamp == 631123200000000000: - # 未收到任何行情, TqSim 时间没有更新 - return _get_trading_day_start_time(self._data['_tqsdk_replay']['replay_dt']) - else: - return tqsim_current_timestamp else: return _datetime_to_timestamp_nano(datetime.now()) @@ -296,9 +286,6 @@ async def link_httpserver(self): "md_url": self._api._md_url, "access_token": self._api._auth._access_token, } - # TODO:在复盘模式下发送 replay_dt 给 web 端,服务器改完后可以去掉 - if isinstance(self._api._backtest, TqReplay): - url_response["replay_dt"] = _datetime_to_timestamp_nano(datetime.combine(self._api._backtest._replay_dt, datetime.min.time())) app = web.Application() app.router.add_get(path='/url', handler=lambda request: TqWebHelper.httpserver_url_handler(url_response)) app.router.add_get(path='/', handler=self.httpserver_index_handler) @@ -367,8 +354,4 @@ def parser_env_arguments(): action["_end_dt"] = os.getenv("TQ_END_DT") if not action["_start_dt"] or not action["_end_dt"]: action["_action"] = None - elif action["_action"] == "replay": - action["_replay_dt"] = os.getenv("TQ_REPLAY_DT") - if not action["_replay_dt"]: - action["_action"] = None return action diff --git a/tqsdk/tradeable/__init__.py b/tqsdk/tradeable/__init__.py index 4ffb5ffb..f0d6ed6b 100644 --- a/tqsdk/tradeable/__init__.py +++ b/tqsdk/tradeable/__init__.py @@ -5,6 +5,6 @@ from tqsdk.tradeable.otg.base_otg import BaseOtg -from tqsdk.tradeable.otg import TqAccount, TqZq, TqKq, TqKqStock, TqCtp, TqRohon, TqJees, TqYida, TqO32, O32Account, TqXuntou, TqTradingUnit +from tqsdk.tradeable.otg import TqAccount, TqZq, TqKq, TqKqStock, TqCtp, TqRohon, TqJees, TqYida, TqO32, O32Account, TqIS, ISAccount, TqXuntou, TqTradingUnit from tqsdk.tradeable.sim.basesim import BaseSim from tqsdk.tradeable.sim import TqSim, TqSimStock diff --git a/tqsdk/tradeable/mixin.py b/tqsdk/tradeable/mixin.py index 1258ba5e..1838f460 100644 --- a/tqsdk/tradeable/mixin.py +++ b/tqsdk/tradeable/mixin.py @@ -196,7 +196,7 @@ def get_trade(self, trade_id: Optional[str] = None) -> Union[Trade, Entity]: Example:: # 多账户模式下, 分别获取各账户的成交记录 - from tqsdk import TqApi, TqAuth, TqMultiAccount, TqAccount + from tqsdk import TqApi, TqAuth, TqAccount, TqKq, TqMultiAccount, TqSim account = TqAccount("N南华期货", "123456", "123456") tqkq = TqKq() diff --git a/tqsdk/tradeable/otg/__init__.py b/tqsdk/tradeable/otg/__init__.py index d965f337..a5afe2ed 100644 --- a/tqsdk/tradeable/otg/__init__.py +++ b/tqsdk/tradeable/otg/__init__.py @@ -11,5 +11,6 @@ from tqsdk.tradeable.otg.tqjees import TqJees from tqsdk.tradeable.otg.tqyida import TqYida from tqsdk.tradeable.otg.tqo32 import TqO32, O32Account +from tqsdk.tradeable.otg.tqis import TqIS, ISAccount from tqsdk.tradeable.otg.tqxuntou import TqXuntou from tqsdk.tradeable.otg.tqtradingunit import TqTradingUnit diff --git a/tqsdk/tradeable/otg/tqctp.py b/tqsdk/tradeable/otg/tqctp.py index 9327bfaf..74435f18 100644 --- a/tqsdk/tradeable/otg/tqctp.py +++ b/tqsdk/tradeable/otg/tqctp.py @@ -30,7 +30,7 @@ def __init__(self, account_id: str, password: str, front_broker: str, front_url: Example1:: - from tqsdk import TqApi, TqCtp + from tqsdk import TqApi, TqAuth, TqCtp account = TqCtp(account_id="CTP 账户", password="CTP 密码", front_broker="CTP 柜台代码", front_url="CTP 柜台地址", app_id="CTP AppID", auth_code="CTP AuthCode") api = TqApi(account, auth=TqAuth("快期账户", "账户密码")) diff --git a/tqsdk/tradeable/otg/tqis.py b/tqsdk/tradeable/otg/tqis.py new file mode 100644 index 00000000..315224a0 --- /dev/null +++ b/tqsdk/tradeable/otg/tqis.py @@ -0,0 +1,123 @@ +# -*- coding:utf-8 -*- +__author__ = 'chenli' + +import hashlib +from dataclasses import dataclass + +from tqsdk.tradeable.otg.base_otg import BaseOtg +from tqsdk.tradeable.mixin import FutureMixin + + +@dataclass +class ISAccount: + user: str # 用户 + fund: str # 基金 + asset_unit: str # 资产单元 + portfolio: str # 组合 + + def __post_init__(self) -> None: + if not isinstance(self.user, str): + raise Exception("user 参数类型应该是 str") + if not isinstance(self.fund, str): + raise Exception("fund 参数类型应该是 str") + if not isinstance(self.asset_unit, str): + raise Exception("asset_unit 参数类型应该是 str") + if not isinstance(self.portfolio, str): + raise Exception("portfolio 参数类型应该是 str") + + @property + def user_name(self) -> str: + return ".".join([self.user, self.fund, self.asset_unit, self.portfolio]) + + +class TqIS(BaseOtg, FutureMixin): + """IS 柜台账户类""" + + def __init__(self, account_id: ISAccount, password: str, td_front_url: str, mc_front_url: str, + license_file: str, auth_code: str, app_id: str) -> None: + """ + 创建 IS 柜台账户实例 + + Args: + account_id (ISAccount): IS 组合账户 + + password (str): IS 用户密码 + + td_front_url (str): IS 交易前置地址,格式如 111.11.111.111:1111 + + mc_front_url (str): IS 查询前置地址,格式如 110.10.110.110:1110 + + license_file (str): IS 许可证文件路径 + + auth_code (str): IS 授权码 + + app_id (str): IS AppID + + Example1:: + + from tqsdk import TqApi, TqAuth, TqIS, ISAccount + + account = TqIS( + account_id=ISAccount(user="用户", fund="基金", asset_unit="资产单元", portfolio="组合"), + password="IS 密码", + td_front_url="trade_front_host:trade_front_port", + mc_front_url="query_front_host:query_front_port", + license_file="/path/to/licenseIS.dat", + auth_code="auth_code", + app_id="app_id", + ) + api = TqApi(account=account, auth=TqAuth("快期账户", "账户密码")) + + 注意: + 1. 使用 TqIS 账户需要安装 tqsdk_zq_otg 包: pip install -U tqsdk_zq_otg + 2. td_front_url、mc_front_url、license_file、auth_code 和 app_id 信息需要向柜台方获取 + + """ + if not isinstance(account_id, ISAccount): + raise Exception("account_id 参数类型应该是 ISAccount") + if not isinstance(td_front_url, str): + raise Exception("td_front_url 参数类型应该是 str") + if not isinstance(mc_front_url, str): + raise Exception("mc_front_url 参数类型应该是 str") + if not isinstance(license_file, str): + raise Exception("license_file 参数类型应该是 str") + if not isinstance(auth_code, str): + raise Exception("auth_code 参数类型应该是 str") + if not isinstance(app_id, str): + raise Exception("app_id 参数类型应该是 str") + self._td_front_url = td_front_url + self._mc_front_url = mc_front_url + self._license_file = license_file + self._auth_code = auth_code + self._app_id = app_id + if not self._license_file: + raise Exception("license_file 参数不能为空字符串") + super(TqIS, self).__init__(broker_id="", account_id=account_id.user_name, password=password, td_url="zqotg://127.0.0.1:0/trade") + + @property + def _account_auth(self): + return { + "feature": "tq_direct", + "account_id": self._account_id, + "auto_add": True, + } + + def _get_account_key(self): + s = self._broker_id + self._account_id + s += self._td_front_url if self._td_front_url else "" + s += self._mc_front_url if self._mc_front_url else "" + s += self._license_file if self._license_file else "" + return hashlib.md5(s.encode('utf-8')).hexdigest() + + async def _send_login_pack(self): + req = { + "aid": "req_login", + "backend": "is", + "user_name": self._account_id, + "password": self._password, + "trading_fronts": [self._td_front_url, self._mc_front_url], + "license_file_addr": self._license_file, + "auth_code": self._auth_code, + "app_id": self._app_id, + } + await self._td_send_chan.send(req) diff --git a/tqsdk/tradeable/otg/tqjees.py b/tqsdk/tradeable/otg/tqjees.py index 13443266..5091e9bd 100644 --- a/tqsdk/tradeable/otg/tqjees.py +++ b/tqsdk/tradeable/otg/tqjees.py @@ -30,7 +30,7 @@ def __init__(self, account_id: str, password: str, front_broker: str, front_url: Example1:: - from tqsdk import TqApi, TqJees + from tqsdk import TqApi, TqAuth, TqJees account = TqJees(account_id="杰宜斯账户", password="杰宜斯密码", front_broker="杰宜斯柜台代码", front_url="杰宜斯柜台地址", app_id="杰宜斯 AppID", auth_code="杰宜斯 AuthCode") api = TqApi(account, auth=TqAuth("快期账户", "账户密码")) diff --git a/tqsdk/tradeable/otg/tqrohon.py b/tqsdk/tradeable/otg/tqrohon.py index 0739add7..11851222 100644 --- a/tqsdk/tradeable/otg/tqrohon.py +++ b/tqsdk/tradeable/otg/tqrohon.py @@ -30,7 +30,7 @@ def __init__(self, account_id: str, password: str, front_broker: str, front_url: Example1:: - from tqsdk import TqApi, TqRohon + from tqsdk import TqApi, TqAuth, TqRohon account = TqRohon(account_id="融航账户", password="融航密码", front_broker="融航柜台代码", front_url="融航柜台地址", app_id="融航 AppID", auth_code="融航 AuthCode") api = TqApi(account, auth=TqAuth("快期账户", "账户密码")) diff --git a/tqsdk/tradeable/otg/tqxuntou.py b/tqsdk/tradeable/otg/tqxuntou.py index 40701145..68eb78dd 100644 --- a/tqsdk/tradeable/otg/tqxuntou.py +++ b/tqsdk/tradeable/otg/tqxuntou.py @@ -30,7 +30,7 @@ def __init__(self, account_id: str, password: str, front_url: str, app_id: str, Example1:: - from tqsdk import TqApi, TqXuntou + from tqsdk import TqApi, TqAuth, TqXuntou account = TqXuntou(account_id="Xuntou 账户", password="Xuntou 密码", front_url="Xuntou 柜台地址", app_id="Xuntou AppID", auth_code="Xuntou AuthCode") api = TqApi(account, auth=TqAuth("快期账户", "账户密码")) diff --git a/tqsdk/tradeable/otg/tqyida.py b/tqsdk/tradeable/otg/tqyida.py index b1672502..62da3718 100644 --- a/tqsdk/tradeable/otg/tqyida.py +++ b/tqsdk/tradeable/otg/tqyida.py @@ -28,7 +28,7 @@ def __init__(self, account_id: str, password: str, front_url: str, app_id: str, Example1:: - from tqsdk import TqApi, TqYida + from tqsdk import TqApi, TqAuth, TqYida account = TqYida(account_id="易达账户", password="易达密码", front_url="易达柜台地址", app_id="易达 AppID", auth_code="易达 AuthCode") api = TqApi(account, auth=TqAuth("快期账户", "账户密码")) diff --git a/tqsdk/tradeable/otg/tqzq.py b/tqsdk/tradeable/otg/tqzq.py index 2a3bc36b..eb8cca69 100644 --- a/tqsdk/tradeable/otg/tqzq.py +++ b/tqsdk/tradeable/otg/tqzq.py @@ -24,7 +24,7 @@ def __init__(self, account_id: str, password: str, td_url: str) -> None: Example1:: - from tqsdk import TqApi, TqZq + from tqsdk import TqApi, TqAuth, TqZq account = TqZq(account_id="众期账户", password="众期密码", td_url="众期柜台地址") api = TqApi(account, auth=TqAuth("快期账户", "账户密码")) diff --git a/tqsdk/zq_otg.py b/tqsdk/zq_otg.py index 5d568c02..8ad0b715 100644 --- a/tqsdk/zq_otg.py +++ b/tqsdk/zq_otg.py @@ -12,6 +12,7 @@ import uuid from pathlib import Path from asyncio.subprocess import DEVNULL, PIPE +from packaging import version from tqsdk.exceptions import TqContextManagerError @@ -33,7 +34,7 @@ def __init__(self, api): from tqsdk_zq_otg import get_zq_otg_path except ImportError: raise Exception(f"使用 {acc_types} 账户需要安装 tqsdk_zq_otg 包: pip install -U tqsdk_zq_otg") from None - if otg_version < "3.9.9": + if version.parse(otg_version) < version.parse("3.10.1"): raise Exception(f"使用 {acc_types} 账户需要更新 tqsdk_zq_otg 包到最新版本: pip install -U tqsdk_zq_otg") self._zq_otg_path = get_zq_otg_path() self._zq_otg_exe = str(Path(self._zq_otg_path) / "otg_adapter")