English Deutsch 日本語
preview
MQL5自动化交易策略(第十八部分):基于包络线趋势反弹的剥头皮交易——核心架构与信号生成(1)

MQL5自动化交易策略(第十八部分):基于包络线趋势反弹的剥头皮交易——核心架构与信号生成(1)

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

引言

上一篇文章(第十七部分)中,我们已经实现了网格马丁剥头皮策略的自动化,并添加了动态看板功能,以便实时监控交易情况。在第十八部分,我们将在MetaQuotes Language 5(MQL5) 中着手开发包络线趋势反弹剥头皮策略的自动化系统,构建EA的核心架构并实现信号生成逻辑。本文将包括几个方面:

  1. 理解策略
  2. 在MQL5中的实现
  3. 回测
  4. 结论

最终,您将为趋势反弹剥头皮策略打下坚实的基础,并为下一部分的交易执行做好准备——让我们开始吧!


理解策略

包络线趋势反弹剥头皮策略采用包络线指标,该指标在移动平均线的基础上上下设定一定偏差(如0.1%至1.4%),形成上下轨道,用于识别价格反转点,从而获取小额利润。该策略的核心逻辑在于:在上升趋势中,当价格触及下轨时产生买入信号;在下降趋势中,当价格触及上轨时产生卖出信号。这些信号会经趋势过滤器确认,例如200周期的指数移动平均线或8周期的相对强弱指标(RSI)。该策略在趋势市场中表现优异,但在震荡行情中容易产生虚假信号,因此需要采取严格的风险控制措施,我们将对此进行详细阐述。

我们的实施计划是开发一个程序,实现该策略的自动化,具体包括初始化包络线和趋势指标、检测反弹信号,并建立可靠的信号验证机制。我们将使用模块化函数来计算价格与轨道之间的相互作用,并对交易进行筛选,以确保高频剥头皮交易的精确执行。通过实施风险控制措施,如限制最大交易频率和进行信号多重确认,将确保策略在不同市场环境下的稳定性和可靠性。简而言之,下图展示了我们的目标。

方案


在MQL5中的实现

要在MQL5中创建此程序,请打开MetaEditor,在导航器中找到“Experts”文件夹,点击“新建”标签,并按照提示创建文件。文件创建完成后,在编程环境中,我们需要声明一些将贯穿整个程序使用的全局变量输入参数

//+------------------------------------------------------------------+
//|                      Envelopes Trend Bounce Scalping Strategy EA |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+

#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict                                           //--- Enable strict compilation for MQL5 compatibility

//--- Include trade operations library
#include <Trade\Trade.mqh> //--- Import MQL5 trade functions for order execution

//--- Input parameters for user configuration
input string OrderComment = __FILE__;                     // Comment to orders
input int MagicNumber = 123456789;                        // Unique identifier for EA orders
double PipPointOverride = 0;                              //--- Override pip point value manually (0 for auto-detection)
input int MaxDeviationSlippage = 10;                      //--- Set maximum slippage in points for trades
bool AllowManualTPSLChanges = true;                       //--- Permit manual adjustment of TP and SL lines on chart
bool OneQuotePerBar = false;                              //--- Process only first tick per bar if true to limit trades
bool AlertOnError = false;                                //--- Trigger MetaTrader alerts for errors
bool NotificationOnError = false;                         //--- Send push notifications for errors
bool EmailOnError = true;                                 //--- Send email notifications for errors
bool DisplayOnChartError = true;                          //--- Show error messages on chart
bool DisplayOrderInfo = false;                            //--- Show order details on chart if enabled
ENUM_TIMEFRAMES DisplayOrderDuringTimeframe = PERIOD_M1;   //--- Set timeframe for order info display (default: M1)
input string CComment = __FILE__;                         //--- Add secondary comment (default: file name)

//--- Global variables for EA functionality
double PipPoint = 0.0001;                                 //--- Initialize pip point (default for 4-digit symbols)
uint OrderFillingType = -1;                               //--- Store order filling type (FOK, IOC, or Return)
uint AccountMarginMode = -1;                              //--- Store account margin mode (Netting or Hedging)
bool StopEA = false;                                      //--- Pause EA operations if true
double UnitsOneLot = 100000;                              //--- Define standard lot size (100,000 units for forex)
int IsDemoLiveOrVisualMode = false;                       //--- Flag demo, live, or visual backtest mode
string Error;                                             //--- Hold current error message
string ErrorPreviousQuote;                                //--- Hold previous quote's error message
string OrderInfoComment;                                  //--- Store order information comments

我们首先搭建程序的核心架构,重点配置库文件、用户输入参数以及用于信号生成的全局变量。我们通过 #include 指令引入 “Trade.mqh” 库,以便处理交易操作。我们定义了一系列输入参数,包括 “OrderComment”、“MagicNumber”(默认值 123456789)、“PipPointOverride” 以及 “MaxDeviationSlippage”(默认值 10)。此外,还包括布尔型参数 “AllowManualTPSLChanges”(默认 true)、“EmailOnError”(默认 true)以及 “DisplayOrderDuringTimeframe”(默认 “PERIOD_M1”)。

我们初始化了全局变量,如 “PipPoint”(默认 0.0001)、“OrderFillingType”、“AccountMarginMode”、“StopEA”(默认 false)以及 “UnitsOneLot”(默认 100,000),同时设置了用于错误和订单追踪的 “Error” 与 “OrderInfoComment”,为后续的指标配置奠定了基础。现在,我们还可以定义一些程序中将要使用的常量和枚举。

//--- Define constants for order types
#define OP_BUY 0                                          //--- Represent Buy order type
#define OP_SELL 1                                         //--- Represent Sell order type

//--- Define constants for market data retrieval
#define MODE_TIME 5                                       //--- Retrieve symbol time
#define MODE_BID 9                                        //--- Retrieve Bid price
#define MODE_ASK 10                                       //--- Retrieve Ask price
#define MODE_POINT 11                                     //--- Retrieve point size
#define MODE_DIGITS 12                                    //--- Retrieve digit count
#define MODE_SPREAD 13                                    //--- Retrieve spread
#define MODE_STOPLEVEL 14                                 //--- Retrieve stop level
#define MODE_LOTSIZE 15                                   //--- Retrieve lot size
#define MODE_TICKVALUE 16                                 //--- Retrieve tick value
#define MODE_TICKSIZE 17                                  //--- Retrieve tick size
#define MODE_SWAPLONG 18                                  //--- Retrieve swap long
#define MODE_SWAPSHORT 19                                 //--- Retrieve swap short
#define MODE_STARTING 20                                  //--- Unused, return 0
#define MODE_EXPIRATION 21                                //--- Unused, return 0
#define MODE_TRADEALLOWED 22                              //--- Unused, return 0
#define MODE_MINLOT 23                                    //--- Retrieve minimum lot
#define MODE_LOTSTEP 24                                   //--- Retrieve lot step
#define MODE_MAXLOT 25                                    //--- Retrieve maximum lot
#define MODE_SWAPTYPE 26                                  //--- Retrieve swap mode
#define MODE_PROFITCALCMODE 27                            //--- Retrieve profit calculation mode
#define MODE_MARGINCALCMODE 28                            //--- Unused, return 0
#define MODE_MARGININIT 29                                //--- Unused, return 0
#define MODE_MARGINMAINTENANCE 30                         //--- Unused, return 0
#define MODE_MARGINHEDGED 31                              //--- Unused, return 0
#define MODE_MARGINREQUIRED 32                            //--- Unused, return 0
#define MODE_FREEZELEVEL 33                               //--- Retrieve freeze level

//--- Define string conversion macros
#define CharToStr CharToString                            //--- Convert char to string
#define DoubleToStr DoubleToString                        //--- Convert double to string
#define StrToDouble StringToDouble                        //--- Convert string to double
#define StrToInteger (int)StringToInteger                 //--- Convert string to integer
#define StrToTime StringToTime                            //--- Convert string to datetime
#define TimeToStr TimeToString                            //--- Convert datetime to string
#define StringGetChar StringGetCharacter                  //--- Get character from string
#define StringSetChar StringSetCharacter                  //--- Set character in string

//--- Define enumerations for order grouping
enum ORDER_GROUP_TYPE { 
   Single=1,                                              //--- Group as single order
   SymbolOrderType=2,                                     //--- Group by symbol and order type
   Basket=3,                                              //--- Group all orders as a basket
   SymbolCode=4                                           //--- Group by symbol
};

//--- Define enumerations for profit calculation
enum ORDER_PROFIT_CALCULATION_TYPE { 
   Pips=1,                                                //--- Calculate profit in pips
   Money=2,                                               //--- Calculate profit in currency
   EquityPercentage=3                                     //--- Calculate profit as equity percentage
};

//--- Define enumerations for CRUD operations
enum CRUD { 
   NoAction=0,                                            //--- Perform no action
   Created=1,                                             //--- Create item
   Updated=2,                                             //--- Update item
   Deleted=3                                              //--- Delete item
};

在此,我们通过定义常量、和枚举来增强程序功能,从而简化订单处理与数据获取流程。我们首先定义了代表订单类型的常量,例如“OP_BUY”(0)和“OP_SELL”(1),以及一系列“MODE_”常量(如“MODE_BID” = 9,“MODE_ASK” = 10),用于获取买/卖价、点差和手数等市场数据。同时,我们还定义了字符串转换宏,例如 CharToStringDoubleToString,以简化数据类型转换操作。

接着,我们创建了“ORDER_GROUP_TYPE”枚举用于对订单进行分类(如“Single” = 1,“SymbolOrderType” = 2);“ORDER_PROFIT_CALCULATION_TYPE”枚举用于定义盈利指标(如“Pips” = 1,“Money” = 2);以及“CRUD”枚举用于管理操作状态(如“Created” = 1,“Updated” = 2)。这些定义将确保数据处理的一致性,并为程序的模块化信号生成提供支持。随后,我们可以定义如下一些辅助函数。

//--- Copy single indicator buffer value
double CopyBufferOneValue(int handle, int index, int shift) {
   double buf[];                                          //--- Declare array for buffer data
   //--- Copy one value from indicator buffer
   if(CopyBuffer(handle, index, shift, 1, buf) > 0)
      return(buf[0]);                                     //--- Return buffer value
   return EMPTY_VALUE;                                    //--- Return EMPTY_VALUE on failure
}

//--- Retrieve current Ask price
double Ask_LibFunc() {
   MqlTick last_tick;                                     //--- Declare tick data structure
   SymbolInfoTick(_Symbol, last_tick);                    //--- Fetch latest tick for symbol
   return last_tick.ask;                                  //--- Return Ask price
}

//--- Retrieve current Bid price
double Bid_LibFunc() {
   MqlTick last_tick;                                     //--- Declare tick data structure
   SymbolInfoTick(_Symbol, last_tick);                    //--- Fetch latest tick for symbol
   return last_tick.bid;                                  //--- Return Bid price
}

//--- Retrieve account equity
double AccountEquity_LibFunc() {
   return AccountInfoDouble(ACCOUNT_EQUITY);              //--- Return current equity
}

//--- Retrieve account free margin
double AccountFreeMargin_LibFunc() {
   return AccountInfoDouble(ACCOUNT_MARGIN_FREE);         //--- Return free margin
}

//--- Retrieve market information for a symbol
double MarketInfo_LibFunc(string symbol, int type) {
   switch(type) {                                                           //--- Handle requested info type
   case MODE_LOW:
      return(SymbolInfoDouble(symbol, SYMBOL_LASTLOW));                     //--- Return last low price
   case MODE_HIGH:
      return(SymbolInfoDouble(symbol, SYMBOL_LASTHIGH));                    //--- Return last high price
   case MODE_TIME:
      return((double)SymbolInfoInteger(symbol, SYMBOL_TIME));               //--- Return symbol time
   case MODE_BID:
      return(Bid_LibFunc());                                                //--- Return Bid price
   case MODE_ASK:
      return(Ask_LibFunc());                                                //--- Return Ask price
   case MODE_POINT:
      return(SymbolInfoDouble(symbol, SYMBOL_POINT));                       //--- Return point size
   case MODE_DIGITS:
      return((double)SymbolInfoInteger(symbol, SYMBOL_DIGITS));             //--- Return digit count
   case MODE_SPREAD:
      return((double)SymbolInfoInteger(symbol, SYMBOL_SPREAD));             //--- Return spread
   case MODE_STOPLEVEL:
      return((double)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL));  //--- Return stop level
   case MODE_LOTSIZE:
      return(SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE));         //--- Return contract size
   case MODE_TICKVALUE:
      return(SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE));            //--- Return tick value
   case MODE_TICKSIZE:
      return(SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE));             //--- Return tick size
   case MODE_SWAPLONG:
      return(SymbolInfoDouble(symbol, SYMBOL_SWAP_LONG));                   //--- Return swap long
   case MODE_SWAPSHORT:
      return(SymbolInfoDouble(symbol, SYMBOL_SWAP_SHORT));                  //--- Return swap short
   case MODE_MINLOT:
      return(SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));                  //--- Return minimum lot
   case MODE_LOTSTEP:
      return(SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP));                 //--- Return lot step
   case MODE_MAXLOT:
      return(SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX));                  //--- Return maximum lot
   case MODE_SWAPTYPE:
      return((double)SymbolInfoInteger(symbol, SYMBOL_SWAP_MODE));          //--- Return swap mode
   case MODE_PROFITCALCMODE:
      return((double)SymbolInfoInteger(symbol, SYMBOL_TRADE_CALC_MODE));    //--- Return profit calc mode
   case MODE_FREEZELEVEL:
      return((double)SymbolInfoInteger(symbol, SYMBOL_TRADE_FREEZE_LEVEL)); //--- Return freeze level
   default:
      return(0);                                         //--- Return 0 for unknown type
   }
   return(0);                                            //--- Ensure fallback return
}

我们创建了一系列实用函数,以便为信号生成提供高效的数据获取支持。我们编写了 “CopyBufferOneValue” 函数,该函数接收指标句柄、缓冲区索引和偏移量作为输入,将指标缓冲区中的单个数值复制到 “buf” 数组中,并返回该数值;若操作失败,则返回 EMPTY_VALUE。该函数对于获取精确的指标数据(例如包络线轨道数值)至关重要。

接下来,我们定义了 “Ask_LibFunc” 和 “Bid_LibFunc” 函数,分别用于获取当前的卖价和买价。这两个函数利用 MqlTick 结构体和 SymbolInfoTick 函数来获取当前交易品种的最新报价数据。同时,我们实现了 “AccountEquity_LibFunc” 和 “AccountFreeMargin_LibFunc” 函数,通过 AccountInfoDouble 返回账户净值和可用保证金,从而支持风险管理计算。

最后,我们创建了 “MarketInfo_LibFunc” 函数。该函数通过 switch 语句配合 “MODE_” 常量(例如 “MODE_BID”、“MODE_ASK”)来获取各种交易品种属性,如点差、手数规模或隔夜利息,对于不支持的类型则返回 0。这些函数将为生成准确的交易信号奠定数据基础。现在,我们可以进一步定义包含主要逻辑的类和函数了。

//--- Define function interface
interface IFunction {
   double GetValue(int index);                            //--- Retrieve value at index
   void Evaluate();                                       //--- Execute function evaluation
   void Init();                                           //--- Initialize function
};

//--- Define base class for handling double values in a circular buffer
class DoubleFunction : public IFunction {
private:
   double _values[];                                      //--- Store array of historical values
   int _zeroIndex;                                        //--- Track current index in circular buffer

protected:
   int ValueCount;                                        //--- Define number of values to store

public:
   //--- Initialize the circular buffer
   void Init() {
      _zeroIndex = -1;                                    //--- Set initial index to -1
      ArrayResize(_values, ValueCount);                   //--- Resize array to hold ValueCount elements
      ArrayInitialize(_values, GetCurrentValue());        //--- Fill array with current value
   }

   //--- Update buffer with new value
   void Evaluate() {
      double currentValue = GetCurrentValue();            //--- Retrieve current value
      _zeroIndex = (_zeroIndex + 1) % ValueCount;         //--- Increment index, wrap around if needed
      _values[_zeroIndex] = currentValue;                 //--- Store new value at current index
   }

   //--- Retrieve value at specified index
   double GetValue(int requestIndex = 0) {
      int requiredIndex = (_zeroIndex + ValueCount - requestIndex) % ValueCount; //--- Calculate index for requested value
      return _values[requiredIndex];                      //--- Return value at calculated index
   }

   //--- Declare pure virtual method for getting current value
   virtual double GetCurrentValue() = 0;                  //--- Require derived classes to implement
};

//--- Define base class for Ask and Bid price functions
class AskBidFunction : public DoubleFunction {
public:
   //--- Initialize AskBidFunction
   void AskBidFunction() {
      ValueCount = 2;                                     //--- Set buffer to store 2 values
   }
};

//--- Define class for retrieving Ask price
class AskFunction : public AskBidFunction {
public:
   //--- Retrieve current Ask price
   double GetCurrentValue() {
      return Ask_LibFunc();                               //--- Return Ask price using utility function
   }
};

//--- Define class for retrieving Bid price
class BidFunction : public AskBidFunction {
public:
   //--- Retrieve current Bid price
   double GetCurrentValue() {
      return Bid_LibFunc();                               //--- Return Bid price using utility function
   }
};

//--- Declare global function pointers for Ask and Bid
IFunction *AskFunc;                                       //--- Point to Ask price function
IFunction *BidFunc;                                       //--- Point to Bid price function

//--- Retrieve order filling type for current symbol
uint GetFillingType() {
   uint fillingType = -1;                                 //--- Initialize filling type as invalid
   uint filling = (uint)SymbolInfoInteger(Symbol(), SYMBOL_FILLING_MODE); //--- Get symbol filling mode
   if ((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK) {
      fillingType = ORDER_FILLING_FOK;                    //--- Set Fill or Kill type
      Print("Filling type: FOK");                         //--- Log FOK filling type
   } else if ((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC) {
      fillingType = ORDER_FILLING_IOC;                    //--- Set Immediate or Cancel type
      Print("Filling type: IOC");                         //--- Log IOC filling type
   } else {
      fillingType = ORDER_FILLING_RETURN;                 //--- Set Return type as default
      Print("Filling type: RETURN");                      //--- Log Return filling type
   }
   return fillingType;                                    //--- Return determined filling type
}

//--- Retrieve trade execution mode for current symbol
uint GetExecutionType() {
   uint executionType = -1;                               //--- Initialize execution type as invalid
   uint execution = (uint)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_EXEMODE); //--- Get symbol execution mode
   if ((execution & SYMBOL_TRADE_EXECUTION_MARKET) == SYMBOL_TRADE_EXECUTION_MARKET) {
      executionType = SYMBOL_TRADE_EXECUTION_MARKET;      //--- Set Market execution mode
      Print("Deal execution mode: Market execution, deviation setting will be ignored."); //--- Log Market mode
   } else if ((execution & SYMBOL_TRADE_EXECUTION_INSTANT) == SYMBOL_TRADE_EXECUTION_INSTANT) {
      executionType = SYMBOL_TRADE_EXECUTION_INSTANT;     //--- Set Instant execution mode
      Print("Deal execution mode: Instant execution, deviation setting might be taken into account, depending on your broker."); //--- Log Instant mode
   } else if ((execution & SYMBOL_TRADE_EXECUTION_REQUEST) == SYMBOL_TRADE_EXECUTION_REQUEST) {
      executionType = SYMBOL_TRADE_EXECUTION_REQUEST;     //--- Set Request execution mode
      Print("Deal execution mode: Request execution, deviation setting might be taken into account, depending on your broker."); //--- Log Request mode
   } else if ((execution & SYMBOL_TRADE_EXECUTION_EXCHANGE) == SYMBOL_TRADE_EXECUTION_EXCHANGE) {
      executionType = SYMBOL_TRADE_EXECUTION_EXCHANGE;    //--- Set Exchange execution mode
      Print("Deal execution mode: Exchange execution, deviation setting will be ignored."); //--- Log Exchange mode
   }
   return executionType;                                  //--- Return determined execution type
}

//--- Retrieve account margin mode
uint GetAccountMarginMode() {
   uint marginMode = -1;                                  //--- Initialize margin mode as invalid
   marginMode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE); //--- Get account margin mode
   if (marginMode == ACCOUNT_MARGIN_MODE_RETAIL_NETTING) {
      Print("Account margin mode: Netting");              //--- Log Netting mode
   } else if (marginMode == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) {
      Print("Account margin mode: Hedging");              //--- Log Hedging mode
   } else if (marginMode == ACCOUNT_MARGIN_MODE_EXCHANGE) {
      Print("Account margin mode: Exchange");             //--- Log Exchange mode
   } else {
      Print("Unknown margin type");                       //--- Log unknown margin mode
   }
   return marginMode;                                     //--- Return determined margin mode
}

//--- Retrieve description for trade error code
string GetErrorDescription(int error_code) {
   string description = "";                               //--- Initialize empty description
   switch (error_code) {                                  //--- Match error code to description
   case 10004: description = "Requote"; break;            //--- Set Requote error
   case 10006: description = "Request rejected"; break;   //--- Set Request rejected error
   case 10007: description = "Request canceled by trader"; break; //--- Set Trader cancel error
   case 10008: description = "Order placed"; break;       //--- Set Order placed status
   case 10009: description = "Request completed"; break;  //--- Set Request completed status
   case 10010: description = "Only part of the request was completed"; break; //--- Set Partial completion error
   case 10011: description = "Request processing error"; break; //--- Set Processing error
   case 10012: description = "Request canceled by timeout"; break; //--- Set Timeout cancel error
   case 10013: description = "Invalid request"; break;    //--- Set Invalid request error
   case 10014: description = "Invalid volume in the request"; break; //--- Set Invalid volume error
   case 10015: description = "Invalid price in the request"; break; //--- Set Invalid price error
   case 10016: description = "Invalid stops in the request"; break; //--- Set Invalid stops error
   case 10017: description = "Trade is disabled"; break;  //--- Set Trade disabled error
   case 10018: description = "Market is closed"; break;   //--- Set Market closed error
   case 10019: description = "There is not enough money to complete the request"; break; //--- Set Insufficient funds error
   case 10020: description = "Prices changed"; break;     //--- Set Price change error
   case 10021: description = "There are no quotes to process the request"; break; //--- Set No quotes error
   case 10022: description = "Invalid order expiration date in the request"; break; //--- Set Invalid expiration error
   case 10023: description = "Order state changed"; break; //--- Set Order state change error
   case 10024: description = "Too frequent requests"; break; //--- Set Too frequent requests error
   case 10025: description = "No changes in request"; break; //--- Set No changes error
   case 10026: description = "Autotrading disabled by server"; break; //--- Set Server autotrading disabled error
   case 10027: description = "Autotrading disabled by client terminal"; break; //--- Set Client autotrading disabled error
   case 10028: description = "Request locked for processing"; break; //--- Set Request locked error
   case 10029: description = "Order or position frozen"; break; //--- Set Frozen order error
   case 10030: description = "Invalid order filling type"; break; //--- Set Invalid filling type error
   case 10031: description = "No connection with the trade server"; break; //--- Set No server connection error
   case 10032: description = "Operation is allowed only for live accounts"; break; //--- Set Live account only error
   case 10033: description = "The number of pending orders has reached the limit"; break; //--- Set Pending order limit error
   case 10034: description = "The volume of orders and positions for the symbol has reached the limit"; break; //--- Set Symbol volume limit error
   case 10035: description = "Incorrect or prohibited order type"; break; //--- Set Incorrect order type error
   case 10036: description = "Position with the specified POSITION_IDENTIFIER has already been closed"; break; //--- Set Position closed error
   case 10038: description = "A close volume exceeds the current position volume"; break; //--- Set Excessive close volume error
   case 10039: description = "A close order already exists for a specified position"; break; //--- Set Existing close order error
   case 10040: description = "The number of open positions simultaneously present on an account has reached the limit"; break; //--- Set Position limit error
   case 10041: description = "The pending order activation request is rejected, the order is canceled"; break; //--- Set Order activation rejected error
   case 10042: description = "The request is rejected, because the 'Only long positions are allowed' rule is set for the symbol"; break; //--- Set Long-only rule error
   case 10043: description = "The request is rejected, because the 'Only short positions are allowed' rule is set for the symbol"; break; //--- Set Short-only rule error
   case 10044: description = "The request is rejected, because the 'Only position closing is allowed' rule is set for the symbol"; break; //--- Set Close-only rule error
   case 10045: description = "The request is rejected, because 'Position closing is allowed only by FIFO rule' flag is set for the trading account"; break; //--- Set FIFO closing rule error
   case 10046: description = "The request is rejected, because the 'Opposite positions on a single symbol are disabled' rule is set for the trading account"; break; //--- Set Opposite positions disabled error
   default: description = "Unknown error code " + IntegerToString(error_code); break; //--- Set unknown error with code
   }
   return description;                                    //--- Return error description
}

//--- Set pip point value for current symbol
void SetPipPoint() {
   if (PipPointOverride != 0) {
      PipPoint = PipPointOverride;                        //--- Use manual override if specified
   } else {
      PipPoint = GetRealPipPoint(Symbol());               //--- Calculate pip point automatically
   }
   Print("Pip (forex)/ Point (indices): " + DoubleToStr(PipPoint, 5)); //--- Log calculated pip point
}

//--- Calculate real pip point based on symbol digits
double GetRealPipPoint(string Currency) {
   double calcPoint = 0;                                  //--- Initialize pip point value
   double calcDigits = Digits();                          //--- Get symbol's decimal digits
   Print("Number of digits after decimal point: " + DoubleToString(calcDigits)); //--- Log digit count
   if (calcDigits == 0) {
      calcPoint = 1;                                      //--- Set pip point to 1 for 0 digits
   } else if (calcDigits == 1) {
      calcPoint = 1;                                      //--- Set pip point to 1 for 1 digit
   } else if (calcDigits == 2) {
      calcPoint = 0.1;                                    //--- Set pip point to 0.1 for 2 digits
   } else if (calcDigits == 3) {
      calcPoint = 0.01;                                   //--- Set pip point to 0.01 for 3 digits
   } else if (calcDigits == 4 || calcDigits == 5) {
      calcPoint = 0.0001;                                 //--- Set pip point to 0.0001 for 4 or 5 digits
   }
   return calcPoint;                                      //--- Return calculated pip point
}

//--- Calculate required margin for an order
bool MarginRequired(ENUM_ORDER_TYPE type, double volume, double &marginRequired) {
   double price;                                          //--- Declare price variable
   if (type == ORDER_TYPE_BUY) {
      price = Ask_LibFunc();                              //--- Set price to Ask for Buy orders
   } else if (type == ORDER_TYPE_SELL) {
      price = Bid_LibFunc();                              //--- Set price to Bid for Sell orders
   } else {
      string message = "MarginRequired: Unsupported ENUM_ORDER_TYPE"; //--- Prepare error message
      HandleErrors(message);                              //--- Log unsupported order type error
      price = Ask_LibFunc();                              //--- Default to Ask price
   }
   if (!OrderCalcMargin(type, _Symbol, volume, price, marginRequired)) {
      HandleErrors(StringFormat("Couldn't calculate required margin, error: %d", GetLastError())); //--- Log margin calculation error
      return false;                                       //--- Return false on failure
   }
   return true;                                           //--- Return true on success
}

//--- Create horizontal line on chart for TP/SL visualization
bool HLineCreate(const long chart_ID = 0, const string name = "HLine", const int sub_window = 0,
                 double price = 0, const color clr = clrRed, const ENUM_LINE_STYLE style = STYLE_SOLID,
                 const int width = 1, const bool back = false, const bool selection = true,
                 const bool hidden = true, const long z_order = 0) {
   uint lineFindResult = ObjectFind(chart_ID, name);      //--- Check if line already exists
   if (lineFindResult != UINT_MAX) {
      Print("HLineCreate object already exists: " + name); //--- Log existing line error
      return false;                                       //--- Return false if line exists
   }
   if (!price) {
      price = Bid_LibFunc();                              //--- Default to Bid price if not specified
   }
   ResetLastError();                                      //--- Clear last error
   if (!ObjectCreate(chart_ID, name, OBJ_HLINE, sub_window, 0, price)) {
      Print(__FUNCTION__, ": failed to create a horizontal line! Error code = ", GetLastError()); //--- Log line creation error
      return false;                                       //--- Return false on failure
   }
   ObjectSetInteger(chart_ID, name, OBJPROP_COLOR, clr);  //--- Set line color
   ObjectSetInteger(chart_ID, name, OBJPROP_STYLE, style); //--- Set line style
   ObjectSetInteger(chart_ID, name, OBJPROP_WIDTH, width); //--- Set line width
   ObjectSetInteger(chart_ID, name, OBJPROP_BACK, back);   //--- Set background rendering
   if (AllowManualTPSLChanges) {
      ObjectSetInteger(chart_ID, name, OBJPROP_SELECTABLE, selection); //--- Enable line selection
      ObjectSetInteger(chart_ID, name, OBJPROP_SELECTED, selection);   //--- Set line as selected
   }
   ObjectSetInteger(chart_ID, name, OBJPROP_HIDDEN, hidden); //--- Hide line in object list
   ObjectSetInteger(chart_ID, name, OBJPROP_ZORDER, z_order); //--- Set mouse click priority
   return true;                                           //--- Return true on success
}

//--- Move existing horizontal line on chart
bool HLineMove(const long chart_ID = 0, const string name = "HLine", double price = 0) {
   uint lineFindResult = ObjectFind(ChartID(), name);     //--- Check if line exists
   if (lineFindResult == UINT_MAX) {
      Print("HLineMove didn't find object: " + name);      //--- Log missing line error
      return false;                                       //--- Return false if line not found
   }
   if (!price) {
      price = SymbolInfoDouble(Symbol(), SYMBOL_BID);      //--- Default to Bid price if not specified
   }
   ResetLastError();                                      //--- Clear last error
   if (!ObjectMove(chart_ID, name, 0, 0, price)) {
      Print(__FUNCTION__, ": failed to move the horizontal line! Error code = ", GetLastError()); //--- Log line move error
      return false;                                       //--- Return false on failure
   }
   return true;                                           //--- Return true on success
}

//--- Delete chart object by name
bool AnyChartObjectDelete(const long chart_ID = 0, const string name = "") {
   uint lineFindResult = ObjectFind(ChartID(), name);     //--- Check if object exists
   if (lineFindResult == UINT_MAX) {
      return false;                                       //--- Return false if object not found
   }
   ResetLastError();                                      //--- Clear last error
   if (!ObjectDelete(chart_ID, name)) {
      Print(__FUNCTION__, ": failed to delete a horizontal line! Error code = ", GetLastError()); //--- Log deletion error
      return false;                                       //--- Return false on failure
   }
   return true;                                           //--- Return true on success
}

//--- Placeholder function for future use
int Dummy(string message) {
   return 0;                                              //--- Return 0 (no operation)
}

在此,通过定义接口、类和实用函数继续开发我们的程序,以管理价格数据和图表视觉元素。我们创建了 “IFunction” 接口,其中指定了 “GetValue”、“Evaluate” 和 “Init” 方法,旨在为价格函数的数据获取建立标准规范。接着,我们定义了继承自 “IFunction” 的 “DoubleFunction” 类,用于管理一个包含 “_values” 数组和 “_zeroIndex” 变量的循环缓冲区。该类实现了 “Init” 方法来初始化缓冲区,“Evaluate” 方法来更新数值,以及 “GetValue” 方法来获取历史数据,并为子类保留了纯虚函数 “GetCurrentValue”。

我们从 “DoubleFunction” 类派生出 “AskBidFunction” 类,将 “ValueCount” 设为 2 以缓冲卖价/买价,并创建了 “AskFunction” 和 “BidFunction” 类,分别在其 “GetCurrentValue” 方法中通过 “Ask_LibFunc” 和 “Bid_LibFunc” 返回当前价格。此外,我们声明了全局指针 “AskFunc” 和 “BidFunc” 以便访问这些函数。我们还实现了 “GetFillingType”、“GetExecutionType” 和 “GetAccountMarginMode” 函数,用于确定经纪商特定的设置,并记录诸如 ORDER_FILLING_FOK 或 “ACCOUNT_MARGIN_MODE_RETAIL_HEDGING” 等模式。“GetErrorDescription” 函数负责将错误代码映射为可读字符串,有助于调试工作。

此外,我们定义了 “SetPipPoint” 和 “GetRealPipPoint” 函数,用于根据交易品种的小数位数计算 “PipPoint” 变量;定义了 “MarginRequired” 函数,利用 “Ask_LibFunc” 或 “Bid_LibFunc” 计算保证金需求;还定义了 “HLineCreate”、“HLineMove” 和 “AnyChartObjectDelete” 函数,用于管理止盈/止损可视化的图表线条,其中 “Dummy” 作为占位符留作后用。现在,我们可以着手声明将要使用的指标,因此需要定义一些额外的输入参数以实现动态控制,如下所示。

//--- Input parameters for indicators
input int iMA_SMA8_ma_period = 14;                        //--- Set SMA period for trend filter (M30 timeframe)
input int iMA_SMA8_ma_shift = 2;                          //--- Set SMA shift for trend filter
input int iMA_SMA_4_ma_period = 9;                        //--- Set SMA period for reverse trend filter (M30 timeframe)
input int iMA_SMA_4_ma_shift = 0;                         //--- Set SMA shift for reverse trend filter
input int iMA_EMA200_ma_period = 200;                     //--- Set EMA period for long-term trend (M1 timeframe)
input int iMA_EMA200_ma_shift = 0;                        //--- Set EMA shift for long-term trend
input int iRSI_RSI_ma_period = 8;                         //--- Set RSI period for overbought/oversold signals (M1 timeframe)
input int iEnvelopes_ENV_LOW_ma_period = 95;              //--- Set Envelopes period for lower band (M1 timeframe)
input int iEnvelopes_ENV_LOW_ma_shift = 0;                //--- Set Envelopes shift for lower band
input double iEnvelopes_ENV_LOW_deviation = 1.4;          //--- Set Envelopes deviation for lower band (1.4%)
input int iEnvelopes_ENV_UPPER_ma_period = 150;           //--- Set Envelopes period for upper band (M1 timeframe)
input int iEnvelopes_ENV_UPPER_ma_shift = 0;              //--- Set Envelopes shift for upper band
input double iEnvelopes_ENV_UPPER_deviation = 0.1;        //--- Set Envelopes deviation for upper band (0.1%)

//--- Indicator handle declarations
int hd_iMA_SMA8;                                          //--- Store handle for 8-period SMA
//--- Retrieve 8-period SMA value
double fn_iMA_SMA8(string symbol, int shift) {
   int index = 0;                                         //--- Set buffer index to 0
   return CopyBufferOneValue(hd_iMA_SMA8, index, shift);  //--- Return SMA value at specified shift
}

int hd_iMA_EMA200;                                        //--- Store handle for 200-period EMA
//--- Retrieve 200-period EMA value
double fn_iMA_EMA200(string symbol, int shift) {
   int index = 0;                                         //--- Set buffer index to 0
   return CopyBufferOneValue(hd_iMA_EMA200, index, shift); //--- Return EMA value at specified shift
}

int hd_iRSI_RSI;                                          //--- Store handle for 8-period RSI
//--- Retrieve RSI value
double fn_iRSI_RSI(string symbol, int shift) {
   int index = 0;                                         //--- Set buffer index to 0
   return CopyBufferOneValue(hd_iRSI_RSI, index, shift);  //--- Return RSI value at specified shift
}

int hd_iEnvelopes_ENV_LOW;                                //--- Store handle for lower Envelopes band
//--- Retrieve lower Envelopes band value
double fn_iEnvelopes_ENV_LOW(string symbol, int mode, int shift) {
   int index = mode;                                      //--- Set buffer index to specified mode
   return CopyBufferOneValue(hd_iEnvelopes_ENV_LOW, index, shift); //--- Return lower Envelopes value
}

int hd_iEnvelopes_ENV_UPPER;                              //--- Store handle for upper Envelopes band
//--- Retrieve upper Envelopes band value
double fn_iEnvelopes_ENV_UPPER(string symbol, int mode, int shift) {
   int index = mode;                                      //--- Set buffer index to specified mode
   return CopyBufferOneValue(hd_iEnvelopes_ENV_UPPER, index, shift); //--- Return upper Envelopes value
}

int hd_iMA_SMA_4;                                         //--- Store handle for 4-period SMA
//--- Retrieve 4-period SMA value
double fn_iMA_SMA_4(string symbol, int shift) {
   int index = 0;                                         //--- Set buffer index to 0
   return CopyBufferOneValue(hd_iMA_SMA_4, index, shift); //--- Return SMA value at specified shift
}

为了配置指标参数,我们使用 “input” 指令声明外部用户输入参数,随后调用 “CopyBufferOneValue” 函数来获取指定的指标数值。接着,我们可以创建一个用于订单管理的类,以此构建核心架构的骨架。

//--- Commission variables
double CommissionAmountPerTrade = 0.0;                    //--- Set fixed commission per trade (default: 0)
double CommissionPercentagePerLot = 0.0;                  //--- Set commission percentage per lot (default: 0)
double CommissionAmountPerLot = 0.0;                      //--- Set fixed commission per lot (default: 0)
double TotalCommission = 0.0;                             //--- Track total commission for all trades
bool UseCommissionInProfitInPips = false;                 //--- Exclude commission from pip profit if false

//--- Define class for order close information
class OrderCloseInfo {
public:
   string ModuleCode;                                     //--- Store module identifier for close condition
   double Price;                                          //--- Store price for TP or SL
   int Percentage;                                        //--- Store percentage of order to close
   bool IsOld;                                            //--- Flag outdated close info

   //--- Default constructor
   void OrderCloseInfo() {}                               //--- Initialize empty close info

   //--- Copy constructor
   void OrderCloseInfo(OrderCloseInfo* ordercloseinfo) {
      ModuleCode = ordercloseinfo.ModuleCode;             //--- Copy module code
      Price = ordercloseinfo.Price;                       //--- Copy price
      Percentage = ordercloseinfo.Percentage;             //--- Copy percentage
      IsOld = ordercloseinfo.IsOld;                       //--- Copy old flag
   }

   //--- Check if Stop Loss is hit
   bool IsClosePriceSLHit(ENUM_ORDER_TYPE type, double ask, double bid) {
      switch (type) {
      case ORDER_TYPE_BUY:
         return bid <= Price;                             //--- Return true if Bid falls below SL for Buy
      case ORDER_TYPE_SELL:
         return ask >= Price;                             //--- Return true if Ask rises above SL for Sell
      }
      return false;                                       //--- Return false for invalid type
   }

   //--- Check if Take Profit is hit
   bool IsClosePriceTPHit(ENUM_ORDER_TYPE type, double ask, double bid) {
      switch (type) {
      case ORDER_TYPE_BUY:
         return bid >= Price;                             //--- Return true if Bid reaches TP for Buy
      case ORDER_TYPE_SELL:
         return ask <= Price;                             //--- Return true if Ask reaches TP for Sell
      }
      return false;                                       //--- Return false for invalid type
   }

   //--- Destructor
   void ~OrderCloseInfo() {}                              //--- Clean up close info
};

//--- Define class for managing order details
class Order {
public:
   ulong Ticket;                                          //--- Store unique order ticket
   ENUM_ORDER_TYPE Type;                                  //--- Store order type (Buy/Sell)
   ENUM_ORDER_STATE State;                                //--- Store order state (e.g., Filled)
   long MagicNumber;                                      //--- Store EA’s magic number
   double Lots;                                           //--- Store order volume in lots
   double OrderFilledLots;                                //--- Store filled volume
   datetime OpenTime;                                     //--- Store order open time
   double OpenPrice;                                      //--- Store order open price
   datetime CloseTime;                                    //--- Store order close time
   double ClosePrice;                                     //--- Store order close price
   double StopLoss;                                       //--- Store Stop Loss price
   double StopLossManual;                                 //--- Store manually set Stop Loss
   double TakeProfit;                                     //--- Store Take Profit price
   double TakeProfitManual;                               //--- Store manually set Take Profit
   datetime Expiration;                                   //--- Store order expiration time
   double CurrentProfitPips;                              //--- Store current profit in pips
   double HighestProfitPips;                              //--- Store highest profit in pips
   double LowestProfitPips;                               //--- Store lowest profit in pips
   string Comment;                                        //--- Store order comment
   uint TradeRetCode;                                     //--- Store trade result code
   ulong TradeDealTicket;                                 //--- Store deal ticket
   double TradePrice;                                     //--- Store trade price
   double TradeVolume;                                    //--- Store trade volume
   double Commission;                                     //--- Store commission cost
   double CommissionInPips;                               //--- Store commission in pips
   string SymbolCode;                                     //--- Store symbol code
   bool IsAwaitingDealExecution;                          //--- Flag pending deal execution
   OrderCloseInfo* CloseInfosTP[];                        //--- Store Take Profit close info
   OrderCloseInfo* CloseInfosSL[];                        //--- Store Stop Loss close info
   Order* ParentOrder;                                    //--- Store parent order for splits
   bool MustBeVisibleOnChart;                             //--- Flag chart visibility

   //--- Initialize order with visibility flag
   void Order(bool mustBeVisibleOnChart) {
      OrderFilledLots = 0.0;                              //--- Set filled lots to 0
      OpenPrice = 0.0;                                    //--- Set open price to 0
      ClosePrice = 0.0;                                   //--- Set close price to 0
      Commission = 0.0;                                   //--- Set commission to 0
      CommissionInPips = 0.0;                             //--- Set commission in pips to 0
      MustBeVisibleOnChart = mustBeVisibleOnChart;        //--- Set chart visibility flag
   }

   //--- Copy order details with visibility flag
   void Order(Order* order, bool mustBeVisibleOnChart) {
      Ticket = order.Ticket;                              //--- Copy ticket
      Type = order.Type;                                  //--- Copy order type
      State = order.State;                                //--- Copy order state
      MagicNumber = order.MagicNumber;                    //--- Copy magic number
      Lots = order.Lots;                                  //--- Copy lots
      OpenTime = order.OpenTime;                          //--- Copy open time
      OpenPrice = order.OpenPrice;                        //--- Copy open price
      CloseTime = order.CloseTime;                        //--- Copy close time
      ClosePrice = order.ClosePrice;                      //--- Copy close price
      StopLoss = order.StopLoss;                          //--- Copy Stop Loss
      StopLossManual = order.StopLossManual;              //--- Copy manual Stop Loss
      TakeProfit = order.TakeProfit;                      //--- Copy Take Profit
      TakeProfitManual = order.TakeProfitManual;          //--- Copy manual Take Profit
      Expiration = order.Expiration;                      //--- Copy expiration
      CurrentProfitPips = order.CurrentProfitPips;        //--- Copy current profit
      HighestProfitPips = order.HighestProfitPips;        //--- Copy highest profit
      LowestProfitPips = order.LowestProfitPips;          //--- Copy lowest profit
      Comment = order.Comment;                            //--- Copy comment
      TradeRetCode = order.TradeRetCode;                  //--- Copy trade result code
      TradeDealTicket = order.TradeDealTicket;            //--- Copy deal ticket
      TradePrice = order.TradePrice;                      //--- Copy trade price
      TradeVolume = order.TradeVolume;                    //--- Copy trade volume
      Commission = order.Commission;                      //--- Copy commission
      CommissionInPips = order.CommissionInPips;          //--- Copy commission in pips
      SymbolCode = order.SymbolCode;                      //--- Copy symbol code
      IsAwaitingDealExecution = order.IsAwaitingDealExecution; //--- Copy execution flag
      ParentOrder = order.ParentOrder;                    //--- Copy parent order
      MustBeVisibleOnChart = mustBeVisibleOnChart;        //--- Set visibility flag
   }

   //--- Split order into partial close
   Order* SplitOrder(int percentageToSplitOff) {
      Order* splittedOffPieceOfOrder = new Order(&this, true); //--- Create new order for split
      splittedOffPieceOfOrder.Lots = CalcVolumePartialClose(this.Lots, percentageToSplitOff); //--- Calculate split volume
      if (this.Lots - splittedOffPieceOfOrder.Lots < 1e-13) {
         splittedOffPieceOfOrder.MustBeVisibleOnChart = false; //--- Hide split if no volume remains
         splittedOffPieceOfOrder.Lots = 0;                   //--- Set split volume to 0
      } else {
         this.Lots = this.Lots - splittedOffPieceOfOrder.Lots; //--- Reduce original order volume
      }
      return splittedOffPieceOfOrder;                        //--- Return split order
   }

   //--- Calculate profit in pipettes
   double CalculateProfitPipettes() {
      double closePrice = GetClosePrice();                   //--- Get current close price
      switch (Type) {
      case ORDER_TYPE_BUY:
         return (closePrice - OpenPrice);                    //--- Return Buy profit in pipettes
      case ORDER_TYPE_SELL:
         return (OpenPrice - closePrice);                    //--- Return Sell profit in pipettes
      }
      return 0;                                              //--- Return 0 for invalid type
   }

   //--- Calculate profit in pips
   double CalculateProfitPips() {
      double pipettes = CalculateProfitPipettes();           //--- Get profit in pipettes
      double pips = pipettes / PipPoint;                     //--- Convert to pips
      if (UseCommissionInProfitInPips) {
         return pips - CommissionInPips;                     //--- Subtract commission if enabled
      }
      return pips;                                           //--- Return profit in pips
   }

   //--- Calculate profit in account currency
   double CalculateProfitCurrency() {
      double closePrice = GetClosePrice();                   //--- Get current close price
      switch (Type) {
      case OP_BUY:
         return (closePrice - OpenPrice) * (UnitsOneLot * TradeVolume) - Commission; //--- Return Buy profit
      case OP_SELL:
         return (OpenPrice - closePrice) * (UnitsOneLot * TradeVolume) - Commission; //--- Return Sell profit
      }
      return 0;                                              //--- Return 0 for invalid type
   }

   //--- Calculate profit as equity percentage
   double CalculateProfitEquityPercentage() {
      double closePrice = GetClosePrice();                   //--- Get current close price
      switch (Type) {
      case OP_BUY:
         return 100 * ((closePrice - OpenPrice) * (UnitsOneLot * TradeVolume) - Commission) / AccountEquity_LibFunc(); //--- Return Buy equity percentage
      case OP_SELL:
         return 100 * ((OpenPrice - closePrice) * (UnitsOneLot * TradeVolume) - Commission) / AccountEquity_LibFunc(); //--- Return Sell equity percentage
      }
      return 0;                                              //--- Return 0 for invalid type
   }

   //--- Calculate price difference in pips
   double CalculateValueDifferencePips(double value) {
      double divOpenPrice = 0.0;                             //--- Initialize price difference
      switch (Type) {
      case OP_BUY:
         divOpenPrice = (value - OpenPrice);                 //--- Calculate Buy difference
         break;
      case OP_SELL:
         divOpenPrice = (OpenPrice - value);                 //--- Calculate Sell difference
         break;
      }
      double pipsDivOpenPrice = divOpenPrice / PipPoint;     //--- Convert to pips
      return pipsDivOpenPrice;                               //--- Return difference in pips
   }

   //--- Retrieve realized profit in pips
   double GetProfitPips() {
      if (CloseTime > 0) {                                   //--- Check if order is closed
         switch (Type) {
         case ORDER_TYPE_BUY: {
            double pipettes = ClosePrice - OpenPrice;        //--- Calculate Buy pipettes
            return pipettes / PipPoint;                      //--- Return Buy profit in pips
         }
         case ORDER_TYPE_SELL: {
            double pipettes = OpenPrice - ClosePrice;        //--- Calculate Sell pipettes
            return pipettes / PipPoint;                      //--- Return Sell profit in pips
         }
         }
      }
      return 0;                                              //--- Return 0 if not closed
   }

   //--- Check if module has processed close info
   bool IsAlreadyProcessedByModule(string moduleCode, OrderCloseInfo* &closeInfos[]) {
      for (int i = 0; i < ArraySize(closeInfos); i++) {
         if (closeInfos[i].ModuleCode == moduleCode && closeInfos[i].IsOld) {
            return true;                                     //--- Return true if processed and old
         }
      }
      return false;                                          //--- Return false if not processed
   }

   //--- Check if module has active close info
   bool HasAValueAlreadyByModule(string moduleCode, OrderCloseInfo* &closeInfos[]) {
      for (int i = 0; i < ArraySize(closeInfos); i++) {
         if (closeInfos[i].ModuleCode == moduleCode && !closeInfos[i].IsOld) {
            return true;                                     //--- Return true if active
         }
      }
      return false;                                          //--- Return false if no active info
   }

   //--- Draw TP and SL lines on chart
   void Paint() {
      if (IsDemoLiveOrVisualMode) {                          //--- Check for demo/live/visual mode
         for (int i = 0; i < ArraySize(CloseInfosTP); i++) {
            if (CloseInfosTP[i].IsOld) continue;             //--- Skip outdated TP info
            PaintTPInfo(CloseInfosTP[i].Price);              //--- Draw TP line
         }
         for (int i = 0; i < ArraySize(CloseInfosSL); i++) {
            if (CloseInfosSL[i].IsOld) continue;             //--- Skip outdated SL info
            PaintSLInfo(CloseInfosSL[i].Price);              //--- Draw SL line
         }
      }
   }

   //--- Set Take Profit information
   bool SetTPInfo(string moduleCode, double price, int percentage) {
      uint result = SetCloseInfo(CloseInfosTP, moduleCode, price, percentage); //--- Update TP info
      if (result != NoAction) {
         if (IsDemoLiveOrVisualMode) {
            PaintTPInfo(price);                              //--- Draw TP line
         }
         return true;                                        //--- Return true on success
      }
      return false;                                          //--- Return false on no action
   }

   //--- Set Stop Loss information
   bool SetSLInfo(string moduleCode, double price, int percentage) {
      uint result = SetCloseInfo(CloseInfosSL, moduleCode, price, percentage); //--- Update SL info
      if (result != NoAction) {
         if (IsDemoLiveOrVisualMode) {
            PaintSLInfo(price);                              //--- Draw SL line
         }
         return true;                                        //--- Return true on success
      }
      return false;                                          //--- Return false on no action
   }

   //--- Retrieve closest Stop Loss price
   double GetClosestSL() {
      double closestSL = 0;                                  //--- Initialize closest SL
      for (int cli = 0; cli < ArraySize(CloseInfosSL); cli++) {
         if (CloseInfosSL[cli].IsOld) continue;              //--- Skip outdated SL
         if ((Type == ORDER_TYPE_BUY && (closestSL == 0 || CloseInfosSL[cli].Price > closestSL)) ||
             (Type == ORDER_TYPE_SELL && (closestSL == 0 || CloseInfosSL[cli].Price < closestSL))) {
            closestSL = CloseInfosSL[cli].Price;             //--- Update closest SL
         }
      }
      return closestSL;                                      //--- Return closest SL price
   }

   //--- Retrieve closest Take Profit price
   double GetClosestTP() {
      double closestTP = 0;                                  //--- Initialize closest TP
      for (int cli = 0; cli < ArraySize(CloseInfosTP); cli++) {
         if (CloseInfosTP[cli].IsOld) continue;              //--- Skip outdated TP
         if ((Type == ORDER_TYPE_BUY && (closestTP == 0 || CloseInfosTP[cli].Price < closestTP)) ||
             (Type == ORDER_TYPE_SELL && (closestTP == 0 || CloseInfosTP[cli].Price > closestTP))) {
            closestTP = CloseInfosTP[cli].Price;             //--- Update closest TP
         }
      }
      return closestTP;                                      //--- Return closest TP price
   }

   //--- Remove Stop Loss information
   bool RemoveSLInfo(string moduleCode) {
      RemoveCloseInfo(CloseInfosSL, moduleCode);             //--- Remove SL info
      if (IsDemoLiveOrVisualMode) {
         double newValue = NULL;                             //--- Initialize new SL value
         for (int i = 0; i < ArraySize(CloseInfosSL); i++) {
            if ((Type == OP_BUY && (newValue == NULL || CloseInfosSL[i].Price > newValue)) ||
                (Type == OP_SELL && (newValue == NULL || CloseInfosSL[i].Price < newValue))) {
               newValue = CloseInfosSL[i].Price;             //--- Update new SL value
            }
         }
         if (newValue == NULL) {
            AnyChartObjectDelete(ChartID(), IntegerToString(Ticket) + "_SL"); //--- Delete SL line
         } else {
            HLineMove(ChartID(), IntegerToString(Ticket) + "_SL", newValue); //--- Move SL line
         }
      }
      return true;                                           //--- Return true on success
   }

   //--- Remove Take Profit information
   bool RemoveTPInfo(string moduleCode) {
      RemoveCloseInfo(CloseInfosTP, moduleCode);             //--- Remove TP info
      if (IsDemoLiveOrVisualMode) {
         double newValue = NULL;                             //--- Initialize new TP value
         for (int i = 0; i < ArraySize(CloseInfosTP); i++) {
            if ((Type == OP_BUY && (newValue == NULL || CloseInfosTP[i].Price < newValue)) ||
                (Type == OP_SELL && (newValue == NULL || CloseInfosTP[i].Price > newValue))) {
               newValue = CloseInfosTP[i].Price;             //--- Update new TP value
            }
         }
         if (newValue == NULL) {
            AnyChartObjectDelete(ChartID(), IntegerToString(Ticket) + "_TP"); //--- Delete TP line
         } else {
            HLineMove(ChartID(), IntegerToString(Ticket) + "_TP", newValue); //--- Move TP line
         }
      }
      return true;                                           //--- Return true on success
   }

   //--- Set or update close info (TP or SL)
   CRUD SetCloseInfo(OrderCloseInfo* &closeInfos[], string moduleCode, double price, int percentage) {
      for (int i = 0; i < ArraySize(closeInfos); i++) {
         if (closeInfos[i].ModuleCode == moduleCode) {
            closeInfos[i].Price = price;                     //--- Update existing price
            return Updated;                                  //--- Return Updated status
         }
      }
      int newSize = ArraySize(closeInfos) + 1;              //--- Calculate new array size
      ArrayResize(closeInfos, newSize);                     //--- Resize close info array
      closeInfos[newSize-1] = new OrderCloseInfo();         //--- Create new close info
      closeInfos[newSize-1].Price = price;                  //--- Set price
      closeInfos[newSize-1].Percentage = percentage;        //--- Set percentage
      closeInfos[newSize-1].ModuleCode = moduleCode;        //--- Set module code
      return Created;                                       //--- Return Created status
   }

   //--- Remove close info (TP or SL)
   CRUD RemoveCloseInfo(OrderCloseInfo* &closeInfos[], string moduleCode) {
      int removedCount = 0;                                  //--- Track removed items
      int arraySize = ArraySize(closeInfos);                 //--- Get current array size
      for (int i = 0; i < arraySize; i++) {
         if (closeInfos[i].ModuleCode == moduleCode) {
            removedCount++;                                  //--- Increment removed count
            if (closeInfos[i] != NULL && CheckPointer(closeInfos[i]) == POINTER_DYNAMIC) {
               delete(closeInfos[i]);                        //--- Delete dynamic close info
            }
            continue;                                        //--- Skip to next item
         }
         closeInfos[i - removedCount] = closeInfos[i];       //--- Shift remaining items
      }
      ArrayResize(closeInfos, arraySize - removedCount);     //--- Resize array
      return Deleted;                                       //--- Return Deleted status
   }

   //--- Destructor for order cleanup
   void ~Order() {
      for (int i = 0; i < ArraySize(CloseInfosTP); i++) {
         if (CloseInfosTP[i] != NULL && CheckPointer(CloseInfosTP[i]) == POINTER_DYNAMIC) {
            delete(CloseInfosTP[i]);                         //--- Delete dynamic TP info
         }
      }
      for (int i = 0; i < ArraySize(CloseInfosSL); i++) {
         if (CloseInfosSL[i] != NULL && CheckPointer(CloseInfosSL[i]) == POINTER_DYNAMIC) {
            delete(CloseInfosSL[i]);                         //--- Delete dynamic SL info
         }
      }
      if (IsDemoLiveOrVisualMode && MustBeVisibleOnChart) {
         AnyChartObjectDelete(ChartID(), IntegerToString(Ticket) + "_TP"); //--- Delete TP line
         AnyChartObjectDelete(ChartID(), IntegerToString(Ticket) + "_SL"); //--- Delete SL line
      }
   }

private:
   //--- Retrieve close price for profit calculation
   double GetClosePrice() {
      if (ClosePrice > 1e-5) {
         return ClosePrice;                                  //--- Return stored close price if set
      } else if (Type == OP_BUY) {
         return SymbolInfoDouble(SymbolCode, SYMBOL_BID);    //--- Return Bid for Buy orders
      }
      return SymbolInfoDouble(SymbolCode, SYMBOL_ASK);       //--- Return Ask for Sell orders
   }

   //--- Calculate volume for partial close
   double CalcVolumePartialClose(double orderVolume, int percentage) {
      return RoundVolume(orderVolume * ((double)percentage / 100)); //--- Return rounded volume
   }

   //--- Round volume to broker specifications
   double RoundVolume(double volume) {
      string pair = Symbol();                                //--- Get current symbol
      double lotStep = MarketInfo_LibFunc(pair, MODE_LOTSTEP); //--- Get lot step
      double minLot = MarketInfo_LibFunc(pair, MODE_MINLOT); //--- Get minimum lot
      volume = MathRound(volume / lotStep) * lotStep;        //--- Round volume to lot step
      if (volume < minLot) volume = minLot;                  //--- Enforce minimum lot
      return volume;                                         //--- Return rounded volume
   }

   //--- Draw Stop Loss line on chart
   void PaintSLInfo(double value) {
      double currentValue;                                   //--- Declare current value
      if (ObjectGetDouble(ChartID(), IntegerToString(Ticket) + "_SL", OBJPROP_PRICE, 0, currentValue)) {
         if (Type == OP_BUY && value > currentValue) {
            HLineMove(ChartID(), IntegerToString(Ticket) + "_SL", value); //--- Move SL line for Buy
         } else if (Type == OP_SELL && value < currentValue) {
            HLineMove(ChartID(), IntegerToString(Ticket) + "_SL", value); //--- Move SL line for Sell
         }
      } else {
         HLineCreate(ChartID(), IntegerToString(Ticket) + "_SL", 0, value, clrRed); //--- Create red SL line
      }
   }

   //--- Draw Take Profit line on chart
   void PaintTPInfo(double value) {
      double currentValue;                                   //--- Declare current value
      if (ObjectGetDouble(ChartID(), IntegerToString(Ticket) + "_TP", OBJPROP_PRICE, 0, currentValue)) {
         if (Type == OP_BUY && value < currentValue) {
            HLineMove(ChartID(), IntegerToString(Ticket) + "_TP", value); //--- Move TP line for Buy
         } else if (Type == OP_SELL && value > currentValue) {
            HLineMove(ChartID(), IntegerToString(Ticket) + "_TP", value); //--- Move TP line for Sell
         }
      } else {
         HLineCreate(ChartID(), IntegerToString(Ticket) + "_TP", 0, value, clrGreen); //--- Create green TP line
      }
   }
};

为了建立佣金处理和订单管理的核心逻辑,我们定义了佣金变量,如 “CommissionAmountPerTrade” (0.0)、“CommissionPercentagePerLot” (0.0)、“CommissionAmountPerLot” (0.0) 以及 “TotalCommission” (0.0),用于跟踪交易成本;同时设置 “UseCommissionInProfitInPips” (false) 以便在点数计算中剔除佣金,确保盈利计算的准确性。

我们创建了 ‘OrderCloseInfo’ 类来管理平仓细节,其中变量 ‘ModuleCode’ 用于模块标识,‘Price’ 指定了止盈/止损价位,‘Percentage’ 确定了部分平仓比例,而 ‘IsOld’ 则用于标记过期数据。该类的方法包括 ‘IsClosePriceSLHit’ 和 ‘IsClosePriceTPHit’,它们利用卖价和买价来分别检查 ORDER_TYPE_BUY 或 ‘ORDER_TYPE_SELL’ 订单是否触及了止损或止盈价位。

随后,我们定义了 ‘Order’ 类来封装订单详情。该类包含了 ‘Ticket’、‘Type’ (ENUM_ORDER_TYPE)、‘State’ (ENUM_ORDER_STATE)、‘MagicNumber’、‘Lots’、‘OpenPrice’、‘StopLoss’、‘TakeProfit’ 以及 ‘Commission’ 等关键变量。关键方法包括:‘Order’ 构造函数(用于初始化)、‘SplitOrder’(处理部分平仓)、‘CalculateProfitPips’ 和 ‘CalculateProfitCurrency’(利用 ‘PipPoint’ 和 ‘UnitsOneLot’ 计算盈利)、以及 ‘SetTPInfo’ 和 ‘SetSLInfo’(用于更新 ‘CloseInfosTP’ 和 ‘CloseInfosSL’ 数组)。

“Paint” 方法通过调用 “PaintTPInfo” 和 “PaintSLInfo” 绘制止盈/止损线,而 “GetClosestSL” 和 “GetClosestTP” 则用于获取最近的止损和止盈价格。“SetCloseInfo” 和 “RemoveCloseInfo” 方法返回 “CRUD” 状态,负责管理止盈/止损的更新;私有方法如 “GetClosePrice” 和 “RoundVolume” 则确保了价格和成交量的处理精度。这些结构将为剥头皮信号提供稳健的订单管理支持。接下来,我们将定义一个函数,用于收集并对订单进行分组,如下所示。

//--- Define class for managing a collection of orders
class OrderCollection {
private:
   Order* _orders[];                                      //--- Store array of order pointers
   int _pointer;                                          //--- Track current iteration index
   int _size;                                             //--- Track number of orders

public:
   //--- Initialize empty order collection
   void OrderCollection() {
      _pointer = -1;                                      //--- Set initial pointer to -1
      _size = 0;                                          //--- Set initial size to 0
   }

   //--- Destructor to clean up orders
   void ~OrderCollection() {
      for (int i = 0; i < ArraySize(_orders); i++) {
         delete(_orders[i]);                              //--- Delete each order object
      }
   }

   //--- Add order to collection
   void Add(Order* item) {
      _size = _size + 1;                                  //--- Increment size
      ArrayResize(_orders, _size, 8);                     //--- Resize array with reserve capacity
      _orders[(_size - 1)] = item;                        //--- Store order at last index
   }

   //--- Remove order at specified index
   Order* Remove(int index) {
      Order* removed = NULL;                              //--- Initialize removed order as null
      if (index >= 0 && index < _size) {                  //--- Check valid index
         removed = _orders[index];                        //--- Store order to be removed
         for (int i = index; i < (_size - 1); i++) {
            _orders[i] = _orders[i + 1];                  //--- Shift orders left
         }
         ArrayResize(_orders, ArraySize(_orders) - 1, 8); //--- Reduce array size
         _size = _size - 1;                               //--- Decrement size
      }
      return removed;                                     //--- Return removed order or null
   }

   //--- Retrieve order at specified index
   Order* Get(int index) {
      if (index >= 0 && index < _size) {                  //--- Check valid index
         return _orders[index];                           //--- Return order at index
      }
      return NULL;                                        //--- Return null for invalid index
   }

   //--- Retrieve number of orders
   int Count() {
      return _size;                                       //--- Return current size
   }

   //--- Reset iterator to start
   void Rewind() {
      _pointer = -1;                                      //--- Set pointer to -1
   }

   //--- Move to next order
   Order* Next() {
      _pointer++;                                         //--- Increment pointer
      if (_pointer == _size) {                            //--- Check if at end
         Rewind();                                        //--- Reset pointer
         return NULL;                                     //--- Return null
      }
      return Current();                                   //--- Return current order
   }

   //--- Move to previous order
   Order* Prev() {
      _pointer--;                                         //--- Decrement pointer
      if (_pointer == -1) {                               //--- Check if before start
         return NULL;                                     //--- Return null
      }
      return Current();                                   //--- Return current order
   }

   //--- Check if more orders exist
   bool HasNext() {
      return (_pointer < (_size - 1));                    //--- Return true if pointer is before end
   }

   //--- Retrieve current order
   Order* Current() {
      return _orders[_pointer];                           //--- Return order at current pointer
   }

   //--- Retrieve current iterator index
   int Key() {
      return _pointer;                                    //--- Return current pointer
   }

   //--- Find index by order ticket
   int GetKeyByTicket(ulong ticket) {
      int keyFound = -1;                                  //--- Initialize found index as -1
      for (int i = 0; i < ArraySize(_orders); i++) {
         if (_orders[i].Ticket == ticket) {               //--- Check ticket match
            keyFound = i;                                 //--- Set found index
         }
      }
      return keyFound;                                    //--- Return found index or -1
   }
};

//--- Define class for managing order operations with broker
class OrderRepository {
private:
   //--- Retrieve order by ticket
   static Order* getByTicket(ulong ticket) {
      bool orderSelected = OrderSelect(ticket);           //--- Select order by ticket
      if (orderSelected) {                                //--- Check if selection succeeded
         Order* order = new Order(false);                 //--- Create new order object
         OrderRepository::fetchSelected(order);           //--- Populate order details
         return order;                                    //--- Return order object
      } else {
         return NULL;                                     //--- Return null if selection failed
      }
   }

   //--- Retrieve close time for historical order
   static datetime OrderCloseTime(ulong ticket) {
      return (datetime)(HistoryOrderGetInteger(ticket, ORDER_TIME_DONE_MSC) / 1000); //--- Return close time in seconds
   }

   //--- Retrieve close price for historical order
   static double OrderClosePrice(ulong ticket) {
      return HistoryOrderGetDouble(ticket, ORDER_PRICE_CURRENT); //--- Return close price
   }

   //--- Populate order details from selected order
   static void fetchSelected(Order& order) {
      COrderInfo orderInfo;                               //--- Declare order info object
      order.Ticket = orderInfo.Ticket();                  //--- Set order ticket
      order.Type = orderInfo.OrderType();                 //--- Set order type
      order.State = orderInfo.State();                    //--- Set order state
      order.MagicNumber = orderInfo.Magic();              //--- Set magic number
      order.Lots = orderInfo.VolumeInitial();             //--- Set initial volume
      order.OpenPrice = orderInfo.PriceOpen();            //--- Set open price
      order.StopLoss = orderInfo.StopLoss();              //--- Set Stop Loss
      order.TakeProfit = orderInfo.TakeProfit();          //--- Set Take Profit
      order.Expiration = orderInfo.TimeExpiration();      //--- Set expiration time
      order.Comment = orderInfo.Comment();                //--- Set comment
      order.OpenTime = orderInfo.TimeSetup();             //--- Set open time
      order.CloseTime = OrderCloseTime(order.Ticket); //--- Set close time
      order.SymbolCode = orderInfo.Symbol();              //--- Set symbol code
      order.TradeVolume = orderInfo.VolumeInitial();      //--- Set trade volume
      CalculateAndSetCommision(order);                    //--- Calculate and set commission
   }

   //--- Modify order’s Stop Loss or Take Profit
   static bool modify(ulong ticket, double stopLoss = NULL, double takeProfit = NULL) {
      CTrade trade;                                       //--- Declare trade object
      Order* order = OrderRepository::getByTicket(ticket); //--- Retrieve order by ticket
      double price = order.OpenPrice;                     //--- Set price to open price
      stopLoss = (stopLoss == NULL) ? order.StopLoss : stopLoss; //--- Use existing SL if null
      takeProfit = (takeProfit == NULL) ? order.TakeProfit : takeProfit; //--- Use existing TP if null
      datetime expiration = order.Expiration;             //--- Set expiration
      bool result = false;                                //--- Initialize result as false
      if (order.State == ORDER_STATE_PLACED) {            //--- Check if order is pending
         result = trade.OrderModify(ticket, price, stopLoss, takeProfit, ORDER_TIME_SPECIFIED, expiration, 0); //--- Modify pending order
      } else if (order.State == ORDER_STATE_FILLED) {     //--- Check if order is filled
         result = trade.PositionModify(ticket, stopLoss, takeProfit); //--- Modify position
      }
      if (CheckPointer(order) == POINTER_DYNAMIC) {        //--- Check if order is dynamic
         delete(order);                                   //--- Delete order object
      }
      return result;                                      //--- Return modification result
   }

public:
   //--- Retrieve open and pending orders
   static OrderCollection* GetOpenOrders(int magic = NULL, int type = NULL, string symbolCode = NULL) {
      OrderCollection* orders = new OrderCollection();    //--- Create new order collection
      //--- Process pending orders
      for (int orderIndex = 0; orderIndex < OrdersTotal(); orderIndex++) {
         bool orderSelected = OrderSelect(OrderGetTicket(orderIndex)); //--- Select order by index
         if (orderSelected) {                             //--- Check if selection succeeded
            Order* order = new Order(false);              //--- Create new order object
            OrderRepository::fetchSelected(order);        //--- Populate order details
            if ((magic == NULL || magic == order.MagicNumber) &&
                (type == NULL || type == order.Type) &&
                (symbolCode == NULL || symbolCode == order.SymbolCode)) { //--- Filter by magic, type, symbol
               orders.Add(order);                         //--- Add order to collection
            } else {
               if (CheckPointer(order) == POINTER_DYNAMIC) {
                  delete(order);                          //--- Delete unused order object
               }
            }
         }
      }
      //--- Process open positions (netting system)
      int total = PositionsTotal();                       //--- Get total positions
      for (int i = total - 1; i >= 0; i--) {
         ulong position_ticket = PositionGetTicket(i);    //--- Get position ticket
         string position_symbol = PositionGetString(POSITION_SYMBOL); //--- Get position symbol
         long position_magicNumber = PositionGetInteger(POSITION_MAGIC); //--- Get position magic number
         double volume = PositionGetDouble(POSITION_VOLUME); //--- Get position volume
         double open_price = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price
         datetime open_time = (datetime)PositionGetInteger(POSITION_TIME); //--- Get open time
         ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get position type
         if (position_magicNumber == MagicNumber) {       //--- Check matching magic number
            Order* order = new Order(false);              //--- Create new order object
            order.Ticket = position_ticket;               //--- Set order ticket
            if (positionType == POSITION_TYPE_BUY) {      //--- Check if Buy position
               order.Type = ORDER_TYPE_BUY;               //--- Set Buy type
            } else if (positionType == POSITION_TYPE_SELL) { //--- Check if Sell position
               order.Type = ORDER_TYPE_SELL;              //--- Set Sell type
            }
            order.Lots = volume;                          //--- Set order volume
            order.TradeVolume = volume;                   //--- Set trade volume
            order.OpenPrice = open_price;                 //--- Set open price
            order.OpenTime = open_time;                   //--- Set open time
            order.MagicNumber = position_magicNumber;     //--- Set magic number
            order.SymbolCode = position_symbol;           //--- Set symbol code
            if ((magic == NULL || magic == order.MagicNumber) &&
                (type == NULL || type == order.Type) &&
                (symbolCode == NULL || symbolCode == order.SymbolCode)) { //--- Filter by magic, type, symbol
               orders.Add(order);                         //--- Add order to collection
            } else {
               if (CheckPointer(order) == POINTER_DYNAMIC) {
                  order.Ticket = -1;                      //--- Invalidate ticket
                  delete(order);                          //--- Delete unused order object
               }
            }
         }
      }
      return orders;                                      //--- Return order collection
   }

   //--- Execute Buy order
   static ulong ExecuteOpenBuy(Order* order) {
      ulong orderTicket = ULONG_MAX;                      //--- Initialize ticket as invalid
      MqlTradeRequest request = {};                       //--- Declare trade request
      MqlTradeResult result = {};                         //--- Declare trade result
      request.action = TRADE_ACTION_DEAL;                 //--- Set action to deal
      request.symbol = Symbol();                          //--- Set symbol to current
      request.volume = order.Lots;                        //--- Set volume
      request.type = ORDER_TYPE_BUY;                      //--- Set Buy type
      request.price = Ask_LibFunc();                      //--- Set price to Ask
      request.deviation = MaxDeviationSlippage;           //--- Set maximum slippage
      request.magic = MagicNumber;                        //--- Set magic number
      request.comment = order.Comment;                    //--- Set order comment
      request.type_filling = (ENUM_ORDER_TYPE_FILLING)OrderFillingType; //--- Set filling type
      ResetLastError();                                   //--- Clear last error
      if (OrderSend(request, result)) {                   //--- Send trade request
         if (result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED) { //--- Check success
            orderTicket = result.order;                   //--- Store order ticket
            order.Ticket = orderTicket;                   //--- Update order ticket
            order.IsAwaitingDealExecution = true;         //--- Flag awaiting execution
         } else {
            Print(StringFormat("OrderSend: retcode=%u", result.retcode)); //--- Log return code
         }
      } else {
         Print(StringFormat("OrderSend: error %d: %s", GetLastError(), GetErrorDescription(result.retcode))); //--- Log error
      }
      return orderTicket;                                 //--- Return order ticket
   }

   //--- Execute Sell order
   static ulong ExecuteOpenSell(Order* order) {
      ulong orderTicket = ULONG_MAX;                      //--- Initialize ticket as invalid
      MqlTradeRequest request = {};                       //--- Declare trade request
      MqlTradeResult result = {};                         //--- Declare trade result
      request.action = TRADE_ACTION_DEAL;                 //--- Set action to deal
      request.symbol = Symbol();                          //--- Set symbol to current
      request.volume = order.Lots;                        //--- Set volume
      request.type = ORDER_TYPE_SELL;                     //--- Set Sell type
      request.price = Bid_LibFunc();                      //--- Set price to Bid
      request.deviation = MaxDeviationSlippage;           //--- Set maximum slippage
      request.magic = MagicNumber;                        //--- Set magic number
      request.comment = order.Comment;                    //--- Set order comment
      request.type_filling = (ENUM_ORDER_TYPE_FILLING)OrderFillingType; //--- Set filling type
      ResetLastError();                                   //--- Clear last error
      if (OrderSend(request, result)) {                   //--- Send trade request
         if (result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED) { //--- Check success
            orderTicket = result.order;                   //--- Store order ticket
            order.Ticket = orderTicket;                   //--- Update order ticket
            order.IsAwaitingDealExecution = true;         //--- Flag awaiting execution
         } else {
            Print(StringFormat("OrderSend: retcode=%u", result.retcode)); //--- Log return code
         }
      } else {
         Print(StringFormat("OrderSend: error %d: %s", GetLastError(), GetErrorDescription(result.retcode))); //--- Log error
      }
      return orderTicket;                                 //--- Return order ticket
   }

   //--- Close position (hedging accounts only)
   static bool ClosePosition(Order* order) {
      CPositionInfo m_position;                           //--- Declare position info object
      CTrade m_trade;                                     //--- Declare trade object
      bool foundPosition = false;                         //--- Initialize position found flag
      for (int i = PositionsTotal() - 1; i >= 0; i--) {   //--- Iterate positions
         if (m_position.SelectByIndex(i)) {               //--- Select position by index
            if (m_position.Ticket() == order.Ticket) {    //--- Check matching ticket
               foundPosition = true;                      //--- Set position found
               uint returnCode = 0;                       //--- Initialize return code
               if (m_trade.PositionClosePartial(order.Ticket, NormalizeDouble(order.Lots, 2), MaxDeviationSlippage)) { //--- Attempt partial close
                  returnCode = m_trade.ResultRetcode();   //--- Get return code
                  if (returnCode == TRADE_RETCODE_DONE || returnCode == TRADE_RETCODE_PLACED) { //--- Check success
                     ulong orderTicket = m_trade.ResultOrder(); //--- Get new order ticket
                     order.Ticket = orderTicket;          //--- Update order ticket
                     order.IsAwaitingDealExecution = true; //--- Flag awaiting execution
                     Print(StringFormat("Successfully created a close order (%d) by EA (%d). Awaiting execution.", orderTicket, MagicNumber)); //--- Log success
                     return true;                         //--- Return true
                  }
                  Print(StringFormat("Placing close order failed, Return code: %d", returnCode)); //--- Log failure
               }
            }
         }
      }
      return false;                                       //--- Return false if position not found
   }

   //--- Retrieve recently closed orders
   static OrderCollection* GetLastClosedOrders(datetime startDatetime = NULL) {
      OrderCollection* lastClosedOrders = new OrderCollection(); //--- Create new order collection
      long positionIds[];                                 //--- Store position IDs
      if (HistorySelect(0, TimeCurrent())) {              //--- Select trade history
         for (int i = HistoryDealsTotal() - 1; i >= 0; i--) { //--- Iterate deals
            ulong dealId = HistoryDealGetTicket(i);       //--- Get deal ticket
            long magicNumber = HistoryDealGetInteger(dealId, DEAL_MAGIC); //--- Get deal magic number
            string symbol = HistoryDealGetString(dealId, DEAL_SYMBOL); //--- Get deal symbol
            if ((magicNumber != MagicNumber && magicNumber != 0) || symbol != Symbol()) { //--- Filter by magic and symbol
               continue;                                  //--- Skip non-matching deals
            }
            if (HistoryDealGetInteger(dealId, DEAL_ENTRY) == DEAL_ENTRY_OUT) { //--- Check if deal is close
               datetime closetime = (datetime)HistoryDealGetInteger(dealId, DEAL_TIME); //--- Get close time
               if (startDatetime > closetime) {           //--- Check if before start time
                  break;                                  //--- Exit loop
               }
               long positionId = HistoryDealGetInteger(dealId, DEAL_POSITION_ID); //--- Get position ID
               for (int pi = 0; pi < ArraySize(positionIds); pi++) { //--- Check existing IDs
                  if (positionIds[pi] == positionId) {    //--- Skip duplicates
                     continue;
                  }
               }
               int size = ArraySize(positionIds);         //--- Get current ID array size
               ArrayResize(positionIds, size + 1);        //--- Add new ID
               positionIds[size] = positionId;            //--- Store position ID
            }
         }
      }
      for (int i = 0; i < ArraySize(positionIds); i++) {  //--- Process each position
         if (HistorySelectByPosition(positionIds[i])) {    //--- Select position history
            Order* order = new Order(false);              //--- Create new order object
            double currentOutVolume = 0;                  //--- Track closed volume
            for (int j = 0; j < HistoryDealsTotal(); j++) { //--- Iterate deals
               ulong ticket = HistoryDealGetTicket(j);    //--- Get deal ticket
               if (HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN) { //--- Check if open deal
                  datetime openTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME); //--- Get open time
                  double openPrice = HistoryDealGetDouble(ticket, DEAL_PRICE); //--- Get open price
                  double lots = HistoryDealGetDouble(ticket, DEAL_VOLUME); //--- Get volume
                  if (order.Ticket == 0) {                //--- Check if first deal
                     order.Ticket = HistoryDealGetInteger(ticket, DEAL_ORDER); //--- Set order ticket
                     long dealType = HistoryDealGetInteger(ticket, DEAL_TYPE); //--- Get deal type
                     if (dealType == ORDER_TYPE_BUY) {    //--- Check if Buy
                        order.Type = ORDER_TYPE_SELL;     //--- Set Sell type (reversed for close)
                     } else if (dealType == ORDER_TYPE_SELL) { //--- Check if Sell
                        order.Type = ORDER_TYPE_BUY;      //--- Set Buy type (reversed for close)
                     } else {
                        Alert("Unknown order.Type in GetLastClosedOrder"); //--- Log unknown type
                     }
                     order.OpenTime = openTime;           //--- Set open time
                     order.OpenPrice = openPrice;         //--- Set open price
                     order.Lots = lots;                   //--- Set volume
                  } else {
                     double averagePrice = ((order.OpenPrice * order.Lots) + (openPrice * lots)) / (order.Lots + lots); //--- Calculate average price
                     order.Lots = order.Lots + lots;      //--- Add volume
                     order.OpenPrice = averagePrice;      //--- Update open price
                  }
               } else if (HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT) { //--- Check if close deal
                  double dealLots = HistoryDealGetDouble(ticket, DEAL_VOLUME); //--- Get close volume
                  double dealClosePrice = HistoryDealGetDouble(ticket, DEAL_PRICE); //--- Get close price
                  if (order.CloseTime == 0) {          //--- Check if first close
                     order.CloseTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME); //--- Set close time
                     order.ClosePrice = dealClosePrice; //--- Set close price
                     currentOutVolume = dealLots;       //--- Set initial close volume
                  } else {
                     double averagePrice = ((order.ClosePrice * currentOutVolume) + (dealClosePrice * dealLots)) / (currentOutVolume + dealLots); //--- Calculate average close price
                     order.CloseTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME); //--- Update close time
                     order.ClosePrice = averagePrice;   //--- Update close price
                     currentOutVolume += dealLots;      //--- Add close volume
                  }
               }
            }
            lastClosedOrders.Add(order);                 //--- Add order to collection
         }
      }
      return lastClosedOrders;                         //--- Return closed orders collection
   }

   //--- Open order (Buy or Sell)
   static bool OpenOrder(Order* order) {
      double price = NULL;                                //--- Initialize price
      ulong ticketId = -1;                                //--- Initialize ticket
      switch (order.Type) {
      case ORDER_TYPE_BUY:
         ticketId = ExecuteOpenBuy(order);                //--- Execute Buy order
         break;
      case ORDER_TYPE_SELL:
         ticketId = ExecuteOpenSell(order);               //--- Execute Sell order
         break;
      }
      bool success = ticketId != ULONG_MAX;               //--- Check if order was opened
      if (success) {
         Print(StringFormat("Successfully opened an order (%d) by EA (%d)", ticketId, MagicNumber)); //--- Log success
      }
      return success;                                     //--- Return true if successful
   }

   //--- Calculate and set commission for order
   static void CalculateAndSetCommision(Order& order) {
      order.Commission = 0.0;                             //--- Initialize commission
      order.CommissionInPips = 0.0;                       //--- Initialize commission in pips
      order.Commission = 2.0 * CommissionAmountPerTrade + //--- Add roundtrip fixed commission
                         CommissionPercentagePerLot * order.Lots * UnitsOneLot + //--- Add percentage commission
                         CommissionAmountPerLot * order.Lots; //--- Add per-lot commission
      if (order.Lots > 1.0e-5 && order.Commission > 1.0e-5) { //--- Check valid volume and commission
         order.CommissionInPips = order.Commission / (order.Lots * UnitsOneLot * PipPoint); //--- Calculate commission in pips
      }
   }
};

我们定义了 “OrderCollection” 类来管理 “Order” 对象集合,内部使用 “_orders” 数组存储,“_pointer” 用于迭代定位,“_size” 用于跟踪订单数量。其方法包括:用于初始化的 “OrderCollection”;用于追加订单的 “Add”;用于删除指定索引订单的 “Remove”;用于获取订单的 “Get”;用于获取数量的 “Count”;以及用于遍历和通过票号定位订单的迭代器方法,如 “Rewind”、“Next”、“Prev”、“HasNext”、“Current”、“Key” 和 “GetKeyByTicket”。

我们还创建了 “OrderRepository” 类来处理与经纪商的交互。其私有方法包括:使用 OrderSelect 获取订单的 “getByTicket”;检索历史订单数据的 “OrderCloseTime” 和 “OrderClosePrice”;通过 “COrderInfo” 填充 “Order” 详情的 “fetchSelected”;以及使用 “CTrade” 更新止损/止盈的 “modify”。公共方法包括 “GetOpenOrders”,用于收集按 “MagicNumber”、“Type” 或 “SymbolCode” 过滤的挂单和持仓订单,同时处理挂单和净头寸。

“ExecuteOpenBuy” 和 “ExecuteOpenSell” 函数利用 MqlTradeRequestMqlTradeResult 发送交易请求,设置 “ORDER_TYPE_BUY” 或 ORDER_TYPE_SELL,使用 “Ask_LibFunc” 或 “Bid_LibFunc” 获取价格,并指定 “OrderFillingType”。“ClosePosition” 则使用 “CPositionInfo” 和 “CTrade” 平仓对冲头寸。“GetLastClosedOrders” 函数通过 HistorySelect 分析成交历史来检索最近平仓的订单,“CalculateAndSetCommision” 则利用 “CommissionAmountPerTrade”、“CommissionPercentagePerLot” 和 “CommissionAmountPerLot” 计算佣金。接下来,我们可以对订单进行分组并进行高效地哈希处理。

//--- Define class for grouping order tickets
class OrderGroupData {
public:
   ulong OrderTicketIds[];                                //--- Store array of order ticket IDs

   //--- Default constructor
   void OrderGroupData() {}                               //--- Initialize empty group

   //--- Copy constructor
   void OrderGroupData(OrderGroupData* ordergroupdata) {} //--- Initialize from existing group (empty)

   //--- Add ticket to group
   void Add(ulong ticketId) {
      int size = ArraySize(OrderTicketIds);               //--- Get current array size
      ArrayResize(OrderTicketIds, size + 1);              //--- Resize array
      OrderTicketIds[size] = ticketId;                    //--- Store ticket at last index
   }

   //--- Remove ticket from group
   void Remove(ulong ticketId) {
      int size = ArraySize(OrderTicketIds);               //--- Get current array size
      int counter = 0;                                    //--- Track new array position
      int counterFound = 0;                               //--- Track removed tickets
      for (int i = 0; i < size; i++) {
         if (OrderTicketIds[i] == ticketId) {             //--- Check matching ticket
            counterFound++;                               //--- Increment found count
            continue;                                     //--- Skip to next
         } else {
            OrderTicketIds[counter] = OrderTicketIds[i];  //--- Shift ticket
            counter++;                                    //--- Increment new position
         }
      }
      if (counterFound > 0) {                             //--- Check if tickets were removed
         ArrayResize(OrderTicketIds, counter);            //--- Resize array to new size
      }
   }

   //--- Destructor
   void ~OrderGroupData() {}                              //--- Clean up group
};

//--- Define class for hash map entry
class OrderGroupHashEntry {
public:
   string _key;                                           //--- Store entry key
   OrderGroupData* _val;                                  //--- Store order group data
   OrderGroupHashEntry* _next;                            //--- Point to next entry

   //--- Default constructor
   OrderGroupHashEntry() {
      _key = NULL;                                        //--- Set key to null
      _val = NULL;                                        //--- Set value to null
      _next = NULL;                                       //--- Set next to null
   }

   //--- Constructor with key and value
   OrderGroupHashEntry(string key, OrderGroupData* val) {
      _key = key;                                         //--- Set key
      _val = val;                                         //--- Set value
      _next = NULL;                                       //--- Set next to null
   }

   //--- Destructor
   ~OrderGroupHashEntry() {
      if (_val != NULL && CheckPointer(_val) == POINTER_DYNAMIC) { //--- Check if value is dynamic
         delete(_val);                                    //--- Delete value
      }
   }
};

//--- Define class for hash map of order groups
class OrderGroupHashMap {
private:
   uint _hashSlots;                                       //--- Store number of hash slots
   int _resizeThreshold;                                  //--- Store resize threshold
   int _hashEntryCount;                                   //--- Track number of entries
   OrderGroupHashEntry* _buckets[];                       //--- Store hash buckets
   bool _adoptValues;                                     //--- Flag value adoption
   uint _foundIndex;                                      //--- Store found index
   OrderGroupHashEntry* _foundEntry;                      //--- Store found entry
   OrderGroupHashEntry* _foundPrev;                       //--- Store previous entry

   //--- Initialize hash map
   void init(uint size, bool adoptValues) {
      _hashSlots = 0;                                     //--- Set initial slots to 0
      _hashEntryCount = 0;                                //--- Set initial entry count to 0
      _adoptValues = adoptValues;                         //--- Set value adoption flag
      rehash(size);                                       //--- Resize hash map
   }

   //--- Calculate hash for key
   uint hash(string s) {
      uchar c[];                                          //--- Declare character array
      uint h = 0;                                         //--- Initialize hash
      if (s != NULL) {                                    //--- Check if key is valid
         h = 5381;                                        //--- Set initial hash value
         int n = StringToCharArray(s, c);                 //--- Convert string to chars
         for (int i = 0; i < n; i++) {
            h = ((h << 5) + h) + c[i];                    //--- Update hash
         }
      }
      return h % _hashSlots;                              //--- Return hash modulo slots
   }

   //--- Find entry by key
   bool find(string keyName) {
      bool found = false;                                 //--- Initialize found flag
      _foundPrev = NULL;                                  //--- Set previous to null
      _foundIndex = hash(keyName);                        //--- Calculate hash index
      if (_foundIndex <= _hashSlots) {                    //--- Check valid index
         for (OrderGroupHashEntry* e = _buckets[_foundIndex]; e != NULL; e = e._next) { //--- Iterate bucket
            if (e._key == keyName) {                      //--- Check key match
               _foundEntry = e;                           //--- Store found entry
               found = true;                              //--- Set found flag
               break;                                     //--- Exit loop
            }
            _foundPrev = e;                               //--- Update previous
         }
      }
      return found;                                       //--- Return found status
   }

   //--- Retrieve number of slots
   uint getSlots() {
      return _hashSlots;                                  //--- Return slot count
   }

   //--- Resize hash map
   bool rehash(uint newSize) {
      bool ret = false;                                   //--- Initialize return flag
      OrderGroupHashEntry* oldTable[];                    //--- Declare old table
      uint oldSize = _hashSlots;                          //--- Store current size
      if (newSize <= getSlots()) {                        //--- Check if resize is needed
         ret = false;                                     //--- Set failure
      } else if (ArrayResize(_buckets, newSize) != newSize) { //--- Resize buckets
         ret = false;                                     //--- Set failure
      } else if (ArrayResize(oldTable, oldSize) != oldSize) { //--- Resize old table
         ret = false;                                     //--- Set failure
      } else {
         uint i = 0;                                      //--- Initialize index
         for (i = 0; i < oldSize; i++) {                  //--- Copy buckets
            oldTable[i] = _buckets[i];                    //--- Store old bucket
         }
         for (i = 0; i < newSize; i++) {                  //--- Clear new buckets
            _buckets[i] = NULL;                           //--- Set to null
         }
         _hashSlots = newSize;                            //--- Update slot count
         _resizeThreshold = (int)_hashSlots / 4 * 3;      //--- Set resize threshold
         for (uint oldHashCode = 0; oldHashCode < oldSize; oldHashCode++) { //--- Rehash entries
            OrderGroupHashEntry* next = NULL;             //--- Initialize next
            for (OrderGroupHashEntry* e = oldTable[oldHashCode]; e != NULL; e = next) { //--- Iterate old bucket
               next = e._next;                            //--- Store next entry
               uint newHashCode = hash(e._key);           //--- Calculate new hash
               e._next = _buckets[newHashCode];           //--- Link to new bucket
               _buckets[newHashCode] = e;                 //--- Store in new bucket
            }
            oldTable[oldHashCode] = NULL;                 //--- Clear old bucket
         }
         ret = true;                                      //--- Set success
      }
      return ret;                                         //--- Return resize result
   }

public:
   //--- Default constructor
   OrderGroupHashMap() {
      init(13, false);                                    //--- Initialize with 13 slots, no adoption
   }

   //--- Constructor with adoption flag
   OrderGroupHashMap(bool adoptValues) {
      init(13, adoptValues);                              //--- Initialize with 13 slots
   }

   //--- Constructor with size
   OrderGroupHashMap(int size) {
      init(size, false);                                  //--- Initialize with specified size, no adoption
   }

   //--- Constructor with size and adoption
   OrderGroupHashMap(int size, bool adoptValues) {
      init(size, adoptValues);                            //--- Initialize with size and adoption
   }

   //--- Destructor
   ~OrderGroupHashMap() {
      for (uint i = 0; i < _hashSlots; i++) {            //--- Iterate buckets
         OrderGroupHashEntry* nextEntry = NULL;           //--- Initialize next
         for (OrderGroupHashEntry* entry = _buckets[i]; entry != NULL; entry = nextEntry) { //--- Iterate entries
            nextEntry = entry._next;                      //--- Store next entry
            if (_adoptValues && entry._val != NULL && CheckPointer(entry._val) == POINTER_DYNAMIC) { //--- Check if value is dynamic
               delete entry._val;                         //--- Delete value
            }
            delete entry;                                 //--- Delete entry
         }
         _buckets[i] = NULL;                              //--- Clear bucket
      }
   }

   //--- Check if key exists
   bool ContainsKey(string keyName) {
      return find(keyName);                               //--- Return true if key found
   }

   //--- Retrieve group data by key
   OrderGroupData* Get(string keyName) {
      OrderGroupData* obj = NULL;                         //--- Initialize return object
      if (find(keyName)) {                                //--- Check if key exists
         obj = _foundEntry._val;                          //--- Set return object
      }
      return obj;                                         //--- Return group data or null
   }

   //--- Retrieve all group data
   void GetAllData(OrderGroupData* &data[]) {
      for (uint i = 0; i < _hashSlots; i++) {            //--- Iterate buckets
         OrderGroupHashEntry* nextEntry = NULL;           //--- Initialize next
         for (OrderGroupHashEntry* entry = _buckets[i]; entry != NULL; entry = nextEntry) { //--- Iterate entries
            if (entry._val != NULL) {                     //--- Check valid value
               int size = ArraySize(data);                //--- Get current array size
               ArrayResize(data, size + 1);               //--- Resize array
               data[size] = entry._val;                   //--- Store value
               nextEntry = entry._next;                   //--- Move to next
            }
         }
      }
   }

   //--- Store or update group data
   OrderGroupData* Put(string keyName, OrderGroupData* obj) {
      OrderGroupData* ret = NULL;                         //--- Initialize return value
      if (find(keyName)) {                                //--- Check if key exists
         ret = _foundEntry._val;                          //--- Store existing value
         if (_adoptValues && _foundEntry._val != NULL && CheckPointer(_foundEntry._val) == POINTER_DYNAMIC) { //--- Check if value is dynamic
            delete _foundEntry._val;                      //--- Delete existing value
         }
         _foundEntry._val = obj;                          //--- Update value
      } else {
         OrderGroupHashEntry* e = new OrderGroupHashEntry(keyName, obj); //--- Create new entry
         OrderGroupHashEntry* first = _buckets[_foundIndex]; //--- Get current bucket head
         e._next = first;                                 //--- Link new entry
         _buckets[_foundIndex] = e;                       //--- Store new entry
         _hashEntryCount++;                               //--- Increment entry count
         if (_hashEntryCount > _resizeThreshold) {        //--- Check if resize needed
            rehash(_hashSlots / 2 * 3);                   //--- Resize hash map
         }
      }
      return ret;                                         //--- Return previous value or null
   }

   //--- Delete entry by key
   bool Delete(string keyName) {
      bool found = false;                                 //--- Initialize found flag
      if (find(keyName)) {                                //--- Check if key exists
         OrderGroupHashEntry* next = _foundEntry._next;   //--- Store next entry
         if (_foundPrev != NULL) {                        //--- Check if previous exists
            _foundPrev._next = next;                      //--- Update previous link
         } else {
            _buckets[_foundIndex] = next;                 //--- Update bucket head
         }
         if (_adoptValues && _foundEntry._val != NULL && CheckPointer(_foundEntry._val) == POINTER_DYNAMIC) { //--- Check if value is dynamic
            delete _foundEntry._val;                      //--- Delete value
         }
         delete _foundEntry;                              //--- Delete entry
         _hashEntryCount--;                               //--- Decrement entry count
         found = true;                                    //--- Set found flag
      }
      return found;                                       //--- Return true if deleted
   }

   //--- Delete multiple keys
   int DeleteKeys(const string& keys[]) {
      int count = 0;                                      //--- Initialize delete count
      for (int i = 0; i < ArraySize(keys); i++) {         //--- Iterate keys
         if (Delete(keys[i])) {                           //--- Attempt to delete key
            count++;                                      //--- Increment count
         }
      }
      return count;                                       //--- Return number of deleted keys
   }

   //--- Delete all keys except specified
   int DeleteKeysExcept(const string& keys[]) {
      int index = 0, count = 0;                           //--- Initialize index and count
      string hashedKeys[];                                //--- Declare hashed keys array
      ArrayResize(hashedKeys, _hashEntryCount);           //--- Resize to entry count
      for (uint i = 0; i < _hashSlots; i++) {            //--- Iterate buckets
         OrderGroupHashEntry* nextEntry = NULL;           //--- Initialize next
         for (OrderGroupHashEntry* entry = _buckets[i]; entry != NULL; entry = nextEntry) { //--- Iterate entries
            nextEntry = entry._next;                      //--- Store next
            if (entry._key != NULL) {                     //--- Check valid key
               hashedKeys[index] = entry._key;            //--- Store key
               index++;                                   //--- Increment index
            }
         }
      }
      for (int i = 0; i < ArraySize(hashedKeys); i++) {   //--- Iterate hashed keys
         bool keep = false;                               //--- Initialize keep flag
         for (int j = 0; j < ArraySize(keys); j++) {      //--- Check against keep keys
            if (hashedKeys[i] == keys[j]) {               //--- Check match
               keep = true;                               //--- Set keep flag
               break;                                     //--- Exit loop
            }
         }
         if (!keep) {                                     //--- Check if key should be deleted
            if (Delete(hashedKeys[i])) {                  //--- Attempt to delete
               count++;                                   //--- Increment count
            }
         }
      }
      return count;                                       //--- Return number of deleted keys
   }
};

为了高效管理和分组订单,从而有条不紊地处理交易,我们定义了 “OrderGroupData” 类,用于在 “OrderTicketIds” 数组中存储订单号 ID。该类包含用于初始化的 “OrderGroupData” 方法、用于添加订单 ID 的 “Add” 方法、通过移动剩余项来删除特定单号的 “Remove” 方法,以及用于清理工作的析构函数 “~OrderGroupData”,以此确保动态订单管理的顺畅。

接下来,我们创建 “OrderGroupHashEntry” 类来表示哈希映射中的条目,其中包含用于存储条目标识符的 “_key”、用于持有 “OrderGroupData” 对象的 “_val”,以及用于在发生冲突时链接条目的 “_next”。其构造函数 “OrderGroupHashEntry” 负责初始化,而析构函数 “~OrderGroupHashEntry” 则在需要时释放动态分配的 “OrderGroupData” 对象。

随后,我们实现 “OrderGroupHashMap” 类,利用哈希表来管理订单组。其私有变量包括:用于存储分类数量的 “_hashSlots”、用于触发扩容的 “_resizeThreshold”、用于跟踪条目数量的 “_hashEntryCount”,以及用于存储 “OrderGroupHashEntry” 数组的 “_buckets”。私有方法 “init” 用于初始化哈希映射,“hash” 用于计算键的哈希值,“find” 用于按键查找条目,“rehash” 则用于调整表的大小。

公共方法包括:用于不同初始化选项的构造函数 “OrderGroupHashMap”;用于检查键是否存在的 “ContainsKey”;用于获取 “OrderGroupData” 的 “Get”;用于收集所有组的 “GetAllData”;用于添加或更新条目的 “Put”;用于移除键的 “Delete”;用于删除多个键的 “DeleteKeys”;以及用于删除除指定键之外所有键的 “DeleteKeysExcept”。析构函数 “~OrderGroupHashMap” 确保了资源的妥善清理。现在,我们需要管理交易状态,为此需要额外定义一个类来执行该操作。

//--- Declare global array to track recent order results
int LastOrderResults[];                                   //--- Store outcomes of recent trades (1 for profit, 0 for loss)

//--- Define class for managing trading state and orders
class Wallet {
private:
   int _openedBuyOrderCount;                              //--- Track number of open Buy orders
   int _openedSellOrderCount;                             //--- Track number of open Sell orders
   ulong _closedOrderCount;                               //--- Track total closed orders
   int _lastOrderResultSize;                              //--- Store size of LastOrderResults array
   ENUM_TIMEFRAMES _lastOrderResultByTimeframe;           //--- Store timeframe for tracking closed orders
   datetime _lastBarStartTime;                            //--- Store start time of last bar
   OrderCollection* _openOrders;                          //--- Store currently open orders
   OrderGroupHashMap* _openOrdersSymbolType;              //--- Store open orders grouped by symbol and type
   OrderGroupHashMap* _openOrdersSymbol;                  //--- Store open orders grouped by symbol
   OrderCollection* _pendingOpenOrders;                   //--- Store pending open orders
   OrderCollection* _pendingCloseOrders;                  //--- Store pending close orders
   Order* _mostRecentOpenOrder;                           //--- Store most recently opened order
   Order* _mostRecentClosedOrder;                         //--- Store most recently closed order
   OrderCollection* _recentClosedOrders;                  //--- Store recently closed orders

public:
   //--- Initialize wallet
   void Wallet() {
      _openedBuyOrderCount = 0;                           //--- Set Buy order count to 0
      _openedSellOrderCount = 0;                          //--- Set Sell order count to 0
      _closedOrderCount = 0;                              //--- Set closed order count to 0
      _lastOrderResultSize = 0;                           //--- Set result size to 0
      _lastOrderResultByTimeframe = NULL;                 //--- Set timeframe to null
      _lastBarStartTime = NULL;                           //--- Set bar start time to null
      _pendingOpenOrders = new OrderCollection();         //--- Create pending open orders collection
      _pendingCloseOrders = new OrderCollection();        //--- Create pending close orders collection
      _recentClosedOrders = new OrderCollection();        //--- Create recent closed orders collection
      _openOrdersSymbolType = NULL;                       //--- Set symbol-type group to null
      _openOrdersSymbol = NULL;                           //--- Set symbol group to null
      _openOrders = new OrderCollection();                //--- Create open orders collection
      _mostRecentOpenOrder = NULL;                        //--- Set recent open order to null
      _mostRecentClosedOrder = NULL;                      //--- Set recent closed order to null
   }

   //--- Destructor to clean up wallet
   void ~Wallet() {
      delete(_pendingOpenOrders);                         //--- Delete pending open orders
      delete(_pendingCloseOrders);                        //--- Delete pending close orders
      delete(_recentClosedOrders);                        //--- Delete recent closed orders
      if (_openOrders != NULL) {                          //--- Check if open orders exist
         delete(_openOrders);                             //--- Delete open orders
      }
      if (_mostRecentOpenOrder != NULL) {                 //--- Check if recent open order exists
         delete(_mostRecentOpenOrder);                    //--- Delete recent open order
      }
      if (_mostRecentClosedOrder != NULL) {               //--- Check if recent closed order exists
         delete(_mostRecentClosedOrder);                  //--- Delete recent closed order
      }
      if (_openOrdersSymbolType != NULL) {                //--- Check if symbol-type group exists
         delete(_openOrdersSymbolType);                   //--- Delete symbol-type group
      }
      if (_openOrdersSymbol != NULL) {                    //--- Check if symbol group exists
         delete(_openOrdersSymbol);                       //--- Delete symbol group
      }
   }

   //--- Handle new tick event
   void HandleTick() {
      if (_lastOrderResultByTimeframe != NULL) {          //--- Check if timeframe is set
         datetime newBarStartTime = iTime(_Symbol, _lastOrderResultByTimeframe, 0); //--- Get current bar start
         if (_lastBarStartTime == newBarStartTime) {      //--- Check if same bar
            return;                                       //--- Exit if no new bar
         } else {
            _lastBarStartTime = newBarStartTime;          //--- Update bar start time
            for (int i = 0; i < _recentClosedOrders.Count(); i++) { //--- Iterate closed orders
               Order* order = _recentClosedOrders.Get(i); //--- Get closed order
               if (CheckPointer(order) != POINTER_INVALID && CheckPointer(order) == POINTER_DYNAMIC) { //--- Check dynamic pointer
                  delete(order);                             //--- Delete order
               }
               _recentClosedOrders.Remove(i);             //--- Remove order
            }
            PrintOrderChanges();                          //--- Log order changes
         }
      }
   }

   //--- Set size of order results array
   void SetLastOrderResultsSize(int size) {
      if (size > _lastOrderResultSize) {                  //--- Check if size increased
         ArrayResize(LastOrderResults, size);             //--- Resize results array
         ArrayInitialize(LastOrderResults, 1);            //--- Initialize with 1 (assume profit)
         _lastOrderResultSize = size;                     //--- Update result size
      }
   }

   //--- Set timeframe for tracking closed orders
   void SetLastClosedOrdersByTimeframe(ENUM_TIMEFRAMES timeframe) {
      if (_lastOrderResultByTimeframe != NULL && timeframe <= _lastOrderResultByTimeframe) { //--- Check if timeframe is valid
         return;                                          //--- Exit if no change needed
      }
      _lastOrderResultByTimeframe = timeframe;            //--- Set new timeframe
      _lastBarStartTime = iTime(_Symbol, _lastOrderResultByTimeframe, 0); //--- Set bar start time
   }

   //--- Retrieve recent closed orders
   OrderCollection* GetRecentClosedOrders() {
      return _recentClosedOrders;                         //--- Return closed orders collection
   }

   //--- Activate order grouping types
   void ActivateOrderGroups(ORDER_GROUP_TYPE &groupTypes[]) {
      for (int i = 0; i < ArrayRange(groupTypes, 0); i++) { //--- Iterate group types
         if (groupTypes[i] == SymbolOrderType && _openOrdersSymbolType == NULL) { //--- Check symbol-type grouping
            _openOrdersSymbolType = new OrderGroupHashMap(); //--- Create symbol-type hash map
         } else if (groupTypes[i] == SymbolCode && _openOrdersSymbol == NULL) { //--- Check symbol grouping
            _openOrdersSymbol = new OrderGroupHashMap();  //--- Create symbol hash map
         }
      }
   }

   //--- Retrieve open orders
   OrderCollection* GetOpenOrders() {
      if (_openOrders == NULL) {                          //--- Check if orders are loaded
         LoadOrdersFromBroker();                          //--- Load orders from broker
      }
      return _openOrders;                                 //--- Return open orders collection
   }

   //--- Retrieve open order by ticket
   Order* GetOpenOrder(ulong ticketId) {
      int index = _openOrders.GetKeyByTicket(ticketId);   //--- Find order index by ticket
      if (index == -1) {                                  //--- Check if not found
         return NULL;                                     //--- Return null
      }
      return _openOrders.Get(index);                      //--- Return order at index
   }

   //--- Retrieve grouped orders by symbol and type
   void GetOpenOrdersSymbolOrderType(OrderGroupData* &data[]) {
      _openOrdersSymbolType.GetAllData(data);             //--- Populate data with grouped orders
   }

   //--- Retrieve grouped orders by symbol
   void GetOpenOrdersSymbol(OrderGroupData* &data[]) {
      _openOrdersSymbol.GetAllData(data);                 //--- Populate data with grouped orders
   }

   //--- Retrieve pending open orders
   OrderCollection* GetPendingOpenOrders() {
      return _pendingOpenOrders;                          //--- Return pending open orders
   }

   //--- Retrieve pending close orders
   OrderCollection* GetPendingCloseOrders() {
      return _pendingCloseOrders;                         //--- Return pending close orders
   }

   //--- Reset pending orders
   void ResetPendingOrders() {
      delete(_pendingOpenOrders);                         //--- Delete existing pending open orders
      delete(_pendingCloseOrders);                        //--- Delete existing pending close orders
      _pendingOpenOrders = new OrderCollection();         //--- Create new pending open orders
      _pendingCloseOrders = new OrderCollection();        //--- Create new pending close orders
      Print("Wallet has " + IntegerToString(_pendingOpenOrders.Count()) + " pending open orders now."); //--- Log open orders count
      Print("Wallet has " + IntegerToString(_pendingCloseOrders.Count()) + " pending close orders now."); //--- Log close orders count
   }

   //--- Check if orders are being opened
   bool AreOrdersBeingOpened() {
      for (int i = _pendingOpenOrders.Count() - 1; i >= 0; i--) { //--- Iterate pending open orders
         if (_pendingOpenOrders.Get(i).IsAwaitingDealExecution) { //--- Check execution status
            return true;                                     //--- Return true if awaiting execution
         }
      }
      return false;                                       //--- Return false if no orders pending
   }

   //--- Check if orders are being closed
   bool AreOrdersBeingClosed() {
      for (int i = _pendingCloseOrders.Count() - 1; i >= 0; i--) { //--- Iterate pending close orders
         if (_pendingCloseOrders.Get(i).IsAwaitingDealExecution) { //--- Check execution status
            return true;                                     //--- Return true if awaiting execution
         }
      }
      return false;                                       //--- Return false if no orders pending
   }

   //--- Reset open orders
   void ResetOpenOrders() {
      _openedBuyOrderCount = 0;                           //--- Reset Buy order count
      _openedSellOrderCount = 0;                          //--- Reset Sell order count
      if (_openOrders != NULL) {                          //--- Check if open orders exist
         delete(_openOrders);                             //--- Delete open orders
         _openOrders = new OrderCollection();             //--- Create new open orders
      }
      if (_openOrdersSymbol != NULL) {                    //--- Check if symbol group exists
         delete(_openOrdersSymbol);                       //--- Delete symbol group
         _openOrdersSymbol = new OrderGroupHashMap();     //--- Create new symbol group
      }
      if (_openOrdersSymbolType != NULL) {                //--- Check if symbol-type group exists
         delete(_openOrdersSymbolType);                   //--- Delete symbol-type group
         _openOrdersSymbolType = new OrderGroupHashMap(); //--- Create new symbol-type group
      }
   }

   //--- Retrieve most recent open order
   Order* GetMostRecentOpenOrder() {
      return _mostRecentOpenOrder;                        //--- Return recent open order
   }

   //--- Retrieve most recent closed order
   Order* GetMostRecentClosedOrder() {
      return _mostRecentClosedOrder;                      //--- Return recent closed order
   }

   //--- Load orders from broker
   void LoadOrdersFromBroker() {
      OrderCollection* brokerOrders = OrderRepository::GetOpenOrders(MagicNumber, NULL, Symbol()); //--- Retrieve open orders
      for (int i = 0; i < brokerOrders.Count(); i++) {    //--- Iterate broker orders
         Order* openOrder = brokerOrders.Get(i);          //--- Get open order
         AddOrderToOpenOrderCollections(openOrder);       //--- Add to collections
         SetMostRecentOpenOrClosedOrder(openOrder);       //--- Update recent order
         CountAddedOrder(openOrder);                      //--- Update order counts
      }
      OrderCollection* lastClosedOrders = OrderRepository::GetLastClosedOrders(_lastBarStartTime); //--- Retrieve closed orders
      for (int i = 0; i < lastClosedOrders.Count(); i++) { //--- Iterate closed orders
         Order* closedOrder = lastClosedOrders.Get(i);    //--- Get closed order
         _recentClosedOrders.Add(new Order(closedOrder, true)); //--- Add to recent closed orders
         SetMostRecentOpenOrClosedOrder(closedOrder);     //--- Update recent order
      }
      delete(lastClosedOrders);                           //--- Delete closed orders collection
      delete(brokerOrders);                               //--- Delete broker orders collection
      PrintOrderChanges();                                //--- Log order changes
      Print("Wallet has " + IntegerToString(GetOpenedOrderCount()) + " orders now."); //--- Log total open orders
   }

   //--- Move pending open order to open status
   void SetPendingOpenOrderToOpen(Order* justOpenedOrder) {
      bool success = false;                               //--- Initialize success flag
      int key = _pendingOpenOrders.GetKeyByTicket(justOpenedOrder.Ticket); //--- Find order by ticket
      if (key != -1) {                                    //--- Check if order found
         if (_mostRecentOpenOrder != NULL && CheckPointer(_mostRecentOpenOrder) != POINTER_INVALID && CheckPointer(_mostRecentOpenOrder) == POINTER_DYNAMIC) { //--- Check existing recent order
            delete(_mostRecentOpenOrder);                 //--- Delete recent open order
         }
         _mostRecentOpenOrder = new Order(justOpenedOrder, false); //--- Set new recent open order
         AddOrderToOpenOrderCollections(justOpenedOrder); //--- Add to collections
         CountAddedOrder(justOpenedOrder);                //--- Update order counts
         delete(justOpenedOrder);                         //--- Delete input order
         _pendingOpenOrders.Remove(key);                  //--- Remove from pending
         success = true;                                  //--- Set success flag
      }
      if (success) {                                      //--- Check if successful
         PrintOrderChanges();                             //--- Log order changes
      } else {
         Alert("Couldn't move pending open order to opened orders for ticketid: " + IntegerToString(justOpenedOrder.Ticket)); //--- Log failure
      }
   }

   //--- Cancel pending open order
   bool CancelPendingOpenOrder(Order* justOpenedOrder) {
      int key = _pendingOpenOrders.GetKeyByTicket(justOpenedOrder.Ticket); //--- Find order by ticket
      if (key != -1) {                                    //--- Check if order found
         delete(justOpenedOrder);                         //--- Delete order
         _pendingOpenOrders.Remove(key);                  //--- Remove from pending
      } else {
         Alert("Couldn't cancel pending open order for ticketid: " + IntegerToString(justOpenedOrder.Ticket)); //--- Log failure
      }
      PrintOrderChanges();                                //--- Log order changes
      return key != -1;                                   //--- Return true if canceled
   }

   //--- Move all open orders to pending close
   void SetAllOpenOrdersToPendingClose() {
      bool success = false;                               //--- Initialize success flag
      for (int i = _openOrders.Count() - 1; i >= 0; i--) { //--- Iterate open orders
         Order* order = _openOrders.Get(i);               //--- Get open order
         if (MoveOpenOrderToPendingCloseOrders(order)) {  //--- Move to pending close
            success = true;                               //--- Set success flag
         }
      }
      if (success) {                                      //--- Check if changes made
         PrintOrderChanges();                             //--- Log order changes
      }
   }

   //--- Move single open order to pending close
   bool SetOpenOrderToPendingClose(Order* orderToClose) {
      bool success = MoveOpenOrderToPendingCloseOrders(orderToClose); //--- Move to pending close
      if (success) {                                      //--- Check if successful
         PrintOrderChanges();                             //--- Log order changes
         return true;                                     //--- Return true
      }
      Alert("Couldn't move open order to pendingclose orders for ticketid: " + IntegerToString(orderToClose.Ticket)); //--- Log failure
      return false;                                       //--- Return false
   }

   //--- Add order to pending close
   bool AddPendingCloseOrder(Order* orderToClose) {
      _pendingCloseOrders.Add(new Order(orderToClose, false)); //--- Add new order to pending close
      if (CheckPointer(orderToClose) != POINTER_INVALID && CheckPointer(orderToClose) == POINTER_DYNAMIC) { //--- Check dynamic pointer
         delete(orderToClose);                            //--- Delete input order
      }
      PrintOrderChanges();                                //--- Log order changes
      return true;                                        //--- Return true
   }

   //--- Move pending close order to closed status
   bool SetPendingCloseOrderToClosed(Order* justClosedOrder) {
      int key = _pendingCloseOrders.GetKeyByTicket(justClosedOrder.Ticket); //--- Find order by ticket
      if (key != -1) {                                    //--- Check if order found
         if (_lastOrderResultSize > 0) {                  //--- Check if results tracking enabled
            for (int i = ArraySize(LastOrderResults) - 1; i > 0; i--) { //--- Shift results
               LastOrderResults[i] = LastOrderResults[i - 1]; //--- Move previous result
            }
            LastOrderResults[0] = justClosedOrder.CalculateProfitPips() > 0 ? 1 : 0; //--- Set result (1 for profit, 0 for loss)
         }
         if (_mostRecentClosedOrder != NULL && CheckPointer(_mostRecentClosedOrder) != POINTER_INVALID && CheckPointer(_mostRecentClosedOrder) == POINTER_DYNAMIC) { //--- Check existing recent closed order
            delete(_mostRecentClosedOrder);               //--- Delete recent closed order
         }
         _mostRecentClosedOrder = new Order(justClosedOrder, false); //--- Set new recent closed order
         _recentClosedOrders.Add(new Order(justClosedOrder, true)); //--- Add to recent closed orders
         _pendingCloseOrders.Remove(key);                 //--- Remove from pending close
         delete(justClosedOrder);                         //--- Delete input order
         _closedOrderCount++;                             //--- Increment closed count
         PrintOrderChanges();                             //--- Log order changes
         return true;                                     //--- Return true
      }
      Alert("Couldn't move open order to removed order for ticketid: " + IntegerToString(justClosedOrder.Ticket)); //--- Log failure
      return false;                                       //--- Return false
   }

   //--- Retrieve total open order count
   int GetOpenedOrderCount() {
      return _openedBuyOrderCount + _openedSellOrderCount; //--- Return sum of Buy and Sell orders
   }

   //--- Retrieve closed order count
   ulong GetClosedOrderCount() {
      return _closedOrderCount;                           //--- Return closed order count
   }

private:
   //--- Add order to open order collections
   void AddOrderToOpenOrderCollections(Order* order) {
      Order* newOpenOrder = new Order(order, true);       //--- Create new order with visibility
      _openOrders.Add(newOpenOrder);                      //--- Add to open orders
      if (IsSymbolOrderTypeOrderGroupActivated()) {       //--- Check symbol-type grouping
         string key = GetOrderGroupSymbolOrderTypeKey(order); //--- Get symbol-type key
         OrderGroupData* orderGroupData = _openOrdersSymbolType.Get(key); //--- Retrieve group data
         if (orderGroupData == NULL) {                    //--- Check if group exists
            orderGroupData = new OrderGroupData();        //--- Create new group
         }
         orderGroupData.Add(newOpenOrder.Ticket);         //--- Add order ticket
         _openOrdersSymbolType.Put(key, orderGroupData);  //--- Store group data
      }
      if (IsSymbolOrderGroupActivated()) {                //--- Check symbol grouping
         string key = GetOrderGroupSymbolKey(order);      //--- Get symbol key
         OrderGroupData* orderGroupData = _openOrdersSymbol.Get(key); //--- Retrieve group data
         if (orderGroupData == NULL) {                    //--- Check if group exists
            orderGroupData = new OrderGroupData();        //--- Create new group
         }
         orderGroupData.Add(newOpenOrder.Ticket);         //--- Add order ticket
         _openOrdersSymbol.Put(key, orderGroupData);      //--- Store group data
      }
      PrintOrderChanges();                                //--- Log order changes
   }

   //--- Remove order from open order collections
   bool RemoveOrderFromOpenOrderCollections(Order* order) {
      int key = GetOpenOrders().GetKeyByTicket(order.Ticket); //--- Find order by ticket
      if (key != -1) {                                    //--- Check if order found
         GetOpenOrders().Remove(key);                     //--- Remove from open orders
         if (_openOrdersSymbolType != NULL) {             //--- Check symbol-type grouping
            string symbolOrderTypeKey = GetOrderGroupSymbolOrderTypeKey(order); //--- Get symbol-type key
            OrderGroupData* symbolOrderTypeGroupData = _openOrdersSymbolType.Get(symbolOrderTypeKey); //--- Retrieve group
            symbolOrderTypeGroupData.Remove(order.Ticket); //--- Remove ticket
         }
         if (_openOrdersSymbol != NULL) {                 //--- Check symbol grouping
            string symbolKey = GetOrderGroupSymbolKey(order); //--- Get symbol key
            OrderGroupData* symbolGroupData = _openOrdersSymbol.Get(symbolKey); //--- Retrieve group
            symbolGroupData.Remove(order.Ticket);         //--- Remove ticket
         }
      }
      return key != -1;                                   //--- Return true if removed
   }

   //--- Update most recent open or closed order
   void SetMostRecentOpenOrClosedOrder(Order* order) {
      if (order.CloseTime == 0) {                         //--- Check if open order
         if (_mostRecentOpenOrder == NULL) {              //--- Check if no recent open order
            _mostRecentOpenOrder = new Order(order, false); //--- Set new recent open order
         } else if (_mostRecentOpenOrder.OpenTime < order.OpenTime) { //--- Check if newer
            delete(_mostRecentOpenOrder);                 //--- Delete existing
            _mostRecentOpenOrder = new Order(order, false); //--- Set new recent open order
         }
      } else {                                            //--- Handle closed order
         if (_mostRecentClosedOrder == NULL) {            //--- Check if no recent closed order
            _mostRecentClosedOrder = new Order(order, false); //--- Set new recent closed order
         } else if (_mostRecentClosedOrder.CloseTime < order.CloseTime) { //--- Check if newer
            delete(_mostRecentClosedOrder);               //--- Delete existing
            _mostRecentClosedOrder = new Order(order, false); //--- Set new recent closed order
         }
      }
   }

   //--- Move open order to pending close
   bool MoveOpenOrderToPendingCloseOrders(Order* orderToClose) {
      if (RemoveOrderFromOpenOrderCollections(orderToClose)) { //--- Remove from open collections
         CountRemovedOrder(orderToClose);                 //--- Update order counts
         _pendingCloseOrders.Add(new Order(orderToClose, false)); //--- Add to pending close
         if (orderToClose.OpenTime == _mostRecentOpenOrder.OpenTime) { //--- Check if recent open order
            if (CheckPointer(_mostRecentOpenOrder) != POINTER_INVALID && CheckPointer(_mostRecentOpenOrder) == POINTER_DYNAMIC) { //--- Check dynamic pointer
               delete(_mostRecentOpenOrder);              //--- Delete recent open order
            }
            _mostRecentOpenOrder = NULL;                  //--- Clear recent open order
            Order* newMostRecentOpenOrder = GetLastOpenOrder(); //--- Get new recent open order
            if (newMostRecentOpenOrder != NULL) {         //--- Check if new order exists
               SetMostRecentOpenOrClosedOrder(newMostRecentOpenOrder); //--- Update recent order
            }
         }
         if (CheckPointer(orderToClose) != POINTER_INVALID && CheckPointer(orderToClose) == POINTER_DYNAMIC) { //--- Check dynamic pointer
            delete(orderToClose);                         //--- Delete input order
         }
         return true;                                     //--- Return true
      }
      return false;                                       //--- Return false if failed
   }

   //--- Retrieve last open order
   Order* GetLastOpenOrder() {
      Order* order = NULL;                                //--- Initialize order
      for (int i = _openOrders.Count() - 1; i >= 0; i--) { //--- Iterate open orders
         return _openOrders.Get(i);                       //--- Return last order
      }
      return NULL;                                        //--- Return null if none
   }

   //--- Generate key for symbol and order type
   string GetOrderGroupSymbolOrderTypeKey(Order* order) {
      return order.SymbolCode + IntegerToString(order.Type); //--- Combine symbol and type
   }

   //--- Generate key for symbol
   string GetOrderGroupSymbolKey(Order* order) {
      return order.SymbolCode;                            //--- Return symbol code
   }

   //--- Check if symbol-type grouping is active
   bool IsSymbolOrderTypeOrderGroupActivated() {
      return _openOrdersSymbolType != NULL;               //--- Return true if active
   }

   //--- Check if symbol grouping is active
   bool IsSymbolOrderGroupActivated() {
      return _openOrdersSymbol != NULL;                   //--- Return true if active
   }

   //--- Increment order count for added order
   void CountAddedOrder(Order* order) {
      if (order.Type == ORDER_TYPE_BUY) {                 //--- Check if Buy order
         _openedBuyOrderCount++;                          //--- Increment Buy count
      } else if (order.Type == ORDER_TYPE_SELL) {         //--- Check if Sell order
         _openedSellOrderCount++;                         //--- Increment Sell count
      }
   }

   //--- Decrement order count for removed order
   void CountRemovedOrder(Order* order) {
      if (order.Type == ORDER_TYPE_BUY) {                 //--- Check if Buy order
         _openedBuyOrderCount--;                          //--- Decrement Buy count
      } else if (order.Type == ORDER_TYPE_SELL) {         //--- Check if Sell order
         _openedSellOrderCount--;                         //--- Decrement Sell count
      }
   }

   //--- Log order state changes
   void PrintOrderChanges() {
      if (DisplayOrderInfo && IsDemoLiveOrVisualMode) {   //--- Check if display enabled
         string comment = "\n     ------------------------------------------------------------"; //--- Start comment
         comment += "\n      :: Pending open orders: " + IntegerToString(_pendingOpenOrders.Count()); //--- Add pending open count
         comment += "\n      :: Open orders: " + IntegerToString(_openedBuyOrderCount) + " (Buy), " + IntegerToString(_openedSellOrderCount) + " (Sell)"; //--- Add open counts
         comment += "\n      :: Pending close orders: " + IntegerToString(_pendingCloseOrders.Count()); //--- Add pending close count
         comment += "\n      :: Recently closed orders: " + IntegerToString(_recentClosedOrders.Count()); //--- Add closed count
         OrderInfoComment = comment;                      //--- Store comment
      }
   }
};

在此,我们实现 “Wallet” 类来管理交易状态和订单。我们声明了全局数组 “LastOrderResults”,用于存储交易结果(1 代表盈利,0 代表亏损)。“Wallet” 使用私有变量来跟踪订单数量和时间,包括 “_openedBuyOrderCount”、“_openedSellOrderCount”、“_closedOrderCount”、“_lastOrderResultSize”、“_lastOrderResultByTimeframe”(ENUM_TIMEFRAMES 类型)以及 “_lastBarStartTime”。同时,它还包含指向 “OrderCollection” 对象的指针(“_openOrders”、“_pendingOpenOrders”、“_pendingCloseOrders”、“_recentClosedOrders”)和 “OrderGroupHashMap” 对象的指针(“_openOrdersSymbolType”、“_openOrdersSymbol”)用于分组,以及指向最近订单的 “_mostRecentOpenOrder” 和 “_mostRecentClosedOrder”。

我们定义了 “Wallet” 构造函数,将计数器初始化为 0,创建新的 “OrderCollection” 实例,并将指针设为 null;而 “~Wallet” 析构函数则负责清理所有动态对象。“HandleTick” 方法利用 iTime 在新K线出现时更新 “_recentClosedOrders”,“SetLastOrderResultsSize” 用于调整 “LastOrderResults” 的大小,“SetLastClosedOrdersByTimeframe” 则用于设置 “_lastOrderResultByTimeframe”。公共方法如 “GetRecentClosedOrders”、“GetOpenOrders”、“GetOpenOrder”、“GetPendingOpenOrders” 和 “GetPendingCloseOrders” 用于获取订单集合,“ActivateOrderGroups” 方法则允许通过 “ORDER_GROUP_TYPE” 值(如 “SymbolOrderType”、“SymbolCode”)启用分组功能。

“ResetPendingOrders” 和 “ResetOpenOrders” 方法用于重新初始化集合,“AreOrdersBeingOpened” 和 “AreOrdersBeingClosed” 方法用于检查是否存在待执行的订单。我们实现了 “LoadOrdersFromBroker” 方法,通过 “OrderRepository::GetOpenOrders” 和 “GetLastClosedOrders” 加载订单;“SetPendingOpenOrderToOpen” 和 “CancelPendingOpenOrder” 方法用于管理挂单;“SetAllOpenOrdersToPendingClose” 和 “SetOpenOrderToPendingClose” 方法用于处理平仓操作;“SetPendingCloseOrderToClosed” 方法则用于更新 “LastOrderResults” 和 “_closedOrderCount”。

私有方法如 “AddOrderToOpenOrderCollections”、“RemoveOrderFromOpenOrderCollections”、“SetMostRecentOpenOrClosedOrder”、“GetOrderGroupSymbolOrderTypeKey” 和 “PrintOrderChanges” 用于支持订单分组并将信息记录到 “OrderInfoComment”。该类将成为剥头皮交易订单管理的核心。为了查看当前进度,让我们定义一个用于管理启动过程的基类,并在初始化时调用它来可视化这一关键成果。

//--- Define enumeration for trade actions
enum TradeAction {
   UnknownAction = 0,                                     //--- Represent unknown action
   OpenBuyAction = 1,                                     //--- Represent open Buy action
   OpenSellAction = 2,                                    //--- Represent open Sell action
   CloseBuyAction = 3,                                    //--- Represent close Buy action
   CloseSellAction = 4                                    //--- Represent close Sell action
};

//--- Define interface for trader
interface ITrader {
   void HandleTick();                                     //--- Handle tick event
   void Init();                                           //--- Initialize trader
   Wallet* GetWallet();                                   //--- Retrieve wallet
};

//--- Declare global trader pointer
ITrader *_ea;                                             //--- Store EA instance

//--- Define main Expert Advisor class
class EA : public ITrader {
private:
   bool _firstTick;                                       //--- Track first tick
   Wallet* _wallet;                                       //--- Store wallet

public:
   //--- Initialize EA
   void EA() {
      _firstTick = true;                                  //--- Set first tick flag
      _wallet = new Wallet();                             //--- Create wallet
      _wallet.SetLastClosedOrdersByTimeframe(DisplayOrderDuringTimeframe); //--- Set closed orders timeframe
   }

   //--- Destructor to clean up EA
   void ~EA() {
      delete(_wallet);                                    //--- Delete wallet
   }

   //--- Initialize EA components
   void Init() {
      IsDemoLiveOrVisualMode = !MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE); //--- Set mode flag
      UnitsOneLot = MarketInfo_LibFunc(Symbol(), MODE_LOTSIZE); //--- Set lot size
      _wallet.LoadOrdersFromBroker();                     //--- Load orders from broker
   }

   //--- Handle tick event
   void HandleTick() {
      if (MQLInfoInteger(MQL_TESTER) == 0) {              //--- Check if not in tester
         SyncOrders();                                    //--- Synchronize orders
      }
      if (AllowManualTPSLChanges) {                       //--- Check if manual TP/SL allowed
         SyncManualTPSLChanges();                         //--- Synchronize manual TP/SL
      }
      AskFunc.Evaluate();                                 //--- Update Ask price
      BidFunc.Evaluate();                                 //--- Update Bid price
      UpdateOrders();                                     //--- Update order profits
      if (!StopEA) {                                      //--- Check if EA not stopped
         _wallet.HandleTick();                            //--- Handle wallet tick
         if (ExecutePendingCloseOrders()) {               //--- Execute close orders
            if (!ExecutePendingOpenOrders()) {            //--- Execute open orders
               HandleErrors(StringFormat("Open (all) order(s) failed. Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error
            }
         } else {
            HandleErrors(StringFormat("Close (all) order(s) failed! Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error
         }
      } else {
         if (ExecutePendingCloseOrders()) {               //--- Execute close orders
            _wallet.SetAllOpenOrdersToPendingClose();     //--- Move open orders to pending close
         } else {
            HandleErrors(StringFormat("Close (all) order(s) failed! Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error
         }
      }
      if (_firstTick) {                                   //--- Check if first tick
         _firstTick = false;                              //--- Clear first tick flag
      }
   }

   //--- Retrieve wallet
   Wallet* GetWallet() {
      return _wallet;                                     //--- Return wallet
   }

private:
   //--- Synchronize orders with broker
   void SyncOrders() {
      OrderCollection* currentOpenOrders = OrderRepository::GetOpenOrders(MagicNumber, NULL, Symbol()); //--- Retrieve open orders
      if (currentOpenOrders.Count() != (_wallet.GetOpenOrders().Count() + _wallet.GetPendingCloseOrders().Count())) { //--- Check order mismatch
         Print("(Manual) orderchanges detected" + " (found in MT: " + IntegerToString(currentOpenOrders.Count()) + " and in wallet: " + IntegerToString(_wallet.GetOpenOrders().Count()) + "), resetting EA, loading open orders."); //--- Log mismatch
         _wallet.ResetOpenOrders();                       //--- Reset open orders
         _wallet.ResetPendingOrders();                    //--- Reset pending orders
         _wallet.LoadOrdersFromBroker();                  //--- Reload orders
      }
      delete(currentOpenOrders);                          //--- Delete orders collection
   }

   //--- Synchronize manual TP/SL changes
   void SyncManualTPSLChanges() {
      _wallet.GetOpenOrders().Rewind();                   //--- Reset orders iterator
      while (_wallet.GetOpenOrders().HasNext()) {         //--- Iterate orders
         Order* order = _wallet.GetOpenOrders().Next();   //--- Get order
         uint lineFindResult = ObjectFind(ChartID(), IntegerToString(order.Ticket) + "_SL"); //--- Find SL line
         if (lineFindResult != UINT_MAX) {                //--- Check if SL line exists
            double currentPosition = ObjectGetDouble(ChartID(), IntegerToString(order.Ticket) + "_SL", OBJPROP_PRICE); //--- Get SL position
            if ((order.StopLossManual == 0 && currentPosition != order.GetClosestSL()) || //--- Check manual SL change
                (order.StopLossManual != 0 && currentPosition != order.StopLossManual)) { //--- Check manual SL mismatch
               order.StopLossManual = currentPosition;       //--- Update manual SL
            }
         }
         lineFindResult = ObjectFind(ChartID(), IntegerToString(order.Ticket) + "_TP"); //--- Find TP line
         if (lineFindResult != UINT_MAX) {                //--- Check if TP line exists
            double currentPosition = ObjectGetDouble(ChartID(), IntegerToString(order.Ticket) + "_TP", OBJPROP_PRICE); //--- Get TP position
            if ((order.TakeProfitManual == 0 && currentPosition != order.GetClosestTP()) || //--- Check manual TP change
                (order.TakeProfitManual != 0 && currentPosition != order.TakeProfitManual)) { //--- Check manual TP mismatch
               order.TakeProfitManual = currentPosition;     //--- Update manual TP
            }
         }
      }
   }

   //--- Update order profits
   void UpdateOrders() {
      _wallet.GetOpenOrders().Rewind();                   //--- Reset orders iterator
      while (_wallet.GetOpenOrders().HasNext()) {         //--- Iterate orders
         Order* order = _wallet.GetOpenOrders().Next();   //--- Get order
         double pipsProfit = order.CalculateProfitPips(); //--- Calculate profit
         order.CurrentProfitPips = pipsProfit;            //--- Update current profit
         if (pipsProfit < order.LowestProfitPips) {       //--- Check if lowest profit
            order.LowestProfitPips = pipsProfit;          //--- Update lowest profit
         } else if (pipsProfit > order.HighestProfitPips) { //--- Check if highest profit
            order.HighestProfitPips = pipsProfit;         //--- Update highest profit
         }
      }
   }

   //--- Execute pending close orders
   bool ExecutePendingCloseOrders() {
      OrderCollection* pendingCloseOrders = _wallet.GetPendingCloseOrders(); //--- Retrieve pending close orders
      int ordersToCloseCount = pendingCloseOrders.Count(); //--- Get count
      if (ordersToCloseCount == 0) {                      //--- Check if no orders
         return true;                                     //--- Return true
      }
      if (_wallet.AreOrdersBeingOpened()) {               //--- Check if orders being opened
         return true;                                     //--- Return true
      }
      int ordersCloseSuccessCount = 0;                    //--- Initialize success count
      for (int i = ordersToCloseCount - 1; i >= 0; i--) { //--- Iterate orders
         Order* pendingCloseOrder = pendingCloseOrders.Get(i); //--- Get order
         if (pendingCloseOrder.IsAwaitingDealExecution) { //--- Check if awaiting execution
            ordersCloseSuccessCount++;                    //--- Increment success count
            continue;                                     //--- Move to next
         }
         bool success;                                    //--- Declare success flag
         if (AccountMarginMode == ACCOUNT_MARGIN_MODE_RETAIL_NETTING) { //--- Check netting mode
            Order* reversedOrder = new Order(pendingCloseOrder, false); //--- Create reversed order
            reversedOrder.Type = pendingCloseOrder.Type == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; //--- Set opposite type
            success = OrderRepository::OpenOrder(reversedOrder); //--- Open reversed order
            if (success) {                                //--- Check if successful
               pendingCloseOrder.Ticket = reversedOrder.Ticket; //--- Update ticket
            }
            delete(reversedOrder);                        //--- Delete reversed order
         } else {
            success = OrderRepository::ClosePosition(pendingCloseOrder); //--- Close position
         }
         if (success) {                                   //--- Check if successful
            ordersCloseSuccessCount++;                    //--- Increment success count
         }
      }
      return ordersCloseSuccessCount == ordersToCloseCount; //--- Return true if all successful
   }

   //--- Execute pending open orders
   bool ExecutePendingOpenOrders() {
      OrderCollection* pendingOpenOrders = _wallet.GetPendingOpenOrders(); //--- Retrieve pending open orders
      int ordersToOpenCount = pendingOpenOrders.Count(); //--- Get count
      if (ordersToOpenCount == 0) {                       //--- Check if no orders
         return true;                                     //--- Return true
      }
      int ordersOpenSuccessCount = 0;                     //--- Initialize success count
      for (int i = ordersToOpenCount - 1; i >= 0; i--) { //--- Iterate orders
         Order* order = pendingOpenOrders.Get(i);         //--- Get order
         if (order.IsAwaitingDealExecution) {             //--- Check if awaiting execution
            ordersOpenSuccessCount++;                     //--- Increment success count
            continue;                                     //--- Move to next
         }
         bool isTradeContextFree = false;                 //--- Initialize trade context flag
         double StartWaitingTime = GetTickCount();        //--- Start timer
         while (true) {                                   //--- Wait for trade context
            if (MQL5InfoInteger(MQL5_TRADE_ALLOWED)) {    //--- Check if trade allowed
               isTradeContextFree = true;                 //--- Set trade context free
               break;                                     //--- Exit loop
            }
            int MaxWaiting_sec = 10;                      //--- Set max wait time
            if (IsStopped()) {                            //--- Check if EA stopped
               HandleErrors("The expert was stopped by a user action."); //--- Log error
               break;                                     //--- Exit loop
            }
            if (GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000) { //--- Check if timeout
               HandleErrors(StringFormat("The (%d seconds) waiting time exceeded. Trade not allowed: EA disabled, market closed or trade context still not free.", MaxWaiting_sec)); //--- Log error
               break;                                     //--- Exit loop
            }
            Sleep(100);                                   //--- Wait briefly
         }
         if (!isTradeContextFree) {                       //--- Check if trade context not free
            if (!_wallet.CancelPendingOpenOrder(order)) { //--- Attempt to cancel order
               HandleErrors("Failed to cancel an order (because it couldn't open). Please see the Journal and Expert tab in Metatrader for more information."); //--- Log error
            }
            continue;                                     //--- Move to next
         }
         bool success = OrderRepository::OpenOrder(order); //--- Open order
         if (success) {                                   //--- Check if successful
            ordersOpenSuccessCount++;                     //--- Increment success count
         } else {
            if (!_wallet.CancelPendingOpenOrder(order)) { //--- Attempt to cancel order
               HandleErrors("Failed to cancel an order (because it couldn't open). Please see the Journal and Expert tab in Metatrader for more information."); //--- Log error
            }
         }
      }
      return ordersOpenSuccessCount == ordersToOpenCount; //--- Return true if all successful
   }
};

在此,我们通过定义交易行为和EA的主逻辑来实现核心架构的搭建。我们创建了 “TradeAction” 枚举来对交易操作进行分类,其中包括 “UnknownAction” (0)、“OpenBuyAction” (1)、“OpenSellAction” (2)、“CloseBuyAction” (3) 和 “CloseSellAction” (4),这为交易管理提供了一个清晰的框架。我们稍后会用到它。接着,我们定义了 “ITrader” 接口,其中包含 “HandleTick”、“Init” 和 “GetWallet” 方法,以规范 EA 的功能,并声明了一个 “ITrader” 类型的全局指针 “_ea”,用于存储 EA 实例。

我们实现了继承自 “ITrader” 的 “EA” 类,其私有变量包括用于跟踪初始 tick 的 “_firstTick” 和用于管理 “Wallet” 实例的 “_wallet”。“EA” 构造函数将 “_firstTick” 初始化为 true,创建一个新的 “Wallet” 实例,并通过 “SetLastClosedOrdersByTimeframe” 方法结合 “DisplayOrderDuringTimeframe” 参数来设置其时间框架。“~EA” 析构函数负责清理 “_wallet”。“Init” 方法利用 MQLInfoInteger 设置 “IsDemoLiveOrVisualMode”,通过 “MarketInfo_LibFunc” 赋值 “UnitsOneLot”,并调用 “_wallet.LoadOrdersFromBroker”。

“HandleTick” 方法通过以下步骤管理 tick:调用 “SyncOrders”(测试器模式下除外);若 “AllowManualTPSLChanges” 为 true,则调用 “SyncManualTPSLChanges”;更新 “AskFunc” 和 “BidFunc”;以及调用 “UpdateOrders” 和 “_wallet.HandleTick”。除非 “StopEA” 为 true,否则它会执行 “ExecutePendingCloseOrders” 和 “ExecutePendingOpenOrders”,并在需要时通过 “HandleErrors” 记录错误,最后清除 “_firstTick” 标志。

私有方法包括:使用 “OrderRepository::GetOpenOrders” 同步订单的 “SyncOrders”;通过 ObjectFindObjectGetDouble 更新手动止盈/止损的 “SyncManualTPSLChanges”;利用 “CalculateProfitPips” 刷新盈利指标的 “UpdateOrders”;使用 “OrderRepository::ClosePosition” 或针对净头寸使用 “OpenOrder” 来平仓的 “ExecutePendingCloseOrders”;以及通过 “OrderRepository::OpenOrder” 开仓的 “ExecutePendingOpenOrders”,同时确保通过 “MQL5InfoInteger” 检查交易上下文并处理取消操作。现在,我们可以在 OnInit 事件处理函数中调用它了。

//--- Set up chart appearance
void SetupChart() {
   ChartSetInteger(ChartID(), CHART_FOREGROUND, 0, false); //--- Set chart foreground to background
}

//--- Initialize Expert Advisor
int OnInit() {
   ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite); //--- Set chart background to white
   ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrRed); //--- Set bearish candles to red
   ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrGreen); //--- Set bullish candles to green
   ChartSetInteger(0, CHART_COLOR_ASK, clrDarkRed); //--- Set Ask line to dark red
   ChartSetInteger(0, CHART_COLOR_BID, clrDarkGreen); //--- Set Bid line to dark green
   ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrRed); //--- Set downward movement to red
   ChartSetInteger(0, CHART_COLOR_CHART_UP, clrGreen); //--- Set upward movement to green
   ChartSetInteger(0, CHART_COLOR_GRID, clrLightGray); //--- Set grid to light gray
   ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack); //--- Set axis and text to black
   ChartSetInteger(0, CHART_COLOR_LAST, clrBlack); //--- Set last price line to black
   OrderFillingType = GetFillingType();                //--- Retrieve order filling type
   if ((int)OrderFillingType == -1) {                  //--- Check if invalid
      HandleErrors("Unsupported filling type " + IntegerToString((int)OrderFillingType)); //--- Log error
      return (INIT_FAILED);                            //--- Return failure
   }
   GetExecutionType();                                 //--- Retrieve execution type
   AccountMarginMode = GetAccountMarginMode();         //--- Retrieve margin mode
   SetPipPoint();                                      //--- Set pip point
   if (PipPoint == 0) {                                //--- Check if invalid
      HandleErrors("Couldn't find correct pip/point for symbol."); //--- Log error
      return (INIT_FAILED);                            //--- Return failure
   }
   AskFunc = new AskFunction();                        //--- Create Ask function
   AskFunc.Init();                                     //--- Initialize Ask function
   BidFunc = new BidFunction();                        //--- Create Bid function
   BidFunc.Init();                                     //--- Initialize Bid function
   OrderInfoComment = "";                              //--- Initialize order comment
   _ea = new EA();                                     //--- Create EA instance
   _ea.Init();                                         //--- Initialize EA
   SetupChart();                                       //--- Set up chart
   hd_iMA_SMA8 = iMA(NULL, PERIOD_M30, iMA_SMA8_ma_period, iMA_SMA8_ma_shift, MODE_SMA, PRICE_CLOSE); //--- Initialize 8-period SMA
   if (hd_iMA_SMA8 < 0) {                              //--- Check if failed
      HandleErrors(StringFormat("Could not find indicator 'iMA'. Error: %d", GetLastError())); //--- Log error
      return -1;                                       //--- Return failure
   }
   hd_iMA_EMA200 = iMA(NULL, PERIOD_M1, iMA_EMA200_ma_period, iMA_EMA200_ma_shift, MODE_EMA, PRICE_CLOSE); //--- Initialize 200-period EMA
   if (hd_iMA_EMA200 < 0) {                            //--- Check if failed
      HandleErrors(StringFormat("Could not find indicator 'iMA'. Error: %d", GetLastError())); //--- Log error
      return -1;                                       //--- Return failure
   }
   hd_iRSI_RSI = iRSI(NULL, PERIOD_M1, iRSI_RSI_ma_period, PRICE_CLOSE); //--- Initialize RSI
   if (hd_iRSI_RSI < 0) {                              //--- Check if failed
      HandleErrors(StringFormat("Could not find indicator 'iRSI'. Error: %d", GetLastError())); //--- Log error
      return -1;                                       //--- Return failure
   }
   hd_iEnvelopes_ENV_LOW = iEnvelopes(NULL, PERIOD_M1, iEnvelopes_ENV_LOW_ma_period, iEnvelopes_ENV_LOW_ma_shift, MODE_SMA, PRICE_CLOSE, iEnvelopes_ENV_LOW_deviation); //--- Initialize lower Envelopes
   if (hd_iEnvelopes_ENV_LOW < 0) {                    //--- Check if failed
      HandleErrors(StringFormat("Could not find indicator 'iEnvelopes'. Error: %d", GetLastError())); //--- Log error
      return -1;                                       //--- Return failure
   }
   hd_iEnvelopes_ENV_UPPER = iEnvelopes(NULL, PERIOD_M1, iEnvelopes_ENV_UPPER_ma_period, iEnvelopes_ENV_UPPER_ma_shift, MODE_SMA, PRICE_CLOSE, iEnvelopes_ENV_UPPER_deviation); //--- Initialize upper Envelopes
   if (hd_iEnvelopes_ENV_UPPER < 0) {                  //--- Check if failed
      HandleErrors(StringFormat("Could not find indicator 'iEnvelopes'. Error: %d", GetLastError())); //--- Log error
      return -1;                                       //--- Return failure
   }
   hd_iMA_SMA_4 = iMA(NULL, PERIOD_M30, iMA_SMA_4_ma_period, iMA_SMA_4_ma_shift, MODE_SMA, PRICE_CLOSE); //--- Initialize 4-period SMA
   if (hd_iMA_SMA_4 < 0) {                             //--- Check if failed
      HandleErrors(StringFormat("Could not find indicator 'iMA'. Error: %d", GetLastError())); //--- Log error
      return -1;                                       //--- Return failure
   }
   return (INIT_SUCCEEDED);                            //--- Return success
}



//--- Handle errors
void HandleErrors(string errorMessage) {
   Print(errorMessage);                                //--- Log error
   if (Error != NULL || errorMessage == ErrorPreviousQuote) { //--- Check existing or repeated error
      return;                                          //--- Exit
   }
   if (AlertOnError) Alert(errorMessage);              //--- Trigger alert if enabled
   if (NotificationOnError) SendNotification(StringFormat("Error by EA (%d) %s", MagicNumber, errorMessage)); //--- Send notification if enabled
   if (EmailOnError) SendMail(StringFormat("Error by EA (%d)", MagicNumber), errorMessage); //--- Send email if enabled
   Error = errorMessage;                               //--- Set current error
   ErrorPreviousQuote = Error;                         //--- Set previous error
}

初始化时,我们定义了 “SetupChart” 函数,通过 ChartSetInteger 函数将 CHART_FOREGROUND 设置为 false,以确保图表背景优先显示,从而提升图像的清晰度。我们实现了 OnInit 函数来初始化 EA,首先通过 “ChartSetInteger” 自定义图表颜色:将 “CHART_COLOR_BACKGROUND” 设为白色、CHART_COLOR_CANDLE_BEAR 设为红色、将 “CHART_COLOR_CANDLE_BULL” 设为绿色、将 “CHART_COLOR_ASK” 设为深红色、将 “CHART_COLOR_BID” 设为深绿色等,以进行区分。

我们调用 “GetFillingType” 来设置 “OrderFillingType”,若无效则返回 INIT_FAILED;同时调用 “GetExecutionType” 和 “GetAccountMarginMode” 来配置交易模式。“SetPipPoint” 函数用于设置 “PipPoint”,并进行失败检查;然后我们实例化 “AskFunc” 和 “BidFunc” 为 “AskFunction” 和 “BidFunction” 对象,并调用它们的 “Init” 方法。

我们创建 “EA” 类的实例 “_ea”,用 “Init” 方法初始化,并调用 “SetupChart”。我们使用 “iMA” 函数初始化指标句柄:“hd_iMA_SMA8”(M30,14周期简单移动平均线)、“hd_iMA_EMA200”(M1,200周期指数移动平均线)、“hd_iMA_SMA_4”(M30,9周期简单移动平均线);使用 “iRSI” 初始化 “hd_iRSI_RSI”(M1,8周期);使用 “iEnvelopes” 初始化 “hd_iEnvelopes_ENV_LOW”(M1,95周期,1.4%偏差)和 “hd_iEnvelopes_ENV_UPPER”(M1,150周期,0.1%偏差)。若失败则返回 -1 并调用 “HandleErrors”。

我们定义了 “HandleErrors” 函数,通过 Print 记录错误,使用 “Error” 和 “ErrorPreviousQuote” 跳过重复错误,并根据 “AlertOnError”、“NotificationOnError” 和 “EmailOnError” 的设置,通过 “Alert”、“SendNotification” 或 “SendMail” 发送通知,然后更新 “Error” 和 “ErrorPreviousQuote”。此设置确保程序已准备好处理核心逻辑。当我们运行程序时,会得到以下输入结果。

输入参数部分

从图中可以看出,用户可以设置输入参数。当我们运行程序时,会得到以下结果。

初始化确认

从图中可以看出,我们已成功初始化程序,已准备好接受订单。至此,我们已经定义了初始化程序的核心基础架构。剩下的就是回测程序以确保其正确启动,这部分将在下一节讨论。


回测

我们将其编译成一个 GIF 文件,如下所示,以展示程序的初始化情况。

回测


结论

综上所述,我们已在MQL5中成功为自动化包络线趋势反弹剥头皮策略打下了坚实基础,构建了一个稳定可靠的智能交易系统架构和信号生成框架。我们配置了基本组件,包括指标初始化、订单管理类和错误处理,以支持精确的剥头皮操作。这为后续实现交易执行和动态管理打好了基础,使我们离一个完全自动化的交易程序更近了一步。敬请期待。

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

交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
开发多币种 EA 交易(第 24 部分):添加新策略(二) 开发多币种 EA 交易(第 24 部分):添加新策略(二)
在本文中,我们将继续将新策略与创建的自动优化系统联系起来。让我们看看需要对优化项目创建 EA 以及第二和第三阶段 EA 进行哪些更改。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
MQL5交易工具(第三部分):构建用于策略交易的多时间周期扫描仪表盘 MQL5交易工具(第三部分):构建用于策略交易的多时间周期扫描仪表盘
在本文中,我们将使用MQL5构建一个多时间周期扫描仪表盘,用于展示实时交易信号。我们设计了一个交互式网格界面,利用多种指标实现信号计算,并添加了关闭按钮。文章结尾将介绍回测结果以及该仪表盘在策略交易中的优势。