
MQL5 Cookbook - 以 MQL5 编写的多币种 EA,利用限价订单工作
介绍
这次,我们将要创建一款多币种 EA,交易算法基于限价订单 Buy Stop(高买) 和 Sell Stop(低卖)。我们打算创建的范式,将被设计为日内交易/测试。本文讨论下列事项:
- 在规定时间范围内进行交易。让我们来创建一个功能,可以设置交易的起止时间。例如,该时间可以是欧洲或美国的交易时段。这样可以确保在优化 EA 参数时,有机会发现更合适的时间范围。
- 布置/修改/删除限价订单。
- 处理交易事件: 检查最后一个持仓是否在止盈或止损位置平仓,以及在成交历史中控制每个品种。
开发 EA
我们打算利用来自文章 MQL5 Cookbook: 多币种EA交易 - 简洁而快速的途径 的代码作为模板。虽然该范式的基本结构将维持不变,但也会引入一些明显的变化。该 EA 将会被设计为日内交易,然而,这种模式可以在必要时切断。当新柱线事件出现时,如果仓位已平,则限价订单将会被即刻布置。
让我们从 EA 的外部参数开始。首先,我们在包含文件 Enums.mqh 中创建一个枚举 ENUM_HOURS。在枚举中标识符的数量等于一天中的小时数:
//--- Hours Enumeration enum ENUM_HOURS { h00 = 0, // 00 : 00 h01 = 1, // 01 : 00 h02 = 2, // 02 : 00 h03 = 3, // 03 : 00 h04 = 4, // 04 : 00 h05 = 5, // 05 : 00 h06 = 6, // 06 : 00 h07 = 7, // 07 : 00 h08 = 8, // 08 : 00 h09 = 9, // 09 : 00 h10 = 10, // 10 : 00 h11 = 11, // 11 : 00 h12 = 12, // 12 : 00 h13 = 13, // 13 : 00 h14 = 14, // 14 : 00 h15 = 15, // 15 : 00 h16 = 16, // 16 : 00 h17 = 17, // 17 : 00 h18 = 18, // 18 : 00 h19 = 19, // 19 : 00 h20 = 20, // 20 : 00 h21 = 21, // 21 : 00 h22 = 22, // 22 : 00 h23 = 23 // 23 : 00 };
之后,在外部参数列表中我们将创建四个与交易时间范围相关的参数:
- TradeInTimeRange - 启用/禁止该模式。正如已经提到的那样,我们即将制作的 EA,不仅可以在确定的时间范围内工作,也可以不间断工作,即连续模式。
- StartTrade - 交易时段的开始小时。若 TradeInTimeRange 模式为开,一旦服务器时间等于该值时,EA 将布置限价订单。
- StopOpenOrders - 布置订单的结束时间。当服务器时间等于该数值时,即使仓位已平,EA 都将停止布置限价订单。
- EndTrade - 交易时段停止的小时。一旦服务器时间等于该数值,EA 停止交易。指定品种的所有持仓都将被平仓,并且所有挂单被删除。
外部参数的列表如下所示。给出的例程用于两个品种。在参数 PendingOrder 中我们设置了一个距当前价位的点数。
//--- External parameters of the Expert Advisor sinput long MagicNumber = 777; // Magic number sinput int Deviation = 10; // Slippage //--- sinput string delimeter_00=""; // -------------------------------- sinput string Symbol_01 ="EURUSD"; // Symbol 1 input bool TradeInTimeRange_01 =true; // | Trading in a time range input ENUM_HOURS StartTrade_01 = h10; // | The hour of the beginning of a trading session input ENUM_HOURS StopOpenOrders_01 = h17; // | The hour of the end of placing orders input ENUM_HOURS EndTrade_01 = h22; // | The hour of the end of a trading session input double PendingOrder_01 = 50; // | Pending order input double TakeProfit_01 = 100; // | Take Profit input double StopLoss_01 = 50; // | Stop Loss input double TrailingStop_01 = 10; // | Trailing Stop input bool Reverse_01 = true; // | Position reversal input double Lot_01 = 0.1; // | Lot //--- sinput string delimeter_01=""; // -------------------------------- sinput string Symbol_02 ="AUDUSD"; // Symbol 2 input bool TradeInTimeRange_02 =true; // | Trading in a time range input ENUM_HOURS StartTrade_02 = h10; // | The hour of the beginning of a trading session input ENUM_HOURS StopOpenOrders_02 = h17; // | The hour of the end of placing orders input ENUM_HOURS EndTrade_02 = h22; // | The hour of the end of a trading session input double PendingOrder_02 = 50; // | Pending order input double TakeProfit_02 = 100; // | Take Profit input double StopLoss_02 = 50; // | Stop Loss input double TrailingStop_02 = 10; // | Trailing Stop input bool Reverse_02 = true; // | Position reversal input double Lot_02 = 0.1; // | Lot
而且相应变化也在数组列表中进行,即填充外部参数的数值:
//--- Arrays for storing external parameters string Symbols[NUMBER_OF_SYMBOLS]; // Symbol bool TradeInTimeRange[NUMBER_OF_SYMBOLS]; // Trading in a time range ENUM_HOURS StartTrade[NUMBER_OF_SYMBOLS]; // The hour of the beginning of a trading session ENUM_HOURS StopOpenOrders[NUMBER_OF_SYMBOLS]; // The hour of the end of placing orders ENUM_HOURS EndTrade[NUMBER_OF_SYMBOLS]; // The hour of the end of a trading session double PendingOrder[NUMBER_OF_SYMBOLS]; // Pending order double TakeProfit[NUMBER_OF_SYMBOLS]; // Take Profit double StopLoss[NUMBER_OF_SYMBOLS]; // Stop Loss double TrailingStop[NUMBER_OF_SYMBOLS]; // Trailing Stop bool Reverse[NUMBER_OF_SYMBOLS]; // Position Reversal double Lot[NUMBER_OF_SYMBOLS]; // Lot
现在我们打算安排为反向模式 (即 Reverse 参数值为 true),当挂单被触发,反向挂单将被删除,并且布置新的。我们不能像改变挂单的价位(价格,止损,止盈)那样来修改它的手数。因此,我们必须将其删除,并在新的挂单中使用所期望的手数。
此外,如果反向模式被启用,且在同一时间设置追踪止损位,则挂单将追随价格。在此基础之上,如果设置了止损位,则将基于挂单计算它的价值。
让我们在全局范围创建两个字符串变量用于挂单注释:
//--- Pending order comments string comment_top_order ="top_order"; string comment_bottom_order ="bottom_order";
在 EA 加载期间,函数 OnInit() 进行初始化,我们将检查外部参数正确与否。评估标准如下。当 TradeInTimeRange 模式启用,交易时段的开始小时不能小于布置限价订单的结束小时。布置限价订单的结束小时,反之,也不能小于交易时段的结束小时。让我们来编写函数 CheckInputParameters() 进行这样的检查:
//+------------------------------------------------------------------+ //| Checks external parameters | //+------------------------------------------------------------------+ bool CheckInputParameters() { //--- Loop through the specified symbols for(int s=0; s<NUMBER_OF_SYMBOLS; s++) { //--- If there is no symbol and the TradeInTimeRange mode is disabled, move on to the following symbol. if(Symbols[s]=="" || !TradeInTimeRange[s]) continue; //--- Check the accuracy of the start and the end of a trade session time if(StartTrade[s]>=EndTrade[s]) { Print(Symbols[s], ": The hour of the beginning of a trade session("+IntegerToString(StartTrade[s])+") " "must be less than the hour of the end of a trade session"("+IntegerToString(EndTrade[s])+")!"); return(false); } //--- A trading session is to start no later that one hour before the hour of placing pending orders. // Pending orders are to be placed no later than one hour before the hour of the end of a trading session. if(StopOpenOrders[s]>=EndTrade[s] || StopOpenOrders[s]<=StartTrade[s]) { Print(Symbols[s], ": The hour of the end of placing orders ("+IntegerToString(StopOpenOrders[s])+") " "is to be less than the hour of the end ("+IntegerToString(EndTrade[s])+") and " "greater than the hour of the beginning of a trading session ("+IntegerToString(StartTrade[s])+")!"); return(false); } } //--- Parameters are correct return(true); }
为了实现这个范式,我们需要函数检查是否处于交易和布置挂单的指定时间范围。我们将命名这些函数 IsInTradeTimeRange() 和 IsInOpenOrdersTimeRange()。它们的工作都相同,唯一的区别是检查范围的上限。再往前,我们会看到这些函数将被用于何处。
//+------------------------------------------------------------------+ //| Checks if we are within the time range for trade | //+------------------------------------------------------------------+ bool IsInTradeTimeRange(int symbol_number) { //--- If TradeInTimeRange mode is enabled if(TradeInTimeRange[symbol_number]) { //--- Structure of the date and time MqlDateTime last_date; //--- Get the last value of the date and time data set TimeTradeServer(last_date); //--- Outside of the allowed time range if(last_date.hour<StartTrade[symbol_number] || last_date.hour>=EndTrade[symbol_number]) return(false); } //--- Within the allowed time range return(true); } //+------------------------------------------------------------------+ //| Checks if we are within the time range for placing orders | //+------------------------------------------------------------------+ bool IsInOpenOrdersTimeRange(int symbol_number) { //--- If the TradeInTimeRange mode if enabled if(TradeInTimeRange[symbol_number]) { //--- Structure of the date and time MqlDateTime last_date; //--- Get the last value of the date and time data set TimeTradeServer(last_date); //--- Outside the allowed time range if(last_date.hour<StartTrade[symbol_number] || last_date.hour>=StopOpenOrders[symbol_number]) return(false); } //--- Within the allowed time range return(true); }
前面的文章中已经分析了用于接收仓位,品种和成交历史的函数。在本文中我们需要一个类似的函数用于获取限价订单的属性。在包含文件 Enums.mqh 中我们打算创建一个带有限价订单属性的枚举:
//--- Enumeration of the properties of a pending order enum ENUM_ORDER_PROPERTIES { O_SYMBOL = 0, O_MAGIC = 1, O_COMMENT = 2, O_PRICE_OPEN = 3, O_PRICE_CURRENT = 4, O_PRICE_STOPLIMIT = 5, O_VOLUME_INITIAL = 6, O_VOLUME_CURRENT = 7, O_SL = 8, O_TP = 9, O_TIME_SETUP = 10, O_TIME_EXPIRATION = 11, O_TIME_SETUP_MSC = 12, O_TYPE_TIME = 13, O_TYPE = 14, O_ALL = 15 };
那么在包含文件 TradeFunctions.mqh 中我们需要编写一个带有限价订单属性的结构,并安装它:
//-- Properties of a pending order struct pending_order_properties { string symbol; // Symbol long magic; // Magic number string comment; // Comment double price_open; // Price specified in the order double price_current; // Current price of the order symbol double price_stoplimit; // Limit order price for the Stop Limit order double volume_initial; // Initial order volume double volume_current; // Current order volume double sl; // Stop Loss level double tp; // Take Profit level datetime time_setup; // Order placement time datetime time_expiration; // Order expiration time datetime time_setup_msc; // The time of placing an order for execution in milliseconds since 01.01.1970 datetime type_time; // Order lifetime ENUM_ORDER_TYPE type; // Position type }; //--- Variable of the order features pending_order_properties ord;
为了得到一个甚或所有挂单的属性,我们要编写一个函数 GetPendingOrderProperties()。挂单被选中之后,我们可以利用这个功能来检索订单的属性。完成这点的方法将在下面描述。
//+------------------------------------------------------------------+ //| Retrieves the properties of the previously selected pending order| //+------------------------------------------------------------------+ void GetPendingOrderProperties(ENUM_ORDER_PROPERTIES order_property) { switch(order_property) { case O_SYMBOL : ord.symbol=OrderGetString(ORDER_SYMBOL); break; case O_MAGIC : ord.magic=OrderGetInteger(ORDER_MAGIC); break; case O_COMMENT : ord.comment=OrderGetString(ORDER_COMMENT); break; case O_PRICE_OPEN : ord.price_open=OrderGetDouble(ORDER_PRICE_OPEN); break; case O_PRICE_CURRENT : ord.price_current=OrderGetDouble(ORDER_PRICE_CURRENT); break; case O_PRICE_STOPLIMIT : ord.price_stoplimit=OrderGetDouble(ORDER_PRICE_STOPLIMIT); break; case O_VOLUME_INITIAL : ord.volume_initial=OrderGetDouble(ORDER_VOLUME_INITIAL); break; case O_VOLUME_CURRENT : ord.volume_current=OrderGetDouble(ORDER_VOLUME_CURRENT); break; case O_SL : ord.sl=OrderGetDouble(ORDER_SL); break; case O_TP : ord.tp=OrderGetDouble(ORDER_TP); break; case O_TIME_SETUP : ord.time_setup=(datetime)OrderGetInteger(ORDER_TIME_SETUP); break; case O_TIME_EXPIRATION : ord.time_expiration=(datetime)OrderGetInteger(ORDER_TIME_EXPIRATION); break; case O_TIME_SETUP_MSC : ord.time_setup_msc=(datetime)OrderGetInteger(ORDER_TIME_SETUP_MSC); break; case O_TYPE_TIME : ord.type_time=(datetime)OrderGetInteger(ORDER_TYPE_TIME); break; case O_TYPE : ord.type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); break; case O_ALL : ord.symbol=OrderGetString(ORDER_SYMBOL); ord.magic=OrderGetInteger(ORDER_MAGIC); ord.comment=OrderGetString(ORDER_COMMENT); ord.price_open=OrderGetDouble(ORDER_PRICE_OPEN); ord.price_current=OrderGetDouble(ORDER_PRICE_CURRENT); ord.price_stoplimit=OrderGetDouble(ORDER_PRICE_STOPLIMIT); ord.volume_initial=OrderGetDouble(ORDER_VOLUME_INITIAL); ord.volume_current=OrderGetDouble(ORDER_VOLUME_CURRENT); ord.sl=OrderGetDouble(ORDER_SL); ord.tp=OrderGetDouble(ORDER_TP); ord.time_setup=(datetime)OrderGetInteger(ORDER_TIME_SETUP); ord.time_expiration=(datetime)OrderGetInteger(ORDER_TIME_EXPIRATION); ord.time_setup_msc=(datetime)OrderGetInteger(ORDER_TIME_SETUP_MSC); ord.type_time=(datetime)OrderGetInteger(ORDER_TYPE_TIME); ord.type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); break; //--- default: Print("Retrieved feature of the pending order was not taken into account in the enumeration "); return; } }
现在,我们将要编写基本的布置,修改和删除挂单函数。函数 SetPendingOrder() 布置一个限价订单。如果限价订单布置失败,函数在日志中加入一条,并有错误代码和描述:
//+------------------------------------------------------------------+ //| Places a pending order | //+------------------------------------------------------------------+ void SetPendingOrder(int symbol_number, // Symbol number ENUM_ORDER_TYPE order_type, // Order type double lot, // Volume double stoplimit_price, // Level of the StopLimit order double price, // Price double sl, // Stop Loss double tp, // Take Profit ENUM_ORDER_TYPE_TIME type_time, // Order Expiration string comment) // Comment //--- Set magic number in the trade structure trade.SetExpertMagicNumber(MagicNumber); //--- If a pending order failed to be placed, print an error message if(!trade.OrderOpen(Symbols[symbol_number], order_type,lot,stoplimit_price,price,sl,tp,type_time,0,comment)) Print("Error when placing a pending order: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
函数 ModifyPendingOrder() 修改一个限价订单。我们这样安排,不仅能修改订单价格,而且可以修改手数,还能将它作为最后传递给函数的参数。如果传递的手数值大于零,则意味着挂单已被删除,并布置一个新的所需手数的订单。在所有其余情况下,我们只需简单地修改已存在订单的价位。
//+------------------------------------------------------------------+ //| Modifies a pending order | //+------------------------------------------------------------------+ void ModifyPendingOrder(int symbol_number, //Symbol number ulong ticket, // Order ticket ENUM_ORDER_TYPE type, // Order type double price, // Order price double sl, // Stop Loss of the order double tp, // Take Profit of the order ENUM_ORDER_TYPE_TIME type_time, // Order expiration datetime time_expiration, // Order expiration time double stoplimit_price, // Price string comment, // Comment double volume) // Volume { //--- If the passed volume value is non-zero, delete the order and place it again if(volume>0) { //--- If the order failed to be deleted, exit if(!DeletePendingOrder(ticket)) return; //--- Place a pending order SetPendingOrder(symbol_number,type,volume,0,price,sl,tp,type_time,comment); //--- Adjust Stop Loss positions as related to the order CorrectStopLossByOrder(symbol_number,price,type); } //--- If the passed volume value is zero, modify the order else { //--- If the pending order failed to be modified, print a relevant message if(!trade.OrderModify(ticket,price,sl,tp,type_time,time_expiration,stoplimit_price)) Print("Error when modifying the pending order price: ", GetLastError()," - ",ErrorDescription(GetLastError())); //--- Otherwise adjust Stop Loss positions as related to the order else CorrectStopLossByOrder(symbol_number,price,type); } }
在上面的代码中突显了两个新函数 DeletePendingOrder() 和 CorrectStopLossByOrder()。第一个是删除挂单,第二个是调整与挂单相关的仓位的止损和止盈。
//+------------------------------------------------------------------+ //| Deletes a pending order | //+------------------------------------------------------------------+ bool DeletePendingOrder(ulong ticket) { //--- If a pending order failed to get deleted, print a relevant message if(!trade.OrderDelete(ticket)) { Print("Error when deleting a pending order: ",GetLastError()," - ",ErrorDescription(GetLastError())); return(false); } //--- return(true); } //+------------------------------------------------------------------+ //| Modifies StopLoss of the position as related to the pending order| //+------------------------------------------------------------------+ void CorrectStopLossByOrder(int symbol_number, // Symbol number double price, // Order Price ENUM_ORDER_TYPE type) // Order Type { //--- If Stop Loss disabled, exit if(StopLoss[symbol_number]==0) return; //--- If Stop Loss enabled double new_sl=0.0; // New Stop Loss value //--- Get a Point value GetSymbolProperties(symbol_number,S_POINT); //--- Number of decimal places GetSymbolProperties(symbol_number,S_DIGITS); //--- Get Take Profit positions GetPositionProperties(symbol_number,P_TP); //--- Calculate as related to the order type switch(type) { case ORDER_TYPE_BUY_STOP : new_sl=NormalizeDouble(price+CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits); break; case ORDER_TYPE_SELL_STOP : new_sl=NormalizeDouble(price-CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits); break; } //--- Modify the position if(!trade.PositionModify(Symbols[symbol_number],new_sl,pos.tp)) Print("Error when modifying position: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
在布置限价订单之前,有必要检查一下是否存在相同注释的挂单。如同本文开始提到的那样,我们将布置一个注释为 "top_order" 的 Buy Stop 订单和一个注释为 "bottom_order" 的 Sell Stop 订单。为了方便检查让我们写一个名为 CheckPendingOrderByComment() 的函数:
//+------------------------------------------------------------------+ //| Checks existence of a pending order by a comment | //+------------------------------------------------------------------+ bool CheckPendingOrderByComment(int symbol_number,string comment) { int total_orders =0; // Total number of pending orders string order_symbol =""; // Order Symbol string order_comment =""; // Order Comment //--- Get the total number of pending orders total_orders=OrdersTotal(); //--- Loop through the total orders for(int i=total_orders-1; i>=0; i--) { //---Select the order by the ticket if(OrderGetTicket(i)>0) { //--- Get the symbol name order_symbol=OrderGetString(ORDER_SYMBOL); //--- If the symbols are equal if(order_symbol==Symbols[symbol_number]) { //--- Get the order comment order_comment=OrderGetString(ORDER_COMMENT); //--- If the comments are equal if(order_comment==comment) return(true); } } } //--- Order with a specified comment not found return(false); }
以上代码显示订单总计数量,可以使用系统函数 OrdersTotal() 获得。但是,要获得指定品种的挂单总数,我们要编写一个用户自定义的函数。我们将它命名为 OrdersTotalBySymbol():
//+------------------------------------------------------------------+ //| Returns the total number of orders for the specified symbol | //+------------------------------------------------------------------+ int OrdersTotalBySymbol(string symbol) { int count =0; // Order counter int total_orders =0; // Total number of pending orders //--- Get the total number of pending orders total_orders=OrdersTotal(); //--- Loop through the total number of orders for(int i=total_orders-1; i>=0; i--) { //--- If an order has been selected if(OrderGetTicket(i)>0) { //--- Get the order symbol GetOrderProperties(O_SYMBOL); //--- If the order symbol and the specified symbol are equal if(ord.symbol==symbol) //--- Increase the counter count++; } } //--- Return the total number of orders return(count); }
在布置限价订单之前,有必要计算一下是否需要止损和止盈位。如果反向模式启用,我们需要单独的用户自定义函数来重新计算和修改追随止损位。
为了计算限价订单价格,让我们来编写函数 CalculatePendingOrder():
//+------------------------------------------------------------------+ //| Calculates the pending order level(price) | //+------------------------------------------------------------------+ double CalculatePendingOrder(int symbol_number,ENUM_ORDER_TYPE order_type) { //--- For the calculated pending order value double price=0.0; //--- If the value for SELL STOP order is to be calculated if(order_type==ORDER_TYPE_SELL_STOP) { //--- Calculate level price=NormalizeDouble(symb.bid-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- Return calculated value if it is less than the lower limit of Stops level // If the value is equal or greater, return the adjusted value return(price<symb.down_level ? price : symb.down_level-symb.offset); } //--- If the value for BUY STOP order is to be calculated if(order_type==ORDER_TYPE_BUY_STOP) { //--- Calculate level price=NormalizeDouble(symb.ask+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- Return the calculated value if it is greater than the upper limit of Stops level // If the value is equal or less, return the adjusted value return(price>symb.up_level ? price : symb.up_level+symb.offset); } //--- return(0.0); }
以下函数代码计算挂单的止损和止盈位。
//+------------------------------------------------------------------+ //| Calculates Stop Loss level for a pending order | //+------------------------------------------------------------------+ double CalculatePendingOrderStopLoss(int symbol_number,ENUM_ORDER_TYPE order_type,double price) { //--- If Stop Loss is required if(StopLoss[symbol_number]>0) { double sl =0.0; // For the Stop Loss calculated value double up_level =0.0; // Upper limit of Stop Levels double down_level =0.0; // Lower limit of Stop Levels //--- If the value for BUY STOP order is to be calculated if(order_type==ORDER_TYPE_BUY_STOP) { //--- Define lower threshold down_level=NormalizeDouble(price-symb.stops_level*symb.point,symb.digits); //--- Calculate level sl=NormalizeDouble(price-CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits); //--- Return the calculated value if it is less than the lower limit of Stop level // If the value is equal or greater, return the adjusted value return(sl<down_level ? sl : NormalizeDouble(down_level-symb.offset,symb.digits)); } //--- If the value for the SELL STOP order is to be calculated if(order_type==ORDER_TYPE_SELL_STOP) { //--- Define the upper threshold up_level=NormalizeDouble(price+symb.stops_level*symb.point,symb.digits); //--- Calculate the level sl=NormalizeDouble(price+CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits); //--- Return the calculated value if it is greater than the upper limit of the Stops level // If the value is less or equal, return the adjusted value. return(sl>up_level ? sl : NormalizeDouble(up_level+symb.offset,symb.digits)); } } //--- return(0.0); } //+------------------------------------------------------------------+ //| Calculates the Take Profit level for a pending order | //+------------------------------------------------------------------+ double CalculatePendingOrderTakeProfit(int symbol_number,ENUM_ORDER_TYPE order_type,double price) { //--- If Take Profit is required if(TakeProfit[symbol_number]>0) { double tp =0.0; // For the calculated Take Profit value double up_level =0.0; // Upper limit of Stop Levels double down_level =0.0; // Lower limit of Stop Levels //--- If the value for SELL STOP order is to be calculated if(order_type==ORDER_TYPE_SELL_STOP) { //--- Define lower threshold down_level=NormalizeDouble(price-symb.stops_level*symb.point,symb.digits); //--- Calculate the level tp=NormalizeDouble(price-CorrectValueBySymbolDigits(TakeProfit[symbol_number]*symb.point),symb.digits); //--- Return the calculated value if it is less than the below limit of the Stops level // If the value is greater or equal, return the adjusted value return(tp<down_level ? tp : NormalizeDouble(down_level-symb.offset,symb.digits)); } //--- If the value for the BUY STOP order is to be calculated if(order_type==ORDER_TYPE_BUY_STOP) { //--- Define the upper threshold up_level=NormalizeDouble(price+symb.stops_level*symb.point,symb.digits); //--- Calculate the level tp=NormalizeDouble(price+CorrectValueBySymbolDigits(TakeProfit[symbol_number]*symb.point),symb.digits); //--- Return the calculated value if it is greater than the upper limit of the Stops level // If the value is less or equal, return the adjusted value return(tp>up_level ? tp : NormalizeDouble(up_level+symb.offset,symb.digits)); } } //--- return(0.0); }
为了计算一个反向挂单的停止位 (价格),我们要编写以下函数 CalculateReverseOrderTrailingStop() 和 ModifyPendingOrderTrailingStop()。您可以在下面找到函数代码。
函数代码 CalculateReverseOrderTrailingStop():
//+----------------------------------------------------------------------------+ //| Calculates the Trailing Stop level for the reversed order | //+----------------------------------------------------------------------------+ double CalculateReverseOrderTrailingStop(int symbol_number,ENUM_POSITION_TYPE position_type) { //--- Variables for calculation double level =0.0; double buy_point =low[symbol_number].value[1]; // Low value for Buy double sell_point =high[symbol_number].value[1]; // High value for Sell //--- Calculate the level for the BUY position if(position_type==POSITION_TYPE_BUY) { //--- Bar's low minus the specified number of points level=NormalizeDouble(buy_point-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- If the calculated level is lower than the lower limit of the Stops level, // the calculation is complete, return the current value of the level if(level<symb.down_level) return(level); //--- If it is not lower, try to calculate based on the bid price else { level=NormalizeDouble(symb.bid-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- If the calculated level is lower than the limit, return the current value of the level // otherwise set the nearest possible value return(level<symb.down_level ? level : symb.down_level-symb.offset); } } //--- Calculate the level for the SELL position if(position_type==POSITION_TYPE_SELL) { // Bar's high plus the specified number of points level=NormalizeDouble(sell_point+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- If the calculated level is higher than the upper limit of the Stops level, // then the calculation is complete, return the current value of the level if(level>symb.up_level) return(level); //--- If it is not higher, try to calculate based on the ask price else { level=NormalizeDouble(symb.ask+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- If the calculated level is higher than the limit, return the current value of the level // Otherwise set the nearest possible value return(level>symb.up_level ? level : symb.up_level+symb.offset); } } //--- return(0.0); }
函数代码 ModifyPendingOrderTrailingStop():
//+------------------------------------------------------------------+ //| Modifying the Trailing Stop level for a pending order | //+------------------------------------------------------------------+ void ModifyPendingOrderTrailingStop(int symbol_number) { //--- Exit, if the reverse position mode is disabled and Trailing Stop is not set if(!Reverse[symbol_number] || TrailingStop[symbol_number]==0) return; //--- double new_level =0.0; // For calculating a new level for a pending order bool condition =false; // For checking the modificating condition int total_orders =0; // Total number of pending orders ulong order_ticket =0; // Order ticket string opposite_order_comment =""; // Opposite order comment ENUM_ORDER_TYPE opposite_order_type =WRONG_VALUE; // Order type //--- Get the flag of presence/absence of a position pos.exists=PositionSelect(Symbols[symbol_number]); //--- If a position is absent if(!pos.exists) return; //--- Get a total number of pending orders total_orders=OrdersTotal(); //--- Get the symbol properties GetSymbolProperties(symbol_number,S_ALL); //--- Get the position properties GetPositionProperties(symbol_number,P_ALL); //--- Get the level for Stop Loss new_level=CalculateReverseOrderTrailingStop(symbol_number,pos.type); //--- Loop through the orders from the last to the first one for(int i=total_orders-1; i>=0; i--) { //--- If the order selected if((order_ticket=OrderGetTicket(i))>0) { //--- Get the order symbol GetPendingOrderProperties(O_SYMBOL); //--- Get the order comment GetPendingOrderProperties(O_COMMENT); //--- Get the order price GetPendingOrderProperties(O_PRICE_OPEN); //--- Depending on the position type, check the relevant condition for the Trailing Stop modification switch(pos.type) { case POSITION_TYPE_BUY : //---If the new order value is greater than the current value plus set step then condition fulfilled condition=new_level>ord.price_open+CorrectValueBySymbolDigits(TrailingStop[symbol_number]*symb.point); //--- Define the type and comment of the reversed pending order for check. opposite_order_type =ORDER_TYPE_SELL_STOP; opposite_order_comment =comment_bottom_order; break; case POSITION_TYPE_SELL : //--- If the new value for the order if less than the current value minus a set step then condition fulfilled condition=new_level<ord.price_open-CorrectValueBySymbolDigits(TrailingStop[symbol_number]*symb.point); //--- Define the type and comment of the reversed pending order for check opposite_order_type =ORDER_TYPE_BUY_STOP; opposite_order_comment =comment_top_order; break; } //--- If condition fulfilled, the order symbol and positions are equal // and order comment and the reversed order comment are equal if(condition && ord.symbol==Symbols[symbol_number] && ord.comment==opposite_order_comment) { double sl=0.0; // Stop Loss double tp=0.0; // Take Profit //--- Get Take Profit and Stop Loss levels sl=CalculatePendingOrderStopLoss(symbol_number,opposite_order_type,new_level); tp=CalculatePendingOrderTakeProfit(symbol_number,opposite_order_type,new_level); //--- Modify order ModifyPendingOrder(symbol_number,order_ticket,opposite_order_type,new_level,sl,tp, ORDER_TIME_GTC,ord.time_expiration,ord.price_stoplimit,ord.comment,0); return; } } } }
某些时候,有必要找出一个仓位是否在止损或止盈位置平仓。在这种特殊情况下,我们将遇到这样的需求。所以让我们编写函数,通过最后的成交注释来识别这个事件。要获取指定品种的最后成交注释,我们要编写一个单独的函数,名为 GetLastDealComment():
//+------------------------------------------------------------------+ //| Returns a the last deal comment for a specified symbol | //+------------------------------------------------------------------+ string GetLastDealComment(int symbol_number) { int total_deals =0; // Total number of deals in the selected history string deal_symbol =""; // Deal symbol string deal_comment =""; // Deal comment //--- If the deals history retrieved if(HistorySelect(0,TimeCurrent())) { //--- Receive the number of deals in the retrieved list total_deals=HistoryDealsTotal(); //--- Loop though the total number of deals in the retrieved list from the last deal to the first one. for(int i=total_deals-1; i>=0; i--) { //--- Receive the deal comment deal_comment=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_COMMENT); //--- Receive the deal symbol deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); //--- If the deal symbol and the current symbol are equal, stop the loop if(deal_symbol==Symbols[symbol_number]) break; } } //--- return(deal_comment); }
现在,很容易编写函数来检测指定品种的最后仓位的平仓原因。以下代码是这些函数 IsClosedByTakeProfit() 和 IsClosedByStopLoss():
//+------------------------------------------------------------------+ //| Returns the reason for closing position at Take Profit | //+------------------------------------------------------------------+ bool IsClosedByTakeProfit(int symbol_number) { string last_comment=""; //--- Get the last deal comment for the specified symbol last_comment=GetLastDealComment(symbol_number); //--- If the comment contain a string "tp" if(StringFind(last_comment,"tp",0)>-1) return(true); //--- If the comment does not contain a string "tp" return(false); } //+------------------------------------------------------------------+ //| Returns the reason for closing position at Stop Loss | //+------------------------------------------------------------------+ bool IsClosedByStopLoss(int symbol_number) { string last_comment=""; //--- Get the last deal comment for the specified symbol last_comment=GetLastDealComment(symbol_number); //--- If the comment contains the string "sl" if(StringFind(last_comment,"sl",0)>-1) return(true); //--- If the comment does not contain the string "sl" return(false); }
我们将进行另一次检查,以确定指定品种在成交历史中的最后一笔记录是否为真实的成交。我们要在内存中保留最后一笔成交的单号。要实现这个目的,我们在全局范围内添加一个数组:
//--- Array for checking the ticket of the last deal for each symbol. ulong last_deal_ticket[NUMBER_OF_SYMBOLS];
函数 IsLastDealTicket() 用于检查最后成交的单号,其代码如下:
//+------------------------------------------------------------------+ //| Returns the event of the last deal for the specified symbol | //+------------------------------------------------------------------+ bool IsLastDealTicket(int symbol_number) { int total_deals =0; // Total number of deals in the selected history list string deal_symbol =""; // Deal symbol ulong deal_ticket =0; // Deal ticket //--- If the deal history was received if(HistorySelect(0,TimeCurrent())) { //--- Get the total number of deals in the received list total_deals=HistoryDealsTotal(); //--- Loop through the total number of deals from the last deal to the first one for(int i=total_deals-1; i>=0; i--) { //--- Get deal ticket deal_ticket=HistoryDealGetTicket(i); //--- Get deal symbol deal_symbol=HistoryDealGetString(deal_ticket,DEAL_SYMBOL); //--- If deal symbol and the current one are equal, stop the loop if(deal_symbol==Symbols[symbol_number]) { //--- If the tickets are equal, exit if(deal_ticket==last_deal_ticket[symbol_number]) return(false); //--- If the tickets are not equal report it else { //--- Save the last deal ticket last_deal_ticket[symbol_number]=deal_ticket; return(true); } } } } //--- return(false); }
如果当前时间超出指定交易范围,无论盈亏与否,持仓将被强制平仓。让我们编写这个函数 ClosePosition() 来平仓:
//+------------------------------------------------------------------+ //| Closes position | //+------------------------------------------------------------------+ void ClosePosition(int symbol_number) { //--- Check if position exists pos.exists=PositionSelect(Symbols[symbol_number]); //--- If there is no position, exit if(!pos.exists) return; //--- Set the slippage value in points trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation)); //--- If the position was not closed, print the relevant message if(!trade.PositionClose(Symbols[symbol_number])) Print("Error when closing position: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
当超出交易时间范围后,持仓被平仓,所有挂单也必须一并删除。函数 DeleteAllPendingOrders() 就是为此编写的,它删除所有指定品种的挂单:
//+------------------------------------------------------------------+ //| Deletes all pending orders | //+------------------------------------------------------------------+ void DeleteAllPendingOrders(int symbol_number) { int total_orders =0; // Total number of pending orders ulong order_ticket =0; // Order ticket //--- Get the total number of pending orders total_orders=OrdersTotal(); //--- Loop through the total number of pending orders for(int i=total_orders-1; i>=0; i--) { //--- If the order selected if((order_ticket=OrderGetTicket(i))>0) { //--- Get the order symbol GetOrderProperties(O_SYMBOL); //--- If the order symbol and the current symbol are equal if(ord.symbol==Symbols[symbol_number]) //--- Delete the order DeletePendingOrder(order_ticket); } } }
所以,我们现在拥有了支持结构化方案的所有必要函数。让我们来看看,经过一些明显变化并增加用于管理挂单的新函数 ManagePendingOrders(),曾经熟悉的函数 TradingBlock() 不见了。在此,将依照挂单的当前状况进行全面控制。
函数 TradingBlock() 用于当前范式,如下所示:
//+------------------------------------------------------------------+ //| Trade block | //+------------------------------------------------------------------+ void TradingBlock(int symbol_number) { double tp=0.0; // Take Profit double sl=0.0; // Stop Loss double lot=0.0; // Volume for position calculation in case of reversed position double order_price=0.0; // Price for placing the order ENUM_ORDER_TYPE order_type=WRONG_VALUE; // Order type for opening position //--- If outside of the time range for placing pending orders if(!IsInOpenOrdersTimeRange(symbol_number)) return; //--- Find out if there is an open position for the symbol pos.exists=PositionSelect(Symbols[symbol_number]); //--- If there is no position if(!pos.exists) { //--- Get symbol properties GetSymbolProperties(symbol_number,S_ALL); //--- Adjust the volume lot=CalculateLot(symbol_number,Lot[symbol_number]); //--- If there is no upper pending order if(!CheckPendingOrderByComment(symbol_number,comment_top_order)) { //--- Get the price for placing a pending order order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_BUY_STOP); //--- Get Take Profit and Stop Loss levels sl=CalculatePendingOrderStopLoss(symbol_number,ORDER_TYPE_BUY_STOP,order_price); tp=CalculatePendingOrderTakeProfit(symbol_number,ORDER_TYPE_BUY_STOP,order_price); //--- Place a pending order SetPendingOrder(symbol_number,ORDER_TYPE_BUY_STOP,lot,0,order_price,sl,tp,ORDER_TIME_GTC,comment_top_order); } //--- If there is no lower pending order if(!CheckPendingOrderByComment(symbol_number,comment_bottom_order)) { //--- Get the price for placing the pending order order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_SELL_STOP); //--- Get Take Profit and Stop Loss levels sl=CalculatePendingOrderStopLoss(symbol_number,ORDER_TYPE_SELL_STOP,order_price); tp=CalculatePendingOrderTakeProfit(symbol_number,ORDER_TYPE_SELL_STOP,order_price); //--- Place a pending order SetPendingOrder(symbol_number,ORDER_TYPE_SELL_STOP,lot,0,order_price,sl,tp,ORDER_TIME_GTC,comment_bottom_order); } } }
函数代码 ManagePendingOrders() 用于管理挂单:
//+------------------------------------------------------------------+ //| Manages pending orders | //+------------------------------------------------------------------+ void ManagePendingOrders() { //--- Loop through the total number of symbols for(int s=0; s<NUMBER_OF_SYMBOLS; s++) { //--- If trading this symbol is forbidden, go to the following one if(Symbols[s]=="") continue; //--- Find out if there is an open position for the symbol pos.exists=PositionSelect(Symbols[s]); //--- If there is no position if(!pos.exists) { //--- If the last deal on current symbol and // position was exited on Take Profit or Stop Loss if(IsLastDealTicket(s) && (IsClosedByStopLoss(s) || IsClosedByTakeProfit(s))) //--- Delete all pending orders for the symbol DeleteAllPendingOrders(s); //--- Go to the following symbol continue; } //--- If there is a position ulong order_ticket =0; // Order ticket int total_orders =0; // Total number of pending orders int symbol_total_orders =0; // Number of pending orders for the specified symbol string opposite_order_comment =""; // Opposite order comment ENUM_ORDER_TYPE opposite_order_type =WRONG_VALUE; // Order type //--- Get the total number of pending orders total_orders=OrdersTotal(); //--- Get the total number of pending orders for the specified symbol symbol_total_orders=OrdersTotalBySymbol(Symbols[s]); //--- Get symbol properties GetSymbolProperties(s,S_ASK); GetSymbolProperties(s,S_BID); //--- Get the comment for the selected position GetPositionProperties(s,P_COMMENT); //--- If the position comment belongs to the upper order, // then the lower order is to be deleted, modified/placed if(pos.comment==comment_top_order) { opposite_order_type =ORDER_TYPE_SELL_STOP; opposite_order_comment =comment_bottom_order; } //--- If the position comment belongs to the lower order, // then the upper order is to be deleted/modified/placed if(pos.comment==comment_bottom_order) { opposite_order_type =ORDER_TYPE_BUY_STOP; opposite_order_comment =comment_top_order; } //--- If there are no pending orders for the specified symbol if(symbol_total_orders==0) { //--- If the position reversal is enabled, place a reversed order if(Reverse[s]) { double tp=0.0; // Take Profit double sl=0.0; // Stop Loss double lot=0.0; // Volume for position calculation in case of reversed positio double order_price=0.0; // Price for placing the order //--- Get the price for placing a pending order order_price=CalculatePendingOrder(s,opposite_order_type); //---Get Take Profit и Stop Loss levels sl=CalculatePendingOrderStopLoss(s,opposite_order_type,order_price); tp=CalculatePendingOrderTakeProfit(s,opposite_order_type,order_price); //--- Calculate double volume lot=CalculateLot(s,pos.volume*2); //--- Place the pending order SetPendingOrder(s,opposite_order_type,lot,0,order_price,sl,tp,ORDER_TIME_GTC,opposite_order_comment); //--- Adjust Stop Loss as related to the order CorrectStopLossByOrder(s,order_price,opposite_order_type); } return; } //--- If there are pending orders for this symbol, then depending on the circumstances delete or // modify the reversed order if(symbol_total_orders>0) { //--- Loop through the total number of orders from the last one to the first one for(int i=total_orders-1; i>=0; i--) { //--- If the order chosen if((order_ticket=OrderGetTicket(i))>0) { //--- Get the order symbol GetPendingOrderProperties(O_SYMBOL); //--- Get the order comment GetPendingOrderProperties(O_COMMENT); //--- If order symbol and position symbol are equal, // and order comment and the reversed order comment are equal if(ord.symbol==Symbols[s] && ord.comment==opposite_order_comment) { //--- If position reversal is disabled if(!Reverse[s]) //--- Delete order DeletePendingOrder(order_ticket); //--- If position reversal is enabled else { double lot=0.0; //--- Get the current order properties GetPendingOrderProperties(O_ALL); //--- Get the current position volume GetPositionProperties(s,P_VOLUME); //--- If the order has been modified already, exit the loop. if(ord.volume_initial>pos.volume) break; //--- Calculate double volume lot=CalculateLot(s,pos.volume*2); //--- Modify (delete and place again) the order ModifyPendingOrder(s,order_ticket,opposite_order_type, ord.price_open,ord.sl,ord.tp, ORDER_TIME_GTC,ord.time_expiration, ord.price_stoplimit,opposite_order_comment,lot); } } } } } } }
现在我们只需要在主程序文件中进行微调。我们将添加交易事件处理器 OnTrade()。在此函数中,将针对与交易事件相关的挂单进行当前状况评估。
//+------------------------------------------------------------------+ //| Processing of trade events | //+------------------------------------------------------------------+ void OnTrade() { //--- Check the state of pending orders ManagePendingOrders(); }
函数 ManagePendingOrders() 也同样用于用户事件处理器 OnChartEvent():
//+------------------------------------------------------------------+ //| User events and chart events handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // Event identifier const long &lparam, // Parameter of long event type const double &dparam, // Parameter of double event type const string &sparam) // Parameter of string event type { //--- If it is a user event if(id>=CHARTEVENT_CUSTOM) { //--- Exit, if trade is prohibited if(CheckTradingPermission()>0) return; //--- If it is a tick event if(lparam==CHARTEVENT_TICK) { //--- Check the state of pending orders ManagePendingOrders(); //--- Check signals and trade according to them CheckSignalsAndTrade(); return; } } }
一些变化也同样在函数 CheckSignalsAndTrade() 里产生。在以下代码里,突显的字符串是本文中分析的新函数。
//+------------------------------------------------------------------+ //| Checks signals and trades based on New Bar event | //+------------------------------------------------------------------+ void CheckSignalsAndTrade() { //--- Loop through all specified signals for(int s=0; s<NUMBER_OF_SYMBOLS; s++) { //--- If trading this symbol is prohibited, exit if(Symbols[s]=="") continue; //--- If the bar is not new, move on to the following symbol if(!CheckNewBar(s)) continue; //--- If there is a new bar else { //--- If outwith the time range if(!IsInTradeTimeRange(s)) { //--- Close position ClosePosition(s); //--- Delete all pending orders DeleteAllPendingOrders(s); //--- Move on to the following symbol continue; } //--- Get bars data GetBarsData(s); //--- Check conditions and trade TradingBlock(s); //--- If position reversal if enabled if(Reverse[s]) //--- Pull up Stop Loss for pending order ModifyPendingOrderTrailingStop(s); //--- If position reversal is disabled else //--- Pull up Stop Loss ModifyTrailingStop(s); } }
现在万事俱备,我们可以尝试优化这个多币种 EA 的参数,让我们按照如下所示来设置策略测试:
图例. 1 - 参数优化测试设置。
首先我们针对当前货币对 EURUSD 优化参数,之后是 AUDUSD。以下屏幕截图示意我们将选择 EURUSD 的哪些参数进行优化:
图例. 2 - 设置多币种 EA 的优化参数
在货币对 EURUSD 的参数进行优化以后,同样对 AUDUSD 的参数进行优化。以下是两个品种一并测试的结果。选择最大恢复因子的结果。为了进行测试,两个品种的手数值均设为 1。
图例. 3 - 两个品种一并测试的结果。
结论
有关东西就这么多了。有了现成函数在手,您可以集中精力发展制定交易决策的思路。在此情况下,变化将在函数 TradingBlock() 和 ManagePendingOrders() 里实现。对于那些最近开始学习 MQL5 的人,我们建议在练习时增加更多的品种并改变交易算法方案。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/755
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.


奥托......现在你可以使用它了 :-)
对我来说,它可以进行交易。
这真是个不错的答复。谢谢!
我只是想指出,文章的作者应该处理好这些问题。
您只需
和所有 MQL5xxx 垃圾就可以了;)这真是个不错的答复。谢谢!
我只是想指出,文章的作者应该处理好这些问题。
嗯嗯......是的,我们知道。
我给了它一些表达方式。
这样做很有效,即使没人说什么,你也会在其他地方注意到:-)
嗯......是的,我们知道。
我已经把它表达出来了。
这样的东西很有效,即使没人说什么,你也会在其他地方注意到它:-)
我的目的是将 MarketOrders 重新编程为 PendigOrders。
不管是谁都可以使用,下面是它的工作 代码。
这不是一个有用的 EA,只是一个如何计算的例子。我希望它是正确的,因为它在测试器中可以正常工作。
这也不是我真正的编程风格,而是非常简单。