开仓涉及到仓位大小,因此,需再创建一个输入框,用于输入手数。参考MT4自带面板,手数框也需要调整大小,故在左右两侧各添加一个“增加”、“减小”手数的调整按钮。
考虑到常规都是右手操作鼠标,右下操作相对会较方便。因此,将面板固定在右下角。同时,创建一个“显示/隐藏”此交易面板按钮。点击后隐藏面板,再次点击后显示面板。如下图示。
下面我们来说创建过程。
第一步:要在MT4上创建对象(按钮、输入框等都是对象,一个按钮就是一个对象),我们得先知道每个对象所必须的基本属性。比如,大小(想要弄多大?),坐标(放在哪里?),其上的字符文本(包括字体、字号、颜色等),背景颜色,边框颜色,以及边框样式,……等等,详细请看MQL帮助文件。下图是按钮创建示例所在的帮助文件的目录位置及相关创建过程。
把本页内容看完,你就知道创建一个按钮需要提供哪些数据了。
MT4的对象的大小以像素计数。在图表上,当图表角定义为左上开始时,从左上往右下,左上为(0,0),右下为最大(X,Y),相当于咱们学数学里的直角坐标系第4象限。如果图表角定义为右下开始,则相当于第2象限。
图表角参数,帮助文件里有。图表角定义从哪个角开始,决定该对象的坐标以哪个角为起点开始算。如下图
明白了第1步,接下来。
第二步,整体设计构想。要创建如图所示的面板。首先,计划大小。先预计宽高为200*80吧(完了以后觉得不好看,不合适,回头再调整修改数据即可)。从下往上,分3行。
第1行,设计2按钮,用于开SELL和开BUY。
第2行,4个按钮和1个输入框,第1个开SELL,第2个用于减小手数,第3个增加手数,第4个开BUY,输入框用于直接输入手数值。
第3行,3个按钮,分别用于平SELL,全平,平BUY。
最下方,创建一个符号“▼”,来处理“显示/隐藏此面板”。总共11个对象,其中10个按钮,1个编辑框。
第三步,整体构想设计好了,开始干。
打开MQL编辑器,创建“EA模板”,勾选“事件函数”(要实现点击相应按钮或输入手数等数据后执行设计的功能(开仓、平仓等),MT4规定是由事件函数负责处理,即 所有相关功能的代码必须写在“事件函数”里),如下图

当然,不勾选的话,以后,直接键入此函数或从其它地方复制过来也一样。
为了不与其它EA或在MT4直接开的单相互影响,定义一个魔术号来单独处理使用本面板的订单。
input int mMagic =88888; //订单识别号
高度分配。对面板整个3行对象的高度进行分配,第1行32,2行22,3行26,总共80。同时设计对象之间的间隙大小 。顺便考虑到适应不同电脑显示像素的适应性,增加scale校正DPI。如下
double scale=NormalizeDouble(TerminalInfoInteger(TERMINAL_SCREEN_DPI)/96.0,2);// #define CONTROLS_GAP_X ((2*scale)<2?2:(int)(2*scale)) // X坐标间隙 #define CONTROLS_GAP_Y ((2*scale)<1?2:(int)(2*scale)) // Y坐标间隙 #define BUTTON_HEIGHT ((int)(32*scale)) // #define BUTTON_HEIGHT1 ((int)(22*scale)) // #define BUTTON_HEIGHT2 ((int)(26*scale)) //
给11个对象分别起名字。随便起,只要能便于识别,程序中不同的地方用它的时候别搞错就行。下面是10个按钮名。
//---按钮事件名 string ButtonEventStr[1], ButtonEventStr0[]= { "_bid", "_ask","offon", "_xsel","_dn", "_up", "_xbuy", "_csel","_cselbuy","_cbuy" };
为了将此面板 与其它EA或指标区别开来(避免同名,造成功能错乱或误删等操作),将以本图表ID号和自定义字符(示例中为“ABC_”)为所有按钮对象名前缀。
string Prefix=""; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { Prefix=IntegerToString(ChartID())+"ABC_"; int str_size=ArraySize(ButtonEventStr0); ArrayResize(ButtonEventStr,str_size); for(int i=0; i<str_size; i++) ButtonEventStr[i]=ButtonEventStr0[i]; AddPrefix(Prefix,ButtonEventStr); //--- return(INIT_SUCCEEDED); }
//+------------------------------------------------------------------+ //| 为所有对象名称添加前缀 //+------------------------------------------------------------------+ void AddPrefix(string aprefix,string &object_names[]) { int object_size=ArraySize(object_names); for(int i=0; i<object_size; i++) { object_names[i]=aprefix+object_names[i]; } }
按钮名设计为数组,是为了在处理按钮事件时方便阅读,结构清晰。
设计起点和宽度,字号。一切准备完成。 编写CreateBtn()函数实现 创建按钮。
int X,Y,Width=0; int Fonts=9; //字号 ...... //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { ...... //---创建按钮对象 X=(int)(4*scale); //起点坐标X Y=(int)(10*scale); //起点坐标Y Width=(int)(200*scale); //宽度 ...... CreateBtn(Prefix,X,Y,Width,Fonts); //创建按钮 //--- return(INIT_SUCCEEDED); }
从第1行第1个按钮开始。在右下角同时创建“显示/隐藏面板”按钮。
bool CreateBtn(string p_fix,int x1,int y1,int width,int fonts,
ENUM_BASE_CORNER corner=CORNER_RIGHT_LOWER,
color clr =clrWhite)
{
string obj_name="";
int x=0,y=0;
int sel_w=0,dn_w=(int)(20*scale),updn_w=0,buy_w=0; //第一行宽度
//---第1行 2个按钮,开空和开多
int temp_w=(width-CONTROLS_GAP_X)/2;
x=x1+width;
y=y1+BUTTON_HEIGHT;
obj_name=p_fix+"_bid";
if(!CreateButton(obj_name,x,y,DoubleToString(Bid,Digits),fonts+5,corner,clr,temp_w,BUTTON_HEIGHT)) return(false);
ObjectSetString(0,obj_name,OBJPROP_TOOLTIP,"点击现价开仓SELL订单");
x=x1+width-(CONTROLS_GAP_X+temp_w);
obj_name=p_fix+"_ask";
if(!CreateButton(obj_name,x,y,DoubleToString(Ask,Digits),fonts+5,corner,clr,temp_w,BUTTON_HEIGHT)) return(false);
ObjectSetString(0,obj_name,OBJPROP_TOOLTIP,"点击现价开仓BUY订单");
//---右下角创建“显示/隐藏面板”按钮
obj_name=p_fix+"offon";
if(!CreateButton(obj_name,2*(int)(10*scale),y1,"▼",fonts,corner,clrLightGray,(int)(10*scale),(int)(10*scale))) return(false);
ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,clrNONE);
ObjectSetInteger(0,obj_name,OBJPROP_BORDER_COLOR,clrNONE);
ObjectSetString(0,obj_name,OBJPROP_TOOLTIP,"显示交易面板");
//---第2行
temp_w=(width-2*CONTROLS_GAP_X-2*dn_w);
sel_w=temp_w/3;
updn_w=temp_w-(2*sel_w);
y=y1+BUTTON_HEIGHT+BUTTON_HEIGHT1;
//--- SELL BID按钮
x=x1+width;
obj_name=p_fix+"_xsel";
if(!CreateButton(obj_name,x,y,"SELL",fonts,corner,clr,sel_w,BUTTON_HEIGHT1)) return(false);
ObjectSetString(0,obj_name,OBJPROP_TOOLTIP,"点击现价开仓SELL订单");
//--- "▼"按钮
x=x1+width-(CONTROLS_GAP_X+sel_w);
obj_name=p_fix+"_dn";
if(!CreateButton(obj_name,x,y,"▼",fonts,corner,clrBlack,dn_w,BUTTON_HEIGHT1)) return(false);
ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,clrLightGray);
ObjectSetInteger(0,obj_name,OBJPROP_BORDER_COLOR,clrNONE);
ObjectSetString(0,obj_name,OBJPROP_TOOLTIP,"减少手数");
//--- LOTS
x=x1+width-(CONTROLS_GAP_X+sel_w+dn_w);
obj_name=p_fix+"_edit_updn";
if(!CreateEdit(obj_name,x,y,DoubleToString(m_lots,2),fonts+2,corner,clrBlack,updn_w,BUTTON_HEIGHT1)) return(false);
//--- "▲"按钮
x=x1+width-(CONTROLS_GAP_X+sel_w+dn_w+updn_w);
obj_name=p_fix+"_up";
if(!CreateButton(obj_name,x,y,"▲",fonts,corner,clrBlack,dn_w,BUTTON_HEIGHT1)) return(false);
ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,clrLightGray);
ObjectSetInteger(0,obj_name,OBJPROP_BORDER_COLOR,clrNONE);
ObjectSetString(0,obj_name,OBJPROP_TOOLTIP,"增加手数");
//--- BUY ASK按钮
x=x1+width-(2*CONTROLS_GAP_X+sel_w+2*dn_w+updn_w);
obj_name=p_fix+"_xbuy";
if(!CreateButton(obj_name,x,y,"BUY",fonts,corner,clr,sel_w,BUTTON_HEIGHT1)) return(false);
ObjectSetString(0,obj_name,OBJPROP_TOOLTIP,"点击现价开仓BUY订单");
//---第3行
temp_w=(width-2*CONTROLS_GAP_X);
sel_w=temp_w/3;
updn_w=temp_w-(2*sel_w);
y=y1+CONTROLS_GAP_Y+BUTTON_HEIGHT+BUTTON_HEIGHT1+BUTTON_HEIGHT2;
//--- 平空
x=x1+width;
obj_name=p_fix+"_csel";
if(!CreateButton(obj_name,x,y,"平 空",fonts+2,corner,clrBlack,sel_w,BUTTON_HEIGHT2)) return(false);
ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,clrLightGray);
ObjectSetString(0,obj_name,OBJPROP_TOOLTIP,"平仓SELL订单");
//--- 全 平
x=x1+width-(CONTROLS_GAP_X+sel_w);
obj_name=p_fix+"_cselbuy";
if(!CreateButton(obj_name,x,y,"全 平",fonts+2,corner,clrBlack,updn_w,BUTTON_HEIGHT2)) return(false);
ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,clrLightGray);
ObjectSetString(0,obj_name,OBJPROP_TOOLTIP,"全平所有EA订单");
//--- 平多
x=x1+width-(2*CONTROLS_GAP_X+sel_w+updn_w);
obj_name=p_fix+"_cbuy";
if(!CreateButton(obj_name,x,y,"平 多",fonts+2,corner,clrBlack,sel_w,BUTTON_HEIGHT2)) return(false);
ObjectSetInteger(0,obj_name,OBJPROP_BGCOLOR,clrLightGray);
ObjectSetString(0,obj_name,OBJPROP_TOOLTIP,"平仓BUY订单");
return(true);
}
CreateButton()函数负责创建一个按钮对象,可以从帮助文档复制过来,对一些属性按本例的需要进行修正一下即可。 第2行,第3行按钮按同样方法创建。
至此,10个按钮和1个编辑 框创建完毕。
上述创建对象工作只需在初始化函数OnInit()里做就行。因为,这些东西只需创建一次即可,以后在行情不断变化时,无需再次创建这些按钮等,也没必要频繁检测是否创建。
第四步,按钮实现。即,例如:当点击第1行第1个按钮(左边)时,要实现开仓SELL单,……。这个事需要在OnChartEvent()函数里实现。
首先,我们先处理编辑框。当我们在此编辑框输入一个数值时,程序读取这个数值,并规范为标准格式的手数形式,保存在m_lots变量中待用。
然后,处理按钮。10个按钮,有点多,全部代码写在这感觉不好看,我们设计一个函数E_Obj_Button(sparam),将点击事件来的常量 传递过去,单独分别处理这10个按钮事件。
//+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id==CHARTEVENT_OBJECT_ENDEDIT) { string spm=Prefix+"_edit_updn"; //我们的编辑框 if(sparam==spm)//当用户修改了此编辑框数据 { string text=""; if(!ObjectGetString(0,spm,OBJPROP_TEXT,0,text)) return; //获取编辑框字符,如果获取失败则返回 m_lots=NormalizeDouble(StrToDouble(text),mdlots); //规范为平台标准化手数格式并保存 if(m_lots<mminlot) m_lots=mminlot; //小于平台规定的最小手数,按最小手数算 if(m_lots>mmaxlot) m_lots=mmaxlot; //大了,按最大手数算 ObjectSetString(0,spm,OBJPROP_TEXT,DoubleToString(m_lots,mdlots)); //重新显示在面板中 } } //---按钮事件 if(id==CHARTEVENT_OBJECT_CLICK) { E_Obj_Button(sparam); //按钮事件处理函数 } }
E_Obj_Button(sparam) 函数 实现如下
//+-------------------------------------------------------------------------------------+ //| 处理按钮事件 | //+-------------------------------------------------------------------------------------+ void E_Obj_Button(string spm) { int size=ArraySize(ButtonEventStr); int e=0; while(e<size) { if(ButtonEventStr[e]==spm) break; e++; } int iticket=0; double ilots=0.0; switch(e) { case 0:// //开空 ilots=NormalizeDouble(m_lots,mdlots); iticket=OrderSend(Symbol(),OP_SELL,ilots,Bid,3,0,0,"MyEA_Btn",mMagic,0,clrRed); if(iticket<=0) Print("开sell单失败。错误 #",GetLastError()); ObjectSetInteger(0,spm,OBJPROP_STATE,false); return ; break; case 1:// //开多1 ilots=NormalizeDouble(m_lots,mdlots); iticket=OrderSend(Symbol(),OP_BUY,ilots,Ask,3,0,0,"MyEA_Btn",mMagic,0,clrBlue); if(iticket<=0) Print("开buy单失败。错误 #",GetLastError()); ObjectSetInteger(0,spm,OBJPROP_STATE,false); return ; break; case 2:// if(ObjectGetInteger(0,spm,OBJPROP_STATE)==true) { HideObject(Prefix+"_"); ObjectSetInteger(0,spm,OBJPROP_STATE,true); } else{ ShowObject(Prefix+"_"); ObjectSetInteger(0,spm,OBJPROP_STATE,false); } break; case 3:// //开空 ilots=NormalizeDouble(m_lots,mdlots); iticket=OrderSend(Symbol(),OP_SELL,ilots,Bid,3,0,0,"MyEA_Btn",mMagic,0,clrRed); if(iticket<=0) Print("开sell单失败。错误 #",GetLastError()); ObjectSetInteger(0,spm,OBJPROP_STATE,false); return ; break; case 4://减少手数 ilots=m_lots-mstep; if(ilots<mminlot) ilots=mminlot; ObjectSetString(0,Prefix+"_edit_updn",OBJPROP_TEXT,DoubleToString(ilots,mdlots)); m_lots=ilots; ObjectSetInteger(0,spm,OBJPROP_STATE,false); break; case 5://增加手数 ilots=m_lots+mstep; if(ilots>mmaxlot) ilots=mmaxlot; ObjectSetString(0,Prefix+"_edit_updn",OBJPROP_TEXT,DoubleToString(ilots,mdlots)); m_lots=ilots; //保存 ObjectSetInteger(0,spm,OBJPROP_STATE,false); break; case 6:// //开多 ilots=NormalizeDouble(m_lots,mdlots); iticket=OrderSend(Symbol(),OP_BUY,ilots,Ask,3,0,0,"MyEA_Btn",mMagic,0,clrBlue); if(iticket<=0) Print("开buy单失败。错误 #",GetLastError()); ObjectSetInteger(0,spm,OBJPROP_STATE,false); return ; break; case 7: // //平空 if(!CloseOrderAll(OP_SELL,mMagic,"用户关闭所有sell单")) Print("关闭所有SELL订单失败。Err:",GetLastError()); Sleep(100); ObjectSetInteger(0,spm,OBJPROP_STATE,false); return ; break; case 8: //全部平仓 if(!CloseOrderAll(-1,mMagic,"用户关闭所有EA单")) Print("关闭所有EA订单失败。Err:",GetLastError()); Sleep(100); ObjectSetInteger(0,spm,OBJPROP_STATE,false); return ; break; case 9: //平 多 if(!CloseOrderAll(OP_BUY,mMagic,"用户关闭所有buy单")) Print("关闭所有SELL订单失败。Err:",GetLastError()); Sleep(100); ObjectSetInteger(0,spm,OBJPROP_STATE,false); return ; break; } }
第五步,参考MT4自带面板形式,当行情价格上涨或下跌时,开仓按钮呈红、蓝色变化。这个当然得在主函数OnTick()里处理了。我们用一个函数RefPanelBidAsk()来处理它。全部代码一行一行写在这也行,只是如果主函数还有其它功能实现代码,全弄在这有些不耐读。
第六步,去初始化。EA退出或切换图表周期 时,删除咱们所创建的所有相关对象。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- DeleteAll(Prefix);//删除所有相关对象 } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //---刷新面板实时Bid/Ask价格 RefPanelBidAsk(); //--- RefOrders(); }
删除函数如下
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void DeleteAll(string prefix) { ObjectsDeleteAll(0,prefix,0,OBJ_BUTTON); ObjectsDeleteAll(0,prefix,0,OBJ_EDIT); }
RefPanelBidAsk() 函数实现刷新面板实时价格,并根据价格变化显示不同的颜色。
//+------------------------------------------------------------------+ //|刷新面板实时价格 | //+------------------------------------------------------------------+ void RefPanelBidAsk() { if(Bid>=StrToDouble(ObjectGetString(0,Prefix+"_bid",OBJPROP_TEXT,0))) { ObjectSetInteger(0,Prefix+"_bid",OBJPROP_BGCOLOR,clrMediumBlue) ; ObjectSetInteger(0,Prefix+"_xsel",OBJPROP_BGCOLOR,clrMediumBlue) ; } else { ObjectSetInteger(0,Prefix+"_bid",OBJPROP_BGCOLOR,clrCrimson) ; ObjectSetInteger(0,Prefix+"_xsel",OBJPROP_BGCOLOR,clrCrimson) ; } ObjectSetString(0,Prefix+"_bid",OBJPROP_TEXT,DoubleToString(Bid,Digits)); if(Ask>=StrToDouble(ObjectGetString(0,Prefix+"_ask",OBJPROP_TEXT,0))) { ObjectSetInteger(0,Prefix+"_ask",OBJPROP_BGCOLOR,clrMediumBlue) ; ObjectSetInteger(0,Prefix+"_xbuy",OBJPROP_BGCOLOR,clrMediumBlue) ; } else { ObjectSetInteger(0,Prefix+"_ask",OBJPROP_BGCOLOR,clrCrimson) ; ObjectSetInteger(0,Prefix+"_xbuy",OBJPROP_BGCOLOR,clrCrimson) ; } ObjectSetString(0,Prefix+"_ask",OBJPROP_TEXT,DoubleToString(Ask,Digits)); }
按设计,当点击“显示/隐藏交易面板”按钮时,要相应隐藏或显示面板。使用ShowObject()和HideObject()实现。
//+------------------------------------------------------------------+ //|设置对象可见 | //+------------------------------------------------------------------+ void ShowObject(string pfix,ENUM_OBJECT objtype=-1) { int objtotal=ObjectsTotal(); string objname=""; for(int i=objtotal-1;i>=0;i--) { objname=ObjectName(0,i); if(StringFind(objname,pfix)>=0 && (objtype==-1 || (objtype!=-1 && ObjectType(objname)==objtype))) { ObjectSetInteger(0,objname,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS); } } } //+------------------------------------------------------------------+ //|设置对象不可见 | //+------------------------------------------------------------------+ void HideObject(string pfix) { int objtotal=ObjectsTotal(); string objname=""; for(int i=objtotal-1;i>=0;i--) { objname=ObjectName(0,i); if(StringFind(objname,pfix)>=0) { ObjectSetInteger(0,objname,OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS); } } }
至此,所有工作完成 。
有兴趣的欢迎讨论交流。完整源码文件 于附件中,可直接使用。
结论 实现批量开平仓
…如果将订单魔术号置0,则将包含在图表上手动开仓单。

这段代码是一个多周期 EMA 指标,同时带有均线汇聚信号提示。它计算了多个不同周期的 EMA,然后检查这些 EMA 是否汇聚,并根据汇聚情况生成交易信号。

优雅而时髦的双值 "价格 "比较。