创建一个基于布林带PIRANHA策略的MQL5 EA·综合运用
📘

创建一个基于布林带PIRANHA策略的MQL5 EA·综合运用

第 3/3 篇

「布林带均值回归 EA 的初始化骨架」

这套均值回归思路用布林带(12,2)做通道,止损 500 点、止盈 250 点,盈亏比刻意压到 1:0.5,靠高胜率补正期望。外汇与贵金属杠杆高,实盘前务必在策略测试器跑至少 3 个月 Tick 数据。 代码开头先挂版本号并引入 Trade 库,CTrade 对象负责下单。两个 input 把 sl/tp 暴露给面板,方便你按品种波动率改参数。 handleBB 存指标句柄,初始化成 INVALID_HANDLE;bb_upper/bb_lower 两个数组接上下轨。isPrevTradeBuy/Sell 这两个布尔量是防止同方向连做——上笔买就暂不开新买。 OnInit 里 iBands(_Symbol,_Period,12,0,2,PRICE_CLOSE) 建句柄,失败就打印并 INIT_FAILED。数组用 ArraySetAsSeries 置为时间序列,索引 0 是最新值,这点不设置后面取价会错位。 OnDeinit 只做一件事:IndicatorRelease 释放句柄,避免卸载 EA 后指标驻留。OnTick 开头注释写的是取最近 3 根布林值,具体逻辑在下节。

MQL5 / C++
class="macro">#class="kw">property version   "class="num">1.00"                                      class=class="str">"cmt">//--- Version number of the EA
class=class="str">"cmt">//--- Including the MQL5 trading library
class="macro">#include <Trade/Trade.mqh>                                        class=class="str">"cmt">//--- Import trading functionalities
CTrade obj_Trade;                                                 class=class="str">"cmt">//--- Creating an object of the CTrade class to handle trading operations
input class="type">int sl_points = class="num">500;
input class="type">int tp_points = class="num">250;
class=class="str">"cmt">//--- Defining variables for Bollinger Bands indicator and price arrays
class="type">int handleBB = INVALID_HANDLE;   class=class="str">"cmt">//--- Store Bollinger Bands handle; initialized as invalid
class="type">class="kw">double bb_upper[], bb_lower[];   class=class="str">"cmt">//--- Arrays to store upper and lower Bollinger Bands values
class=class="str">"cmt">//--- Flags to track if the last trade was a buy or sell
class="type">bool isPrevTradeBuy = false, isPrevTradeSell = false;   class=class="str">"cmt">//--- Prevent consecutive trades in the same direction
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   class=class="str">"cmt">//--- Create Bollinger Bands indicator handle with a period of class="num">12, no shift, and a deviation of class="num">2
   handleBB = iBands(_Symbol, _Period, class="num">12, class="num">0, class="num">2, PRICE_CLOSE);  
   
   class=class="str">"cmt">//--- Check if the Bollinger Bands handle was created successfully
   if (handleBB == INVALID_HANDLE){
      Print("ERROR: UNABLE TO CREATE THE BB HANDLE. REVERTING");   class=class="str">"cmt">//--- Print error if handle creation fails
      class="kw">return (INIT_FAILED);   class=class="str">"cmt">//--- Return initialization failed
   }
   
   class=class="str">"cmt">//--- Set the arrays for the Bollinger Bands to be time-series based(most recent data at index class="num">0)
   ArraySetAsSeries(bb_upper, true);   class=class="str">"cmt">//--- Set upper band array as series
   ArraySetAsSeries(bb_lower, true);   class=class="str">"cmt">//--- Set lower band array as series
   
   class="kw">return(INIT_SUCCEEDED);   class=class="str">"cmt">//--- Initialization successful
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   class=class="str">"cmt">//--- Function to handle cleanup when the EA is removed from the chart
   IndicatorRelease(handleBB); class=class="str">"cmt">//--- Release the indicator handle
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   class=class="str">"cmt">//--- Retrieve the most recent Bollinger Bands values(class="num">3 data points)

布林带穿越信号的抓取与下单护栏

这段逻辑先守住数据闸门:向上、下轨各取最近 3 根缓冲,若 CopyBuffer 返回值小于 3,说明行情刚启动或句柄失效,直接打印错误并 return,避免拿空数组去比价。实盘里 XAUUSD 在跳空开盘时常见这种取数失败,漏掉这层判断会让 EA 在边界乱报信号。 价格与品种信息全部按当前图表对齐:Ask/Bid 用 NormalizeDouble 按 _Digits 截断,low0/high0 取 0 号 K 线的高低点,currTimeBar0 记这根 bar 的开盘时间。signalTime 用 static 存下来,核心目的只有一个——同一根 bar 只触发一次信号,防止 OnTick 高频重入连开。 买信号判定为 low0 跌破下轨且本 bar 未发过:打印带 TimeCurrent 的日志,更新 signalTime,然后在无持仓且上笔不是买的前提下,用 obj_Trade.Buy 开 0.01 手,止损止盈按 sl_points / tp_points 乘 _Point 偏移。卖信号对称,high0 破上轨才考虑。外汇与贵金属杠杆高,这种固定 0.01 手测试仓也需警惕滑点扩大止损。 直接把下面这段塞进你的 EA 的 OnTick 里就能跑,重点看 signalTime 那行——很多人回测盈利实盘亏,就是少了这根 bar 去重。

MQL5 / C++
  if(CopyBuffer(handleBB, UPPER_BAND, class="num">0, class="num">3, bb_upper) < class="num">3){
        Print("UNABLE TO GET UPPER BAND REQUESTED DATA. REVERTING NOW!");  class=class="str">"cmt">//--- Error if data fetch fails
        class="kw">return;
  }
  if(CopyBuffer(handleBB, LOWER_BAND, class="num">0, class="num">3, bb_lower) < class="num">3){
        Print("UNABLE TO GET LOWER BAND REQUESTED DATA. REVERTING NOW!");  class=class="str">"cmt">//--- Error if data fetch fails
        class="kw">return;
  }

  class=class="str">"cmt">//--- Get current Ask and Bid prices
  class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);  class=class="str">"cmt">//--- Normalize Ask price to correct digits
  class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);  class=class="str">"cmt">//--- Normalize Bid price to correct digits
  class=class="str">"cmt">//--- Get the low and high prices of the current bar
  class="type">class="kw">double low0 = iLow(_Symbol, _Period, class="num">0);      class=class="str">"cmt">//--- Lowest price of the current bar
  class="type">class="kw">double high0 = iHigh(_Symbol, _Period, class="num">0);    class=class="str">"cmt">//--- Highest price of the current bar

  class=class="str">"cmt">//--- Get the timestamp of the current bar
  class="type">class="kw">datetime currTimeBar0 = iTime(_Symbol, _Period, class="num">0);  class=class="str">"cmt">//--- Time of the current bar
  class="kw">static class="type">class="kw">datetime signalTime = currTimeBar0;           class=class="str">"cmt">//--- Static variable to store the signal time
  class=class="str">"cmt">//--- Check for a buy signal when price crosses below the lower Bollinger Band
  if(low0 < bb_lower[class="num">0] && signalTime != currTimeBar0){
        Print("BUY SIGNAL @ ", TimeCurrent());  class=class="str">"cmt">//--- Log the buy signal with the current time
        signalTime = currTimeBar0;              class=class="str">"cmt">//--- Update signal time to avoid duplicate trades
        if(PositionsTotal() == class="num">0 && !isPrevTradeBuy){
           obj_Trade.Buy(class="num">0.01, _Symbol, Ask, Ask - sl_points * _Point, Ask + tp_points * _Point);  class=class="str">"cmt">//--- Open a buy position with predefined parameters
           isPrevTradeBuy = true; isPrevTradeSell = false;  class=class="str">"cmt">//--- Update trade flags
        }
  }

  class=class="str">"cmt">//--- Check for a sell signal when price crosses above the upper Bollinger Band
  else if(high0 > bb_upper[class="num">0] && signalTime != currTimeBar0){
        Print("SELL SIGNAL @ ", TimeCurrent());  class=class="str">"cmt">//--- Log the sell signal with the current time
        signalTime = currTimeBar0;              class=class="str">"cmt">//--- Update signal time to avoid duplicate trades
        if(PositionsTotal() == class="num">0 && !isPrevTradeSell){

◍ 卖单触发与标志位翻转的细节

上面这段是 Sell 分支里真正下单并维护状态的两行,常被忽略的是 SL/TP 不是写死价格,而是用 Bid 加减点数乘 _Point 动态算出来。sl_points 与 tp_points 若在外层定义为 200 和 400,则实际止损挂在距 Bid 20 点、止盈距 Bid 40 点(标准账户 _Point=0.0001)。 isPrevTradeBuy/isPrevTradeSell 这两个布尔标志在每次成交后必须翻转,否则下一根 K 线仍判定“上笔是买”会直接阻断反向开仓逻辑。在 MT5 策略测试器里把这两行注释掉跑一遍,你会看到净值曲线出现连续同向加仓而非交替,回撤可能放大 1.5 倍以上。 外汇与贵金属杠杆品种跳空频繁,用 Bid+sl_points*_Point 做硬止损不保证滑点下的成交价,实盘需评估流动性风险。

MQL5 / C++
  obj_Trade.Sell(class="num">0.01, _Symbol, Bid, Bid + sl_points * _Point, Bid - tp_points * _Point);  class=class="str">"cmt">//--- Open a sell position with predefined parameters
  isPrevTradeBuy = false; isPrevTradeSell = true;  class=class="str">"cmt">//--- Update trade flags
  }

「别急着下结论」

这套 PIRANHA + 布林带的 EA 骨架已经跑通:从指标句柄配置到策略测试器里的参数微调,核心逻辑都能在 MT5 里复现。附件里的 PIRANHA.mq5 约 5.57 KB,直接拖进编辑器就能编译,先别改策略,用 2023 年欧美盘数据跑一遍默认参数,看清胜率和回撤再说。 外汇与贵金属自动化高风险,回测漂亮不等于实盘能活。把本文当基石而非圣杯,在自定义品种上验证过波动阈值再谈加仓逻辑,比急着上线更划算。 作者留了个口子:讨论区有人拿它改去跑多符号投资组合,今年才起步。你想清楚自己要盯几个品种、扛多大滑点,再决定要不要接着堆功能。

常见问题

先在 OnInit 里用 iBands 拿到布林带句柄,并定义好偏差倍数、周期、手数等外部输入,句柄无效就直接返回 INIT_FAILED。
只在收盘价完全收在带外且前一根还在带内时视为穿越,同时加最小止损距离和最大持仓检查,避免连续追单。
可以。小布能读取你的策略逻辑,标出标志位翻转遗漏或死锁的地方,并给出改写建议,省去你手动逐行查。
通常是 OrderSend 成功后才该把 sellFlag 置 true,若放在发送前或没判断返回码,就会一直满足开仓条件。
不能。外汇和贵金属属高风险,样本期可能只是震荡市,换行情易失效,建议先开模拟验证再控仓实盘。