10、期货账户对接:CTP接口接入、交易指令封装、账户资金与持仓管理

好,咱们进入实战环节最硬核的部分——账户对接。

说实话,很多做策略的人,策略回测跑得飞起,一到实盘就卡在接口对接上。CTP(综合交易平台)是上期技术开发的,国内期货公司几乎都在用。你想想看,不把它搞定,策略写得再好也白搭。

10.1 CTP接口接入:从零开始连上交易所

CTP接口其实是一套C++的动态库。我个人习惯用Python封装,因为策略开发效率高。但底层逻辑是一样的——你得先搞定三个核心对象:交易接口行情接口回调处理

核心流程:

  1. 创建交易API实例(CThostFtdcTraderApi)
  2. 注册回调类(继承CThostFtdcTraderSpi)
  3. 订阅私有流(订单、成交、资金变动)
  4. 调用Init()连接前置机
  5. 等待OnFrontConnected回调
  6. 发起登录认证(ReqAuthenticate + ReqUserLogin)
  7. 等待OnRspUserLogin回调,确认登录成功

我在项目中遇到过一个问题:CTP前置机地址配错了,结果连了半小时没反应。后来发现是端口号写成了TCP的,实际应该用UDP的。嗯,这里要注意——交易和行情前置机地址是分开的,别搞混。

// 伪代码示例:CTP初始化
TraderApi = CThostFtdcTraderApi::CreateFtdcTraderApi();
TraderSpi = new MyTraderSpi();
TraderApi->RegisterSpi(TraderSpi);
TraderApi->SubscribePrivateTopic(THOST_TERT_QUICK);
TraderApi->RegisterFront("tcp://192.168.1.100:41205");
TraderApi->Init();

避坑指南:我曾经因为忘记调用SubscribePrivateTopic,导致订单状态回调收不到。CTP默认不订阅私有流,你得主动告诉它「我要听」。另外,登录时记得先做认证(ReqAuthenticate),再登录(ReqUserLogin),顺序反了会被踢。

10.2 交易指令封装:把CTP的原始接口变成策略能用的

CTP原生的接口参数太多了。一个报单函数,参数列表能写满一屏幕。你想想看,策略里每次下单都要填那么多字段,不出错才怪。

我的做法是——封装一层交易指令对象。把常用的下单模式抽象成几个方法:

  • 限价单:指定价格、数量、开平标志
  • 市价单:只给数量,价格由交易所撮合
  • 止损单:触发条件价格,到达后转市价
  • 止盈单:类似止损,方向相反

说白了,就是把CTP的CThostFtdcInputOrderField结构体,包装成一个Order对象。策略层只需要传「品种、方向、数量、价格类型」四个字段就够了。

class Order:
    def __init__(self, symbol, direction, volume, price_type, price=0):
        self.symbol = symbol        # 合约代码
        self.direction = direction  # 'buy' or 'sell'
        self.volume = volume        # 手数
        self.price_type = price_type # 'limit' or 'market'
        self.price = price          # 限价单才需要
        
    def to_ctp_field(self):
        # 转换成CTP的InputOrder结构
        field = CThostFtdcInputOrderField()
        field.InstrumentID = self.symbol
        field.Direction = THOST_FTDC_D_Buy if self.direction == 'buy' else THOST_FTDC_D_Sell
        field.VolumeTotalOriginal = self.volume
        field.OrderPriceType = THOST_FTDC_OPT_LimitPrice if self.price_type == 'limit' else THOST_FTDC_OPT_AnyPrice
        field.LimitPrice = self.price
        return field

个人经验:我习惯在封装层加一个「订单ID生成器」。CTP的OrderRef字段需要唯一,我直接用「策略ID + 时间戳 + 自增序号」拼接。这样出了问题,查日志一眼就能定位是哪个策略下的单。

10.3 账户资金与持仓管理:实时盯住你的钱和货

账户管理这块,很多人只关注下单,忽略了资金和持仓的同步。我见过最惨的情况——策略以为账户里还有100万,实际只剩10万了,结果连续报单被拒,仓位全乱套。

CTP提供了两个关键回调:

  • OnRspQryTradingAccount:查询资金账户信息
  • OnRspQryInvestorPosition:查询持仓信息

但注意,这两个是查询接口,不是推送接口。你得自己定时轮询,或者利用成交回报来更新。

我的做法是维护一个本地账户快照

字段 来源 更新时机
可用资金 OnRspQryTradingAccount 每30秒轮询一次
持仓数量 OnRspQryInvestorPosition 每30秒轮询一次
冻结资金 OnRtnOrder(订单状态变化) 实时更新
持仓盈亏 OnRtnTrade(成交回报) 实时更新

为什么会这样设计?因为CTP的查询接口有频率限制,你每秒查一次会被封IP。但订单和成交的回调是实时的,可以用来做增量更新。

我曾经踩过的坑:只依赖轮询更新持仓,结果在快速行情下,轮询间隔内发生了多笔成交,持仓数据滞后了3秒。策略以为还有仓位,继续开仓,结果超仓了。后来改成「成交回报实时更新 + 轮询兜底校验」的双保险模式,再没出过问题。

10.4 核心逻辑流程图

下面这张图,是我自己画的一个CTP对接的核心流程。你看一眼,基本就知道整个链路怎么走了。

CTP账户对接核心流程 1. 创建API实例 2. 注册回调类 3. 连接前置机 4. 认证 + 登录 5. 查询资金/持仓 6. 下单/撤单循环 关键说明 • 交易/行情前置机分开 • 必须订阅私有流 • 先认证再登录 • 资金轮询30秒/次 • 持仓实时+轮询双保险 • OrderRef需全局唯一 • 撤单需知道原OrderRef • 断线重连自动恢复 • 注意CTP频率限制 • 日志记录所有交互 • 模拟盘先跑一周

嗯,这张图基本把CTP对接的骨架画出来了。你照着这个流程走,至少不会漏掉关键步骤。

10.5 实战中的几个细节

最后聊几个我实战中反复踩过的坑:

  • 断线重连:CTP前置机偶尔会断,你得在OnFrontDisconnected回调里启动重连逻辑。我一般等5秒再重连,连续失败3次就报警。
  • 订单状态机:CTP的订单状态有十几种(已报、已撤、部分成交、全部成交...)。我建议只关注三个状态:全部成交已撤单废单。其他中间状态一律视为「等待中」。
  • 撤单逻辑:撤单需要传入原订单的OrderRef和FrontID。我习惯在本地维护一个「订单字典」,key是OrderRef,value是订单详情。撤单时直接查字典,不用再调查询接口。

一个小技巧:CTP的成交回报(OnRtnTrade)里带的有成交价格和数量。我每次收到成交回报,就立刻更新本地持仓和资金快照。这样即使轮询还没到,策略也能拿到最新的账户状态。

好了,CTP对接这块就聊到这儿。说白了,就是「连上去、发指令、盯账户」这三件事。但每一件都有不少细节,你多跑几次模拟盘,自然就熟了。


公众号:蓝海资料掘金营,微信deep3321