English Русский Español Deutsch 日本語 Português
preview
MQL5 交易工具包(第 1 部分):开发仓位管理 EX5 库

MQL5 交易工具包(第 1 部分):开发仓位管理 EX5 库

MetaTrader 5示例 | 16 一月 2025, 09:40
508 0
Wanateki Solutions LTD
Kelvin Muturi Muigua

概述

作为一名软件开发人员,我经常发现创建自己的代码库或工具箱既方便又高效。这节省了我的时间,因为我不必为各种 MQL5 开发项目中所需的常见任务反复重写代码。在本系列文章中,我们将创建一个 MQL5 交易库,负责执行 MQL5 开发项目中的常见重复性任务。

在第一篇文章中,我们将讨论什么是开发库、开发库的重要性以及您可以使用 MQL5 创建的不同类型的代码库。然后,我们将继续创建一个 MQL5 函数库来处理各种仓位操作,作为一个实际示例,帮助您巩固对如何在实际项目中使用代码库的理解。


MQL5 中的代码库是什么?

MQL5 代码库是预先编写的代码函数(ex5)或动态链接库(DLL),我们可以使用这些代码库有效加快 MetaTrader 5 平台的 EA 交易、自定义指标、脚本或服务的开发进程。 

将代码库想象成机械师的工具箱。就像机械师的工具箱里有各种用于特定任务的工具(扳手、螺丝刀等)一样,代码库中也有用于类似任务的预先写好的函数。每个函数都针对程序中的特定任务,如打开、关闭或修改仓位、发送推送通知、数据库管理等,就像扳手拧紧螺栓或螺丝刀拧螺丝一样。


MQL5 中的代码库类型

作为 MQL5 开发人员,您有几种构建代码库或工具箱的选择:

  • 函数库(ex5):这些库采用程序编码方式,为特定任务提供一系列预先写好的函数。它们还提供了代码封装的额外安全优势。将它们视为单独的工具,每个工具都是为特定的工作而设计的。
  • 第三方 C++ 动态链接库:您可以将预先编写的 C++ 库整合为 DLL(动态链接库)。这将扩展 MQL5 的功能,使您能够利用外部功能。

MetaTrader 5 还提供了扩展工具箱的其他方法:

  • .NET 库:MetaEditor 通过 "智能的" 函数导入与 .NET 库无缝集成,无需自定义封装器。
  • Python 语言模块:新支持的 Python 语言模块允许您在 MQL5 项目中利用 Python 功能。

如果您熟悉 C++,就可以创建自定义 DLL,轻松集成到您的 MQL5 项目中。您可以利用 Microsoft Visual Studio 等工具开发 C++ 源代码文件(CPP 和 H),将其编译为 DLL,然后导入到 MetaEditor,与 MQL5 代码一起使用。

与 MQL5 库相似的其他代码资源

  • 类/包含文件(*.mqh):MQL5 包含文件采用面向对象编程,提供封装数据和功能的预建类。把它们想象成结合了函数/方法和数据结构的更复杂的工具。从本质上讲,类或结构不能导出以创建 MQL5 库 (ex5),但您可以在 MQL5 库函数中使用指向类或结构的指针和引用。


为什么需要创建或使用 ex5 MQL5 库?

为 MQL5 项目创建自己的代码库可以使您的开发过程更加高效。这些库保存为 .ex5 文件,就像一个个人工具箱,里面装满了您为特定任务优化的函数。

易于重复使用和模块化设计

无需在每次启动新项目时重写常用函数,从而节省时间。使用 .ex5 库,您只需编写一次代码,优化代码以获得最佳性能,导出函数,然后轻松将其导入任何项目。这种模块化方法将核心功能与特定项目的逻辑分离开来,从而保持代码的整洁和有序。库中的每一部分都是创建强大交易系统的基石。

利用封装确保函数安全

创建 MQL5 库可帮助您共享函数,同时隐藏源代码。封装可以确保代码的细节安全,不被用户看到,同时还能提供清晰的功能界面。您只需与其他开发人员共享 .ex5 库文件以及导出函数定义的清晰文档,他们就可以将库函数导入到自己的项目中。.ex5 库文件有效地隐藏了源代码和导出函数的编码方法,从而在主项目代码中保持了一个安全的封装好的工作区。

升级简单,长期受益

当出现新的语言功能或旧的语言功能被淘汰时,使用 .ex5 库可以轻松更新代码。只需更新库代码、重新部署并重新编译所有使用它的项目,就能自动获得更改。这为您节省了大量时间和精力,尤其是在大型项目中。该库充当代码库的中央系统,一次更新或更改将影响所有相关项目,从而从长远来看提高您的工作效率。


如何在 MQL5 中创建 ex5 库?

所有 .ex5 库都以 .mq5 源代码文件开始,在文件开头添加 #property 库指令,并使用特殊关键字 export 指定一个或多个可导出的函数。.mq5 库源代码文件在编译后会转换为 .ex5 库文件,安全地封装或隐藏源代码,使其可随时导入到其他 MQL5 项目中使用。

使用 MetaEditor IDE 可以轻松创建新的 MQL5 库。按照以下步骤创建新的库源代码文件(.mq5),该文件将包含仓位管理函数,随后将编译成 .ex5 库。

第一步:打开 MetaEditor IDE,使用 "新建"菜单项按钮启动 "MQL 向导"。

MQL5 向导新库文件

第二步:选择 "程序库" 选项,然后点击 "下一页"。

MQL5 向导新建库文件

第三步:在"库文件的常规属性"部分,填写新库的文件夹和名称 "Libraries\Toolkit\PositionsManager" 然后点击"完成"来生成新库。

MQL5 向导新建库文件一般属性

MQL5 库通常存储在扩展名为 ".mq5" 的文件中。该文件包含了为各种特定任务编写的不同函数的源代码。代码库默认存储在 MetaTrader 5 安装目录下的 MQL5\Libraries 文件夹中。访问库文件夹的一个快速方法是使用 MetaEditor 中的导航面板

MetaEditor 导航面板中的 MQL5 默认库文件夹

现在我们有了一个新创建的空白 MQL5 库 PositionsManager.mq5 文件,其中包含头部属性指令和一个注释掉的函数。在继续之前,请记住保存新文件。这是我们新生成的库文件的样子:

//+------------------------------------------------------------------+
//|                                             PositionsManager.mq5 |
//|                          Copyright 2024, Wanateki Solutions Ltd. |
//|                                         https://www.wanateki.com |
//+------------------------------------------------------------------+
#property library
#property copyright "Copyright 2024, Wanateki Solutions Ltd."
#property link      "https://www.wanateki.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| My function                                                      |
//+------------------------------------------------------------------+
// int MyCalculator(int value,int value2) export
//   {
//    return(value+value2);
//   }
//+------------------------------------------------------------------+

MQL5 库源代码文件的组成部分

MQL5 库源代码文件 (.mq5) 由两个主要部分组成:

1.#property library 指令 :property 指令必须添加到库 .mq5 源代码文件的顶部。library 属性可让编译器知道给定文件是一个库,并在 .mq5 库编译后产生的 .ex5 文件中的编译库头文件中存储相关说明。

#property library

2. 导出函数 :MQL5 库的核心是导出函数。这些是库的主要组成部分,因为它们负责执行程序库要执行的所有繁重任务。导出的 MQL5 函数与普通的 MQL5 函数类似,只是在声明时使用了 export 后置修饰符 使其可以在编译后被导入到其他 MQL5 程序中使用。export 修饰符指示编译器将指定函数添加到该库文件导出ex5 函数表中。只有使用 export 修饰符声明的函数才能被其他 mql5 程序访问和检测,这些程序可以在导入后使用特殊的 #import 指令导入后调用。

//+------------------------------------------------------------------+
//| Example of an exported function with the export postmodifier         |
//+------------------------------------------------------------------+
int ExportedFunction(int a, int b) export
  {
   return(a + b);
  }
//+------------------------------------------------------------------+

MQL5 库不需要像 EA 交易、自定义指标或脚本那样直接执行任何标准事件处理。这意味着它们没有任何标准函数,如 OnInit()OnDeinit()OnTick()


用于仓位管理的 MQL5 函数库

仓位管理是每个正在开发中的 EA 交易系统的基本任务。这些基本操作构成了任何算法交易系统的核心。为避免重复编码,MQL5 开发人员应使用库来有效管理仓位。这可确保开发人员不必为每个 EA 交易系统重写相同的仓位管理代码。

在本节中,我们将在新创建的 PositionsManager.mq5 文件中添加一些代码,以使用 MQL5 创建仓位管理库。该库将处理所有与仓位相关的操作。通过将其导入到您的 EA 交易代码中,您可以有效地执行和管理仓位,保持代码库的整洁和有序。


全局变量


MQL5 中的交易请求由一个特殊的预定义结构表示,该结构称为 MqlTradeRequest。此结构包括执行交易操作所需的所有必要字段,并确保所有所需的订单请求数据都打包在一个数据结构中。处理交易请求时,结果会保存在另一个预定义结构中,该结构称为 MqlTradeResultMqlTradeResult 负责向我们提供有关交易请求结果的详细信息,包括请求是否成功以及有关交易执行的任何相关数据。

由于我们将在大多数函数中使用这两种特殊的数据结构,让我们首先将它们声明为全局变量,以便它们在整个库中都可用。

//---Global variables
//-------------------------------
//-- Trade operations request and result data structures
MqlTradeRequest tradeRequest;
MqlTradeResult  tradeResult;
//-------------------------------


仓位错误管理函数


我们库中的第一个函数将是一个错误管理函数。在使用 MQL5 开仓、修改和平仓时,我们经常会遇到不同类型的错误,这些错误要么要求我们中止操作,要么要求我们重新向交易服务器发送仓位管理请求。创建一个专门的函数来监控和建议正确的操作,是正确编码和优化库的必要条件。

在创建错误处理函数之前,我们需要了解可能遇到的各种 MQL5 仓位管理错误代码。下表重点介绍了我们在管理不同仓位操作时需要检测和处理的一些返回的交易服务器和运行时错误代码。 您可以在 MQL5 文档中找到所有错误和警告代码的完整列表。

代码 代码常量  描述
所采取的行动 代码类型 
10004  TRADE_RETCODE_REQUOTE 重新报价 重新发送订单请求。 交易服务器返回代码 (RETCODE)
10008  TRADE_RETCODE_PLACED 订单已下 无需采取行动。操作成功。 交易服务器返回代码 (RETCODE)
10009  TRADE_RETCODE_DONE 请求已完成 无需采取行动。操作已完成。
交易服务器返回代码 (RETCODE)
10013  TRADE_RETCODE_INVALID 无效申请 停止重新发送订单初始化请求并更新订单详情。 交易服务器返回代码 (RETCODE)
10014  TRADE_RETCODE_INVALID_VOLUME 请求中的交易量无效 停止重新发送订单初始化请求并更新订单详情。 交易服务器返回代码 (RETCODE)
10016  TRADE_RETCODE_INVALID_STOPS 请求中的无效止损 停止重新发送订单初始化请求并更新订单详情。 交易服务器返回代码 (RETCODE)
10017  TRADE_RETCODE_TRADE_DISABLED 交易被禁止 终止所有交易操作,停止重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
10018  TRADE_RETCODE_MARKET_CLOSED 市场关闭 停止重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
10019  TRADE_RETCODE_NOO_MONEY 没有足够的资金完成申请 停止重新发送订单初始化请求并更新订单详情。 交易服务器返回代码 (RETCODE)
10026  TRADE_RETCODE_SERVER_DISABLES_AT 服务器禁止自动交易 服务器不允许交易。停止重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
10027  TRADE_RETCODE_CLIENT_DISABLES_AT 客户端禁止自动交易 客户端已禁止 EA 交易。停止重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
10034  TRADE_RETCODE_LIMIT_VOLUME 交易品种的订单量和持仓量已达到上限 停止重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
10011  TRADE_RETCODE_ERROR 请求处理错误 继续重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
10012  TRADE_RETCODE_TIMEOUT 请求因超时而取消 暂停执行几毫秒,然后继续重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
10015  TRADE_RETCODE_INVALID_PRICE 请求中的价格无效 更新订单输入价格并重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
10020  TRADE_RETCODE_PRICE_CHANGED 价格已变化 更新订单输入价格并重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
10021  TRADE_RETCODE_PRICE_OFF 没有用于处理申请的报价 暂停执行几毫秒,然后重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
10024  TRADE_RETCODE_TOO_MANY_REQUESTS 请求过于频繁 暂停执行几秒钟,然后重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
 10031  TRADE_RETCODE_CONNECTION 无法连接交易服务器 暂停执行几毫秒,然后再次重新发送订单初始化请求。 交易服务器返回代码 (RETCODE)
 0  ERR_SUCCESS 操作成功完成 停止重新发送请求。订单已发送。   运行时错误代码
 4752  ERR_TRADE_DISABLED 禁止使用 EA 交易进行交易 停止重新发送订单初始化请求。 运行时错误代码
 4753  ERR_TRADE_POSITION_NOT_FOUND 未找到仓位 停止重新发送交易操作请求。
运行时错误代码
 4754  ERR_TRADE_ORDER_NOT_FOUND 未找到订单 停止重新发送订单请求。
运行时错误代码
 4755  ERR_TRADE_DEAL_NOT_FOUND 未找到交易 停止重新发送订单请求。
运行时错误代码


让我们在库中创建第一个函数来处理上述错误。错误处理函数名为 ErrorAdvisor(),属于布尔类型,即根据遇到的错误类型返回 TrueFalse。它将接受两个参数,以帮助数据处理:

  1. callingFunc (string):该参数存储调用 ErrorAdvisor() 的函数名称或标识符。
  2. symbol (string):该参数存储正在处理的资产的交易品种名称。
  3. tradeServerErrorCode (integer):该参数存储遇到的错误类型。

如果错误可恢复且不严重,ErrorAdvisor() 函数将返回 True。这表明调用函数尚未执行订单,应重新发送订单请求。如果 ErrorAdvisor() 返回 False,则表示调用函数应停止发送任何订单请求,因为订单已被成功执行,或者遇到了无法恢复的严重错误。

切记在函数开头的大括号前加上 export 后置修饰符,以表明该函数属于一个库,可在其他 MQL5 程序中使用。

//------------------------------------------------------------------+
// ErrorAdvisor(): Error analysis and processing function.          |
// Returns true if order opening failed and order can be re-sent    |
// Returns false if the error is critical and can not be executed   |
//------------------------------------------------------------------+
bool ErrorAdvisor(string callingFunc, string symbol, int tradeServerErrorCode)  export
  {
//-- place the function body here
  }

首先,让我们声明并初始化一个整数变量,用于存储当前的运行时错误。给该整数命名为 runtimeErrorCode,并调用GetLastError() 函数来存储最近的运行时错误。 我们将使用这个变量来处理第二个嵌套的 switch 操作符中可能遇到的运行时错误。

//-- save the current runtime error code
   int runtimeErrorCode = GetLastError();

我们将使用嵌套的 switch 操作符扫描和处理交易服务器返回错误(retcode)和运行时错误。这样做很方便,因为我们可以快速识别错误类型,在专家日志中为用户打印说明,并指示调用函数如何继续操作。你会发现我把错误分成了两类:

  1. 表示订单已完成或未执行的错误:这些错误将返回 False,指示调用函数停止发送订单请求。
  2. 表示订单不完整的错误:这些错误将返回 True,指示调用函数重新发送订单请求。

第一个 switch 操作符将处理交易服务器返回代码,第二个嵌套的 switch 操作符将处理运行时错误代码。 这种方法避免了对每个错误代码或警告进行顺序检查,从而最大限度地减少了函数代码。

现在,让我们编写第一个 switch 语句,检查遇到的tradeServerErrorCode,看看交易服务器报告了哪种错误。

switch(tradeServerErrorCode)//-- check for trade server errors
     {
        //--- Cases to scan different retcodes/server return codes
     }

在 switch 语句中,我们将为交易服务器返回的不同错误代码添加 case。下面是其中的一些:

重新报价 (代码 10004):这意味着自用户尝试打开订单以来,价格已发生变化。在这种情况下,我们需要在日志中打印一条信息,使用 Sleep() 函数等待几毫秒,然后告诉调用函数重试打开订单。

case 10004:
    Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Requote!");
    Sleep(10);
    return(true);    //--- Exit the function and retry opening the order again

成功下单(代码 10008):如果返回此代码,则表示一切顺利,订单已下。我们可以在日志中打印一条订单成功的信息,然后告诉调用函数停止尝试打开订单。

case 10008:
    Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Order placed!");
    return(false);    //--- success - order placed ok. exit function

我们将为其他交易服务器错误(代码 10009、10011、10012 等)添加类似的 case,按照相同的逻辑在专家日志打印一条信息,必要时稍作等待,并指示调用函数是否再次尝试发送交易请求。

如果交易服务器错误的 switch 语句没有找到匹配的错误,这意味着该错误可能是运行时错误,只能通过创建另一个 switch 语句来扫描 GetLastError() 函数返回的当前运行时错误。为了应对这一挑战,我们将在前一个 switch 语句的默认部分使用嵌套的 switch 语句来检查我们之前保存的 runtimeErrorCode 的值。

default:
  switch(runtimeErrorCode)//-- check for runtime errors
    //-- Add cases for different runtime errors here
  }
}

重复处理交易服务器错误的相同过程,并为不同的运行时错误代码添加 case:

无错误(代码 0):这意味着,从我们程序的角度来看,一切都很顺利。我们可以在日志中打印一条信息,说明没有错误,然后告诉调用函数停止尝试。

case 0:
    Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") The operation completed successfully!");
    ResetLastError(); //--- reset error cache
    return(false);    //--- Exit the function and stop trying to open order

我们将继续为其他运行时错误(代码 4752、4753、4754 等)添加类似 case,按照相同的逻辑在专家日志打印错误信息,并告诉调用函数停止或继续交易请求。

由于我们只考虑了可能影响订单执行过程的最重要错误代码,而没有扫描或处理所有可能存在的错误代码,因此我们可能会遇到当前代码中尚未处理或考虑的错误。在这种情况下,我们将在日志中打印一条消息,指示发生了未知(其他)错误,指定服务器返回代码错误和遇到的运行时错误,然后告诉调用函数停止尝试打开订单。

default: //--- All other error codes
    Print(symbol, " - ", callingFunc, " *OTHER* Error occurred \r\nTrade Server RetCode: ", tradeServerErrorCode, ", Runtime Error Code = ", runtimeErrorCode);
    ResetLastError(); //--- reset error cache
    return(false);    //--- Exit the function and stop trying to open order
    break;

下面是已完成所有代码段的完整错误管理函数 ErrorAdvisor():

bool ErrorAdvisor(string callingFunc, string symbol, int tradeServerErrorCode)  export
  {
//-- save the current runtime error code
   int runtimeErrorCode = GetLastError();

   switch(tradeServerErrorCode)//-- check for trade server errors
     {
      case 10004:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Requote!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again
      case 10008:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Order placed!");
         return(false);    //--- success - order placed ok. exit function

      case 10009:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request completed!");
         return(false);    //--- success - order placed ok. exit function

      case 10011:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request processing error!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again

      case 10012:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request canceled by timeout!");
         Sleep(100);
         return(true);    //--- Exit the function and retry opening the order again

      case 10015:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Invalid price in the request!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again

      case 10020:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Prices changed!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again

      case 10021:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") There are no quotes to process the request!");
         Sleep(100);
         return(true);    //--- Exit the function and retry opening the order again

      case 10024:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Too frequent requests!");
         Sleep(1000);
         return(true);    //--- Exit the function and retry opening the order again

      case 10031:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") No connection with the trade server!");
         Sleep(100);
         return(true);    //--- Exit the function and retry opening the order again

      default:
         switch(runtimeErrorCode)//-- check for runtime errors
            case 0:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") The operation completed successfully!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4752:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Trading by Expert Advisors prohibited!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4753:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Position not found!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4754:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Order not found!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4755:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Deal not found!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            default: //--- All other error codes
               Print(symbol, " - ", callingFunc, " *OTHER* Error occurred \r\nTrade Server RetCode: ", tradeServerErrorCode, ", Runtime Error Code = ", runtimeErrorCode);
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order
               break;
           }
     }
  }


交易权限函数


该函数用于验证交易终端当前是否允许交易。它考虑了用户、交易服务器和经纪商的授权。在任何仓位操作或订单请求发送到交易服务器进行处理之前,都会调用该函数。

我们将把函数命名为 TradingIsAllowed(),并赋予其 布尔 返回类型。如果允许并启用交易,则返回 True 布尔值;如果禁用或不允许自动交易,则返回 False 布尔值 。它将不包含任何参数或参数,并将包含下面的代码段:

//+-----------------------------------------------------------------------+
//| TradingIsAllowed() verifies whether auto-trading is currently allowed |                                                                 |
//+-----------------------------------------------------------------------+
bool TradingIsAllowed() export
  {
   if(
      !IsStopped() &&
      MQLInfoInteger(MQL_TRADE_ALLOWED) && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) &&
      AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) && AccountInfoInteger(ACCOUNT_TRADE_EXPERT)
   )
     {
      return(true);//-- trading is allowed, exit and return true
     }
   return(false);//-- trading is not allowed, exit and return false
  }


订单详情记录和打印函数


这是一个简单的函数,用于记录和打印不同交易操作或建仓请求的属性。它为 EA 交易的用户通过 MetaTrader 5 专家日志选项卡随时了解 EA 交易的运行状况提供了一种简单的方法。您会注意到,该函数没有导出,因此只能通过其他导出函数访问,这些函数会明确调用或执行该函数。我们将把它命名为 PrintOrderDetails(),并指定它不会返回任何数据,因此是一个 void 类型的函数,它将接收一个字符串变量作为输入参数或参数。

//+-----------------------------------------------------------------------+
//| PrintOrderDetails() prints the order details for the EA log           |
//+-----------------------------------------------------------------------+
void PrintOrderDetails(string header)
  {
   string orderDescription;
//-- Print the order details
   orderDescription += "_______________________________________________________________________________________\r\n";
   orderDescription += "--> "  + tradeRequest.symbol + " " + EnumToString(tradeRequest.type) + " " + header +
                       " <--\r\n";
   orderDescription += "Order ticket: " + (string)tradeRequest.order + "\r\n";
   orderDescription += "Volume: " + StringFormat("%G", tradeRequest.volume) + "\r\n";
   orderDescription += "Price: " + StringFormat("%G", tradeRequest.price) + "\r\n";
   orderDescription += "Stop Loss: " + StringFormat("%G", tradeRequest.sl) + "\r\n";
   orderDescription += "Take Profit: " + StringFormat("%G", tradeRequest.tp) + "\r\n";
   orderDescription += "Comment: " + tradeRequest.comment + "\r\n";
   orderDescription += "Magic Number: " + StringFormat("%d", tradeRequest.magic) + "\r\n";
   orderDescription += "Order filling: " + EnumToString(tradeRequest.type_filling)+ "\r\n";
   orderDescription += "Deviation points: " + StringFormat("%G", tradeRequest.deviation) + "\r\n";
   orderDescription += "RETCODE: " + (string)(tradeResult.retcode) + "\r\n";
   orderDescription += "Runtime Code: " + (string)(GetLastError()) + "\r\n";
   orderDescription += "---";
   Print(orderDescription);
  }


开启仓位函数


我们将把这些函数分为两类:

  1. OpenBuyPositions() 函数:顾名思义,这个可导出函数将负责打开新的买入仓位。
  2. OpenSellPositions() 函数:顾名思义,这项可导出函数的唯一任务就是打开新的卖出仓位。

OpenBuyPositions() 函数

该函数的类型为 bool,如果成功按指令建立新的买入仓位,则返回 true 值;如果无法建立新的买入仓位,则返回 false 值。它需要六个参数:

  1. ulong magicNumber:用于保存 EA 交易的幻数,以便轻松修改或关闭仓位,并轻松筛选仓位。
  2. string symbol:保存正在执行请求的交易品种或资产名称。
  3. double lotSize:存储要建立的买入仓位的交易量。
  4. int sl:存储买入仓位的止损值(点数)。
  5. int tp:存储买入仓位的止盈值(点数)。
  6. string positionComment:用于保存或存储买入仓位的注释。

我们将首先对函数定义进行编码。请注意,我们在函数开头的大括号前添加了 export 后修饰符,以指示编译器将此函数导出,供其他引用此库的 MQL5 项目使用。

bool OpenBuyPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export
  {
        //--- Function body
  }

在尝试打开订单之前,让我们检查一下我们的 EA 交易系统是否允许交易。为此,我们将调用之前创建的 TradingIsAllowed() 函数。TradingIsAllowed() 函数将扫描各种设置和权限,以确保启用自动交易。

如果 TradingIsAllowed() 返回 false,则表示交易被禁用,我们的 EA 交易无法打开订单。在这种情况下,我们也将立即从该函数返回 false ,并在不打开新买入订单的情况下退出该函数。

//-- first check if the ea is allowed to trade
if(!TradingIsAllowed())
  {
   return(false); //--- algo trading is disabled, exit function
  }

下一步是为订单请求做准备,清除之前交易尝试中的残留数据。为此,让我们在文件开头创建的两个全局交易数据结构:tradeRequesttradeResult 上使用 ZeroMemory() 函数。它们将分别存储我们要打开的买入订单的详细信息和交易服务器返回的结果。

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

现在,让我们在 tradeRequest 数据结构变量中设置建立买入仓位的参数:

  • tradeRequest.type:将其设置为ORDER_TYPE_BUY,表示买入订单。
  • tradeRequest.action:将其设置为 TRADE_ACTION_DEAL,以指定开设新仓位。
  • tradeRequest.magic:我们将在此赋值作为参数提供的 magicNumber。这有助于识别我们的 EA 交易开启的订单。
  • tradeRequest.symbol:将提供的交易品种作为参数赋值,指定要交易的货币对。
  • tradeRequest.tptradeRequest.sl:我们先将其设置为 0,因为我们稍后会处理止盈 (TP) 和止损 (SL) 水平。
  • tradeRequest.comment:在此指定作为参数提供的 positionComment ,该参数可用于为订单添加文本注释。
  • tradeRequest.deviation:将其设置为允许的偏差最大为交易品种当前点差的两倍。这样,平台就能灵活地找到匹配的订单价格,并限制订单重新报价。

//-- initialize the parameters to open a buy position
   tradeRequest.type = ORDER_TYPE_BUY;
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.magic = magicNumber;
   tradeRequest.symbol = symbol;
   tradeRequest.tp = 0;
   tradeRequest.sl = 0;
   tradeRequest.comment = positionComment;
   tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2;

订单量或手数大小的标准化是非常重要的一步。为此,我们将调整 lotSize 变量参数,确保其在所选符号的允许范围内。下面是我们的调整方法:

//-- set and moderate the lot size or volume
   lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));  //-- Verify that volume is not less than allowed minimum
   lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX));  //-- Verify that volume is not more than allowed maximum
   lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step
   tradeRequest.volume = lotSize;

接下来,我们将使用 ResetLastError() 函数清除平台上之前的运行时错误代码。这将确保我们稍后能从 ErrorAdvisor() 函数中获得准确的错误代码。

//--- Reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function
   ResetLastError();

现在,我们将进入一个循环,该循环最多可尝试打开订单 100 次(最多迭代 101 次)。如果由于暂时的市场波动或其他原因导致订单无法在第一次尝试时打开,这个循环就会起到保护作用。

for(int loop = 0; loop <= 100; loop++) //-- try opening the order untill it is successful (100 max tries)
   {
    //--- Place the order request code to open a new buy position
   }

for 循环的第一项任务是在每次迭代中更新订单开盘价,以防订单价格重新报价。我们将使用 SymbolInfoDouble(symbol,SYMBOL_ASK) 获取该交易品种的当前要价,并将其赋值给 tradeRequest.price。这可确保我们的订单请求反映最新的市场价格。

//--- update order opening price on each iteration
   tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_ASK);

接下来,我们将更新每次迭代的止盈和止损值,使其与更新后的入市价格相匹配,同时对其值进行规范化处理,以确保它们符合所需的精度要求,然后将它们赋值给tradeRequest.tptradeRequest.tp 数据结构。

//-- set the take profit and stop loss on each iteration
   if(tp > 0)
     {
      tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits);
     }
   if(sl > 0)
     {
      tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits);
     }

接下来,我们将把订单发送到交易服务器进行执行。为此,我们将使用 OrderSend() 函数,将准备好的 tradeRequest 和一个空的 tradeResult 变量作为参数。该函数尝试根据 tradeRequest 中存储的规格打开订单。执行完成后,结果(包括成功或错误代码)将存储在 tradeResult 变量中。

我们为 OrderSend() 函数设置的 if 语句将允许我们检查并确认订单请求是否成功。如果 OrderSend() 返回 true,则表示订单请求发送成功;如果返回 false,则表示请求发送失败。

然后,我们调用先前编码的函数 PrintOrderDetails() 并发送信息 "Sent OK",将此信息记录在专家日志中。

此外,检查 tradeResult.retcode 以确认订单是否成功执行。从 OpenBuyPosition() 函数中返回 true 表示成功,使用 break 完全退出循环。如果 OrderSend() 返回 false(表示订单请求失败),则表示出现了问题。我们将调用 PrintOrderDetails() 并传入 "Sending Failed" 信息来记录这些信息。我们还将打印一条错误信息,以突出显示我们遇到的不同错误代码,并从 OpenBuyPosition() 函数返回 false 表示失败,同时使用 break 退出循环。

//--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Print the order details
         PrintOrderDetails("Sent OK");

         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, ": CONFIRMED: Successfully openend a ", symbol, " BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
            PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
            Print("_______________________________________________________________________________________");
            return(true); //-- exit the function
            break; //--- success - order placed ok. exit the for loop
           }
        }
      else //-- Order request failed
        {
         //-- Print the order details
         PrintOrderDetails("Sending Failed");

         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, ": ", symbol, " ERROR opening a BUY POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume);
            Print("_______________________________________________________________________________________");
            return(false); //-- exit the function
            break; //-- exit the for loop
           }
        }

下面是 OpenBuyPosition() 的所有代码段及其正确顺序:

//-------------------------------------------------------------------+
// OpenBuyPosition(): Function to open a new buy entry order.        |
//+------------------------------------------------------------------+
bool OpenBuyPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export
  {
//-- first check if the ea is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the parameters to open a buy position
   tradeRequest.type = ORDER_TYPE_BUY;
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.magic = magicNumber;
   tradeRequest.symbol = symbol;
   tradeRequest.tp = 0;
   tradeRequest.sl = 0;
   tradeRequest.comment = positionComment;
   tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2;

//-- set and moderate the lot size or volume
   lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));  //-- Verify that volume is not less than allowed minimum
   lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX));  //-- Verify that volume is not more than allowed maximum
   lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step
   tradeRequest.volume = lotSize;

//--- Reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function
   ResetLastError();

   for(int loop = 0; loop <= 100; loop++) //-- try opening the order untill it is successful (100 max tries)
     {
      //--- update order opening price on each iteration
      tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_ASK);

      //-- set the take profit and stop loss on each iteration
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits);
        }

      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Print the order details
         PrintOrderDetails("Sent OK");

         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, ": CONFIRMED: Successfully openend a ", symbol, " BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
            PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
            Print("_______________________________________________________________________________________");
            return(true); //-- exit the function
            break; //--- success - order placed ok. exit the for loop
           }
        }
      else //-- Order request failed
        {
         //-- Print the order details
         PrintOrderDetails("Sending Failed");

         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, ": ", symbol, " ERROR opening a BUY POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume);
            Print("_______________________________________________________________________________________");
            return(false); //-- exit the function
            break; //-- exit the for loop
           }
        }
     }
   return(false);
  }


OpenSellPositios() 函数

该函数与我们在上面完成编码的 OpenBuyPosition() 函数非常相似,并遵循相同的过程,只是在处理订单类型等方面有一些不同。OpenSellPosition() 函数旨在打开一个新的卖出仓位。它包括一个 for 循环,在失败时进行多次尝试,只要提供有效的交易请求参数,成功率就会大大提高。以下是 OpenSellPosition() 函数代码:

//-------------------------------------------------------------------+
// OpenSellPosition(): Function to open a new sell entry order.      |
//+------------------------------------------------------------------+
bool OpenSellPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export
  {
//-- first check if the ea is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the parameters to open a sell position
   tradeRequest.type = ORDER_TYPE_SELL;
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.magic = magicNumber;
   tradeRequest.symbol = symbol;
   tradeRequest.tp = 0;
   tradeRequest.sl = 0;
   tradeRequest.comment = positionComment;
   tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2;

//-- set and moderate the lot size or volume
   lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));  //-- Verify that volume is not less than allowed minimum
   lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX));  //-- Verify that volume is not more than allowed maximum
   lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step
   tradeRequest.volume = lotSize;

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try opening the order (101 max) times untill it is successful
     {
      //--- update order opening price on each iteration
      tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_BID);

      //-- set the take profit and stop loss on each iteration
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price - (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price + (sl * _Point), _Digits);
        }

      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Print the order details
         PrintOrderDetails("Sent OK");

         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print("CONFIRMED: Successfully openend a ", symbol, " SELL POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
            PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
            Print("_______________________________________________________________________________________");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- Order request failed
        {
         //-- Print the order details
         PrintOrderDetails("Sending Failed");

         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped())
           {
            Print(symbol, " ERROR opening a SELL POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume);
            Print("_______________________________________________________________________________________");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }
   return(false);
  }


仓位止损和止盈修改函数


库中的下一个函数名为 SetSlTpByTicket(),负责使用仓位编号作为过滤机制,修改现有未结仓位的止损(SL)和止盈(TP)水平。它将仓位的交易单编号、以点为单位的预期止损点数(SL)和以点为单位的预期止盈点数(TP)作为参数,并尝试在交易服务器上更新仓位的止损点数(SL)和止盈点数(TP)。函数将返回一个布尔值(truefalse)。如果成功修改了仓位的止损和止盈水平,则返回 true;如果无法成功设置或修改止损或止盈水平,则返回 false

以下是 SetSlTpByTicket() 函数参数的详细说明:

  1. ulong positionTicket:这是我们要修改的仓位的唯一标识符。
  2. int sl:这是以距离开盘价点数(pip)为单位的理想止损位。
  3. int tp:这是所需的止盈水平,单位是距离开盘价的点数(pip)。

记得在函数定义中使用 export 后修饰符,以便我们的程序库可以从外部访问它。

bool SetSlTpByTicket(ulong positionTicket, int sl, int tp) export
  {
//-- Function body
  }

与上述其他函数一样,我们首先要使用 TradingIsAllowed() 函数检查我们的 EA 交易是否允许交易。如果交易被禁止,函数退出并返回 false。

//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

在选择使用 positionTicket 参数指定的仓位之前,我们将首先重置运行时错误代码系统变量,以便为稍后的 ErrorAdvisor() 函数获取准确的可操作错误响应。如果选择成功,就会打印一条信息,表明仓位已选定,从而为我们访问仓位属性开了绿灯。如果选择失败,则会打印错误信息以及使用 GetLastError() 获取的错误代码。然后函数返回 false,退出。

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to set SLTP.");
     }
   else
     {
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

选择仓位后,我们需要收集并保存有关该仓位的一些详细信息。我们将利用这些信息进行计算,供以后参考。

//-- create variables to store the calculated tp and sl prices to send to the trade server
   double tpPrice = 0.0, slPrice = 0.0;
   double newTpPrice = 0.0, newSlPrice = 0.0;

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   double volume = PositionGetDouble(POSITION_VOLUME);
   double currentPositionSlPrice = PositionGetDouble(POSITION_SL);
   double currentPositionTpPrice = PositionGetDouble(POSITION_TP);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

我们还需要一些特定交易品种的信息:

//-- Get some information about the positions symbol
   int symbolDigits = (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS); //-- Number of symbol decimal places
   int symbolStopLevel = (int)SymbolInfoInteger(positionSymbol, SYMBOL_TRADE_STOPS_LEVEL);
   double symbolPoint = SymbolInfoDouble(positionSymbol, SYMBOL_POINT);
   double positionPriceCurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
   int spread = (int)SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD);

现在,让我们根据仓位的开仓价、以点为单位的所需止损/止盈价格以及仓位类型(买入或卖出)计算新的止损 (SL) 和止盈 (TP) 价格。在验证之前,我们将存储这些初始计算结果:

//--Save the non-validated tp and sl prices
   if(positionType == POSITION_TYPE_BUY) //-- Calculate and store the non-validated sl and tp prices
     {
      newSlPrice = entryPrice - (sl * symbolPoint);
      newTpPrice = entryPrice + (tp * symbolPoint);
     }
   else  //-- SELL POSITION
     {
      newSlPrice = entryPrice + (sl * symbolPoint);
      newTpPrice = entryPrice - (tp * symbolPoint);
     }

接下来,我们打印之前收集的仓位详细信息摘要:

//-- Print position properties before modification
   string positionProperties = "--> "  + positionSymbol + " " + EnumToString(positionType) + " SLTP Modification Details" +
   " <--\r\n";
   positionProperties += "------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", volume) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New Proposed SL: " + (string)newSlPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New Proposed TP: " + (string)newTpPrice + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "---";
   Print(positionProperties);

由于库用户提供的 SL 和 TP 值可能无法直接用于 OrderSend() 函数,在继续之前,我们需要对它们的值进行简单的验证:

//-- validate the sl and tp to a proper double that can be used in the OrderSend() function
   if(sl == 0)
     {
      slPrice = 0.0;
     }
   if(tp == 0)
     {
      tpPrice = 0.0;
     }

现在,我们需要根据之前保存的交易品种详细信息执行更复杂的验证。我们将把验证逻辑分为两组,一组用于买入仓位,另一组用于卖出仓位。止损和止盈的验证将基于交易品种的当前价格、最低止损水平限制以及交易品种的点差。

如果指定的 TP 或 SL 价格无效,并且超出了所需范围,则将保留原始 TP 或 SL 价格,并打印一条信息,解释修改失败的原因。完成 SL 和 TP 值验证后,我们将打印另一份摘要,记录已确认和验证的值,以供参考:

//--- Check if the sl and tp are valid in relation to the current price and set the tpPrice
   if(positionType == POSITION_TYPE_BUY)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice + (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice - (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP is valid
         tp > 0 &&
         (
            newTpPrice <= entryPrice + (spread * symbolPoint) ||
            newTpPrice <= positionPriceCurrent ||
            (
               newTpPrice - entryPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent > entryPrice && newTpPrice - positionPriceCurrent < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice >= positionPriceCurrent ||
            entryPrice - newSlPrice < symbolStopLevel * symbolPoint ||
            positionPriceCurrent - newSlPrice < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }
   if(positionType == POSITION_TYPE_SELL)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice - (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice + (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP price is valid
         tp > 0 &&
         (
            newTpPrice >= entryPrice - (spread * symbolPoint) ||
            newTpPrice >= positionPriceCurrent ||
            (
               entryPrice - newTpPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent < entryPrice && positionPriceCurrent - newTpPrice < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice <= positionPriceCurrent ||
            newSlPrice - entryPrice < symbolStopLevel * symbolPoint ||
            newSlPrice - positionPriceCurrent < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }

//-- Print verified position properties before modification
   positionProperties = "---\r\n";
   positionProperties += "--> Validated and Confirmed SL and TP: <--\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + ", Price Current: " + StringFormat("%G", positionPriceCurrent) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New SL: " + (string)slPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New TP: " + (string)tpPrice + "\r\n";
   Print(positionProperties);

现在我们有了经过验证的 SL 值和 TP 值,是时候向交易服务器发送请求来修改它们了。我们使用 ZeroMemory() 函数 来清除 tradeRequesttradeResult 结构,确保它们不包含之前操作的残余数据。然后,我们用以下信息初始化 tradeRequest 结构:

  • action:设置为TRADE_ACTION_SLTP,表示修改止损和止盈。
  • position:设置为 positionTicket,以指定我们正在处理的仓位。
  • symbol:设置为 positionSymbol,用于标识此仓位的交易品种。
  • sl:设置为包含已验证止损值的 slPrice
  • tp:设置为包含有效止盈值的 tpPrice

接下来,我们将调用 ResetLastError() 函数清除内部存储的所有错误代码。这可确保我们在订单发送过程中获得准确的错误代码。

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

我们准备将订单发送到交易服务器。不过,经验告诉我,由于临时网络问题或服务器超载,订单执行可能偶尔会失败。这意味着我们需要找到一种智能的方法来处理重试发送订单。为了解决这个问题,我们将使用一个 for 循环,最多迭代 101 次(loop <= 100)。这种重试机制有助于处理订单执行过程中可能出现的临时错误。

在 for 循环中,我们使用 OrderSend() 发送包含在 tradeRequest 中的订单请求,并将结果存储在 tradeResult 中。如果 OrderSend() 返回 true,则表示已成功更改 SL 和 TP 价格,订单请求已顺利完成。

我们还将通过检查 tradeResult.retcode 中的特定代码(10008 或 10009)进行最后确认,这些代码表示该仓位的 SL/TP 修改成功。如果代码匹配,我们会打印一条确认信息,其中包含仓位编号、交易品种和返回代码等详细信息。然后,我们使用 return(true) 成功退出函数。break 语句退出循环只是为了确保我们退出 for 循环,以避免不必要的迭代。如果 OrderSend() 返回 falseretcode 与成功代码不匹配,则表示出错。

//-- initialize the parameters to set the sltp
   tradeRequest.action = TRADE_ACTION_SLTP; //-- Trade operation type for setting sl and tp
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.sl = slPrice;
   tradeRequest.tp = tpPrice;

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try modifying the sl and tp 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            PrintFormat("Successfully modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- Order request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            PrintFormat("ERROR modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }

以下是 SetSlTpByTicket() 函数的所有代码段及其正确顺序。确保您的函数包含以下代码的所有组件:

bool SetSlTpByTicket(ulong positionTicket, int sl, int tp) export
  {
//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to set SLTP.");
     }
   else
     {
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

//-- create variables to store the calculated tp and sl prices to send to the trade server
   double tpPrice = 0.0, slPrice = 0.0;
   double newTpPrice = 0.0, newSlPrice = 0.0;

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   double volume = PositionGetDouble(POSITION_VOLUME);
   double currentPositionSlPrice = PositionGetDouble(POSITION_SL);
   double currentPositionTpPrice = PositionGetDouble(POSITION_TP);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

//-- Get some information about the positions symbol
   int symbolDigits = (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS); //-- Number of symbol decimal places
   int symbolStopLevel = (int)SymbolInfoInteger(positionSymbol, SYMBOL_TRADE_STOPS_LEVEL);
   double symbolPoint = SymbolInfoDouble(positionSymbol, SYMBOL_POINT);
   double positionPriceCurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
   int spread = (int)SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD);

//--Save the non-validated tp and sl prices
   if(positionType == POSITION_TYPE_BUY) //-- Calculate and store the non-validated sl and tp prices
     {
      newSlPrice = entryPrice - (sl * symbolPoint);
      newTpPrice = entryPrice + (tp * symbolPoint);
     }
   else  //-- SELL POSITION
     {
      newSlPrice = entryPrice + (sl * symbolPoint);
      newTpPrice = entryPrice - (tp * symbolPoint);
     }

//-- Print position properties before modification
   string positionProperties = "--> "  + positionSymbol + " " + EnumToString(positionType) + " SLTP Modification Details" +
   " <--\r\n";
   positionProperties += "------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", volume) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New Proposed SL: " + (string)newSlPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New Proposed TP: " + (string)newTpPrice + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "---";
   Print(positionProperties);

//-- validate the sl and tp to a proper double that can be used in the OrderSend() function
   if(sl == 0)
     {
      slPrice = 0.0;
     }
   if(tp == 0)
     {
      tpPrice = 0.0;
     }

//--- Check if the sl and tp are valid in relation to the current price and set the tpPrice
   if(positionType == POSITION_TYPE_BUY)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice + (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice - (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP is valid
         tp > 0 &&
         (
            newTpPrice <= entryPrice + (spread * symbolPoint) ||
            newTpPrice <= positionPriceCurrent ||
            (
               newTpPrice - entryPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent > entryPrice && newTpPrice - positionPriceCurrent < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice >= positionPriceCurrent ||
            entryPrice - newSlPrice < symbolStopLevel * symbolPoint ||
            positionPriceCurrent - newSlPrice < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }
   if(positionType == POSITION_TYPE_SELL)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice - (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice + (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP price is valid
         tp > 0 &&
         (
            newTpPrice >= entryPrice - (spread * symbolPoint) ||
            newTpPrice >= positionPriceCurrent ||
            (
               entryPrice - newTpPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent < entryPrice && positionPriceCurrent - newTpPrice < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice <= positionPriceCurrent ||
            newSlPrice - entryPrice < symbolStopLevel * symbolPoint ||
            newSlPrice - positionPriceCurrent < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }

//-- Print verified position properties before modification
   positionProperties = "---\r\n";
   positionProperties += "--> Validated and Confirmed SL and TP: <--\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + ", Price Current: " + StringFormat("%G", positionPriceCurrent) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New SL: " + (string)slPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New TP: " + (string)tpPrice + "\r\n";
   Print(positionProperties);

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the parameters to set the sltp
   tradeRequest.action = TRADE_ACTION_SLTP; //-- Trade operation type for setting sl and tp
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.sl = slPrice;
   tradeRequest.tp = tpPrice;

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try modifying the sl and tp 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            PrintFormat("Successfully modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- Order request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            PrintFormat("ERROR modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }
   return(false);
  }


仓位关闭函数


该函数名为 ClosePositionByTicket(),将使用结构化方法确保我们能根据仓位编号有效平仓。它以仓位的编号为参数。它将检查是否允许交易,使用提供的编号选择仓位,检索并打印其属性,准备交易请求,并尝试平仓,同时处理发生的任何错误。

首先,我们定义函数并指定它将返回一个布尔值truefalse),并接受一个参数作为参数。

bool ClosePositionByTicket(ulong positionTicket) export
  {
//--- Function body
  }

然后,我们将检查 EA 交易是否允许交易。

//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

接下来,我们使用 ResetLastError() 函数重置之前的错误,然后使用提供的仓位编号选择仓位。如果仓位被选中,我们会打印一条确认选择的信息;如果选择失败,我们会打印一条错误信息,并返回 false 退出函数。

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to be closed.");
     }
   else
     {
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

成功选择仓位后,我们将保存其属性并打印出来。

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double positionVolume = PositionGetDouble(POSITION_VOLUME);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

//-- Print position properties before closing it
   string positionProperties;
   positionProperties += "-- "  + positionSymbol + " " + EnumToString(positionType) + " Details" +
   " -------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", PositionGetDouble(POSITION_VOLUME)) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", PositionGetDouble(POSITION_PRICE_OPEN)) + "\r\n";
   positionProperties += "SL: " + StringFormat("%G", PositionGetDouble(POSITION_SL)) + "\r\n";
   positionProperties += "TP: " + StringFormat("%G", PositionGetDouble(POSITION_TP)) + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "_______________________________________________________________________________________";
   Print(positionProperties);

接下来,我们使用 ZeroMemory() 函数重置 tradeRequesttradeResult 数据结构值,清除其中的任何先前数据。然后,通过将交易操作设置为 TRADE_ACTION_DEAL (表示交易终止操作)、仓位编号、交易品种、交易量和价格偏差,初始化交易请求参数,以平仓。

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the trade reqiest parameters to close the position
   tradeRequest.action = TRADE_ACTION_DEAL; //-- Trade operation type for closing a position
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.volume = positionVolume;
   tradeRequest.deviation = SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD) * 2;

现在,我们需要根据仓位是买入还是卖出来确定仓位的收盘价和订单类型。

//--- Set the price and order type of the position being closed
   if(positionType == POSITION_TYPE_BUY)
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID);
      tradeRequest.type = ORDER_TYPE_SELL;
     }
   else//--- For sell type positions
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK);
      tradeRequest.type = ORDER_TYPE_BUY;
     }

最后,我们使用 ResetLastError() 函数重置之前的错误,然后尝试发送交易请求平仓。我们将使用 for 循环多次尝试发送平仓请求,以确保即使在互联网连接较弱或发生非关键错误时也能平仓。如果订单成功发送并执行(返回代码 10008 或 10009),我们将打印成功信息并返回 true。如果订单失败,我们会调用 ErrorAdvisor() 函数来处理错误。如果 ErrorAdvisor() 函数显示严重错误或 EA 交易停止运行,我们会打印错误信息并返回 false,表示平仓失败。

ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try closing the position 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("Successfully closed position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- position closing request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("ERROR closing position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }

确保按以下顺序排列上述所有代码段。下面是按适当顺序排列的所有 ClosePositionByTicket() 函数代码段:

bool ClosePositionByTicket(ulong positionTicket) export
  {
//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to be closed.");
     }
   else
     {
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double positionVolume = PositionGetDouble(POSITION_VOLUME);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

//-- Print position properties before closing it
   string positionProperties;
   positionProperties += "-- "  + positionSymbol + " " + EnumToString(positionType) + " Details" +
   " -------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", PositionGetDouble(POSITION_VOLUME)) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", PositionGetDouble(POSITION_PRICE_OPEN)) + "\r\n";
   positionProperties += "SL: " + StringFormat("%G", PositionGetDouble(POSITION_SL)) + "\r\n";
   positionProperties += "TP: " + StringFormat("%G", PositionGetDouble(POSITION_TP)) + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "_______________________________________________________________________________________";
   Print(positionProperties);

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the trade reqiest parameters to close the position
   tradeRequest.action = TRADE_ACTION_DEAL; //-- Trade operation type for closing a position
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.volume = positionVolume;
   tradeRequest.deviation = SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD) * 2;

//--- Set the price and order type of the position being closed
   if(positionType == POSITION_TYPE_BUY)
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID);
      tradeRequest.type = ORDER_TYPE_SELL;
     }
   else//--- For sell type positions
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK);
      tradeRequest.type = ORDER_TYPE_BUY;
     }

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try closing the position 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("Successfully closed position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- position closing request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("ERROR closing position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }
   return(false);
  }

保存并编译该库的 PositionsManager.mq5 源代码文件,你会发现一个新的 PositionsManager.ex5 库文件在 libraries\Toolkit\ 文件夹中生成了一个新的 PositionsManager.ex5 库文件。


结论

至此,您已经对 MQL5 ex5 库及其创建过程有了扎实的了解。在下一篇文章中,我们将扩展我们的仓位管理库,为各种仓位管理任务提供更多的功能,然后通过不同的实际示例演示如何在任何 MQL5 项目中实施 ex5 库。本文底部附有该库的源代码文件 PositionsManager.mq5,其中包括我们上面创建的所有函数。


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

附加的文件 |
开发回放系统(第 53 部分):事情变得复杂(五) 开发回放系统(第 53 部分):事情变得复杂(五)
在本文中,我们将介绍一个很少有人了解的重要话题:定制事件。危险。这些要素的优缺点。对于希望成为 MQL5 或其他语言专业程序员的人来说,本主题至关重要。在此,我们将重点介绍 MQL5 和 MetaTrader 5。
情绪分析与深度学习在交易策略中的应用以及使用Python进行回测 情绪分析与深度学习在交易策略中的应用以及使用Python进行回测
在本文中,我们将介绍如何使用Python中的情绪分析和ONNX模型,并将它们应用于EA中。使用一个脚本运行TensorFlow训练的ONNX模型,以进行深度学习预测;而通过另一个脚本获取新闻标题,并使用人工智能技术量化情绪。
神经网络变得简单(第 87 部分):时间序列补片化 神经网络变得简单(第 87 部分):时间序列补片化
预测在时间序列分析中扮演重要角色。在新文章中,我们将谈谈时间序列补片化的益处。
用于预测波动性的计量经济学工具:GARCH模型 用于预测波动性的计量经济学工具:GARCH模型
文章描述了条件异方差非线性模型(GARCH)的特性。在GARCH模型的基础上,构建了iGARCH指标来预测未来一步的波动性。该模型参数的估计使用了ALGLIB数值分析库。