开发一个跨平台的EA交易来根据风险设置止损和获利

2 八月 2019, 08:25
Roman Klymenko
0
1 595

简介

正如您可能知道的,对于任何交易,都强烈建议遵循资金管理规则。这意味着不建议进入一个可以损失超过 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像素的图表和对话框窗口示例:

带对话窗口的 EA 交易版本

图1. 带对话窗口的 EA 交易版本

如您所见,窗口甚至不能完全适应屏幕,

为了解决这个问题,我又创建了两个EA版本。

在第一个版本中,“设置”窗口在默认情况下是隐藏的,可以通过单击设置按钮打开。此EA仍在MetaTrader 5市场上提供。

第二个版本没有任何设置窗口,所有EA设置都在输入参数中给出。这样就不需要使用对话框窗口。但是,这会导致下面的一个问题:

  • 每次启动时需要重新配置EA
  • 每次启动时需要从一个设置文件上载设置
  • 需要更改EA代码中的默认设置并重新编译它

作为程序员,我希望您选择第三个选项。在本文中,我们将创建第二个EA版本的简化克隆,下面是EA操作的样子:

不带对话框的 EA 交易版本

图2. 不带对话框的 EA 交易版本

输入参数

为了更深入地了解整个工作范围,让我们看看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倍。

其它参数. 您还可以:

  • 更改EA订单的幻数
  • 为订单设置注释
  • 选择界面语言:英语或俄语(默认)

开启仓位函数

由于我们正在编写一个跨平台专家顾问,它应该在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操作格式的更多详细信息,请阅读上述文章。

EA 界面的编程

在本文中,我们不会从头开始考虑EA开发,我们还假定读者至少具有基本的MQL知识。

在本文中,我们将研究如何实现主要的EA部分,这将帮助您在必要时实现其他功能。

让我们从EA界面开始。

将在EA启动时创建以下界面元素:

  • 一条将包含当前美元价差、点数和价格百分比,以及交易品种交易结束时间的注释;
  • 在与当前价格的价差距离处显示一条水平线,用于放置止损;
  • "Show (0) open price line" 按钮将被添加,这将在订单打开价格处显示绿色水平线;
  • 打开具有指定参数的订单需要另一个按钮。

因此,要创建具有所需参数(使用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 报价的数据,变量的内容在 OnInitOnTick 函数中使用下面的指令更新:

SymbolInfoTick(_Symbol,lastme);

其它变量在 OnInit 函数中初始化,isClosedtime_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 交易用户界面元素的交互,也就是说,它指的是运行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上下文菜单,单击设置热键并享受快速访问:

为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 交易的扩展版本的功能:

也许您所需的功能已经可用。

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/6986

附加的文件 |
openWithRisk.ex5 (126.3 KB)
openWithRisk.ex4 (56.42 KB)
openWithRisk.mq5 (119.01 KB)
openWithRisk.mq4 (119.01 KB)
轻松快捷开发 MetaTrader 程序的函数库(第九部分):与 MQL4 的兼容性 - 准备数据 轻松快捷开发 MetaTrader 程序的函数库(第九部分):与 MQL4 的兼容性 - 准备数据

在之前的文章中,我们已着手创建一个大型跨平台函数库,简化 MetaTrader 5 和 MetaTrader 4 平台程序的开发。 在第八部分中,我们实现了跟踪订单和持仓修改事件的类。 在此,我们将令其与 MQL4 完全兼容来,极大改进函数库。

轻松快捷开发 MetaTrader 程序的函数库(第八部分):订单和持仓修改事件 轻松快捷开发 MetaTrader 程序的函数库(第八部分):订单和持仓修改事件

在之前的文章中,我们已着手创建一个大型跨平台函数库,简化 MetaTrader 5 和 MetaTrader 4 平台程序的开发。 在第七部分中,我们加入了 StopLimit 订单激活跟踪,以及准备了其它涉及订单和持仓事件的跟踪功能。 在本文中,我们将开发用于跟踪订单和持仓修改事件的类。

攫取盈利至最后的点位 攫取盈利至最后的点位

本文尝试阐述在算法交易领域中将理论与实践相结合。 有关创建交易系统的大多数讨论都与依据历史柱线和其上应用的各种指标有关联。 这是覆盖率最高的领域,因此我们不会再过多涉及。 柱线体现出非常人工的实体; 因此,我们将使用更接近原始数据的东西,即价格的即时报价。

通过谷歌服务安排邮寄活动 通过谷歌服务安排邮寄活动

交易者可能希望安排一次邮寄活动,以维持与其他交易者、订户、客户或朋友的业务关系。此外,可能需要发送屏幕截图、日志或报告。这些任务可能不是最经常出现的任务,但是拥有这样的特性显然是一个优势。本文讨论同时使用几个Google服务,在C#上开发适当的程序集,并将其与MQL工具集成。