本系列是程序化交易与实现的入门。
本文主要实现了通过CTP接口读取期货行情数据,实现的语言是C++,IDE是VS2017.
需要的头问件、库文件均来自CTP接口的公开文件ThostTradeApi。
Api文档来自于公开的CTP接口文档。
新建项目——Windows控制台应用程序
在vs2017中,stdafx.h变成了pch.h文件,这对于程序的运行没有影响。
首先新建头文件Spi.h,用于继承ThostFtdcMdApi.h中的CThostFtdMdSpi函数。同时,定义一系列虚函数,用于连接行情服务器、订阅行情以及获得市场深度数据。此外,还定义了时间戳类TimeStamp,在存储深度行情序列的时候,可以用来标明时间戳。
#pragma once
#include <string.h>
#include ".\ThostFtdcMdApi.h"
#include ".\ThostFtdcUserApiStruct.h"
//继承CThostFtdcMdSpi, 此处先定义虚函数, 在Spi.cpp中实现
class CMdSpi : public CThostFtdcMdSpi
{
public:
//错误应答
virtual void OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast);
//当客户端与交易后台通信连接被断开后,该方法被调用。发生此情况后,API会自动重新连接
virtual void OnFrontDisconnected(int Reason);
void OnHeartBeatWarning(int nTimeLapse);
//心跳超时警告。当长时间未接收到报文时,调用此方法
virtual void OnFrontConnected();
//登录请求响应
virtual void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast);
//订阅行情应答
virtual void OnRspSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast);
//取消订阅行情应答
virtual void OnRspUnSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast);
//深度行情通知
virtual void OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData);
private:
void ReqUserLogin();
void SubscribeMarketData();
bool IsErrorRspInfo(CThostFtdcRspInfoField *pRspInfo);
};
//时间戳类, 用于存储返回的时间
class TimeStamp {
public:
//在实际返回的数据中,ts的的是类似于11:02:13这样的时间格式UpdateTime, 而mesc只会取两个值0或者500的UpdateMillic
char ts[9];
int mesc;
char help[7];
TimeStamp(char* ts, int mesc) {
strcpy_s(this->ts, 9, ts);
int j = 0;
for (int i = 0; i < 9; i++) {
if (ts[i] == ':') {
continue;
}
else {
help[j] = ts[i];
j++;
}
}
if (mesc == 0) {
help[6] = '0';
}
else {
help[6] = '5';
}
this->mesc = mesc;
};
bool operator < (const TimeStamp &other) const {
//strcmp(str1, str2), 比较两个字符串, str1=str2: 返回0; str1>str2: 返回正数; str1<str2: 返回负数.
//即两个字符串自左向右逐个字符比较(按ASCII值大小进行比较), 直到遇见不同的字符或者遇见'\0'为止.
int c = strcmp(other.ts, ts);
//当other.ts>ts时, 也就是str1表示的时间在str2后面, 比str2晚, 那么c>0
//而实际比较中, ts1 < ts2, 表示左边的ts1早于ts2, 因为按照逻辑, ts2才是other.ts
if (c > 0) {
return true;
}
//如果秒数相同, 那么也是str1的最后修改毫秒数更大的话, 就返回true, 那么也就是str1代表更迟的时刻, 就返回true
else if (c == 0 && other.mesc > mesc) {
return true;
}
//如果完全相同, 或者str2 > str1, 那么就返回false
return false;
}
};
有了头文件之后,可以开始定义具体的执行程序Spi.cpp,在其中将具体定义了头文件中的虚函数。
#include "pch.h"
#include "Spi.h"
#include <iostream>
#include ".\ThostFtdcMdApi.h"
#include <vector>
#include <map>
using namespace std;
//User_Api的配置参数
extern CThostFtdcMdApi *pUserApi;
//配置参数
extern char FRONT_ADDR[];
extern TThostFtdcBrokerIDType BROKER_ID;
extern TThostFtdcInvestorIDType INVESTOR_ID;
extern TThostFtdcPasswordType PASSWORD;
//这是一个指向字符型的指针数组, 它的长度是可变的
extern const char* ppInstrumentID[];
extern int iInstrumentID;
extern map<string, CThostFtdcDepthMarketDataField> g_mapQuotes;
//设置一个timeStamp, 可以依照InstrumentID+timeStamp查找到某一合约某一时刻的数据.
//设置vector, 存储深度行情数据
//设置TimeStamp, 存储时间数据
extern TimeStamp ts;
//extern vector<CThostFtdcDepthMarketDataField*> series;
//extern map<int, map<TimeStamp, vector<CThostFtdcDepthMarketDataField>>> instrument_depth_data;
//请求编号
extern int iRequestID;
bool CMdSpi::IsErrorRspInfo(CThostFtdcRspInfoField *pRspInfo)
{
//如果ErrorID != 0, 说明收到了错误的响应
bool bResult = ((pRspInfo) && (pRspInfo->ErrorID != 0));
if (bResult)
{
cerr << "-->>ErrorID = " << pRspInfo->ErrorID << ", ErrorMsg =" << pRspInfo->ErrorMsg << endl;
}
return bResult;
}
void CMdSpi::OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
//cout: Standard input/output stream
//cerr: Error output stream, mainly for wrong output
//clog: Standard error output stream
cerr << "-- >>> " << __FUNCTION__ << endl;
IsErrorRspInfo(pRspInfo);
}
void CMdSpi::OnFrontDisconnected(int nReason)
{
cerr << "--->>> " << __FUNCTION__ << endl;
cerr << "--->>> Reason = " << nReason << endl;
}
void CMdSpi::OnHeartBeatWarning(int nTimeLapse)
{
cerr << "--->>> " << __FUNCTION__ << endl;
cerr << "--->>> nTimerLapse = " << nTimeLapse << endl;
}
void CMdSpi::ReqUserLogin()
{
CThostFtdcReqUserLoginField req;
//memset用于将某一块内存中的内容全部设置为指定的值。
//如将指针req指向的内存地址,其后的sizeof(req)大小都设置为0
memset(&req, 0, sizeof(req));
strcpy_s(req.BrokerID, BROKER_ID);
strcpy_s(req.UserID, INVESTOR_ID);
strcpy_s(req.Password, PASSWORD);
int iResult = pUserApi->ReqUserLogin(&req, ++iRequestID);
//以下用到了一种三目运算符,如: x<0?y=10:z=20, 问号前面的表达式是要被测试的表达式。
//如果正确,就执行第一个语句, 如果错误, 就执行第二个语句
cerr << "-->>发送用户登录请求:" << ((iRequestID == 0) ? "成功" : "失败") << endl;
}
void CMdSpi::OnFrontConnected()
{
cerr << "--->>>" << __FUNCTION__ << endl;
//用户登录请求
ReqUserLogin();
}
void CMdSpi::SubscribeMarketData()
{
int iResult = pUserApi->SubscribeMarketData(ppInstrumentID, iInstrumentID);
cerr << "--->>> 发送行情订阅请求: " << ((iResult == 0) ? "成功" : "失败") << endl;
}
void CMdSpi::OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
cerr << "--->>> " << __FUNCTION__ << endl;
if (bIsLast && !IsErrorRspInfo(pRspInfo))
{
///获取当前交易日
cerr << "--->>> 获取当前交易日 = " << pUserApi->GetTradingDay() << endl;
// 请求订阅行情
SubscribeMarketData();
}
}
void CMdSpi::OnRspSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
//cerr << __FUNCTION__ << endl;
//cout << pSpecificInstrument->InstrumentID << endl;
return;
}
void CMdSpi::OnRspUnSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
//cerr << __FUNCTION__ << endl;
return;
}
void CMdSpi::OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData)
{
//map的基本功能是迭代器
/*
g_mapQuotes[pDepthMarketData->InstrumentID] = *pDepthMarketData;
map<string, CThostFtdcDepthMarketDataField>::iterator iter;
for (int i = 0; i < iInstrumentID; i++) :
{
if (pDepthMarketData->InstrumentID[0] == ppInstrumentID[i][0] &&
pDepthMarketData->InstrumentID[1] == ppInstrumentID[i][1] &&
strcmp(pDepthMarketData->InstrumentID, ppInstrumentID[i] != 0)
{
iter = g_mapQuotes.find(ppInstrumentID[i]);
iter != g_mapQuotes.end();
if (strcmp(pDepthMarketData->InstrumentID, ppInstrumentID[i]) > 0)
{
cout<<pDepthMarketData->InstrumentID<<"Last:"<<iter->first.c_str()<<"最新"<<pDepthMarketData->LastPrice
<<iter->first.c_str()<<"卖一"<<iter.first
cout
<< pDepthMarketData->InstrumentID << "\t"
<< pDepthMarketData->ClosePrice << "\t"
<< pDepthMarketData->AskPrice1 << "\t"
<< pDepthMarketData->AskVolume1 << "\t"
<< pDepthMarketData->BidPrice1 << "\t"
<< pDepthMarketData->BidVolume1 << "\t"
<< pDepthMarketData->UpdateTime
<< pDepthMarketData->TradingDay << endl;
}
}
}
*/
cout
<< pDepthMarketData->InstrumentID << "\t"
<< pDepthMarketData->ClosePrice << "\t"
<< pDepthMarketData->AskPrice1 << "\t"
<< pDepthMarketData->AskVolume1 << "\t"
<< pDepthMarketData->BidPrice1 << "\t"
<< pDepthMarketData->BidVolume1 << "\t"
<< pDepthMarketData->UpdateTime
<< pDepthMarketData->TradingDay << endl;
}
头文件定义完之后,可以开始执行main函数,读取期货行情。
#include "pch.h"
#include <vector>
#include <map>
#include <iostream>
#include "Spi.h"
#include ".\ThostFtdcMdApi.h"
using namespace std;
// UserApi对象
CThostFtdcMdApi* pUserApi;
// 配置参数
char FRONT_ADDR[] = "XXXXXXX";
TThostFtdcBrokerIDType BROKER_ID = "XXXXX";
TThostFtdcInvestorIDType INVESTOR_ID = "XXXXXXXXXXX";
TThostFtdcPasswordType PASSWORD = "XXXXXXXXXXXXXXXXXX";
const char *ppInstrumentID[] =
{ "IF1903", "IF1904", "IF1906", "IF1909",
"IH1903", "IH1904", "IH1906", "IH1909",
"IC1903", "IC1904", "IC1906", "IC1909"};
int iInstrumentID = 12;
int iRequestID = 0;
int main()
{
// 初始化UserApi
pUserApi = CThostFtdcMdApi::CreateFtdcMdApi();
CThostFtdcMdSpi* pUserSpi = new CMdSpi();
pUserApi->RegisterSpi(pUserSpi);
pUserApi->RegisterFront(FRONT_ADDR);
pUserApi->Init();
pUserApi->Join();
// pUserApi->Release();
}
至此,已经可以读取期货行情了,本程序读取的是IF/IH/IC的1903,1904,1906,1909合约的行情数据。
结果如下:
因为是周末,所以没有任何数据。