本例子演示在买一价往上10个跳的价格上挂空单, 挂单后10秒后撤单。
与外汇交易不同,为了防止盘口愰骗,各交易所都将频繁撤单列入异常交易管理规范请注意。

EA是通过调用mt5ctp.dll进行期货交易,所以EA需要先引用mt5ctp.dll,该DLL的头文件mt5ctp.mqh在\MQL5\Include目录下。
#property copyright "www.wewin28.com 1145412@qq.com"
#property link "http://www.wewin28.com"
#property version "1.1"
#include <mt5ctp.mqh>
对于非主连合约如rb2209可以通过Symbol()属性获得当前图表的合约,但对于主连合约如rb9999,则需要通过SYMBOL_ISIN属性获得主连合约现在对应的合约(rb2301),当主连合约rb9999随着时间发生换月后,通过SYMBOL_ISIN属性得到的合约就会自动变为rb2305。使用后者的写法可以让EA在主连合约和非主连合约的图表上都能获得当前图表的合约。
通过SYMBOL_EXCHANGE属性获得该合约对应的交易所, 如获得rb2301所属的交易所SHFE即上期所。
通过SYMBOL_TRADE_TICK_SIZE属性获得该合约每一跳的大小,如螺纹钢是1,股指期货是0.2。
string symbol=SymbolInfoString(Symbol(), SYMBOL_ISIN); //获取合约(如果是主力合约的话取对应的合约)
string exchange=SymbolInfoString(symbol, SYMBOL_EXCHANGE); //该品种的交易所
double tickSize=SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); //该品种每跳大小 tick size
定义了几个全局变量:
orderSysId是用来保存CTP挂单回调中的CTP订单编号,这个变量在之后的撤单中需要使用。
eaOrderRef是EA报单编号,作用类似外汇EA的魔术号码。
placeOrderDateTime是记录挂单成功的时间,该时间过了10秒后会撤单。
orders是记录当前挂单的数量。
splitCommma是代表逗号。
string orderSysId=""; //交易所订单编号 CTP order Id
long eaOrderRef=0; //EA报单编号 magic number
datetime placeOrderDateTime=D'1970.01.01 00:00'; //挂单时间 place order datetime
int orders=0; //挂单数量 how many ea orders
ushort splitCommma=StringGetCharacter(",",0);
如果当前没有挂单,就发送报单:
先获得最新买一价,将报单价格定位买一价+10个跳,报单手数1手。
调用mt5ctp.dll getOrderRefCTP函数生成EA报单编号并保存在eaOrderRef变量,期货交易所对报单编号格式有规定,不能像外汇EA的魔术号码可随意自行指定, 否则报单会失败,所以EA需先调用getOrderRefCTP函数生成EA报单编号再报单。
调用mt5ctp.dll sendOrderLimit函数发送限价报单,其中第一个参数是合约,第二个参数是mt5ctp.mqh头文件中定义的枚举ENUM_CTP_SELL_ORDER(代表空单),第三个参数是mt5ctp.mqh头文件中定义的枚举ENUM_CTP_OPEN_POSITION(代表开仓),第四个参数是报单的价格,第五个参数是报单的手数,第六个参数是EA报单编号,第七个参数是mt5ctp.mqh头文件中定义的枚举ENUM_CTP_ACCOUNT_SPECULATION(代表开户的期货账号是投机)。
void OnTick()
{
//---
if(orders==0) //如果没有就挂单
{
double ask=getAsk(); //取得买1价
double price=ask+10*tickSize; 买1价+10个跳
int vol=1; //手数 quantity
eaOrderRef=getOrderRefCTP(); //生成EA报单编号 generate EA magic number
int res=sendOrderLimit(symbol, ENUM_CTP_SELL_ORDER, ENUM_CTP_OPEN_POSITION, price, vol, (string)eaOrderRef, ENUM_CTP_ACCOUNT_SPECULATION); //买1价+10个跳价格挂空单 place limit order at ask+10 tick size
//0 代表本地报单成功 local ok
//-1 表示网络连接失败 network disconect
//-2,表示未处理请求超过许可数 request too much
//-3,表示每秒发送请求数超过许可数 request too fast within one second
if(res!=0)
{
printf("sendOrderLimit error %d", 1);
}
}
sendOrderLimit函数调用后会同步返回本地电脑发送报单请求的结果,0是本地电脑向交易所成功发送报单,-1是网络连接失败,-2是未处理请求超过许可数,-3是每秒发送请求数超过许可数。但即使sendOrderLimit函数返回0(只是完成了下图中1和2),也不代表已经成功在交易所挂单,还需要等待交易所异步返回对EA报单的撮合结果即报单回调和成交回调(即下图3)。这与外汇交易有不同,更多请看MT5Future:MT5 EA交易期货2-期货与外汇交易机制。
MT5通过mt5ctp.dll得到交易所对该报单的处理结果(即报单回调),然后把报单回调作为MQL图表事件发送给打开的全部图表,EA 通过MQL图表事件响应函数OnChartEvent得到这些回调(即下图4)。

MQL期货图表事件的ID都是3000,EA需要在OnChartEvent函数中只处理ID是3000的事件。
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
if(id==3000) //只处理MT5 CTP的事件 process MT5 CTP events only
{
报单回调是一个字符串,格式如下:
OnRtnOrder, 交易所, 合约, EA报单编号, CTP报单编号, 多空, 开平, 报单状态枚举, 报单状态信息, 报单价格, 报单手数, 成交手数, 报单时间, 撤单时间,FrontID,SessionID,e
EA通过OnChartEvent函数的sparam参数获得图表事件中的交易所报单回调。把sparam对应的字符串按逗号拆分后保存到字符数组chartEvents[]。因为在OnChartEvent函数中ID是3000的事件包括了各种的交易所回调(如报单回调,成交回调,撤单回调,错误回调,仓位回调,资金回调), 所以EA需要先根据chartEvents[0]="OnRtnOrder"筛选出其中的报单回调。
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
if(id==3000) //只处理MT5 CTP的事件 process MT5 CTP events only
{
string chartEvents[];
int n=StringSplit(sparam,splitCommma,chartEvents); //CTP成交回调中的各个数据用逗号分隔
if(n>0)
{
string eventType=chartEvents[0];
if(eventType=="OnRtnOrder") //CTP报单回调 send order callback
{
chartEvents数组的第4个元素是EA报单编号。如果有多个EA同时在不同的图表上运行并报单,本图表的OnChartEvent也会接收到其他图表上的EA产生的报单回调,之前EA已经生成并记录了本次报单编号在eaOrderRef变量中,所以只有报单回调中的EA报单编号等于eaOrderRef变量才是本EA的报单回调(作用类似于外汇EA中的魔术号码)。
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
if(id==3000) //只处理MT5 CTP的事件 process MT5 CTP events only
{
string chartEvents[];
int n=StringSplit(sparam,splitCommma,chartEvents); //CTP成交回调中的各个数据用逗号分隔
if(n>0)
{
string eventType=chartEvents[0];
if(eventType=="OnRtnOrder") //CTP报单回调 send order callback
{
printf(sparam);
string orderRef=chartEvents[3]; //CTP报单回调中的报单编号 ea order id in callback
if(orderRef==(string)eaOrderRef) //CTP报单回调中的订单编号是本EA的报单编号 is my ea order
{
EA获得报单回调中的CTP根据这次报单生成的服务器端编号,,多或空,开仓或平仓,获得交易所对这次报单的撮合结果。
当交易所撮合结果是3(未成交还在队列中)或 4(未成交不在队列中),就是挂单成功了,记录当前挂单的数量为1,并记录成功挂单的时间。
当交易所撮合结果是5(撤单),就是撤单成功了。
if(orderRef==(string)eaOrderRef) //CTP报单回调中的订单编号是本EA的报单编号 is my ea order
{
orderSysId=chartEvents[4]; //CTP报单回调中的交易所订单编号 exchange order id in callback
string buySell=chartEvents[5]; //多/空 buy/sell
string combOffsetFlag=chartEvents[6]; //开/平 open/close
/*
orderStatus 枚举 enum:
0 = 全部成交 all filled
1 = 部分成交还在队列中 part filled in the queue
2 = 部分成交不在队列中 part filled not in the queue
3 = 未成交还在队列中 not yet fill in the queue
4 = 未成交不在队列中 not yet fill not in the queue
5 = 撤单 canceled
'a' = 未知 unknown
'b' = 尚未触发 not touched
'c' = 已触发 touched
*/
string orderStatus=chartEvents[7]; //报单状态 exchange order status
if(orderStatus=="3" || orderStatus=="4") //该报单已经挂单还没全部成交 my ea order not yet fill and pending
{
orders=1;
placeOrderDateTime=TimeCurrent();
printf("挂单成功 pending order successfully");
}
if(orderStatus=="5") //该报单撤单 canceled
{
orders=0;
printf("撤单成功 cancel order successfully");
}
string statusMsg=chartEvents[8]; //报单状态信息 exchange order status description
printf("my ea order %s", orderSysId);
}
挂单成功10秒后会撤销这个挂单,调用mt5ctp.dll的cancelOrderCTP函数进行撤单,其中第一个参数是该合约属于的交易所,第二个参数是合约,第三个参数是CTP根据之前报单生成的服务器端编号
cancelOrderCTP函数调用后会同步返回本地电脑处理撤单请求的结果,0是本地向交易所成功发送撤单,-1是网络连接失败,-2是未处理请求超过许可数,-3是表示每秒发送请求数超过许可数。但即使cancelOrderCTP函数返回0(只是完成了1和2),也不代表已经成功在交易所撤单,还需要等待交易所异步返回对撤单的处理结果即撤单回调(即下图3),MT5通过mt5ctp.dll得到交易所的撤单回调,并把撤单回调作为MQL图表事件发送给全部图表,EA 通过MQL图表事件响应函数OnChartEvent得到这些回调(即下图4)。如何取得和处理撤单回调与上面处理报单回调同理。
if(orders>0) //10秒后撤单 cancel order in 10 seconds
{
if(TimeCurrent()-placeOrderDateTime>10)
{
printf("10秒后撤单 cancel my order in 10 seconds");
int res=cancelOrderCTP(exchange, symbol, orderSysId);
//0 代表报单成功 send order ok
//-1 表示网络连接失败 network disconect
//-2,表示未处理请求超过许可数 request too much
//-3,表示每秒发送请求数超过许可数 request too fast within one second
if(res!=0)
{
printf("cancelOrderCTP error %s", res);
}
}
}
