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