ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

TqSdk 与 vn.py 差异全解析:从 vn.py 迁移到天勤量化的架构、策略范式与实战对照指南

TqSdk 与 vn.py 差异全解析:从 vn.py 迁移到天勤量化的架构、策略范式与实战对照指南
  • 金融科技

【免费下载链接】tqsdk-python

天勤量化开发包, 期货量化, 实时行情/历史数据/实盘交易

项目地址:https://gitcode.com/gh_mirrors/tq/tqsdk-python
点击查看免费下载

本文面向已有 vn.py 使用经验的量化开发者,系统梳理 TqSdk(天勤量化开发包)与 vn.py 在系统架构、策略组织方式、K 线数据、行情更新模型、图形界面与回测等维度的核心差异。阅读完本文,你将理解为什么 TqSdk 把"每个策略做成一个独立运行的 Python 文件"、wait_update/is_changing驱动的编程范式与 vn.py 事件回调的区别,并能在迁移已有策略代码时快速套用对应的 TqSdk 写法。如果你只是想先跑通一套最小程序,建议先阅读 quickstart 与 framework;本文更适合在迁移已有策略代码时对照阅读。

一、系统整体架构:all-in-one 与网络协作组件之争

vn.py 是一套 all-in-one 的结构,在一个 Python 软件包中包含了数据库、行情接收/存储、交易接口、图形界面等功能,策略、引擎与接口之间通过进程内的事件循环紧密耦合。

TqSdk 则采用基于网络协作的组件设计,整个系统由以下几个关键组件构成:

  • 行情网关(Open Md Gateway):负责提供实时行情和历史数据,官方专门运维行情数据库,用户可直接使用,不需要自己接收和存储数据;
  • 交易中继网关(Open Trade Gateway):负责连接到期货公司交易系统(CTP / FEMAS / UFX 等);
  • DIFF 协议层:上面两个网关统一以 Diff 协议对下方提供服务,天勤终端和 TqSdk 都按照 Diff 协议连接到行情网关与交易中继网关,实现行情和交易功能。

这样的结构给用户带来的直接好处是:

  • TqSdk 很小,安装方便:只要简单pip install tqsdk即可,无需自行搭建数据库和消息队列;
  • 行情数据开箱即用:官方运维行情数据库,用户直接调用即可,无需自己接收和存储数据;
  • 交易接口被大幅度简化:不再需要处理 CTP 接口的复杂回调,也不需要发起任何查询请求——账户、持仓、委托等状态会随 Diff 协议自动推送并合并更新。

对于需要直连期货公司交易系统的用户,TqSdk 也提供了TqCtp模块支持用户直连(参见 tqsdk.TqCtp 参考文档 与 企业版直连说明)。

从仓库源码来看,这一"薄客户端 + 网络网关"的设计在 tqsdk/api.py 中体现得很明显:TqApi对象内部维护与服务器的 WebSocket 通道(_send_chan/_recv_chan),并通过 tqsdk/diff.py 实现 Diff 协议的增量数据合并,用户侧只需要持有各类业务对象的"引用"并反复调用wait_update即可让数据不断推进。

二、每个策略是一个单独运行的 py 文件:从被调用方到调用方

vn.py 的范式:从基类派生,由框架调用

在 vn.py 中,实现一个策略程序通常是从CtaTemplate等基类派生一个子类:

class DoubleMaStrategy(CtaTemplate): parameters = ["fast_window", "slow_window"] variables = ["fast_ma0", "fast_ma1", "slow_ma0", "slow_ma1"] def __init__(self, cta_engine, strategy_name, vt_symbol, setting): ... def on_tick(self, tick: TickData): ... def on_bar(self, bar: BarData): ...

这个DoubleMaStrategy类写好以后,由 vn.py 的策略管理器(Vnpy cta runner)负责加载运行。整个程序中,vn.py 作为调用方,用户代码作为被调用方:框架负责接收行情和回单,然后调用用户的事件响应函数;用户策略调用框架提供的下单函数来发送交易指令。

TqSdk 的范式:无统一基类,函数自由组合

而在 TqSdk 中,策略程序并没有一个统一的基类。TqSdk 只提供一些行情和交易函数,用户可以任意组合它们来实现自己的策略程序。还是以双均线策略为例:

''' 双均线策略 ''' from tqsdk import TqApi, TqAuth, TqSim, TargetPosTask from tqsdk.tafunc import ma SHORT = 30 LONG = 60 SYMBOL = "SHFE.bu2609" api = TqApi(auth=TqAuth("快期账户", "账户密码")) data_length = LONG + 2 klines = api.get_kline_serial(SYMBOL, duration_seconds=60, data_length=data_length) target_pos = TargetPosTask(api, SYMBOL) while True: api.wait_update() if api.is_changing(klines.iloc[-1], "datetime"): # 产生新k线:重新计算SMA short_avg = ma(klines.close, SHORT) # 短周期 long_avg = ma(klines.close, LONG) # 长周期 # 均线下穿,做空 if long_avg.iloc[-2] < short_avg.iloc[-2] and long_avg.iloc[-1] > short_avg.iloc[-1]: target_pos.set_target_volume(-3) print("均线下穿,做空") # 均线上穿,做多 if short_avg.iloc[-2] < long_avg.iloc[-2] and short_avg.iloc[-1] > long_avg.iloc[-1]: target_pos.set_target_volume(3) print("均线上穿,做多")

以上代码文件单独运行,即可执行一个双均线交易策略。整个程序结构中,用户代码作为调用方,TqSdk 库代码作为被调用方,每个策略是完全独立的进程。仓库中的双均线示例可参考 tqsdk/demo/tutorial/t50.py。

TqSdk 将每个策略作为一个独立进程运行,这样可以:

  • 在运行多策略时充分利用多 CPU 的计算能力;
  • 每个策略都可以随时启动/停止/调试/修改代码,而不影响其它策略程序的运行;
  • 方便地针对单个策略程序进行调试。

在策略程序中,用户代码可以随意调用 TqSdk 包中的任意函数,这带来了更大的自由度,例如:

  • 在一个策略程序中使用多个合约或周期的 K 线数据、盘口数据和 Tick 数据,对某些类型的策略来说很方便;
  • 对多个合约的交易指令进行精细管理;
  • 管理复杂的子任务;
  • 方便策略程序与其它库或框架集成。

实战示例:多合约套利策略

以一个价差回归套利策略为例,可见"多合约同时跟踪 + 各自独立调仓"在 TqSdk 中如何轻松实现:

''' 价差回归 当近月-远月的价差大于200时做空近月,做多远月 当价差小于150时平仓 ''' from tqsdk import TqApi, TqAuth, TargetPosTask api = TqApi(auth=TqAuth("快期账户", "账户密码")) 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() if api.is_changing(quote_near) or api.is_changing(quote_deferred): spread = quote_near.last_price - quote_deferred.last_price print("当前价差:", spread) if spread > 250: print("目标持仓: 空近月,多远月") # 设置目标持仓为正数表示多头,负数表示空头,0表示空仓 target_pos_near.set_target_volume(-1) target_pos_deferred.set_target_volume(1) elif spread < 200: print("目标持仓: 空仓") target_pos_near.set_target_volume(0) target_pos_deferred.set_target_volume(0)

在这个程序中,我们同时跟踪两个合约的行情信息,并为两个合约各创建一个调仓任务,可以方便地实现套利策略。

TargetPosTask 的底层行为

TargetPosTask是"目标持仓"风格的调仓任务,其核心语义(来自 tqsdk/lib/target_pos_task.py)值得了解:

  • set_target_volume只是设置目标手数(正数多头、负数空头、0 空仓),并不立即下单或撤单;实际的下单/撤单动作在之后的每次wait_update()时执行。因此必须保证set_target_volume后还会继续调用wait_update();
  • 每次下单后,任务会根据后续行情计算最新价格,当价格变化时会先撤回本程序发出的在场委托,再按最新价格重新下单;
  • 请勿在使用 TargetPosTask 的同时使用insert_order()函数,否则将导致 TargetPosTask 报错或错误下单;
  • 参数offset_priority(默认"今昨,开")控制开平仓顺序:"今昨,开"表示先平今仓再平昨仓、等待平仓完成后开仓(避免开仓保证金不足);"今昨开"表示所有指令同时发出(适合有单向大边的品种);"昨开"禁止平今仓(适合股指这类平今手续费较高的品种);"开"只开仓不平仓(适合锁仓操作);
  • 若同时设置min_volume与max_volume,则进入大单拆分模式,每笔以两者之间的随机手数下单,全部成交后再继续处理剩余手数,直至剩余手数小于max_volume直接下剩余手数。

三、K 线数据与指标计算:谁生成 K 线?

使用 vn.py 时,K 线由 vn.py 接收实时行情后在用户电脑上生成,并存储于用户电脑上的数据库中。

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

  • 用户不再需要维护 K 线数据库;用户电脑实时行情中断后,也不再需要补历史数据;
  • 行情服务器生成 K 线时,采用了按 K 线时间严格补全对齐的算法,这与 vn.py 或其它软件有明显区别;
  • 行情数据只在每次程序运行时通过网络获取,不在用户硬盘保存。如果策略研究工作需要大量静态历史数据,推荐使用数据下载工具(DataDownloader,参见 tqsdk/tools/downloader.py 及其示例 tqsdk/demo/tutorial/downloader.py),另行下载 csv 文件使用。

K 线序列是 pandas.DataFrame

TqSdk 中的 K 线序列采用pandas.DataFrame格式(由TqApi.get_kline_serial返回,参见 tqsdk/api.py 中该函数的文档注释)。pandas 提供了非常丰富的数据处理函数,可以方便地进行数据处理,例如:

ks = api.get_kline_serial("SHFE.cu2607", 60) print(ks.iloc[-1]) # <- 最后一根K线 print(ks.close) # <- 收盘价序列 ks.high - ks.high.shift(1) # <- 每根K线最高价-前一根K线最高价, 形成一个新序列

get_kline_serial的关键参数与行为:

  • symbol:合约代码(如"SHFE.cu2607"),格式为交易所代码.合约代码;也可以传入合约列表一次性提取多个合约的 K 线,并按相同时间向第一个合约(主合约)对齐(副合约字段名带数字后缀,如open1、close2,datetime/duration为公用字段);
  • duration_seconds:K 线周期,以秒为单位(1 分钟线为 60,1 小时线为 3600,日线为 86400);周期在日线以内时可以任意填写,在日线以上时只能是日线(86400)的整数倍,最大为 28 天(86400*28);
  • data_length:需要获取的序列长度,默认 200 根,每个序列最大支持请求 10000 个数据;
  • adj_type:可选复权类型,"F"前复权、"B"后复权,仅对股票和基金类型合约有效,暂不支持多合约 K 线复权;
  • 返回的 DataFrame 包含id、datetime(K 线起点时间,自 unix epoch 以来的纳秒数)、open、high、low、close、volume、open_oi、close_oi等列;
  • 注意:获取后的 kline 对象请不要修改/重命名原始的 kline 序列,否则可能导致后续的 K 线数据不更新。

tafunc 指标函数库

TqSdk 也通过tqsdk.tafunc模块提供了一批行情分析中常用的计算函数(实现在 tqsdk/tafunc.py),例如:

from tqsdk import tafunc 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线的收盘价标准差

仓库中tafunc还提供了ma(简单移动平均)、sma(扩展指数加权移动平均)、crossup/crossdown(向上/向下穿越)、time_to_datetime(纳秒时间转datetime.datetime)等常用工具函数,均可与 K 线序列直接配合使用;更多用法可参考 tqsdk/ta.py 与 tqsdk/demo/ta.py。

四、数据接收和更新:回调模型 vs wait_update/is_changing

vn.py 的事件回调模型

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

class DoubleMaStrategy(CtaTemplate): def on_tick(self, tick: TickData): ... def on_bar(self, bar: BarData): ... def on_order(self, order: OrderData): pass def on_trade(self, trade: TradeData): self.put_event()

TqSdk 的 wait_update/is_changing 模型

TqSdk 不使用事件回调机制。TqApi.wait_update函数设计用来获取任意数据更新:

from tqsdk import TqApi, TqAuth api = TqApi(auth=TqAuth("快期账户", "账户密码")) ks = api.get_kline_serial("SHFE.cu2607", 60) while True: api.wait_update() # <- 这个 wait_update 将尝试更新所有数据. 如果没有任何新信息, 程序会阻塞在这一句. 一旦有任意数据被更新, 程序会继续往下执行 print(ks.close.iloc[-1]) # <- 最后一根K线的收盘价

一次wait_update可能更新多个实体。在这种情况下,TqApi.is_changing被用来判断某个实体(或其中某个字段)是否有变更:

from tqsdk import TqApi, TqAuth api = TqApi(auth=TqAuth("快期账户", "账户密码")) 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 将尝试更新所有数据. 如果没有任何新信息, 程序会阻塞在这一句. 一旦有任意数据被更新, 程序会继续往下执行 if api.is_changing(q): # <- 这个 is_changing 用来判定这次更新是否影响到了q print(q) if api.is_changing(x, "status"): # <- 这个 is_changing 用来判定这次更新是否影响到了报单的status字段 print(x)

需要记住的要点

TqSdk 针对行情数据和交易信息都采用相同的wait_update/is_changing方案。用户需要记住的要点包括:

  • get_quote、get_kline_serial、insert_order等业务函数返回的是引用对象(reference,而非 value),它们的字段会在wait_update返回后刷新;
  • 用户程序除执行自己的业务逻辑外,还需要反复调用wait_update;在两次wait_update之间,数据不会继续推进;
  • 用insert_order函数下单后,还要继续调用wait_update,报单指令才会真正发出;
  • 用户程序中需要避免阻塞,不要直接用sleep暂停主循环;如果需要定时控制,请参考 定时器文档。

从源码层面看(tqsdk/api.py 中的wait_update实现),每次调用wait_update()会同时完成四件事:

  1. 实际发出待发送的网络请求(如行情订阅、下单、撤单等);
  2. 推进正在运行的后台任务(如 TargetPosTask 等调仓任务);
  3. 尝试从服务器接收并合并新的业务数据包;
  4. 如果当前没有新数据,则继续等待。

wait_update还支持deadline参数(unix epoch 以来的秒数,即time.time()):不填则无限等待直到有业务数据更新;填写后到达截止时间仍无更新则返回False。is_changing则通过比对本次收到的 Diff 数据包与业务对象的路径/字段(_is_obj_changing),判断特定对象或字段(支持str或list形式)是否在本轮更新中发生变化;K 线产生新一根时其所有字段都算作有更新,此时is_changing(klines.iloc[-1])一定返回 True。

关于 wait_update 机制的详细说明,请见 framework 文档。仓库中的综合演示可参考 tqsdk/demo/tutorial/t30.py,它同时演示了 tick 序列变化、新 K 线产生(is_changing(klines.iloc[-1], "datetime"))与收盘价变化(is_changing(klines.iloc[-1], "close"))三种判断。

五、下单 API:insert_order 的参数全景

虽然关联文档以TargetPosTask为主,但 TqSdk 也提供精细下单函数insert_order(tqsdk/api.py),它会在调用后立即返回,委托请求在下一次wait_update时发出。核心参数如下:

  • symbol:拟下单的合约代码,格式为交易所代码.合约代码;
  • direction:"BUY"或"SELL";
  • offset:"OPEN"、"CLOSE"或"CLOSETODAY"(上期所和上期能源分平今/平昨,平今用"CLOSETODAY",平昨用"CLOSE";其他交易所直接用"CLOSE"按交易所规则平仓;股票交易无需填写);
  • volume:下单数量,期货为手数,A 股为股数;
  • limit_price:可选下单价格。数字类型为限价单;None(默认)为市价单(郑商所期货/期权、大商所期货支持);"BEST"为最优一档(仅中金所支持);"FIVELEVEL"为最优五档(仅中金所支持);股票目前仅支持限价单,必须指定;
  • advanced:可选"FAK"(剩余即撤销)、"FOK"(全成或全撤),郑商所期货品种不支持 FOK;股票不支持该参数;
  • order_id:可选指定下单单号,默认由 api 自动生成;
  • account:多账户模式下指定发送指令的账户实例(如TqAccount/TqKq/TqSim)。

示例:

from tqsdk import TqApi, TqAuth api = TqApi(auth=TqAuth("快期账户", "账户密码")) order = api.insert_order(symbol="DCE.m1809", direction="BUY", offset="OPEN", volume=3) while True: api.wait_update() print("单状态: %s, 已成交: %d 手" % (order.status, order.volume_orign - order.volume_left))

六、图形界面:web_gui 与自定义图表

TqSdk 提供web_gui(web_gui 文档)来供有图形化需求的用户使用:

  • 策略运行时,交易记录和持仓记录自动在行情图上标记,可以快速定位跳转,可以跨周期缩放定位;
  • 策略回测时,提供回测报告、图上标记和对应的回测分析报告;
  • 策略运行和回测信息自动保存,可事后随时查阅显示。

TqSdk 配合 web_gui 使用时,还支持自定义绘制行情图表。TqApi构造参数web_gui(默认为False不启用)可以传True(每次以随机端口生成网页),也可以直接设置本机 IP 和端口web_gui="[ip]:port"(ip 可选,默认为0.0.0.0)。为了图形化界面能够接收到程序传输的数据并刷新,程序中需要以循环调用api.wait_update的形式更新和获取数据。自定义绘制示例:

from tqsdk import TqApi, TqAuth api = TqApi(auth=TqAuth("快期账户","账户密码"), web_gui=True) # 获取 cu2607 和 cu2608 的日线数据 klines = api.get_kline_serial("SHFE.cu2607", 86400) klines2 = api.get_kline_serial("SHFE.cu2608", 86400) # 算出 cu2608 - cu2607 的价差,并以折线型态显示在副图 klines["dif"] = klines2["close"] - klines["close"] klines["dif.board"] = "DIF" klines["dif.color"] = 0xFF00FF00 klines["dif.width"] = 3

七、回测:无需准备数据的事件驱动回测

使用 TqSdk 开发的策略可以回测:

  • 提供 Tick 及 K 线级别的回测;
  • TqSdk 允许在一个策略中使用任意多个数据序列,回测框架会正确识别并处理这种情况;
  • 回测前不需要准备数据——历史行情由行情服务器按需提供。

回测的入口是把TqBacktest实例传入TqApi构造函数(参见 tqsdk/backtest/backtest.py 与 回测文档)。TqBacktest(start_dt, end_dt)的起止时间都应为北京时间,可以传date(指交易日)或datetime(具体时间点,无时区信息时默认为北京时间)。回测模式下 K 线会在刚创建出来时和结束时分别更新一次;wait_update每次最多推进一个行情时间;回测结束后会抛出BacktestFinished异常。回测模式下 quote 的更新频率由所订阅的 tick 和 K 线周期确定:订阅了 tick 则按 tick 频率生成;否则按 K 线周期生成;既无 tick 也无 K 线(或 K 线周期大于分钟线)时,会自动订阅分钟线来生成 quote。

一个完整的回测示例(可参考 tqsdk/demo/tutorial/backtest.py):

from datetime import date from tqsdk import TqApi, TqAuth, TqBacktest, TargetPosTask ''' 如果当前价格大于5分钟K线的MA15则开多仓 如果小于则平仓 回测从 2018-05-01 到 2018-10-01 ''' # 在创建 api 实例时传入 TqBacktest 就会进入回测模式 api = TqApi(backtest=TqBacktest(start_dt=date(2018, 5, 1), end_dt=date(2018, 10, 1)), 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") while True: api.wait_update() if api.is_changing(klines): ma = sum(klines.close.iloc[-15:]) / 15 print("最新价", klines.close.iloc[-1], "MA", ma) if klines.close.iloc[-1] > ma: print("最新价大于MA: 目标多头5手") # 设置目标持仓为多头5手 target_pos.set_target_volume(5) elif klines.close.iloc[-1] < ma: print("最新价小于MA: 目标空仓") # 设置目标持仓为空仓 target_pos.set_target_volume(0)

关于策略回测的详细说明,请见 backtest 文档;配合 web_gui 时,回测还会自动生成含成交统计图表的回测报告。

八、vn.py 用户迁移对照速查表

维度vn.pyTqSdk
整体架构all-in-one:数据库、行情、交易、GUI 集成在一个包内网络协作组件:行情网关 + 交易中继网关 + Diff 协议,客户端轻量
策略组织从CtaTemplate等基类派生,由策略管理器加载运行,框架调用用户代码无统一基类,每个策略是一个独立运行的 py 文件,用户代码调用 TqSdk 函数
多策略运行同一进程内由框架统一调度每策略独立进程,可充分利用多 CPU、独立启停与调试
K 线数据本地接收行情生成 K 线并存入本地数据库由行情网关生成并推送,无需维护 K 线数据库、无需补历史数据
K 线格式自有数据结构pandas.DataFrame,可用 pandas 与tqsdk.tafunc丰富函数处理
数据更新模型事件回调:on_tick/on_bar/on_order/on_trade轮询式驱动:wait_update()获取任意更新 +is_changing(obj, key)判断具体对象/字段
下单方式调用框架下单接口,框架管理状态insert_order精细下单(wait_update后才真正发出),或TargetPosTask目标持仓自动调仓
图形界面内置 GUIweb_gui=True启动网页图形化界面,支持自定义图表绘制
回测依赖本地数据无需准备数据,支持 Tick/K 线级别、多数据序列回测

九、推荐学习步骤

要学习使用 TqSdk,推荐从 quickstart 开始,然后阅读 framework 理解wait_update驱动模型;需要批量导入历史数据做研究时,可参考 DataDownloader 文档 与 tqsdk/demo/tutorial/downloader.py;需要直连 CTP 等期货公司系统时,可进一步阅读 TqCtp 参考 与 企业版说明。

迁移已有 vn.py 策略时,最核心的思维转变有三点:策略从"被框架调用的类"变成"独立运行的脚本";数据获取从"回调推送"变成"循环拉取 + 变化判断";K 线从"本地维护的数据库"变成"网关生成、随用随取的 DataFrame 序列"。把握住这三点,其余函数级的迁移都可以对照 tqsdk/api.py 的文档注释与 tqsdk/demo/tutorial 下的示例快速完成。

  • 金融科技

【免费下载链接】tqsdk-python

天勤量化开发包, 期货量化, 实时行情/历史数据/实盘交易

项目地址:https://gitcode.com/gh_mirrors/tq/tqsdk-python
点击查看免费下载

相关推荐

上一篇:Activepieces 异步 Webhook 确认机制剖析:为何 ACK 走 Redis 持久化而非 Postgres
下一篇:如何构建个人数字记忆库:WeChatMsg聊天记录备份与深度分析完全指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进