MQL5 Cookbook: 在EA交易中使用指标来设置交易条件·进阶篇
「通道类指标的反序取值与信号判定」
当指标类型切到 PCH(价格通道)时,第一件事是把两个缓冲区数组用 ArraySetAsSeries 翻成时间序列序:索引 0 是最新柱、1 是前一柱,依此类推。这一步不做,后面拿到的 buffer[1] 和 buffer[2] 会错位,信号逻辑直接反着跑。 CopyBuffer 拉数据时,AllowedNumberOfSegments 决定要取多少段。若任意一条缓冲区实际复制到的元素数小于该值,就打印当前品种、周期和 GetLastError 描述的失败原因并返回 false,调用方会触发 GetIndicatorHandle 重试。外汇与贵金属杠杆高,这类句柄失效在跳空开盘时概率明显上升,别假设它永远一次成功。 GetSignal 里对 MA 与 CCI 走另一套判定:卖出信号不看绝对值,只看缓冲区的连续递减。AllowedNumberOfSegments 设为 3 时,只要 buffer1[1]<buffer1[2] 就返 SELL;设到 6 时则要求 [1]~[4] 四组相邻值严格递减。把这段改成 >= 判定,就能在 MT5 里快速验证不同段数下信号触发频率的差异。
if(Indicator==PCH) { class=class="str">"cmt">//--- 反转索引顺序 (... class="num">3 class="num">2 class="num">1 class="num">0) ArraySetAsSeries(indicator_buffer1,true); ArraySetAsSeries(indicator_buffer2,true); class=class="str">"cmt">//--- 取得指标值 if(CopyBuffer(indicator_handle,class="num">0,class="num">0,AllowedNumberOfSegments,indicator_buffer1)<AllowedNumberOfSegments || CopyBuffer(indicator_handle,class="num">1,class="num">0,AllowedNumberOfSegments,indicator_buffer2)<AllowedNumberOfSegments) { Print("复制数值失败 ("+ _Symbol+"; "+TimeframeToString(Period())+") (至 indicator_buffer1 或 indicator_buffer2 数组)错误 ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); class="kw">return(class="kw">false); } } class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//--- 如果指标句柄没有获得,重试 else GetIndicatorHandle(); class=class="str">"cmt">//--- class="kw">return(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 检查条件并返回信号 | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_ORDER_TYPE GetSignal() { class=class="str">"cmt">//--- 检查针对移动平均和CCI指标的条件 if(Indicator==MA || Indicator==CCI) { class=class="str">"cmt">//--- 卖出信号 if(AllowedNumberOfSegments==class="num">3 && indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2]) class="kw">return(ORDER_TYPE_SELL); class=class="str">"cmt">//--- if(AllowedNumberOfSegments==class="num">4 && indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2] && indicator_buffer1[class="num">2]<indicator_buffer1[class="num">3]) class="kw">return(ORDER_TYPE_SELL); class=class="str">"cmt">//--- if(AllowedNumberOfSegments==class="num">5 && indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2] && indicator_buffer1[class="num">2]<indicator_buffer1[class="num">3] && indicator_buffer1[class="num">3]<indicator_buffer1[class="num">4]) class="kw">return(ORDER_TYPE_SELL); class=class="str">"cmt">//--- if(AllowedNumberOfSegments==class="num">6 && indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2] && indicator_buffer1[class="num">2]<indicator_buffer1[class="num">3] && indicator_buffer1[class="num">3]<indicator_buffer1[class="num">4] &&
◍ 分段计数驱动的多段顺势信号
这段逻辑把 ZigZag 类缓冲的连续段数(AllowedNumberOfSegments)当成信号强度开关:3 段就只看最近两节高低关系,段数越多要求连续同向的节数也越多,到 7 段及以上必须连续 6 节同向下坡才发 SELL,连续 6 节同上坡才发 BUY。 实际在 MT5 里跑,你会看到段数门槛直接过滤掉震荡期的毛刺——比如 AllowedNumberOfSegments==5 时,indicator_buffer1[1] 到 [4] 必须严格递减才返回 ORDER_TYPE_SELL,任何一节回头就废单。 PCH 分支另算一套:close_price[1] 收在 indicator_buffer2[1] 下方且 open_price[1] 开在上方,才构成一根吞没式阴线候选。外汇与贵金属波动大,这类形态触发后价格仍可能反向,仅作概率倾向参考。
if(AllowedNumberOfSegments>=class="num">7 && indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2] && indicator_buffer1[class="num">2]<indicator_buffer1[class="num">3] && indicator_buffer1[class="num">3]<indicator_buffer1[class="num">4] && indicator_buffer1[class="num">4]<indicator_buffer1[class="num">5] && indicator_buffer1[class="num">5]<indicator_buffer1[class="num">6]) class="kw">return(ORDER_TYPE_SELL); class=class="str">"cmt">//--- 买入信号 if(AllowedNumberOfSegments==class="num">3 && indicator_buffer1[class="num">1]>indicator_buffer1[class="num">2]) class="kw">return(ORDER_TYPE_BUY); if(AllowedNumberOfSegments==class="num">4 && indicator_buffer1[class="num">1]>indicator_buffer1[class="num">2] && indicator_buffer1[class="num">2]>indicator_buffer1[class="num">3]) class="kw">return(ORDER_TYPE_BUY); if(AllowedNumberOfSegments==class="num">5 && indicator_buffer1[class="num">1]>indicator_buffer1[class="num">2] && indicator_buffer1[class="num">2]>indicator_buffer1[class="num">3] && indicator_buffer1[class="num">3]>indicator_buffer1[class="num">4]) class="kw">return(ORDER_TYPE_BUY); if(AllowedNumberOfSegments==class="num">6 && indicator_buffer1[class="num">1]>indicator_buffer1[class="num">2] && indicator_buffer1[class="num">2]>indicator_buffer1[class="num">3] && indicator_buffer1[class="num">3]>indicator_buffer1[class="num">4] && indicator_buffer1[class="num">4]>indicator_buffer1[class="num">5]) class="kw">return(ORDER_TYPE_BUY); if(AllowedNumberOfSegments>=class="num">7 && indicator_buffer1[class="num">1]>indicator_buffer1[class="num">2] && indicator_buffer1[class="num">2]>indicator_buffer1[class="num">3] && indicator_buffer1[class="num">3]>indicator_buffer1[class="num">4] && indicator_buffer1[class="num">4]>indicator_buffer1[class="num">5] && indicator_buffer1[class="num">5]>indicator_buffer1[class="num">6]) class="kw">return(ORDER_TYPE_BUY); class=class="str">"cmt">//--- 用于检查 MultiRange_PCH 指标条件的区块 if(Indicator==PCH) { class=class="str">"cmt">//--- 卖出信号 if(close_price[class="num">1]<indicator_buffer2[class="num">1] && open_price[class="num">1]>indicator_buffer2[class="num">1])
持仓状态下的加仓与反手逻辑
当账户里已经有持仓时,EA 不会盲目追新信号,而是先读仓位类型和最后一笔成交价,再结合指标类别决定动作。 若用的是 MA 或 CCI 类指标,多单持仓下出现卖出信号会直接反手;空单持仓下若收盘价跌破 last_deal_price 减去 VolumeIncreaseStep*_Point 的修正值,则倾向加空。 反过来,空单遇买入信号反手做多;多单若收盘价突破 last_deal_price 加上同样步长修正值,可能触发加多。 若指标切换为 PCH(MultiRange_PCH),条件区块独立判断,不沿用上述均线类阈值,实盘前应在 MT5 策略测试器里分别跑两组参数验证触发频率。外汇与贵金属杠杆高,这类加仓逻辑可能快速放大回撤。
if(Indicator==PCH) { class=class="str">"cmt">//--- 卖出信号 if(pos.type==POSITION_TYPE_BUY &&
「信号判定与品种属性结构体」
上面的代码片段给出了网格类 EA 的核心信号分支:当上一根 K 线收在指标缓冲 2 下方且开盘在上方,空单持仓时返回 ORDER_TYPE_SELL;若空单收盘价已低于上次成交价减去 CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point),同样加空。反向逻辑对称:空单收在指标缓冲 1 上方且开盘在下方平空反多,多单超过上次成交价加步长则加多。 没有命中任何条件时统一 return(WRONG_VALUE),这意味着主循环里要靠这个值过滤掉静默周期,避免无谓下单。外汇与贵金属杠杆高,这种加码逻辑在单边行情中回撤可能快速放大,实盘前务必在 MT5 策略测试器跑至少 3 个月 tick 数据。 代码后半段定义了 symbol_properties 结构体,把交易品种的关键属性一次性打包:digits 决定报价精度,point 是点值,spread 是点差点差,stops_level 和 up_level/down_level 框住止损摆放边界。execution_mode 用 ENUM_SYMBOL_TRADE_EXECUTION 标记成交模式(即时、市价、交易所等),写跨品种 EA 时直接读这个字段切换下单方式,比硬编常量稳。 把结构体塞进全局缓存后,每次 OnTick 不必反复调 SymbolInfoInteger / SymbolInfoDouble,仅用本地成员做判断,回测中处理 20+ 品种时耗时倾向下降一个数量级。
close_price[class="num">1]<indicator_buffer2[class="num">1] && open_price[class="num">1]>indicator_buffer2[class="num">1]) class="kw">return(ORDER_TYPE_SELL); class=class="str">"cmt">//--- if(pos.type==POSITION_TYPE_SELL && close_price[class="num">1]<pos.last_deal_price-CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point)) class="kw">return(ORDER_TYPE_SELL); class=class="str">"cmt">//--- 买入信号 if(pos.type==POSITION_TYPE_SELL && close_price[class="num">1]>indicator_buffer1[class="num">1] && open_price[class="num">1]<indicator_buffer1[class="num">1]) class="kw">return(ORDER_TYPE_BUY); class=class="str">"cmt">//--- if(pos.type==POSITION_TYPE_BUY && close_price[class="num">1]>pos.last_deal_price+CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point)) class="kw">return(ORDER_TYPE_BUY); } } class=class="str">"cmt">//--- 没有信号 class="kw">return(WRONG_VALUE); } class=class="str">"cmt">//--- 交易品种属性 class="kw">struct symbol_properties { class="type">int digits; class=class="str">"cmt">// 价格中的小数点位数 class="type">int spread; class=class="str">"cmt">// 点差点数 class="type">int stops_level; class=class="str">"cmt">// 止损级别 class="type">class="kw">double point; class=class="str">"cmt">// 点值 class="type">class="kw">double ask; class=class="str">"cmt">// 买价 class="type">class="kw">double bid; class=class="str">"cmt">// 卖价 class="type">class="kw">double volume_min; class=class="str">"cmt">// 交易的最小交易量 class="type">class="kw">double volume_max; class=class="str">"cmt">// 交易的最大交易量 class="type">class="kw">double volume_limit; class=class="str">"cmt">// 仓位以及单方向订单的最大允许交易量 class="type">class="kw">double volume_step; class=class="str">"cmt">// 交易中交易量改变的最小步长 class="type">class="kw">double offset; class=class="str">"cmt">// 事务中价格的最大可能偏移 class="type">class="kw">double up_level; class=class="str">"cmt">// 止损价位上限 class="type">class="kw">double down_level; class=class="str">"cmt">// 止损价位下限 ENUM_SYMBOL_TRADE_EXECUTION execution_mode; class=class="str">"cmt">// 执行模式 }; class=class="str">"cmt">//--- 仓位属性的枚举 enum ENUM_SYMBOL_PROPERTIES {
◍ 把交易品种属性一次性读进结构体
上面这段枚举把 15 个关键字段编了号,从 0 到 14,覆盖报价精度、点差、止损位、点值、买卖价、最小最大成交量以及执行模式。编号 13 的 S_EXECUTION_MODE 单独用 case 分支去读 SYMBOL_TRADE_EXEMODE,返回的是 ENUM_SYMBOL_TRADE_EXECUTION 类型,决定该品种是市价执行还是交易所式撮合。 S_ALL 分支则是暴力全量刷新:digits 和 spread 走 SymbolInfoInteger 强转 int,ask/bid 用 NormalizeDouble 按 digits 截断,up_level 与 down_level 直接拿 ask/bid 加减 stops_level*point 算出来。外汇与贵金属杠杆高,stops_level 若大于 0 而你挂单距离小于该值,MT5 会直接拒单,开仓前务必用这套把实际边界打印出来核对。 把这段逻辑塞进 EA 的刷新函数,每次 OnTick 或手动触发时跑一遍 S_ALL,就能让后续下单模块随时拿到一致的品种上下文,不用反复查终端。
S_DIGITS = class="num">0, S_SPREAD = class="num">1, S_STOPSLEVEL = class="num">2, S_POINT = class="num">3, S_ASK = class="num">4, S_BID = class="num">5, S_VOLUME_MIN = class="num">6, S_VOLUME_MAX = class="num">7, S_VOLUME_LIMIT = class="num">8, S_VOLUME_STEP = class="num">9, S_FILTER = class="num">10, S_UP_LEVEL = class="num">11, S_DOWN_LEVEL = class="num">12, S_EXECUTION_MODE = class="num">13, S_ALL = class="num">14 }; case S_EXECUTION_MODE: symb.execution_mode=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE); class="kw">break; class=class="str">"cmt">//--- case S_ALL : symb.digits=(class="type">int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); symb.spread=(class="type">int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD); symb.stops_level=(class="type">int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); symb.point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); symb.ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),symb.digits); symb.bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),symb.digits); symb.volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); symb.volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); symb.volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT); symb.volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); symb.offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*symb.point),symb.digits); symb.up_level=NormalizeDouble(symb.ask+symb.stops_level*symb.point,symb.digits); symb.down_level=NormalizeDouble(symb.bid-symb.stops_level*symb.point,symb.digits); symb.execution_mode=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE); class="kw">break; class=class="str">"cmt">//--- class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 开启一个仓位 | class=class="str">"cmt">//+------------------------------------------------------------------+