正如您可能知道的,对于任何交易,都强烈建议遵循资金管理规则。这意味着不建议进入一个可以损失超过 N% 存款的交易,
N 是由交易者选择的。为了符合这一规则,人们应该正确地计算出交易手数的值。
在相关的课程中,演示者通常会显示一个现成的 Excel 文件,其中包括每个交易品种的相关手数计算公式,因此,他们通过“简单地输入”其止损值来获得所需的手数,
这真的那么“简单”吗?手数计算操作可能需要一分钟或更长时间,因此,当你最终确定手数时,价格可能会远离预期的切入点。此外,这需要您执行额外的操作。这种方法的另一个缺点是,手工计算常常增加出错的机会。
所以让我们试着让这个过程真正简单,为此,我们将创建一个EA交易,在可视化模式下设置建仓价和止损价。根据这些参数和您的风险值,EA将设置适当的手数,并在相关方向上打开一个头寸。
我们已经概述了第一项任务,
我们的 EA 交易将执行的另一项任务是根据所选的 TP 与 SL 的比率设定获利价格。
Gerchik 和其他成功的交易者建议使用获利,这至少是你止损的3倍。也就是说,如果你用 40 个点的止损分,那么获利至少应该是 120 个点。如果价格没有太多机会达到这个水平,你最好不要进入这个交易。
为了更方便的统计计算,最好使用相同的SL/TP比率。例如,使用 TP/SL比率为3:1、4:1等进场交易。你应该根据你的交易目标为自己选择具体的比率。
问题是:如何在不浪费时间的情况下实现盈利?Excel是唯一可能的解决方案吗?或者我们应该猜一下图表上的大概位置吗?
这种功能将在 EA 交易设置中实现。将提供一个特殊参数,用于入场交易的获利与止损之比。例如,数值为 4 表示比率为4 比 1,然后,EA将设定4倍于止损值的获利。
EA 交易的形式. 在进行EA开发之前,我们需要决定它的运行模式。这可能是最困难的任务,
我使用类似的EA已经一年多了,自从EA出现以来,它已经发生了很大的变化。
在其第一个版本中,EA启动后显示了一个对话框窗口:它允许更改任何新仓位的开启参数,这些设置被自动保存,并在进一步的EA启动期间使用。这就是这种方法的主要优点,通常,您只需配置一次所需的设置,并在将来继续使用它们。
然而,带有设置的对话框窗口有一个很大的缺点:它几乎占据了整个图表窗口空间。由于此窗口的存在,无法跟踪价格变动。由于第一次设置后不再使用对话框窗口,因此该窗口会妨碍图表视图,而不会提供任何好处。
下面是一个640像素的图表和对话框窗口示例:
图1. 带对话窗口的 EA 交易版本
如您所见,窗口甚至不能完全适应屏幕,
为了解决这个问题,我又创建了两个EA版本。
在第一个版本中,“设置”窗口在默认情况下是隐藏的,可以通过单击设置按钮打开。此EA仍在MetaTrader 5市场上提供。
第二个版本没有任何设置窗口,所有EA设置都在输入参数中给出。这样就不需要使用对话框窗口。但是,这会导致下面的一个问题:
作为程序员,我希望您选择第三个选项。在本文中,我们将创建第二个EA版本的简化克隆,下面是EA操作的样子:
图2. 不带对话框的 EA 交易版本
为了更深入地了解整个工作范围,让我们看看EA输入参数:
图3. EA 交易的输入参数
交易活动将由止损控制,因此,让我们注意前两个参数:" Stop Loss type" (止损类型) 和 "Stop Loss value in $ or %" (以钱数或者百分比表示的止损值)。
这个值默认以美元设置,在 "Stop Loss type" 参数中指定。止损也可以设置为余额的百分比。如果选择止损百分比,则指定值不能超过存款的5%。这样做是为了避免设置EA参数时出错。
参数 "Stop Loss value in $ or %" 表示止损时您可以承受的损失金额。
Stop Loss value in cents (keys 7 and 8)(以分表示的止损值 (按键 7 和 8)). 另一个止损相关的参数是: "Stop Loss value in cents (keys 7 and 8)".
接下来,让我们实现一组快捷键,使用它我们可以通过单击设置所需的停止损失。例如,如果您在交易中总是使用7分的止损,那么您只需要按键盘上的7键,止损将设置为与当前价格的指定距离。我们稍后将对此进行详细讨论。
请注意,此参数不确定止损时您将损失的金额,而是开盘价与止损触发价之间的距离。
No deal entry if with min. lot the risk is greater than specified.(如果使用最小手数风险还是大于指定的,就不交易) 因为交易入场手数是根据 " Stop Loss value in $ or %" 参数自动计算的, 当最低允许手数会导致每笔交易的风险高于规定值时,可能会出现这种情况。
在这种情况下,您可以按照最小手数交易并忽略风险,也可以取消开展交易。此参数中定义了适当的行为。
默认情况下,如果风险增加,EA会阻止进入市场。
Cancel limit order after, hours.(取消限价订单的小时数)除了进入市场外,EA还支持根据指定价格下限价订单。此参数允许限制订单寿命,
寿命以小时为单位。例如,如果寿命设置为2小时,并且限制订单在2小时内未触发,则应删除该订单。
Tale Profit multiplier.(获利倍数)如果您的交易具有特定的获利与止损比率,此参数可用于根据您的规则配置自动获利设置,
默认值为4,这意味着,应将获利设定在这样一个价格上,即获利时的利润将是可能亏损的4倍。
其它参数. 您还可以:
由于我们正在编写一个跨平台专家顾问,它应该在MetaTrader 4和MetaTrader 5中都有效。但是,不同的EA版本具有不同的开启仓位函数,为了在两个平台中启用EA操作,我们将使用条件编译。
我在文章中已经提到了这种编译,例如,在文章 开发一个跨平台网格EA中。
简而言之,条件编译代码如下:
#ifdef __MQL5__ //MQL5 代码 #else //MQL4 代码 在画线之前#endif
在本文中,我们将使用条件编译的可能性3次,其中涉及到仓位开启函数。其余代码可以在MetaTrader 4和MetaTrader 5中使用。
在 开发一个跨平台网格EA 文章中,仓位开启函数已经开发过了,因此,让我们使用现成的解决方案。我们需要添加它的功能以设置限价订单寿命:
// 仓位开启函数中可能的订单类型 enum TypeOfPos { MY_BUY, MY_SELL, MY_BUYSTOP, MY_BUYLIMIT, MY_SELLSTOP, MY_SELLLIMIT, MY_BUYSLTP, MY_SELLSLTP, }; // 为 MT5 选择交易执行类型 #ifdef __MQL5__ enum TypeOfFilling //交易执行类型 { FOK,//ORDER_FILLING_FOK RETURN,// ORDER_FILLING_RETURN IOC,//ORDER_FILLING_IOC }; input TypeOfFilling useORDER_FILLING_RETURN=FOK; //订单执行模式 在画线之前#endif /* 开仓或限价下单函数 */ bool pdxSendOrder(TypeOfPos mytype, double price, double sl, double tp, double volume, ulong position=0, string comment="", string sym="", datetime expiration=0){ if( !StringLen(sym) ){ sym=_Symbol; } int curDigits=(int) SymbolInfoInteger(sym, SYMBOL_DIGITS); if(sl>0){ sl=NormalizeDouble(sl,curDigits); } if(tp>0){ tp=NormalizeDouble(tp,curDigits); } if(price>0){ price=NormalizeDouble(price,curDigits); }else{ #ifdef __MQL5__ #else MqlTick latest_price; SymbolInfoTick(sym,latest_price); if( mytype == MY_SELL ){ price=latest_price.ask; }else if( mytype == MY_BUY ){ price=latest_price.bid; } 在画线之前#endif } #ifdef __MQL5__ ENUM_TRADE_REQUEST_ACTIONS action=TRADE_ACTION_DEAL; ENUM_ORDER_TYPE type=ORDER_TYPE_BUY; switch(mytype){ case MY_BUY: action=TRADE_ACTION_DEAL; type=ORDER_TYPE_BUY; break; case MY_BUYSLTP: action=TRADE_ACTION_SLTP; type=ORDER_TYPE_BUY; break; case MY_BUYSTOP: action=TRADE_ACTION_PENDING; type=ORDER_TYPE_BUY_STOP; break; case MY_BUYLIMIT: action=TRADE_ACTION_PENDING; type=ORDER_TYPE_BUY_LIMIT; break; case MY_SELL: action=TRADE_ACTION_DEAL; type=ORDER_TYPE_SELL; break; case MY_SELLSLTP: action=TRADE_ACTION_SLTP; type=ORDER_TYPE_SELL; break; case MY_SELLSTOP: action=TRADE_ACTION_PENDING; type=ORDER_TYPE_SELL_STOP; break; case MY_SELLLIMIT: action=TRADE_ACTION_PENDING; type=ORDER_TYPE_SELL_LIMIT; break; } MqlTradeRequest mrequest; MqlTradeResult mresult; ZeroMemory(mrequest); mrequest.action = action; mrequest.sl = sl; mrequest.tp = tp; mrequest.symbol = sym; if(expiration>0){ mrequest.type_time = ORDER_TIME_SPECIFIED_DAY; mrequest.expiration = expiration; } if(position>0){ mrequest.position = position; } if(StringLen(comment)){ mrequest.comment=comment; } if(action!=TRADE_ACTION_SLTP){ if(price>0){ mrequest.price = price; } if(volume>0){ mrequest.volume = volume; } mrequest.type = type; mrequest.magic = EA_Magic; switch(useORDER_FILLING_RETURN){ case FOK: mrequest.type_filling = ORDER_FILLING_FOK; break; case RETURN: mrequest.type_filling = ORDER_FILLING_RETURN; break; case IOC: mrequest.type_filling = ORDER_FILLING_IOC; break; } mrequest.deviation=100; } if(OrderSend(mrequest,mresult)){ if(mresult.retcode==10009 || mresult.retcode==10008){ if(action!=TRADE_ACTION_SLTP){ switch(type){ case ORDER_TYPE_BUY: // Alert("Order Buy #:",mresult.order," sl",sl," tp",tp," p",price," !!"); break; case ORDER_TYPE_SELL: // Alert("Order Sell #:",mresult.order," sl",sl," tp",tp," p",price," !!"); break; } }else{ // Alert("Order Modify SL #:",mresult.order," sl",sl," tp",tp," !!"); } return true; }else{ msgErr(GetLastError(), mresult.retcode); } } #else int type=OP_BUY; switch(mytype){ case MY_BUY: type=OP_BUY; break; case MY_BUYSTOP: type=OP_BUYSTOP; break; case MY_BUYLIMIT: type=OP_BUYLIMIT; break; case MY_SELL: type=OP_SELL; break; case MY_SELLSTOP: type=OP_SELLSTOP; break; case MY_SELLLIMIT: type=OP_SELLLIMIT; break; } if(OrderSend(sym, type, volume, price, 100, sl, tp, comment, EA_Magic, expiration)<0){ msgErr(GetLastError()); }else{ switch(type){ case OP_BUY: Alert("Order Buy sl",sl," tp",tp," p",price," !!"); break; case OP_SELL: Alert("Order Sell sl",sl," tp",tp," p",price," !!"); break; } return true; } 在画线之前#endif return false; }
在MetaTrader 5中,打开仓位时应选择仓位执行类型。所以为 MetaTrader 5 另外增加一个输入参数: " Order fill mode".
不同的经纪商支持不同的订单执行类型。经纪商中最常用的一种是 ORDER_FILLING_FOK. 因此默认选中。如果代理不支持此模式,则可以选择任何其他所需的模式。
另一种机制(前面在开发跨平台网格EA中描述)是本地化EA交易生成的文本消息的功能,因此,我们不再探讨。有关EA操作格式的更多详细信息,请阅读上述文章。
在本文中,我们不会从头开始考虑EA开发,我们还假定读者至少具有基本的MQL知识。
在本文中,我们将研究如何实现主要的EA部分,这将帮助您在必要时实现其他功能。
让我们从EA界面开始。
将在EA启动时创建以下界面元素:
因此,要创建具有所需参数(使用EA输入设置)的订单,必须将红线移到应设置止损的价格。
如果红线高于当前价格,则打开空头头寸。如果红线低于当前价格,则打开一个多头头寸。
打开仓位的交易量将自动计算,以便在止损的情况下,您将损失接近EA参数中指定的数量。你所要做的就是点击仓位打开按钮,之后将以市场价格开仓。
如果要下限价订单,应另外单击“Show(0)open price line”,并将出现的绿线移动到要打开限价订单的价格。限额订单类型和方向将根据止损和获利线的位置自动确定(止损高于或低于开盘价)。
基本上,无需单击“Show(0)open price line”按钮,一旦您将红色止损线移动到所需的水平,按钮将自动按下,一个绿色的开仓价格线将出现。如果移动此行,它将设置限价订单,如果你把线保留在它的位置上,一个市场仓位将被打开。
我们分析了工作原理,现在我们可以继续编程了。
操作图表注释. 是使用标准的 Comment 函数来操作图表注释的,因此,我们需要准备一个字符串,该字符串将通过Comment函数显示在图表上。为此, 让我们创建一个自定义函数 getmespread:
/* 在图表注释中显示点差和交易结束时间的数据 */ void getmespread(){ string msg=""; // 取得交易品种的点差 curSpread=lastme.ask-lastme.bid; // 如果市场没有关闭,显示点差信息 if( !isClosed ){ if(curSpread>0){ StringAdd(msg, langs.Label1_spread+": "+(string) DoubleToString(curSpread, (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS))+" "+currencyS+" ("+DoubleToString(curSpread/curPoint, 0)+langs.lbl_point+")"); StringAdd(msg, "; "+DoubleToString(((curSpread)/lastme.bid)*100, 3)+"%"); }else{ StringAdd(msg, langs.Label1_spread+": "+langs.lblNo); } StringAdd(msg, "; "); } // 如果我们可以确定的话,显示市场收盘时间 if(StringLen(time_info)){ StringAdd(msg, " "+time_info); } Comment(msg); }
getmespread 将在 EA 初始化 (OnInit) 以及每一新报价 (OnTick) 时调用。
在 getmespread 中, 我们使用了5个全局 EA 变量: lastme, isClosed, time_info, currencyS, curPoint.
lastme 变量保存 Ask, Bid 和 Last 报价的数据,变量的内容在 OnInit 和 OnTick 函数中使用下面的指令更新:
SymbolInfoTick(_Symbol,lastme);
其它变量在 OnInit 函数中初始化,isClosed 和 time_info 是如下初始化的:
isClosed=false; // 取得当前日期 TimeToStruct(TimeCurrent(), curDay); // 取得今日的交易品种交易时间 if(SymbolInfoSessionTrade(_Symbol, (ENUM_DAY_OF_WEEK) curDay.day_of_week, 0, dfrom, dto)){ time_info=""; TimeToStruct(dto, curEndTime); TimeToStruct(dfrom, curStartTime); isEndTime=true; string tmpmsg=""; tmp_val=curEndTime.hour; if(tmp_val<10){ StringAdd(tmpmsg, "0"); } StringAdd(tmpmsg, (string) tmp_val+":"); tmp_val=curEndTime.min; if(tmp_val<10){ StringAdd(tmpmsg, "0"); } StringAdd(tmpmsg, (string) tmp_val); if(curEndTime.hour==curDay.hour){ if(tmp_val>curDay.min){ }else{ isClosed=true; } }else{ if(curEndTime.hour==0){ }else{ if( curEndTime.hour>1 && (curDay.hour>curEndTime.hour || curDay.hour==0)){ StringAdd(time_info, " ("+langs.lbl_close+")"); isClosed=true; }else if(curDay.hour<curStartTime.hour ){ StringAdd(time_info, " ("+langs.lbl_close+")"); isEndTime=false; isClosed=true; }else if(curDay.hour==curStartTime.hour && curDay.min<curStartTime.min ){ StringAdd(time_info, " ("+langs.lbl_close+")"); isEndTime=false; isClosed=true; } } } if(isEndTime){ StringAdd(time_info, langs.lblshow_TIME+": "+tmpmsg+time_info); }else{ StringAdd(time_info, langs.lblshow_TIME2+": "+tmpmsg+time_info); } }
currencyS变量将存储用于当前金融工具利润计算的货币,货币可以:
currencyS=SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT);
交易品种的点值大小将保存在 curPoint 变量中:
curPoint=SymbolInfoDouble(_Symbol, SYMBOL_POINT);
止损线. EA启动时,图表上只显示一条线:放置止损的红线。
与按钮类似,这条线将在OnInit函数中绘制。在画线之前,我们需要检查图表上是否已经有这样一条线,如果有这条线,不要创建新线和其他UI元素。相反,将以下数据添加到全局变量:当前行级别的价格和交易未结行的价格(如果图表上有任何价格):
// 如果图表上有止损和开仓价格线 // 把价格加到全局变量中 if(ObjectFind(0, exprefix+"_stop")>=0){ draw_stop=ObjectGetDouble(0, exprefix+"_stop", OBJPROP_PRICE); if(ObjectFind(0, exprefix+"_open")>=0){ draw_open=ObjectGetDouble(0, exprefix+"_open", OBJPROP_PRICE); } // 否则创建整个 EA 交易的 UI }else{ draw_open=lastme.bid; draw_stop=draw_open-(SymbolInfoInteger(_Symbol, SYMBOL_SPREAD)*curPoint); ObjectCreate(0, exprefix+"_stop", OBJ_HLINE, 0, 0, draw_stop); ObjectSetInteger(0,exprefix+"_stop",OBJPROP_SELECTABLE,1); ObjectSetInteger(0,exprefix+"_stop",OBJPROP_SELECTED,1); ObjectSetInteger(0,exprefix+"_stop",OBJPROP_STYLE,STYLE_DASHDOTDOT); ObjectSetInteger(0,exprefix+"_stop",OBJPROP_ANCHOR,ANCHOR_TOP); // 其它界面元素 }
一般来说,图表是否可能已经有了UI元素?
如果您不实现代码,它在关闭EA时删除所有创建的用户界面元素,那么相应的元素肯定会留在图表上。即使实现了此代码,也可能在EA操作中发生错误,因此EA将关闭,但创建的元素将保留在图表上。因此,在创建图表之前,一定要检查图表上是否存在这样的元素。
因此我们创造了一条红线,此外,我们将其设置为默认选中。因此,您不需要双击线来选择它。你只需要把它转移到你想要的价格。但是,如果你现在移动红线,什么都不会发生,执行适当操作的代码尚未实现。
与UI元素的任何交互都是在标准OnChartEvent函数中执行的。移动界面元素会生成ID为 CHARTEVENT_OBJECT_DRAG 的事件,. 这样,为了在图表上实现移动,OnChartEvent函数必须截获此事件,检查哪个元素调用了它,如果该元素属于EA,则应执行所需的代码:
void OnChartEvent(const int id, // 事件 ID const long& lparam, // 长整型的事件参数 const double& dparam, // 双精度浮点型的事件参数 const string& sparam) // 字符串类型的事件参数 { switch(id){ case CHARTEVENT_OBJECT_DRAG: if(sparam==exprefix+"_stop"){ setstopbyline(); showOpenLine(); ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_STATE, true); } break; } }
移动红线后,将启动setstopbyline函数,该函数将记住未来订单的止损水平:
/* “记住”未来订单的止损等级 */ void setstopbyline(){ // 取得止损线所在的价格 double curprice=ObjectGetDouble(0, exprefix+"_stop", OBJPROP_PRICE); // 如果价格与EA发布时止损线所在的价格不同, if( curprice>0 && curprice != draw_stop ){ double tmp_double=SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if( tmp_double>0 && tmp_double!=1 ){ if(tmp_double<1){ resval=DoubleToString(curprice/tmp_double, 8); if( StringFind(resval, ".00000000")>0 ){}else{ curprice=MathFloor(curprice)+MathFloor((curprice-MathFloor(curprice))/tmp_double)*tmp_double; } }else{ if( MathMod(curprice,tmp_double) ){ curprice= MathFloor(curprice/tmp_double)*tmp_double; } } } draw_stop=STOPLOSS_PRICE=curprice; updatebuttontext(); ChartRedraw(0); } }
除了setstopbyline函数外,移动红线会导致图表上开仓价格线的外观(showOpenLine函数)和“show(0)open price line按钮状态的更改。
开仓线和按钮. "Show (0) open price line" 按钮也是在 EA 初始化过程中创建的:
if(ObjectFind(0, exprefix+"_openbtn")<0){ ObjectCreate(0, exprefix+"_openbtn", OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_XDISTANCE,0); ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_YDISTANCE,33); ObjectSetString(0,exprefix+"_openbtn",OBJPROP_TEXT, langs.btnShowOpenLine); ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_XSIZE,333); ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_FONTSIZE, 8); ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_YSIZE,25); }
如上所述,与界面元素的任何交互都是在标准OnChartEvent函数中处理的。这包括按按钮,ID 为 CHARTEVENT_OBJECT_CLICK 的事件就是负责它的,我们需要拦截这个事件,检查事件源并执行适当的操作。为此,让我们把额外的 case 加到 OnChartEvent 函数的 switch 操作符中:
case CHARTEVENT_OBJECT_CLICK: if (sparam==exprefix+"_openbtn"){ updateOpenLine(); } break;
updateOpenLine 函数会在点击按钮 "Show (0) open price line" 时调用, 它是主 showOpenLine 函数的一个小的封装,此函数只在图表中显示开仓价格:
void showOpenLine(){ if(ObjectFind(0, exprefix+"_open")<0){ draw_open=lastme.bid; ObjectCreate(0, exprefix+"_open", OBJ_HLINE, 0, 0, draw_open); ObjectSetInteger(0,exprefix+"_open",OBJPROP_SELECTABLE,1); ObjectSetInteger(0,exprefix+"_open",OBJPROP_SELECTED,1); ObjectSetInteger(0,exprefix+"_open",OBJPROP_STYLE,STYLE_DASHDOTDOT); ObjectSetInteger(0,exprefix+"_open",OBJPROP_ANCHOR,ANCHOR_TOP); ObjectSetInteger(0,exprefix+"_open",OBJPROP_COLOR,clrGreen); } }
现在我们需要重写 CHARTEVENT_OBJECT_DRAG 事件处理函数,这样就能对止损线和开仓价格线的移动做出回应:
case CHARTEVENT_OBJECT_DRAG: if(sparam==exprefix+"_stop"){ setstopbyline(); showOpenLine(); ObjectSetInteger(0,exprefix+"_openbtn",OBJPROP_STATE, true); }else if(sparam==exprefix+"_open"){ curprice=ObjectGetDouble(0, exprefix+"_open", OBJPROP_PRICE); if( curprice>0 && curprice != draw_open ){ double tmp_double=SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if( tmp_double>0 && tmp_double!=1 ){ if(tmp_double<1){ resval=DoubleToString(curprice/tmp_double, 8); if( StringFind(resval, ".00000000")>0 ){}else{ curprice=MathFloor(curprice)+MathFloor((curprice-MathFloor(curprice))/tmp_double)*tmp_double; } }else{ if( MathMod(curprice,tmp_double) ){ curprice= MathFloor(curprice/tmp_double)*tmp_double; } } } draw_open=open=OPEN_PRICE=curprice; updatebuttontext(); ObjectSetString(0,exprefix+"Edit3",OBJPROP_TEXT,0, (string) NormalizeDouble(draw_open, _Digits)); ChartRedraw(0); } } break;
获利线. 除了红绿线,我们还需要实现一条虚线。当您将红色止损线移动到所需的价格水平后,它将出现。虚线将显示交易获利水平的价格:
图4. 止损、开仓和获利线
仓位开启按钮. 仓位开启按钮的画法和 "Show (0) open price line" 按钮类似,
在点击这个按钮的时候会生成 CHARTEVENT_OBJECT_CLICK 事件,这个事件的处理之前已经探讨过,按下了仓位开启按钮之后,就会运行 startPosition 函数:
case CHARTEVENT_OBJECT_CLICK: if (sparam==exprefix+"_send"){ startPosition(); }else if (sparam==exprefix+"_openbtn"){ updateOpenLine(); } break;
EA操作完成后删除UI元素. 完成 EA 交易的操作后,不要忘记正确删除界面元素。如果不这样做,所有元素都将留在图表上。
在EA操作完成期间执行任何命令,请将其添加到标准OnDeInit函数:
void OnDeinit(const int reason) { if(reason!=REASON_CHARTCHANGE){ ObjectsDeleteAll(0, exprefix); Comment(""); } }
reason 变量包含有关完成EA操作的原因的信息。现在对我们来说唯一重要的原因是事件框架的变化 ( REASON_CHARTCHANGE). 默认情况下,时间框架的更改会导致EA操作完成并重新启动,这对我们来说不是最可接受的行为。如果时间框架发生变化,所有设置的止损和开仓价水平将被重置。
因此,在OnDeInit中,我们应该检查EA关闭的原因是否是时间框架更改。只有当关闭的原因不同时,我们才应该删除所有元素并清除图表上的注释。
我们考虑用鼠标下订单,但是,有时使用按键进行快速操作非常有用。
按下键盘键也指与 EA 交易用户界面元素的交互,也就是说,它指的是运行EA的整个图表。按键应当在 OnChartEvent 函数中处理,
每当按下某个键时,都会生成CHARTEVENT_KEYDOWN事件。按下按键的代码在 sparam 参数中加入,这些数据足以启动按键处理:
void OnChartEvent(const int id, // 事件 ID const long& lparam, // 长整型的事件参数 const double& dparam, // 双精度浮点型的事件参数 const string& sparam) // 字符串类型的事件参数 { string text=""; double curprice=0; switch(id){ case CHARTEVENT_OBJECT_CLICK: // 在图表上按下按钮 break; case CHARTEVENT_OBJECT_DRAG: // 移动线 break; case CHARTEVENT_KEYDOWN: switch((int) sparam){ // 在不下订单的情况下终止EA操作 case 45: //x closeNotSave(); break; // 下订单并完成EA操作 case 31: //s startPosition(); break; // 设置最小可能止损以打开买入头寸 case 22: //u setMinStopBuy(); break; // 设置最低可能止损以打开卖出头寸 case 38: //l setMinStopSell(); break; // 取消设定的开仓价 case 44: //z setZero(); ChartRedraw(); break; // 将止损设置为当前价格的0.2%,以打开多头头寸 case 3: //2 set02StopBuy(); break; // 将止损设置为当前价格的0.2%,以打开空头头寸 case 4: //3 set02StopSell(); break; // 将止损设置为当前价格的7分(CENT_STOP 参数) // 来开启买入仓位 case 8: //7 set7StopBuy(); break; // 将止损设置为当前价格的7分(CENT_STOP 参数) // 来开启卖出仓位 case 9: //8 set7StopSell(); break; } break; } }
这样,如果您将固定停止损失设置为最小可能大小,即0.2%或美分,那么您甚至不需要使用鼠标。启动EA,按“2”键从多头方向的价格将停止损失设置为0.2%,按“S”键,将打开适当的仓位。
如果您使用的是MetaTrader 5,那么您甚至可以使用指定的热键从键盘启动EA。在导航器窗口中,调用EA上下文菜单,单击设置热键并享受快速访问:
图5. 为EA交易指定热键
现在让我们探讨一下仓位打开函数 startPosition. 几乎没有什么有趣的。我们只需检查我们需要的所有数据的可用性:止损价、持仓开盘价和EA设置。之后,EA根据您的风险设置计算进场手数值,然后再调用之前提到的函数 pdxSendOrder。
最有趣的是交易量计算机制。
首先,我们需要用最小的可能交易量来计算止损情况下的潜在损失。MQL5 中的功能实现与 MQL4中的功能实现不同,
MQL5 有一个特别的 OrderCalcProfit 函数,它允许计算交易品种价格达到指定水平时可获得的潜在利润的大小。此函数允许计算潜在利润和潜在损失。
在MQL4中使用了更复杂的亏损计算公式。
这里是结果函数:
double getMyProfit(double fPrice, double fSL, double fLot, bool forLong=true){ double fProfit=0; fPrice=NormalizeDouble(fPrice,_Digits); fSL=NormalizeDouble(fSL,_Digits); #ifdef __MQL5__ if( forLong ){ if(OrderCalcProfit(ORDER_TYPE_BUY, _Symbol, fLot, fPrice, fSL, fProfit)){}; }else{ if(OrderCalcProfit(ORDER_TYPE_SELL, _Symbol, fLot, fPrice, fSL, fProfit)){}; } #else if( forLong ){ fProfit=(fPrice-fSL)*fLot* (1 / MarketInfo(_Symbol, MODE_POINT)) * MarketInfo(_Symbol, MODE_TICKVALUE); }else{ fProfit=(fSL-fPrice)*fLot* (1 / MarketInfo(_Symbol, MODE_POINT)) * MarketInfo(_Symbol, MODE_TICKVALUE); } 在画线之前#endif if( fProfit!=0 ){ fProfit=MathAbs(fProfit); } return fProfit; }
因此,我们用最小交易量计算了亏损,现在,我们需要确定亏损不会增加指定风险设置的交易量:
profit=getMyProfit(open, STOPLOSS_PRICE, lot); if( profit!=0 ){ // 如果最小手数的损失小于你的风险, // 计算适当的交易量 if( profit<stopin_value ){ // 取得想要的交易量 lot*=(stopin_value/profit); // 如果交易量与允许的 // 这种交易品种的最小步长不对应 if( SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP)==0.01 ){ lot=(floor(lot*100))/100; }else if( SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP)==0.1 ){ lot=(floor(lot*10))/10; }else{ lot=floor(lot); } // 如果最小手数损失大于风险, // 如果在EA参数中设置了此选项,则取消仓位的打开 }else if( profit>stopin_value && EXIT_IF_MORE ){ Alert(langs.wrnEXIT_IF_MORE1+": "+(string) lot+" "+langs.wrnEXIT_IF_MORE2+": "+(string) profit+" "+AccountInfoString(ACCOUNT_CURRENCY)+" ("+(string) stopin_value+" "+AccountInfoString(ACCOUNT_CURRENCY)+")!"); return; } }
EA 交易会检查某些条件,以便在不允许打开时不打开交易。
例如,在初始化期间,EA检查当前资产工具的最小允许手数,如果这个值等于0, EA 就不会启动。因为它不能打开这样一个交易品种的仓位:
if(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)==0){ Alert(langs.wrnMinVolume); ExpertRemove(); }
它还检查交易品种是否允许交易,如果禁止交易品种的交易或只允许关闭之前打开的交易,则不会启动EA:
if(SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED || SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_CLOSEONLY ){ Alert(langs.wrnOnlyClose); ExpertRemove(); }
开仓时,EA检查开仓价格和止损水平的正确性,例如,如果最低价格步长为0.25,止损设置为23.29,则经纪商将不接受您的订单。一般来说,EA可以自动将价格调整到适当的值(止损价格将设置为23.25或23.5)。因此,不可能显示无效价格。但是,还将执行附加检查:
if( SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)>0 && SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)!=1 ){ resval=DoubleToString(STOPLOSS_PRICE/SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE), 8); if( StringFind(resval, ".00000000")>0 ){}else{ Alert(langs.wrnSYMBOL_TRADE_TICK_SIZE+" "+(string) SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)+"! "+langs.wrnSYMBOL_TRADE_TICK_SIZE_end); return; } }
在本文中,我们只实现了基本的下单功能。但即使是这些功能,也可以对那些交易 Gerchik 水平或任何其他水平的人提供很大的帮助。
希望这样可以避免使用Excel表格。这将提高您的交易速度和准确性,因此,你的利润最终会增长。
任何EA改进都是受欢迎的,
如果您没有足够的编程技能,但您需要任何特定的功能,请随时与我联系,然而,这可能需要一些费用。
请检查市场上此 EA 交易的扩展版本的功能:
也许您所需的功能已经可用。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程