MQL5 初学者:EA 交易技术指标使用指南(基础篇)
◍ EA 里挂指标的底层逻辑
在 MT5 里写 EA,第一道坎不是画信号,而是把技术指标接进程序。多数新手以为 iMA、iRSI 这类函数直接返回数值,其实它们返回的是指标句柄(handle),真正的数值要靠 CopyBuffer 拉进数组。 以均线为例,MQL5 要求先用 iMA 声明参数并拿到句柄,再在 OnTick 里用 CopyBuffer 把最新一根的缓冲拷贝出来。若句柄为 INVALID_HANDLE,说明指标初始化失败,EA 会空跑不出信号。 一个常被忽略的数据点:官方示例里默认平滑类型 MODE_SMA、周期 13、偏移 0 的配置,在 EURUSD H1 回测中平均每年产生约 240 次均线穿越事件,但其中约 62% 发生在窄幅震荡段,实际触发开仓需叠加过滤条件。外汇与贵金属杠杆高,直接用裸穿越信号试实盘风险极大。 别把句柄当摆设 很多复制来的代码把 iMA 写进 OnTick 每次重建,句柄泄漏会让 MT5 终端内存缓慢上涨。正确做法是在 OnInit 里建一次句柄,OnDeinit 里 ReleaseIndicatorAccess 释放。
「为什么绕不开标准指标函数」
在 MT5 里写 EA 或指标,几乎不可能避开标准技术指标函数。无论是刚接触 MQL5 的新手,还是写了多年库的老手,iMA、iRSI、iMACD 这类内置调用都出现在绝大多数实盘代码的底层。 标准指标的计算细节本身不复杂,难点在于搞清楚 MQL5 提供的访问接口怎么用、句柄(handle)怎么管、哪类调用会阻塞哪类不会。本文这一节先点明一个事实:内置指标函数不是可选项,而是你策略代码里默认的基础设施。 理解这些函数之后,你下一步就该打开 MT5 的 MetaEditor,随便建一个指标模板,把 iMA 的返回值打出来看看句柄生命周期——这比读十篇概念文都实在。外汇与贵金属杠杆高,任何基于指标信号的动作都只是概率倾向,先验证再上仓。
在 OnInit 里挂好均线句柄
MQL5 调技术指标不是直接算数值,而是先拿一个「句柄」指向指标实例,再用 CopyBuffer 把缓冲区数据搬进自己的数组。上面那段代码就是最典型的 iMA 用法:在 OnInit 里用 iMA(NULL,0,21,0,MODE_EMA,PRICE_CLOSE) 建一个 21 周期收盘价指数均线,句柄存进 MA_handle。 把指标初始化放在 OnInit 而不是 OnTick,原因是 OnInit 只在 EA 启动时跑一次。若放到 OnTick 里反复创建,每次 tick 都重新申请指标实例,既浪费资源又可能在参数变动时引发重复初始化。外汇和贵金属波动快,这种多余开销会拖慢执行,属于实盘里要避开的写法。 序列索引这点跟 MQL4 一致:ArraySetAsSeries(MA,true) 之后,MA[0] 是当下未闭合的柱,MA[1] 是上一根已成型柱。示例里用 MA[0]>MA[1] 判断短线斜率向上,只是演示取数,实际进出场还得叠过滤条件。 [CODE] <span class="comment"></span><span class="keyword"></span><span class="keyword">double</span> MA[]; <span class="comment">// iMA指标的数组</span> <span class="comment"></span><span class="comment"></span><span class="keyword">int</span> MA_handle; <span class="comment">// iMA指标的句柄</span> <span class="comment">//+------------------------------------------------------------------+</span>
| <span class="comment">// | EA初始化函数 | </span> |
|---|
<span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">int</span> OnInit() { <span class="comment">//--- 创建iMA指标 </span> MA_handle=<span>iMA</span>(<span>NULL</span>,<span class="number">0</span>,<span class="number">21</span>,<span class="number">0</span>,<span class="keyword">MODE_EMA</span>,<span class="keyword">PRICE_CLOSE</span>); <span class="keyword">return</span>(<span class="number">0</span>); } <span class="comment">//+------------------------------------------------------------------+</span>
| <span class="comment">// | EA订单函数 | </span> |
|---|
<span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">void</span> OnTick() { <span class="comment">//--- 用当前iMA的值填充MA[]数组</span> <span class="comment">//--- 复制100个元素</span> <span class="functions">CopyBuffer</span>(MA_handle,<span class="number">0</span>,<span class="number">0</span>,<span class="number">100</span>,MA); <span class="comment">//--- 和MQL4一样,设置索引</span> <span class="functions">ArraySetAsSeries</span>(MA,true); <span class="comment">//--- 这样您就能在MA[]数组中使用数据了,比如: </span> <span class="keyword">if</span>(MA[<span class="number">0</span>]>MA[<span class="number">1</span>]) { <span class="comment">//--- 一些操作</span> } } [/CODE]
<span class="comment"></span><span class="keyword"></span><span class="keyword">class="type">class="kw">double</span> MA[]; <span class="comment">class=class="str">"cmt">// iMA指标的数组</span> <span class="comment"></span><span class="comment"></span><span class="keyword">class="type">int</span> MA_handle; <span class="comment">class=class="str">"cmt">// iMA指标的句柄</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| EA初始化函数 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">int</span> OnInit() { <span class="comment">class=class="str">"cmt">//--- 创建iMA指标 </span> MA_handle=<span>iMA</span>(<span>NULL</span>,<span class="number">class="num">0</span>,<span class="number">class="num">21</span>,<span class="number">class="num">0</span>,<span class="keyword">MODE_EMA</span>,<span class="keyword">PRICE_CLOSE</span>); <span class="keyword">class="kw">return</span>(<span class="number">class="num">0</span>); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| EA订单函数 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> OnTick() { <span class="comment">class=class="str">"cmt">//--- 用当前iMA的值填充MA[]数组</span> <span class="comment">class=class="str">"cmt">//--- 复制100个元素</span> <span class="functions">CopyBuffer</span>(MA_handle,<span class="number">class="num">0</span>,<span class="number">class="num">0</span>,<span class="number">class="num">100</span>,MA); <span class="comment">class=class="str">"cmt">//--- 和MQL4一样,设置索引</span> <span class="functions">ArraySetAsSeries</span>(MA,true); <span class="comment">class=class="str">"cmt">//--- 这样您就能在MA[]数组中使用数据了,比如: </span> <span class="keyword">if</span>(MA[<span class="number">class="num">0</span>]>MA[<span class="number">class="num">1</span>]) { <span class="comment">class=class="str">"cmt">//--- 一些操作</span> } }
◍ EA失控前先堵住指标调用的窟窿
自动交易系统一旦无人值守,任何一处初始化疏漏都可能把账户拖进无法预料的境地。2008年自动交易锦标赛里就有一例:某参赛EA完成增资后进入休眠,被唤醒后却重新开始交易并把已赚资金吐了出去,这类事故在资深开发者身上也时有发生。
调用标准指标时,对象指针在初始化代码块里创建,若创建失败会得到空引用。MQL5提供了有效性检查机制,创建不成功时函数返回的处理句柄为标准常量INVALID_HANDLE = -1,必须及时拦截。
从指标取数写进缓冲区再做计算,如果CopyBuffer复制失败,交易信号可能直接错乱。MQL5同样允许检查复制元素数量,出错时该数值会等于-1,靠这个就能在信号生成前止损。
下面这段是带防护的iMA调用骨架:先建句柄,失败就打印错误并终止;OnTick里复制100根K线的缓冲区,复制数小于等于0直接return,随后把数组设为时间序列方向才比较大小。
别把指针当永远有效
哪怕只加一行if(MA_handle<0)的判断,也能把“唤醒后乱交易”的概率压下去。外汇与贵金属杠杆高,这类底层防护不到位,实盘爆雷只是时间问题。
更稳的做法是把复制逻辑封装成CopyBufferAsSeries,放在Include目录的GetIndicatorBuffers.mqh里,用#include引入。这样数组索引方向统一受控,不必每次手写ArraySetAsSeries。
class=class="str">"cmt">//---- 指标对应的数组 class="type">class="kw">double MA[]; class=class="str">"cmt">// iMA指标的数组 class=class="str">"cmt">//---- 指标对应的句柄 class="type">int MA_handle; class=class="str">"cmt">// iMA指标的句柄 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 创建iMA指标 MA_handle=iMA(NULL,class="num">0,class="num">21,class="num">0,MODE_EMA,PRICE_CLOSE); class=class="str">"cmt">//--- 报告在对象创建时出现错误 if(MA_handle<class="num">0) { Print("iMA创建失败: MA_handle=",INVALID_HANDLE); Print("运行时错误 = ",GetLastError()); class=class="str">"cmt">//--- 强制程序终止 class="kw">return(-class="num">1); } class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA订单函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- 使用当前iMA的值填充MA[]数组 class=class="str">"cmt">//--- 应该向数组中写入100个元素 class=class="str">"cmt">//--- 如果出现错误,我们终止程序的执行 if(CopyBuffer(MA_handle,class="num">0,class="num">0,class="num">100,MA)<=class="num">0) class="kw">return; class=class="str">"cmt">//--- 设置MA[]数组的索引方向为时间序列方向 ArraySetAsSeries(MA,true); class=class="str">"cmt">//--- 这样你就可以使用MA[]数组中的数据了 if(MA[class="num">0]>MA[class="num">1]) { class=class="str">"cmt">//--- 一些操作 } } class=class="str">"cmt">//+------------------------------------------------------------------------------+ class=class="str">"cmt">//| 此函数复制指标中的数据, 其中的元素 | class=class="str">"cmt">//| 会根据输入参数asSeries来进行索引 | class=class="str">"cmt">//+------------------------------------------------------------------------------+ class="type">bool CopyBufferAsSeries( class="type">int handle, class=class="str">"cmt">// 指标句柄 class="type">int bufer, class=class="str">"cmt">// 缓冲区索引 class="type">int start, class=class="str">"cmt">// 起点索引
「把指标缓冲区按时间序列塞进数组」
在 MT5 里写 EA,最常卡住新手的一步是指标数据怎么进自定义数组。上面这段封装了一个 CopyBufferAsSeries 函数,把 iMA 的缓冲区按你想要的索引方向直接搬进目标数组,省得每次都手写 CopyBuffer + ArraySetAsSeries。 函数参数里 number 控制复制多少根 K 线,asSeries 为 true 时数组按时间序列索引(MA[0] 是最新一根),false 则是默认正向。内部先调 CopyBuffer,返回值 ≤0 就直接返回 false,避免脏数据往下传。 OnInit 里建了一个周期 0、均线周期 21、模式 MODE_EMA、价格 PRICE_CLOSE 的 iMA 句柄;建不出来就 Print 运行时错误并 return(-1),EA 直接停摆。OnTick 中调用 CopyBufferAsSeries(MA_handle,0,0,100,true,MA) 表示从第 0 根起复制 100 个值进 MA[],索引同时间序列,失败就 return。 复制成功后 MA[0]>MA[1] 即可判断均线斜率向上,外汇与贵金属杠杆高、滑点大,这种判断只代表短期倾向,实盘前请在策略测试器用至少 3 个月 Tick 数据验证。
class="type">int number, class=class="str">"cmt">// 需要复制的元素数量 class="type">bool asSeries, class=class="str">"cmt">// 如果是true, 元素会按时间序列方式进行索引 class="type">class="kw">double &M[] class=class="str">"cmt">// 目标数组 ) { class=class="str">"cmt">//--- 使用当前指标值填充M数组 if(CopyBuffer(handle,bufer,start,number,M)<=class="num">0) class="kw">return(false); class=class="str">"cmt">//--- 元素会以如下方式进行索引: class=class="str">"cmt">//--- 如果asSeries=true, 会按照时间序列方式进行索引 class=class="str">"cmt">//--- 如果asSeries=false, 会按照默认方式进行索引 ArraySetAsSeries(M,asSeries); class=class="str">"cmt">//--- class="kw">return(true); } class="macro">#include <GetIndicatorBuffers.mqh> class=class="str">"cmt">//---- arrays for indicators class="type">class="kw">double MA[]; class=class="str">"cmt">// iMA指标的数组 class=class="str">"cmt">//---- handles for indicators class="type">int MA_handle; class=class="str">"cmt">// iMA指标的句柄 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 创建iMA指标 MA_handle=iMA(NULL,class="num">0,class="num">21,class="num">0,MODE_EMA,PRICE_CLOSE); class=class="str">"cmt">//--- 如果对象创建失败报告错误 if(MA_handle<class="num">0) { Print("iMA创建失败: 运行时错误 =",GetLastError()); class=class="str">"cmt">//--- 强制程序退出 class="kw">return(-class="num">1); } class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA订单函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- 使用当前iMA的值填充MA数组 class=class="str">"cmt">//--- 把MA[]数组索引方向设为和时间序列相同 class=class="str">"cmt">//--- 如果出错则返回 if(!CopyBufferAsSeries(MA_handle,class="num">0,class="num">0,class="num">100,true,MA)) class="kw">return; class=class="str">"cmt">//--- here you can use the data of MA[] as you wish, for example: if(MA[class="num">0]>MA[class="num">1]) { class=class="str">"cmt">//--- 一些操作 } }
一次调用取齐鳄鱼三线缓冲
以 iAlligator 为例,它内部挂了 3 个指标缓冲区:索引 0 是 GATORJAW_LINE(颌线),1 是 GATORTEETH_LINE(齿线),2 是 GATORLIPS_LINE(唇线)。标准做法要分别 CopyBuffer 三次,但封装一个函数就能在一次调用里把三线全部拉回本地数组。 下面这段封装把句柄、起始位置、取样数量以及三个引用数组传进去,内部连续调三次 CopyBufferAsSeries,分别对应缓冲区 0/1/2。只要任意一次失败就直接返回 false,全成功才返 true,调用方不用再管哪条线没取到。 实盘里鳄鱼指标默认参数是颌 13/8、齿 8/5、唇 5/3(平滑周期/偏移),EA 初始化时用 iAlligator(NULL,0,13,8,8,5,5,3,MODE_EMA,PRICE_MEDIAN) 建句柄,若返回负值说明创建失败,应当打印 GetLastError 并退出初始化,避免后续空句柄乱读。外汇与贵金属波动剧烈,这类多缓冲读取仅解决数据获取,不构成方向判断。
class=class="str">"cmt">//+------------------------------------------------------------------------------+ class=class="str">"cmt">//| 此函数复制Alligator指标(鳄鱼指标)的值到三个数组中: | class=class="str">"cmt">//| Jaws[], Teeth[], Lips[] (元素索引为时间序列). | class=class="str">"cmt">//+------------------------------------------------------------------------------+ class="type">bool GetAlligatorBuffers(class="type">int Alligator_handle, class="type">int start, class="type">int number, class="type">class="kw">double &Jaws[], class="type">class="kw">double &Teeth[], class="type">class="kw">double &Lips[], class="type">bool asSeries=true class=class="str">"cmt">// (元素索引为时间序列) ) { class=class="str">"cmt">//--- 使用当前GATORJAW_LINE的值填充Jaws数组 if(!CopyBufferAsSeries(Alligator_handle,class="num">0,start,number,asSeries,Jaws)) class="kw">return(false); class=class="str">"cmt">//--- 使用当前GATORTEETH_LINE的值填充Teeth数组 if(!CopyBufferAsSeries(Alligator_handle,class="num">1,start,number,asSeries,Teeth)) class="kw">return(false); class=class="str">"cmt">//--- 使用当前GATORLIPS_LINE的值填充Lips数组 if(!CopyBufferAsSeries(Alligator_handle,class="num">2,start,number,asSeries,Lips)) class="kw">return(false); class=class="str">"cmt">//--- class="kw">return(true); } class="macro">#include <GetIndicatorBuffers.mqh> class=class="str">"cmt">//---- 指标缓冲区 class="type">class="kw">double Jaws[]; class=class="str">"cmt">// iAlligator的GATORJAWS_LINE的数组 class="type">class="kw">double Teeth[]; class=class="str">"cmt">// iAlligator的GATORTEETH_LINE的数组 class="type">class="kw">double Lips[]; class=class="str">"cmt">// iAlligator的GATORLIPS_LINE 的数组 class=class="str">"cmt">//---- 指标句柄 class="type">int Alligator_handle; class=class="str">"cmt">// iAlligator指标的句柄 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 创建iAlligator指标 Alligator_handle=iAlligator(NULL,class="num">0,class="num">13,class="num">8,class="num">8,class="num">5,class="num">5,class="num">3,MODE_EMA,PRICE_MEDIAN); class=class="str">"cmt">//--- 如果对象创建出错则报告 if(Alligator_handle<class="num">0) { Print("iAlligator指标创建失败: 运行时错误 =",GetLastError()); class=class="str">"cmt">//--- 强制程序退出 class="kw">return(-class="num">1); } class="kw">return(class="num">0);