English Русский Español Deutsch 日本語
preview
MQL5自动化交易策略(第四部分):构建多层级区域恢复系统

MQL5自动化交易策略(第四部分):构建多层级区域恢复系统

MetaTrader 5交易 |
503 0
Allan Munene Mutiiria
Allan Munene Mutiiria

概述

上一篇文章(本系列第三部分)中,我们探讨了区域恢复RSI系统,展示了如何通过MetaQuotes语言5(MQL5)将基于RSI的信号生成机制与动态区域恢复策略相结合,以实现交易管理并从不利市场波动中恢复仓位。在本文(第四部分)中,我们将基于上述基础,引入多层级区域恢复系统——这是一种能够同时处理多个独立信号的高级交易管理方案。

该系统利用RSI指标生成交易信号,并通过动态数组结构将每个信号无缝整合至区域恢复逻辑中。其核心目标是扩展恢复机制,实现多交易组合的高效管理,从而降低整体回撤幅度并优化交易结果。

我们将引导您完成从策略设计方案制定、MQL5系统编码到绩效回测的全流程。为便于理解与操作,我们将步骤拆解为以下主题模块:

  1. 策略设计方案
  2. 在MQL5中的实现
  3. 回测
  4. 结论

到结束时,您将切实掌握如何构建并优化多层级区域恢复系统,实现动态且稳健的交易管理。


策略设计方案

多层级区域恢复系统将采用结构化设计,高效管理多重交易信号。为了实现这一目标,我们将定义一个结构体(struct),作为创建独立交易组合的模板。由RSI指标生成的每个交易信号,都将对应一个专属交易组合,并以数组元素的形式存储。例如:当系统生成信号1时,将创建组合1,该组合不仅存储初始交易信息,还会管理与此信号关联的所有恢复仓位。同理,信号2将触发组合2的创建,该组合会基于信号2的参数独立追踪并执行所有恢复交易。以下是交易组合与信号属性的可视化示意图:

交易组合可视化

每个交易组合将包含关键数据,包括信号方向(买入/卖出)、入场价格、恢复价位、动态计算的交易手数及其他交易专属参数。当RSI识别出新信号时,系统会将其添加至数组,确保多信号的并行处理能力。恢复交易将在对应组合内动态计算并执行,保证各交易设置独立运行且互不干扰。以下是多信号独立处理示例:

多信号独立处理示例

通过这种结构化设计,系统将具备高度的可扩展性与灵活性。每个交易组合将作为独立单元运行,使系统能够针对每个信号动态响应市场变化。该设计简化了复杂交易组合的追踪与管理,所有信号及其关联的恢复交易均实现有序整合。基于数组的交易组合系统将成为构建稳健且自适应的多层级区域恢复系统的基础,可在保持效率与清晰度的同时,应对多样化的交易场景。那么,让我们开始吧。


在MQL5中的实现

在掌握多层级区域恢复交易策略的理论框架后,让我们将理论自动化,并通过MQL5为MetaTrader 5开发一款EA。

要创建一个EA,在您的MetaTrader 5终端上,点击工具(Tools)选项卡并检查MetaQuotes语言编辑器,或者直接按键盘上的F4键。另外,您也可以点击工具栏上的IDE(集成开发环境)图标。这样就会打开MetaQuotes语言编辑器环境,该环境允许用户编写自动交易、技术指标、脚本和函数库。打开MetaEditor后,在工具栏上,点击“文件”选项卡,然后勾选“新建文件”,或者直接按CTRL + N键,以创建一个新文档。或者,您也可以点击工具栏选项卡上的“新建”图标。这将弹出一个MQL向导窗口。

在弹出的向导中,选中EA(模板),然后单击下一步。在EA的一般属性中,在名称部分,提供您的文件名称。请注意,如果要指定或创建一个不存在的文件夹,您需要在EA名称前使用反斜杠。例如,这里我们默认“Experts\”。这意味着我们的EA将被创建在Experts文件夹中,我们可以去那里找。其他部分相对直观,但您可以按照向导底部的链接了解如何精准地执行该过程。

新的EA名称

在输入您希望的EA文件名后,依次点击“下一步”、再“下一步”,然后点击“完成”。在完成上述所有操作后,我们现在可以开始编写和实现我们的策略了。

首先,我们从定义一些关于EA的基础数据开始。包括EA的名称、版权信息以及指向MetaQuotes网站的链接。我们还指定了EA的版本号,设置为“1.00”。

//+------------------------------------------------------------------+
//|                           1. Zone Recovery RSI EA Multi-Zone.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

当加载程序时,系统将自动显示元数据信息。接下来,我们可以添加一些将显示在用户界面上的输入参数,具体设置如下:

sinput group "General EA Settings"
input double inputlot = 0.01;
input double inputzonesizepts = 200;
input double inputzonetragetpts = 400;
input double inputlotmultiplier = 2.0;
input double inputTrailingStopPts = 50; // Trailing stop distance in points
input double inputMinimumProfitPts = 50; // Minimum profit points before trailing stop starts
input bool inputTrailingStopEnabled = true; // Enable or disable trailing stop

我们将在"通用EA设置"分类下定义一组输入参数,允许用户在运行智能交易系统(EA)前配置核心设置。这些参数通过MQL5中的input数据类型声明,可直接在EA的输入设置面板中调整,无需修改代码。每个输入参数均用于精确控制EA的交易行为和风险管理。

"inputlot"参数:定义开仓的初始手数,默认值为0.01,实现交易仓位的精确控制。"inputzonesizepts"参数:指定恢复区域的点数大小,默认200点,决定恢复交易之间的间隔距离。"inputzonetragetpts"参数:设置目标盈利点数,默认400点,指导EA在何时以盈利状态平仓。

"inputlotmultiplier"参数:恢复交易手数乘数,默认2.0,使EA能根据乘数动态计算递增的恢复仓位。另外,追踪止损功能通过三个参数实现:"inputTrailingStopPts"参数定义追踪止损点数,默认50点,当市场朝有利方向移动时自动调整止损。"inputMinimumProfitPts"参数同样默认50点,确保仅在交易达到最低盈利阈值后激活追踪止损。

最后,inputTrailingStopEnabled参数采用bool(布尔)类型,允许用户根据需要启用或禁用追踪止损功能。这种灵活性确保EA能适应不同交易策略、风险偏好及市场环境,为高效交易与风险管理提供可定制的框架。接下来,由于需要执行开仓操作,我们需通过#include指令引入附加的文件,以调用"CTrade类"从而创建交易对象。这一步至关重要,因为我们需要使用它开立交易。

#include <Trade/Trade.mqh>
//--- Includes the MQL5 Trade library for handling trading operations.

预处理器会将#include<Trade/Trade.mqh>这一行替换为Trade.mqh文件的内容。尖括号表示Trade.mqh文件将从标准目录中获取(通常路径为terminal_installation_directory\MQL5\Include)。当前目录不会被包含在搜索范围内。这条指令可以放在程序的任何地方,但通常所有的包含指令都放在源代码的开头,这样可以使代码结构更清晰,也便于引用。以下是从导航栏中选取的具体文件:

CTrade类

接下来,我们需要声明一些将在交易系统中使用的关键全局变量。

//--- Global variables for RSI logic
int rsiPeriod = 14;                //--- The period used for calculating the RSI indicator.
int rsiHandle;                     //--- Handle for the RSI indicator, used to retrieve RSI values.
double rsiBuffer[];                //--- Array to store the RSI values retrieved from the indicator.
datetime lastBarTime = 0;          //--- Holds the time of the last processed bar to prevent duplicate signals.

这里,我们定义一组用于管理RSI指标逻辑的全局变量,这些变量将驱动EA生成交易信号。这些变量旨在高效处理RSI值的计算、获取与加工,确保流程的优化性。通过声明为全局变量,保证其在EA各处均可访问,从而实现一致且有效的信号生成。我们将类型为整型的"rsiPeriod"变量设为14,指定RSI指标的计算回溯周期。该值决定了EA将分析的K线数量以计算RSI值,从而控制指标的灵敏度。接下来,我们声明另一个整型变量rsiHandle,用于存储RSI指标的句柄。该句柄通过调用iRSI函数初始化RSI时获取,使我们能够直接从终端的指标缓冲区读取RSI值。

为存储这些RSI值,我们创建名为"rsiBuffer[]"的双精度类型动态数组。该数组将保存每根K线的RSI计算值,用于识别市场超买或超卖状态。此外,我们定义"lastBarTime"(日期时间型)变量来存储最后处理K线的时间戳。通过追踪该值,可以确保EA仅在新K线出现时处理信号,避免同一K线的重复触发。现在,我们可以定义通用交易组合参数,并将其结构关联到每一个生成的信号。为此,我们使用struct结构体,其通用语法如下:。

//--- Struct to track individual position recovery states
struct PositionRecovery {
        //--- Member 1
        //--- Member 2
        //--- Member 3

        //--- Method 1
        //...
};

为将相关数据变量集中管理,我们需要使用结构体,以下是其通用原型。我们定义名为"PositionRecovery"的struct结构体,作为组织和管理EA中单个持仓恢复状态相关数据的方案。该struct作为自定义数据类型,允许我们将相关变量(成员)和函数(方法)封装为单一实体。

语法说明:

"struct" "PositionRecovery { ... };"

以下声明了一个名为"PositionRecovery"的结构体。使用关键字struct定义结构体,大括号{ ... }用于涵盖其成员变量与方法。在MQL5中,结构体定义末尾的分号(;)为必填项。

  • 成员变量

成员变量是定义在结构体内部、用于存储每个"PositionRecovery"实例特有数据的变量。

//--- 成员变量1:例如交易初始手数或入场价格等数据的占位符。

//--- 成员变量2:可表示恢复区域大小或当前交易状态等参数。

//--- 成员变量3:附加数据,如已执行的恢复交易次数或恢复完成标识。

这些成员变量使我们能够封装跟踪和管理单个恢复流程所需的所有信息。

  • 方法

方法是定义在结构体内部、对其成员变量进行操作的功能函数。

//--- 方法1:占位符,例如计算下一次恢复交易的手数或检查是否达到恢复目标。

通过将数据(成员变量)与逻辑(方法)结合,结构体可实现更灵活且自包含的功能。理解上述概念后,我们即可开始定义结构体的成员变量。

//--- Struct to track individual position recovery states
struct PositionRecovery {
   CTrade trade;                    //--- Object to handle trading operations.
   double initialLotSize;           //--- Initial lot size for this position.
   double currentLotSize;           //--- Current lot size in the recovery sequence.
   double zoneSize;                 //--- Distance in points defining the recovery zone size.
   double targetSize;               //--- Distance in points defining the profit target range.
   double multiplier;               //--- Lot size multiplier for recovery trades.
   string symbol;                   //--- Trading symbol.
   ENUM_ORDER_TYPE lastOrderType;   //--- Type of the last order (BUY or SELL).
   double lastOrderPrice;           //--- Price of the last executed order.
   double zoneHigh;                 //--- Upper boundary of the recovery zone.
   double zoneLow;                  //--- Lower boundary of the recovery zone.
   double zoneTargetHigh;           //--- Upper boundary of the target range.
   double zoneTargetLow;            //--- Lower boundary of the target range.
   bool isRecovery;                 //--- Whether the recovery is active.
   ulong tickets[];                 //--- Array to store tickets of positions associated with this recovery.
   double trailingStop;             //--- Trailing stop level
   double initialEntryPrice;        //--- Initial entry price for trailing stop calculation

   //---

};

以下我们创建一个名为"PositionRecovery"的struct,用于组织和管理跟踪单个持仓恢复状态所需的所有数据。通过使用该结构体,可确保每个恢复流程独立处理,从而有效管理多个交易信号。

我们定义"CTrade trade"对象,用于执行与该持仓恢复相关的开仓、平仓及订单修改等交易操作。设置"initialLotSize"存储序列中首笔交易的手数,"currentLotSize"则跟踪恢复过程中最新交易的手数。为控制恢复策略,使用"zoneSize" 指定恢复区域距离(以点数计),并通过"targetSize"定义盈利目标区间。

为支持动态手数计算,引入"multiplier"作为后续恢复交易的手数乘数。添加"symbol"字段标识当前恢复的交易品种,确保EA在正确品种上执行交易。使用枚举类型ENUM_ORDER_TYPE声明"lastOrderType"变量,存储最后执行的订单类型(如买入/卖出),并通过"lastOrderPrice"记录其成交价格,用于跟踪恢复状态。定义"zoneHigh"和"zoneLow"作为恢复区间的上下边界,"zoneTargetHigh"和"zoneTargetLow"则用来标记盈利目标区间。

通过"isRecovery"判断恢复是否被激活,根据需要设置为true或false。另外,包含"tickets[]"数组,存储恢复序列中所有交易的订单号,便于单独跟踪和管理。最后,添加"trailingStop"字段指定追踪止损距离,以及"initialEntryPrice"记录首笔交易的入场价格,用于计算追踪止损。这些组件使我们能够在恢复过程中动态保护利润。

定义成员变量后,需在每次创建实例(即每个持仓组)时进行初始化。为此,可创建一个方法实现平滑的初始化逻辑。

//--- Initialize position recovery
void Initialize(double lot, double zonePts, double targetPts, double lotMultiplier, string _symbol, ENUM_ORDER_TYPE type, double price) {
   initialLotSize = lot;             //--- Assign initial lot size.
   currentLotSize = lot;             //--- Set current lot size equal to initial lot size.
   zoneSize = zonePts * _Point;      //--- Calculate zone size in points.
   targetSize = targetPts * _Point;  //--- Calculate target size in points.
   multiplier = lotMultiplier;       //--- Assign lot size multiplier.
   symbol = _symbol;                 //--- Assign the trading symbol.
   lastOrderType = type;             //--- Set the type of the last order.
   lastOrderPrice = price;           //--- Record the price of the last executed order.
   isRecovery = false;               //--- Set recovery as inactive initially.
   ArrayResize(tickets, 0);          //--- Initialize the tickets array.
   trailingStop = 0;                 //--- Initialize trailing stop
   initialEntryPrice = price;        //--- Set initial entry price
   CalculateZones();                 //--- Calculate recovery and target zones.
}

我们定义了"Initialize"方法,负责设置单个持仓恢复所需的所有参数,并初始化其状态。该方法确保每个恢复实例均能根据输入值正确配置,从而动态管理交易。将输入参数"lot"赋值给"initialLotSize",指定恢复序列中首笔交易的手数。同时设置"currentLotSize" = "initialLotSize",因为首笔交易的手数相同。接下来,使用输入参数"zonePts"和"targetPts"分别计算恢复区间大小(点数)和盈利目标范围(点数),通过乘以 _Point 常数(品种点值)将点数转换为实际价格差值。这些计算定义了管理恢复交易及其目标的距离阈值。

将输入参数"lotMultiplier"赋值给"multiplier"变量,确定后续恢复交易的手数递增规则。将交易品种赋值给"symbol",确保所有交易在正确市场执行。设置"lastOrderType" = "type"和"lastOrderPrice" = "price",记录最近订单的类型和成交价格。这些值用于跟踪当前恢复状态。另外,将"isRecovery" 初始化为false,表示恢复流程首次创建时未激活。

使用"ArrayResize"函数将 "tickets" 数组大小调整为 0,清空现有数据,准备存储该恢复实例关联的交易订单号。将"trailingStop"初始化为 0,以增加灵活性;并设置"initialEntryPrice" = price,为追踪止损计算提供基准值。最后,我们调用"CalculateZones"方法,计算恢复区间和目标范围的上下边界。确保EA拥有有效管理交易所需的所有数据。通过"Initialize"方法,我们为每个恢复流程建立了完整且明确的起点,确保所有相关参数正确设置,从而实现高效的交易管理。接下来,我们将定义"CalculateZones"函数,负责计算恢复区间的水平。

//--- Calculate dynamic zones and targets
void CalculateZones() {
   if (lastOrderType == ORDER_TYPE_BUY) { //--- If the last order was a BUY...
      zoneHigh = lastOrderPrice;         //--- Set upper boundary at the last order price.
      zoneLow = zoneHigh - zoneSize;     //--- Set lower boundary below the last order price.
      zoneTargetHigh = zoneHigh + targetSize; //--- Define target range above recovery zone.
      zoneTargetLow = zoneLow - targetSize;   //--- Define target range below recovery zone.
   } else if (lastOrderType == ORDER_TYPE_SELL) { //--- If the last order was a SELL...
      zoneLow = lastOrderPrice;                //--- Set lower boundary at the last order price.
      zoneHigh = zoneLow + zoneSize;           //--- Set upper boundary above the last order price.
      zoneTargetLow = zoneLow - targetSize;    //--- Define target range below recovery zone.
      zoneTargetHigh = zoneHigh + targetSize;  //--- Define target range above recovery zone.
   }
}

这里,我们定义了"CalculateZones" 方法,该方法根据最近执行订单的类型和成交价格,动态地计算持仓恢复区间的边界以及盈利目标范围。该方法确保每个恢复流程均拥有明确的价格水平,为后续交易决策提供依据,使系统能够根据市场波动做出合理的响应。

我们首先检查“lastOrderType”,以确定最近的订单是买入还是卖出。如果"lastOrderType"为ORDER_TYPE_BUY,将"zoneHigh"设置为"lastOrderPrice"(最近多头订单的入场价),作为恢复区间的上边界。随后,用“zoneHigh”减去“zoneSize”(转换为点数),得到下边界“zoneLow”。另外,我们设定盈利目标范围:"zoneTargetHigh" = "targetSize" + "zoneHigh";"zoneTargetLow" = "zoneLow" – "targetSize"。<因此确保恢复区间和盈利目标范围均以买入订单价格作为基准。

如果"lastOrderType"为ORDER_TYPE_SELL,我们应用反向逻辑。在这样的情况下,将"zoneLow"赋值为"lastOrderPrice",即以卖出订单的入场价作为恢复区间的下限。通过在"zoneLow"基础上增加"zoneSize"计算上界 "zoneHigh"。对于盈利目标范围,我们可以这样计算:从"zoneLow" 减去"targetSize" ,确定盈利目标的最小值;在"zoneHigh"的基础上增加"targetSize",确定盈利目标的最大值。这些边界均以卖出订单价格作为基准设定。这些层级定义的可视化结果如下图所示:

系统层级示意图

定义好区间层级后,就可以继续开仓。为提升代码可维护性,将开仓逻辑封装为独立方法,便于代码结构复用。

//--- Open a trade with comments for position type
bool OpenTrade(ENUM_ORDER_TYPE type, string comment) {
   if (type == ORDER_TYPE_BUY) { //--- For a BUY order...
      if (trade.Buy(currentLotSize, symbol, 0, 0, 0, comment)) { //--- Attempt to place a BUY trade.
         lastOrderType = ORDER_TYPE_BUY;                        //--- Update the last order type.
         lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Record the current price.
         ArrayResize(tickets, ArraySize(tickets) + 1);          //--- Resize the tickets array.
         tickets[ArraySize(tickets) - 1] = trade.ResultOrder(); //--- Store the new ticket.
         CalculateZones();                                      //--- Recalculate zones.
         isRecovery = false;                                    //--- Ensure recovery is inactive for initial trade.
         Print("Opened BUY Position, Ticket: ", tickets[ArraySize(tickets) - 1]);
         return true;                                           //--- Return success.
      }
   } else if (type == ORDER_TYPE_SELL) { //--- For a SELL order...
      if (trade.Sell(currentLotSize, symbol, 0, 0, 0, comment)) { //--- Attempt to place a SELL trade.
         lastOrderType = ORDER_TYPE_SELL;                        //--- Update the last order type.
         lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID);  //--- Record the current price.
         ArrayResize(tickets, ArraySize(tickets) + 1);           //--- Resize the tickets array.
         tickets[ArraySize(tickets) - 1] = trade.ResultOrder();  //--- Store the new ticket.
         CalculateZones();                                       //--- Recalculate zones.
         isRecovery = false;                                     //--- Ensure recovery is inactive for initial trade.
         Print("Opened SELL Position, Ticket: ", tickets[ArraySize(tickets) - 1]);
         return true;                                            //--- Return success.
      }
   }
   return false; //--- If the trade was not placed, return false.
}

在布尔函数 "OpenTrade"中,我们按指定类型(买入或卖出)开立新订单,并且同步更新多级区间恢复系统的关键数据。这样可以确保正确开单,并更新所有相关数据,以保持与恢复流程的同步。当"type"参数为ORDER_TYPE_BUY时,调用"trade.Buy"方法执行买入操作。该方法使用"currentLotSize"、"symbol"与 "comment"参数执行交易,将止损和止盈水平暂设为0(未指定),后续根据动态计算的区间目标定义这些参数。将"lastOrderType" 记为ORDER_TYPE_BUY,标识最近的订单类型;使用包含"SYMBOL_BID"参数的SymbolInfoDouble函数获得当前市场价,赋给"lastOrderPrice",作为后续区间计算的基准。

接下来,我们使用ArrayResize函数调整"tickets"数组的大小,为新开立的订单预留存储空间,并存储通过"trade.ResultOrder()"方法成功开仓的订单编号。这一步骤确保与当前恢复实例相关的所有订单均能被高效地追踪和管理。之后,我们调用"CalculateZones"函数,基于最新订单价格,重新计算恢复区间与目标区间。最后,将 "isRecovery"设置为false,表明这是初始开仓,而非恢复流程。打印成功日志并返回true,表示开仓成功。

如果 "type"参数为"ORDER_TYPE_SELL",我们会采用类似的逻辑:调用"trade.Sell"方法,按照指定参数执行卖出订单。成交后,将"lastOrderType" 更新为ORDER_TYPE_SELL,并将当前市场价记录于"lastOrderPrice"中。调整"tickets"数组大小,并将新开立的订单编号存入数组,此操作与处理买入订单时的逻辑完全一致。通过"CalculateZones"函数重新计算价格区间,并将isRecovery标识设置为false。最后,系统会打印成功消息,并返回true表示操作完成。

如果因订单类型(买入/卖出)导致开仓失败,函数将直接返回false,明确标识操作未成功。这一结构确保系统化管理交易信息,所有与恢复相关的数据都能正确更新,从而实现无缝的交易管理。在成功开仓并完成价格区间的计算后,我们就可以继续在每个Tick上监控这些区间,当价格进入任一区间时,系统将自动执行对应的恢复策略。

//--- Manage zone recovery
void ManageZones() {
   double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current price.
   if (lastOrderType == ORDER_TYPE_BUY && currentPrice <= zoneLow) { //--- If price drops below the recovery zone for a BUY...
      double previousLotSize = currentLotSize;                       //--- Store the current lot size temporarily.
      currentLotSize *= multiplier;                                 //--- Tentatively increase lot size.
      if (OpenTrade(ORDER_TYPE_SELL, "Recovery Position")) {        //--- Attempt to open a SELL recovery trade.
         isRecovery = true;                                         //--- Mark recovery as active if trade is successful.
      } else {
         currentLotSize = previousLotSize;                          //--- Revert the lot size if the trade fails.
      }
   } else if (lastOrderType == ORDER_TYPE_SELL && currentPrice >= zoneHigh) { //--- If price rises above the recovery zone for a SELL...
      double previousLotSize = currentLotSize;                       //--- Store the current lot size temporarily.
      currentLotSize *= multiplier;                                 //--- Tentatively increase lot size.
      if (OpenTrade(ORDER_TYPE_BUY, "Recovery Position")) {         //--- Attempt to open a BUY recovery trade.
         isRecovery = true;                                         //--- Mark recovery as active if trade is successful.
      } else {
         currentLotSize = previousLotSize;                          //--- Revert the lot size if the trade fails.
      }
   }
}

我们定义了一个名为"ManageZones"的函数,用于实时监控恢复区间的市场价格,并在价格反向偏离初始订单时触发相应的操作。首先,我们使用SymbolInfoDouble函数获取最新的买入价,作为市场价格判断基准。之后,我们检查价格是否突破恢复区间边界,对于买入订单而言边界下限为“zoneLow”,而卖出订单的边界上限则为“zoneHigh”。

如果上一笔订单为买入订单("lastOrderType" = ORDER_TYPE_BUY),且当前价格跌破恢复区间下限("zoneLow"),我们将执行补仓(加仓)操作。我们先保存当前手数到"previousLotSize"中,再将"currentLotSize"乘以"multiplier"实现加仓。随后调用"OpenTrade" 函数尝试开立卖出恢复订单。如果恢复订单成功成交,则将"isRecovery"设置为true,标记恢复模式已启动。如果下单失败,则把手数恢复为原先保存在"previousLotSize"中的值。

同样地,如果上一笔订单为卖出订单("lastOrderType" = ORDER_TYPE_SELL),且当前价格突破恢复区间上限(zoneHigh),我们将执行与买入订单对称的补仓操作。增加手数后,我们尝试开立买入恢复订单。如果恢复订单成功成交,将"isRecovery" 设为true;如果失败,则恢复手数。这样可以确保系统根据市场情况有效管理恢复交易,动态调整仓位规模,并在必要时采取纠正措施。最后,当价格触及预设目标价位时,我们需要平仓,因此还需编写处理该逻辑的函数。

//--- Check and close trades at targets
void CheckCloseAtTargets() {
   double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current price.
   if (lastOrderType == ORDER_TYPE_BUY && currentPrice >= zoneTargetHigh) { //--- If price reaches the target for a BUY...
      ClosePositionsAtTarget();                               //--- Close positions that meet the target criteria.
   } else if (lastOrderType == ORDER_TYPE_SELL && currentPrice <= zoneTargetLow) { //--- If price reaches the target for a SELL...
      ClosePositionsAtTarget();                               //--- Close positions that meet the target criteria.
   }
}

定义一个无返回值函数"CheckCloseAtTargets",用于检测市场价格是否触及预设的平仓目标价位,并在满足条件时关闭符合要求的持仓。首先,我们通过"SymbolInfoDoubleSYMBOL_BID)"函数获取当前交易品种的买入价,作为价格比较基准。之后,对于买入订单,检查买入价是否达到或超过预设的目标区间高位("zoneTargetHigh");对于卖出订单,检查买入价是否跌破或等于预设的目标区间低位("zoneTargetLow")。

如果上一笔交易为买入订单("lastOrderType" = ORDER_TYPE_BUY),且当前价格上涨至或超过预设的目标区间高位("zoneTargetHigh"),则认为持仓已达到预期盈利目标。此时,系统调用"ClosePositionsAtTarget"函数,平仓所有符合目标条件的持仓。同理,如果上一笔交易为卖出订单("lastOrderType" = ORDER_TYPE_SELL),且当前价格下跌至或低于预设的目标区间低位("zoneTargetLow"),系统同样调用"ClosePositionsAtTarget" 函数执行平仓操作。该函数确保市场触及指定盈利目标时自动平仓,锁定收益并完成交易恢复流程。

为复用平仓逻辑,我们封装了"ClosePositionsAtTarget"函数。该函数代码段如下:

//--- Close positions that have reached the target
void ClosePositionsAtTarget() {
   for (int i = ArraySize(tickets) - 1; i >= 0; i--) {              //--- Iterate through all tickets.
      ulong ticket = tickets[i];                                    //--- Get the position ticket.
      int retries = 10;                                             //--- Set retry count.
      while (retries > 0) {                                         //--- Retry until successful or retries exhausted.
         if (trade.PositionClose(ticket)) {                         //--- Attempt to close the position.
            Print("CLOSED # ", ticket, " Trailed and closed: ", (trailingStop != 0));
            ArrayRemove(tickets, i);                                //--- Remove the ticket from the array on success.
            retries = 0;                                            //--- Exit the loop on success.
         } else {
            retries--;                                              //--- Decrement retries on failure.
            Sleep(100);                                             //--- Wait before retrying.
         }
      }
   }
   if (ArraySize(tickets) == 0) {                                   //--- If all tickets are closed...
      Reset();                                                      //--- Reset recovery state after closing the target positions.
   }
}

在"ClosePositionsAtTarget"函数中,我们通过遍历存储在"tickets"数组中的所有持仓订单,尝试平仓那些已达到目标价位的订单。首先,我们从后往前遍历 "tickets"数组,确保在平仓并移除订单时不会漏掉任何仓位。对于每个订单号,设置最多"retries"次重试:如果首次平仓失败,系统将继续尝试直至成功。

针对每笔持仓,我们调用"trade.PositionClose(ticket)"函数尝试平仓。如果平仓成功,打印日志说明该订单号已平仓,并通过"trailingStop != 0" 检查是否使用了追踪止损。随后用 ArrayRemove从"tickets"数组移除该订单号,并将"retries" 设置为0以退出重试循环。如果平仓失败,则将"retries" 减 1,使用Sleep函数短暂等待,再重新尝试,避免对函数造成过度冲击。

尝试将全部仓位平仓后,使用ArraySize函数检查"tickets"数组是否已空。如果全部平仓完毕,调用"Reset"函数清空恢复状态及相关数据,为下一轮交易做好准备。以上这些就是全部。然而,市场未必总会触及我们的目标价位,因此可以做进一步优化:当仓位达到最低盈利时,改用追踪止损代替苦等目标价位。同样,我们把该逻辑封装在一个方法里。

//--- Apply trailing stop logic to initial positions
void ApplyTrailingStop() {
   if (inputTrailingStopEnabled && ArraySize(tickets) == 1) { // Ensure trailing stop is enabled and there is only one position (initial position)
      ulong ticket = tickets[0]; // Get the ticket of the initial position
      double entryPrice = GetPositionEntryPrice(ticket); // Get the entry price of the position by ticket
      double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); // Get the current price
      double newTrailingStop;

      if (lastOrderType == ORDER_TYPE_BUY) {
         if (currentPrice > entryPrice + (inputMinimumProfitPts + inputTrailingStopPts) * _Point) {
            newTrailingStop = currentPrice - inputTrailingStopPts * _Point; // Calculate new trailing stop for BUY
            if (newTrailingStop > trailingStop) {
               trailingStop = newTrailingStop; // Update trailing stop if the new one is higher
               Print("Trailing BUY Position, Ticket: ", ticket, " New Trailing Stop: ", trailingStop);
            }
         }

         if (trailingStop != 0 && currentPrice <= trailingStop) {
            Print("Trailed and closing BUY Position, Ticket: ", ticket);
            ClosePositionsAtTarget(); // Close position if the price falls below the trailing stop
         }
      } else if (lastOrderType == ORDER_TYPE_SELL) {
         if (currentPrice < entryPrice - (inputMinimumProfitPts + inputTrailingStopPts) * _Point) {
            newTrailingStop = currentPrice + inputTrailingStopPts * _Point; // Calculate new trailing stop for SELL
            if (newTrailingStop < trailingStop) {
               trailingStop = newTrailingStop; // Update trailing stop if the new one is lower
               Print("Trailing SELL Position, Ticket: ", ticket, " New Trailing Stop: ", trailingStop);
            }
         }

         if (trailingStop != 0 && currentPrice >= trailingStop) {
            Print("Trailed and closing SELL Position, Ticket: ", ticket);
            ClosePositionsAtTarget(); // Close position if the price rises above the trailing stop
         }
      }
   }
}

在自定义的 "ApplyTrailingStop"方法中,我们仅在启用追踪止损且仅有一笔持仓为初始订单时执行该逻辑。首先,通过"inputTrailingStopEnabled"确认跟踪止损功能是否启用,并且根据"ArraySize(tickets) = 1"确保当前是否仅有一笔持仓。随后,我们获取初始持仓的订单号,并通过"GetPositionEntryPrice" 函数获取其入场价。同时用SymbolInfoDouble函数读取当前市场价。

对于买入订单,我们检查当前价格是否已高于入场价达到指定幅度(该幅度为最小盈利点数与追踪止损点数之和,即"inputMinimumProfitPts + inputTrailingStopPts"),并据此设定新的追踪止损价位。如果计算出的追踪止损价位高于当前"trailingStop"值,则更新跟踪止损并打印新止损水平日志。一旦当前价格回落至或跌破该追踪止损价位,立即调用"ClosePositionsAtTarget"函数平仓。

对于卖出订单,我们采用类似的逻辑,但方向相反。我们检查当前价格是否从入场价下跌超过特定的幅度,必要时下调追踪止损。如果计算出的追踪止损低于当前"trailingStop"值,则更新并打印新水平日志。一旦当前价回升至或突破该追踪止损价位,立即平仓。该函数确保追踪止损跟随市场情况动态调整,允许锁定利润,同时避免蒙受重大损失。如果价格朝向有利方向移动,系统将自动调整追踪止损价位;如果价格反向波动并触及追踪止损价位,则立即平仓以锁定利润或限制损失。

您可能已经注意到,我们使用自定义函数来获取入场价。以下是该函数的逻辑:

//--- Get the entry price of a position by ticket
double GetPositionEntryPrice(ulong ticket) {
   if (PositionSelectByTicket(ticket)) {
      return PositionGetDouble(POSITION_PRICE_OPEN);
   } else {
      Print("Failed to select position by ticket: ", ticket);
      return 0.0;
   }
}

在这部分,我们定义了"GetPositionEntryPrice"函数,通过给定的订单号获取持仓的入场价。首先,我们使用PositionSelectByTicket函数尝试选中对应订单号的持仓。如果选中成功,调用"PositionGetDouble (POSITION_PRICE_OPEN)"获取开仓价格。如果无法选中(如订单号无效或持仓已平仓),我们打印一条错误信息并返回0.0,表示未能获取入场价。

完成开仓与平仓后,我们还需要重置系统并移除相关交易组合,以此作为清理流程。以下是在"Reset"函数中如何实现清理逻辑:

//--- Reset recovery state
void Reset() {
   currentLotSize = inputlot; //--- Reset lot size to initial value.
   lastOrderType = -1;              //--- Clear the last order type.
   lastOrderPrice = 0.0;            //--- Reset the last order price.
   isRecovery = false;              //--- Mark recovery as inactive.
   ArrayResize(tickets, 0);         //--- Clear the tickets array.
   trailingStop = 0;                //--- Reset trailing stop
   initialEntryPrice = 0.0;         //--- Reset initial entry price
   Print("Strategy BASKET reset after closing trades.");
}

在"Reset"函数中,我们将恢复状态重置,为新一轮交易周期做准备。首先,我们将"currentLotSize" 恢复为用户初始设定值"inputlot",确保将手数大小重置为用户定义的起始数。我们还把"lastOrderType"设置为 -1(表示无活动订单类型),并且将"lastOrderPrice"清零,有效移除上一单价格信息。

接着,我们将“isRecovery”设置为false,标记恢复状态为未激活,确保在重置时不会触发任何恢复逻辑。随后,我们使用 ArrayResize函数清空“tickets”数组,移除之前恢复过程中保存的所有持仓订单号。另外,我们将“trailingStop”重置为 0,并将“initialEntryPrice”清零,彻底清除旧订单的追踪止损和入场价格。最后,打印日志“Strategy BASKET reset after closing trades”,通知系统已完成重置,恢复状态已清空。该函数确保系统处于干净状态,随时准备好进入下一轮交易周期。

在定义完结构属性后,我们就可以生成信号并将其添加到已定义的结构中。然而,由于需要管理大量动态信号,我们必须定义一个数组结构,它将作为一个“总组合”,用于为每个生成的信号创建对应的“子组合”。其实现方式如下:

//--- Dynamic list to track multiple positions
PositionRecovery recoveryArray[]; //--- Dynamic array for recovery instances.

在这部分,我们声明一个名为“recoveryArray”的动态数组,用于跟踪和管理多个仓位的恢复实例。该数组基于“PositionRecovery”结构,可独立存储多笔交易的恢复状态。每个数组元素代表一个独立的恢复设置,包含所有相关属性,如手数、区间边界和关联的交易订单号。

通过将数组设为动态,我们可在运行时使用ArrayResize等函数按需扩展或收缩其大小。这样就能随时新增恢复实例以对应新交易信号,或移除已完成的恢复,确保内存使用高效,并能适应不同交易场景。此方法对同时管理多笔交易至关重要,它让每笔交易的恢复逻辑在独立的数据“组合”内运作,互不影响。

完成数组定义后,我们现在可以开始编写信号生成逻辑。我们需在OnInit事件处理器中初始化指标句柄,这样就会在程序启动时被调用。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE); //--- Create RSI indicator handle.
   if (rsiHandle == INVALID_HANDLE) {                                 //--- Check if handle creation failed.
      Print("Failed to create RSI handle. Error: ", GetLastError());  //--- Print error message.
      return(INIT_FAILED);                                            //--- Return initialization failure.
   }
   ArraySetAsSeries(rsiBuffer, true); //--- Set RSI buffer as a time series.
   Print("Multi-Zone Recovery Strategy initialized."); //--- Log initialization success.
   return(INIT_SUCCEEDED); //--- Return initialization success.
}

OnInit事件处理器中,我们初始化EA运行所需的关键组件。首先使用iRSI函数生成RSI指标句柄,该函数用于计算当前交易品种和周期的RSI。此句柄允许EA动态获取RSI值。如果句柄创建失败,返回INVALID_HANDLE,通过 Print函数输出一条错误日志并返回INIT_FAILED终止初始化流程。接下来,我们使用ArraySetAsSeries将 "rsiBuffer" 数组设置为时间序列模式,确保数据按时间倒序排列。在成功完成所有初始化步骤后,我们打印一条确认信息并返回 INIT_SUCCEEDED,标识EA运行准备就绪。之后在OnDeinit事件处理器中,我们清理句柄以节省资源。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if (rsiHandle != INVALID_HANDLE)             //--- Check if RSI handle is valid.
      IndicatorRelease(rsiHandle);              //--- Release the RSI handle.
   Print("Multi-Zone Recovery Strategy deinitialized."); //--- Log deinitialization.
}

在这部分,我们处理EA被移除或停用时的清理和资源管理。首先检查RSI指标"rsiHandle"是否有效(即不等于 INVALID_HANDLE),避免对无效句柄操作。如果句柄有效,调用IndicatorRelease()函数释放其占用的系统资源,防止内存泄漏。最后,使用Print()输出确认信息,表明多区域恢复策略(Multi-Zone Recovery Strategy)已成功卸载。该函数确保干净、有序地关闭程序,不会留下任何残留资源或后台进程。

随后,我们进入OnTick事件处理器,它将利用前面定义的结构处理系统的全部核心逻辑。首先,我们需要获取指标数据,以便后续分析使用。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   if (CopyBuffer(rsiHandle, 0, 1, 2, rsiBuffer) <= 0) { //--- Copy the RSI buffer values.
      Print("Failed to copy RSI buffer. Error: ", GetLastError()); //--- Print error on failure.
      return;                                                     //--- Exit on failure.
   }

   //---

}

OnTick函数中,我们实现每个价格更新(新tick)时触发的交易逻辑如下:第一步,使用CopyBuffer把 RSI 指标值复制到"rsiBuffer"数组中。我们指定“rsiHandle”来标识RSI指标,将缓冲区索引设置为0,并请求从最新K线起取两个值。如果操作失败(返回值≤0),我们会立即使用Print打印一条错误信息(包含从GetLastError函数获取的错误详情)。记录错误后,我们立即使用return退出函数。这样确保了如果RSI数据检索失败,其余逻辑不会被执行,从而保障了EA的完整性和稳定性。

如果成功获取数据,我们可以将其用于后续信号生成逻辑。

datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); //--- Get the time of the current bar.
if (currentBarTime != lastBarTime) {                         //--- Check if a new bar has formed.
   lastBarTime = currentBarTime;                             //--- Update the last processed bar time.
   if (rsiBuffer[1] > 30 && rsiBuffer[0] <= 30) {            //--- Check for oversold RSI crossing up.
      Print("BUY SIGNAL");
      PositionRecovery newRecovery;                          //--- Create a new recovery instance.
      newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_BUY, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery.
      newRecovery.OpenTrade(ORDER_TYPE_BUY, "Initial Position"); //--- Open an initial BUY position.
      ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array.
      recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array.
   } else if (rsiBuffer[1] < 70 && rsiBuffer[0] >= 70) {      //--- Check for overbought RSI crossing down.
      Print("SELL SIGNAL");
      PositionRecovery newRecovery;                          //--- Create a new recovery instance.
      newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_SELL, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery.
      newRecovery.OpenTrade(ORDER_TYPE_SELL, "Initial Position"); //--- Open an initial SELL position.
      ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array.
      recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array.
   }
}

在这部分,我们重点实现新K线形成检测,并基于指标突破特定水平位生成交易信号。首先,我们使用iTime()函数获取当前K线的开盘时间,存储于 "currentBarTime"变量中。通过比较"currentBarTime" 与"lastBarTime"判断是否形成新K线。如果两者不同,说明新K线已形成,因为我们将"lastBarTime" 更新为"currentBarTime",防止重复处理。

接下来,我们将评估基于RSI的交易信号条件。如果前一K线的RSI值("rsiBuffer[1]" )大于30且当前K线RSI值("rsiBuffer[0]")小于等于30,表明RSI指标向上穿越,形成超卖反弹信号。在此情况下,我们打印“BUY SIGNAL”信息,并新建一个名为“newRecovery”的"PositionRecovery"实例。我们随后调用"newRecovery"的"Initialize"方法,配置恢复参数,包括:"inputlot"、"inputzonesizepts"、"inputzonetragetpts"、"inputlotmultiplier"、交易品种、订单类型(如ORDER_TYPE_BUY),以及通过 "SymbolInfoDouble"函数获取的当前买入价。初始化完成后,使用"OpenTrade"方法开立首笔"买入"订单,传入"ORDER_TYPE_BUY"与描述性注释。

类似地,如果缓冲区中前一根K线的RSI值("rsiBuffer[1]")小于70,而当前K线的RSI值(rsiBuffer[0])大于或等于70,则表明市场进入超买状态且RSI指标向下穿越。在此情况下,我们打印 "SELL SIGNAL" 信息,并创建"PositionRecovery" 类的实例。使用相同的参数初始化后,将订单类型设置为ORDER_TYPE_SELL,我们使用“OpenTrade”方法开立首笔"卖出"订单。

最终,针对"买入"和"卖出"信号,我们将初始化后的"PositionRecovery"实例添加至"recoveryArray"数组中通过ArrayResize动态调整数组大小,并将新实例赋值至数组末尾,确保独立跟踪每个持仓恢复逻辑。此机制负责根据初始仓位和条件初始化持仓组合(Basket)。为执行持仓管理,需在每个交易周期(tick)中遍历主持仓组合内的所有子持仓,并对每个子持仓应用与主策略结构一致的管理逻辑。逻辑说明如下:

for (int i = 0; i < ArraySize(recoveryArray); i++) { //--- Iterate through all recovery instances.
   recoveryArray[i].ManageZones();                 //--- Manage zones for each recovery instance.
   recoveryArray[i].CheckCloseAtTargets();         //--- Check and close positions at targets.
   recoveryArray[i].ApplyTrailingStop();           //--- Apply trailing stop logic to initial positions.
}

为实现对各仓位的独立管理,我们使用for loop遍历存储在"recoveryArray"中的所有恢复实例。该循环确保每个恢复实例被单独处理,使系统能够对多个恢复场景保持独立控制。循环从索引i = 0开始,直到ArraySize函数返回的"recoveryArray"中的所有元素全部处理完毕为止。

在循环处理过程中,会对每个持仓恢复实例依次调用以下三个关键方法:首先,通过点运算符(.)调用“ManageZones”方法,该方法会持续监控价格相对于预设恢复区间的波动情况。一旦价格突破区间边界,该方法会尝试开立恢复头寸,并且按预设倍数动态调整开仓手数。

接下来,执行"CheckCloseAtTargets" 方法以判断价格是否触及恢复实例的目标价位。如果条件满足,该方法会平掉所有关联仓位,并重置恢复实例,确保锁定利润并准备下一交易周期。

最后,对恢复实例的初始仓位应用"ApplyTrailingStop"方法,实现追踪止损逻辑。该方法随价格有利变动动态调整追踪止损价位,从而锁定利润。一旦价格反转并触及追踪止损,会确保平仓,防止潜在亏损扩大。

通过依次处理每个恢复实例,系统可同时管理多笔独立头寸,确保所有恢复场景都能按预设策略动态执行。为了确保程序运行正常,我们启动实测,结果如下。

策略重置示例

由图可见,系统在关闭某个恢复实例后成功完成了重置,这正是本系统的核心目标之一。然而,该关闭操作并未影响其他正在运行的实例,说明该实例在数组中被独立处理。为进一步确认,我们切换到交易标签页,可以看到仍有其他活跃实例正在运行。

交易示例

由图可见,当前系统中仍存在多个持仓恢复实例,其中两个实例已进入恢复模式。这些实例通过右侧的注释字段被清晰区分,标注内容表明了头寸类型(初始头寸或恢复头寸)。这一结果证明,我们已成功实现了目标,余下的部分就是对程序进行回测并分析其性能。相关内容将在下一节展开说明。


回测

为评估程序的性能和稳健性,必须首先模拟历史市场环境。通过这一过程,可检验程序在恢复场景处理、价格波动适应性和交易管理方面的表现。回测结果能提供关键指标数据,包括策略的盈利能力、回撤水平及风险管理能力。程序通过逐tick处理历史价格数据,基于预设阈值(如超卖30或超买70,该值按用户偏好定制)生成买卖信号。当信号触发时,EA初始化新的恢复实例,执行首笔交易,并且在指定恢复区间内持续追踪价格波动。 

我们针对系统的动态恢复功能进行了严格测试,重点验证:通过倍数调整手数、必要时开立对冲头寸、在不同市场条件下的稳定性。程序对每个信号生成的恢复场景进行独立评估,通过"recoveryArray"数组实现完全隔离的头寸管理。这样确保即使同时存在多个活跃恢复实例,策略仍保持有序地运行。我们采用以下配置对程序进行了5个月历史数据测试:

组合设置

完成后,我们得到以下结果:

策略测试图

图表

策略测试报告

报告

从上述图表中可见,尽管账户余额与净值曲线在整体上呈现平滑趋势,但在特定阶段仍会出现明显波动。这种波动主要源于恢复层级叠加效应和生成信号数量激增。因此,我们可以通过启用追踪止损功能限制最大持仓数量。以下是我们得到的结果:

REPORT_TRAILING ENABLED

由图可见,启用追踪止损功能后,总交易次数下降,且胜率明显提升。为进一步控制仓位数量,可加入“持仓上限”逻辑——当已开仓位达到预设数量时,即使出现新信号也不再加仓。为实现该逻辑,需先定义以下新增输入变量:

input bool inputEnablePositionsRestriction = true; // Enable Maximum positions restriction
input int inputMaximumPositions = 11; // Maximum number of positions

这些输入参数包含两个关键设置:启用/禁用限制的开关标识和当限制启用时系统允许的最大持仓数。随后,在OnTick事件处理器中,一旦信号得到确认,便加入这层附加的交易限制逻辑。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   if (CopyBuffer(rsiHandle, 0, 1, 2, rsiBuffer) <= 0) { //--- Copy the RSI buffer values.
      Print("Failed to copy RSI buffer. Error: ", GetLastError()); //--- Print error on failure.
      return;                                                     //--- Exit on failure.
   }

   datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); //--- Get the time of the current bar.
   if (currentBarTime != lastBarTime) {                         //--- Check if a new bar has formed.
      lastBarTime = currentBarTime;                             //--- Update the last processed bar time.
      if (rsiBuffer[1] > 30 && rsiBuffer[0] <= 30) {            //--- Check for oversold RSI crossing up.
         Print("BUY SIGNAL");
         if (inputEnablePositionsRestriction == false || inputMaximumPositions > PositionsTotal()){
            PositionRecovery newRecovery;                          //--- Create a new recovery instance.
            newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_BUY, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery.
            newRecovery.OpenTrade(ORDER_TYPE_BUY, "Initial Position"); //--- Open an initial BUY position.
            ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array.
            recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array.
         }
         else {
            Print("FAILED: Maximum positions threshold hit!");
         }
      } else if (rsiBuffer[1] < 70 && rsiBuffer[0] >= 70) {      //--- Check for overbought RSI crossing down.
         Print("SELL SIGNAL");
         if (inputEnablePositionsRestriction == false || inputMaximumPositions > PositionsTotal()){
            PositionRecovery newRecovery;                          //--- Create a new recovery instance.
            newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_SELL, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery.
            newRecovery.OpenTrade(ORDER_TYPE_SELL, "Initial Position"); //--- Open an initial SELL position.
            ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array.
            recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array.
         }
         else {
            Print("FAILED: Maximum positions threshold hit!");
         }
      }
   }

   for (int i = 0; i < ArraySize(recoveryArray); i++) { //--- Iterate through all recovery instances.
      recoveryArray[i].ManageZones();                 //--- Manage zones for each recovery instance.
      recoveryArray[i].CheckCloseAtTargets();         //--- Check and close positions at targets.
      recoveryArray[i].ApplyTrailingStop();           //--- Apply trailing stop logic to initial positions.
   }
}

这里,我们实现了一套持仓数量动态管控系统,用于控制EA在任何时刻可开立的最大持仓数量。此逻辑首先判断仓位限制是否被关闭("inputEnablePositionsRestriction" = false)或者当前已开仓数量(PositionsTotal)是否低于用户设定的上限("inputMaximumPositions")如果任一情况成立,EA将继续开新单,确保该机制与用户设定的无限制交易或限额交易偏好保持一致。

然而,当两种情况均不成立(即限制启用且已达上限)时,EA将不再开立新订单,并在终端记录:相反,EA会在终端记录一条失败消息:“失败:达到最大仓位阈值!”。此消息作为一种信息反馈机制,帮助用户理解为什么没有执行额外的交易。相关改动我们已用浅黄色高亮显示。实测结果如下:

REPORT_MAXIMUM ORDERS RESTRICTION

由图可见,交易数量进一步减少,胜率继续提升。验证了我们已成功构建多区域恢复系统的目标。在下方GIF动图中,我们可视化整个模拟过程,再次确认目标已达成。

MULTI-ZONE GIF


结论

总体而言,本文详细阐述了基于多层级区域恢复策略构建稳健型MQL5 EA的全过程。通过整合自动化信号识别、动态恢复管理及追踪止损等盈利保障机制,我们成功开发出一个可同时处理多个独立恢复实例的弹性交易系统。该实现的核心模块包括:交易信号的生成、持仓数量管控逻辑和恢复策略与退出机制的高效协同。

免责声明:本文仅作为MQL5编程教学参考资料。尽管所提出的多层级区域恢复系统为交易管理提供了结构化框架,但市场行为本身具有不确定性。交易涉及金融风险,历史表现不代表未来结果。在实盘应用前必须完成全面的策略测试和有效的风险管理。

通过遵循本指南中讨论的方法,您可以扩展您在算法交易方面的专业知识,并应用这些原则来创建更复杂的交易系统。开发顺利,祝您交易成功!

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17001

以 MQL5 实现强化分类任务的融汇方法 以 MQL5 实现强化分类任务的融汇方法
在本文中,我们讲述以 MQL5 实现若干融汇分类器,并讨论了它们在不同状况下的功效。
开发回放系统(第 77 部分):新 Chart Trade(四) 开发回放系统(第 77 部分):新 Chart Trade(四)
在本文中,我们将介绍创建通信协议时需要考虑的一些措施和预防措施。这些都是非常简单明了的事情,所以我们在本文中不会详细介绍。但要了解会发生什么,您需要了解文章的内容。
在MQL5中自动化交易策略(第5部分):开发自适应交叉RSI交易套件策略 在MQL5中自动化交易策略(第5部分):开发自适应交叉RSI交易套件策略
在本文中,我们开发了自适应交叉RSI交易套件系统。该系统使用周期为14和50的移动平均线交叉来产生信号,并由一个周期为14的RSI过滤器进行确认。该系统包含一个交易日过滤器、带注释的信号箭头,以及一个用于监控的实时仪表盘。 这种方法确保了自动化交易中的精确性和适应性。
集成学习模型中的门控机制 集成学习模型中的门控机制
在本文中,我们继续探讨集成模型,重点讨论“门控”的概念,尤其是门控如何通过整合模型输出来提升预测准确性或模型泛化能力。