MetaTrader 5 / EA

移动平均(Moving Average) - MetaTrader 5EA

6517
(53)

包含于MetaTrader 5客户终端标准安装中的移动平均EA交易是一个EA实例, 它使用移动平均指标进行交易.

EA文件, 即 Moving Average.mq5 位于 "terminal_data_folder\MQL5\Experts\Examples\Moving Average\" 文件夹. 这个EA交易也是使用技术指标, 交易历史函数以及标准库中交易类的实例. 另外, 此EA也包含了基于交易结果的资金管理系统.

让我们了解此EA交易的结构以及它的工作原理.

1. EA 属性

//+------------------------------------------------------------------+ //|                                              Moving Averages.mq5 | //|                   Copyright 2009-2013, MetaQuotes Software Corp. | //|                                              https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2009-2013, MetaQuotes Software Corp." #property link      "https://www.mql5.com" #property version   "1.00

前5行代码包含了一个注释, 其后3行使用#property预处理指令设置了MQL5程序的属性(版权, 链接, 版本).

当您运行EA交易时, 它们显示于"Common"页面:


图 1. 移动平均 EA 的通用参数


1.2. 包含文件

下一步, #include指令告诉编译器包含"Trade.mqh"文件.

这个文件是标准库的一部分, 它包含了CTrade类以方便访问交易函数.

#include <Trade\Trade.mqh>

包含文件名显示于括号"<>;"中, 所以路径为相对路径: "terminal_data_folder\Include\".

1.3输入

然后是类型, 名称, 默认值和注释. 它们的角色在图2中显示

input double MaximumRisk        = 0.02;    // 最大风险百分比 input double DecreaseFactor     = 3;       // 递减因子 input int    MovingPeriod       = 12;      // 移动平均周期数 input int    MovingShift        = 6;       // 移动平均转换术 

MaximumRisk和DecreaseFactor参数将用于资金管理, MovingPeriod和MovingShift参数设置移动平均技术指标的周期数和转换数, 将用于检查交易条件.

输入参数行的文本注释, 以及默认值将在"选项"页面显示, 而不是显示输入参数的名称:


图 2. 移动平均EA交易的输入参数

1.4. Global Variables

之后是声明的ExtHandle全局变量. 它将用于保存移动平均指标的句柄.

//--- int   ExtHandle=0;

之后是6个函数. 它们的目的都在函数体之前的注释中有所描述:

  1. TradeSizeOptimized() - 计算优化的手数大小;
  2. CheckForOpen() - 检查建仓条件;
  3. CheckForClose() - 检查平仓条件;
  4. OnInit() - EA初始化函数;
  5. OnTick() - EA订单函数;
  6. OnDeinit() - EA去初始化函数;

最后的三个函数是事件处理函数; 在它们的代码中调用了前面三个服务函数.


2. 事件处理函数

2.1. OnInit() 初始化函数

OnInit()函数在EA交易的第一次启动时调用一次. 通常在OnInit()事件处理函数中, EA交易准备以下操作: 输入参数检查, 初始化指标以及参数等. 如果出现了关键错误, 当进一步的工作都变得没有意义时, 函数退出并返回代码INIT_FAILED.

//+------------------------------------------------------------------+ //| Expert initialization function                                   | //+------------------------------------------------------------------+ int OnInit(void)   { //---    ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);    if(ExtHandle==INVALID_HANDLE)      {       printf("创建 MA 指标错误");       return(INIT_FAILED);      } //---    return(INIT_SUCCEEDED);   } 

因为EA交易是基于移动平均指标的, 通过调用iMA()创建了移动平均指标, 其句柄保存在ExtHandle全局变量中.

如果出现错误, OnInit()退出并返回代码INIT_FAILED - 这是初始化不成功时EA/指标工作结束的正确处理方式.


2.2. OnTick() 函数

OnTick()函数在EA运行时, 交易品种图表上每次收到新报价的时候调用.

//+------------------------------------------------------------------+ //| Expert tick function                                             | //+------------------------------------------------------------------+ void OnTick(void)   { //---    if(PositionSelect(_Symbol))       CheckForClose();    else       CheckForOpen(); //---   }

PositionSelect() 函数用于定义当前交易品种有无开启仓位.

如果已有开启的仓位, 则调用 CheckForClose() 函数, 它会分析当前市场的状态并平仓 否则调用 CheckForOpen() , 它用于检查市场进场条件并在条件达到时建立新的仓位.


2.3. OnDeInit() 去初始化函数

当从图表中移除EA时调用OnDeInit()函数. 如果在运行中创建了图形对象, 它们可以被从图表上移除.

//+------------------------------------------------------------------+ //| Expert deinitialization function                                 | //+------------------------------------------------------------------+ void OnDeinit(const int reason)   {   } //+------------------------------------------------------------------+

在本例中EA交易在去初始化时不进行任何操作.


3. 服务函数

3.1. TradeSizeOptimized() 函数

此函数根据指定的风险水平和交易结果计算并返回优化过的建仓手数.

//+------------------------------------------------------------------+ //| 计算优化的手数                                        | //+------------------------------------------------------------------+ double TradeSizeOptimized(void)   {    double price=0.0;    double margin=0.0; //--- 计算手数    if(!SymbolInfoDouble(_Symbol,SYMBOL_ASK,price))       return(0.0);    if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,margin))       return(0.0);    if(margin<=0.0)       return(0.0);    double lot=NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)*MaximumRisk/margin,2); //--- 计算连续亏损交易的数量    if(DecreaseFactor>0)      {       //--- 请求整个交易历史       HistorySelect(0,TimeCurrent());       //--       int    orders=HistoryDealsTotal();  // 交易总数       int    losses=0;                    // 连续亏损交易数       for(int i=orders-1;i>=0;i--)         {          ulong ticket=HistoryDealGetTicket(i);          if(ticket==0)            {             Print("HistoryDealGetTicket 失败, 没有交易历史");             break;            }          //--- 检查交易的交易品种          if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol)             continue;          //---检查利润          double profit=HistoryDealGetDouble(ticket,DEAL_PROFIT);          if(profit>0.0)             break;          if(profit<0.0)             losses++;         }       //---       if(losses>1)          lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);      } //--- 规范化并检查交易量是否允许    double stepvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);    lot=stepvol*NormalizeDouble(lot/stepvol,0);    double minvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);    if(lot<minvol)       lot=minvol;    double maxvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);    if(lot>maxvol)       lot=maxvol; //--- 返回交易量数值    return(lot);   } 

SymbolInfoDouble()函数用于检查当前交易品种价格, 之后OrderCalcMargin()函数用于取得下单所要求的保证金水平(本例中为买入订单). 初始手数是由下单要求的保证金值, 账户中可用保证金 (AccountInfoDouble(ACCOUNT_FREEMARGIN)) 以及输入参数 MaximumRisk 所指定的最大允许风险共同决定的.

如果 DecreaseFactor 输入参数值为正, 则会分析历史中的交易, 并根据连续亏损数调整手数大小: 初始手数大小乘以 (1-losses/DecreaseFactor).

然后交易量要根据当前交易品种允许的最小步长做舍入. 还需要检查交易量允许的最小 (minvol) 和最大值 ​​(maxvol), 如果手数超出, 还要进行调整. 最终, 函数返回交易量数值.


3.2. CheckForOpen() 函数

CheckForOpen() 用于检查建仓条件, 如果条件符合则建立仓位 (本例中为价格和移动平均交叉).

//+------------------------------------------------------------------+ //| 检查建仓条件                                | //+------------------------------------------------------------------+ void CheckForOpen(void)   {    MqlRates rt[2]; //--- 复制价格数值    if(CopyRates(_Symbol,_Period,0,2,rt)!=2)      {       Print("CopyRates of ",_Symbol," 失败, 没有历史");       return;      } //--- 只在新柱第一个订单进行交易    if(rt[1].tick_volume>1)       return; //--- 取得移动平均当前值     double   ma[1];    if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)      {       Print("从 iMA CopyBuffer失败, 没有数据");       return;      } //--- 检查信号    ENUM_ORDER_TYPE signal=WRONG_VALUE;    if(rt[0].open>ma[0] && rt[0].close<ma[0])       signal=ORDER_TYPE_SELL;    // 卖出条件    else      {       if(rt[0].open<ma[0] && rt[0].close>ma[0])          signal=ORDER_TYPE_BUY;  // 买入条件      } //--- 额外检查    if(signal!=WRONG_VALUE)       if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))          if(Bars(_Symbol,_Period)>100)            {             CTrade trade;             trade.PositionOpen(_Symbol,signal,TradeSizeOptimized(),                                SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ?SYMBOL_BID:SYMBOL_ASK),                                0,0);            } //---   }

当使用移动平均交易时, 您需要检查价格是否和移动平均交叉. 使用CopyRates() 函数, 复制当前价格的两个数值到 rt[], rt[1] 对应当前柱, rt[0] - 已完成柱.

如果当前柱的交易量等于1, 说明开始了一个新柱. 请注意, 在某些情况下(出现成批报价)使用这种方法检查新柱可能会出错, 所以应该比较新柱的时间与当前报价的时间来确定是否出现新柱(参见 IsNewBar).

使用CopyBuffer()函数取得移动平均指标的当前值, 并在ma[]数组中保存它, 其中只包含一个值. 然后程序检查价格是否与移动平均交叉并作额外检查(是否允许EA交易以及历史柱是否存在). 如果成功, 通过调用交易对象(一个CTrade的实例)的PositionOpen()方法建立该交易品种的对应仓位.

使用SymbolInfoDouble() 函数返回买家或者买家报价以取得建仓价格. 仓位的交易量通过以上描述的 TradeSizeOptimized() 函数决定.


3.3. CheckForClose() 函数

CheckForClose() 函数检查平仓条件, 如果条件符合则平仓.

//+------------------------------------------------------------------+ //| 检查平仓条件                               | //+------------------------------------------------------------------+ void CheckForClose(void)   {    MqlRates rt[2]; //--- 复制价格数值    if(CopyRates(_Symbol,_Period,0,2,rt)!=2)      {       Print("CopyRates of ",_Symbol," 失败, 没有历史");       return;      } //--- 只在新柱第一个订单进行交易    if(rt[1].tick_volume>1)       return; //--- 取得移动平均指标的当前值    double   ma[1];    if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)      {       Print("从 iMA CopyBuffer 失败 没有数据");       return;      } //--- 取得之前使用 PositionSelect() 得到的仓位类型    bool signal=false;    long type=PositionGetInteger(POSITION_TYPE);    if(type==(long)POSITION_TYPE_BUY   && rt[0].open>ma[0] && rt[0].close<ma[0])       signal=true;    if(type==(long)POSITION_TYPE_SELL  && rt[0].open<ma[0] && rt[0].close>ma[0])       signal=true; //--- 额外检查    if(signal)       if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))          if(Bars(_Symbol,_Period)>100)            {             CTrade trade;             trade.PositionClose(_Symbol,3);            } //---   }

CheckForClose() 函数的算法和CheckForOpen()的算法类似. 根据当前的建仓方向, 检查平仓条件 (买入则价格向下交叉MA而卖出则向上交叉MA). 通过交易对象(CTrade的实例)的PositionClose()方法可以平仓.


4. 回归测试

可以使用MetaTrader 5客户终端中的策略测试器获得参数的最佳值.

例如, 当在2012年1月1日到2013年8月1日之间优化MovingPeriod参数时, 最佳结果的 MovingPeriod=45:

移动平均EA交易的回归测试结果

移动平均EA交易的回归测试结果

结论:

MetaTrader 5 终端中的移动平均EA交易是一个实例, 它使用了技术指标, 交易历史函数以及标准库中的交易类. 另外, 此EA也包含了基于交易结果的资金管理系统.

由MetaQuotes Ltd译自俄语
原代码: https://www.mql5.com/ru/code/1921