该例子演示了EA如何报期货市价单开仓,开仓后10秒钟报市价单平仓。
在外汇交易里没有市价单,而期货交易有市价单,
市价单报单时不指定价格,让交易所自行成交,就是做多无论多贵都买,做空无论多便宜都卖,不管价格高低只求成交。
比方说日内交易者在市场收盘前会报市价单平掉当天仓位, 又或者当仓位亏损后报市价单平仓止损。
市价单不是每个期货交易所都支持,对于不支持的交易所我们可以用涨停跌停价格报限价单实现市价单效果。
模拟SIMNOW不支持市价单。
EA是通过调用mt5ctp.dll进行期货交易,所以EA需要先引用mt5ctp.dll的头文件mt5ctp.mqh,该头文件在\MQL5\Include目录下。
#property copyright "www.wewin28.com 1145412@qq.com"
#property link "http://www.wewin28.com"
#property version "1.2"
#include <mt5ctp.mqh> //引用MT5CTP头文件
EA获取当前图表的合约。对于非主连合约如rb2209可以通过Symbol()属性获得当前图表的合约,但对于主连合约如rb9999,则需要通过SYMBOL_ISIN属性获得主连合约现在对应的合约(rb2301),当主连合约rb9999随着时间发生换月后,通过SYMBOL_ISIN属性得到的合约就会自动变为rb2305。使用后者的写法可以让EA在主连合约和非主连合约的图表上都能获得当前图表的合约。
通过SYMBOL_EXCHANGE属性获得该合约对应的交易所, 如获得rb2301所属的交易所SHFE即上期所。
通过SYMBOL_DIGITS属性获得该合约报价的小数后位数,如螺纹钢是0,股指是1。
splitCommma是代表逗号。
string symbol=SymbolInfoString(Symbol(), SYMBOL_ISIN); //获取合约(如果是主力合约的话取对应的合约)
string exchange=SymbolInfoString(Symbol(), SYMBOL_EXCHANGE); //该品种的交易所
long digits=SymbolInfoInteger(symbol, SYMBOL_DIGITS); //该品种价格的小数位数 get decimal
ushort splitCommma=StringGetCharacter(",",0);
定义报单时间全局变量entryTime,用来判断是否已经报单。
定义仓位全局变量,如果pos等于0就是空仓,如果大于0就是持仓。
eaOrderRef是EA报单编号,作用类似外汇EA的魔术号码。
datetime entryTime=D'1970.01.01 00:00'; //报单时间全局变量
int pos=0; //EA仓位全局变量
long eaOrderRef=0; //EA报单编号全局变量
如果还没报单还是空仓,就报单手数1手。
调用mt5ctp.dll getOrderRefCTP接口函数生成EA报单编号并保存在eaOrderRef变量,CTP对报单编号格式有规定,不能像外汇EA的魔术号码可随意自行指定, 否则报单会失败,所以EA需先调用getOrderRefCTP函数生成EA报单编号再报单。
上期所、能源中心和中金所不支持市价单,所以如果是这些交易所的合约用涨停价报限价单实现市价单功能。
通过SYMBOL_SESSION_PRICE_LIMIT_MAX属性获得合约的涨停价。
大商所和郑商所支持市价单,如果是这些交易所的合约调用mt5ctp.dll sendOrderMarket接口函数报市价单。
调用mt5ctp.dll sendOrderLimit接口函数用涨停价报限价单,其中第一个参数是合约,第二个参数是mt5ctp.mqh头文件中定义的枚举ENUM_CTP_BUY_ORDER(代表多单),第三个参数是mt5ctp.mqh头文件中定义的枚举ENUM_CTP_OPEN_POSITION(代表开仓),第四个参数是报单的价格(涨停价),第五个参数是报单的手数,第六个参数是EA报单编号,第七个参数是mt5ctp.mqh头文件中定义的枚举ENUM_CTP_ACCOUNT_SPECULATION(代表开户的期货账号是投机)。
void OnTick(){if(entryTime==D'1970.01.01 00:00'){if(pos==0) //空仓 { int vol=1; //手数 quantity eaOrderRef=getOrderRefCTP(); //生成ea报单编号 generate ea magic numberif(exchange=="SHFE" || exchange=="INE" || exchange=="CFFEX"){double priceLimitMax=SymbolInfoDouble(symbol, SYMBOL_SESSION_PRICE_LIMIT_MAX);int res=sendOrderLimit(symbol, ENUM_CTP_BUY_ORDER, ENUM_CTP_OPEN_POSITION, priceLimitMax, vol, (string)eaOrderRef, ENUM_CTP_ACCOUNT_SPECULATION); //发涨停价限价单多开 send buy limit order with daily max price//0 代表报单成功 local send order ok //-1 表示网络连接失败 network disconect//-2,表示未处理请求超过许可数 request too much //-3,表示每秒发送请求数超过许可数 request too fast in one secondif(res!=0){printf("sendOrderLimit error %s", res);} }else{int res=sendOrderMarket(symbol, ENUM_CTP_BUY_ORDER, ENUM_CTP_OPEN_POSITION, vol, (string)eaOrderRef, ENUM_CTP_ACCOUNT_SPECULATION); //市价单多开 market order buyif(res!=0){printf("sendOrderMarket error %s", res);} }} }
sendOrderLimit函数调用后会同步返回本地电脑发送报单请求的结果,0是本地电脑向交易所成功发送报单,-1是网络连接失败,-2是未处理请求超过许可数,-3是每秒发送请求数超过许可数。但即使sendOrderLimit函数返回0(只是完成了下图中的1和2),也不代表已经成功在交易所挂单,还需要等待交易所异步返回对EA报单的撮合结果即报单回调和成交回调(下图中的3)。MT5通过mt5ctp.dll得到交易所的各种回调(如报单回调,成交回调,撤单回调,错误回调,仓位回调,资金回调),之后会把这些回调作为图表事件发送给全部图表,EA 通过MQL图表事件响应函数OnChartEvent得到这些回调(即下图中的4)。
成交10秒之后报市价单平仓,调用mt5ctp.dll getOrderRefCTP接口函数生成EA报单编号并保存在eaOrderRef变量,CTP对报单编号格式有规定,不能像外汇EA的魔术号码可随意自行指定, 否则报单会失败,所以EA需先调用getOrderRefCTP函数生成EA报单编号再报单。
上期所、能源中心和中金所没有市价单,所以这些交易所的合约用跌停价报限价单实现市价单功能。
通过SYMBOL_SESSION_PRICE_LIMIT_MIN属性获得当前跌停价。
用跌停价报空单(平多仓)。
大商所和郑商所支持市价单,如果是这些交易所的合约报市价单平仓。
if(pos!=0) //持仓10秒后市价平仓 close position in 10 seconds by marking order{if(TimeCurrent()-entryTime>10) //10秒后平仓 close position in 10 seconds { eaOrderRef=getOrderRefCTP(); //生成ea报单编号 generate ea order magic numberif(exchange=="SHFE" || exchange=="INE" || exchange=="CFFEX"){double priceLimitMin=SymbolInfoDouble(symbol, SYMBOL_SESSION_PRICE_LIMIT_MIN);int res=sendOrderLimit(symbol, ENUM_CTP_SELL_ORDER, ENUM_CTP_CLOSE_POSITION, priceLimitMin, pos, (string)eaOrderRef, ENUM_CTP_ACCOUNT_SPECULATION); //发跌停价限价单空平 send sell limit order with daily min priceif(res!=0){printf("sendOrderLimit error %s", res);} }else{int res=sendOrderMarket(symbol, ENUM_CTP_SELL_ORDER, ENUM_CTP_CLOSE_POSITION, pos, (string)eaOrderRef, ENUM_CTP_ACCOUNT_SPECULATION); //市价单空平 market order sellif(res!=0){printf("sendOrderMarket error %s", res);} } } }
EA报单后通过mt5ctp.dll得到交易所对该报单撮合的结果即报单回调,然后mt5ctp.dll会把该报单回调作为图表事件发送给全部图表,EA 通过MQL图表事件响应函数OnChartEvent得到该报单的结果。
MT5期货发出的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 event only{
EA通过OnChartEvent函数的sparam参数获得图表事件中的交易所报单回调。
报单回调是一个字符串,格式如下:
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 event only{ string chartEvents[]; int n=StringSplit(sparam,splitCommma,chartEvents); //CTP成交回调中的各个数据用逗号分隔 callback string split by commaif(n>0){string eventType=chartEvents[0]; if(eventType=="OnRtnOrder") //CTP报单回调 send order callback{
获得报单回调中的EA报单编号。
chartEvents数组的第4个元素是EA报单编号。如果有多个EA同时在不同的图表上运行并报单,本图表的OnChartEvent也会接收到其他图表上的EA产生的报单回调,在第40行已经生成并记录了EA报单编号在eaOrderRef变量中,所以只有报单回调中的EA报单编号等于eaOrderRef变量才是本EA的报单回调(作用类似于外汇EA中的魔术号码)。
获得报单回调中的CTP报单编号、报单做多或做空、报单开仓或平仓、报单的价格、报单的手数,报单的成交手数,报单时间,报单的撤单时间和交易所对这次报单的撮合结果。
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{if(id==3000) //只处理MT5 CTP的事件 process MT5 CTP event only{ string chartEvents[]; int n=StringSplit(sparam,splitCommma,chartEvents); //CTP成交回调中的各个数据用逗号分隔 callback string split by commaif(n>0){string eventType=chartEvents[0]; if(eventType=="OnRtnOrder") //CTP报单回调 send order callback{printf(sparam);//string symbol=chartEvents[2]; string orderRef=chartEvents[3]; //EA报单编号(魔术号码) ea magic numberif(orderRef==(string)eaOrderRef) //是本EA的报单 is my ea order{ string orderSysId=chartEvents[4]; //CTP服务器报单编号 CTP order Id string buySellEnum=chartEvents[5]; //多空 buy sellstring combOffsetFlag=chartEvents[6]; //开平 open close string orderStatusEnum=chartEvents[7]; //报单状态枚举 string statusMsg=chartEvents[8]; //报单状态信息double price=(double)chartEvents[9]; //报单价格 string priceWithDigital=DoubleToString(price, (int)digits);string vol=chartEvents[10]; //报单手数string fillVol=chartEvents[11]; //成交手数 string orderTime=chartEvents[12]; //报单时间 string orderCancelTime=chartEvents[13]; //撤单时间}}
EA报单后交易所除了发送报单回调以外,如果成交了紧接着还会发送成交回调,然后mt5ctp.dll会把该报单回调作为图表事件发送给全部图表,EA 通过MQL图表事件响应函数OnChartEvent得到该报单的成交回调。
成交回调是一个字符串,格式如下:
OnRtnTrade, 交易所, 合约, EA报单编号, CTP报单编号, 成交编号, 多空, 开平, 成交价格, 成交手数, 成交时间,e
chartEvents数组的第4个元素是EA报单编号。如果有多个EA同时在不同的图表上运行并报单,本图表的OnChartEvent也会接收到其他图表上的EA报单后产生的成交回调,在第38行eaOrderRef变量已经记录了本次的报单编号,所以成交回调中的EA报单编号等于该变量才是本EA的成交回调(作用类似于外汇EA中的魔术号码)。
遍历chartEvents数组,取出成交回调中的交易所、合约、CTP报单编号、CTP成交编号、报单做多或是做空,报单开仓或者平仓,报单成交价格,报单成交手数,报单成交时间, 并计算EA仓位。
if(eventType=="OnRtnTrade") //CTP成交回调 get filled event{printf(sparam);string orderRef=chartEvents[3]; //EA报单编号 magicif(orderRef==(string)eaOrderRef) //如果CTP成交回调中的EA报单编号是本EA的报单编号就记录仓位 my EA order get filled{string exchange2=chartEvents[1]; //CTP成交回调中的交易所 exchangestring symbol2=chartEvents[2]; //CTP成交回调中的合约 instrument of callbackstring orderSysId=chartEvents[4]; //CTP成交回调中的CTP报单编号string fillId=chartEvents[5]; //CTP成交回调中的成交编号 filled Idstring buySellEnum=chartEvents[6]; //CTP成交回调中的多/空 buy/sell string openCloseEnum=chartEvents[7]; //CTP成交回调中的开平 0=开仓 1=平仓 2=强平 3=平今 4=平昨 5=强减 6=本地强平 double price=(double)chartEvents[8]; //成交价格 filled price string buySell="";string vol=chartEvents[9]; //成交手数 filled quantityif(buySellEnum=="0") //0=多 1=空 {pos+=(int)vol;}else {pos-=(int)vol;} string tradeTime=chartEvents[10]; //成交时间 filled datetime}}