TqSdk2 与使用Ctp接口开发策略程序有哪些差别

如果您曾经直接使用CTP接口开发过交易策略程序, 目前刚开始接触 TqSdk2, 下面的信息将帮助您尽快理解 TqSdk2.

系统整体架构

CTP接口直接连接到期货公司交易系统, 从期货公司系统获取行情并执行交易指令.

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

交易中继网关
交易中继网关
交易所行情
交易所行情
行情网关
Open Md Gateway
行情网关 Open Md Gateway
DIFF 协议
TqSdk
TqSdk
期货公司交易系统
CTP/FEMAS/UFX
期货公司交易系统 CTP/FEMAS/UFX
Viewer does not support full SVG 1.1

如图所示, 整个系统结构包括这些关键组件:

  • 行情网关 (Open Md Gateway) 负责提供实时行情和历史数据

  • 交易中继网关 (Open Trade Gateway) 负责连接到期货公司交易系统

  • 上面两个网关统一以 Diff 协议对下方提供服务

  • TqSdk2 按照Diff协议连接到行情网关和交易中继网关, 实现行情和交易功能

这样的结构可以给用户带来一些好处:

  • TqSdk2 很小, 安装也很方便, 只要简单 pip install tqsdk2 即可

  • 官方专门运维行情数据库, 用户可以直接使用, 不需要自己接收和存储数据

  • 交易相关接口被大幅度简化, 不再需要处理 CTP 接口的复杂回调, 也不需要发起任何查询请求

  • 任何语言只要支持 websocket 协议, 都可以用来进行策略开发

同时对于速度更加有要求的用户,TqSdk2 通过将中继服务器并入本地,来提供直连模式供用户使用:

期货公司交易系统
CTP/FEMAS/UFX
期货公司交易系统 CTP/FEMAS/UFX
交易所行情
交易所行情
行情网关
Open Md Gateway
行情网关 Open Md Gateway
DIFF 协议
TqSdk2
TqSdk2
Viewer does not support full SVG 1.1

因此在选择 TqSdk2 直连模式时:

  • 用户代码从中继模式切换到直连模式下的代码,只用修改一行

  • 省去了用户交易指令传输需要经过交易中继网关流程,减少了用户交易指令到达期货公司的延迟

to do:补充 example 和期货公司程序化接入流程

需要注意选择直连模式时,用户需要向期货公司申请程序化接入并且填写自己的接入信息

同时 TqSdk2 中将 TqSdk 里的底层代码全部用 C++ 进行了重构,这会给用户额外带来这些好处:

  • 维持了 TqSdk 中对外接口,让用户 TqSdk 中的代码可以在大多数情况下无缝迁移到 TqSdk2

  • 有效减少了系统内交易指令运算耗时

  • 将同等代码 TqSdk 的回测速度提升了十倍以上

K线数据与指标计算

Ctp 接口不提供K线数据.

在 TqSdk2 中, K线数据和其它行情数据一样是由行情网关生成并推送的:

  • 用户不再需要维护K线数据库. 用户电脑实时行情中断后, 也不再需要补历史数据

  • 行情服务器生成K线时, 采用了按K线时间严格补全对齐的算法. 这与其它软件有明显区别, 详见 https://www.shinnytech.com/blog/why-our-kline-different/

  • 行情数据只在每次程序运行时通过网络获取, 不在用户硬盘保存. 如果策略研究工作需要大量静态历史数据, 我们推荐使用数据下载工具, 另行下载csv文件使用.

TqSdk2 中的K线序列采用 pandas.DataFrame 格式. pandas 提供了 非常丰富的数据处理函数 , 使我们可以非常方便的进行数据处理, 例如:

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

TqSdk2 也通过 tqsdk.tafunc 提供了一批行情分析中常用的计算函数, 例如:

from tqsdk2 import tafunc
ks = api.get_kline_serial("SHFE.cu1901", 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线的收盘价标准差

数据接收和更新

Ctp 接口按照事件回调模型设计, 使用 CThostFtdcTraderSpi 的 OnXXX 回调函数进行行情数据和回单处理:

class MySpiHandler
  : public CThostFtdcTraderSpi
{
public:
  ///当客户端与交易后台建立起通信连接时(还未登录前),该方法被调用。
  virtual void OnFrontConnected();

  ///报单通知
  virtual void OnRtnOrder(CThostFtdcOrderField *pOrder);

  ///成交通知
  virtual void OnRtnTrade(CThostFtdcTradeField *pTrade);
}

TqSdk2 则不使用事件回调机制. wait_update() 函数设计用来获取任意数据更新, 像这样:

api = TqApi(auth=TqAuth("信易账户", "账户密码"))
x = api.insert_order("SHFE.cu1901", direction="BUY", offset="OPEN", volume=1, limit_price=50000)

while True:
  api.wait_update()       # <- 这个 wait_update 将尝试更新所有数据. 如果没有任何新信息, 程序会阻塞在这一句. 一旦有任意数据被更新, 程序会继续往下执行
  print(x)                # <- 显示委托单最新状态

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

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)

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)

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

  • get_quote, get_kline_serial, insert_order 等业务函数返回的是一个引用(refrence, not value), 它们的值总是在 wait_update 时更新.

  • 用户程序除执行自己业务逻辑外, 需要反复调用 wait_update. 在两次 wait_update 间, 所有数据都不更新

  • 用 insert_order 函数下单, 报单指令实际是在 insert_order 后调用 wait_update 时发出的.

  • 用户程序中需要避免阻塞, 不要使用 sleep 暂停程序

关于 wait_update 机制的详细说明, 请见 策略程序结构

其它区别

此外, 还有一些差别值得注意

  • TqSdk 要求 Python 3.6.4 以上版本, 不支持 Python 2.x

要学习使用 TqSdk, 推荐从 十分钟快速入门 开始