
创建一个基于布林带PIRANHA策略的MQL5 EA
引言
在本文中,我们将探讨如何基于 PIRANHA 策略,在MQL5中创建一款EA,并重点整合布林带。随着交易者对高效自动化方案的渴求,PIRANHA 策略应运而生——它以系统化方式捕捉市场波动,因而深受外汇玩家的青睐。
首先,我们将概述 PIRANHA 策略的核心理念,为自动化交易打下坚实基础。接着,深入剖析布林带这一经典指标:它通过度量市场波动,帮助我们锁定潜在进出场点。
随后,我们将引导您完成在 MQL5 中的编码过程,重点介绍推动该策略的核心函数和逻辑。此外,还会讨论 EA 的性能测试、参数优化,以及实盘部署的最佳实践。本文将涵盖以下主题:
- PIRANHA 策略概览
- 布林带深度解析
- 策略的具体实现
- 在MQL5中的实现
- 测试
- 结论
读完本文,您将具备开发 MQL5 EA 的完整知识体系,能够高效运用 PIRANHA 策略与布林带,全面提升交易表现。让我们开始吧。
PIRANHA 策略概览
PIRANHA 策略是一套动态交易系统,专为捕捉外汇市场波动而设。它以“快、准、狠”著称,正如其名称来源——水中迅捷的食人鲳,出手即中。核心技能:该策略以波动率为锚,通过高度相对化的方式,帮助交易者精准定位市场进出场点。
在本策略中,布林带为核心组件。作为衡量波动的常用指标,布林带可直观呈现市场的起伏。我们选用 12 周期布林带,经验证其结构能在长期内有效揭示价格行为。标准差倍数设为 2,在捕捉主要行情的同时,过滤掉微小波动干扰。上下轨形成“天花板”与“地板”,分别对应潜在的超买与超卖区。价格跌破下轨 → 视为极佳买入机会;价格突破上轨 → 提示可考虑卖出。示意图如下:
风险管理同样是 PIRANHA 策略的核心环节。它强调通过明确的止损与止盈水平来保护本金。具体设定如下:做多时,止损放在入场价下方 100 点,止盈设在入场价上方 50 点。这套纪律性流程既控制潜在亏损,又锁定利润,使交易方法更具可持续性。
总而言之,PIRANHA 策略将技术分析与波动率捕捉、风险管理融为一体。理解这些原则与参数后,交易者即可更从容地在汇市航行,做出契合自身目标的理性决策。接下来,我们将在MQL5中把这一策略落地,让 PIRANHA 在自动化交易系统中真正“活”起来。
布林带深度解析
交易者可借助布林带这一稳健的技术分析工具,在剧烈波动的市场中捕捉潜在价格走向。John Bollinger于 20 世纪 80 年代提出该指标。它由三个部分组成:中轨(移动平均线)、上轨与下轨。通过计算,交易者可以衡量当前价格相对均值偏离了多少。
首先,我们计算中轨(即移动平均线),通常采用 20 周期的简单移动平均线(SMA)。SMA 的公式为:
其中 𝑃𝑖 为第 i 周期的收盘价,n 为周期数(此处为 20)。举例:若最近 20 个周期的收盘价如下:
周期 | 收盘价(𝑃𝑖) |
---|---|
1 | 1.1050 |
2 | 1.1070 |
3 | 1.1030 |
4 | 1.1080 |
5 | 1.1040 |
6 | 1.1100 |
7 | 1.1120 |
8 | 1.1150 |
9 | 1.1090 |
10 | 1.1060 |
11 | 1.1085 |
12 | 1.1105 |
13 | 1.1130 |
14 | 1.1110 |
15 | 1.1075 |
16 | 1.1055 |
17 | 1.1080 |
18 | 1.1095 |
19 | 1.1115 |
20 | 1.1120 |
我们把这些价格累加并除以20:
接下来,我们计算 标准差,用于衡量收盘价相对于简单移动平均线(SMA)的波动幅度。其 (𝜎) 公式为:
以我们已算出的 SMA = 1.1080 为基准,先求出每个收盘价与该均值的差值平方,再取平均并开平方根即可得到标准差。例如,前几个差值平方的结果如下:
在计算了所有20个方差后,我们得到:
- 上轨 = SMA + (k × σ)
- 下轨 = SMA − (k × σ)
这里,我们一般设置k=2(代表两个标准差)。代入,得:
- 上轨 = 1.1080 + (2 × 0.0030) = 1.1140
- 下轨 = 1.1080 − (2 × 0.0030) = 1.1020
布林带计算结果如下:
- 中轨(SMA):1.1080
- 上轨:1.1140
- 下轨:1.1020
这三个轨道如下所示:
两条轨道之间的空间会随着市况而动态变化。 当轨道扩张,意味着波动率上升——行情往往开始剧烈。当轨道收缩,则提示市场正进入盘整。交易者常关注价格与轨道的触碰或突破,以此生成信号。价格触及上轨 → 视为超买;价格触及下轨 → 视为超卖。
综上所述,布林带的计算围绕三个核心:SMA 中轨、标准差、上轨与下轨。掌握这些公式不仅是量化阅读的训练,更为交易者提供了做出明智决策的“弹药”——尤其在运用 PIRANHA 策略时尤为关键。
策略的具体实现
上轨:卖出条件
当价格向上穿越并收于布林带上轨之外时,即发出“市场可能超买”的信号。这表明涨势过度,后续很可能出现向下修正。因此,我们把该情形视为卖出信号。因此,若当前 K 线收盘价仍位于上轨之上,即开空仓。以期捕捉潜在反转或回撤。
下轨:买入条件
反之,当价格向下穿越并收于布林带下轨之外时,则暗示市场可能超卖。这代表跌幅已深,随时有望反弹。于是,我们把该情形视为买入信号。因此,若当前 K 线收盘价位于下轨之下,即开多仓,博弈向上反转。
这些策略的图形化表达,对我们在MQL5中实现这些交易条件时非常有帮助,可作为编写精确入场和出场规则的代码的参考。
在MQL5中的实现
在掌握了 PIRANHA 交易策略的全部理论之后,接下来我们把这套策略在MetaTrader 5 中自动化,打造一款EA。
要在MetaTrader 5终端中创建EA,请点击“工具”选项卡并选择“MetaQuotes语言编辑器”,或者简单地在键盘上按F4键。另外,您还可以点击工具栏上的IDE(集成开发环境)图标。这将打开MetaQuotes语言编辑器,允许您编写EA、技术指标、脚本和函数库。
一旦MetaEditor被打开,在工具栏上,导航到“文件”选项卡并选择“新建文件”,或者简单地按CTRL + N,来创建一个新文档。另外,您也可以点击工具栏上的“新建”图标。这将弹出一个MQL向导(MQL Wizard)窗口。
在弹出的向导中,选择“Expert Advisor (template) ”并点击“下一步”。
在EA的一般属性中,在名称部分,输入您的EA文件的名称。请注意,如果要指定或创建一个不存在的文件夹,您需要在EA名称前使用反斜杠。例如,这里我们默认有“Experts\”。这意味着我们的EA将被创建在Experts文件夹中,我们可以在那里找到它。其他部分都很容易理解,您也可以按照向导底部的链接去详细了解这一过程。
在提供了您想要的EA文件名后,点击“下一步”,再点击“下一步”,然后点击“完成”。 完成这些步骤后,我们现在就可以开始编写和规划我们的策略了。
首先我们从定义EA的基础数据开始。这包括EA的名称、版本信息和MetaQuotes的网站链接。我们还将指定EA的版本号,设置为“1.00”。
//+------------------------------------------------------------------+ //| PIRANHA.mq5 | //| Allan Munene Mutiiria, Forex Algo-Trader. | //| https://forexalgo-trader.com | //+------------------------------------------------------------------+ //--- Properties to define metadata about the Expert Advisor (EA) #property copyright "Allan Munene Mutiiria, Forex Algo-Trader." //--- Copyright information #property link "https://forexalgo-trader.com" //--- Link to the creator's website #property version "1.00" //--- Version number of the EA
在加载程序时,会呈现出与下图类似的信息。
首先,在源代码的开头使用#include包含一个交易实例。这样我们就能够访问CTrade类,并使用它来创建一个交易对象。这非常关键,因为我们需要用它来执行交易。
//--- Including the MQL5 trading library #include <Trade/Trade.mqh> //--- Import trading functionalities CTrade obj_Trade; //--- Creating an object of the CTrade class to handle trading operations
预处理器会用文件Trade.mqh的内容替换#include <Trade/Trade.mqh>这一行。尖括号表示Trade.mqh文件将从标准目录(通常是terminal_installation_directory\MQL5\Include)中获取。当前目录不会包含在搜索路径中。这行代码可以放在程序的任何位置,但通常,为了代码结构更好和引用更方便,所有的包含指令都放在源代码的开头。声明CTrade类的obj_Trade对象将使我们能够轻松访问该类中包含的方法,这得益于MQL5开发者的设计。
我们需要先创建指标句柄(handle),以便在策略中调用所需的指标。
//--- Defining variables for Bollinger Bands indicator and price arrays int handleBB = INVALID_HANDLE; //--- Store Bollinger Bands handle; initialized as invalid double bb_upper[], bb_lower[]; //--- Arrays to store upper and lower Bollinger Bands values
在这里,我们声明并初始化一个integer变量 handleBB,它将作为布林带指标在 EA 中的唯一句柄。在 MQL5 中,句柄 是系统分配给某个指标的唯一标识符,方便在整段代码中随时引用该指标。先把 handleBB 初始化为INVALID_HANDLE,可确保在真正创建指标前不会误用无效句柄,从而避免意外错误。除了句柄,我们还定义两个动态数组:bb_upper 和 bb_lower,分别用于存储布林带的上轨与下轨数值。借助这两个数组,我们可以实时捕获并分析指标状态,为基于布林带条件执行交易策略提供可靠依据。再次强调:我们将确保同一时刻仅保留一个方向的单一持仓。
//--- Flags to track if the last trade was a buy or sell bool isPrevTradeBuy = false, isPrevTradeSell = false; //--- Prevent consecutive trades in the same direction
在此,我们声明并初始化两个Boolean标志变量 isPrevTradeBuy 与 isPrevTradeSell,用于记录上一笔已执行交易的方向。两者均初始化为 false,表示尚未发生任何交易。这两个标志将在交易逻辑中发挥关键作用,保证EA不会在同一个方向上连续开仓。比如,若上一笔为买单,isPrevTradeBuy 置为 true,在新卖单出现前禁止再次开多;若上一笔为卖单,isPrevTradeSell 置为 true,在新买单出现前禁止再次开空。由此避免同方向冗余开仓,保持策略平衡。
接下来,我们需要实现OnInit事件处理函数。该函数会在 EA 挂载到图表时由系统自动调用,用于完成所有初始化工作。该函数负责初始化EA,包括创建指标句柄,初始化变量,预分配资源。简言之,OnInit 是 EA 开始处理市场数据前的“启动脚本”。它具体如下:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ // OnInit is called when the EA is initialized on the chart //... }
在 OnInit 事件处理器中,我们需要先初始化指标句柄,以便为其分配数据值。
//--- Create Bollinger Bands indicator handle with a period of 12, no shift, and a deviation of 2 handleBB = iBands(_Symbol, _Period, 12, 0, 2, PRICE_CLOSE);
这里,我们通过调用iBands函数来创建布林带指标的句柄,并按给定参数生成该指标。传入的参数包括:_Symbol当前图表的交易品种;_Period所用时间周期(可以是分钟、小时、日线等)。布林带的参数包括:周期设为 12,表示计算指标所用的 K 线根数;偏移设为 0,表示不对轨道进行前后平移;标准差设为 2,这决定轨道与移动平均值间的距离。PRICE_CLOSE表示以收盘价作为计算依据。若创建成功,句柄变量 handleBB 将保存一个有效的标识符,供后续获取数据和进行分析。因此,在继续之前必须先检查句柄是否创建成功。
//--- Check if the Bollinger Bands handle was created successfully if (handleBB == INVALID_HANDLE){ Print("ERROR: UNABLE TO CREATE THE BB HANDLE. REVERTING"); //--- Print error if handle creation fails return (INIT_FAILED); //--- Return initialization failed }
在此,我们通过判断布林带指标的句柄是否等于INVALID_HANDLE来验证其创建是否成功。若句柄无效,我们即打印错误信息:“ERROR: UNABLE TO CREATE THE BB HANDLE. REVERTING”,以便在初始化阶段快速定位问题。随后返回INIT_FAILED,表明EA未能正常初始化。若检查通过,则继续将数据数组设为时间序列格式。
//--- Set the arrays for the Bollinger Bands to be time-series based (most recent data at index 0) ArraySetAsSeries(bb_upper, true); //--- Set upper band array as series ArraySetAsSeries(bb_lower, true); //--- Set lower band array as series return(INIT_SUCCEEDED); //--- Initialization successful
在此处,我们调用ArraySetAsSeries函数,并把第二个参数设为 true,从而将布林带数组 bb_upper 与 bb_lower 配置为时间序列格式。这样一来,最新数据被存放在索引 0 的位置,方便在分析行情时快速获取最新值。通过这种数组排布,我们的数据结构契合了交易算法中的常见做法——最新信息往往最为关键。最后,我们返回INIT_SUCCEEDED,表明初始化流程已全部成功完成,EA可以继续执行后续操作。
至此,初始化部分的每一步都运行无误。负责程序初始化的完整源代码如下:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create Bollinger Bands indicator handle with a period of 12, no shift, and a deviation of 2 handleBB = iBands(_Symbol, _Period, 12, 0, 2, PRICE_CLOSE); //--- Check if the Bollinger Bands handle was created successfully if (handleBB == INVALID_HANDLE){ Print("ERROR: UNABLE TO CREATE THE BB HANDLE. REVERTING"); //--- Print error if handle creation fails return (INIT_FAILED); //--- Return initialization failed } //--- Set the arrays for the Bollinger Bands to be time-series based (most recent data at index 0) ArraySetAsSeries(bb_upper, true); //--- Set upper band array as series ArraySetAsSeries(bb_lower, true); //--- Set lower band array as series return(INIT_SUCCEEDED); //--- Initialization successful }
接下来进入OnDeinit事件处理器——当程序被反初始化时自动调用的函数。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ // OnDeinit is called when the EA is removed from the chart or terminated //... }
OnDeinit会在 EA 从图表移除或终端关闭时触发。我们必须利用此事件处理器,确保资源得到正确清理与管理。当 EA 终止时,需要释放在初始化阶段创建的所有指标句柄。若不释放,就可能留下已占用的内存地址,造成资源浪费;我们绝不想留下任何无用资源。正因如此,OnDeinit 至关重要,在任何编程环境中清理步骤都不可忽视。
IndicatorRelease(handleBB); //--- Release the indicator handle
此处,我们仅用一行代码调用 IndicatorRelease 并传入 handleBB,即可释放先前创建的布林带指标句柄。及时清理对于维持平台性能尤为关键,尤其当您同时运行多个EA或长时间开启终端时。因此,完整的资源释放源代码如下:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Function to handle cleanup when the EA is removed from the chart IndicatorRelease(handleBB); //--- Release the indicator handle }
接下来,每当行情更新时,我们都需要侦测交易机会。这通过 OnTick 事件处理器实现。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ // OnTick is called whenever there is a new market tick (price update) //... }
事件处理函数 OnTick 会在每次出现新 tick 或市况变动时执行,处理最新价格信息。它是 EA 运行的核心:在这里执行交易逻辑,只要逻辑设计得当,就能带来盈利。每当行情变化,我们随即评估市场状态,决定是否开仓或平仓。该函数随市况实时触发,确保策略紧跟最新价格与指标数值。
为及时掌握最新市况,我们先获取当前报价。
//--- Get current Ask and Bid prices double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Normalize Ask price to correct digits double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Normalize Bid price to correct digits
此处使用Ask和Bid来取得交易品种的最新买卖价。要获取这些价格,我们使用SymbolInfoDouble函数。用SYMBOL_ASK、SYMBOL_BID分别指定买价与卖价。随后用NormalizeDouble按_Digits保留正确小数位。这步很关键,避免浮点误差导致下单价格偏差。如果我们不这么做,可能会导致价格计算错误。接着,我们复制指标值以供分析与下单。
//--- Retrieve the most recent Bollinger Bands values (3 data points) if (CopyBuffer(handleBB, UPPER_BAND, 0, 3, bb_upper) < 3){ Print("UNABLE TO GET UPPER BAND REQUESTED DATA. REVERTING NOW!"); //--- Error if data fetch fails return; } if (CopyBuffer(handleBB, LOWER_BAND, 0, 3, bb_lower) < 3){ Print("UNABLE TO GET LOWER BAND REQUESTED DATA. REVERTING NOW!"); //--- Error if data fetch fails return; }
CopyBuffer用于获取最近 3 个周期的布林带上下轨值。首次调用 CopyBuffer 时,我们从上轨(upper band)获取数据,起始索引为 0,并将结果存入 bb_upper 数组。如果该函数返回值小于 3,说明数据拉取失败,于是输出错误信息: “UNABLE TO GET UPPER BAND REQUESTED DATA. REVERTING NOW!”随后立即退出函数,避免后续逻辑继续执行。对于下轨也采用同样的处理流程,以确保在获取下轨数据时同样能捕获并处理任何错误。需要指出的是,在引用缓冲区索引时,我们使用布林带指标允许的“指标线标识符”,而非直接使用缓冲区编号。这样做最简单,也能避免混淆,核心逻辑保持不变。下方是缓冲区编号的可视化示意图。
为了将指标数值与价格进行比对,我们需要获取当前 K 线的关键价格——即最高价与最低价。
//--- Get the low and high prices of the current bar double low0 = iLow(_Symbol, _Period, 0); //--- Lowest price of the current bar double high0 = iHigh(_Symbol, _Period, 0); //--- Highest price of the current bar
这里,我们通过调用iLow和iHigh两个函数来获取当前 K 线的最低价与最高价。iLow 会返回指定品种(_Symbol)和周期(_Period)下,当前 K 线(索引 0)的最低价,并把该值存入变量 low0。iHigh 同理,返回当前 K 线的最高价,并存入变量 high0。接下来,我们还需确保在同一根 K 线内只触发一次信号。为此采用了以下逻辑代码。
//--- Get the timestamp of the current bar datetime currTimeBar0 = iTime(_Symbol, _Period, 0); //--- Time of the current bar static datetime signalTime = currTimeBar0; //--- Static variable to store the signal time
在此,我们使用函数iTime获取当前 K 线的时间戳,该函数返回指定品种 (_Symbol) 和周期 (_Period) 下、索引为 0 的 K 线时间。该时间戳存入变量 currTimeBar0。随后,我们声明一个静态变量 signalTime,并用 currTimeBar0 的值为其初始化。由于 signalTime 被设为static,其值在函数调用之间保持不变,从而让我们能追踪最近一次产生交易信号的时间。这对策略至关重要,可防止在同一根 K 线内多次触发信号,确保每根 K 线仅执行一次信号操作。完成上述准备工作后,便可开始检测信号。首先检查买入信号。
//--- Check for a buy signal when price crosses below the lower Bollinger Band if (low0 < bb_lower[0]){ Print("BUY SIGNAL @ ", TimeCurrent()); //--- Log the buy signal with the current time }
在此,我们通过判断当前 K 线的最低价(变量 low0)是否低于最新下轨值(数组 bb_lower[0])来检测潜在买入信号。若 low0 < bb_lower[0],即价格已跌破下轨,暗示可能出现超卖,构成买入机会。一旦条件成立,程序便调用Print函数输出 “BUY SIGNAL @” 并附带TimeCurrent取得的当前时间,方便追踪 EA 的决策时点。这条提示让我们能够追踪何时捕捉到买入信号,为 EA 的决策过程提供透明度和可追溯性。运行后,我们得到如下输出。
从已有的输出可以看到,每当多头条件满足时,我们都会在每个 tick 打印一次信号。而我们希望在每根 K 线只打印一次信号,即使该 K 线内条件多次成立。为此,我们引入以下逻辑。
//--- Check for a buy signal when price crosses below the lower Bollinger Band if (low0 < bb_lower[0] && signalTime != currTimeBar0){ Print("BUY SIGNAL @ ", TimeCurrent()); //--- Log the buy signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades }
这里,我们在原有条件上增加了一个额外检查,以避免在同一根 K 线内产生重复交易。最初,我们只验证了当前 K 线最低价 low0 是否低于最新下轨 bb_lower[0]。现在,我们加入第二个条件 signalTime != currTimeBar0,确保当前 K 线时间戳 currTimeBar0 与上一次记录的信号时间 signalTime 不同。随后,将 signalTime 更新为 currTimeBar0,从而确认每根 K 线仅处理一次买入信号,即使价格在该 K 线内多次跌破下轨。运行更新后的代码,我们得到如下输出。
成功了。现在我们看到每根K线只打印一次信号。接着我们就能根据信号进行开仓做多了。
if (PositionsTotal() == 0 && !isPrevTradeBuy){ obj_Trade.Buy(0.01, _Symbol, Ask, Ask - 100 * _Point, Ask + 50 * _Point); //--- Open a buy position with predefined parameters isPrevTradeBuy = true; isPrevTradeSell = false; //--- Update trade flags }
此处,我们进一步加入条件,确保仅在特定情况下才执行买入交易。先用函数PositionsTotal检查当前总持仓是否为 0,确保没有其它仓位存在。再验证上一笔交易不是买单,即 !isPrevTradeBuy。防止连续开多,并且确保我们的EA不会在已经存在多单的情况下再次下多单。
若两项条件同时满足,则调用 obj_Trade.Buy 开仓。我们将订单手数设为 0.01 手,交易品种为当前图表品种 (_Symbol),入场价格使用 Ask。止损设置在当前 Ask 下方 100 点,止盈则设在 Ask 上方 50 点,从而形成完整的风险管理规则。成功开多后,立即更新交易方向标志:把 isPrevTradeBuy 设为 true,isPrevTradeSell 设为 false;这表示上一笔是买单,在出现卖出信号前不会再开新多单。卖出逻辑采用完全对称的方式,具体如下。
//--- Check for a sell signal when price crosses above the upper Bollinger Band else if (high0 > bb_upper[0] && signalTime != currTimeBar0){ Print("SELL SIGNAL @ ", TimeCurrent()); //--- Log the sell signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades if (PositionsTotal() == 0 && !isPrevTradeSell){ obj_Trade.Sell(0.01, _Symbol, Bid, Bid + 100 * _Point, Bid - 50 * _Point); //--- Open a sell position with predefined parameters isPrevTradeBuy = false; isPrevTradeSell = true; //--- Update trade flags } }
编译并运行程序,我们得到如下输出。
可以看到我们成功执行了买入操作。随着实现完成,我们已将基于布林带的 PIRANHA 策略整合完毕,并配置程序在符合既定条件时响应买卖信号。在下一部分,我们将专注于测试程序,评估其表现,并微调参数以获得最佳结果。
测试
在完成实现之后,下一步关键步骤是对EA进行彻底测试,以评估其性能并优化其参数。有效的测试可以确保该策略在各种市场条件下表现如预期,最小化交易过程中出现意外问题的风险。在这里,我们将使用MetaTrader 5策略测试器进行回测和优化,以找到策略的最佳输入参数。
首先,我们将为“止损(SL)”和“止盈(TP)”设定初始输入参数,这两个数值对策略的风险管理至关重要。在最初的实现中,SL 和 TP 是以固定点数写入的。然而,为了给策略留出更大的回旋空间,并更有效地捕捉市场波动,我们将把这两个输入参数改为可灵活调整的形式,并在测试阶段进行优化。现在对代码做如下更新:
//--- INPUTS input int sl_points = 500; input int tp_points = 250; //--- //--- Check for a buy signal when price crosses below the lower Bollinger Band if (low0 < bb_lower[0] && signalTime != currTimeBar0){ Print("BUY SIGNAL @ ", TimeCurrent()); //--- Log the buy signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades if (PositionsTotal() == 0 && !isPrevTradeBuy){ obj_Trade.Buy(0.01, _Symbol, Ask, Ask - sl_points * _Point, Ask + tp_points * _Point); //--- Open a buy position with predefined parameters isPrevTradeBuy = true; isPrevTradeSell = false; //--- Update trade flags } } //--- Check for a sell signal when price crosses above the upper Bollinger Band else if (high0 > bb_upper[0] && signalTime != currTimeBar0){ Print("SELL SIGNAL @ ", TimeCurrent()); //--- Log the sell signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades if (PositionsTotal() == 0 && !isPrevTradeSell){ obj_Trade.Sell(0.01, _Symbol, Bid, Bid + sl_points * _Point, Bid - tp_points * _Point); //--- Open a sell position with predefined parameters isPrevTradeBuy = false; isPrevTradeSell = true; //--- Update trade flags } }
输入参数使我们能够在不同的品种和商品上进行动态优化。我们运行这个程序,就可得到如下输出。
成功了!我们的程序按预期执行。实现 PIRANHA 策略的完整源码片段如下:
//+------------------------------------------------------------------+ //| PIRANHA.mq5 | //| Allan Munene Mutiiria, Forex Algo-Trader. | //| https://forexalgo-trader.com | //+------------------------------------------------------------------+ //--- Properties to define metadata about the Expert Advisor (EA) #property copyright "Allan Munene Mutiiria, Forex Algo-Trader." //--- Copyright information #property link "https://forexalgo-trader.com" //--- Link to the creator's website #property version "1.00" //--- Version number of the EA //--- Including the MQL5 trading library #include <Trade/Trade.mqh> //--- Import trading functionalities CTrade obj_Trade; //--- Creating an object of the CTrade class to handle trading operations input int sl_points = 500; input int tp_points = 250; //--- Defining variables for Bollinger Bands indicator and price arrays int handleBB = INVALID_HANDLE; //--- Store Bollinger Bands handle; initialized as invalid double bb_upper[], bb_lower[]; //--- Arrays to store upper and lower Bollinger Bands values //--- Flags to track if the last trade was a buy or sell bool isPrevTradeBuy = false, isPrevTradeSell = false; //--- Prevent consecutive trades in the same direction //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create Bollinger Bands indicator handle with a period of 12, no shift, and a deviation of 2 handleBB = iBands(_Symbol, _Period, 12, 0, 2, PRICE_CLOSE); //--- Check if the Bollinger Bands handle was created successfully if (handleBB == INVALID_HANDLE){ Print("ERROR: UNABLE TO CREATE THE BB HANDLE. REVERTING"); //--- Print error if handle creation fails return (INIT_FAILED); //--- Return initialization failed } //--- Set the arrays for the Bollinger Bands to be time-series based (most recent data at index 0) ArraySetAsSeries(bb_upper, true); //--- Set upper band array as series ArraySetAsSeries(bb_lower, true); //--- Set lower band array as series return(INIT_SUCCEEDED); //--- Initialization successful } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Function to handle cleanup when the EA is removed from the chart IndicatorRelease(handleBB); //--- Release the indicator handle } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Retrieve the most recent Bollinger Bands values (3 data points) if (CopyBuffer(handleBB, UPPER_BAND, 0, 3, bb_upper) < 3){ Print("UNABLE TO GET UPPER BAND REQUESTED DATA. REVERTING NOW!"); //--- Error if data fetch fails return; } if (CopyBuffer(handleBB, LOWER_BAND, 0, 3, bb_lower) < 3){ Print("UNABLE TO GET LOWER BAND REQUESTED DATA. REVERTING NOW!"); //--- Error if data fetch fails return; } //--- Get current Ask and Bid prices double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Normalize Ask price to correct digits double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Normalize Bid price to correct digits //--- Get the low and high prices of the current bar double low0 = iLow(_Symbol, _Period, 0); //--- Lowest price of the current bar double high0 = iHigh(_Symbol, _Period, 0); //--- Highest price of the current bar //--- Get the timestamp of the current bar datetime currTimeBar0 = iTime(_Symbol, _Period, 0); //--- Time of the current bar static datetime signalTime = currTimeBar0; //--- Static variable to store the signal time //--- Check for a buy signal when price crosses below the lower Bollinger Band if (low0 < bb_lower[0] && signalTime != currTimeBar0){ Print("BUY SIGNAL @ ", TimeCurrent()); //--- Log the buy signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades if (PositionsTotal() == 0 && !isPrevTradeBuy){ obj_Trade.Buy(0.01, _Symbol, Ask, Ask - sl_points * _Point, Ask + tp_points * _Point); //--- Open a buy position with predefined parameters isPrevTradeBuy = true; isPrevTradeSell = false; //--- Update trade flags } } //--- Check for a sell signal when price crosses above the upper Bollinger Band else if (high0 > bb_upper[0] && signalTime != currTimeBar0){ Print("SELL SIGNAL @ ", TimeCurrent()); //--- Log the sell signal with the current time signalTime = currTimeBar0; //--- Update signal time to avoid duplicate trades if (PositionsTotal() == 0 && !isPrevTradeSell){ obj_Trade.Sell(0.01, _Symbol, Bid, Bid + sl_points * _Point, Bid - tp_points * _Point); //--- Open a sell position with predefined parameters isPrevTradeBuy = false; isPrevTradeSell = true; //--- Update trade flags } } } //+------------------------------------------------------------------+
回测结果:
回测结果图形:
在测试阶段,我们对输入参数进行了优化,并使用策略测试器验证了该策略的表现。通过对止损(SL)和止盈(TP)数值的调整,PIRANHA 策略获得了更大的灵活性。从而能够更好地应对市场波动。我们已经确认,在回测和优化该策略时,它能按照预期运行并取得良好的结果。
结论
在本文中,我们完整展示了如何基于 PIRANHA 策略,使用布林带在 MetaQuotes Language 5(MQL5)中开发一款 EA。我们首先,深入理解 PIRANHA 策略的核心思想;接着,系统梳理布林带的作用——捕捉市场波动并设定进出场点。
随后,逐步演示编码实现:配置指标句柄、构建交易逻辑。最后,通过 MetaTrader 5 的策略测试器,调整关键参数并验证策略在不同市场环境下的有效性。
免责声明:本文所提供的信息仅用于教育目的。本文旨在为基于 PIRANHA 策略开发EA提供思路与范例,并可作为后续进一步优化与测试、构建更高级系统的基石。本文所讨论的策略和方法并不保证任何交易结果,使用本文内容的风险由您自行承担。在应用任何自动化交易解决方案之前,请务必进行彻底测试,并考虑潜在的市场状况。
总体而言,本文旨在为您自动化 PIRANHA 策略并量身定制交易风格提供指引。我们希望它能带来有价值的见解,并激励您在 MQL5 中继续探索、构建更精妙的交易系统。祝编程愉快,交易成功!
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16034


非常感谢 - 非常有趣的文章和详细的条件。我将以此为基础,在自定义角色 上编写和测试我的机器人。
当然,非常欢迎。