1. 为什么选择CTP-API v6.7.0与Python封装如果你正在开发量化交易系统或者想为自己的交易策略找一个稳定、低延迟的官方接口那么上期技术上期所的CTP-API几乎是国内期货市场的唯一标准答案。我从业这么多年从早期的版本一路用过来可以说v6.7.0是一个非常重要的里程碑版本。这个版本最大的一个亮点就是引入了LZ4压缩算法来处理查询类请求的流量。你可能觉得“压缩”听起来不是什么大事但在实际交易中尤其是高频或程序化场景下网络传输的数据量直接影响到响应速度和系统稳定性。v6.7.0之后查询合约、资金、持仓这些高频操作产生的网络包体积大幅减小相当于给你的网络通道“减了负”整体延迟和稳定性都有可感知的提升。所以无论是新建项目还是老系统升级我都强烈建议从v6.7.0起步。那为什么还要费劲用Python再封装一次呢官方的CTP-API是C动态库性能没得说但对于大多数策略研发和数据分析师来说C的门槛还是太高了。Python以其简洁的语法、丰富的数据科学库如Pandas、NumPy和强大的社区生态成为了量化研究、快速原型验证的首选语言。把C的API封装成Python模块就能让我们用Python的便利性去驱动底层的C高性能引擎实现“鱼与熊掌兼得”。这次我们要用的封装工具是Swig它是一个非常成熟的工具能自动帮我们在C和Python以及很多其他语言之间搭建桥梁避免了手动写一大堆繁琐的C扩展代码省时省力还更可靠。接下来我就带你手把手走通在Windows和Linux两大主流系统下的完整封装流程过程中遇到的坑和技巧也会一并分享给你。2. 动手前的准备工作资源与工具盘点磨刀不误砍柴工先把所有需要的“食材”备齐。首先你得去上期技术的官网下载CTP-API v6.7.0的开发包。这里有个小细节需要注意官网提供的包通常包含交易接口Trader API和行情接口Md API我们两者都需要。下载下来的是一个ZIP压缩包比如名字可能是v6.7.0_traderapi_20230209.zip解压后你会看到针对Windows和Linux的不同子目录里面包含了核心的C头文件.h和编译好的动态链接库Windows的.dll/.libLinux的.so。第二个关键工具是Swig。你需要去Swig的官方网站下载对应你操作系统的安装包。对于Windows用户我推荐直接下载那个可执行的安装程序.exe安装时记得勾选“将Swig添加到系统环境变量PATH中”这样后面在命令行里就能直接用了。对于Linux用户通常用包管理器安装会更方便比如在CentOS或RedHat系用yum install swig在Ubuntu或Debian系用apt-get install swig。安装完成后在终端输入swig -version能正确显示版本号就说明安装成功了。第三个是Python环境。我个人更推荐使用Anaconda来管理Python环境因为它集成了很多科学计算包环境隔离也做得很好。你需要确保安装的是Python 3.6及以上版本因为Swig对Python 3的支持更完善我们也会使用-py3参数。最后对于Windows用户你还需要一个C编译器最通用的选择就是Visual Studio。社区版Visual Studio Community是免费的功能完全足够。在安装时记得勾选“使用C的桌面开发”这个工作负载它会安装必要的编译工具链和库。准备好这三样东西我们的“厨房”就算布置完毕可以开始烹饪了。3. Windows平台封装全流程详解Windows下的封装过程步骤稍多但跟着我的步骤走基本不会出错。首先把你下载的CTP-API压缩包解压到一个干净的目录比如D:\ctp_api_v6.7.0。进入解压后的文件夹找到v6.7.2_20230913_winApi\traderapi\20230913_traderapi64_se_windows这样的路径具体文件夹名可能因版本略有不同这里存放着64位版本的核心库文件thosttraderapi_se.dll、thosttraderapi_se.lib以及对应的头文件。### 3.1 编写Swig接口定义文件.i文件这是封装的核心步骤我们需要创建两个接口定义文件thosttraderapi.i和thostmduserapi.i。这个文件的作用是告诉Swig哪些C的类、函数和数据结构需要暴露给Python以及如何处理它们之间的类型转换。特别是CTP-API中大量使用了中文编码的字符串GBK而Python 3默认使用UTF-8所以编码转换是必须处理的一环。下面是我调整优化后的thosttraderapi.i文件内容关键部分加了注释%module(directors1) thosttraderapi %{ // 包含必要的C头文件 #include ThostFtdcTraderApi.h #include codecvt #include locale #include vector #include string using namespace std; // 针对Windows和Linux定义不同的locale用于后续的字符编码转换 #ifdef _MSC_VER const static locale g_loc(zh-CN); #else const static locale g_loc(zh_CN.GB18030); #endif %} // 核心定义一个类型映射typemap处理所有char数组和字符串的返回值转换 // 当C函数返回char[]时Swig会执行这段代码将其从GBK转换为UTF-8再交给Python %typemap(out) char[ANY], char[] { const std::string gb2312($1); std::vectorwchar_t wstr(gb2312.size()); wchar_t* wstrEnd nullptr; const char* gbEnd nullptr; mbstate_t state {}; int res use_facetcodecvtwchar_t, char, mbstate_t (g_loc).in(state, gb2312.data(), gb2312.data() gb2312.size(), gbEnd, wstr.data(), wstr.data() wstr.size(), wstrEnd); if (codecvt_base::ok res) { wstring_convertcodecvt_utf8wchar_t cutf8; std::string result cutf8.to_bytes(wstring(wstr.data(), wstrEnd)); $result SWIG_FromCharPtrAndSize(result.c_str(), result.size()); } else { std::string result; $result SWIG_FromCharPtrAndSize(result.c_str(), result.size()); } } // 启用“director”特性允许Python类继承C的虚函数类如Spi回调类 // 这是实现事件驱动回调模型的关键 %feature(director) CThostFtdcTraderSpi; // 忽略一些不常用的枚举常量简化生成的Python接口 %ignore THOST_FTDC_VTC_BankBankToFuture; // ... 其他需要忽略的枚举 // 最终包含官方的头文件Swig会解析它们并生成包装代码 %include ThostFtdcUserApiDataType.h %include ThostFtdcUserApiStruct.h %include ThostFtdcTraderApi.hthostmduserapi.i文件的内容与此类似只是模块名和包含的头文件改为行情相关的。把这个文件保存到和CTP头文件同一目录下。### 3.2 使用Swig生成包装代码打开命令提示符CMD或者PowerShell导航到存放.i文件和CTP头文件的目录。依次执行以下两条命令swig -threads -py3 -c -python thosttraderapi.i swig -threads -py3 -c -python thostmduserapi.i这里的参数很重要-threads确保生成支持多线程的代码-py3指定生成Python 3的包装器-c告诉Swig源语言是C。命令执行成功后你会看到目录里新生成了四个文件thosttraderapi_wrap.cxx、thosttraderapi.py、thostmduserapi_wrap.cxx、thostmduserapi.py。其中.cxx文件是Swig生成的、将C和Python粘合在一起的C源代码而.py文件则是Python端的模块入口。### 3.3 使用Visual Studio编译生成Python模块.pyd这是Windows下最具挑战性的一步但配置好了就是一劳永逸。打开Visual Studio创建两个新的“动态链接库(DLL)”项目分别命名为_thosttraderapi和_thostmduserapi。将上一步生成的thosttraderapi_wrap.cxx添加到交易接口项目将thostmduserapi_wrap.cxx添加到行情接口项目。同时把CTP官方的头文件.h和库文件.lib也添加到对应项目的“附加包含目录”和“附加依赖项”中。项目属性的配置是关键我以_thosttraderapi项目为例说明C/C - 常规 - 附加包含目录这里需要添加两个路径。一是Python的头文件路径比如D:\Anaconda3\include二是CTP头文件所在的当前目录。链接器 - 常规 - 附加库目录添加Python的库文件路径如D:\Anaconda3\libs。链接器 - 输入 - 附加依赖项这里需要添加具体的库文件名。通常包括python37.lib请根据你的Python版本调整如python39.lib和thosttraderapi_se.lib。配置完成后选择Release模式和x64平台然后编译项目。如果一切顺利在项目的输出目录通常是x64\Release\下你会得到_thosttraderapi.dll。Python在Windows下无法直接加载.dll作为扩展模块我们需要将其重命名为.pyd。所以把生成的_thosttraderapi.dll复制一份改名为_thosttraderapi.pyd。对_thostmduserapi项目执行相同的操作。至此Windows下的封装就完成了。最终你需要的文件是6个两个.pyd文件、两个.py文件以及CTP官方的两个.dll运行时库thosttraderapi_se.dll和thostmduserapi_se.dll。4. Linux平台封装全流程详解Linux下的封装过程更“原生”主要依靠GCC编译器和Makefile整体感觉更简洁。首先通过SSH或终端登录到你的Linux服务器或开发机将CTP-API的Linux版压缩包上传并解压。进入解压后的目录例如v6.7.0_20230209_api_traderapi_se_linux64你会看到.so库文件和头文件。### 4.1 准备Swig接口文件与生成包装代码Linux下使用的thosttraderapi.i和thostmduserapi.i文件其内容与Windows版本完全一样直接复制过来即可。同样在终端里使用Swig命令生成包装代码swig -threads -py3 -c -python thosttraderapi.i swig -threads -py3 -c -python thostmduserapi.i执行后同样会生成四个文件两个.cxx和两个.py。### 4.2 编写Makefile自动化编译Linux下我们通常用Makefile来管理编译过程。为交易接口创建一个名为make_traderapi的文件内容如下。你需要将/usr/include/python3.6m替换成你自己系统上Python头文件的实际路径。可以使用命令python3-config --includes来查看。OBJSthosttraderapi_wrap.o INCLUDE-I./ -I/usr/include/python3.6m TARGET_thosttraderapi.so CPPFLAG-shared -fPIC CCg $(TARGET) : $(OBJS) $(CC) $(CPPFLAG) $(INCLUDE) -o $(TARGET) $(OBJS) -L. -lthosttraderapi_se $(OBJS) : %.o : %.cxx $(CC) -c -fPIC $(INCLUDE) $ -o $ clean: -rm -f $(OBJS) -rm -f $(TARGET)再为行情接口创建一个类似的make_mduserapi文件。这里解释几个关键点-shared -fPIC是编译动态链接库的标准参数-L. -lthosttraderapi_se告诉链接器在当前目录.下寻找名为libthosttraderapi_se.so的库注意链接时库名的规则。### 4.3 执行编译与重命名库文件在编译前有一个非常重要的步骤CTP官方提供的库文件可能叫thosttraderapi_se.so但GCC链接器在-l参数后默认查找的是lib开头、.so结尾的文件。所以我们需要创建一个符合链接器查找规则的软链接或者直接重命名# 为交易接口库创建链接 ln -s thosttraderapi_se.so libthosttraderapi_se.so # 或者直接重命名 # mv thosttraderapi_se.so libthosttraderapi_se.so # 为行情接口库创建链接 ln -s thostmduserapi_se.so libthostmduserapi_se.so然后使用我们写好的Makefile进行编译make -f make_traderapi make -f make_mduserapi如果编译成功当前目录下就会生成_thosttraderapi.so和_thostmduserapi.so这两个Python扩展模块。在Linux下.so文件可以直接被Python的import语句加载。最终你同样得到6个关键文件两个.so扩展模块、两个.pyPython文件以及CTP官方的两个运行时.so库。5. 跨平台兼容性核心问题与解决方案把同一套代码在Windows和Linux上都能跑通这才是“跨平台”封装的价值所在。在实际操作中我踩过不少坑总结下来主要有以下几个核心差异点需要特别注意。### 5.1 动态库文件命名与链接差异这是第一个拦路虎。在Windows下CTP官方提供的库文件通常叫thosttraderapi_se.dll运行时库和thosttraderapi_se.lib用于链接的导入库。在Visual Studio项目中我们在“附加依赖项”里直接写thosttraderapi_se.lib即可。而在Linux下官方提供的是thosttraderapi_se.so。但GCC/G的链接器ld有一个默认规则当使用-l参数指定库时例如-lthosttraderapi_se它会去寻找名为libthosttraderapi_se.so的文件。这就是为什么在Linux的Makefile里我们需要通过创建软链接ln -s或直接重命名来满足链接器的查找约定。这个差异虽然小但如果不了解编译时的“找不到-lxxx库”错误足以让人排查半天。### 5.2 字符编码与本地化Locale设置CTP-API内部使用的是GB2312或GBK编码来传递中文信息而现代Python 3全面转向了UTF-8。如果不做转换在Python端收到的中文会是乱码。我们在Swig接口文件.i中编写的那个复杂的%typemap(out)就是专门为了解决这个问题。它利用C标准库的codecvt和locale在数据从C返回到Python的边界上自动进行GBK到UTF-8的转换。这里还有一个平台差异在Windows下设置locale时使用zh-CN而在Linux下通常需要使用zh_CN.GB18030或zh_CN.GBK。我们在.i文件中通过#ifdef _MSC_VER这个预编译指令来区分两个平台就是为了保证无论在哪个系统下编译都能使用正确的locale名称。### 5.3 编译器与运行时依赖Windows的编译环境由Visual Studio主导它自带了一套完整的运行时库如MSVCRT。我们编译好的.pyd文件在目标机器上运行时可能需要安装对应版本的Visual C Redistributable否则会提示缺少vcruntime140.dll等错误。而在Linux下编译依赖GCC/G和glibc运行时的依赖相对简单通常只需要确保glibc的版本不低于编译环境即可。为了最大程度保证兼容性特别是在Linux生产服务器上部署时一个实用的建议是尽量在较低版本的Linux发行版比如CentOS 7或使用较老GCC版本的环境中编译你的封装模块这样生成的.so文件在更高版本的系统上通常也能正常运行。6. 封装成果测试与基础使用示例费了这么大劲封装好当然要赶紧测试一下能不能用。这里我提供一个最简单的连接测试示例确保我们的封装是成功的。首先把你封装生成的6个文件Windows是2个.pyd2个.py2个.dllLinux是2个.so2个.py2个.so官方库放到同一个目录下或者放到Python解释器能够搜索到的路径中。### 6.1 测试行情接口连接创建一个测试文件比如test_md.py。我们先测试行情接口因为它通常不需要登录连接更快。import sys from thostmduserapi import * # 1. 创建回调类继承自CThostFtdcMdSpi class CMdSpi(CThostFtdcMdSpi): def __init__(self): super().__init__() def OnFrontConnected(self): 当与行情前置机连接成功时被调用 print(行情前置机连接成功) # 连接成功后可以在这里执行用户登录或订阅行情等操作 # 为了简单测试我们直接断开 print(连接测试通过即将断开。) def OnFrontDisconnected(self, nReason): 当连接断开时被调用 print(f行情前置机连接断开原因代码: {nReason}) # 2. 主程序 if __name__ __main__: # 创建行情API实例 # 参数是“flow”目录路径用于存储连接产生的临时文件传入空字符串表示使用当前目录 md_api CThostFtdcMdApi_CreateFtdcMdApi() # 创建我们自定义的回调对象 md_spi CMdSpi() # 将回调对象注册给API实例 md_api.RegisterSpi(md_spi) # 订阅公共流行情流 md_api.SubscribePublicTopic(THOST_TERT_RESTART) # 订阅私有流交易流行情接口这里通常不需要但按惯例设置 md_api.SubscribePrivateTopic(THOST_TERT_RESTART) # 连接前置机。这里需要替换成真实的模拟或实盘行情前置机地址。 # 模拟环境地址通常由期货公司提供例如tcp://180.168.146.187:10131 front_address tcp://180.168.146.187:10131 md_api.RegisterFront(front_address) # 初始化运行此调用后网络连接线程才会启动 md_api.Init() print(行情API初始化完成等待连接事件...) # 等待一段时间让回调事件发生 import time time.sleep(3) # 释放API实例 md_api.Release() print(测试结束。)运行这个脚本如果看到输出“行情前置机连接成功”那么恭喜你行情接口的封装基本成功了这说明Python已经能成功加载我们编译的C扩展模块并完成了底层的网络连接。### 6.2 一个更完整的交易接口交互示例交易接口的测试稍微复杂因为涉及登录、确认等流程。下面是一个简化的框架展示了如何组织代码。import sys from thosttraderapi import * class CTraderSpi(CThostFtdcTraderSpi): def __init__(self, api): super().__init__() self.api api self.request_id 0 def OnFrontConnected(self): print(交易前置机连接成功开始登录...) req CThostFtdcReqUserLoginField() # 以下字段需要替换为你的模拟或实盘账户信息 req.BrokerID 9999 req.UserID your_user_id req.Password your_password self.request_id 1 ret self.api.ReqUserLogin(req, self.request_id) if ret 0: print(登录请求发送成功) else: print(f登录请求发送失败错误码: {ret}) def OnRspUserLogin(self, pRspUserLogin, pRspInfo, nRequestID, bIsLast): if pRspInfo is not None and pRspInfo.ErrorID ! 0: print(f登录失败: ErrorID{pRspInfo.ErrorID}, ErrorMsg{pRspInfo.ErrorMsg}) else: print(f登录成功交易日: {pRspUserLogin.TradingDay}) # 登录成功后可以查询资金、持仓等 # self.query_account() # 可以继续添加其他回调方法如OnRspQryTradingAccount, OnRtnOrder等 if __name__ __main__: trader_api CThostFtdcTraderApi_CreateFtdcTraderApi() trader_spi CTraderSpi(trader_api) trader_api.RegisterSpi(trader_spi) trader_api.SubscribePublicTopic(THOST_TERT_RESTART) trader_api.SubscribePrivateTopic(THOST_TERT_RESTART) # 使用交易前置机地址 trader_api.RegisterFront(tcp://180.168.146.187:10130) trader_api.Init() print(交易API已初始化等待事件...) # 保持主线程运行等待回调 import time try: while True: time.sleep(1) except KeyboardInterrupt: print(用户中断开始退出...) trader_api.Release()这个示例给出了一个事件驱动的框架。CTP-API采用异步回调模式所有网络响应、订单状态更新都会通过Spi回调类的方法通知我们。在真实开发中你需要根据业务逻辑完善各个回调函数。通过这两个测试你可以验证封装模块的功能完整性为后续的策略开发打下坚实基础。记住首次使用务必在模拟环境中充分测试确保所有环节稳定无误。