首页 系统交易 帖子

移动平均线EA:MetaTrader 5中的交易利器

附件
1921.zip (1.81 KB, 下载 0次)

移动平均线EA是MetaTrader 5客户端终端标准包中自带的一个示例,它利用移动平均线指标进行交易。

该EA文件“Moving Average.mq5”存放在“terminal_data_folder\MQL5\Experts\Examples\Moving Average\”目录下。这款EA展示了如何使用技术指标交易历史功能和标准库的交易类。此外,EA还包含一个基于交易结果的资金管理系统。

接下来,我们来看看这款EA的结构及其工作原理。

1. EA属性

//+------------------------------------------------------------------+
//|                                              移动平均线.mq5 |
//|                  版权所有 2009-2013, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "版权所有 2009-2013, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

前五行是注释,接下来的三行使用预处理指令#property设置了MQL5程序的属性(版权、链接、版本)。

运行EA时,这些信息会显示在“常规”选项卡中:


图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. 全局变量

随后声明了全局变量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退出。

//+------------------------------------------------------------------+
//| EA初始化函数                                             |
//+------------------------------------------------------------------+
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逐笔函数                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//---
   if(PositionSelect(_Symbol))
      CheckForClose();
   else
      CheckForOpen();
//---
  }

PositionSelect()函数用于确定当前符号是否有未平仓的持仓。

如果存在未平仓的持仓,则调用CheckForClose()函数,该函数分析市场当前状态并关闭持仓;如果没有,则调用CheckForOpen()函数,检查市场入场条件并在条件满足时开仓。


2.3. OnDeInit()去初始化函数

OnDeInit()在EA从图表中移除时调用。如果程序在运行期间放置了图形对象,则可以将其从图表中移除。

//+------------------------------------------------------------------+
//| EA去初始化函数                                             |
//+------------------------------------------------------------------+
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-损失/DecreaseFactor)。

然后交易量被“规范化”为当前符号的最小允许交易量(stepvol)的倍数。同时请求最小(minvol)和最大可能值(maxvol)的交易量,如果手数超出允许范围,则进行调整。因此,该函数返回计算出的交易量值。


3.2. CheckForOpen()函数

CheckForOpen()用于检查开仓条件,并在交易条件满足时开仓(在本例中为价格穿越移动平均线时)。

//+------------------------------------------------------------------+
//| 检查开仓条件                               |
//+------------------------------------------------------------------+
void CheckForOpen(void)
  {
   MqlRates rt[2];
//--- 复制价格值
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("复制",_Symbol,"的CopyRates失败,没有历史");
      return;
     }
//--- 仅在新柱的第一个tick交易
   if(rt[1].tick_volume>1)
      return;
//--- 获取移动平均线指标的当前值 
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer从iMA失败,没有数据");
      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]对应已完成的柱。

通过检查当前柱的tick交易量是否为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("复制",_Symbol,"的CopyRates失败,没有历史");
      return;
     }
//--- 仅在新柱的第一个tick交易
   if(rt[1].tick_volume>1)
      return;
//--- 获取移动平均线指标的当前值
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer从iMA失败,没有数据");
      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()函数的算法相似。根据当前未平仓持仓的方向,检查其平仓条件(价格向下穿越买入或向上穿越卖出)。通过调用交易对象(CTrade的实例)的PositionClose()方法来平仓。


4. 回测

可以通过策略测试器来寻找最佳参数值。

例如,在2012年01月01日至2013年08月01日的时间范围内优化移动平均线周期参数时,最佳结果为MovingPeriod=45:

移动平均线EA的回测结果

移动平均线EA的回测结果

结论:

包含在MetaTrader 5终端标准包中的移动平均线EA是使用技术指标交易历史功能和标准库的交易类的示例。此外,该EA还包括一个基于交易结果的资金管理系统。


相关帖子

评论 (0)