请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3364680 新帖:1

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

我是游客发表于:8 月 2 日 15:00回复(1)

简介

正如您可能知道的,对于任何交易,都强烈建议遵循资金管理规则。这意味着不建议进入一个可以损失超过 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 交易的扩展版本的功能:

  • 用于 MetaTrader 4 的 EA 交易;
  • 用于 MetaTrader 5 的 EA 交易.

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

全部回复

0/140

达人推荐

量化课程

    移动端课程