一、准备工作
- 确保你有相应的库文件以及头文件, 没有的可以在这个SimNow仿真交易【官方网站】下载
- 确保有接口手册, 这能提高开发效率, ::::::上海期货信息技术有限公司::::::提供了相应chm的下载
二、代码实现
1. 准备配置文件Config.h
在其中用宏来代替相应位置的变量, 这样做的好处是方便我们后续修改账户密码, 以及修改想要关注的行情编号
下面代码的双引号中写上你自己的对应字符串
#ifndef _CONFIG_H
#define _CONFIG_H
// 定义行情服务器地址
#define URL ""
// 定义期货公司编号
#define MyBrokerID ""
// 定义用户账号
#define MyUserID ""
// 定义用户密码
#define MyPassword ""
// 定义用户产品信息
#define MyUserProductInfo ""
// 定义订阅的行情数据
#define SubscribeMarketDatas { "c2405", "m2405" }
// 用于控制是否在终端打印详细数据, 注销PRINT_DATA则不打印
// #define PRINT_DATA
#endif
2. 准备MdSpi.h, 在其中定义MdSpi来继承CThostFtdcMdSpi, 以便之后重新其中对应的Spi函数, 来实现我们想要的功能, 函数内对应结构体请自行在chm中查看
class MdSpi : public CThostFtdcMdSpi
{
public:
// 构造函数, 初始化行情线程
MdSpi(CThostFtdcMdApi *pMdApi, string path = "./Datas");
// 析构函数, 释放行情线程
~MdSpi();
// 当客户端与交易后台建立起通信连接时(还未登录前),该方法被调用。
void OnFrontConnected();
// 登录请求响应
void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast);
// 订阅行情应答
void OnRspSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast);
// 深度行情通知
void OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData);
private:
string __savePath; // 行情数据保存路径
CThostFtdcMdApi *__pMdUserApi = nullptr; // CTP行情API指针
string __tradingDay; // 交易日
map<string, string> __saveFiles; // 保存文件映射
map<string, int> __saveDataNums; // 保存数据数量
};
3. 在MdSpi.cpp中实现相应的代码逻辑
服务器连接成功后, 会调用OnFrontConnected函数, 因此在其中做登录操作
// 当客户端与交易后台建立起通信连接时的回调函数
void MdSpi::OnFrontConnected()
{
cout << "建立网络连接成功" << endl; // 输出网络连接成功消息
// 创建登录请求数据结构
CThostFtdcReqUserLoginField loginReq;
memset(&loginReq, 0, sizeof(loginReq)); // 将结构体初始化为0
// 填充登录信息
strcpy(loginReq.BrokerID, MyBrokerID);
strcpy(loginReq.UserID, MyUserID);
strcpy(loginReq.Password, MyPassword);
strcpy(loginReq.UserProductInfo, MyUserProductInfo);
static int requestID = 0; // 请求编号
// 发送登录请求
int result = __pMdUserApi->ReqUserLogin(&loginReq, ++requestID);
if (!result)
cout << "发送登录请求成功" << endl; // 输出登录请求成功消息
else
cerr << "发送登录请求失败" << endl; // 输出登录请求失败消息
cout << "------当前版本号 :" << __pMdUserApi->GetApiVersion() << " ------" << endl; // 输出当前API版本号
}
登陆成功后, 会调用OnRspUserLogin函数, 我们可以在这里订阅行情, 订阅的行情就是你在Config.h中配置的SubscribeMarketDatas的字符串数组
// 登录请求响应的回调函数
void MdSpi::OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
bool bResult = pRspInfo && (pRspInfo->ErrorID != 0); // 判断登录是否成功
if (!bResult)
{
cout << "账户登录成功" << endl; // 输出账户登录成功消息
cout << "交易日: " << pRspUserLogin->TradingDay << endl; // 输出交易日信息
__tradingDay = pRspUserLogin->TradingDay; // 记录交易日信息
// 开始订阅行情
const char *ppInstrumentID[] = SubscribeMarketDatas; // 获取待订阅合约数组
__pMdUserApi->SubscribeMarketData((char **)ppInstrumentID, sizeof(ppInstrumentID) / sizeof(ppInstrumentID[0])); // 发送订阅行情请求
}
else
cerr << "返回错误--->>> ErrorID=" << pRspInfo->ErrorID << ", ErrorMsg=" << pRspInfo->ErrorMsg << endl; // 输出登录失败消息
}
订阅行情之后, 服务器会给我们发送信息, 然后会分别调用OnRspSubMarketData和OnRtnDepthMarketData函数, 具体调用哪一个, 取决于通信协议中服务器发送过来的信号
下面代码中checkAndCreateDir函数实现的功能是: 检查给定路径是否存在,并在不存在时尝试创建该路径所代表的目录, 与主题无关请自行实现
#ifdef PRINT_DATA是宏开关, 可以控制是否在终端打印详细数据
void MdSpi::OnRspSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
// 检查响应信息是否存在错误
bool bResult = pRspInfo && (pRspInfo->ErrorID != 0);
if (!bResult)
{
// 如果订阅成功
cout << "订阅行情成功" << endl;
cout << "合约代码: " << pSpecificInstrument->InstrumentID << endl;
// 检查是否需要存储行情数据
if (checkAndCreateDir(__savePath) && checkAndCreateDir(__savePath + "/" + pSpecificInstrument->InstrumentID))
{
// 如果需要存储数据,创建目录
cout << __savePath + "/" + pSpecificInstrument->InstrumentID + "创建成功" << endl;
}
else
{
// 创建目录失败,退出程序
cout << __savePath + "/" + pSpecificInstrument->InstrumentID + "创建失败" << endl;
exit(1);
}
// 设置保存文件的路径
__saveFiles[pSpecificInstrument->InstrumentID] = __savePath + "/" + pSpecificInstrument->InstrumentID + "/" + __tradingDay + ".csv";
// 已写入的数据数目
__saveDataNums[pSpecificInstrument->InstrumentID] = 0;
// 创建并写入文件表头
ofstream outFile;
outFile.open(__saveFiles[pSpecificInstrument->InstrumentID], std::ios::out | std::ios::binary);
outFile << "交易日,合约代码,交易所代码,最新价,数量,成交金额,持仓量,最后修改时间,最后修改毫秒,申买价一,申买量一,申卖价一,申卖量一" << endl;
outFile.close();
}
else
{
// 如果订阅失败,输出错误信息
cerr << "返回错误--->>> ErrorID=" << pRspInfo->ErrorID << ", ErrorMsg=" << pRspInfo->ErrorMsg << endl;
}
}
void MdSpi::OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData)
{
#ifdef PRINT_DATA
// 打印行情信息
cout << "=====获得深度行情=====" << endl;
cout << "交易日: " << pDepthMarketData->TradingDay << endl; // 获取交易日
cout << "交易所代码: " << pDepthMarketData->ExchangeID << endl; // 获取交易所代码
cout << "合约代码: " << pDepthMarketData->InstrumentID << endl; // 获取合约代码
cout << "合约在交易所的代码: " << pDepthMarketData->ExchangeInstID << endl; // 获取合约在交易所的代码
cout << "最新价: " << pDepthMarketData->LastPrice << endl; // 获取最新价
cout << "数量: " << pDepthMarketData->Volume << endl; // 获取数量
#endif
cout << pDepthMarketData->InstrumentID << "已采集数据量:" << ++__saveDataNums[pDepthMarketData->InstrumentID] << endl;
// 将行情写入到CSV文件中
// 如果只获取某一个合约的行情,可以逐tick地存入文件或数据库
ofstream outFile;
outFile.open(__saveFiles[pDepthMarketData->InstrumentID], ios::app); // 以追加写入方式打开文件
outFile << pDepthMarketData->TradingDay << "," // 写入交易日
<< pDepthMarketData->InstrumentID << "," // 写入合约代码
<< pDepthMarketData->ExchangeID << "," // 写入交易所代码
<< pDepthMarketData->LastPrice << "," // 写入最新价
<< pDepthMarketData->Volume << "," // 写入数量
<< pDepthMarketData->Turnover << "," // 写入成交金额
<< pDepthMarketData->OpenInterest << "," // 写入持仓量
<< pDepthMarketData->UpdateTime << "," // 写入最后修改时间
<< pDepthMarketData->UpdateMillisec << "," // 写入最后修改毫秒
<< pDepthMarketData->BidPrice1 << "," // 写入申买价一
<< pDepthMarketData->BidVolume1 << "," // 写入申买量一
<< pDepthMarketData->AskPrice1 << "," // 写入申卖价一
<< pDepthMarketData->AskVolume1 << endl; // 写入申卖量一
outFile.close(); // 关闭文件流
}
4. 最后我们在main.cpp中定义API实例和MdSpi实例, 最后编译运行即可
int main(int argc, char *argv[])
{
// 创建CTP行情API实例
CThostFtdcMdApi *pMdUserApi = CThostFtdcMdApi::CreateFtdcMdApi();
// 创建MdSpi实例,并传入行情API指针
CThostFtdcMdSpi *pMdUserSpi = argc == 2 ? new MdSpi(pMdUserApi, argv[1]) : new MdSpi(pMdUserApi);
// 将MdSpi实例注册到行情API
pMdUserApi->RegisterSpi(pMdUserSpi);
// 设置行情服务器地址
pMdUserApi->RegisterFront(URL);
// 初始化行情API
cout << "初始化行情API" << endl;
pMdUserApi->Init();
// 阻塞程序,直到行情API线程退出
pMdUserApi->Join();
// 释放MdSpi实例内存
delete pMdUserSpi;
return 0;
}
三、最终效果展示
CTP