MetaEditor:模板作为支点

MetaQuotes | 17 三月, 2016

简介

作为编程语言,MQL4 绝大部分用于一次性编写和调试的代码。 这些使用包括:

然而,并非所有开发人员都了解,因此,并非所有开发人员都会使用此类强大的机制来轻松、可靠地将 Expert Advisor 编写成使用 Expert Advisor Wizard 创建的现成模板。 本文介绍了此工具的一些优势。



什么是模板?

就 MetaEditor 而言,什么是模板? 模板是存储在终端中的同名文件夹 Root_directory_MetaEditor_4/experts/templates/ 中的 .mqt 文件。

在上图中,我们可以看到 10 个此类文件。 基本文件为:

其他模板(Alligator.mqt 等)旨在根据模板名称中给定的指标名称创建指标。 例如,我们来用 MetaEditor 打开模板 Library.mqt。 为此,我们应在“文件类型”字段中指定“所有文件 (*.*)”:

我们将看到,此文件的内容不是非常大。

<expert>
type=LIBRARY_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
//+------------------------------------------------------------------+
//| My function                                                      |
//+------------------------------------------------------------------+
// int MyCalculator(int value,int value2)
//   {
//    return(value+value2);
//   }
//+------------------------------------------------------------------+

前三行说明此模板属于哪种类型:

<expert>
type=LIBRARY_ADVISOR
</expert>

type=LIBRARY_ADVISOR 行显然在告知 MetaEditor 此文件是一个库模板。 MetaEditor 将根据您的选择使用所需模板: EA、自定义指标等等。

然后是替代宏 #header#,它的确将被您在遵循 Expert Advisor Wizard 说明操作时为自己选择的名称所替代。

例如,如果您将 EA 命名为 My_Best_Expert_Advisor,则将产生以下行,替代 #header# 宏:

//+------------------------------------------------------------------+
//|                                       My_Best_Expert_Advisor.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+

在上述注释块中,我们可以看到有关 EA 名称、作者和网站链接的信息。 所有这些数据已输入 Expert Advisor Wizard 的相应字段中。 接下来的行:

#property copyright "#copyright#"
#property link      "#link#"

包含宏 #copyright# 和 #link#,它们显然对应于 Expert Advisor Wizard 中的字段。

如何使用模板?

我们最感兴趣的是模板能否插入到我们在创建 EA 时自动生成的代码中的可能性。 例如,如果您有创建脚本方面的经验,您会知道,脚本仅用于在图表上启动一次。 有时需要为脚本算法指定一些外部参数,因而,在启动脚本时可能需要更改这些参数。 在默认情况下,Expert Advisor Wizard 并不提供这种可能性。 然而,可使用针对编译器的指令实现这种可能性。

#property show_inputs

将此行添加到任何脚本代码中就足够了,将出现一个包含参数的窗口。 让我们来举例说明具体如何进行操作,以使 Expert Advisor Wizard 将此行自动添加到每个新建的脚本中。 为此,我们来打开一个 EA 模板:

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

仅在用于替换外部参数的宏 (#extern_variables#) 之前添加一行。 下面是我们获得的代码:

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#property show_inputs
 
#extern_variables#
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

接下来,我们将保存更改并开始使用 Expert Advisor Wizard 编写脚本。 让我们来将脚本命名为 TestScript,在按下“完成”之后,

获得以下结果:
//+------------------------------------------------------------------+
//|                                                   TestScript.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

新脚本与旧模板制定的脚本仅有一行不同,但能够给我们带来很大的帮助。 现在,我们无需在每个新脚本中手动输入以下行:

#property show_inputs

将自动添加此行。 不管怎样,对此行进行注释(如果不需要此行)总比每次重新输入此行简单得多。 我们来只手动添加一行包含名称为 emptyParam 的参数的内容。

//+------------------------------------------------------------------+
//|                                                   TestScript.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
extern int emptyParam=1;
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

让我们来编译脚本并在图表上启动脚本。 现在,脚本不会立即运行,但允许我们修改外部参数:

这就是如何使用模板的最简单的示例。 具体思路是,我们仅在模板中添加我们在新建每个 MQL4 程序时最需要的代码行。 然后,每次我们使用 Expert Advisor Wizard 时都会自动添加这些代码行。

重要事项: 当您安装 MetaTrader 4 客户终端时,所有模板都已重新编写。 因此,您自己应复制模板。


我们从哪里获得模板?

能够使用现成的模板确实能够让我们尽可能准确地编写 EA。 新手如何编写他们的 EA? 例如,我们来考虑基于两个移动平均线的相交的策略。 下面是根据此策略创建 EA 的简单技术要求。

  1. 获取短期和长期平均线的值。
  2. 查看它们的相交情况。
  3. 如果短期平均线与长期平均线自下而上相交,则买入,止损位=N 个点。
  4. 如果短期平均线与长期平均线自上而下相交,则卖出,止损位=N 个点。
  5. 如果有多头持仓,根据卖出信号平仓。
  6. 如果有空头持仓,根据买入信号平仓。

那么,EA 已准备就绪并已进行优化。 现在的思路是添加一个新参数,例如获利位= S 个点。 或者替换交易提醒块:使用 Stochastic 的值,而非移动平均数相交的值。 诸如此类等等。 对于每个替代的 EA,我们需要对代码进行一些更改,会突然发现更改并非易事。 在某个时刻,我们发现,我们只需编写一个包含所有新函数的全新 EA。 当然,经验丰富的 Expert Advisors 编写者已经明智地采取了此方式,并使用了他们喜爱的实践和方法。 本文开头列出的技术可为他们提供帮助,即(我们来重复一遍):

然而,他们并没有使用模板产生最大效应。 那么,什么是模板 EA?要怎样使其发挥作用? 我认为,模板 EA 是实际 EA 的通用原型,但没有任何特定功能。 实际上,它是某个 SuperClass,包括一些纯虚拟的函数(关于面向对象的编程)或接口(关于 Java)。 模板将说明 EA 做什么,但并没有说明它怎样做。 这就是我们为什么应在创建模板之前分析所需功能的原因。 我们应首先创建 EA 的结构。



Expert Advisor 结构

EA 应做什么? 首先,EA 非常接近于交易,这意味着,它执行交易操作 - 请求对交易仓位进行建仓和平仓。 此外,EA 还执行以下操作:修改现有持仓的止损位和获利位(若需要),下挂单或删除挂单,修改挂单的止损位和获利位。 开始呈现一个简单的结构:

  1. 接收交易信号的块。
  2. 建立市价订单或下挂单。
  3. 关闭持有订单和删除挂单。

因此,我们将自动交易的总体思路分成三个子问题。 现在,我们知道,我们应实现能够做出当前信号决策的功能(第 1 个块):买入,卖出或观望市价(无交易信号)。 然后(第 2 个块),基于交易信号,我们能够建仓或下挂单。 为什么是“可以”,而不是“必须”呢? 因为建仓块可考虑交易信号,但不一定要遵循信号进行交易。 例如,我们已经有一些持仓。 新建仓可能会让交易帐户承受不合理的风险。 EA 的最后一个重要功能(第 3 个块) - 关闭持有订单和删除挂单 - 也是独立的。 我们可以根据交易信号或其他考虑因素(仓位保持时间、交易时段结束等)进行平仓。 因此,将 EA 逻辑分成三个部分并不是毫无根据的。



进一步细化

我们细想一下,可以得出上述结构不完整的结论。 让我们来在每个块中进行进一步细化。

首先,我们可以针对每个输入的变动计算交易信号(变动实际上是价格变动),或者仅在建立每个新条柱时执行这些计算。 此时,这可能并不重要,但如果我们不提前提供这些,可能会导致将来难以更改代码。

第二,我们还可以更改这些条柱的时间范围:尽管我们倾向于我们的 EA 能够在一小时图表上进行交易,但我们将来可能需要对 EA 进行更改以便其能够在四小时图表上进行交易。 我们还应考虑到这一点。

第三,我们应统一交易信号。 我们需要严格的系统化。 由于我们只有三种交易信号,我们首先让块根据 MQL4 使用严格描述的常量显示这些信号:

因此,第一个块显示如下:

/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       
      b) Every bar of the given timeframe     
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/

然后,我们需要一个计算用于新建仓或修改现有仓的重要仓位的块。 这也是一项不依赖于代码的其他部分的功能。

/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/

我们没有在此处显示仓位、手数的计算,但它也是参数。 然而,我们可能希望在一个独立的块中放置待建仓仓位计算函数,我们可以按惯例将此块命名为资金管理块。

下一个块是修改订单位。 同样,它也必须是独立的,因为我们可以修改止损位和获利位。 除此之外,您可以在每个变动时或仅在建立新条柱时对它们进行修改。 如果我们在 EA 中提供了这种灵活性,将来我们进行优化就简单得多了。

/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
        c) Modification of each open order for every new bar of the selected timeframe (SL and TP)
        d) Modification of each pending order for every new bar (OpenPrice, SL and TP)
*/

请注意,我们可以在建立新条柱时指定修改时间范围,方式与块 #1 (接收交易信号)中的方式相同。

下一个块是用于关闭订单的块。 我们可以由于两个原因而关闭订单,即:时间和信号。 在这里,我们是指收到的与持仓反向的交易信号,以及暗示需要保持仓位的条件发生变动。

/**
      4. 
        a) Close open order by time
        b) Close open order by signal
*/

我将挂单删除块与平仓块分开来了,因为平仓(例如,持仓的价格开始非常剧烈的变动,我们需要尽快平仓)的优先级有时比删除挂单(通常,挂单的价格与当前价格有一段很长的距离)的优先级高很多。

/**
      5. 
        a) Delete pending order by time
        b) Delete pending order by condition
*/

挂单删除块中的所有内容都非常明确,因此无需添加其他注释。

最后一个块是用于建仓和下挂单的块。

/**
      6. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/

尽管奇怪,但它确实是最后一个块。 如果我们细想一下,此 EA 结构非常有逻辑性:首先,我们应做好所有必要的准备(关闭所有必须关闭的内容,删除所有冗余的挂单),而不将任何问题留到以后解决,只有到这时,才下新订单并积极干预市价。

/**
      7. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/

然而,此块还包括一些变体。 您可以市价建仓(以卖价买入或以买入价卖出),您可以建立截止到期日期和时间(交易服务器将删除过期挂单)或者到期时间不超过取消时间(直至我们自己进行删除)的挂单。

所有上述块形成了以下 EA 模板结构:

<expert>
  type=EXPERT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
 
//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       
      b) Every bar of the given timeframe     
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/
 
 
/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
        c) Modification of each open order for every new bar of the selected timeframe (SL and TP)
        d) Modification of each pending order for every new bar (OpenPrice, SL and TP)
*/
 
 
/**
      4. 
        a) Close open order by time
        b) Close open order by signal
*/
 
/**
      5. 
        a) Delete pending order by time
        b) Delete pending order by condition
*/
 
 
/**
      6. 
        a) Open an order at the market price
        b) Place a pending order without expiry date and time
        c) Place a pending order with expiry date and time
*/
 
//----
   return(0);
  }
//+------------------------------------------------------------------+

我们现在可以原样保存我们的模板。 此模板很有用,即使它没有包含任何更多的功能。 它是需要写入 EA 的所有块的文本描述。 每个 Expert Advisor 编写者都可以添加他/她喜欢的结构,或者对结构进行全盘修改。 然而,在任何情况下,当使用 EA Wizard 新建 EA 时,您将看到一个结构的文本说明,以便您不会忘记您应在代码中以怎样的顺序实现哪些功能。 但是,我们可以做出更多努力。



开始实现

现在,我们可以开始用广义函数填充 EA 模板结构。 这些函数不会描述所需操作的最终实现情况,但它们会描述模板结构与用户最终函数之间标准交互。 因此,我们不会描述用于接收交易信号的特定函数,但我们会描述在广义函数中用户必须在何处插入特定函数调用。

预定义的时间范围出现新条柱的函数

由于我们已经决定在每次变动时或者在用户预定义的某个时间范围上出现新条柱时在块 #1 中显示交易信号,因此,首先我们需要一个告知我们事件的函数: “时间范围中出现新条柱”。 此函数很简单,如下所示:

//+----------------------------------------------------------------------+
//|  It returns the sign of a new bar appearance for the given timeframe |
//+----------------------------------------------------------------------+
bool isNewBar(int timeFrame)
   {
   bool res=false;
   
   // the array contains open time of the current (zero) bar
   // for 7 (seven) timeframes
   static datetime _sTime[7];  
   int i=6;
 
   switch (timeFrame) 
      {
      case 1  : i=0; break;
      case 5  : i=2; break;
      case 15 : i=3; break;
      case 30 : i=4; break;
      case 60 : i=5; break;
      case 240: break;
      case 1440:break;
      default:  timeFrame = 1440;
      }
//----
   if (_sTime[i]==0 || _sTime[i]!=iTime(Symbol(),timeFrame,0))
      {
      _sTime[i] = iTime(Symbol(),timeFrame,0);
      res=true;
      }
      
//----
   return(res);   
   }

函数 isNewBar() 将时间范围值作为参数和分钟数。 我们来介绍一个新的外部变量 TradeSignalBarPeriod,它将指示必要的时间范围:

extern int  TradeSignalBarPeriod       = 0;

默认为零。 这意味着,EA 在交易中将跟踪“其”时间范围上的新条柱,此时间范围为此 EA 在实时模式交易时所连接的图表的时间范围,或者等同于其用于进行测试的时间范围。 用户可引入任何预定义的常数来选择时间范围。 然而,其中可能存在错误。 因此,我们来添加一个可检查变量 TradeSignalBarPeriod 的值的函数,如果值错误,则更正错误。

//+------------------------------------------------------------------+
//|  if the timeframe is specified incorrect, it returns zero        |
//+------------------------------------------------------------------+
int getCorrectTimeFrame(int period)
   {
   int res=period;
//----
   switch (period)
      {
      case PERIOD_D1:  break;  // allowed timeframe, don't do anything
      case PERIOD_H4:  break;  // allowed timeframe, don't do anything
      case PERIOD_H1:  break;  // allowed timeframe, don't do anything
      case PERIOD_M30: break;  // allowed timeframe, don't do anything
      case PERIOD_M15: break;  // allowed timeframe, don't do anything
      case PERIOD_M5:  break;  // allowed timeframe, don't do anything
      case PERIOD_M1:  break;  // allowed timeframe, don't do anything
      default: res=Period();   // incorrect timeframe, set a default one
      }
//----
   return(res);      
   }

如果没有错误,则此函数不进行任何操作。 我们将仅在 EA 初始化的最初阶段使用一次此函数。 因此,我们将此函数的调用放入函数 init() 中:

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
//----
   TradeSignalBarPeriod=getCorrectTimeFrame(TradeSignalBarPeriod);   
   StartMessage(); // remind the EA settings
//----
   return(0);
  }

正如您所看到的,除此函数之外,块 init() 中还有一个新的用户定义函数 StartMessage()。 下文将对它进行介绍,在这里我仅解释它的作用。 Expert Advisor 收集了大量设置参数,在一段时间过后,用户很容易忘记这些参数。 函数 StartMessage() 专门用于在交易帐户上启动 EA 时或在回溯测试时提供 EA 参数的最小描述。 您稍后可以在终端或测试程序的日志中看到这些参数。 我认为此函数在任何 EA 中都很有用,这就是我为什么也在 EA 模板中纳入此函数的原因。



接收交易提醒

现在,我们转到产生交易提醒的块。 但是,在此之前,我们来考虑更多一些情况。 一旦创建了 EA,我们有时可能需要检查它在一周内的某些天的工作情况。 例如,我们仅需要在星期三进行交易,因此 EA 必须“保持沉默”。 因此我们可以提供这种提前选择交易日期的可能性。 让我们来创建外部整数变量 TradeDay。 如果它的值等于零(星期天=0,星期一=1 等等),则我们像往常一样进行交易。 然而,如果是零以外的其他值,则我们仅在一周内的指定日期进行交易。

而且,我们可能需要执行反向操作。 那么,我们应指定一周内禁止 EA 进行交易的日期。 让我们来创建一个更具逻辑性的外部变量 ReversDay。 如果它等于“false”,则逻辑为正常逻辑(TradeDay 指向交易日)。 如果它为“true”,则条件反转,TradeDay 将指向一周内我们不进行交易的日期。 例如,TradeDay=5 且 ReversDay=true 意味着我们在星期五 (Friday=5) 不进行交易。

我们应提供的最后一个变动是交易系统的反转。 例如,我们将来可能需要完全反转我们的策略(将买入改为卖出,将卖出改为买入),看看会发生什么。 如果从一开始就提供了这种可能性,则在将来的某一天我们能够使用到。 为此,只需要再引入一个外部变量 ReversTradeSignal 就已足够。 如果它等于“false”(默认值),则初始策略起作用。 然而,如果它的值设置为“true”,则将反转系统。 因此,添加的外部变量如下所示:

// constant "off market"
#define OP_BALANCE 6
 
// trade day setting
extern int  TradeDay                   = 0; 
extern bool ReversDay                  = false;
 
// trading system reverse
extern bool ReversTradeSignal          = false;
 
//  trading frequency settings
extern bool TradeSignalEveryTick       = false;
extern int  TradeSignalBarPeriod       = 0;

我添加了表示没有交易信号可用的 OP_BALANCE 常数。 此外,还有一个逻辑变量 TradeSignalEveryTick。 如果它为 true,则意味着在每次变动时收到交易信号。 正如您所看到的,我们没有编写任何有关我们的交易系统的代码行,但是我们已经引入了一些变量,但一段时间过后可能会忘记这些变量的用途。 这就是为什么我们还编写了告知函数 StartMessage() 的原因。

//+------------------------------------------------------------------+
//| It shows a message about EA settings                             |
//+------------------------------------------------------------------+
void StartMessage()
   {
   int i=0;
   string currString="";
   string array[3];
//----
   array[0]=StringConcatenate("Expert Advisor ",WindowExpertName()," has the following settings:");
 
   if (TradeSignalEveryTick)
      array[1]="1) trade signals are considered at every tick; ";
   else       
      array[1]=StringConcatenate("1)trade signals are considered at each bar with the period of ",
                        TradeSignalBarPeriod," minutes;");
 
   if (TradeDay==0)   // trade day is not specified
      array[2]="2)trading is allowed on any day of week; ";
   else 
      {
      if (ReversDay) //  no trading allowed on the specified day
         array[2]=StringConcatenate("2)trading is allowed on all days but day number ",TradeDay);
      else           //  trading is allowed only on the specified day of week
         array[2]=StringConcatenate("2)trading is allowed only on day number ",TradeDay);
      }
   for ( i=0;i<3;i++) currString=StringConcatenate(currString,"\n",array[i]);
   Comment(currString);
 
   for (i=2;i>=0;i--) Print(array[i]);
   
//----
   return;
   }

此函数显示 EA 名称以及有关 EA 设置的最低量信息。 在编写特定 EA 时,您可以在数组“array[]”中添加其他行。

现在,我们准备创建一个能够计算交易信号的函数。 下列设置将传递给此函数:

//+------------------------------------------------------------------+
//| receive a trade signal                                           |
//+------------------------------------------------------------------+
// tradeDay - day of week, on which we trade; if it is equal to zero, then we trade all days
//
// useReversTradeDay - if it is equal to 'true', then trading days become non-trading days
//
// everyTick  - if it is equal to zero, the function will calculate a signal for every tick
//
// period - if everyTick==false, then it is calculated as soon as a new bar with this period appears
int getTradeSignal(int tradeDay,          // it is usually equal to 0
                   bool useReversTradeDay,// it is usually equal to 'false'
                   bool everyTick,        // signal is calculated at every tick
                    int period            // working period for indicators and signals
                   )
   {
   int signal=OP_BALANCE;
//----
   if (tradeDay!=0)  // we will consider days of week
      {
      // day, on which we don't trade
      if (useReversTradeDay && tradeDay==DayOfWeek()) return(signal);
      
      // we trade on all days, except the day equal to tradeDay 
      if (!useReversTradeDay && tradeDay!=DayOfWeek()) return(signal);
 
      }
 
   if (!everyTick) // if we don't take trade signals at every tick
      { // nor we have any new bar on the timeframe of 'period' minutes,
      if (!isNewBar(period)) return(signal); // then exit with an empty signal
      }
 
// Fill function yourFunction() with your code/algorithm
   signal=yourFunction(period);
 
 
   if (signal!=OP_BUY && signal!=OP_SELL) signal = OP_BALANCE;
   
//----
   return(signal);   
   }

正如您前面看到的,我们围绕一行代码编写了多行绑定内容。

   signal=yourFunction();

在编写自己的策略时,我们将编写应返回以下三个值之一的函数(取代 yourFunction()):



标识“朋友或敌人”的块

编写 EA 时遇到的另一个障碍是检测 EA 要使用的订单的订单编号。 此问题通常通过循环 for() 解决,在此循环中,使用 OrderSelect() 函数选择订单编号。 然而,您应牢记我们可能需要多次检测“友好”订单。 在一些情况下,我们应修改止损位。 在另一些情况下,我们需要关闭市价订单。 在第三种情况下,我们还可能需要删除一些挂单。

出于对“友好”订单进行标准处理的原因,我们决定在每次开始使用 start() 函数时用所有必要的参数填充“友好”订单的数组。 为此,我们在全局层面引入两个数组:

double Tickets[][9];// array to store information about "friendly" orders:
// Tickets[][0] - ticket number
// Tickets[][1] - order type
// Tickets[][2] - lots
// Tickets[][3] - open price
// Tickets[][4] - Stop Loss
// Tickets[][5] - TakeProfit
// Tickets[][6] - MagicNumber
// Tickets[][7] - expiration time
// Tickets[][8] - open time
// 
 
string CommentsTicket[1000][2];         //array to store symbols and comments on orders. 1000 lines would be enough
// CommentsTicket[][0] - symbol name
// CommentsTicket[][1] - comment on the order

用于填充这些数组的函数相当简单:

//+------------------------------------------------------------------+
//| It prepares the array of "friendly" orders                       |
//+------------------------------------------------------------------+
void PrepareTickets(double & arrayTickets[][9], string & comm[][2],int MN)
   {
   int count=0;   // filling counter
   
   // let's make the array size large not to allocate memory for it every time
   ArrayResize(arrayTickets,20);
//----
   int total=OrdersTotal();
   for (int i=0;i<total;i++)
      {
      bool ourTicket=false;
      if (OrderSelect(i,SELECT_BY_POS))
         {
         if (!isOurOrder(OrderTicket()))
            {// if the special function has not detected the order as "friendly" one,
            // make usual checks
 
            // check for Symbol
            if (OrderSymbol()!= Symbol()) continue;
         
            //  other checks...
            // ....
         
            // last check, that for MagicNumber
            if (OrderMagicNumber()!=ExpertMagicNumber) continue;
         
            }
         // we haven't been stopped anywhere, so this is a "friendly" order
         //  fill out the array
         arrayTickets[count][0] = OrderTicket();
         arrayTickets[count][1] = OrderType();
         arrayTickets[count][2] = OrderLots();
         arrayTickets[count][3] = OrderOpenPrice();
         arrayTickets[count][4] = OrderStopLoss();
         arrayTickets[count][5] = OrderTakeProfit();
         arrayTickets[count][6] = OrderMagicNumber();
         arrayTickets[count][7] = OrderExpiration();
         arrayTickets[count][8] = OrderOpenTime();
 
         comm[count][0] = OrderSymbol();
         comm[count][1] = OrderComment();
         // let's increase the counter of filled "friendly" orders
         count++;
         }
      }
   
   // and now let's truncate the array sizes to the minimum essential ones 
   ArrayResize(arrayTickets,count);
   ArrayResize(comm,count);
 
//----
   return;   
   }

举个例子,我给出了能够使用 OrderMagicNumber()OrderSymbol() 在广义函数中区分“友好”订单和“敌对”订单的条件的最小量描述。 在循环中搜索所有订单时,我们跳过了“敌人”,用“朋友”的数据填充数组。 为了使此函数更加灵活,我们可以在其中再调用一个用户定义的函数,我们可以在此函数中描述用于检测订单是“朋友”还是“敌人”的一些其他条件。 在此例中,我添加了检查全球变量: 如果终端中有一个使用我们 EA 的名称的全局变量,而且它的值等于某个订单的订单编号,则此订单也将被视为“朋友”。 这很有帮助,例如,如果手动建立了仓位,现在,我们希望由 EA 处理此仓位。

//| It returns 'true', if a Global variable with this name is available|
//+--------------------------------------------------------------------+
bool isOurOrder(int ticket)
   {
   bool res=false;
   
   // we don't use global variables in test mode!
   if (IsTesting()) return(true);// immediately return the positive result
 
   int temp;
//----
   for (int i=0;i<5;i++)
      {
      if (GlobalVariableCheck(WindowExpertName()+"_ticket_"+i))
         {// there is such a global variable
            temp=GlobalVariableGet(WindowExpertName()+"_ticket_"+i);
            if (temp==ticket)
               { // found a GV with the value equal to 'ticket'
               res=true;  // so it is a "friendly" order
               break;
               }
         }
      }
//----
   return(res);   
   }

我们已经完成了大部分的准备工作。 现在,start() 函数的开头部分如下所示:

 
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   // always update the array size before the first use
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtain the arrays of "friendly" orders
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
 
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       (TradeSignalEveryTick=true)
      b) Each bar of the preset period    (TradeSignalBarPeriod=...)
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/


为现有订单计算止损/获利位

现在,我们进入下一个阶段。 我们获得了一个有关“友好”订单的二维数组,即 Tickets[][9]。 现在,我们来计算新的止损位和获利位,了解每个订单的当前特点。 计算出的新值应存储在某个位置,因此,我们再来创建一个用于存储的全局数组:

double newSL_and_TP[][5];// array to store the new values of SL and TP
// newSL_and_TP[][0] - ticket number to be controlled
// newSL_and_TP[][1] - new values of SL
// newSL_and_TP[][2] - new values of TP
// newSL_and_TP[][3] - new Open price (for pending orders)
// newSL_and_TP[][4] - 0 for open orders and 1 for pending orders

可能只需要解释以下函数的参数:

newSL_and_TP[][4] - 0 for open orders and 1 for pending orders

我们将需要此参数来在单独的函数中修改市价订单和挂单。 我们来创建一个函数,此函数使用数组 Ticketsp[][9] 和系统反转符号,并用值填充新的数组 newSL_and_TP[][5](第二个维度中的比例在括号中给出)。 此函数的调用显示如下:

   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);

第一个参数 ReversTradeSignal 可以使用值“true” (即系统已反转),也可以使用值“false”。 第二个参数是“友好”订单的数组(如果没有订单,则它的大小等于零)。 第三个参数是要用此函数填充的数组。 此函数如下所示:

//+------------------------------------------------------------------+
//|  It creates an array with the new values of SL and TP            |
//+------------------------------------------------------------------+
void CalculateSL_and_TP(bool ReversTrade,       // system reverse 
                      double arrayTickets[][9], // array of "friendly" orders
                      double &amp; arraySL_TP[][5]  // new values of SL, TP and openPrice
                      )
   {
   // first of all, zeroize the obtained array !!
   ArrayResize(arraySL_TP,0);
   // if the order array is empty, then exit   
   int    i,size=ArrayRange(arrayTickets,0);
   if (size==0) return;
//----
   int    sizeSL_TP=0;
   double lots, openPrice, SL,TP;
   double newSL,newTP, newOpen;
   double oldOpenPrice, oldSL,oldTP;    
 
   int type,ticket,oldType;
   for (i=0;i>size;i++)
      {
      ticket    = arrayTickets[i][0]; //ticket number
      type      = arrayTickets[i][1]; //order type
      lots      = arrayTickets[i][2]; //order volume
      openPrice = arrayTickets[i][3]; //order open price
      SL        = arrayTickets[i][4]; //Stop Loss
      TP        = arrayTickets[i][5]; //Take Profit
      
      if (ReversTrade) //  reverse all levels considering the spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Invalid order type Type=",type," in function CalculateSL_and_TP()!!!");                           
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)  
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL) 
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // no system reverse
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }
      
      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // if it is a pending order, obtain a new open price
      if (type<OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL<0 || newTP<0 || newOpen<0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL<0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP<0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen<0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type<OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }         
//----
   return;
   }

首先,我们来将包含新的止损位值和获利位值的数组大小设置为零。 然后,我们应了解包含“友好”订单的数组的大小。

   int   size=ArrayRange(arrayTickets,0);   // if the order array is empty, then exit    if (size==0) return;

如果没有订单,则无需进行任何操作,从而退出,同时用大小为零的数字 arraySL_TP[][] 表示输出。 大小为零的数组意味着没有对这些数字进行操作的指令。 接下来,我们安排一个查看传递的所有待处理的订单的循环:

   for (i=0;i<size;i++)
      {
      ticket    = arrayTickets[i][0]; //ticket number
      type      = arrayTickets[i][1]; //order type
      lots      = arrayTickets[i][2]; //order volume
      openPrice = arrayTickets[i][3]; //order open price
      SL        = arrayTickets[i][4]; //Stop Loss
      TP        = arrayTickets[i][5]; //Take Profit
      
      oldOpenPrice = openPrice;
      oldSL = SL;
      oldTP = TP;
         
      
      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // if it is a pending order, then receive a new open price
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }

我们计算 oldSL、oldTP 和 oldOpenPrice 的当前值(因为我们更改了挂单的开盘价),并将这些值作为参数传递到用于计算止损位、获利位和开盘价的新值的函数。

      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
     
      // if it is a pending order, obtain a new open price
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);

那么,如果在止损位、获利位和开盘价的新值中至少有一个值不等于零,则我们要做的就是将止损位、获利位和开盘价的新值的数组大小增加一(即必须修改此订单)。

      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket   
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // new level of SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // new level of TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // new open price
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // market order
            else           arraySL_TP[sizeSL_TP][4]=0; // market order
         }
      }

现在,我们需要考虑反转交易系统的可能性。 反转意味着什么? 它意味着买入变成卖出,反之亦然。 由于我们以卖价买入,以买入价卖出,开盘价将根据点差值进行变动。 此外,止损位和获利位再次考虑点差。 我们可以对其进行编码以便考虑所有因素:

      if (ReversTrade) //  reverse all levels considering spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Invalid order type Type=",type," in function CalculateSL_and_TP()!!!");                           
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)  
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL) 
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // no system reverse
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }

我在这里未提供对函数 CalculateSL_and_TP() 做出的更改的完整代码,因为此函数非常长。 您可以在附件 Template_EA.mqt 中看到它。 因此,我们可以说,我们成功地解决了计算止损位、获利位和开盘价的新值的任务。 现在,我们需要创建要从此计算块调用的函数。 它们是在创建特定 EA 时要填充的空函数,我们将它们命名为“形状函数”。



形状函数

下面是这些函数:

//+------------------------------------------------------------------+
//|  It calculates the level of Stop Loss                            |
//+------------------------------------------------------------------+
double getNewSL(int    type,      // type of the order, for which we calculate it
                double lots,      // volume, it can be useful
                double openPrice, // open price
                double stopLoss,  // current level of Stop Loss
                double takeProfit // current level of Take Profit
                )
   {
   double res=-1;
//----
//  here is the code of calculations for Stop Loss
//----
   return(res);   
   }
 
//+------------------------------------------------------------------+
//|  It calculates the level of  Take Profit                         |
//+------------------------------------------------------------------+
double getNewTP(int    type,      // type of the order, for which we calculate it
                double lots,      // volume, it can be useful
                double openPrice, // open price
                double stopLoss,  // current level of Stop Loss
                double takeProfit // current level of Take Profit
                )
   {
   double res=-1;
//----
//  here is the code of calculations for Take Profit
//----
   return(res);   
   }
 
//+------------------------------------------------------------------+
//|  It calculates the new open price for a pending order            |
//+------------------------------------------------------------------+
double getNewOpenPricePending(int    type,      // type of the order, for which we calculate it
                              double lots,      // volume, it can be useful
                              double openPrice, // open price
                              double stopLoss,  // current level of Stop Loss
                              double takeProfit // current level of Take Profit
                              )
   {
   double res=-1;
//----
//  here is the code of calculations for open price 
 
//----
   return(res);   
   }

这三个函数非常相似,每个函数获得一个相同的输入集,并返回变量 res 中的 double 类型的值。 此变量即时用负值进行初始化,如果我们没有插入自己的代码进行计算,它就是即将返回的负值。 这意味着不存在计算的级别,因为价格始终为正值。

此外,我们还可以在这里为从用于通过函数 getTradeSignal() 计算交易信号的块调用的 yourFunction() 编写形状:

//+------------------------------------------------------------------+
//|  Function to produce trade signals                               |
//+------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
//  (there must be a code producing trade signals considering timeframe workPeriod here)
//----
   return (res);   
   }

此函数获得 workPeriod - 我们在调用自定义或标准指标时必须使用的时段。 返回变量 res 用 OP_BALANCE 的值进行初始化。 如果没有插入用于更改此变量的值的代码,它就是函数将返回的值。 这被接收作为无交易信号。

此模板中没有更多的自定义函数形状。 获得有关它们的使用的技术,您将能够在代码的适当位置插入所有必要的形状。

为新订单计算参数

现在,我们需要获得下一个新的市价订单或挂单所需的止损位、获利位、交易量和其他参数的值。 为此,我们来创建一些变量,它们的名称将说明它们的作用。

   double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL, pendingTP, pendingComment);

这些函数将在两个函数中获得它们的值:一个函数为市价订单计算值,另一个函数为挂单计算值。 这种区分让我们能够更轻松地编写代码以及后续更改代码。 首先,我们来考虑函数 CalculateNewMarketValues():

//+------------------------------------------------------------------+
//|  It calculates the data for opening a new order                  |
//+------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots, 
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // if there is no trade signal, exit
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // this means that we won't open
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 
 
//----
   //  insert your own code to calculate all parameters
//----
   return;   
   }

此函数接收当前交易信号 trSignal,并基于此信号计算所有返回参数。

请注意,在这里以及任何其他地方,所有值必须用安全值进行初始化。

如果我们没有插入自己的代码,变量 marketType 将保留值 -1(负一),意味着不打算建立订单。 在这里,应提醒的是,交易操作的常数使用非负值。 为挂单计算建仓参数的函数非常相似:

//+------------------------------------------------------------------+
//|  It calculates the data for placing a pending order              |
//+------------------------------------------------------------------+
void CalculateNewPendingValues(int    trSignal, 
                               int    & pendingType, 
                               double & pendingOpenPrice, 
                               double & pendingLots, 
                               double & pendingSL, 
                               double & pendingTP, 
                               string & pendingComment)
   {
   // if there is no trade signal, exit
   if (trSignal==OP_BALANCE) return;
 
   pendingType      = -1; 
   pendingOpenPrice = 0; 
   pendingLots      = 0; 
   pendingSL        = 0; 
   pendingTP        = 0; 
   pendingComment   = 0;
//----
   //insert your own code to calculate all parameters
//----
   return;
   }

唯一区别是,它多计算一个参数 - 挂单开盘价。



修改订单

EA 工作中的下一个操作是修改“友好”订单。 两个单独的函数用于此目的。

   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);

它们非常类似,每个函数使用以下参数作为输入:

我们来考虑第一个参数 - ModifyMarkets():

//+------------------------------------------------------------------+
//|  Modifications of market orders                                  |
//+------------------------------------------------------------------+
void  ModifyMarkets(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // nothing to modify - exit
 
   bool res;
//----
   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }
 
   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // it is not a market order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // it is not a market order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
      }         
 
//----
   return;   
   }

第一次检查已经是标准检查 - 检查数组 newSL_and_TP[][] 的大小是否为零以便进行修改。 第二次检查:第一次检查是否有必要在每次变动时进行修改。 如果没有必要 (ModifyEveryTick=false),则检查 ModifyBarPeriod 分钟的时间范围上是否出现新条柱。 如果没有通过检查,则退出,不进行任何操作:

   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }

然而,如果成功地通过了这些初步检查,我们可以开始修改订单。 同时,我们不要忘记考虑系统的两种工作方式:直接和反转。

   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         //  order modification code for a direct system
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         //  order modification code for a reversed system
         }
      }

它们之间的唯一区别是,newSL_and_TP[i][1] 和 newSL_and_TP[i][2](止损位和获利位)的值在函数 OrderSend() 中进行互换。

用于修改挂单的函数非常相似,但是我们在其中添加了接收挂单开盘价:

//+------------------------------------------------------------------+
//|  Modifications of market orders                                  |
//+------------------------------------------------------------------+
void  ModifyPendings(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // nothing to modify - exit
 
   bool res;
//----
   if (!ModifyEveryTick )// if every-tick modifying is prohibited
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no new bar appeared
      }
 
   if (!Revers) // direct working
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // it is not a pending order - skip it
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
       }  
   else  // trading system is reversed, transpose SL and TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // it is not a pending order - skip it 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// modification failed
            {
            Print("Failed modifying order #",ticket,". Error ",GetLastError());
            // further processing of the situation
            }
         }
      }         
 
//----
   return;   
   }

我还希望您注意到,两个函数中都已选中订单类型:

         type=newSL_and_TP[i][4];

根据变量“type”的值(0 或 1), 程序将决定是处理还是跳过此订单编号。 这就是有关用于修改订单的函数的所有说明。



关闭市价订单

现在,我们需要编码关闭市价订单。 为此,我们将使用两个包含有关要关闭的订单的信息的数组,以及两个用于处理这些数组的函数:

/**
      4. 
        a) Close an open order by time
        b) Close an open order by signal
*/
   // tickets of orders to be closed
   int ticketsToClose[][2];
   double lotsToClose[]; 
   
   // zeroize array sizes
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // prepare a ticket array for closing
   if (trSignal!=OP_BALANCE) P
        repareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // close the specified orders
   CloseMarketOrders(ticketsToClose,lotsToClose);

数组 ticketsToClose[][2] 存储要关闭的订单的订单编号和类型的值,数组 lotsToClose[] 包含有关要关闭的每个仓位的关闭手数的信息。 函数 PrepareTicketsToClose() 接收作为输入的订单数组 Tickets[][] 和当前交易信号的值。 买入交易信号也可作为关闭卖出订单的指令。 函数 PrepareTicketsToClose() 本身使用较少数据编写而成。

//+------------------------------------------------------------------+
//|  Prepare an array of tickets to close orders                     |
//+------------------------------------------------------------------+
void PrepareTicketsToClose(int signal, bool Revers, int & ticketsClose[][2], 
                                   double & lots[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
 
   int i,type,ticket,closeSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // if it is not a market order, then skip it
      if (type>OP_SELL) continue;
 
      if (Revers) // reverse the order type
         {
         if (type==OP_BUY) type=OP_SELL; else type=OP_BUY;
         }
      
      // here we will decide the fate of each open order
      //  whether we retain it in the market or add to the array of orders to be closed
      if (type==OP_BUY)
         {
         //  
         // code that allows to retain Buy
         
         // as an example
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELL)
         {
         //  
         // code that allows to retain Sell
         
         // as an example
         if (signal==OP_SELL) continue;
         }
 
      closeSize=ArrayRange(ticketsClose,0);
      ArrayResize(ticketsClose,closeSize+1);
      ArrayResize(lots,closeSize+1);
      ticketsClose[closeSize][0] = arrayTickets[i][0]; // ticket #
      ticketsClose[closeSize][1] = arrayTickets[i][1]; // order type
      Print("arrayTickets[i][0]=",arrayTickets[i][0],"   ticketsClose[closeSize][0]=",ticketsClose[closeSize][0]);
      
      // here we will specify the amount of lots to be closed
      lots[closeSize] = arrayTickets[i][2]; // volume to be closed
      // they can be closed partially, then you should rewrite the code line above
      }
//----
   return;   
   }

您应在其中添加您自己的条件,可将需要的订单纳入要关闭的订单列表中。 如果输入数组 arrayTickets 的大小为零(即我们没有订单要进行处理),则像往常一样提早退出函数。

函数 CloseMarketOrders() 没有任何难点:

//+------------------------------------------------------------------+
//|  It closes orders with the given tickets                         |
//+------------------------------------------------------------------+
void CloseMarketOrders(int ticketsArray[][2], double lotsArray[])
   {  
//----
   int i,size=ArrayRange(ticketsArray,0);
   if (size==0) return;
   
   int ticket,type;
   double lots;
   bool res;
   
   int total=OrdersTotal(); 
   Print("",size," orders should be closed, orders opened:",total);
   
   for (i=0;i<size;i++)
      {
      ticket = ticketsArray[i][0];
      type   = ticketsArray[i][1];
      lots   = lotsArray[i];
      Print("Close order #",ticket," type=",type," ",lots," lots" );
      Print(" ");
      RefreshRates(); // just in case, update the data of the market environment
 
      // buy closing block
      if (type==OP_BUY)
         {
         res = OrderClose(ticket,lots,Bid,Slippage,Orange);
         if (!res)
            {
            Print("Failed closing Buy order #",ticket,"!  Error #",GetLastError());
            //  further error processing, code independently
            }
         }
 
      //  sell closing block
      if (type==OP_SELL)
         {
         res = OrderClose(ticket,lots,Ask,Slippage,Orange);
         if (!res)
            {
            Print("Failed closing Sell order #",ticket,"!  Error #",GetLastError());
            //  further error processing, code independently
            }
         }
 
      } 
//----
   return;
   }

在此循环中,用于关闭的数组已进行分配,市价环境已使用函数 RefreshRates() 进行更新,程序将尝试在达到与订单类型相对应的价格时关闭订单。 将对关闭时发生的错误进行最低限度的分析,您应添加自己的算法以处理此情况。



删除挂单

删除挂单的操作与关闭市价订单的操作非常类似。 它仅不包含数量数组,由于我们只需了解要删除的挂单的订单编号:

/**
      5. 
        a) Delete a pending order by time
        b) Delete a pending order by condition
*/
   // tickets of orders to be deleted
   int ticketsToDelete[];
 
   // prepare a ticket array for orders to be deleted
   if (trSignal!=OP_BALANCE) 
        PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // delete the specified orders
   DeletePendingOrders(ticketsToDelete);

相应的,此函数块的语法也很相似,因此我在这里不对其进行详细说明:

//+------------------------------------------------------------------+
//|  Prepare an array of tickets to delete pending orders            |
//+------------------------------------------------------------------+
void PrepareTicketsToDelete(int signal, bool Revers, int & ticketsDelete[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
   ArrayResize(ticketsDelete,0);
 
   int i,type,ticket,deleteSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // if it is not a pending order, then skip
      if (type<=OP_SELL) continue;
      
      if (Revers) // reverse the order type
         {
         switch (type)
            {
            case OP_BUYLIMIT : type = OP_SELLSTOP; break;
            case OP_SELLLIMIT: type = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : type = OP_SELLLIMIT; break;
            case OP_SELLSTOP : type = OP_BUYLIMIT ; break;
            }
         }
 
      // here we will decide the fate of each pending order
      //  whether we retain it or add to the array of orders to be deleted
      //  here we will give an example of a buying signal retaining 
      // pending orders OP_BUYLIMIT and OP_BUYSTOP
      // the same for selling signals
      if (type==OP_BUYLIMIT || OP_BUYSTOP)
         {
         //  
         // code that allows to retain Buy
         // as an example
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELLLIMIT || OP_SELLSTOP)
         {
         //  
         // code that allows to retain Sell
         // as an example
         if (signal==OP_SELL) continue;
         }
 
      deleteSize=ArraySize(ticketsDelete);
      ArrayResize(ticketsDelete,deleteSize+1);
      ticketsDelete[deleteSize] = arrayTickets[i][0];
      }
//----
   return;   
   }


根据市价建仓和下挂单

还剩最后两个操作。 我们已经收到了交易信号,制定了订单列表,修改、关闭并删除了所有需要的订单。 现在,我们可以根据市价建仓,如果合理的话,或者下挂单。

/**
      6. 
        a) Open an order by market
        b) Place a pending order without expiration
        c) Place a pending order with expiration
*/
 
   if (trSignal!=OP_BALANCE) 
           OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);

   if (trSignal!=OP_BALANCE) 
           SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL, pendingTP,pendingComment);

第一个函数 OpenMarketOrder() 获取所有需要的输入,包括系统反转的符号。

///+------------------------------------------------------------------+
//|  It opens a position by market                                    |
//+-------------------------------------------------------------------+
void OpenMarketOrder(bool   reversTrade,// sign of system reverse
                     int    Type,       // order type - OP_BUY or OP_SELL
                     double lots,       // volume of the position to be opened
                     double SL,         // level of StopLoss
                     double TP,         // level of TakeProfit
                     string comment)    // comment on the order
   {
 
   //Print("Open order Type=",Type,"  lots=",lots,"  SL=",SL,"TP=",TP,
        " comment=",comment,"  ExpertMagicNumber=",ExpertMagicNumber);
   int openType;
   
   if (reversTrade)                       // reverse the signals
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
      if (Type==OP_BUY)  
         {
         openType = OP_SELL; // Buy will become Sell
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELL) 
         {
         openType = OP_BUY;  // Sell will become Buy
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP-Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp-Point*spread,digits);
            else TP=0;
         }
      }
   else
      {
      openType=Type;
      }   
//----
 
   if (lots==0) return;
   
   RefreshRates();
   double price;
   color Color;
   if (openType==OP_BUY) 
      {
      price = Ask;
      Color = Blue; 
      }
   if (openType==OP_SELL) 
      {
      price=Bid;
      Color = Red; 
      }
   bool ticket = OrderSend(Symbol(),openType,lots,price,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color); 
   if (ticket>0)
      {
      Print("Failed to open a position by market");
      Print("Type=",openType,"  lots=",lots,"  SL=",SL,"TP=",TP," comment=",comment,
        "  ExpertMagicNumber=",ExpertMagicNumber);
      //  further processing of the situation, code independently
      }
//----
   return;
   }

除反转情况以外,此函数没有任何复杂的问题。 对于系统反转,您应寄托于止损位和获利位并添加一个等于点差值的偏移。 在这里,主要事情是,不要忘记止损位或获利位的任意值都可以等于零。

函数 SendPendingOrder() 只是稍微复杂一些,因为我们需要考虑到,分别有两种类型的买入和卖出挂单。 其他方面与 OpenMarketOrder() 类似:

//+------------------------------------------------------------------+
//|  It places a pending order                                       |
//+------------------------------------------------------------------+
void SendPendingOrder(bool   reversTrade,// sign of system reverse
                      int    Type,
                      double OpenPrice, 
                      double Lots, 
                      double SL, 
                      double TP,
                      string comment)
   {
   //Print("SendPendingOrder()  Type=",Type);
   
   if (Type==-1) return; 
//----
 
   int openType;
   
   if (reversTrade)    // reverse order type and levels
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
 
      if (Type==OP_BUYLIMIT || Type==OP_BUYSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice - spread*Point,digits);
         temp=SL;
         if (TP!=0)  SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELLLIMIT || Type==OP_SELLSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice + spread*Point,digits);
         temp=SL;
         if (TP!=0) SL = NormalizeDouble(TP - Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp - Point*spread,digits);
            else TP=0;
         }
 
      switch (Type)
         {
         case OP_BUYLIMIT:  openType = OP_SELLSTOP ; break;
         case OP_SELLLIMIT: openType = OP_BUYSTOP  ; break;
         case OP_BUYSTOP:   openType = OP_SELLLIMIT; break;
         case OP_SELLSTOP:  openType = OP_BUYLIMIT ; break;
         default: Print("Invalid order type Type=",Type," in function SendPendingOrder()!!!");                           
         
         }
      }
   else openType = Type;   
      
   color Color;
   if (openType==OP_SELLLIMIT || openType==OP_SELLSTOP)  Color = Red;
   if (openType==OP_BUYLIMIT  || openType==OP_BUYSTOP)   Color = Blue;
 
   bool res = OrderSend(Symbol(),openType,Lots,OpenPrice,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color); 
   if (!res)
      {
      Print("Failed placing pending order");
      //  further processing of the situation, code independently
      }
 
//----
   return;
   }

在这两个函数中,有关交易请求错误的处理都是最低限度的,您可以为此添加自己的代码。



最终版本的函数 start()

一旦创建了所有必要的函数,我们可以再来看看函数 start(),它已经从文本模板转变成一个功能完备的代码:

//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   // always zeroize the array size before the first use
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtain arrays of "friendly" orders
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
/**
      1. Trade Signals . Receiving trade signals
      a) Every tick                       (TradeSignalEveryTick=true)
      b) Every bar in the preset period   (TradeSignalBarPeriod=...)
      OP_BUY      - to buy
      OP_SELL     - to sell
      OP_BALANCE  - no signal
*/
 
 
   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
                       
/*   
   if (trSignal==OP_BUY) Print("Buy signal");                    
   if (trSignal==OP_SELL) Print("Sell signal");                    
   if (trSignal!=OP_SELL && trSignal!=OP_BUY) Print("The current signal is equal to ",trSignal);
*/
 
/**
      2. 
        a) Calculate SL and TP for each open order
        b) Calculate OpenPrice, SL, TP, and Lots for a new order
        c) Calculate OpenPrice, SL and TP for each pending order
*/
 
   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);
 
 
   double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL,
        &nbsp;pendingTP, pendingComment);
 
/**
      3. 
        a) Modification of each open order for every tick (SL and TP)        
               (ModifyMarketOrderEveryTick = true)
               
        b) Modification of each pending order for every tick (OpenPrice, SL and TP)
               (ModifyPendingEveryTick = true)
        
        c) Modification of each open order on each new bar in the preset period (SL and TP)
               (ModifyMarketBarPeriod = ...)
        
        d) Modification of each pending order on each new bar in the preset period (OpenPrice, SL and TP)
               (ModifyPendingBarPeriod = ...)
        
*/
 
   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);
 
/**
      4. 
        a) Close an open order by time
        b) Close an open order by signal
*/
   // tickets of orders to be closed
   int ticketsToClose[][2];
   double lotsToClose[]; 
   
   // zeroize array sizes
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // prepare a ticket array for closing
   if (trSignal!=OP_BALANCE) PrepareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // close the specified orders
   CloseMarketOrders(ticketsToClose,lotsToClose);
 
/**
      5. 
        a) Delete a pending order by time
        b) Delete a pending order by condition
*/
   // tickets of orders to be deleted
   int ticketsToDelete[];
 
   // prepare a ticket array for orders to be deleted
   if (trSignal!=OP_BALANCE) PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // delete the specified orders
   DeletePendingOrders(ticketsToDelete);
 
/**
      6. 
        a) Open an order by market
        b) Place a pending order without expiration
        c) Place a pending order with expiration
*/
 
   if (trSignal!=OP_BALANCE) 
      OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);
   
   if (trSignal!=OP_BALANCE) 
      SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL, 
            pendingTP,pendingComment);
//----
   return(0);
  }
//+------------------------------------------------------------------+

EA 的操作逻辑完全可见,同时具体的实现细节隐藏在相应的函数中。 在进行程序调试时,我们知道应在何处查找错误。 除此之外,Expert Advisor 的整体逻辑区分明确:我们知道我们应在哪个函数中更改代码(如果我们需要更改用于查找“友好”订单的算法),我们知道要在何处插入建仓和平仓的条件,以及我们应在哪个函数中引入追踪止损位(如果需要)。 剩下唯一要做的事情是在实践中使用现成的模板。



使用示例

本文附上了获得的模板 Template_EA.mqt。 您可以将它在文件夹 C:\Program Files\MetaTrader 4\experts\templates\ 中另存为 Expert.mqt。 在此例中,在创建新的 EA 时,此文件将始终用作为模板,您将自动插入上述所有函数。 还有另一个替代方案 - 在同一文件夹中保存此模板,但不更改其名称。 然后,在创建新的 Expert Advisor 时,您将能够手动将此文件指定为模板。

现在,我们可以选择模板 Template_EA,然后按“下一步”。 我们来考虑编写以下简单策略:

让我们将新的 EA 命名为 Osc_test,并引入所需的外部参数:

按“完成”,将看到 Expert Advisor Wizard 已将这些参数插入到基于上述模板创建的 EA 中。

因此,我们在创建 EA 时添加了所需参数:

//+------------------------------------------------------------------+
//|                                                     Osc_test.mq4 |
//|                      Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net"
 
//---- input parameters
extern int       Kperiod=18;//5;
extern int       Dperiod=14;//3;
extern int       slowPeriod=10;//3;
extern int       UpLevel=90;
extern int       DownLevel=10;
extern int       StopLoss=100;
extern int       TakeProfit=100;
extern double    Lots=0.1;
 
// constant "off market"
#define OP_BALANCE 6

现在,我们来在 yourFunction() 中插入用于产生买入和卖出信号的代码:

//+------------------------------------------------------------------+
//|  Function to produce trade signals                               |
//+------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
   double prevValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,2);
   double currValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,1);
 
   if (currValue>DownLevel && prevValue<DownLevel) res=OP_BUY;
   if (currValue<UpLevel && prevValue>UpLevel) res=OP_SELL;
 
//----
   return (res);   
   }

这仅使用了四行。 剩下唯一要做的事情是明确用于按市价准备建仓数据的函数 CalculateNewMarketValue() 。

//+------------------------------------------------------------------+
//|  It calculates the data to open a new order                      |
//+------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots, 
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // if there is no trade signal, exit
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // this means that we won't open
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 
 
//----
   //  insert your own code to calculate all parameters
 
   if (trSignal==OP_BUY  && StopLoss!=0) marketSL = Bid - StopLoss*Point;
   if (trSignal==OP_SELL && StopLoss!=0) marketSL = Ask + StopLoss*Point;
 
   if (trSignal==OP_BUY  && TakeProfit!=0) marketTP = Bid + TakeProfit*Point;
   if (trSignal==OP_SELL && TakeProfit!=0) marketTP = Ask - TakeProfit*Point;
 
   marketLots = Lots;
 
   if (trSignal==OP_BUY) marketType = OP_BUY;
   if (trSignal==OP_SELL) marketType = OP_SELL;
 
   marketcomment="test";
//----
   return;   
   }

正如您所看到的,这也只需要添加 5 (五!)行。 因此,我们仅根据需要编写代码来描述我们的简单策略。 这是此方法的第一个优势。

模板创建要求您精心设计典型的 EA 实现的所有细节。 但是,将来当您创建新的策略时,会给您带来回报。



其他优势

还有更多操作。 我们来在任何符号和任何时间范围上测试获得的 EA。 例如,假设对象为 EURUSD H1,使用默认设置:

在此例中,无论是否盈利都没有关系。 我们来看看测试报告。

交易量为 430 个,其中 241 个为卖出交易,189 个为买入交易。 现在,我们来反转系统:当有卖出交易时,我们来开始买入;当有买入交易时,我们来开始卖出。 为此,我们来在参数 ReversTradeSignal 中设置“true”。 这是系统反转的符号:

在不更改任何其他参数的情况下开始测试。 下面是获得的测试报告:

的确,我们现在有 241 个买入交易和 189 个卖出交易。 卖出交易量和买入交易量进行互换了。 获利交易的百分比也发生了反转。 我们不需要重写编写 EA 来查看反转的 EA 是如何工作的!

然而,这还不完整。 我们还有诸如 TradeDay 的参数,您记得吗? 默认情况下,它等于零,但如果我们希望 EA 仅在星期五进行交易呢? 将它的值设置为 5:

在不触及任何其他参数的情况下开始测试。 查看结果。 我们可以看到测试程序报告,它显示了 EA 仅在星期五用反转系统进行交易时会怎样。

我们看到,在初始的 430 个交易中,仅剩余 81 个交易。 这意味着,其他交易是在一周内的其他日期进行的。 在此例中,我们没有过多的关注结果仍具盈利性。 假设,如果我们允许 EA 在星期五以外的其他日期进行交易,我们想了解 EA 会怎样进行交易? 为此,我们添加了参数 ReversDay - 只需将其切换到“true’。

我们来开始测试,然后查看报告:

有 430 个交易,我们减去星期五的交易 (81),获得了 349 个。 将所有数字加起来: 430-81=349。 因此,我们正确地反转了交易日期。 我们不需要对 EA 重新编程。



总结

我个人认为本文有两个重大缺点。 一方面,本文内容过于简洁,没有对任何函数提供任何详细的说明。 另一方面,对于第一次阅读而言,内容有点过长。 然而,我希望最终会有一批拥护者支持使用此方法来用 MQL4 创建 EA。 此方法适合团队工作,优于任何其他方法: 通过集思广益创建需要的模板,这比仅仅不断地改进某个交易算法更为合理。

您可以为很多符号的同时交易创建一个特殊的模板,您可以为交易函数返回的错误添加进行特殊错误处理的代码。 您还可以添加信息函数、日志记录函数以及基于测试结果或实时交易创建特殊报告的函数。 在选择交易会话之后,您可以针对交易添加一些限制,不仅可以按一周内的日期进行限制,还可以按小时进行限制。 您也可以在单独的 *.mqh 文件中积累所有服务(接口)函数,以便仅查看要重新定义的函数。 此方法真的有很多可能性。

主要优势是,一旦制定完毕,模板可供连续使用。 同时,您的 EA 出现错误的可能性也会降低。