1.开发环境
qt5.9.9 msvc2017环境(mingw不能运行) 以及自带的Qt Creator,上期提供的穿透式api,并且导入qcustomplot库以实现图表效果。
本程序借鉴了b站视频:BV1ET4y1L7iN。
2.效果展示
实现了行情,委托,成交,持仓,合约,等表的展示,以及用户的登录和下单等操作。注:登录部分数据固定,可以自行拓展,可以通过下载快期交易平台来查看自己的下单等操作。程序运行后,当行情表没有数据,请使用另一个可以使用的端口登录本程序(在快期交易平台可以查看)。
3.代码简介
引用:综合交易平台(Comprehensive Transaction Platform,CTP)是专门为期货公司开发的一套期货经纪业务管理系统,由交易、风险控制和结算三大系统组成。导入上期提供的api接口后,分别对行情,委托,成交,持仓,合约来进行实现。
CTP 的所有接口都分为 Spi 和 Api 两种,这里对其简单说明
API:Api 类提供了交易/行情的各种功能,但这些需要我们主动对服务器发出的请求。
SPI:Spi 类提供了交易/行情相关的回调接口,我们需要继承该类并重载这些接口,以获取响应数据。
api可以理解为是上期为我们提供的已经封装好的一类函数,我们只需直接调用这些函数,大部分无需重写,即可使用该函数功能,而spi体现了一种回调的思想,当我们的api调用后,spi会返回一些东西,我们可以通过这些返回值来判断api执行是否正常。
举个例子,当我们需要登录行情的时候,我们需要使用RspUserLogin函数,当该函数执行完毕后,会有与之对应的OnRspUserLogin函数被自动调用,我们可以利用其中的返回值来判断登录是否成功。
要实现行情部分,我们需要创建一个类来继承CThostFtdcMdSpi并且重写部分函数。
举个例子,当客户端与交易后台进行通讯连接时,我们可以对OnFrontConnected重写来监听连接是否成功。
同时实现行情的登录。接下来,我们通过对OnRtnDepthMarketData 的重写来实现行情信息的获取,由于涉及到多个类之间的信息通信,我们在获取到数据后将其拼接为一个由 , 隔开的字符串并且写一个带有字符串参数的信号sendData来传输数据,在主类中获取并且打印在表单上。
交易部分则复杂一些。不过还是让我们从客户端登录做起,照葫芦画瓢,我们重写了tradeapi中OnFrontConnected 函数,来在信号连 接的时候就实现其登录,以下的userid也就是注册时得到的investorID可以修改为您自己的investorID。
要实现个人账户的登录,我们在登录按钮上写一个槽函数,将正确的账户密码写上去,登陆成功。
在登录后,程序会立马获取合约,持仓,账户等信息,三者的写法不会有很大区别,让我们以合约为例子来讲一下。
我们需要重写OnRspQryInstrument函数,并且使用多个字符串来接受pInstrument中的数据,接下来我们将其拼接为一个字符串,同上,由于涉及到多个类之间的信息通信,我们在获取到数据后将其拼接为一个由 , 隔开的字符串并且写一个带有字符串参数的信号sendDataHy来传输数据,同时,我们也要将这些数据存储在一个两重字符串数组中以便之后使用,最后,在主类中分割字符串并将其写入表中。
合约,持仓,账户表单操作差异不大,学会了合约之后,就可以举一反三了。我们来到很重要的一步,下单以及撤单,请继续阅读以下内容。
要实现这一功能,我们需要自行写一个函数并且主动调用它了,我们找到ReqOrderInsert函数,发现这个函数可以用于下单,于是我们可以自己定义一个同名的ReqOrderInsert函数,但是它拥有参数以便我们调用它。
void CTraderSpi::ReqOrderInsert(QString dm,QString lx,int lots,double price)
{
CThostFtdcInputOrderField ord ;
memset(&ord, 0, sizeof(ord));
strcpy_s(ord.BrokerID, "9999");
strcpy_s(ord.InvestorID, "204925");
strcpy_s(ord.ExchangeID, "SHFE");
strcpy_s(ord.InstrumentID,dm.toStdString().data() );
sprintf(ORDER_REF,"%d",iRequestID);
strcpy_s(ord.OrderRef,ORDER_REF );
ord.OrderPriceType = THOST_FTDC_OPT_LimitPrice;//限价
if(lx =="kd"){
ord.Direction = THOST_FTDC_D_Buy;//买 DIRECTION
ord.CombOffsetFlag[0] = THOST_FTDC_OF_Open;//开
}
else if(lx =="pd"){
ord.Direction = THOST_FTDC_D_Buy;//买 DIRECTION
ord.CombOffsetFlag[0] = THOST_FTDC_OF_CloseToday;//开
}
else if(lx =="kk"){
ord.Direction = THOST_FTDC_D_Sell;//买 DIRECTION
ord.CombOffsetFlag[0] = THOST_FTDC_OF_Open;//开
}
else if(lx =="pk"){
ord.Direction = THOST_FTDC_D_Sell;//买 DIRECTION
ord.CombOffsetFlag[0] = THOST_FTDC_OF_CloseToday;//开
}
ord.CombHedgeFlag[0] = THOST_FTDC_HF_Speculation;//投机
ord.LimitPrice = price;
ord.VolumeTotalOriginal = lots;
ord.TimeCondition = THOST_FTDC_TC_GFD;///当日有效
ord.VolumeCondition = THOST_FTDC_VC_AV;///任意数量
ord.MinVolume = 1;
ord.ContingentCondition = THOST_FTDC_CC_Immediately;
ord.StopPrice = 0;
ord.ForceCloseReason = THOST_FTDC_FCC_NotForceClose;
ord.IsAutoSuspend = 0;
pUserApi->ReqOrderInsert(&ord, ++iRequestID);
};
函数看起来非常之长,但是其中很多都是比较固定的值,对于我们新手来说,复制粘贴就完事了,参数lx是用来区分是开多,平多,还是开空,平空的,作为非金融专业的我也不是很懂这些,大家可以自行查找以进行拓展,我们将ord数据写好后调用以ord为参数的ReqOrderInsert,即可实现下单。
再执行下单后,委托会出现数据,如果是处于还未成交的状况下,我们可以实现撤单功能,那么在委托的表单中,我们就需要实现 点击后识别点击的一栏并且出现一个撤单选项,点击撤单我们就可以进行撤单操作。
代码如下:
void MainWindow::OnWTmenu(const QPoint &pt)
{
qDebug()<<"onwtmenu";
QMenu menu;
menu.addAction(ui->actioncd);
menu.exec(ui->tableWT_2->mapToGlobal(pt));
}
void MainWindow::ct()
{
if(ui->tableWT_2->rowCount()==0)return;
int i = ui->tableWT_2->currentIndex().row();
QString wth = ui->tableWT_2->item(i,7)->text();
QString jys = ui->tableWT_2->item(i,8)->text();
QString brokerid = "9999";
qDebug()<<wth;
qDebug()<<jys;
if(wth == "")return;
ptdUserSpi->ReqOrderAction(brokerid,wth,jys);
}
撤单相较来说简单一点,我们获取到点击的行数后,通过行数来获得委托号以及交易所,最后加上brokerID即可调用撤单,撤单成功后,委托表中其状态会变为以撤单,同时,你也可以登录快期交易平台来查看交易是否撤单成功。
4.代码获取
账号注册: sinmow (有的时间段可能登录不上)。
gitee: QTctp: QT简单实现ctp程序 。
推荐阅读文章 :CTP 学习笔记_EmoryHuang的博客-CSDN博客