MQL5自动化交易策略(第十八部分):基于包络线趋势反弹的剥头皮交易——核心架构与信号生成(1)
引言
在上一篇文章(第十七部分)中,我们已经实现了网格马丁剥头皮策略的自动化,并添加了动态看板功能,以便实时监控交易情况。在第十八部分,我们将在MetaQuotes Language 5(MQL5) 中着手开发包络线趋势反弹剥头皮策略的自动化系统,构建EA的核心架构并实现信号生成逻辑。本文将包括几个方面:
最终,您将为趋势反弹剥头皮策略打下坚实的基础,并为下一部分的交易执行做好准备——让我们开始吧!
理解策略
包络线趋势反弹剥头皮策略采用包络线指标,该指标在移动平均线的基础上上下设定一定偏差(如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),用于获取买/卖价、点差和手数等市场数据。同时,我们还定义了字符串转换宏,例如 CharToString 和 DoubleToString,以简化数据类型转换操作。
接着,我们创建了“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” 函数利用 MqlTradeRequest 和 MqlTradeResult 发送交易请求,设置 “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”;通过 ObjectFind 和 ObjectGetDouble 更新手动止盈/止损的 “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
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
新手在交易中的10个基本错误
MQL5交易工具(第三部分):构建用于策略交易的多时间周期扫描仪表盘