初学者快速入门或简明指南

Dmitriy Parfenovich | 24 十月, 2013

简介

亲爱的读者,您好!本文中,我们会试着为您解释并向您呈现可以如何轻松快速地掌握创建“EA 交易”、使用指标等等原则的要领。本文面向初学者,所以不会包含任何难懂或晦涩的示例。因此,对于那些已经清楚如何编写“EA 交易”程序的人来说,本文可能没有那么多的启示感悟,信息量也不是很充分。

“EA 交易”及其结构

“EA 交易”是一种以 MQL 语言缩写、指定交易执行或搁置情况的程序。

基本上,“EA 交易”的结构可以由超大数量的块构成,但为了方便理解,我想展示的是一个利用 MetaEditor 默认生成的非常简单的示例。

整体的“EA 交易”可以从视觉上划分为 4 个部分,每个部分都负责待执行的某特定部分的工作。

主要的“EA 交易”块 
图 1. 主要的“EA 交易”块

  1. 参数块中包含允许终端以恰当方式处理“EA 交易”的信息。最常见的参数为“EA 交易”版本、生产厂家名称以及一个简要描述。

  2. OnInit() 块会在“EA 交易”被载入终端时获得控制权。其中可能包含与此“EA 交易”初始化相关的各种数据 - 声明变量与数组、获取指标句柄等。也就是说,此块并不包含任何与交易直接关联的函数。

  3. OnDeinit() 块充当的是 OnInit() 块的反方向。会在“EA 交易”完成其操作(“EA 交易”/终端关闭或某“EA 交易”未能成功初始化)时被调用。此块的主要功能之一,即是重新分配“EA 交易”占用且不再需要的内存空间。换而言之,它描述出了删除变量、数组和指标句柄等等的过程。

  4. OnTick() 块每当由服务器接收与交易品种(货币对)相关的新信息时,就会被调用。它会指明交易执行的情况以及交易本身的函数。

MetaEditor 中默认生成的新文档示例
   图 2. MetaEditor 中默认生成的新文档示例

我们利用上例对其进行解释。我们已有一段“空” EA 的代码,一种需要随后填写的“EA 交易”模板。
这里我们可能看到的内容如下:

我前面说过,这种结构可以复杂得多,而且可能由大量的块构成,而不像这个易于掌握的示例这么简单。如果您觉得这个已经满足不了您的需求,则可以添加自己的块。
 

指标及其处理方式

指标是在 MQL 中缩写的小型程序,会在价格图表或其下方的一个独立窗口中显示,允许我们对市场执行技术分析。

所有的指标都可以划分为两种类型:顺势指标摆荡指标

顺势指标一般均于价格图表中绘制,用于识别趋势方向;而摆荡指标则通常见于价格图表下方,用于识别进场点。

大多数指标都至少有一个缓冲区(指标缓冲区),其中包含某给定时间点其读数数据。与“EA 交易”相似,指标拥有其借以计算的交易品种和时间表。

可将指标缓冲区视为一个队列,该队列的最后一个元素是一个运行值。

移动平均线指标示例
   图 3. 移动平均线指标示例

指标缓冲区是一种数组,其中的第一个元素(索引为 0)携带最右侧烛形的相关数据,接下来的元素(索引为 1)则携带右数第二个烛形的相关数据,如此等等。而这种元素布置则被称为时间序列

看一看下方示例:
假设我们的货币对为欧元兑美元,时间表为 1 小时。
首先,我们需要向“EA 交易”添加指标并获取其句柄。

句柄是一种指向指标的独特指针,可以让我们处理程序中任何地方的指标。

int iMA_handle; 
iMA_handle=iMA("EURUSD",PERIOD_H1,10,0,MODE_SMA,PRICE_CLOSE);
我们仔细观察一下。

第一行描述的是一个将用于存储指标句柄的变量。第二行会调用指标(此处为移动平均线指标),指定其参数,并将句柄保存到变量中以备将来使用。
MetaEditor 中键入 "iMA" 后,此行上方就会出现一个工具提示,显示逗号分隔指标调用参数。

“移动平均线”指标参数的工具提示示例
   图 4. “移动平均线”指标参数的工具提示示例

下方我们可以看到从左到右排列的各个参数:

  1. 交易品种名称(工具提示中以粗体字符显示)是一种文本参数,货币对(交易品种);
  2. 时间表
  3. 指标时期(此处为平均时期);
  4. 图表平移向前/向后 N 个柱。正数表示图表向前平移 N 个柱,而负数则表示图表向后平移 N 个柱;
  5. 平均方法
  6. 适用价格或某不同指标的句柄。

有一系列独特的变量以及其针对每一种指标的类型。如果您碰到一个未知指标,其相关信息通常都可以在内置的上下文“帮助”中找到。比如说,一旦您键入 iMA 并按下 F1之后,就会打开一个“帮助”窗口,提供该特定指标的相关信息,以及其所有属性的详尽描述。

通过按下 F1 调用该指标描述“帮助”窗口示例
   图 5. 通过按下 F1 调用该指标描述“帮助”窗口示例

代码编写完成并于终端中启动“EA 交易”后,我们就会发现(一旦价格图表的右上角出现“EA 交易”)图表中的指标不见了。这并非错误 - 完全在意料当中。想令其出现,我们需要再添加一行:

ChartIndicatorAdd(ChartID(),0,iMA_handle);

现在,我们看看结果如何。鼠标指针悬停于 ChartIndicatorAdd 命令上方,按 F1 阅读该命令用途相关的“帮助”信息。它表示此命令:

将一个带有指定句柄的指标添加到某个指定的图表窗口中。

等于零的第二个参数为子窗口编号。子窗口通常包含摆荡指标,位于价格图表下方。记住没有?这种东西可能会有很多。欲于子窗口中显示指数,您只需要按照比现有编号大 1 (即现有最后一个编号的下一个数字)的标准指定子窗口编号。

代码行已如下变更:

ChartIndicatorAdd(ChartID(),1,iMA_handle);

我们的指标会于价格图表下方的子窗口中显示。

现在,到了尝试从指标获取一些数据的时候了。为此目的,我们声明一个动态数组,为方便起见按时间序列排列数组索引,并将指标值复制到该数组当中。

double iMA_buf[];
ArraySetAsSeries(iMA_buf,true);
CopyBuffer(iMA_handle,0,0,3,iMA_buf);

上述示例表明,我们已经将双型动态数组 iMA_buf[] 声明为“移动平均线”指标(基于价格及价格片段)。

下一行则按照较小索引号元素存储较旧值、而较大索引号元素则存储较新值的原则设置数组的索引。如此使用是为了方便以避免混淆,因为所有指标中的指标缓冲区都按时间序列索引。

最后一行用于将指标值复制到 iMA_buf[] 数组中。这些数据已经可以使用了。

 

订单、交易与持仓

我们从订单开始。

市价单是指按当前市价卖出或买入特定数量指定金融工具的指令。
挂单则指根据特定情况执行交易的指令。挂单有一个特定的到期时间,届时即被删除。

为了更清楚地表达,我们来看一个例子:我们建一个 1 手的长仓,也就是说,我们(例如)以当前市价下一个手数为 1 的订单。如请求有效,则会被发往服务器进行处理。只要处理完成,此终端的 "Trade" (交易)选项卡中就会出现带订单参数的持仓。假设我们之后决定建另一个同为 1 手的长仓。待此订单处理完成后,我们并不会在 "Trade" 选项卡中看到两个订单,而是一个 2 手的持仓。即,持仓是一系列订单的执行成果。

现在我们继续练习。如欲制作一个请求,需要填充下述结构字段:

struct MqlTradeRequest
{
ENUM_TRADE_REQUEST_ACTIONS action; // 操作类型
ulong magic; // EA交易的ID(幻数)
ulong order; // 订单号
string symbol; // 交易工具
double volume; // 请求的交易手数
double price; // 价格 
double stoplimit; // 订单的StopLimit水平
double sl; // 订单的止损水平
double tp; // 订单的获利水平
ulong deviation; // 请求价格的最大允许点差
ENUM_ORDER_TYPE type; // 订单类型
ENUM_ORDER_TYPE_FILLING type_filling; // 订单的执行类型
ENUM_ORDER_TYPE_TIME type_time; // 订单的持续类型
datetime expiration; // 订单过期时间(订单的ORDER_TIME_SPECIFIED类型)
string comment; // 订单的备注
};

因为订单多种多样,每种订单类型又都有其各自的强制参数集。我可不会长篇大论地讲解这些字段。网站上已就此提供了大量的相关信息。如果某特定订单类型的强制参数连一个都未指定(或未正确指定),则请求会失败。

上述结构在此处的布局,只是为了方便论证填写时产生的困难。

“止损”与“获利”

“止损”与“获利”都是作为“后备措施”下达的特殊订单。即,如有错误或是“EA 交易”建了一个呈现损失的仓位,则“止损”订单可将损失限定于某个特定的预定义价位处。

“获利”与其类似,只是这次是限制利润。不再为平仓担心可能会变得很有必要。一旦达到某特定的价位,它将会平仓。也就是说,如果市场对我们不利或是我们想要获利,这些订单就是我们的“保险方案”。

这种类型的订单不能自行独立下达 - 只能修改现有的持仓。

采用标准库

好,我们终于走到了标准库。此库随本终端一同提供,并由此得名 - 标准库。它由方便“EA 交易”编程及部分承担复杂过程(比如交易请求生成)的各种函数构成。

交易库(亦见交易类)位于下述路径:Include\Trade\ 且可利用 #include 指令添加。
例如:

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>

上述类会被视作基础类,因为只利用这两个类(库),即可实现大多数“EA 交易”的编程。我称其为库:


有些时候,还有一种库很有用:
#include <Trade\OrderInfo.mqh>
它包含操作订单的相关函数,比如说,我们的策略要求采用挂单。

还记得要求妥善运用知识、含各种参数的交易请求结构吗?
现在,我会向大家展示一个利用此库完成交易请求的示例:
CTrade m_Trade;
m_Trade.Sell(lot,symbol_name,price,sl,tp,comment);

这里共有 6 个参数,其中只有一个为必填(订单数量 - 此为第一参数)。
现在我就分别指定:

平仓的方法有许多种:

  1. 完全平仓
    CPositionInfo m_Position;
    m_Position.Select(symbol_name);
    m_Trade.PositionClose(symbol_name);
    
  2. 通过下达一个相同数量的反向订单平仓
    CTrade m_Trade;
    m_Trade.Buy(lot,symbol_name,price,sl,tp,comment);
    
  3. 采用一种更加复杂的方法,借此搜遍所有持仓,挑选出符合待进一步平仓的参数(交易品种、类型、魔术数字、仓位识别等)要求的一种。
    因其对于初学者而言很难,所以我就不给示例了。

把所有内容串起来

现在,到了把新习得的知识串成一个“EA 交易”的时候了。

//+------------------------------------------------------------------+
//|                                           fast-start-example.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| EA初始化函数                                                  |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>                                         //包含执行交易的库
#include <Trade\PositionInfo.mqh>                                  //包含获取持仓信息的库

int               iMA_handle;                              //存储指标句柄的变量
double            iMA_buf[];                               //存储指标值的动态数组
double            Close_buf[];                             //存储每个柱形收盘价格的动态数组

string            my_symbol;                               //存储交易品种的变量
ENUM_TIMEFRAMES   my_timeframe;                             //存储时间框架的变量

CTrade            m_Trade;                                 //执行交易的结构体
CPositionInfo     m_Position;                              //获取持仓信息的结构体
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   my_symbol=Symbol();                                      //保存当前图表的交易品种,用于此EA对其进一步的操作。
   my_timeframe=PERIOD_CURRENT;                              //保存图表的当前时间框架,用于此EA对其的进一步操作。
   iMA_handle=iMA(my_symbol,my_timeframe,40,0,MODE_SMA,PRICE_CLOSE);  //应用此指标并获取指标句柄
   if(iMA_handle==INVALID_HANDLE)                            //检查指标句柄是否可用
   {
      Print("Failed to get the indicator handle");              //如果句柄没有获取到,打印相关报错信息到日志文件中
      return(-1);                                           //完成报错处理
   
}
   ChartIndicatorAdd(ChartID(),0,iMA_handle);                  //将指标添加到价格图表中
   ArraySetAsSeries(iMA_buf,true);                            //将iMA_buf数组的索引设置为时间序列
   ArraySetAsSeries(Close_buf,true);                          //将Close_buf数组的索引设置为时间序列
   return(0);                                               //返回0,初始化结束
  
}
//+------------------------------------------------------------------+
//| EA去初始化函数                                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   IndicatorRelease(iMA_handle);                             //删除指标句柄并释放分配给它的存储空间
   ArrayFree(iMA_buf);                                      //释放动态数组iMA_buf的数据
   ArrayFree(Close_buf);                                    //释放动态数组Close_buf的数据
  
}
//+------------------------------------------------------------------+
//| EA的tick函数                                                      |
//+------------------------------------------------------------------+
void OnTick()
  {
   int err1=0;                                             //用于存储指标缓存处理结果的变量
   int err2=0;                                             //用于存储价格图表处理结果的变量
   
   err1=CopyBuffer(iMA_handle,0,1,2,iMA_buf);               //将数据从指标数组中拷贝到动态数组iMA_buf中,用于进一步处理
   err2=CopyClose(my_symbol,my_timeframe,1,2,Close_buf);    //将价格图表数据拷贝到动态数Close_buf中,用于进一步处理
   if(err1<0 || err2<0)                                    //如果出错
   {
      Print("Failed to copy data from the indicator buffer or price chart buffer");  //打印相关错误信息到日志文件
      return;                                                               //并退出函数
   
}

   if(iMA_buf[1]>Close_buf[1] && iMA_buf[0]<Close_buf[0])   //如果指标值大于收盘价并且开始变小
     {
      if(m_Position.Select(my_symbol))                     //如果该交易品种的持仓已经存在
        {
         if(m_Position.PositionType()==POSITION_TYPE_SELL) m_Trade.PositionClose(my_symbol);  //并且这是一个卖出持仓,那么平仓
         if(m_Position.PositionType()==POSITION_TYPE_BUY) return;                              //或者,如果是一个买入持仓,那么退出
        
}
      m_Trade.Buy(0.1,my_symbol);                          //如果到这里,说明没有持仓;那么我们开仓
     
}
   if(iMA_buf[1]<Close_buf[1] && iMA_buf[0]>Close_buf[0])  //如果指标值小于收盘价并且在变大
     {
      if(m_Position.Select(my_symbol))                     //如果该交易品种的持仓已经存在
        {
         if(m_Position.PositionType()==POSITION_TYPE_BUY) m_Trade.PositionClose(my_symbol);   //并且这是一个买入持仓,那么平仓
         if(m_Position.PositionType()==POSITION_TYPE_SELL) return;                             //或者,如果这是一个卖出持仓,那么退出
        
}
      m_Trade.Sell(0.1,my_symbol);                         //如果我们到这里,说明没有持仓;那么我们开仓
     
}
  
}
//+------------------------------------------------------------------+

我们如下利用相关参数对“EA 交易”进行测试:

因为我们从第一柱(零柱为当前、活动柱)开始使用指标值与收盘价,所以此图表不会重绘。也就是说,我们可以使用“仅开盘价”交易模式。它不会影响到测试质量,却会令运行加快。

而这里则是采用历史数据的快速测试结果。

我们的“EA 交易”测试结果
   图 6. 我们的“EA 交易”测试结果

赔损当然不能被忽略。但是,本文主旨并不是要编写一个拥有超大利润潜力和极低赔损的“超级‘EA 交易’”,而是要说明一个掌握基础知识的人可以如何轻松地制作一个“EA 交易”。
我们已经完成了由不到一百行代码所构成的“EA 交易”。
 

总结

本文讲到了编制“EA 交易”程序时要考虑到的主要原则。通过学习,我们已经知道了如何使用 MetaEditor 5 中的内置上下文“帮助”来获取各个函数的相关信息,了解了订单和持仓的总体概念,而且掌握了标准库的使用。