创建一个基于布林带PIRANHA策略的MQL5 EA·综合运用
「布林带均值回归 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 根布林值,具体逻辑在下节。
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 去重。
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 做硬止损不保证滑点下的成交价,实盘需评估流动性风险。
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 年欧美盘数据跑一遍默认参数,看清胜率和回撤再说。 外汇与贵金属自动化高风险,回测漂亮不等于实盘能活。把本文当基石而非圣杯,在自定义品种上验证过波动阈值再谈加仓逻辑,比急着上线更划算。 作者留了个口子:讨论区有人拿它改去跑多符号投资组合,今年才起步。你想清楚自己要盯几个品种、扛多大滑点,再决定要不要接着堆功能。