交易员生存技巧: 由指标制作的快餐·进阶篇
把 MACD 封装成一行取数函数
在 MT5 里直接反复调 iMACD 句柄很啰嗦,老手通常把它包一层:传入品种、周期和三个均线周期,返回指定 buffer 与 shift 的当前值。下面这段就是一个典型的 MQL5 封装,把指标句柄、CopyBuffer 和越界判断全塞进一个 double 函数里。 代码里 fast_ema_period、slow_ema_period、signal_period 分别对应快线、慢线和信号线周期,buffer 参数决定取主图柱还是信号线(0 通常是 MACD 主线,1 是信号线)。shift=0 拿最新一根,shift=1 就是前一根——回测里常靠这个比对穿越。 外汇与贵金属杠杆高,这类取数函数只是工具,信号真假仍要看上下文,用错周期可能频繁假突破。开 MT5 把这段贴进脚本,改 symbol 为 XAUUSD 跑一下,能直观看到不同 shift 下的柱值差异。
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">// 缓存区 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); }
「把 iAC 包成 MQL4 式取数函数」
在 MT5 里直接写 iAC(symbol, timeframe, shift) 这种 MQL4 风格调用是编不过的,得先拿指标句柄再 CopyBuffer。下面这段把 Accelerator Oscillator 封装成旧版语法,方便从 MQL4 迁移过来的老代码少改几行。 关键点在 NaN 的处理:用 double NaN=double("nan") 造一个安静的缺失值,函数开头 result 先等于它,句柄无效或拷贝失败就原样返回,不会让调用方拿到 0 误判成真实信号。外汇与贵金属波动大,这种缺失标记能避免脏数据进策略。 OnTick 里另有一行 iMACD 取主缓存索引 1,即倒数第二根 BAR 的 MACD 值(12/26/9 参数,收盘价),用 Comment 打到图表左上。开 MT5 把下面代码贴进 EA,切到 XAUUSD 的 M5,能直接看见 MACD 主线前一柱数值随 tick 刷新。
class="type">void OnDeinit(class="kw">const class="type">int reason) { 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,MAIN_LINE,class="num">1); Comment("MACD, 主缓存区, 索引 class="num">1: ",DoubleToString(macd_main_1,Digits()+class="num">1)); } class="type">class="kw">double NaN=class="type">class="kw">double("nan"); class="type">class="kw">double iAC( class="type">class="kw">string symbol, class=class="str">"cmt">// 品名 ENUM_TIMEFRAMES timeframe, class=class="str">"cmt">// 时间帧 class="type">int shift class=class="str">"cmt">// 偏移 ) { class="type">class="kw">double result=NaN; class=class="str">"cmt">//--- class="type">int handle=iAC(symbol,timeframe); if(handle==INVALID_HANDLE) { Print(__FUNCTION__,": INVALID_HANDLE 错误=",GetLastError()); class="kw">return(result); } class="type">class="kw">double val[class="num">1]; class="type">int copied=CopyBuffer(handle,class="num">0,shift,class="num">1,val); if(copied>class="num">0) result=val[class="num">0]; else Print(__FUNCTION__,": CopyBuffer 错误=",GetLastError()); class="kw">return(result); } class="type">void OnStart() {
◍ NaN 比较陷阱与布林通道缓冲区编号差异
在 MQL5 里用 double("nan") 造出的 NaN,跟任何有限数或 0 做大小、相等比较都返回 false。下面这段最小示例直接在 MT5 跑,打印结果全是 false:NaN>10.3=false、NaN<-5=false、(NaN==0)=false、(NaN==NaN)=false。
这意味着你若用 if(price==EMPTY_VALUE) 或 if(val==val) 去筛指标空值,逻辑会直接失效;正确做法是用 MathIsValidNumber() 或显式判断 val!=val 来捕获 NaN。外汇与贵金属行情在跳空或离线计算时可能产生这类脏值,属于高风险数据异常,务必在 EA 初始化阶段兜底。
顺带看指标缓冲区编号:MQL4 的 iBands 用 0-MODE_MAIN、1-MODE_UPPER、2-MODE_LOWER;MQL5 对应 0-BASE_LINE、1-UPPER_BAND、2-LOWER_BAND,映射关系整齐。但 iEnvelopes 在 MQL5 里变成了 0-UPPER_LINE、1-LOWER_LINE,没有中线缓冲,老代码迁移时若硬套 MQL4 的 0 号为主线会读错数据。
class="type">class="kw">double NaN=class="type">class="kw">double("nan"); class="type">class="kw">double a=class="num">10.3; class="type">class="kw">double b=-class="num">5; class="type">class="kw">double otherNaN=class="type">class="kw">double("nan"); Print("NaN>class="num">10.3=",NaN>a); Print("NaN<-class="num">5=",NaN<b); Print("(NaN==class="num">0)=",NaN==class="num">0); Print("(NaN==NaN)=",NaN==otherNaN); class=class="str">"cmt">//--- 结果 NaN>class="num">10.3=class="kw">false NaN<-class="num">5=class="kw">false (NaN==class="num">0)=class="kw">false (NaN==NaN)=class="kw">false
逐笔报价下两种 MACD 调用的内存账本
把终端最大柱线锁在 100000,开 14 个图表:13 个挂 EA,第 14 个跑 Terminal memory used.mq5,每 10 秒打印一次 TERMINAL_MEMORY_USED,启动后静置不动、不切页面。 对比 iMACd(正规 iMACD 调用)与 MACD MQL4 style EA short(MQL4 风格逐笔调用),初始任务管理器占用分别为 279.7 MB 与 279.9 MB,TERMINAL_MEMORY_USED 都是 745 MB;静跑 10 分钟两者几乎零增长,说明单纯挂着不切周期时,两种写法内存表现一致。 把 13 个图表从 M30 切到 H1 后再看:iMACd 的任务管理器占用跳到 398.0 MB(10 分钟后 398.3 MB),而 MQL4 风格 EA 只到 319.2 MB(10 分钟后 330.5 MB)。TERMINAL_MEMORY_USED 两边分别为 869 与 874 MB。 有意思的是,切高周期后正规 iMACD 反而比 MQL4 风格多吃约 79 MB 物理内存,但终端自报标识符两者只差 5 MB。外汇与贵金属品种多图同跑时,这种隐性开销差异值得在 MT5 任务管理器里自己复测一遍。
「把 MQL4 版 MACD EA 搬进 MQL5 后的速度与内存账」
把经典 MACD Sample.mq4 以最小改动移植到 MQL5,需要包含 IndicatorsMQL4.mqh 以及 CPositionInfo、CTrade、CSymbolInfo、CAccountInfo 等交易类,并补一组指标线名称的 define 模块,才能用 MQL4 风格去抓指标值和下单。 为了兼容三位或五位报价小数,要引入一个 m_adjusted_point 乘数;在 OnInit 里初始化 m_symbol 对象后顺手算好它,OnTick 中调用 RefreshRates() 确认报价非 0.0,再用 NormalizePrice 把开仓价规整到最小变动位。 原版 MQL5 的 MACD Sample 一次从 CopyBuffer 拉两个值(数组维度 2),而 MQL4 风格版主线、信号线、EMA 各取两次单值。我们做了三个对照 EA:标准版、单次取值版、MQL4 风格移植版。 在 MetaQuotes-Demo 上跑 USDJPY M30,区间 2017.02.01–2018.01.16,PC 每次测试后重置。逐笔真实报价模式下标准版耗时 0:01:19.485,单次取值版 0:01:20.344,MQL4 风格版 0:02:37.422——后者比正确访问指标的同类慢约两倍。 内存侧用 Terminal memory used.mq5 每 10 秒打一次 TERMINAL_MEMORY_USED,14 图表环境里标准版 813 MB、单次版 813 MB、MQL4 风格版 818 MB。结论很直接:MQL4 风格移植在内存上几乎无差别,代价全压在执行耗时上。外汇与贵金属交易本身高风险,这类回测只反映历史环境,实盘性能可能随经纪商服务器漂移。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 如果处理过任何仓位, 主函数返回 true | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CSampleExpert::Processing(class="type">void) { class=class="str">"cmt">//--- 更新汇率 if(!m_symbol.RefreshRates()) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 更新指标 if(BarsCalculated(m_handle_macd)<class="num">2 || BarsCalculated(m_handle_ema)<class="num">2) class="kw">return(class="kw">false); if(CopyBuffer(m_handle_macd,class="num">0,class="num">0,class="num">2,m_buff_MACD_main) !=class="num">2 || CopyBuffer(m_handle_macd,class="num">1,class="num">0,class="num">2,m_buff_MACD_signal)!=class="num">2 || CopyBuffer(m_handle_ema,class="num">0,class="num">0,class="num">2,m_buff_EMA) !=class="num">2) class="kw">return(class="kw">false); class=class="str">"cmt">// m_indicators.Refresh(); class=class="str">"cmt">//--- 简化编码并加速访问 class=class="str">"cmt">//--- 数据被放入内部变量 m_macd_current =m_buff_MACD_main[class="num">0]; m_macd_previous =m_buff_MACD_main[class="num">1]; m_signal_current =m_buff_MACD_signal[class="num">0]; m_signal_previous=m_buff_MACD_signal[class="num">1]; m_ema_current =m_buff_EMA[class="num">0]; m_ema_previous =m_buff_EMA[class="num">1]; class=class="str">"cmt">//--- 简化编码并加快访问放入内部变量的数据
◍ 把 MQL4 习惯搬进 MQL5 的指标读取
老脚本党刚切到 MT5 最容易卡在指标调用上。上面这段用 iMACD / iMA 直接取柱值,就是典型的 MQL4 写法:12/26/9 参数的 MACD 主线和信号线,加上一条自定义周期的 EMA,各取当前与上一根两根柱,够做交叉判断用。 真正跑起来时,用 BarsCalculated 先确认两个句柄都至少有 2 根数据,否则直接 return(false) 避免空缓冲崩 EA。注释掉的那几行是早期 CopyBuffer 批量拷 2 根的做法,后来改成每次只拷 1 根进内部变量,访问更快、代码更短。 底部那串 #include 才是 MQL5 正统路数:PositionInfo、Trade、SymbolInfo、AccountInfo 四个对象把仓位和品种信息封装好。若你手头有 MQL4 老策略想重写,挂上 IndicatorsMQL4.mqh 并用 MODE_MAIN / MODE_SIGNAL 宏定义,基本能零改动移植指标读取逻辑。外汇与贵金属杠杆高,回测通过不代表实盘稳,上 MT5 前先开模拟盘验证信号触发节奏。
MacdCurrent=iMACD(NULL,class="num">0,class="num">12,class="num">26,class="num">9,PRICE_CLOSE,MODE_MAIN,class="num">0); MacdPrevious=iMACD(NULL,class="num">0,class="num">12,class="num">26,class="num">9,PRICE_CLOSE,MODE_MAIN,class="num">1); SignalCurrent=iMACD(NULL,class="num">0,class="num">12,class="num">26,class="num">9,PRICE_CLOSE,MODE_SIGNAL,class="num">0); SignalPrevious=iMACD(NULL,class="num">0,class="num">12,class="num">26,class="num">9,PRICE_CLOSE,MODE_SIGNAL,class="num">1); MaCurrent=iMA(NULL,class="num">0,MATrendPeriod,class="num">0,MODE_EMA,PRICE_CLOSE,class="num">0); MaPrevious=iMA(NULL,class="num">0,MATrendPeriod,class="num">0,MODE_EMA,PRICE_CLOSE,class="num">1); class=class="str">"cmt">//--- 更新指标 if(BarsCalculated(m_handle_macd)<class="num">2 || BarsCalculated(m_handle_ema)<class="num">2) class="kw">return(class="kw">false); class=class="str">"cmt">// if(CopyBuffer(m_handle_macd,class="num">0,class="num">0,class="num">2,m_buff_MACD_main) !=class="num">2 || class=class="str">"cmt">// CopyBuffer(m_handle_macd,class="num">1,class="num">0,class="num">2,m_buff_MACD_signal)!=class="num">2 || class=class="str">"cmt">// CopyBuffer(m_handle_ema,class="num">0,class="num">0,class="num">2,m_buff_EMA) !=class="num">2) class=class="str">"cmt">// class="kw">return(class="kw">false); class=class="str">"cmt">// m_indicators.Refresh(); class=class="str">"cmt">//--- 简化编码并加速访问 class=class="str">"cmt">//--- 数据被放入内部变量 CopyBuffer(m_handle_macd,class="num">0,class="num">0,class="num">1,m_buff_MACD_main); m_macd_current=m_buff_MACD_main[class="num">0]; CopyBuffer(m_handle_macd,class="num">0,class="num">1,class="num">1,m_buff_MACD_main); m_macd_previous=m_buff_MACD_main[class="num">0]; CopyBuffer(m_handle_macd,class="num">1,class="num">0,class="num">1,m_buff_MACD_signal); m_signal_current=m_buff_MACD_signal[class="num">0]; CopyBuffer(m_handle_macd,class="num">1,class="num">1,class="num">1,m_buff_MACD_signal); m_signal_previous=m_buff_MACD_signal[class="num">0]; CopyBuffer(m_handle_ema,class="num">0,class="num">0,class="num">1,m_buff_EMA); m_ema_current=m_buff_EMA[class="num">0]; CopyBuffer(m_handle_ema,class="num">0,class="num">1,class="num">1,m_buff_EMA); m_ema_previous=m_buff_EMA[class="num">0]; class="macro">#class="kw">property description "以 MQL4 的风格访问指标" class="macro">#define MODE_MAIN class="num">0 class="macro">#define MODE_SIGNAL class="num">1 class="macro">#include <SimpleCall\IndicatorsMQL4.mqh> class=class="str">"cmt">//--- class="macro">#include <Trade\PositionInfo.mqh> class="macro">#include <Trade\Trade.mqh> class="macro">#include <Trade\SymbolInfo.mqh> class="macro">#include <Trade\AccountInfo.mqh> CPositionInfo m_position; class=class="str">"cmt">// 交易仓位对象 CTrade m_trade; class=class="str">"cmt">// 交易对象 CSymbolInfo m_symbol; class=class="str">"cmt">// 品名信息对象