十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C++跨平台高频量化交易平台实战:架构设计与性能优化

C++跨平台高频量化交易平台实战:架构设计与性能优化 简介面向国内期货高频交易场景这是一套基于C开发的跨平台量化交易平台源码工程。资源对CTP等柜台接口做了友好封装提供统一策略接口支持CTP、QDP、Femas等多套交易API可运行于Linux和Windows。低延时直连柜台行情与交易组件可任意组合切换策略无需改动仓位、挂单等本地维护策略可同步获取支持自动开平和Tick级回测适合有一定C基础、希望快速搭建低延时交易系统的量化开发人员。包体方面资源共118个文件压缩包约65.94MB。核心代码以57个h头文件和5个cpp源文件为主同时包含20个lib、4个dll、4个a和2个so等预编译库覆盖Linux与Windows多平台另附6个xml配置、多个工程文件与说明文档便于直接编译或集成。已有426人学习下载文件结构清晰PandoraSimulator、策略示例等内容可帮助读者理解框架用法并在此基础上扩展自己的策略逻辑。 最近刚把一个从零开始写的C跨平台高频量化交易平台从原型推进到了可以对接多个交易所接口的可用状态。整个过程踩了不少坑也总结了不少经验今天就把整套项目的设计思路、接口适配方案、跨平台构建、性能优化和风控细节一次性讲清楚。不管你是想搭一个自己的量化框架还是打算在团队里引入C作为核心语言这篇都值得收藏。先说结论C依然是低延迟交易场景里绕不开的选择但“用C写一个交易平台”和“把一个交易平台写得又快又稳”完全是两码事真正的功夫都花在接口抽象、内存管理、并发模型和容错设计上。1. 项目定位与技术选型为什么偏偏是C1.1 平台要解决的核心问题这类平台最核心的需求是处理“高频”两个字。高频交易意味着行情推送频率高、订单生命周期极短、对延迟极其敏感。一套完整的量化交易系统通常包括行情接入、策略计算、订单管理、风控、回测、账户管理等多个模块而我们希望用一套代码同时跑在Windows和Linux上还能对接不同机构的交易API这就是整个项目最直接的出发点。很多人在第一步就纠结语言我直接把结论放在前面Python适合策略研究但不太适合做高频交易。高频交易平台的延迟预算往往只有微秒到毫秒级而Python的动态类型和全局解释器锁很难在这种场景下稳定发挥。C的优势不在于写起来方便而在于它能把延迟做到可控、可预测、可优化到极致。1.2 为什么不是Python、Go或者Java拿Python对比写策略和研究确实高效但实盘高频时GIL、垃圾回收暂停、动态分发都会成为不稳定因素。Go语言在并发模型上很优秀编译和部署也方便但Go有GC内存分配和回收会产生不可预测的停顿高频场景下你就得各种逃逸分析加内存池反而费劲。Java有JIT长跑之后性能不差但JVM调优、类加载、GC参数这些要伺候好并不轻松而且在一些低延迟Linux环境中Java的启动时延和内存占用也不太友好。C则直接把内存和并发交给你控制可以用无锁数据结构、自定义内存池、CPU缓存对齐来优化在关键路径上做到完全不触发系统调用、不动态分配内存这是高频场景的基本素养。当然C开发效率低、编译慢、容易出错也是事实所以我们后面会通过架构设计来扬长避短。1.3 整体架构与模块划分整个平台没有用一个大单体而是按职责拆成几个相对独立的模块接入层负责各种交易API和行情API的适配对外提供统一的行情和交易接口。策略引擎运行策略逻辑接收行情后计算信号并生成订单请求。订单管理管理订单状态机处理回报、成交、撤单并和风控模块协同。风控中心在订单进出前后做额度、频率、黑白名单、熔断等检查。回测引擎用历史数据模拟撮合帮助验证策略逻辑。公共工具库包括日志、配置、时间、统计、序列化等基础能力。这些模块之间通过事件驱动的方式通信核心路径上使用无锁队列传递消息IO线程、策略线程、风控线程分工明确。微信里有个很形象的类比这就像一家餐厅的厨房下单、炒菜、传菜、洗碗各干各的但都在同一个通风管道里传递单据谁慢了就会堵住整条线。2. 多交易API接入层设计让一套代码对接N个柜台2.1 交易API的差异到底有多大“支持多种交易API”这句话听起来简单实际上坑特别深。不同柜台或者交易所的接口风格天差地别有的是REST接口有的是WebSocket有的是FIX协议还有一些是各机构私有的C插件接口。比如期货柜台很多直接基于C动态库做回调股票柜台有自己的一套协议外盘券商则可能开放FIX或类似FIX的网关接口。行情接口更是五花八门有的是全量快照推送有的是增量订阅还有的需要自己维护深度行情。如果我们每对接一个接口就把策略层逻辑改一遍那这个平台基本没法维护。所以我们提出了一个核心原则接口层要窄策略层要宽。2.2 统一接口抽象与适配器模式在接入层定义了一个统一的交易接口和行情接口所有策略和订单模块只和这套抽象打交道。比如行情接口抽象出订阅合约、接收快照、接收逐笔成交、接收深度行情等动作交易接口抽象出登录、报单、撤单、查询持仓、查询资金、接收成交回报等动作。每个实际API都实现为这套抽象的一个适配器。C里实现这个抽象我推荐用纯虚接口加工厂模式而不是把所有可能的字段都塞进一个大结构体里。举个例子不管是REST返回的JSON还是FIX的Tag-Value最终都转成我们自己的内部消息对象这样策略层永远不用关心外部协议细节。class IMarketDataApi { public: virtual void Login(const LoginConfig config) 0; virtual void Subscribe(const std::vectorInstrumentId instruments) 0; virtual void SetCallback(std::shared_ptrIMarketDataCallback callback) 0; virtual void Disconnect() 0; virtual ~IMarketDataApi() default; };每个适配器里都有一层转换逻辑把外部的协议数据映射成内部统一的结构体。刚开始做的时候大家会觉得多写不少代码但后面对接第七、第八个API时你会感谢这层抽象。2.3 动态加载插件与管理生命周期考虑到不同API的依赖经常冲突我们一开始就把每个API适配器做成了动态库插件主程序只负责加载某个目录下的shared library通过导出函数获取适配器对象。这样某个新接口出问题时我们不用重新编译整个平台只要替换对应的插件文件。插件的生命周期管理要注意几点动态库的路径必须在配置里显式指定不能靠搜索当前目录。插件内部不能和主程序使用不同版本的全局运行时否则跨模块释放内存会崩溃。每次加载后要校验版本号、接口兼容性防止跑错版本。我遇到过最尴尬的一次就是同一个FIX引擎被两个插件重复加载导致session和socket资源冲突盘前最后几分钟才定位到差点错过了开盘。后来我们在插件加载入口加了一个全局资源表每次加载前检查才彻底解决这个问题。2.4 行情与订单两条数据流怎么设计交易系统里数据流必须分清楚行情是海量的、快速的订单是低频但极其关键的。我们用了两条线程处理行情线程处理行情流交易线程处理订单回报。两线程之间不直接锁共享内存而是通过无锁队列把关键事件提交给策略引擎和风控引擎。订单回报的通道尤其不能丢数据我们会在适配器里给每个回报生成一个序号并在主线程定期做对账用确认请求补齐遗漏。这是从一次惨痛教训里学来的当时某个API的WebSocket连接半开持续丢失成交回报直接导致持仓不平还好没有穿仓否则后果就大了。3. 跨平台构建与基础库选择3.1 用CMake统一构建别让平台差异入侵代码平台用了CMake版本直接定在3.20以上C标准定为C17编译器在Windows上使用MSVCLinux使用GCCmacOS用Clang。CMake的好处是它天然支持多平台预设我们可以通过一个参数切换编译目标平台。跨平台的第一步不是写代码而是写CMakeLists。我们遇到的最大问题不是编译失败而是第三方库的平台差异。比如MySQL或Redis客户端库在不同平台上依赖的头文件和链接名不一样需要专门写Platform.cmake来做条件判断。不要把所有平台判断塞进业务代码里业务代码里到处是#ifdef _WIN32这会让维护变成灾难。一个比较靠谱的做法是写一层薄薄的SystemUtil把平台相关的功能收拢起来比如获取毫秒时间戳、设置线程名称、绑定CPU核心、获取当前可执行文件路径等。业务代码只调用这层封装不直接使用平台API。3.2 并发原语与网络层选型跨平台并发和网络库的选择很重要。对于线程、原子变量、互斥量直接用C11之后的标准库就足够了比直接用操作系统原语更安全。网络和事件循环我推荐使用Boost.Asio或者独立版的ASIO。它在Windows上默认使用IOCPLinux上使用epoll接口虽略有差异但异步模型是一致的。在高频交易里网络IO不能随便用一个框架你必须清楚每个read/write/accept调用是否会产生系统调用是否能在某个线程内完成连接断开时回调是否及时。ASIO配合自定义分配器可以做到接收和发送缓冲区完全复用避免在热路径上频繁创建对象。3.3 开发调试环境与CI开发调试环境也有讲究。Windows上我们用Visual Studio或者VSCodeCMake插件做本地开发Linux上以VSCode Remote为主。VSCode的c开发体验现在很成熟关键在于配置好tasks.json和launch.json让编译和调试能一键跑通。编译结束后立刻用CTest跑一遍单元测试并且用AddressSanitizer和UndefinedBehaviorSanitizer跑一次测试集。这些在Windows和Linux都可以用。CI直接上GitHub Actions或者GitLab CI每次提交都触发编译、单元测试、静态检查发现问题早修早好别攒到一个版本最后集中踩雷。3.4 跨平台遇到的那些坑我记录几个印象深刻的跨平台坑字节序不同平台一个是大端一个是小端网络传输的行情数据尤其要小心必须统一使用网络字节序解析并在文档里写清楚。换行符Windows和Linux文本模式不一样凡是写配置文件和日志一定要用二进制模式打开否则字符串里会多出\r。动态库后缀Windows上dllLinux上somacOS上dylib加载路径和命名规则完全不同不能用同一套拼接逻辑。环境变量Windows在注册表里存储部分系统信息Linux则靠/etc目录这些各自封装成接口不要裸调。这些都不是复杂问题但忽视它们会导致各种莫名其妙的现象经常白天在Windows上跑得好好的晚上换到Linux就崩。4. 高频场景下的性能优化实践4.1 热路径上禁止动态分配高频系统最重要的原则之一就是交易核心路径上不要做堆内存分配。new/delete是不可控的尤其是大量高频小对象堆积起来会触发内存碎片和分配器锁争抢。我们给策略和订单模块准备了核心内存池对象在创建时从内存池里预分配在线程结束时统一释放。举个例子行情快照对象可以做成预分配的对象池每次行情到来时从池里取出一个槽位填充数据策略读完之后归还。这个过程是无锁的用atomic变量做索引递增即可实测下来比直接new快几个数量级。4.2 无锁队列和环形缓冲区线程间交换数据我们第一版用了mutex加condition_variable结果行情稍微一密集策略线程就被阻塞得厉害延迟雪崩。后来把行情分发改成了无锁的MPSC或SPSC环形队列配合内存屏障延迟稳定了很多。如果你自己写无锁队列必须注意内存序尤其是多生产者单消费者场景需要仔细处理ABA问题。如果不确定建议先用成熟的库比如Boost.Lockfree但要注意它内部也可能分配内存需要设置容量上限。环形缓冲区的容量必须足够容纳一个交易日的最大事件数否则生产者会阻塞或丢数据。4.3 低延迟网络与系统调优网络延迟优化更多是在操作系统层面。Linux我们通过setsockopt设置TCP_NODELAY关闭Nagle算法同时把socket接收和发送缓冲区调大如果条件允许通过SO_BUSY_POLL开启忙轮询减少中断次数。Windows上则要关注Nagle算法和TCP环境参数。线程还需要绑定CPU核心尤其是一台机器上同时跑行情线程和策略线程时绑定不同的物理核可以显著减少线程切换时的cache miss。用pthread_setaffinity_np在Linux上设置Windows对应SetThreadAffinityMask。4.4 时间、日志与精度问题时间戳是高频系统最容易忽略的黑洞。不同API拿到的行情时间可能是服务器本地时间也可能带时区信息统一转换成UTC时间戳策略里再做时区映射跨市场交易才不会错乱。日志也是个硬伤。一开始我们用同步日志行情高峰期直接让主线程卡在文件IO上后来才把所有核心日志改成异步队列并限制了单次日志长度。普通业务日志不走热路径真正热路径上我们能不记日志就不记必须记的也尽量在内存里合并后批量写入。精度问题更要命。金融里金额、价格不能拿double随便算尤其计算持仓均价、手续费、盈亏时double的浮点误差积累后会出现资金对不平的情况。我们的做法是统一用整数表示最小单位乘数由每个合约的报价精度决定这样加减都精确乘法运算再额外处理舍入规则。5. 回测、风控与运维5.1 回测撮合引擎怎么模拟写回测撮合引擎核心目标是尽量贴近真实成交。我们模拟了限价单、市价单支持部分成交、滑点模型、手续费模型。滑点不能简单设一个固定值至少要根据历史行情的买卖价差波动区间来模拟。回测里跑赢的策略放到实盘不一定能赢但回测撮合太粗糙肯定更不靠谱。撮合引擎用逐笔行情做驱动单子会挂在订单簿上等对手单成交时间由行情时间触发而不是人为设定一个固定的成交延迟。这样回测出来的收益曲线就像有呼吸的生命体而不是一根假直线。5.2 风控模块必须前置风控不是实盘才需要的东西回测里也要模拟。平台的风控模块分为交易前和交易后两层。交易前检查下单频率、单笔数量、总仓位、最大亏损、禁止交易合约交易后监控成交回报和持仓变化是否和策略预期一致发现异常立刻熔断并通知运维。这里要特别强调一个实践体验风控不能只做一个回调函数而必须放在订单管理流水线里在订单进入券商接口之前拦截。我们曾经把风控放在策略线程里结果策略执行卡住时风控也可能跟着卡住后来改成风控消息全部走独立队列和独立线程主流程挂掉也不影响风控熔断。5.3 断线重连与可用性交易接口断线是必然事件问题只在于多久能发现、多久能恢复。每个API适配器内置心跳机制如果一段时间没有心跳立刻触发连接断开回调。重连时不能反复高频尝试否则会把交易所的网关打爆。我们的做法是初始重试间隔1秒最多延迟到30秒同时保留原连接的所有未确认订单状态在重连后立即做一次查询对齐。关键的交易账号在另一个进程里做了主从热备主进程异常推出后备用进程通过共享内存里的心跳和会话状态判断自己是否接管。这个机制不是必须第一版就做但如果你真要用这套平台做高频交易生产环境一定得有否则一次盘中崩溃就够你喝一壶。6. 写在最后的几句大实话这个项目做下来我最大的感触是平台本身的架构和风控逻辑比任何策略都重要。C的高性能优势只有建立在干净的接口抽象和稳定的跨平台基础设施上才能发挥出来。如果你是为了快速验证一个策略真没必要一开始就上C但如果你目标就是高频、低延迟、多市场接入那C这条路迟早要走。另外多交易API接入的价值其实不在于同时连多少个接口而在于一套代码能轻松切换并平滑迁移。真正要接新市场时你会发现前期那层抽象和统一数据格式帮了多大的忙。还有一个小技巧所有对接的API文档一定要在代码里留下具体版本号和测试用例否则几个月后某个交易所悄悄升级协议你连自己什么时候断的都不知道。本文还有配套的精品资源点击获取
返回列表