交易员生存技巧: 由指标制作的快餐(基础篇)
◍ 指标快餐时代的交易员自保
- 年 3 月 12 日,一篇在 MT5 社区获得 21 324 次浏览、129 条评论的文章直白点出一个现实:大量交易员依赖指标拼凑出的“快餐”信号做决策,却很少追问底层逻辑。
俄国有句老话——若你想禁止某种很糟糕的东西,那便去允许它。放在外汇与贵金属市场,意思是过度管制交易员的偷懒本能反而无效,不如让其在实盘里自行碰壁。 对资深交易者而言,这类指标快餐的高风险在于:信号滞后与过拟合在震荡行情里会连续触发假突破。开 MT5 把任意默认指标加载到 XAUUSD 的 M15 图,回看最近 3 个月,你能数出多少次指标金叉后价格反向。 真正有用的动作只有一个:把指标当过滤器而非触发器,用价格行为确认后再动手。
「从 MQL4 敌意史看 MQL5 的学习曲线」
- 年 MT4 发布,MQL-II 被 MQL4 替代,当时不少交易者指责这门类 C 语言「太复杂、不可能掌握」。十二年后回看,这类抱怨显得奇怪,但历史正在重演:如今同样有人觉得 MQL5 比 MQL4 更难上手。
这恰恰说明算法交易开发者的整体水平在抬升——MQL5 把更接近 C++ 的工具交给程序员,能最大化检查每个操作结果(交易处理里极关键),也能按需控内存;老 MQL4 语法宽松、这类能力少得多。 关于复杂性的争论大概率会在短时间内被遗忘。对还停留在 MQL4 习惯的人,直接看熟悉函数在 MQL5 里怎么写最实用:刚切到 MT5 时,用接近 MQL4 的风格取指标数据和序列,整体反而更简单,函数清晰、适合单步调试。外汇与贵金属杠杆高,策略代码先在策略测试器跑历史再上模拟盘。
在 MT5 里套用 MQL4 指标调用习惯
MQL4 取指标值常把创建与读取压成一行,比如 iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1) 一步到位;MQL5 则拆成声明句柄、创建并检查、再用独立函数取数三步。若想沿用老写法,可把句柄创建和取数封进一个函数,用起来像 MQL4 但底层仍是 MQL5 句柄逻辑。 文档提醒指标计算要时间,最好在 OnInit() 里建一次句柄。但实测反复以相同参数调用指标函数不会让引用计数翻倍,只增 1 次,MT5 会复用已建句柄,所以封装函数里每次调用也跑得通且不爆内存——不过规范仍建议 OnInit() 收一次句柄,原因后文再讲。 把 double iMACD() 之类塞进 IndicatorsMQL5.mqh,放在 [数据文件夹]\MQL5\Include\SimpleCall,调用时写 MAIN_LINE 而非 MODE_MAIN,代码能短一大截。测试器可视模式跑 'MACD MQL4 style EA short.mq5',光标停 1 号柱能直接核对返回值。 MQL4 风格函数只返 double,出错时返 0.0 或 EMPTY_VALUE(DBL_MAX) 都易误判,因为 MACD 正常值就可能为 0、分形缓冲本就填 EMPTY_VALUE。稳妥做法是全局设 NaN,错误即返 NaN,它和任何数比较都为 false;交易前用 MathIsValidNumber 拦一下即可。 线标识常量也有坑:iAlligator 在 MQL4 是 1/2/3 对应颌/齿/唇,MQL5 是 0/1/2,数字相同意义不同。所以分了 IndicatorsMQL4.mqh 与 IndicatorsMQL5.mqh 两个头文件,前者内部把 MQL4 线号转成 MQL5 用,像 iBands 的 MODE_UPPER 转 1、iEnvelopes 的却转 0,不能简单 #define 覆盖。开 MT5 把两个文件都拖出来对照 include 路径,能少踩不少幽灵 bug。外汇与贵金属杠杆高,这类封装仅降开发成本,不预示任何信号胜率。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| iMACd.mq4 | class=class="str">"cmt">//| 版权所有 © class="num">2018, Vladimir Karputov | class=class="str">"cmt">//| http://wmua.ru/slesar/ | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "版权所有 © class="num">2018, Vladimir Karputov" class="macro">#class="kw">property link "http:class=class="str">"cmt">//wmua.ru/slesar/" class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property strict class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统逆初函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统逐笔报价函数 |
◍ 用句柄拿前一根 MACD 主线值
在 MT5 的 EA 里直接调 iMACD 取数,和先建句柄再读数是两码事。上面那段 OnTick 里的写法是在每次报价时现取前一根(shift=1)的 MACD 主线:快线 12、慢线 26、信号 9、收盘价计算,返回的是已闭合那根 K 线的指标值。 更稳的做法是在 OnInit 里把句柄建好,品种和周期用 Symbol() 与 Period() 跟随当前图表,参数同样 12/26/9/PRICE_CLOSE。若返回 INVALID_HANDLE,说明指标创建失败,代码会打印品种、周期和错误码并终止初始化,避免后面空跑。 外汇与贵金属杠杆高,指标句柄失效可能导致信号漏读,实盘前应在策略测试器用至少 3 个月 tick 数据验证创建成功率。
class="type">void OnTick() { class=class="str">"cmt">//--- class="type">class="kw">double macd_main_1=iMACD(NULL,class="num">0,class="num">12,class="num">26,class="num">9,PRICE_CLOSE,MODE_MAIN,class="num">1); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| iMACD.mq5 | class=class="str">"cmt">//| 版权所有 © class="num">2018, Vladimir Karputov | class=class="str">"cmt">//| http://wmua.ru/slesar/ | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "版权所有 © class="num">2018, Vladimir Karputov" class="macro">#class="kw">property link "http:class=class="str">"cmt">//wmua.ru/slesar/" class="macro">#class="kw">property version "class="num">1.000" class="type">int handle_iMACD; class=class="str">"cmt">// 用于存储 iMACD 指标句柄的变量 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 创建指标 iMACD 的句柄 handle_iMACD=iMACD(Symbol(),Period(),class="num">12,class="num">26,class="num">9,PRICE_CLOSE); class=class="str">"cmt">//--- 如果未能创建句柄 if(handle_iMACD==INVALID_HANDLE) { class=class="str">"cmt">//--- 告之失败并输出错误代码 PrintFormat("创建 iMACD 指标句柄失败, 品种 %s/%s, 错误代码 %d", Symbol(), EnumToString(Period()), GetLastError()); class=class="str">"cmt">//--- 指标提前停止 class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统逆初函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+
「在 OnTick 里抓取 MACD 前一根柱」
EA 的逐笔报价入口 OnTick() 只做一件事:取 MACD 主线在第 1 根柱(即已收盘的那根)的值。注意 index 传 1 而不是 0,0 是当前未定型柱,回测或实盘里拿它做信号容易被毛刺骗。 iMACDGet 是对 CopyBuffer 的薄封装。它先 ResetLastError 清掉旧错误码,再按 buffer 编号(0=MAIN_LINE,1=SIGNAL_LINE)和 index 取 1 个值进长度为 1 的数组;复制失败就打印错误码并返回 0.0,调用方需把这 0.0 当成「指标未就绪」而不是真实零轴交叉。 下面这段是 MQL4 风格的 iMACD 函数头,参数顺序和 MQL5 的 handle 模式不同:品名、周期、快/慢 EMA 周期、信号周期、适用价格、缓存区。MT5 里新写策略建议直接用 iMACDGet 这类封装,少碰旧式多参调用,编译器和运行时都更干净。 外汇与贵金属杠杆高,这类取数封装只解决「拿得对」,不解决「看得准」,信号误触发可能在一两根 K 线内吞掉账户几个百分点。
class=class="str">"cmt">//| 智能系统逐笔报价函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- class="type">class="kw">double macd_main_1=iMACDGet(MAIN_LINE,class="num">1); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 获取 iMACD 缓存区数据值 | class=class="str">"cmt">//| 缓存区编号如下: | class=class="str">"cmt">//| class="num">0 - MAIN_LINE, class="num">1 - SIGNAL_LINE | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double iMACDGet(class="kw">const class="type">int buffer,class="kw">const class="type">int index) { class="type">class="kw">double MACD[class="num">1]; class=class="str">"cmt">//--- 清除错误代码 ResetLastError(); class=class="str">"cmt">//--- 用索引为 class="num">0 的指标缓冲区数值填充 iMACDBuffer 数组的一部分 if(CopyBuffer(handle_iMACD,buffer,index,class="num">1, MACD)<class="num">0) { class=class="str">"cmt">//--- 如果复制失败, 告之错误代码 PrintFormat("从 iMACD 指标复制数据失败, 错误代码 %d",GetLastError()); class=class="str">"cmt">//--- 以零为结果退出 - 意即该指标被视为未计算 class="kw">return(class="num">0.0); } class="kw">return(MACD[class="num">0]); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| iMACD 函数的 MQL4 表述方式 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double iMACD( class="type">class="kw">string symbol, class=class="str">"cmt">// 品名 ENUM_TIMEFRAMES period, class=class="str">"cmt">// 周期 class="type">int fast_ema_period, class=class="str">"cmt">// 快速均线计算周期 class="type">int slow_ema_period, class=class="str">"cmt">// 慢速均线计算周期 class="type">int signal_period, class=class="str">"cmt">// 差值均化周期 ENUM_APPLIED_PRICE applied_price, class=class="str">"cmt">// 处理价格 class="type">int buffer class=class="str">"cmt">// 缓存区
把 MACD 调用改回老式写法
MQL5 标准库用 iMACD 拿句柄再 CopyBuffer,但很多从 MQL4 迁过来的老策略偏好直接一行取值的写法。下面这段把 MACD 主线和信号线缓冲编号用宏定义锁死,MODE_MAIN=0、MODE_SIGNAL=1,避免后面调参时写错缓冲区。 核心取数逻辑是先建句柄,再开一个长度为 1 的数组,用 CopyBuffer 从指定 shift 拷 1 根柱。copied>0 才把 val[0] 赋给 result,否则返回 NULL,这样行情断层时不会拿到脏数。 OnTick 里直接 iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1) 取偏离当前 1 根的主线值,相当于 MQL4 的 iMACD 第九参数。外汇与贵金属杠杆高,这种偏移取数只解决接口兼容,信号本身不预示方向,实盘前请在 MT5 策略测试器跑一遍 12/26/9 默认参数验证延迟。
class="type">int shift class=class="str">"cmt">// 偏移 ) { class="type">class="kw">double result=NULL; class=class="str">"cmt">//--- class="type">int handle=iMACD(symbol,period,fast_ema_period,slow_ema_period,signal_period, applied_price); class="type">class="kw">double val[class="num">1]; class="type">int copied=CopyBuffer(handle,buffer,shift,class="num">1,val); if(copied>class="num">0) result=val[class="num">0]; class="kw">return(result); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| MACD MQL4 style EA.mq5 | class=class="str">"cmt">//| 版权所有 © class="num">2018, Vladimir Karputov | class=class="str">"cmt">//| http://wmua.ru/slesar/ | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "版权所有 © class="num">2018, Vladimir Karputov" class="macro">#class="kw">property link "http:class=class="str">"cmt">//wmua.ru/slesar/" class="macro">#class="kw">property version "class="num">1.000" class="macro">#define MODE_MAIN class="num">0 class="macro">#define MODE_SIGNAL class="num">1 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统逆初函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统逐笔报价函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- class="type">class="kw">double macd_main_1=iMACD(NULL,class="num">0,class="num">12,class="num">26,class="num">9,PRICE_CLOSE,MODE_MAIN,class="num">1); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| iMACD 函数的 MQL4 表述方式 |