在Linux系统下用C++实现CTP接收存储合tick行情

本文详细介绍了如何使用C++编程语言配合CThostFtdc库实现期货交易系统的MDAPI接口,包括配置文件编写、MdSpi类的实现、登录与行情订阅过程,以及数据处理和文件保存方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、准备工作

  1. 确保你有相应的库文件以及头文件, 没有的可以在这个SimNow仿真交易【官方网站】下载
  2. 确保有接口手册, 这能提高开发效率, ::::::上海期货信息技术有限公司::::::提供了相应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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值