CTP 作为期货量化交易的标准接口,是每个量化开发者必须掌握的核心技能,但很多新手卡在环境配置、编译报错、接口调用上,折腾几天都跑不通一个最简单的行情 Demo。今天直接给你一套零门槛、可直接编译运行的 Linux C++ CTP 行情完整 Demo,从环境搭建到代码运行,一步到位,复制粘贴就能用,彻底告别 CTP 入门难的问题!一、先搞懂:这篇 Demo 能给你什么?
1、纯原生 C++ 实现
不依赖第三方框架,底层逻辑一目了然
2、Linux 环境一键编译
解决 90% 新手遇到的链接、编译报错
3、完整行情接收
实时获取期货合约最新价、买一卖一、成交量、持仓量等全量行情数据
4、注释超详细
每行关键代码都有说明,新手也能看懂
5、即拿即用
替换期货公司账号信息,直接运行接收实盘 / 仿真行情
二、前置准备(1 分钟搞定)
1. 环境要求
系统:Ubuntu/CentOS 等任意 Linux 发行版(64 位)
编译器:g++(安装命令:sudo apt install g++或sudo yum install gcc-c++)
CTP 行情库:官方thostmduserapi.so动态库(我已集成在 Demo 里,无需单独下载)
2. 核心文件说明
三、完整可编译源码(直接复制)
1. 主程序:testMdApi.cpp
#include"./include/ThostFtdcMdApi.h"#include"MdSpi.h"#include<cstdlib>#include<iostream>// 全局变量CThostFtdcMdApi* pUserApi = NULL;const char FRONT_ADDR[] = "tcp://ip:port";int iRequestID = 0;intmain(){ pUserApi = CThostFtdcMdApi::CreateFtdcMdApi(); if(!pUserApi) { std::cerr << "Error: Failed to create MD API instance" << std::endl; return 1; } CThostFtdcMdSpi* pUserSpi = new CMdSpi(); if(!pUserSpi) { std::cerr << "Error: Failed to create MD SPI instance" << std::endl; return 2; } pUserApi->RegisterSpi(pUserSpi); pUserApi->RegisterFront(const_cast<char*>(FRONT_ADDR)); pUserApi->Init(); pUserApi->Join(); // 清理 pUserApi->Release();//#pragma GCC diagnostic push//#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" delete pUserSpi;//#pragma GCC diagnostic pop return 0;}
2、SPI回调类头文件 MdSpi.h
#pragma once#include"./include/ThostFtdcMdApi.h"#include<string>class CMdSpi : public CThostFtdcMdSpi{public: virtual ~CMdSpi() {} virtualvoidOnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); virtualvoidOnFrontDisconnected(int nReason); virtualvoidOnHeartBeatWarning(int nTimeLapse); virtualvoidOnFrontConnected(); virtualvoidOnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); virtualvoidOnRspSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); virtualvoidOnRspUnSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); virtualvoidOnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData);private: voidReqUserLogin(); voidSubscribeMarketData(); boolIsErrorRspInfo(CThostFtdcRspInfoField *pRspInfo);};
3、SPI回调类CPP文件 MdSpi.CPP
#include"MdSpi.h"#include<iostream>#include<cstring>#include<cstdio>#include<ctime>#include<fstream>#include<iomanip>#include<limits>// USER_API参数extern CThostFtdcMdApi* pUserApi;// 配置参数const TThostFtdcBrokerIDType BROKER_ID = "9999";const TThostFtdcInvestorIDType INVESTOR_ID = "";const TThostFtdcPasswordType PASSWORD = "";char* ppInstrumentID[] = {(char*)"rb2605",(char*)"AP605"};const int iInstrumentID = 2;// 请求编号extern int iRequestID;time_t pos = 0;std::ofstream OutStream;// C++98 兼容的无效浮点数定义namespace { const double InvalidDouble = 1.0 / 0.0; // 产生inf,可通过isinf检测}boolCMdSpi::IsErrorRspInfo(CThostFtdcRspInfoField *pRspInfo){ bool bResult = (pRspInfo && (pRspInfo->ErrorID != 0)); if (bResult) { std::cerr << "--->>> ErrorID=" << pRspInfo->ErrorID << ", ErrorMsg="; if(pRspInfo->ErrorMsg) std::cerr << pRspInfo->ErrorMsg; std::cerr << std::endl; } return bResult;}voidCMdSpi::OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast){ std::cerr << "--->>> "<< __FUNCTION__ << std::endl; IsErrorRspInfo(pRspInfo);}voidCMdSpi::OnFrontDisconnected(int nReason){ std::cerr << "--->>> " << __FUNCTION__ << std::endl; std::cerr << "--->>> Reason = " << nReason << std::endl;}voidCMdSpi::OnHeartBeatWarning(int nTimeLapse){ std::cerr << "--->>> " << __FUNCTION__ << std::endl; std::cerr << "--->>> nTimerLapse = " << nTimeLapse << std::endl;}voidCMdSpi::OnFrontConnected(){ std::cerr << "--->>> " << __FUNCTION__ << std::endl; ReqUserLogin();}voidCMdSpi::ReqUserLogin(){ CThostFtdcReqUserLoginField req; std::memset(&req, 0, sizeof(req)); std::strncpy(req.BrokerID, BROKER_ID, sizeof(req.BrokerID) - 1); std::strncpy(req.UserID, INVESTOR_ID, sizeof(req.UserID) - 1); std::strncpy(req.Password, PASSWORD, sizeof(req.Password) - 1); int iResult = pUserApi->ReqUserLogin(&req, ++iRequestID); std::cerr << "--->>> 发送用户登录请求: " << ((iResult == 0) ? "成功" : "失败") << std::endl;}voidCMdSpi::OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast){ std::cerr << "--->>> " << __FUNCTION__ << std::endl; if (bIsLast && !IsErrorRspInfo(pRspInfo)) { std::cerr << "--->>> 获取当前交易日 = " << pUserApi->GetTradingDay() << std::endl; OutStream.open("quot.csv", std::ios::out | std::ios::app); if(!OutStream.is_open()) { std::cerr << "输出文件路径错误" << std::endl; return; } SubscribeMarketData(); }}voidCMdSpi::SubscribeMarketData(){ int iResult = pUserApi->SubscribeMarketData(ppInstrumentID, iInstrumentID); std::cerr << "--->>> 发送行情订阅请求: " << ((iResult == 0) ? "成功" : "失败") << std::endl;}voidCMdSpi::OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData){ if(pDepthMarketData) { // 兼容C++98的无效值检测 if(!(pDepthMarketData->LastPrice == pDepthMarketData->LastPrice) || // NaN检测 pDepthMarketData->LastPrice == InvalidDouble) { pDepthMarketData->LastPrice = 0.00; } // 其他字段同理... char szRec[256]={0}; std::snprintf(szRec, sizeof(szRec), "%s,%s,%d,%s,%.2f,%d,%.2f,%d,%.2f", pDepthMarketData->TradingDay, pDepthMarketData->UpdateTime, pDepthMarketData->UpdateMillisec, pDepthMarketData->InstrumentID, pDepthMarketData->BidPrice1, pDepthMarketData->BidVolume1, pDepthMarketData->AskPrice1, pDepthMarketData->AskVolume1, pDepthMarketData->LastPrice); OutStream << szRec << "\n"; OutStream.flush(); std::cerr << "--->>> "<< szRec << std::endl; }}voidCMdSpi::OnRspSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast){ std::cerr << __FUNCTION__ << std::endl; if (pSpecificInstrument) std::cerr << "InstrumentID: " << pSpecificInstrument->InstrumentID << std::endl; IsErrorRspInfo(pRspInfo);}voidCMdSpi::OnRspUnSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast){ std::cerr << __FUNCTION__ << std::endl; if (pSpecificInstrument) std::cerr << "InstrumentID: " << pSpecificInstrument->InstrumentID << std::endl; IsErrorRspInfo(pRspInfo);}
4. 必备文件(直接下载 / 复制)
ThostFtdcMdApi.h
CTP 官方行情头文件(直接从期货公司官网下载,或私信我获取)
libthostmduserapi.so
CTP 官方 64 位 Linux 动态库(必须和头文件版本匹配)
四、一键编译运行(零报错)
1、进入到代码运行目录
2、创建build文件夹
3、进入build目录
4、编译
5、进入到执行程序目录
6、运行程序
五、运行成功效果(如图所示)
六、新手必改的 3 个地方(不修改无法运行)
FRONT_ADDR替换为你的期货公司行情前置地址
ppInstrumentID替换为你需要订阅的合约
iInstrumentID根据ppInstrumentID来填写实际订阅数量
七、常见问题 1 分钟解决
报错:找不到 libthostmduserapi.so
解决:执行export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
订阅失败
解决:合约代码写错(如 rb2605 小写、空格)
八、写在最后
CTP 入门根本不用啃官方晦涩的文档,一个可运行的 Demo 胜过千言万语。这套代码是我实测可用的极简版本,没有任何冗余代码,完全贴合新手入门需求:看懂它,你就掌握了 CTP 行情接口 80% 的核心逻辑,后续对接交易接口、写量化策略都会事半功倍。粉丝福利
需要完整文件包(头文件 + 动态库 + 源码)、CTP 行情Demo的朋友,直接评论区扣「1」,我私信发给你!