简介:这份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宏做比对,不一致就强制走一遍全量回归。希望帮到你。
本文还有配套的精品资源,点击获取