news 2026/10/23 9:46:33

CATS API 全双工异步回调实战:量化交易接入层避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CATS API 全双工异步回调实战:量化交易接入层避坑指南

简介:这份PDF文档是中信证券CATS自动化交易平台的API参考手册,面向量化交易开发者、程序化交易客户端开发者及机构技术团队,用于解决自动炒股系统开发中底层通信与协议处理复杂的问题。文档系统梳理了CATS API的调用流程与函数分类,涵盖初始化与退出清理、通信会话管理、连接交易与行情服务器、工具函数、接口入出参处理、本地内存数据库操作,以及账户管理、交易订阅、行情订阅等模块,并给出CATSAPI_Init、CATSAPI_Execute_CatsConnect、CATSAPI_MDB_ExecBatchSQL等具体接口说明。资源包为1个PDF文件,大小约509KB,便于随时查阅与检索。目前已有3302人学习下载,适合希望快速理解CATS API全貌、降低开发难度并构建个性化自动化交易系统的开发者参考。

1. 拆开 CATS API 工具包:全双工异步回调到底解决了谁的痛点

如果你写过券商柜台或期货 CTP 的接入层,大概都经历过那种“协议文档三百页、回调线程满天飞”的绝望。CATS API 这套东西,本质上是把底层通信、压缩加密、协议编解码全部封进一个动态库,对外只暴露两类函数:Prepare_*负责填参数,Execute_*负责发请求,结果通过回调推回来。全双工异步意味着你发委托和收行情走的是同一套会话句柄,不需要自己维护心跳线程和重连状态机。

它适合谁?适合已经有策略逻辑、但不想在接入层反复造轮子的量化开发者。尤其是做算法交易、需要订阅 Level2 逐笔和委托队列的人,CATS 把算法模板管理也做进了 API,TWAP、VWAP、冰山、狙击手这些算法可以直接通过接口创建实例,不用自己从零实现拆单逻辑。但要注意,这套 API 是 C 接口风格,Windows 下用WINAPI调用约定,Linux 下需要确认 .so 的导出符号,跨平台编译时头文件里的CATSHANDLE类型要特别留意。

下面按“初始化 → 会话管理 → 业务请求 → 订阅推送 → 避坑 → 进阶验证”的顺序,把这份参考手册里真正能落地的部分拆开讲。

2. 初始化与通信会话:句柄、SSL 和重连回调怎么配

2.1 从 CATSAPI_Init 到 InitSession 的调用顺序

很多人拿到库之后第一反应是直接调CATSAPI_InitSession,结果返回非零,查CATSAPI_GetLastError发现是“库未初始化”。正确顺序是先把库加载起来,再创建会话句柄。CATSAPI_Init的参数debug_console控制是否弹出调试窗口,生产环境建议传 0,调试阶段传 1 可以看到内部日志输出。

// 初始化 CATS API 库,debug_console=1 时弹出调试窗口 int ret = CATSAPI_Init(1); if (ret != 0) { // 初始化失败,通常是 dll/so 加载路径不对或版本不匹配 printf("CATSAPI_Init failed: %d\n", ret); return -1; } // 创建会话句柄 CATSHANDLE hHandle = NULL; // use_ssl=1 启用加密通道,start_conn=1 表示自动后台连接 // 后面四个回调用于交易和行情断线重连通知 ret = CATSAPI_InitSession( &hHandle, // 输出参数,会话句柄 1, // use_ssl: 是否启用 SSL 通道 1, // start_conn: 是否自动后台连接 OnTrdReConnected, // 交易重连成功回调 NULL, // 交易重连回调参数 OnTrdDisConnected, // 交易断线回调 NULL, // 交易断线回调参数 OnHqReConnected, // 行情重连成功回调 NULL, // 行情重连回调参数 OnHqDisConnected, // 行情断线回调 NULL // 行情断线回调参数 ); if (ret != 0 || hHandle == NULL) { printf("InitSession failed: %d\n", ret); CATSAPI_Fini(); return -1; }

逻辑说明:CATSAPI_Init只负责加载库和初始化全局状态,不建立任何网络连接。CATSAPI_InitSession才是创建会话的地方,use_ssl参数决定底层是否走加密通道,start_conn为 1 时会自动在后台尝试连接 CATS 服务器。四个回调函数指针允许传 NULL,但如果你做的是实盘交易,交易断线回调必须实现,否则断线后策略还在傻等成交回报。

参数上最容易翻车的是CATSHANDLE的传递方式。它是输出参数,必须传&hHandle,而且初始化前要置 NULL。有些开发者习惯用CATSHANDLE*直接声明一个未初始化的指针传进去,结果库内部写入时访问了野指针,直接崩溃。

2.2 连接服务器:Prepare 和 Execute 的配对逻辑

CATS API 里所有需要与服务器交互的操作都遵循“Prepare + Execute”两步走。CATSAPI_Prepare_CatsConnect负责把连接参数(交易服务器地址、行情服务器地址、账号信息等)塞进内部请求结构,CATSAPI_Execute_CatsConnect才真正发起连接。这种设计的好处是你可以先 Prepare 多个请求,再按业务优先级依次 Execute。

// 准备连接请求 ret = CATSAPI_Prepare_CatsConnect( hHandle, // 会话句柄 "trade.example.com", // 交易服务器地址(示例) 9001, // 交易端口 "hq.example.com", // 行情服务器地址(示例) 9002 // 行情端口 ); if (ret != 0) { printf("Prepare_CatsConnect failed: %d\n", ret); CATSAPI_ExitSession(hHandle); CATSAPI_Fini(); return -1; } // 执行连接 ret = CATSAPI_Execute_CatsConnect(hHandle); if (ret != 0) { printf("Execute_CatsConnect failed: %d\n", ret); // 连接失败后不要立即重试,先检查网络和服务器状态 return -1; }

这里有个血泪经验:Prepare_CatsConnect的参数里服务器地址和端口是分开传的,但有些版本的文档写的是“地址:端口”格式。如果你按文档传了带冒号的字符串,Prepare 会返回参数错误。实际对接时以头文件里的函数原型为准,不要只看文档描述。

连接成功后,交易和行情通道是独立维护的。交易断线不影响行情推送,行情断线也不影响已报委托的状态查询。但如果你在交易断线期间发了新委托,Execute_OrderSingle会返回连接错误,这时候需要等重连回调触发后再重新发送。

3. 业务请求与订阅推送:回调注册、参数设置和内存数据库

3.1 回调函数的注册时机与线程安全

CATS API 的推送业务全部走回调。订阅行情、订阅委托成交、订阅资金持仓变动,都是先调PreSub_*准备订阅请求,再调Subscribe_*正式订阅,数据到达时触发你注册的回调函数。回调函数在库内部的网络线程里执行,不是主线程。

// 行情数据回调,注意:这个函数在库的网络线程中被调用 void OnMarketData(CATSHANDLE hHandle, void* arg) { // 从回调参数中获取行情数据 // 不要在这里做耗时操作,否则会阻塞后续行情推送 int ret = CATSAPI_GetCStrField(hHandle, "InstrumentID", buf, sizeof(buf)); if (ret == 0) { double lastPrice = 0.0; CATSAPI_GetFloatField(hHandle, "LastPrice", &lastPrice); // 把数据推入无锁队列,由策略线程消费 push_to_lockfree_queue(buf, lastPrice); } } // 订阅行情 ret = CATSAPI_PreSub_MarketData(hHandle, "600000", "SH"); if (ret == 0) { ret = CATSAPI_Subscribe_MarketData(hHandle, OnMarketData, NULL); }

逻辑说明:CATSAPI_PreSub_MarketData的参数是合约代码和交易所代码,交易所代码用数据字典里的SH、SZ等。CATSAPI_Subscribe_MarketData的第二个参数是回调函数指针,第三个是用户自定义参数,会原样传回给回调。

关键点:回调函数里绝对不能做阻塞操作。我见过有人在行情回调里直接写数据库,结果行情高峰期回调线程被 IO 卡住,后续行情全部积压,最后触发库内部的缓冲区溢出保护,连接被强制断开。正确做法是在回调里只做数据拷贝,把处理逻辑丢到自己的策略线程。

3.2 SetParam 与 GetField:入参出参的按名访问

CATS API 没有为每个业务接口定义独立的参数结构体,而是用CATSAPI_SetParam按参数名设置输入,用CATSAPI_GetIntField、CATSAPI_GetCStrField等按字段名获取输出。这种设计灵活但容易拼错参数名。

// 设置单笔委托的输入参数 CATSAPI_SetParam(hHandle, "InstrumentID", "600000"); CATSAPI_SetParam(hHandle, "ExchangeID", "SH"); CATSAPI_SetParam(hHandle, "Direction", "1"); // 1=买入 CATSAPI_SetParam(hHandle, "OrderPriceType", "0"); // 0=限价单 CATSAPI_SetParam(hHandle, "LimitPrice", "10.50"); CATSAPI_SetParam(hHandle, "VolumeTotalOriginal", "100"); // 执行委托 ret = CATSAPI_Execute_OrderSingle(hHandle); if (ret != 0) { char errMsg[256] = {0}; CATSAPI_GetLastError(hHandle, errMsg, sizeof(errMsg)); printf("OrderSingle failed: %s\n", errMsg); }

参数名是大小写敏感的,InstrumentID不能写成InstrumentId。Direction的取值参考数据字典:证券买入是1,卖出是2,融资买入是A,融券卖出是B。期货的FA到FP对应开平仓的各种组合,填错方向会导致委托被柜台拒绝。

CATSAPI_GetLastError是排错的第一入口。每次Execute_*返回非零后,都应该立即调用它获取错误描述。错误信息里通常包含柜台返回的具体拒绝原因,比如“资金不足”“合约不存在”“非交易时段”等。

3.3 本地内存数据库:MDB 系列函数的批量操作

CATS API 内置了一个本地内存数据库,支持 INSERT、UPDATE、DELETE、SELECT 和批量 SQL。这个 MDB 不是用来替代你本地的持久化存储的,它的定位是缓存从柜台查询回来的静态数据,比如商品信息、交易时间、算法参数等,方便策略快速查询。

// 批量执行 SQL,用于初始化本地缓存 const char* sqlBatch[] = { "INSERT INTO CommodityCache VALUES ('600000','SH','股票','T+1')", "INSERT INTO CommodityCache VALUES ('000001','SZ','股票','T+1')", NULL }; ret = CATSAPI_MDB_ExecBatchSQL(hHandle, sqlBatch); // 查询并遍历结果集 ret = CATSAPI_MDB_Select(hHandle, "SELECT * FROM CommodityCache WHERE ExchangeID='SH'"); if (ret == 0) { int colCount = 0; CATSAPI_MDB_GetColCount(hHandle, &colCount); // 移动光标到第一条记录 CATSAPI_MDB_MoveNext(hHandle); while (ret == 0) { for (int i = 0; i < colCount; i++) { char colName[64] = {0}; CATSAPI_MDB_GetColName(hHandle, i, colName, sizeof(colName)); // 根据列名获取数据... } ret = CATSAPI_MDB_MoveNext(hHandle); } }

注意CATSAPI_MDB_MoveNext的用法:执行完 SELECT 后光标不在任何记录上,必须先调一次 MoveNext 移到第一条,之后每次 MoveNext 移到下一条,返回非零表示没有更多记录。这个行为和很多数据库游标库不一样,容易踩坑。

4. 避坑与排查:回调线程、参数名和重连状态机

4.1 回调线程里调用 Execute 导致死锁

现象:在行情回调函数里直接调用CATSAPI_Execute_OrderSingle发委托,程序卡死,日志停在“准备发送委托”之后。

原因:回调函数运行在库的网络接收线程中,而Execute_*内部需要获取发送锁。如果网络线程持有接收锁的同时去抢发送锁,而发送线程又在等接收锁,就形成死锁。

解决:回调函数里只做数据入队,所有Execute_*调用都放到独立的策略线程。如果确实需要在回调里发单,用CATSAPI_Prepare_OrderSingle准备好请求,然后通过线程间消息通知策略线程执行。

4.2 参数名拼写错误导致 SetParam 静默失败

现象:CATSAPI_SetParam返回 0 表示成功,但Execute_OrderSingle报“缺少必填参数”。

原因:SetParam对未知参数名不报错,只是忽略。如果你把VolumeTotalOriginal写成VolumeTotal,SetParam 返回成功,但实际没有设置任何值。

解决:对照头文件里的参数名宏定义,不要手写字符串。常见做法是用#define把参数名定义成常量,编译期就能发现拼写错误。

4.3 重连回调里重新登录导致会话状态混乱

现象:交易断线后重连成功,在OnTrdReConnected回调里立即调用CATSAPI_Execute_CatsLogin重新登录,结果返回“会话已存在”。

原因:重连回调触发时,库内部已经自动完成了底层会话恢复,但交易账户的登录状态需要单独恢复。如果直接调CatsLogin,会和库内部的自动恢复流程冲突。

解决:重连回调里只做业务层的状态标记,比如设置一个need_relogin标志,然后在策略线程里检查这个标志,调用CATSAPI_Prepare_TradeAccLogin和CATSAPI_Execute_TradeAccLogin恢复交易账户登录。不要直接调CatsLogin。

4.4 订阅 Level2 逐笔成交后内存持续增长

现象:程序运行几小时后内存占用从几百 MB 涨到几个 GB,最终 OOM。

原因:CATSAPI_Subscribe_StockMatchDetailL2推送的逐笔成交数据量极大,如果回调函数里把每条数据都存进一个无界队列,消费速度跟不上生产速度,队列就会无限膨胀。

解决:给队列设上限,满了就丢弃最旧的数据或者做采样。另外,Level2 逐笔数据通常只需要最近几分钟的,历史数据应该落盘而不是常驻内存。常见做法是回调里只保留最新 N 条,或者用环形缓冲区。

4.5 算法实例状态订阅后收不到推送

现象:调用了CATSAPI_Subscribe_AlgoStatus,但算法实例状态变化时回调不触发。

原因:算法状态推送需要先通过CATSAPI_Prepare_QryCatsAlgo查询客户授权算法,确认该算法已授权。如果算法未授权,订阅请求会返回成功但不会推送任何数据。

解决:订阅前先查询授权算法列表,确认目标算法在返回结果中。另外,算法实例的创建需要通过CATSAPI_Prepare_AddAlgoTemplates添加模板后才能实例化,直接订阅一个不存在的实例 ID 也不会有推送。

5. 进阶验证:用日志和错误码定位接口调用链

CATS API 提供了四个日志函数:logdebug、logwarn、logerror、loginfo。很多人只把它们当普通日志用,其实这四个函数的输出会同时写到调试窗口和本地日志文件,而且日志里会带上库内部的调用链信息。当你遇到“Execute 返回非零但 GetLastError 信息模糊”的情况,把日志级别调到 debug,重新跑一遍,通常能在日志里看到具体是哪个底层步骤失败了。

我一般会在初始化之后立即设置日志文件路径,然后把logdebug的开关打开,跑一轮完整的“登录 → 查询 → 订阅 → 委托 → 撤单”流程,把日志从头到尾看一遍。重点关注三类信息:连接建立时的协议版本协商、登录时的会话 ID 分配、委托发送时的柜台序列号。这三类信息在后续排错时是唯一的追踪线索。

验证接口是否真正可用,不要只看返回值。Execute_*返回 0 只代表请求成功发到了库内部,不代表柜台已经受理。真正的确认信号是回调推送:委托状态从“新建”变成“部分成交”或“完全成交”,资金持仓订阅推送了变动数据。如果发了委托但一直收不到状态推送,先检查CATSAPI_Subscribe_OrderUpdate是否订阅成功,再检查委托的ExchangeID和InstrumentID是否匹配。

还有一个容易忽略的点:CATSAPI_GetVersion返回的版本号要和头文件里的版本宏对比。我遇到过库文件是旧版本、头文件是新版本的情况,编译链接都通过,但运行时某些新增参数被旧库忽略,导致行为不符合预期。从那以后我每次拿到新的 CATS API 包,第一件事就是写一个最小程序,调CATSAPI_GetVersion打印版本,和头文件里的CATSAPI_VERSION宏做比对,不一致就强制走一遍全量回归。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 14:11:09

VSCode+MinGW-w64配置C/C++开发环境:从编译器选择到GDB调试全指南

入行这几年&#xff0c;我给身边人讲C/C开发环境配置的次数&#xff0c;怎么也有二十次了。每次都是从编译器选型讲起&#xff0c;再扯到环境变量、tasks.json、launch.json&#xff0c;讲完一遍对方似懂非懂&#xff0c;过两天又回来问"为什么我的gcc不是内部或外部命令&…

作者头像 李华
网站建设 2026/10/20 16:01:28

C#上位机OPC通讯实例:从OPC UA/DA选型到PLC数据读写

简介&#xff1a;一套由工控老马出品的C# OPC通讯实例程序源码&#xff0c;专为工业自动化领域的开发人员及入门新手设计&#xff0c;解决C#客户端通过OPC服务器与PLC进行实时数据读写交互的实操难题。源码包含完整的Visual Studio解决方案&#xff0c;涵盖项目文件、窗体界面、…

作者头像 李华
网站建设 2026/10/20 16:01:32

HTML元素与布局区块概念表:前端布局知识地图与实战指南

1. 为什么我建议每个前端新手都做一份“元素与布局区块”概念表先说一个我踩过的坑。刚接触前端那会儿&#xff0c;我每天都能背出十几个HTML标签&#xff0c;什么div、span、p、a、ul、li&#xff0c;背得滚瓜烂熟&#xff0c;可真到动手写页面的时候&#xff0c;布局全靠猜&a…

作者头像 李华
网站建设 2026/10/20 16:01:32

AnyPS5实战指南:从局域网到跨网络,让PS5串流到任何设备

大概两个月前&#xff0c;我发了一条动态吐槽&#xff1a;“客厅电视被家里人占了&#xff0c;PS5只能吃灰。”评论区里有个朋友回了一句&#xff1a;“你需要的不是新电视&#xff0c;是 AnyPS5。”我当时没太在意&#xff0c;后来才发现他说的是圈内那套把 PlayStation 5 串流…

作者头像 李华
网站建设 2026/10/20 16:01:27

WPS JS宏实现编号规范化:巧用padStart与正则零宽断言

半夜被设备台账折磨到怀疑人生这种事&#xff0c;估计不少搞过生产管理或数据处理的小伙伴都经历过&#xff1a;标准要求编号格式统一成“前缀-三位流水号”&#xff0c;结果表格里什么妖魔鬼怪都有&#xff0c;MD-1、MD_23、配件MD-12保留、MD-3-2021……人工对着标准改到眼花…

作者头像 李华
网站建设 2026/10/20 2:27:00

为什么静态化发布是企业官网的优选:安全、速度与收录

给企业官网做技术选型时&#xff0c;"动态渲染还是静态化发布"是个值得想清楚的问题。什么是静态化发布&#xff1a; 内容在"发布"动作时渲染成纯 HTML 文件——index.html、各栏目内页&#xff0c;外加 sitemap.xml 和 robots.txt&#xff0c;访客访问时直…

作者头像 李华