交易员生存技巧: 由指标制作的快餐(基础篇)
📘

交易员生存技巧: 由指标制作的快餐(基础篇)

第 1/3 篇

◍ 指标快餐时代的交易员自保

  • 年 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。外汇与贵金属杠杆高,这类封装仅降开发成本,不预示任何信号胜率。

MQL5 / C++
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 数据验证创建成功率。

MQL5 / C++
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 线内吞掉账户几个百分点。

MQL5 / C++
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 默认参数验证延迟。

MQL5 / C++
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 表述方式                                       |

常见问题

有必要。现成指标只给信号不给你风控逻辑,遇到假突破容易盲目跟单,自己写能控制进场条件和仓位。
先别急着改语法,把旧调用习惯映射到新平台的句柄机制,用 iMACD 拿句柄后再 CopyBuffer 取数,少踩坑。
可以。小布能基于品种页直接跑诊断,把指标的滞后段、假信号区标出来,你省去手动回测的时间。
在报价事件函数里用句柄配合缓冲拷贝,指定索引 1 就能拿到前一根主线值,别在初始化里写会拿不到实时更新。
单品种影响不大,但多品种轮询时老式写法重复计算多,倾向用句柄一次取数更稳。