外汇EA编写教程:使用MetaTrader 5中的对冲终端面板进行双向交易和头寸对冲,第二部分

目录

  • 简介

  • 第1章。EA和HedgetTerminal API及其面板之间的交互
    • 1.1。安装Hedgetrmianl API。第一启动库
    • 1.2。EA与HedgetTerminal面板集成
    • 1.3。HedgetTerminal API操作的一般原则
    • 1.4。交易选择
    • 1.5。使用getHedgeError()获取错误代码
    • 1.6。使用total actions任务()和getaction结果()详细分析事务并识别错误
    • 1.7。跟踪任务执行状态
    • 1.8。双向头寸的修改与清算
    • 1.9。从EA设置HedgetTerminal属性
    • 1.10。同步和异步操作模式
    • 1.11。通过脚本例程管理双向仓库属性
    • 1.12。混沌II EA例程中的sendtraderrequest函数和hedgetraderrequest结构
    • 1.13。经纪人虚拟复制品
  • 第二章。对冲终端API手册
    • 2.1。事务选择功能
      • 函数事务总数()
      • 函数TransactionType()
      • 函数事务选择()
      • 函数HedgeOrderSelect()。
      • 函数HedgedDealSelect()。
    • 2.2。获取所选事务属性的函数
      • 函数hedgepositiongetinteger()
      • 函数HedgepositionGetDouble()
      • 函数HedgepositionGetString()
      • 函数HedgeOrderGetInteger()
      • 函数HedgeOrderGetDouble()
      • 函数HedgedDealeGetInteger()
      • 函数HedgedDealgetDouble()
    • 2.3。从EA设置和检索HedgetTerminal属性的函数
      • 函数HedgePropertySetInteger()
      • 函数HedgeProperty GetInteger()
    • 2.4。获取和处理错误代码的函数
      • 函数GetHedgeError()
      • 功能重置错误()
      • 函数总计操作任务()
      • 函数GetActionResult()
    • 2.5。交易
      • 函数sendTradeRequest()
      • 交易请求结构HedgetTradeRequest
    • 2.6。使用事务选择函数枚举操作
      • 枚举转换类型
      • 枚举模式选择
      • 枚举模式交易
    • 2.7。获取事务属性函数的操作枚举
      • 枚举变换方向
      • 枚举对冲头寸状态
      • 枚举对冲头寸状态
      • 枚举对冲位置属性整数
      • Enum_Hedge_Position_Prop_Double
      • Enum_Hedge_Position_Prop_字符串
      • 枚举对冲订单状态
      • Enum_Hedge_Order_Selected_类型
      • 枚举对冲顺序属性整数
      • Enum_Hedge_Order_Prop_Double
      • Enum_Hedge_Deal_Prop_Integer
      • Enum_Hedge_Deal_Prop_Double
    • 2.8。设置并获取HedgetTerminal属性的枚举
      • 枚举对冲属性整数
    • 2.9。具有错误处理代码函数的操作的枚举
      • 枚举任务状态
      • 枚举
      • 枚举目标类型
    • 2.10。Enumeration of operations with transaction requests
      • 枚举请求类型
      • 枚举关闭类型
  • 第3章。异步交易的基础
    • 3.1。同步交易指令组织发送方案
    • 3.2。一种组织和发送异步事务订单的方案
    • 3.3。异步订单执行速度
  • 第4章。Meta Atter 5集成开发环境中的多线程编程基础
    • 4.1。以联合交换引号为例的多线程编程
    • 4.2。使用多线程

      的EAS之间的交互

  • 附加说明
  • 结论

介绍

本文是“元交易者5中使用对冲终端(对冲终端)面板的双向交易和头寸对冲的第一部分”的延续。在第二部分中,我们将讨论EA和其他MQL5程序与HedgetTerminalAPI库的集成。阅读本文了解如何操作图书馆。它将帮助您在舒适简单的交易环境中创建双向交易EA。

除了对库的描述外,本文还讨论了异步事务的基础和多线程编程。这些描述将在本文的第3节和第4节中给出。因此,这些信息对于那些对双向交易不感兴趣的交易者同样有用,因为他们可以找到一些关于异步和多线程编程的新内容。

下面提供的信息是为了解MQL5编程语言的经验丰富的算法交易者提供的。如果您不了解MQL5,请阅读本文的第一部分,其中包含解释库和HedgetTerminal面板的一般原则的简单图表和绘图。


第1章。EA和HedgeTerminal及其面板之间的交互。

1.1。安装Hedgetrmianl API。第一启动库

安装HedgetTerminalAPI的过程与安装HT可视化面板的过程不同,因为库不能在MetaTrader 5中独立运行。换句话说,您需要开发一个专门的EA来从库中调用HedgetTerminalInstall()函数。此函数将设置一个特殊的头文件原型。mqh描述HedgeTerminal API中可用的函数。

在计算机上安装库有三个步骤:

步骤1。将HedgetTerminalAPI库下载到您的计算机。与您的终端位置相关的库:/mql5/experts/market/hedgetterminalapi.ex5。

步骤2。使用MQL5向导中的标准模板创建新的EA。mql5向导生成以下源代码:

//+------------------------------------------------------------------+
//|                                   InstallHedgeTerminalExpert.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| EA 初始化函数                                                      |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| EA 逆初函数                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| EA 即时报价函数                                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  }

步骤3。结果EA只需要一个函数-OnInit(),并指定从HedgetTerminalAPI库导出的EdgeTerminalInstall()安装程序函数。在OnInit()函数中正确运行此函数。以黄色标记的源代码执行这些操作:

//+------------------------------------------------------------------+
//|                                   InstallHedgeTerminalExpert.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#import "HedgeTerminalAPI.ex5"
   void HedgeTerminalInstall(void);
#import

//+------------------------------------------------------------------+
//| EA 初始化函数                                                      |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   HedgeTerminalInstall();
   ExpertRemove();   
//---
   return(INIT_SUCCEEDED);
  }

步骤4。您的进一步操作取决于您是否购买了库。如果您购买了它,您可以直接在图形上实时运行EA。这将启动整个HedgetTerminal产品线的标准安装程序。您只需按照“使用MetaTrader 5第I部分中的对冲终端面板进行双向交易和头寸对冲”一文第2.1和2.2节中所述的说明完成此步骤。安装向导将在您的计算机上安装所有必需的文件,包括头文件和测试EA。

如果您不购买这个库,只想测试它,就不能实时地操作EA,但是可以在策略测试中运行EA来测试API。在这种情况下,安装程序将不运行。在测试模式下,HedgeTermianal API在单用户模式下工作,因此不需要在正常模式下安装文件。也就是说,您不需要任何配置。

一旦EA测试完成,将在终端的公用文件夹中创建文件夹/对冲终端。metatrader终端公用目录的正常路径是c:/users/<username>/appdata/roaming/metaquotes/terminal/common/files/hedgetterminal/,其中<username>是您当前使用的计算机帐户的名称。The / HedgeTerminal folder already contains files / MQL5 / Include / Prototypes. mqh和/mql5/专家/chaos2。MQ5。将这些文件复制到终端的同一目录:files prototype.mqh to/metatrader5/mql5/include,以及文件chaos 2.mq5 to/metatrader5/mql5/experts。

文件原型。mqh是一个头文件,包含从HedgetTerminalAPI库导出的函数描述。他们的目的和描述包含在他们的笔记中。

文件chaos 2.mq5包含“chaos II EA例程中的sendtraderrequest函数和hedgetraderrequest结构”一节中描述的EA例程。通过这种方式,您可以直观地了解HedgetTerminalAPI是如何工作的,以及如何使用HedgetTerminal虚拟化技术开发EA。

复制的文件可以用于EA。所以您只需要在EA源代码中包含头文件就可以开始使用这个库了。下面是一个例子:

#include <Prototypes.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int transTotal = TransactionsTotal();
   printf((string)transTotal);
  }

例如,上面的代码捕获职位总数,并在MetaTrader 5终端的专家列中显示数字。

重要的是要理解HedgeTerminal在首次调用其任何函数时初始化。初始化就是调用lazy。因此,第一次调用它的任何函数都需要很长的时间。如果您希望在第一次调用时得到快速响应,则必须预先初始化ht,例如,可以调用OnInit()模块中的TransactionTotal()函数。

使用惰性初始化,可以省略EA中的显式初始化。这大大简化了对冲终端的操作,无需提前配置。

1.2。EA与HedgetTerminal面板集成

如果您有HedgetTerminal可视化面板和功能齐全的库版本,您可以实时运行,并且您可以将EA与面板集成,以便通过它们执行的所有事务也可以显示在面板中。通常,集成是不可见的。如果使用HedgetTerminal API功能,机器人执行的操作将自动显示在面板上。但是,您可以通过在每个提交的事务中指定EA名称来扩展可见性。您可以通过在文件设置中取消对以下代码行的注释来完成此操作。XML:

<Column ID="Magic" Name="Magic" Width="100"/>

此标记位于“活动位置”和“历史位置”部分。

现在,注释已经被删除,标签也包含在流程中。面板重新启动后,活动和历史仓库表中将出现一个新的“magic”列。此列包含仓库所属的EA的幻数。

如果要显示EA名称而不是幻数,请将名称添加到别名文件Expertiales中。XML。例如,EA的幻数是123847,您希望显示其名称,例如“expro 1.1”,并将以下标签添加到文件中:

<Expert Magic="123847" Name="ExPro 1.1"></Expert>

如果正确完成,则EA名称将替换相应列中的幻数:

图例. 1.  显示 EA 名字替代魔幻数字

传说。1。显示EA名称而不是幻数

注意,面板和EA之间的通信是实时的。这意味着,如果您直接在面板上清算某个EA的头寸,那么下次调用TransactionStotal()函数时,EA就会知道这一点。相反,情况也是如此:当EA清算其头寸时,它会立即从激活的头寸栏中消失。

1.3。HedgetTerminal API操作的一般原则

除了双向头寸,对冲终端还可以处理其他类型的交易,如提单、交易和经纪业务。HedgetTerminal将所有类型视为单个交易组。一笔交易,一张账单,一个双向头寸——所有这些都是交易。但是,事务不能单独存在。在面向对象编程中,事务可以扩展到抽象基准类,并且所有事务实例(如事务和双向位置)都继承自该类。在这方面,HedgetTerminal API的所有功能都可以分为几个组:

  1. 事务搜索和选择功能。元交易者4函数ordersend()和orderselect()的共同签名和工作模式几乎相同。
  2. 获取所选事务属性的函数。每个事务都有一组特殊的属性和一个特殊的选择属性函数。函数联合签名及其工作方式类似于访问仓库、事务和订单的MetaTrader 5系统函数(如OrderGetDouble()或HistoryDealeGetInteger())。
  3. HedgeTerminal API仅使用一个事务函数:sendTradeRequest()。此功能可以关闭双向或部分位置。相同的功能可用于修改停止损耗、停止增益或非现场注释。该函数的工作方式与metatrader 5中的ordersend()类似。
  4. 获取一般错误函数getHedgeError(),用于详细分析HedgeTerminal事务行为:total actions task()和getAction result()。它还用于错误检测。在MetaTrader 4或MetaTrader 5中没有类似的功能。

所有函数的操作与使用metatrader 4和metatrader 5的系统函数类似。通常,函数的输入是一些标识符(枚举值),函数返回相应的值。

每个函数都有一个指定的枚举可用。常见的呼叫签名如下:

<value> = Function(<identifier>);

让我们来看一个获取位置唯一标识符的示例。这就是它在MetaTrader 5中看到的:

ulong id = PositionGetInteger(POSITION_IDENTIFIER);

在对冲终端中,接收双向位置的唯一标识符如下:

ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID)

功能的一般原理是相同的。只有一种枚举类型不同。

1.4。交易选择

浏览交易列表以选择交易类似于在MetaTrader 4中搜索订单。然而,在MetaTrader 4中,只能搜索订单,而在HedgetTerminal中,所有内容都可以搜索为交易,例如账单或对冲头寸。因此,应首先使用TransactionSelect()函数选择每个事务,然后通过TransactionType()标识其类型。

两个事务列表可用于日期:活动事务和历史事务。列表的应用程序定义为基于枚举模式交易修饰符。It is similar to the MODE_TRADES modifier in MetaTrader 4.

事务搜索和选择算法如下:

1: for(int i=TransactionsTotal(MODE_TRADES)-1; i>=0; i--)
2:     {
3:      if(!TransactionSelect(i,SELECT_BY_POS,MODE_TRADES))continue;
4:      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;
5:      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;
6:      if(HedgePositionGetString(HEDGE_POSITION_SYMBOL) != Symbol())continue;
7:      if(HedgePositionGetInteger(HEDGE_POSITION_STATE) == POSITION_STATE_FROZEN)continue;
8:      ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID)
9:     }

代码迭代for(第1行)循环中的活动事务列表。在处理事务之前,请使用TransactionSelect()(第3行)选择它。从这些交易中仅选择双向头寸(第4行)。如果仓库的幻数与当前运行和EA的幻数不匹配,则HT将移动到下一个仓库(第5行和第6行)。然后定义位置的唯一标识符(第8行)。

特别注意7号线。应检查所选位置是否可能修改条件。如果仓库处于修改过程中,则无法在当前线程中对其进行更改,尽管您可以获取其属性。如果仓库被锁定,最好等到它被解锁后才能访问其属性,或者再次尝试修改它。属性对冲位置状态用于查询仓库是否可以修改。

位置“冻结”修改器表示位置“冻结”,不能更改。位置状态活动修改器表示位置处于活动状态,可以更改。这些修饰符列在枚举对冲位置状态枚举中,并记录在相应的章节中。

如果需要遍历一个历史事务,那么函数transactiontotal()和transactionselect()中的模式_trades修饰符必须替换为模式_history。

在对冲终端中,一个交易可以嵌套另一个交易。这与MetaTrader 5中的概念非常不同,因为它没有嵌套。例如,对冲终端中的历史双向头寸由两个订单组成,每个订单包含一个交易。嵌套可以表示为:

图例. 2. 嵌套事务

传说。2。嵌套事务

在HedgetTerminal可视面板上可以清楚地看到事务嵌套。

以下屏幕截图说明了magicex 1.3位置的详细信息:

图例. 3. 在 HedgeTerminal 面板上的嵌套事务

传说。三。对冲终端面板上的嵌套交易

可以访问双向位置中特定订单或事务的属性。

为此目的:

  1. 选择历史交易并确认它是双向位置。
  2. 使用HedgeOrderSelect()选择此位置的订单;
  3. 获取用于选择订单的属性:包含的交易号;
  4. 通过遍历所有事务来选择事务所属的顺序。
  5. 获取所需的事务属性。

注意:当选择事务时,它的指定属性将变为可用。例如,如果一个交易是一个订单,您可以在通过HedgeOrderSelect()选择后找到它的交易号(HedgeOrder_Deals_Total)或平均加权准入价格(Hedge_Deal_Price Executed)。

让我们看看97610的价格,它在屏幕截图中用红色标记。这笔交易是magicex 1.3ea双向定位的一部分。

通过以下代码,EA可以访问其仓库及其事务:

#include <Prototypes.mqh>

ulong Magic=5760655; // MagicEx 1.3.

//+------------------------------------------------------------------+
//| EA 即时报价函数                                                    |
//+------------------------------------------------------------------+ 
void OnTick()
  {
   for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
    {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;        // 选择事务 #i;
      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;                 // 如果事务不是仓位 - 继续;
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;  // 如果仓位不是主要的 - 继续;
      ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID);   // 获取平仓订单 id;
      if(id!=5917888)continue;                                             // 如果仓位 id  != 5917888 - 继续;
      printf("1: -> 选择仓位 #"+(string)id);                        // 打印仓位 id;
      if(!HedgeOrderSelect(ORDER_SELECTED_CLOSED))continue;                // 选择平仓订单或继续;    
      ulong order_id = HedgeOrderGetInteger(HEDGE_ORDER_ID);               // 获取平仓订单 id;
      printf("2: ----> 选择订单 #" + (string)order_id);                // 打印订单 id;
      int deals_total = (int)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);// 获取已选订单的成交总数;
      for(int deal_index = deals_total-1; deal_index >= 0; deal_index--)   // 搜索成交 #1197610...
        {
         if(!HedgeDealSelect(deal_index))continue;                         // 以索引选择成交或继续;
         ulong deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);               // 获取当前成交 id;
         if(deal_id != 1197610)continue;                                   // 选择成交 #1197610;
         double price = HedgeDealGetDouble(HEDGE_DEAL_PRICE_EXECUTED);     // 获取执行价格;
         printf("3: --------> 选择成交 #"+(string)deal_id+              // 打印执行价格;
              ". Executed price = "+DoubleToString(price,0));
        }
     }
  }

代码执行后,以下条目将出现在MetaTrader 5终端的EA日志栏中:

2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      3: --------> 选择成交 #1197610. Executed price = 4735
2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      2: ----> 选择订单 #6389111
2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      1: -> 选择仓位 #5917888

EA首先选择仓库位置,然后选择仓库89111中的订单。选择订单后,EA开始搜索交易号1197610。当找到一个事务时,EA会得到它的执行价格并在日志中输出该价格。

1.5。如何使用getHedgeError()获取错误代码

在对冲终端环境中工作时,可能会出现错误和意外情况。在这些情况下,使用了误差采集和分析功能。

最简单的情况是,当您忘记使用TransactionSelect()函数选择事务时,会出现错误。函数transactionType()返回在这种情况下未定义的修饰符trans。

为了理解问题的来源,我们需要得到最后一个错误的修饰符。修饰符将告诉我们事务现在已被选中。以下代码执行以下操作:

for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
  {
   //if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;        // 忘记选择;
   ENUM_TRANS_TYPE type = TransactionType();
   if(type == TRANS_NOT_DEFINED)
   {
      ENUM_HEDGE_ERR error = GetHedgeError();
      printf("错误, 事务类型未定义。原因: " + EnumToString(error));
   }
  }

因此,消息是:

错误, 事务类型未定义。原因: HEDGE_ERR_TRANS_NOTSELECTED

错误ID提示我们在尝试获取事务类型之前忘记选择事务。

所有可能的错误都列在枚举对冲错误结构中。

1.6。使用total actions任务()和getaction结果()详细分析事务并识别错误

除了在HedgetTerminal环境中工作期间发生的错误外,调用sendtraderRequest()也可能导致事务错误。这些类型的错误更难处理。Executing a task through SendTradeRequest () may contain multiple transaction activities (subtasks). 例如,要更改受止损头寸保护的头寸的场外注释,必须执行两个交易操作:

  1. 取消与该止损头寸对应的提单;
  2. 在上一个订单的位置放置带有新注释的新账单。

如果触发新的止损单,其注释将显示为结束注释,这是正确的方式。

但是,任务可以部分执行。假设账单取消成功,但由于什么原因,新订单无法下单。在这种情况下,位置将被保留,但没有停止丢失位置。为了能够处理这个错误,EA需要调用一个特殊任务的记录并搜索子任务是否失败。

这可以使用两个函数完成:total actions task()返回此任务的事务操作(子任务)总数,getaction result()接受子任务索引并返回其类型及其执行结果。因为所有事务操作都是使用标准的metatrader 5工具执行的,所以它们的执行结果对应于事务服务器的返回代码。

通常,故障原因的搜索算法如下:

  1. total actions task()用于获取任务的所有子任务数。
  2. 在for循环中搜索所有子任务。确定每个子任务的类型及其结果。

假设无法放置带有新注释的停止订单,因为执行价格太接近当前价格。

以下例行代码说明了EA如何查找故障原因:

#include <Prototypes.mqh> 

ulong Magic=5760655; // MagicEx 1.3.

//+------------------------------------------------------------------+
//| EA 即时报价函数                                                    |
//+------------------------------------------------------------------+ 
void OnTick()
  {
//检测活跃仓位
   for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
     {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;
      ENUM_TRANS_TYPE type=TransactionType();
      if(type==TRANS_NOT_DEFINED)
        {
         ENUM_HEDGE_ERR error=GetHedgeError();
         printf("错误, 事务未定义。原因: "+EnumToString(error));
        }
      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;
      if(HedgePositionGetString(HEDGE_POSITION_SYMBOL) != Symbol())continue;
      HedgeTradeRequest request;
      request.action=REQUEST_MODIFY_COMMENT;
      request.exit_comment="我的新注释";
      if(!SendTradeRequest(request)) // 出错?
        {
         for(uint action=0; action < TotalActionsTask(); action++)
           {
            ENUM_TARGET_TYPE typeAction;
            int retcode=0;
            GetActionResult(action, typeAction, retcode);
            printf("动作#" + (string)action + ": " + EnumToString(type) +(string)retcode);
           }
        }
     }
  }

代码执行后,将出现以下消息:

动作 #0 TARGET_DELETE_PENDING_ORDER 10009 (TRADE_RETCODE_PLACED)
动作 #1 TARGET_SET_PENDING_ORDER 10015 (TRADE_RETCODE_INVALID_PRICE)

通过比较事务服务器返回代码的标准修饰符的数目,我们发现挂起的订单已成功删除,但新订单的放置失败。事务服务器返回错误代码10015(错误价格),这意味着当前价格与停止价格太接近。

知道这一点,EA可以控制停止位。为此,EA只能使用相同的sendTradeRequest()函数来展开仓库。

1.7。跟踪事务任务执行状态

每个事务任务可以包含任意数量的按顺序执行的子任务。

在异步模式下,任务的执行可以传递几个代码。任务也可能被“冻结”。因此,执行EA的控制任务是必要的。当使用hedgepositiongetinteger()状态修饰符调用hedgepositiongetinteger()函数时,它返回枚举任务状态类型枚举,包括当前仓库任务的状态。

例如,如果发送关仓订单后出现错误,因为仓库没有关闭,您需要获取任务状态。

以下示例显示了一个异步EA代码,该代码执行仓库任务的状态以进行分析:

ENUM_TASK_STATUS status=HedgePositionGetInteger(HEDGE_POSITION_TASK_STATUS);
switch(status)
  {
   case TASK_STATUS_COMPLETE:
      printf("任务完成!");
      break;
   case TASK_STATUS_EXECUTING:
      printf("任务正在执行。等待...");
      Sleep(200);
      break;
   case TASK_STATUS_FAILED:
      printf("记录执行任务。打印记录...");
      for(int i=0; i<TotalActionsTask(); i++)
        {
         ENUM_TARGET_TYPE type;
         uint retcode;
         GetActionResult(i,type,retcode);
         printf("#"+i+" "+EnumToString(type)+" "+retcode);
        }
      break;
   case TASK_STATUS_WAITING:
      printf("任务很快开始。");
      break;
  }

注意,一些复杂的任务需要双重迭代。

在异步模式下,交易环境中的信号变化事件将开始新的迭代。因此,所有迭代都会一个接一个地执行,然后从事务服务器接收相应的信息。在同步模式下,任务执行略有不同。

同步方法使用同步操作模拟器,因为用户可以一次完成复合任务。模拟器的时滞。例如,在子任务开始执行之后,模拟器无法将执行线程返回到EA。相反,它等待一段时间来预测交易环境的变化。然后重新审视交易环境。如果知道子任务已成功完成,则启动下一个子任务。

这个过程会降低整体性能,因为需要等待一段时间。但它可以将复杂的任务分解成非常简单的执行序列,并按顺序调用单个函数。所以您几乎不需要用同步方法分析任务执行记录。

1.8。如何修改和关闭双向位置

sendTradeRequest()函数可用于修改和关闭双向位置。只有三个选项可用于活动位置:

  1. 仓库可以全部或部分关闭。
  2. 可修改止损和止损。
  3. 可以修改出库单。

无法更改历史位置。与metatrader 5中的ordersend()函数类似,sendTradeRequest()使用预编译队列来形成名为hedgetTraderRequest的事务结构。有关sendTradeRequest()函数和HedgetTradeRequest结构的更多详细信息,请参阅文档。该程序显示了混沌II-EA和平面段的位置修改。

1.9。如何通过EA设置对冲终端属性

HedgeTerminal handles a set of attributes, such as refresh rate, number of response seconds waiting from the server, and others.

所有这些属性都在设置中定义。XML。当EA实时运行时,库从文件中读取属性并设置适当的内部参数。在图上测试EA时,文件设置。未使用XML。但是,在某些情况下,您可能需要单独修改EA属性,不管它们是在图中运行还是在策略测试中运行。

这些可以通过一组称为hedgepropertySet的特殊函数来实现。当前版本的集合中只有一个原型:

enum ENUM_HEDGE_PROP_INTEGER
{
   HEDGE_PROP_TIMEOUT,
};

bool HedgePropertySetInteger(ENUM_HEDGE_PROP_INTEGER property, int value)

例如,将库的超时设置为等待服务器响应,然后按如下方式写入:

bool res = HedgePropertySetInteger(HEDGE_PROP_TIMEOUT, 30);

如果在发送异步请求后30秒内未收到服务器响应,则锁定位置将解锁。

1.10。同步和异步操作模式

HedgetTerminal及其API事务完全异步执行。

然而,这种模式要求EA更加复杂。为了隐藏这种复杂性,HedgetTerminalAPI包括一个特殊的同步操作模拟器,允许在传统的同步模式下开发EA,并与HedgetTerminalAPI的异步算法通信。当通过sendTradeRequest()修改和展平双向仓库时,会发生这种交互。此函数允许您在执行事务任务时使用同步模式或异步模式。默认情况下,所有事务操作都由同步操作模拟器同步执行。但是,如果事务请求(HedgetTradeRequest结构)包含显式的特殊标志asynch_mode=true,则事务任务将在异步模式下执行。

在异步模式下,任务将独立于主线程执行。异步EA和HedgetTerminal的异步算法之间的交互还没有完全实现。

同步模拟器非常简单。它按顺序启动子任务,并等待一段时间,直到元交易者5的交易环境发生变化。模拟器分析这些变化并确定当前任务的状态。如果任务成功执行,模拟器将进入下一步。

同步模拟器将导致事务指令执行的轻微延迟。这实际上是由于在元交易者5交易环境中,交易行为的执行反馈需要一点时间。访问环境的主要原因是,实际上,HedgeTermianlAPI无法访问以同步线程模拟器模式进入OnTradeTransaction()处理器的事件。

异步线程之间通过模拟器以及异步和同步线程之间的交互问题非常复杂,没有明确的解决方案。

1.11。通过脚本例程管理双向仓库属性

在下面的脚本中,transactionselect()函数在操作事务列表中搜索所有可用的事务。

从列表中选择每个事务。如果事务是仓库,则将访问并打印它的一些属性。除了仓库的属性外,还打印仓库内的订单和交易属性。首先使用HedgeOrderSelect()和HedgeDealSelect()选择订单和交易。

使用系统函数printf将所有位置及其顺序和事务属性打印在一行中。

//+------------------------------------------------------------------+
//|                                           sample_using_htapi.mq5 |
//|         Copyright 2014, Vasiliy Sokolov, Russia, St.-Petersburg. |
//|                              https://login.mql5.com/ru/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Vasiliy Sokolov."
#property link      "https://login.mql5.com/ru/users/c-4"
#property version   "1.00"

// 包含 HedgeTerminalAPI 程序库的函数原型。
#include <Prototypes.mqh> 

//+------------------------------------------------------------------+
//| EA 即时报价函数                                                    |
//+------------------------------------------------------------------+ 
void OnStart()
  {
   // 在事务列表里搜索所有事务...
   for(int i=TransactionsTotal(); i>=0; i--)
     {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_TRADES))                           // 从活跃事务里选择
        {
         ENUM_HEDGE_ERR error=GetHedgeError();                                      // 若选择失败, 获取原因
         printf("选择事务错误 # "+(string)i+". 原因: "+            // 打印原因
                EnumToString(error));
         ResetHedgeError();                                                         // 清除错误
         continue;                                                                  // 继续下一个事务
        }
      // 仅处理对冲仓位
      if(TransactionType()==TRANS_HEDGE_POSITION) 
        {
         // --- 仓位说明 --- //
         ENUM_TRANS_DIRECTION direction=(ENUM_TRANS_DIRECTION)                      // 获取方向说明
                              HedgePositionGetInteger(HEDGE_POSITION_DIRECTION);
         double price_entry = HedgeOrderGetDouble(HEDGE_ORDER_PRICE_EXECUTED);      // 获取持仓量
         string symbol = HedgePositionGetString(HEDGE_POSITION_SYMBOL);             // 获取持仓品名
         // --- 订单说明 --- //
         if(!HedgeOrderSelect(ORDER_SELECTED_INIT))continue;                        // 在持仓里选择初始订单
         double slippage = HedgeOrderGetDouble(HEDGE_ORDER_SLIPPAGE);               // 获取滑点
         uint deals_total = (uint)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);    // 获取成交总数
         // --- 成交说明 --- //
         double commissions=0.0;
         ulong deal_id=0;
         //在成交列表里搜索所有成交...
         for(uint d_index=0; d_index<deals_total; d_index++)                        
           {
            if(!HedgeDealSelect(d_index))continue;                                  // 按照其索引选择成交
            deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);                           // 获取成交标识
            commissions += HedgeDealGetDouble(HEDGE_DEAL_COMMISSION);               // 计算佣金
           }
         int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
         printf("仓位 #" + (string)i + ": 方向 " + EnumToString(direction) +     // 打印结果行
         "; 入场价格 " + DoubleToString(price_entry, digits) + 
         "; 初始滑点 " + DoubleToString(slippage, 2) + "; 最后成交标识 " +
         (string)deal_id + "; 佣金总和 " + DoubleToString(commissions, 2));
        }
     }
  }

1.12。sendTradeRequest()函数与HedgetTradeRequest结构以混沌II EA为例

作为一个惯例,让我们以比尔·威廉姆斯在书中混乱的交易行为为基础。交易策略的第二个版本开发了一个交易机器人。

我们不会完全照搬他的建议,通过省略鳄鱼指数和其他一些条件来简化计划。这种策略的选择源于几个考虑因素。最重要的是,这种战术包括复合阵地的维修战术。有时,你需要部分清算你的头寸,并移动止损损失来实现收支平衡。

当市场达到盈亏平衡点时,止损头寸将跟随价格走势。第二个考虑是要对这个策略有一个很好的理解,并且为它开发的指标包含在标准的metatrader 5发行包中。让我们稍微修改和简化一些规则,以避免EA的复杂逻辑并阻碍其主要目标:指示EA与HedgetTerminalAPI库交互。EA逻辑更多地利用了HedgetTerminalAPI的事务功能。这是对图书馆的一次很好的测试。

我们从倒柱开始。多头反转列是其高位的三分之一的收盘价,N列的最低收盘价是最低的。短反转列是低价位收盘价的三分之一,最高反转列是N列最高的反转列。n是一个随机选择的参数,可以在EA启动期间设置。这与经典的“混沌2”策略不同。

定义倒柱线后,放置两个悬挂卡瓦。对于多头列,订单放置高于其最高价格,而对于短头列,订单放置低于其最低价格。如果这两个订单在旧的待处理列中未能触发,则信号是它们已过时,订单将被取消。在用户在图形上启动EA之前,可以设置oldpending和n的值。

顺序触发并分为两个双向位置。EA通过在注释中分别添加数字“1”和“2”来区分它们。这不是一个非常优雅的解决方案,但它有利于演示。一旦订单被触发,止损头寸将被置于倒转列的最高(短期)或最低(长期)价格。

第一个职位有严格的目标。设定可触发止损收益时,持仓利润应等于绝对损失。例如,如果一个多头头寸开仓为1.0000,其止损设置为0.9000,则止损头寸应设置为1.0000+(1.0000-0.9000)=1.1000。EA将在止损或止损位置离开。

第二个位置是长线。它的止损将跟随市场。当一个新的比尔威廉斯分形形成时,停止损失移动。对于多位置,停损按低阶分形运动,而对于短位置则采用高阶分类。EA仅在停止损失位置离开田地。

下图说明了该策略:

图例. 4. 在价格图表上的混沌 2 EA 双向仓位示意

传说。4。价格图上的混沌2-ea双向位置表示

“反转”列用红色边框标记。在这个图表中,n周期等于2。对于此策略,请在最合适的时间进行选择。空头显示为蓝色虚线,多头显示为绿色。如图所示,多头和空头头寸可以同时存在,即使是在相对简单的策略中。注意周期为2014年1月5日至8日。

这是AUDCAD下行趋势的转折点。1月4日,多头反转栏收到信号,1月5日开放了两个多头头寸。此时,仍有三个空头头寸,它们的止损点将跟随趋势(红色虚线)。然后,在1月7日,空头头寸停止触发,只留下多头头寸在市场上。

净头寸的变化很难监控,因为净交易不考虑EA维持的实际头寸数量。HedgetTerminal允许EA监控其各自的头寸,而不管当前的净头寸如何,从而可以捕获这些图表并制定类似的策略。

下面的代码实现了这个策略。

我故意不使用面向对象编程,以便代码适合初学者:

//+------------------------------------------------------------------+
//|                                                       Chaos2.mq5 |
//|     Copyright 2014, Vasiliy Sokolov specially for HedgeTerminal. |
//|                                          St.-Petersburg, Russia. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Vasiliy Sokolov."
#property link      "https://login.mql5.com/ru/users/c-4"
#property version   "1.00"

//+------------------------------------------------------------------+
//| 包含文件                                                          |
//+------------------------------------------------------------------+
#include <Prototypes.mqh>           // 包含 HedgeTerminalAPI 程序库的函数原型

//+------------------------------------------------------------------+
//| 输入参数。                                                         |
//+------------------------------------------------------------------+
input uint N=2;                     // 极大/极小周期
input uint OldPending=3;            // 过时挂单

//+------------------------------------------------------------------+
//| EA 的私有变量。                                                    |
//+------------------------------------------------------------------+
ulong Magic = 2314;                 // EA 魔幻数字
datetime lastTime = 0;              // 函数 DetectNewBar 的最后记忆时间
int hFractals = INVALID_HANDLE;     // 指标 '分形' 的句柄参阅: 'https://www.mql5.com/en/docs/indicators/ifractals'
//+------------------------------------------------------------------+
//| 比尔·威廉姆斯策略的柱线类型                                          |
//+------------------------------------------------------------------+
enum ENUM_BAR_TYPE
  {
   BAR_TYPE_ORDINARY,               // 一般柱线。
   BAR_TYPE_BEARISH,                // 该柱线收盘价位于高点三分之一处且它的最小值是 N 周期的最低
   BAR_TYPE_BULLISH,                // 该柱线收盘价位于低点三分之一处且它的最大值是 N 周期的最高
  };
//+------------------------------------------------------------------+
//| 极值类型。                                                         |
//+------------------------------------------------------------------+
enum ENUM_TYPE_EXTREMUM
  {
   TYPE_EXTREMUM_HIGHEST,           // 最高价极值
   TYPE_EXTREMUM_LOWEST             // 最低价极值
  };
//+------------------------------------------------------------------+
//| 仓位类型。                                                         |
//+------------------------------------------------------------------+
enum ENUM_ENTRY_TYPE
  {
   ENTRY_BUY1,                      // 多头仓位带止损
   ENTRY_BUY2,                      // 多头仓位带止盈
   ENTRY_SELL1,                     // 空头仓位带止盈
   ENTRY_SELL2,                     // 空头仓位带止损
   ENTRY_BAD_COMMENT                // 错误注释仓位
  };
//+------------------------------------------------------------------+
//| EA 初始化函数                                                      |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 创建 '分形' 指标 ---//
   hFractals=iFractals(Symbol(),NULL);
   if(hFractals==INVALID_HANDLE)
      printf("警告!指标 '分形' 未能创建。原因: "+
             (string)GetLastError());
//--- 以时间帧调整魔幻数字 ---//
   int minPeriod=PeriodSeconds()/60;
   string strMagic=(string)Magic+(string)minPeriod;
   Magic=StringToInteger(strMagic);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| EA 逆初函数                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 删除指标 '分形' ---//
   if(hFractals!=INVALID_HANDLE)
      IndicatorRelease(hFractals);
//---
  }
//+------------------------------------------------------------------+
//| EA 即时报价函数                                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 仅当新柱线开盘时运行逻辑。---//
   int totals=SupportPositions();
   if(NewBarDetect()==true)
     {
      MqlRates rates[];
      CopyRates(Symbol(),NULL,1,1,rates);
      MqlRates prevBar=rates[0];
      //--- 设置新挂单 ---//
      double closeRate=GetCloseRate(prevBar);
      if(closeRate<=30 && BarIsExtremum(1,N,TYPE_EXTREMUM_HIGHEST))
        {
         DeleteOldPendingOrders(0);
         SetNewPendingOrder(1,BAR_TYPE_BEARISH);
        }
      else if(closeRate>=70 && BarIsExtremum(1,N,TYPE_EXTREMUM_LOWEST))
        {
         DeleteOldPendingOrders(0);
         SetNewPendingOrder(1,BAR_TYPE_BULLISH);
        }
      DeleteOldPendingOrders(OldPending);
     }
//---
  }
//+------------------------------------------------------------------+
//| 分析开仓并在需要时修改。                                             |
//+------------------------------------------------------------------+
int SupportPositions()
  {
//---
   int count=0;
   //--- 分析活跃仓位... ---//
   for(int i=0; i<TransactionsTotal(); i++) // 获取仓位总数。
     {
      //--- 选择主要活跃仓位 ---//
      if(!TransactionSelect(i, SELECT_BY_POS, MODE_TRADES))continue;             // 选择活跃事务
      if(TransactionType() != TRANS_HEDGE_POSITION)continue;                     // 仅选择对冲仓位
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)                 // 以魔幻数字选择主要仓位
      if(HedgePositionGetInteger(HEDGE_POSITION_STATE) == POSITION_STATE_FROZEN) // 如果仓位为零 - 继续
         continue;                                                               // 稍后尝试访问仓位
      count++;
      //--- 我们要选择哪个仓位?... ---//
      ENUM_ENTRY_TYPE type=IdentifySelectPosition();
      bool modify=false;
      double sl = 0.0;
      double tp = 0.0;
      switch(type)
        {
         case ENTRY_BUY1:
         case ENTRY_SELL1:
           {
            //--- 检查止损, 止盈位并在需要时修改它。---//
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            sl=GetStopLossLevel();
            if(!DoubleEquals(sl,currentStop))
               modify=true;
            tp=GetTakeProfitLevel();
            double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
            double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
            //--- 如果价格超越止盈位, 按止盈平仓
            bool isBuyTp=tp<bid && !DoubleEquals(tp,0.0) && type==ENTRY_BUY1;
            bool isSellTp=tp>ask && type==ENTRY_SELL1;
            if(isBuyTp || isSellTp)
              {
               HedgeTradeRequest request;
               request.action=REQUEST_CLOSE_POSITION;
               request.exit_comment="由 EA 止盈平仓";
               request.close_type=CLOSE_AS_TAKE_PROFIT;
               if(!SendTradeRequest(request))
                 {
                  ENUM_HEDGE_ERR error=GetHedgeError();
                  string logs=error==HEDGE_ERR_TASK_FAILED ?". 打印日志..." : "";
                  printf("因止盈失败平仓。原因: "+EnumToString(error)+" "+logs);
                  if(error==HEDGE_ERR_TASK_FAILED)
                     PrintTaskLog();
                  ResetHedgeError();
                 }
               else break;
              }
            double currentTakeProfit=HedgePositionGetDouble(HEDGE_POSITION_TP);
            if(!DoubleEquals(tp,currentTakeProfit))
               modify=true;
            break;
           }
         case ENTRY_BUY2:
           {
            //--- 检查止损位并设置修改标志。---//
            sl=GetStopLossLevel();
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            if(sl>currentStop)
               modify=true;
            break;
           }
         case ENTRY_SELL2:
           {
            //--- 检查止损位并设置修改标志。---//
            sl=GetStopLossLevel();
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            bool usingSL=HedgePositionGetInteger(HEDGE_POSITION_USING_SL);
            if(sl<currentStop || !usingSL)
               modify=true;
            break;
           }
        }
      //--- 如果需要修改止损, 止盈位 - 修改它。---//
      if(modify)
        {
         HedgeTradeRequest request;
         request.action=REQUEST_MODIFY_SLTP;
         request.sl = sl;
         request.tp = tp;
         if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
            request.exit_comment="由止盈位离场";
         else
            request.exit_comment="由尾随止损离场";
         if(!SendTradeRequest(request))
           {
            ENUM_HEDGE_ERR error=GetHedgeError();
            string logs=error==HEDGE_ERR_TASK_FAILED ?". 打印日志..." : "";
            printf("修改止损或止盈失败。原因: "+EnumToString(error)+" "+logs);
            if(error==HEDGE_ERR_TASK_FAILED)
               PrintTaskLog();
            ResetHedgeError();
           }
         else break;
        }
     }
   return count;
//---
  }
//+------------------------------------------------------------------+
//| 返回选择仓位的止损位。                                              |
//| 结果                                                              |
//|   止损位                                                          |
//+------------------------------------------------------------------+
double GetStopLossLevel()
  {
//---
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   double fractals[];
   double sl=0.0;
   MqlRates ReversalBar;

   if(!LoadReversalBar(ReversalBar))
     {
      printf("反转柱线加载失败。");
      return sl;
     }
   //--- 我们要选择哪个仓位?... ---//
   switch(IdentifySelectPosition())
     {
      case ENTRY_SELL2:
        {
         if(HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
           {
            sl=NormalizeDouble(HedgePositionGetDouble(HEDGE_POSITION_SL),Digits());
            CopyBuffer(hFractals,UPPER_LINE,ReversalBar.time,TimeCurrent(),fractals);
            for(int i=ArraySize(fractals)-4; i>=0; i--)
              {
               if(DoubleEquals(fractals[i],DBL_MAX))continue;
               if(DoubleEquals(fractals[i],sl))continue;
               if(fractals[i]<sl)
                 {
                  double price= SymbolInfoDouble(Symbol(),SYMBOL_ASK);
                  int ifreeze =(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL);
                  double freeze=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*ifreeze;
                  if(fractals[i]>price+freeze)
                     sl=NormalizeDouble(fractals[i]+point,Digits());
                 }
              }
            break;
           }
        }
      case ENTRY_SELL1:
         sl=ReversalBar.high+point;
         break;
      case ENTRY_BUY2:
         if(HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
           {
            sl=NormalizeDouble(HedgePositionGetDouble(HEDGE_POSITION_SL),Digits());
            CopyBuffer(hFractals,LOWER_LINE,ReversalBar.time,TimeCurrent(),fractals);
            for(int i=ArraySize(fractals)-4; i>=0; i--)
              {
               if(DoubleEquals(fractals[i],DBL_MAX))continue;
               if(DoubleEquals(fractals[i],sl))continue;
               if(fractals[i]>sl)
                 {
                  double price= SymbolInfoDouble(Symbol(),SYMBOL_BID);
                  int ifreeze =(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL);
                  double freeze=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*ifreeze;
                  if(fractals[i]<price-freeze)
                     sl=NormalizeDouble(fractals[i]-point,Digits());
                 }
              }
            break;
           }
      case ENTRY_BUY1:
         sl=ReversalBar.low-point;
     }
   sl=NormalizeDouble(sl,Digits());
   return sl;
//---
  }
//+------------------------------------------------------------------+
//| 返回选择仓位的止盈位。                                              |
//| 结果                                                              |
//|   止盈位                                                          |
//+------------------------------------------------------------------+
double GetTakeProfitLevel()
  {
//---
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   ENUM_ENTRY_TYPE type=IdentifySelectPosition();
   double tp=0.0;
   if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
     {
      if(!HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
         return tp;
      double sl=HedgePositionGetDouble(HEDGE_POSITION_SL);
      double openPrice=HedgePositionGetDouble(HEDGE_POSITION_PRICE_OPEN);
      double deltaStopLoss=MathAbs(NormalizeDouble(openPrice-sl,Digits()));
      if(type==ENTRY_BUY1)
         tp=openPrice+deltaStopLoss;
      if(type==ENTRY_SELL1)
         tp=openPrice-deltaStopLoss;
      return tp;
     }
   else
      return 0.0;
//---
  }
//+------------------------------------------------------------------+
//| 识别选择的仓位类型。                                                |
//| 结果                                                              |
//|   返回仓位类型。参阅 ENUM_ENTRY_TYPE                                |
//+------------------------------------------------------------------+
ENUM_ENTRY_TYPE IdentifySelectPosition()
  {
//---   
   string comment=HedgePositionGetString(HEDGE_POSITION_ENTRY_COMMENT);
   int pos=StringLen(comment)-2;
   string subStr=StringSubstr(comment,pos);
   ENUM_TRANS_DIRECTION posDir=(ENUM_TRANS_DIRECTION)HedgePositionGetInteger(HEDGE_POSITION_DIRECTION);
   if(subStr=="#0")
     {
      if(posDir==TRANS_LONG)
         return ENTRY_BUY1;
      if(posDir==TRANS_SHORT)
         return ENTRY_SELL1;
     }
   else if(subStr=="#1")
     {
      if(posDir==TRANS_LONG)
         return ENTRY_BUY2;
      if(posDir==TRANS_SHORT)
         return ENTRY_SELL2;
     }
   return ENTRY_BAD_COMMENT;
//---
  }
//+------------------------------------------------------------------+
//| 依据 index_bar 所在柱线设置或高或低的挂单                            |
//| 输入参数                                                          |
//|   index_bar - 柱线索引。                                          |
//|   barType - 柱线类型。参阅枚举 ENUM_BAR_TYPE。                      |
//| 结果                                                              |
//|   True 如果新订单成功设置, 否则 false。                              | 
//+------------------------------------------------------------------+
bool SetNewPendingOrder(int index_bar,ENUM_BAR_TYPE barType)
  {
//---
   MqlRates rates[1];
   CopyRates(Symbol(),NULL,index_bar,1,rates);
   MqlTradeRequest request={0};
   request.volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   double vol=request.volume;
   request.symbol = Symbol();
   request.action = TRADE_ACTION_PENDING;
   request.type_filling=ORDER_FILLING_FOK;
   request.type_time=ORDER_TIME_GTC;
   request.magic=Magic;
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   string comment="";
   if(barType==BAR_TYPE_BEARISH)
     {
      request.price=rates[0].low-point;
      comment="空头柱线空单入场";
      request.type=ORDER_TYPE_SELL_STOP;
     }
   else if(barType==BAR_TYPE_BULLISH)
     {
      request.price=rates[0].high+point;
      comment="多头柱线多单入场";
      request.type=ORDER_TYPE_BUY_STOP;
     }
   MqlTradeResult result={0};
//--- 发送挂单两次...
   for(int i=0; i<2; i++)
     {
      request.comment=comment+" #"+(string)i;       // 按照注释删除订单;
      if(!OrderSend(request,result))
        {
         printf("交易错误 #"+(string)result.retcode+" "+
                result.comment);
         return false;
        }
     }
   return true;
//---
  }
//+------------------------------------------------------------------+
//| 删除旧挂单。如果挂单设置旧于                                         |
//| n_bars 之前, 挂单将被删除。                                         |
//| 输入参数                                                           |
//|   period - 柱线计数。                                              |
//+------------------------------------------------------------------+
void DeleteOldPendingOrders(int n_bars)
  {
//---
   for(int i=0; i<OrdersTotal(); i++)
     {
      ulong ticket = OrderGetTicket(i);            // 按照索引获取订单号。
      if(!OrderSelect(ticket))                     // 如果未选择继续。
         continue;
      if(Magic!=OrderGetInteger(ORDER_MAGIC))      // 如果魔幻数字不是主体。
         continue;
      if(OrderGetString(ORDER_SYMBOL)!=Symbol())   // 如果品名不是主体继续。
         continue;
      //--- 过时计数 ---//
      datetime timeSetup=(datetime)OrderGetInteger(ORDER_TIME_SETUP);
      int secElapsed=(int)(TimeCurrent()-timeSetup);
      //--- 删除旧挂单 ---//
      if(secElapsed>=PeriodSeconds() *n_bars)
        {
         MqlTradeRequest request={0};
         MqlTradeResult result={0};
         request.action= TRADE_ACTION_REMOVE;
         request.order = ticket;
         if(!OrderSend(request,result))
            printf("删除挂单失败。原因 #"+(string)result.retcode+" "+result.comment);
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| 检测新柱线                                                         |
//+------------------------------------------------------------------+
bool NewBarDetect(void)
  {
//---
   datetime timeArray[1];
   CopyTime(Symbol(),NULL,0,1,timeArray);
   if(lastTime!=timeArray[0])
     {
      lastTime=timeArray[0];
      return true;
     }
   return false;
//---
  }
//+------------------------------------------------------------------+
//| 获取收盘价。在混沌交易策略里定义的柱线类型                             |
//| 并等于枚举 'ENUM_TYPE_BAR'.                                        |
//| 输入参数                                                           |
//|   index - 柱线系列索引。例如:                                       |
//|   '0' - 当前柱线。1 - 前一柱线。                                    |
//| 结果                                                              |
//|   ENUM_TYPE_BAR 类型。                                            | 
//+------------------------------------------------------------------+
double GetCloseRate(const MqlRates &bar)
  {
//---
   double highLowDelta = bar.high-bar.low;      // 计算柱线高度。
   double lowCloseDelta = bar.close - bar.low;  // 计算收盘价 - 最低价增量。
   double percentClose=0.0;
   if(!DoubleEquals(lowCloseDelta, 0.0))                    // 除零保护。
      percentClose = lowCloseDelta/highLowDelta*100.0;      // 计算 'lowCloseDelta' 与 'highLowDelta' 的百分比。
   return percentClose;
//---
  }
//+------------------------------------------------------------------+
//| 如果索引处柱线为极值 - 返回 true, 否则                               |
//| 返回 false。                                                      |
//| 输入参数                                                          |
//|   index - 柱线索引。                                               |
//|   period - 极值周期的柱线数量。                                     |
//|   type - 极值类型。参阅 ENUM_TYPE_EXTREMUM TYPE 枚举。              |
//| 结果                                                              |
//|   True - 如果柱线是极值, 否则 false。                               | 
//+------------------------------------------------------------------+
bool BarIsExtremum(const int index,const int period,ENUM_TYPE_EXTREMUM type)
  {
//--- 拷贝汇率 --- //
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   CopyRates(Symbol(),NULL,index,N+1,rates);
//--- 搜索极值 --- //
   for(int i=1; i<ArraySize(rates); i++)
     {
      //--- 如果您打算包含交易量分析则重置注释。---//
      //if(rates[0].tick_volume<rates[i].tick_volume)
      //   return false;
      if(type==TYPE_EXTREMUM_HIGHEST && 
         rates[0].high<rates[i].high)
         return false;
      if(type==TYPE_EXTREMUM_LOWEST && 
         rates[0].low>rates[i].low)
         return false;
     }
   return true;
//---
  }
//+------------------------------------------------------------------+
//| 打印当前错误和结果。                                                |
//+------------------------------------------------------------------+  
void PrintTaskLog()
  {
//---
   uint totals=(uint)HedgePositionGetInteger(HEDGE_POSITION_ACTIONS_TOTAL);
   for(uint i = 0; i<totals; i++)
     {
      uint retcode=0;
      ENUM_TARGET_TYPE type;
      GetActionResult(i,type,retcode);
      printf("---> 动作 #"+(string)i+"; "+EnumToString(type)+"; 返回码: "+(string)retcode);
     }
//---
  }
//+------------------------------------------------------------------+
//| 加载反转柱线。当前仓位必须已选择。                                    |
//| 输出参数                                                          |
//|   bar - MqlRates 柱线。                                           |
//+------------------------------------------------------------------+  
bool LoadReversalBar(MqlRates &bar)
  {
//---
   datetime time=(datetime)(HedgePositionGetInteger(HEDGE_POSITION_ENTRY_TIME_SETUP_MSC)/1000+1);
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   CopyRates(Symbol(),NULL,time,2,rates);
   int size=ArraySize(rates);
   if(size==0)return false;
   bar=rates[size-1];
   return true;
//---   
  }
//+------------------------------------------------------------------+
//| 比较两个双精度数字。                                                |
//| 结果                                                              |
//|   True 如果两个双精度数字相等, 否则 false。                          |
//+------------------------------------------------------------------+
bool DoubleEquals(const double a,const double b)
  {
//---
   return(fabs(a-b)<=16*DBL_EPSILON*fmax(fabs(a),fabs(b)));
//---
  }

下面是代码工作原理的简要描述。每次报价时都会调用EA。它使用barisextremum()函数来分析前一列:如果它是空的或是多头的,它将阻止两个账单(函数setNewPendingOrder())。一旦激活,提单将转换为仓库位置。然后,EA为位置设置停止和停止。

不幸的是,这些价格不能与清单放在一起,因为它们不是真正的头寸。价格可以通过SupportPositions()功能设置。为了正确的操作,我们需要知道位置应该设置为停止盈利,并且应该按照分形来跟踪移动。仓库定义已由identify selectposition()函数完成。它分析初始仓库的注释,如果包含字符串“1”,则设置更接近的目标,如果包含“2”,则应用尾随。

为了修改或关闭双向位置,需要创建一个特殊的事务请求,然后将其发送到sendTradeRequest()函数以执行:

...
if(modify)
  {
   HedgeTradeRequest request;
   request.action=REQUEST_MODIFY_SLTP;
   request.sl = sl;
   request.tp = tp;
   if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
      request.exit_comment="由止盈位离场";
   else
      request.exit_comment="由尾随止损离场";
   if(!SendTradeRequest(request))
     {
      ENUM_HEDGE_ERR error=GetHedgeError();
      string logs=error==HEDGE_ERR_TASK_FAILED ?". 打印日志..." : "";
      printf("修改止损或止盈失败。原因: "+EnumToString(error)+" "+logs);
      if(error==HEDGE_ERR_TASK_FAILED)
         PrintTaskLog();
      ResetHedgeError();
     }
   else break;
  }
...

应注意错误处理。

如果发送失败且函数返回false,则需要使用getHedgeError()函数获取最终的错误代码。在某些情况下,事务订单的执行根本不会开始。如果未预先选择仓库,则查询不正确,无法执行。

如果一个订单没有执行,那么分析它的实现日志就没有意义了,这就足以得到一个错误代码。

但是,如果查询正确并且由于某种原因未执行订单,则返回错误对冲任务失败。在这种情况下,需要搜索日志来分析订单执行记录。这可以通过特殊函数printtasklog()完成:

//+------------------------------------------------------------------+
//| 打印当前错误和结果。                                                |
//+------------------------------------------------------------------+  
void PrintTaskLog()
  {
//---
   uint totals=(uint)HedgePositionGetInteger(HEDGE_POSITION_ACTIONS_TOTAL);
   for(uint i = 0; i<totals; i++)
     {
      uint retcode=0;
      ENUM_TARGET_TYPE type;
      GetActionResult(i,type,retcode);
      printf("---> 动作 #"+(string)i+"; "+EnumToString(type)+"; 返回码: "+(string)retcode);
     }
//---
  }

这些消息识别出故障的原因并修复它。

现在让我们来说明混沌2ea及其在HedgetTerminal中的实时位置。EA在图M1中运行:

图例. 5. 在 HedgeTerminal 面板中混沌2 EA 的双向仓位示意

传说。5。屏蔽终端面板中混沌2ea的双向定位

如你所见,ea的双向位置完美共存。

1.13。Virtual “Repeated Varieties” of Brokers

当MetaTrader5发布后,一些经纪人开始提供所谓的副本。它们的报价与原始金融工具相同,但通常有一个后缀,如“_m”或“_1”。引入它们是为了允许交易者使用虚拟相同品种持有双向头寸。

然而,对于使用机器人的算法交易者来说,这些变体几乎是无用的。这就是原因。假设我们需要在不使用HedgetTerminalAPI库的情况下编写“混沌II”EA。相反,我们有一些重复的品种。我们该怎么做?想象一下,在一个金融工具上的所有销售业务,如欧元兑美元,以及在另一个产品上的所有购买业务,如欧元兑美元的M1。

但是如果这个物种已经由其他机器人或商人操作,会发生什么呢?即使物种是闲置的,这个问题仍然让机器人感到困惑,也就是说,在同一方向上的并行位置。

上面的截图代表三个位置,它们可以有更多。仓库不同于保护性站点,这就是为什么它们不能合并到单个净位置的原因。解决办法是为新的复制品种开一个新的仓库。但这些还不够,因为机器人需要六个重复的金融工具(每个方向三个)。如果两个机器人在不同的金融工具上运行,则需要12个副本。

没有哪个经纪协会提供过如此多的重复品种。但是,即使这些变量是无限的,并且总是闲置的,也需要一个复杂的分解算法。机器人应穿过所有可用物种,搜索重复物种及其位置。这将产生比支付更多的问题。nbsp;

即使使用重复的品种也会更麻烦。这是对其使用过程中出现的其他问题的总结:

  • 您需要为每一个副本付款,因为当您在两个不同的金融工具上维持两个双向头寸时,锁定或部分锁定的掉期始终为负数。
  • 并非所有的经济学家都提供重复的品种。Strategies developed for brokers offering duplicate products will not be used for brokers offering only one financial instrument. 不同的名字是另一个潜在的问题来源。
  • 创造重复品种并非总是可能的。在一个透明的市场中,监管是严格的,任何交易都是财务文件。在这样的市场中,净定位是事实上的标准,因此创造单独品种的可能性很小。例如,在莫斯科证券交易所,没有一位经济学家提供重复产品。在监管不那么严格的市场中,经纪人可以为客户创造任何种类的产品。
  • 重复的金融工具对与机器人交易无效。在上述混乱的2ea例子中,我们揭示了它们效率低下的原因。

复制品种基本上是在经纪人端虚拟化的。HedgetTerminal在客户端使用虚拟化。

我们在这两种情况下都使用虚拟化。它改变了交易者义务的真实履行。通过虚拟化,一个仓库可以分为两个仓库。当它发生在客户机端时,没有问题,因为客户机可以提供他们想要的东西。但是,如果虚拟化是由经纪人完成的,那么监管机构和授权机构可能面临着如何提供有关相关产品的真实信息的问题。第二个困难在于需要两组气功API:一组用于纯模式的函数和修饰符,另一组用于双向模式。

许多算法交易者已经找到了将多个交易绑定到一个位置的方法。这些方法中的许多都很好地工作,有许多文章描述了它们。然而,仓库虚拟化是一个比表面上看起来更复杂的过程。在HedgeTerminal中,仓库虚拟化的辅助算法是超过20000行的源代码。但是,HedgetTerminal只实现基本功能。为EA中的双向仓库创建类似的代码卷将消耗大量资源。

第2章。HedgetTerminal API手册

2.1。事务选择功能

函数事务总数()

函数返回事务列表中可用的事务总数。这是遍历可用事务的基本功能(请参见本文第1.4和1.11节中的示例)。

int TransactionsTotal(ENUM_MODE_TRADES pool = MODE_TRADES);

参数

  • [in]pool=mode_trades-指定要选择的数据源标识符。它可以是枚举模式交易枚举值之一。

返回值

函数返回事务列表中可用的事务总数。


函数TransactionType()

函数返回选定的事务类型。

ENUM_TRANS_TYPE TransactionType(void);

返回值

返回值。该值可以是枚举事务类型值之一。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。


函数事务选择()

函数选择事务进行进一步操作。函数按索引或唯一标识符选择事务列表中的事务。

bool TransactionSelect(int index,
     ENUM_MODE_SELECT select = SELECT_BY_POS,
     ENUM_MODE_TRADES pool=MODE_TRADES
     );

参数

  • [in]索引-基于’select’参数的订单列表中订单索引或交易的唯一标识符。
  • [in]select=select_by_pos-参数类型’index’的标识符。该值可以是枚举模式选择值之一。
  • [in]pool=mode_trades-指定要选择的数据源标识符。它可以是枚举模式交易枚举值之一。

返回值

如果已成功选择事务,则返回true,否则返回false。要获取错误详细信息,请调用GetHedgeError()。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。

笔记

如果选择了基于其索引的事务,则操作复杂性对应于O(1)。如果选择了基于其唯一标识符的事务,则操作复杂性逐渐趋向于O(log2(n))。


函数HedgeOrderSelect()。

函数选择双向位置中包含的订单之一。包含订单请求的双向仓库必须使用TransactionSelect()提前选择。

bool HedgeOrderSelect(ENUM_HEDGE_ORDER_SELECTED_TYPE type);

参数

  • [输入]输入-所选订单标识符。该值可以是Enum_Hedge_Order_Selected_类型的枚举值之一。

返回值

如果已成功选择订单,则返回“真”,否则返回“假”。要获取错误详细信息,请调用GetHedgeError()。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。


函数HedgedDealSelect()。

函数选择已执行订单的一个交易记录。The order for the selected transaction must be pre-selected using the HedgeOrderSelect () function.

bool HedgeDealSelect(int index);

参数

  • [in]索引-从已执行订单列表中选择交易的索引编号。要获取订单中所有交易的总数,请使用HedgeOrderGetInteger()函数调用相应的订单属性。对于参数,使用enum_hedge_order_prop_integer修饰符相当于hedge_order_deals_total值。

返回值

如果已成功选择事务,则返回true,否则返回false。要获取错误详细信息,请调用GetHedgeError()。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。


2.2。获取所选事务属性的函数

函数hedgepositiongetinteger()

函数返回所选双向位置的属性。根据请求属性的类型,属性可以是int、long、datetime或bool类型。必须使用TransactionSelect()函数预先选择双向位置。

ulong HedgePositionGetInteger(ENUM_HEDGE_POSITION_PROP_INTEGER property);

参数

  • [in]属性-双向仓库的属性标识符。该值可以是枚举对冲交易属性整数的枚举值之一。

返回值

值为ulong类型。若要进一步使用此值,应将其类型显式转换为请求的属性类型。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。

函数HedgepositionGetDouble()

函数返回所选双向位置的属性。返回属性类型应为double。属性类型已由枚举u hedge u position u prop u double枚举指定。必须使用TransactionSelect()函数预先选择双向位置。

ulong HedgePositionGetDouble(ENUM_HEDGE_POSITION_PROP_DOUBLE property);

参数

  • [in]属性-双向仓库的属性标识符。该值可以是Enum_Hedge_Deal_Prop_Double的枚举值之一。

返回值

数字类型为双精度。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。


函数HedgepositionGetString()

函数返回所选双向位置的属性。属性是字符串类型。属性类型已由枚举对冲位置属性字符串枚举指定。必须使用TransactionSelect()函数预先选择双向位置。

ulong HedgePositionGetString(ENUM_HEDGE_POSITION_PROP_STRING property);

参数

  • [in]属性-双向仓库的属性标识符。该值可以是枚举u hedge u position u prop u字符串枚举值之一。

返回值

值为字符串类型。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。


函数HedgeOrderGetInteger()

函数返回所选顺序的属性,这是双向位置的一部分。属性的类型可以是int、long、datetime或bool。属性类型已由枚举u hedge u order u prop u integer枚举指定。必须使用HedgeOrderSelect()函数预先选择订单。

ulong HedgeOrderGetInteger(ENUM_HEDGE_ORDER_PROP_INTEGER property);

参数

  • [in] property – Order attribute identifier, which is part of a bidirectional warehouse. 该值可以是枚举u hedge u order u prop u integer枚举值之一。

返回值

值为ulong类型。若要进一步使用此值,应将其类型显式转换为请求的属性类型。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。


函数HedgeOrderGetDouble()

函数返回所选顺序的属性,这是双向位置的一部分。请求属性为双类型。属性类型已由枚举u hedge u order u prop u double enumeration指定。必须使用HedgeOrderSelect()函数预先选择订单。

double HedgeOrderGetDouble(ENUM_HEDGE_ORDER_PROP_DOUBLE property);

参数

  • [in] property – Order attribute identifier, which is part of a bidirectional warehouse. 该值可以是Enum_Hedge_Order_Prop_Double的枚举值之一。

返回值

数字类型为双精度。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。


函数HedgedDealeGetInteger()

函数返回所选事务的属性,该属性是已执行订单的一部分。属性可以是int、long、datetime或bool类型。属性类型已由枚举u hedge u deal u prop u integer枚举指定。必须使用HedgedDealSelect()函数预先选择交易。

ulong HedgeOrderGetInteger(ENUM_HEDGE_DEAL_PROP_INTEGER property);

参数

  • [in]属性-所选事务的属性标识符,它是已执行订单的一部分。该值可以是枚举对冲交易属性整数的枚举值之一。

返回值

值为ulong类型。若要进一步使用此值,应将其类型显式转换为请求的属性类型。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。


函数HedgedDealgetDouble()

函数返回所选事务的属性,该属性是已执行订单的一部分。属性可以是double类型。属性类型已由Enum_Hedge_Deal_Prop_Double枚举指定。必须使用HedgedDealSelect()函数预先选择交易。

ulong HedgeOrderGetDouble(ENUM_HEDGE_DEAL_PROP_DOUBLE property);

参数

  • [in]属性-所选事务的属性标识符,它是已执行订单的一部分。该值可以是Enum_Hedge_Deal_Prop_Double的枚举值之一。

返回值

数字类型是double。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。

2.3。从EA设置和检索HedgetTerminal属性的函数

函数HedgePropertySetInteger()

函数设置HedgetTerminal属性之一。属性可以是int、long、datetime或bool类型。属性类型已由枚举u hedge u prop u integer枚举指定。

bool HedgePropertySetInteger(ENUM_HEDGE_PROP_INTEGER property, long value);

参数

  • [in]属性-要设置的对冲终端属性标识符。该值可以是枚举对冲属性整数枚举值之一。

返回值

数字类型bool。如果属性设置成功,函数将返回true或false。

使用例程

在例程中,当发送异步请求时,该函数用于设置仓库锁定时间。如果在发送异步请求后30秒内未收到服务器响应,则被阻止的存储将被解锁。

void SetTimeOut()
  {
   bool res=HedgePropertySetInteger(HEDGE_PROP_TIMEOUT,30);
   if(res)
      printf("属性设置成功");
   else
      printf("属性未设置");
  }


函数HedgeProperty GetInteger()。

函数来获取HedgetTerminal属性之一。属性可以是int、long、datetime或bool类型。属性类型已由枚举u hedge u prop u integer枚举指定。

long HedgePropertyGetInteger(ENUM_HEDGE_PROP_INTEGER property);

参数

  • [in]属性-从HedgetTerminal接收的属性标识符。该值可以是枚举对冲属性整数枚举值之一。

返回值

数字类型为长。

使用例程

函数在发送异步请求后接收仓库阻塞时间,并将其显示在终端上。

void GetTimeOut()
  {
   int seconds=HedgePropertyGetInteger(HEDGE_PROP_TIMEOUT);
   printf("超时为 "+(string) 秒);
  }


2.4。获取和处理错误代码的函数

函数GetHedgeError()

函数返回从上一个操作中检索到的错误标识符。The error identifier corresponds to the ENUM_HEDGE_ERR enumeration.

ENUM_HEDGE_ERR GetHedgeError(void);

返回值

位置ID值可以是枚举值之一。

笔记

调用后,getHedgeError()函数无法重置错误ID。若要重置错误ID,请使用resethedgerError()函数。

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。

功能重置错误()

函数重置接收到的最后一个错误标识符。调用后,getHedgeError()返回的枚举u hedge u err标识符将等效于hedge u err u not u error。

void ResetHedgeError(void);

使用例程

请参阅本文第1.11节中的示例:“通过脚本例程管理双向仓库属性”。


函数总计操作任务()

使用hedgepositionselect()函数选择仓库后,可以通过sendTradeRequest()函数对其进行修改。例如,可以对其进行清算或修改其非现场注释。此修改由特殊事务任务执行。每个任务可以由多个事务操作(子任务)组成。任务可能失败。在这种情况下,您可能需要分析任务中包含的所有子任务的结果,以了解子任务失败的原因。

函数totalActionTask()返回在所选位置执行的上一个事务任务中包含的子任务总数。知道子任务的总数后,可以根据子任务的索引遍历所有子任务,并使用getAction result()函数分析子任务的执行结果,找出故障所在。

uint TotalActionsTask(void);

返回值

返回任务中的子任务总数。

使用例程

请参阅本文第1.6节中的示例:“使用total actions task()和getaction result()详细分析事务并识别错误”。


函数GetActionResult()

函数获取任务中的子任务索引(请参见操作任务总数())。通过引用参数,返回子任务类型及其执行结果。子任务类型已由枚举目标类型枚举定义。子任务执行结果对应于MetaTrader 5交易服务器的返回代码。

void GetActionResult(uint index, ENUM_TARGET_TYPE& target_type, uint& retcode);

参数

  • [in]索引-子任务列表中的子任务索引。
  • [出]目标类型-子任务类型。该值可以是枚举目标类型枚举值之一。
  • [out]retcode-事务服务器返回子任务执行的代码。

使用例程

请参阅本文第1.6节中的示例:“使用total actions task()和getaction result()详细分析事务并识别错误”。

2.5。交易

函数sendTradeRequest()

该函数发送一个请求,以修改HedgetTerminalAPI中选定的双向位置。函数执行会导致以下三种操作之一:

  1. 完全或部分关闭;
  2. 修改止损或止损头寸;
  3. 修改离港通知单。

操作类型及其参数在HedgetTradeRequest结构中指定,即引用作为参数传递。在调用函数之前,必须使用TransactionSelect()函数预先选择双向仓库。

bool SendTradeRequest(HedgeTradeRequest& request);

参数

[in]请求-修改双向位置的请求结构。参见HedgetTradeRequest结构的描述,其中包括结构描述及其字段的解释。

返回值

如果仓库修改请求已成功执行,则返回true。否则,返回false。在请求执行失败的情况下,函数totalActionstack()和getActionResult()用于查找失败的原因。

笔记

请求以异步模式发送,如果任务已成功放置或启动,则返回标志包含true。但是,我们必须记住,即使任务成功启动,也不能保证它的执行。因此,此标志不能用于在异步模式下控制任务完成。在同步模式下,任务在单个线程中启动和执行,因此在同步模式下,可以使用此标志控制事务请求的执行结果。


交易请求结构HedgetTradeRequest()

通过调用sendtraderrequest()函数,以事务请求为参数,关闭或修改套期保值终端内的双向头寸。请求由一个特殊的预定义结构HedgetTradeRequest表示,它包括关闭或修改所选仓库所需的所有字段:

struct HedgeTradeRequest
  {
   ENUM_REQUEST_TYPE action;             // 动作类型
   double            volume;             // 持仓量
   ENUM_CLOSE_TYPE   close_type;         // 平仓标记
   double            sl;                 // 止损位
   double            tp;                 // 止盈位
   string            exit_comment;       // 离场注释
   uint              retcode;            // 最后执行操作的返回代码
   bool              asynch_mode;        // true 如果需异步执行, 否则 false
   ulong             deviation;          // 偏离的价格步长
                     HedgeTradeRequest() // 省缺参数
     {
      action=REQUEST_CLOSE_POSITION;
      asynch_mode=false;
      volume=0.0;
      sl = 0.0;
      tp = 0.0;
      retcode=0;
      deviation=3;
     }
  };

字段说明

领域 描述
行动 &操作仓库所需的操作类型。该值可以是任何枚举请求类型枚举值之一。
体积 &清算。可以小于当前活动位置。如果成交量为零,该头寸将完全关闭。
S.S. &停止丢失位置。
和TP &位置的关闭位置。
&退出评论 &非现场位置注释。
反码 &操作执行的返回代码。
&异步模式 &nbsp;true以异步模式发送请求,否则为false。
&偏差 &最大价格偏差。


2.6。使用事务选择函数枚举操作

枚举转换类型

所有交易都可用于分析,包括提单和活动和历史交易列表中的双向头寸。

枚举枚举枚举事务类型包括选定的事务类型。此枚举由TransactionType()函数返回。以下是枚举字段及其说明:

领域 描述
&nbsp;未定义trans_ &nbsp;transactionselect()函数未选择事务,或其类型未定义。
&跨对冲头寸 &交易是双向的。
&交易经纪业务 &交易是经纪人交易(帐户操作)。例如,现金录入或账户调整。
&交易挂起订单 &交易是提单。
&交易交换位置 &交易是净头寸交换利息。

枚举模式选择

transactionselect()函数中的参数设置索引的枚举类型定义。

领域 描述
&nbsp;按位置选择 &nbsp;index参数传递列表中的事务索引。
&nbsp;按票选择 &nbsp;index参数传递单个数字。

枚举模式交易

此枚举定义使用TransactionSelect()选择的事务的数据源。

领域 描述
&nbsp;模式交易 &nbsp;所选事务来自活动事务。
&nbsp;模式历史记录 &nbsp;所选事务来自历史事务。

2.7。获取事务属性函数的操作枚举

枚举枚举枚举方向

每一笔交易,无论是交易还是双向头寸,都有市场方向。

这个市场方向是由Enum_Trans_Direction枚举定义的。以下是它们的字段及其说明:

领域 描述
&nbsp;传输 &交易方向未定义。例如,经纪人不具有操作帐户的交易的市场导向,使用此修改器。
&nbsp;跨长 &nbsp;表示交易(订单双向仓库)是买入交易。
&翻译公司 &nbsp;表示交易(订单双向仓库)是销售交易。

枚举枚举对冲位置状态

枚举包含双向位置的状态。

领域 描述
&对冲头寸活跃 &活动位置。激活位置显示在HedgetTerminal面板的激活栏中。
&对冲头寸历史 & nbsp; historical position. 历史位置显示在对冲终端面板的历史列中。

枚举枚举对冲位置状态

枚举包含双向位置的状态。

领域 描述
&位置状态激活 &nbsp;所选头寸处于激活状态,可以使用HedgetTradeRequest进行修改。
&位置“状态”冻结 &nbsp;所选位置已锁定,无法修改。如果收到此修改器,请等待仓库解锁。

枚举枚举对冲位置属性整数

HedgepositionGetInteger()返回的属性类型枚举。

领域 描述
&nbsp;对冲头寸输入时间设置 &nbsp;自1970年1月1日起的毫秒数,在放置初始双向仓库订单时设置。
&对冲头寸输入时间执行 &nbsp;自1970年1月1日起的毫秒数,在执行双向位置的初始订单时设置(打开时间)。
&nbsp;对冲头寸退出时间设置 &nbsp;自1970年1月1日双向仓单关闭以来的毫秒数。
&nbsp;对冲头寸退出时间执行 &nbsp;自1970年1月1日起的毫秒数,在执行双向仓库订单时设置(仓库关闭时间)。
&nbsp;对冲头寸类型 &双向位置的类型。相当于初始订单类型。包括系统枚举枚举枚举顺序类型的值之一。
&nbsp;对冲头寸方向 &定位方向。由枚举枚举枚举方向定义。
&对冲头寸魔术 &选择位置所属的EA幻数。零值表示仓库是手动打开的。
&nbsp;对冲头寸关闭类型 &结束订单的标记。由枚举关闭类型定义。
&nbsp;对冲头寸 &位置ID等于初始订单标识符。
&nbsp;对冲头寸输入订单ID &初始订单标识符。
&nbsp;对冲头寸退出订单ID &历史仓单标识符。
&nbsp;对冲头寸 &位置状态。由Enum_Hedge_Position_状态定义。
&对冲头寸状态 &位置状态。由Enum_Hedge_Position_State定义。nbsp;
&nbsp;对冲头寸 &使用停车标志。如果使用了stop loss,hedgepositiongetinteger()函数将返回true,否则返回false。
&nbsp;使用TP对冲头寸 &使用停车标志。如果使用停止收益,hedgepositiongetinteger()返回true,否则返回false。
&nbsp;对冲头寸任务状态 &所选仓库的任务状态。可以修改位置。修改器用于跟踪此位置的更改。位置状态由枚举任务状态定义。
&nbsp;对冲头寸行动总计 &返回通过修改仓库启动的子任务总数。

nbsp;

枚举枚举对冲位置属性双

HedgepositionGetDouble()函数返回的属性类型的枚举。

领域 描述
&对冲头寸量 &双向交易量。
&nbsp;对冲头寸价格 &头寸的加权平均开盘价。
&对冲头寸价格 &头寸的加权平均收盘价。
&nbsp;对冲头寸价格当前 &活跃头寸的当前价格。对于历史仓位,此修改器返回未平仓价格。
&对冲头寸 &停止位置。零表示没有停止损耗。
&对冲头寸 &停止位置。零表示未使用的止损收益。
&对冲头寸委员会 &为职位支付的佣金。
&对冲头寸下滑 &滑动点。
&对冲头寸利润货币 &头寸损益。这个价值是以黄金货币计算的。
&对冲头寸利润点 &头寸损益。以该岗位对应的理财产品指定点数为单位。

笔记

滑动点对冲头寸滑动的计算是最佳进入价格和平均加权进入价格之间的差额。

nbsp;

枚举枚举对冲位置属性字符串

HedgepositionGetString()函数返回的属性类型的枚举。

领域 描述
&nbsp;对冲头寸符号 &当前股票品种。
&nbsp;对冲头寸输入注释 &职务记录。
&对冲头寸退出评论 &非现场位置注释。

nbsp;

枚举枚举对冲顺序状态

枚举包括订单类型。

领域 描述
&对冲订单待定 &订单是提单,在MetaTrader 5的交易栏中可见。
&nbsp;对冲订单历史 &nbsp;该订单是一个历史订单,可以在MetaTrader 5的历史列中看到。

枚举枚举对冲顺序所选类型

HedgeOrderSelect()返回的选定订单类型的枚举。

领域 数值
&nbsp;订单已选择u init &该顺序初始化双向位置。
&已选择订单已关闭 &订单将在双向位置关闭。
& nbsp; ORDER_SELECTED_SL &订购为停止损失位置。

枚举枚举对冲顺序属性整数

HedgepositionGetInteger()函数返回的属性类型的枚举。

领域 描述
&nbsp;对冲订单 &唯一订单标识符。
&nbsp;对冲订单状态 &订单状态。它可以是枚举对冲顺序状态枚举值之一。
&nbsp;对冲订单交易总数 &已完成订单的总营业额。零用于提单。
&nbsp;对冲订单时间设置 &挂起列表放置时间,自1970年1月1日起以毫秒计。
&nbsp;对冲指令执行时间 &自1970年1月1日起执行的订单的执行时间(毫秒)。
&nbsp;对冲订单时间取消 &自1970年1月1日起执行的订单的取消时间(毫秒)。

笔记

订单执行时间对冲订单执行时间等于最终交易时间。nbsp;

枚举枚举对冲顺序属性双

HedgeOrderGetDouble()函数返回的属性类型的枚举。

领域 描述
&nbsp;对冲订单量设置 &指定订单量。
&nbsp;执行对冲订单量 &订单执行量。如果是提单,执行量为零。
&nbsp;对冲订单量被拒绝 &nbsp;丁丁卷执行失败。等于初始交易量与执行后交易量之差。
&nbsp;对冲订单价格设置 &订单放置价格。
&执行对冲指令价格 &订单的平均加权执行价格。
&对冲订单佣金 &支付给经纪人执行订单的佣金。以黄金货币。
&对冲订单下滑 &订单滑动点。

笔记

滑动点对冲的计算是最佳执行交易与订单的平均加权入门价之间的差额。

枚举枚举对冲交易属性整数

HedgedDealGetInteger()返回的属性类型枚举。

领域 描述
&nbsp;对冲交易 &唯一的事务标识符。
&对冲交易时间执行 &事务执行时间,自1970年1月1日起以毫秒计。

枚举枚举对冲交易属性双

HedgedDealGetDouble()返回的属性类型枚举。

领域 描述
&对冲交易量 &营业额。
&执行对冲交易 &交易执行价格。
&对冲交易佣金 &支付给经纪人执行交易的佣金。以黄金货币。

nbsp;

2.8。设置并获取HedgetTerminal属性的枚举

枚举枚举枚举对冲属性整数

您打算获取/设置HedgetTerminal属性类型的枚举。

领域 描述
&nbsp;对冲属性超时 &超时秒,等待事务服务器响应,然后对冲终端解锁修改的位置。

2.9。具有错误处理代码函数的操作的枚举

枚举枚举枚举任务状态

每个双向位置都可以修改。位置由事务处理任务修改。

The execution status of each transaction task is defined by ENUM_TASK_STATUS. 以下是它们的字段及其说明:

领域 描述
&nbsp;任务状态等待 &无当前任务或任务等待。
&nbsp;任务状态正在执行 &nbsp;当前正在执行事务任务。
&任务状态完成 &该头寸的交易任务已成功完成。
&nbsp;任务状态失败 &该头寸的交易任务失败。


枚举枚举对冲错误

枚举中包含的错误ID可以由GetHedgeError()返回。

领域 描述
&nbsp;对冲错误 &无错误。
&nbsp;对冲任务失败 &所选位置的任务失败。
&nbsp;对冲交易找不到 &找不到事务。
&对冲错误指数 &不正确的索引。
&nbsp;对冲错误量 &不正确的交易量。
&未选择对冲交易 &nbsp;不能使用TransactionSelect()预先选择事务。
&nbsp;对冲错误参数 &nbsp;传递的参数之一不正确。
&nbsp;套期保值冻结 &nbsp;双向位置当前正在修改,无法修改。等待位置释放。
&nbsp;对冲-错误-位置-无变化 &交易请求无变化。

nbsp;

枚举枚举枚举目标类型

GetActionResult()函数返回的选定任务类型的枚举定义。

领域 描述
&目标值 &子任务未定义。
&nbsp;目标创建任务 &正在创建子任务。这种类型在HedgetTerminalAPI的内部逻辑中使用。
&nbsp;目标删除挂起的订单 &删除账单。
&nbsp;目标设置挂起订单 &开立汇票。
&目标修改待处理订单 &修改账单价格。
&按市场分类的目标交易 &发展交易业务。

2.10。具有错误处理代码函数的操作的枚举

枚举枚举枚举请求类型

此枚举描述应用于双向位置的对冲终端的操作。

领域 描述
&nbsp;请求关闭位置 &清算。如果HedgetTradeRequest结构的交易量字段包含的小于当前值,则只有部分头寸被关闭。在这种情况下,关闭部分对应于体积字段的值。
&nbsp;请求修改 &设置或修改现有的停止丢失或停止位置。
&nbsp;请求修改注释 & nbsp; revise an off-site note for an active position.

枚举枚举枚举关闭类型

辅助枚举为双向仓库订单定义了一个特殊标记。标记表示关闭原因。原因如下:

  • 该位置接触最大损失或停止损失位置。
  • 该位置触及最大利润或停止位置。
  • 该头寸以市价收盘。停止丢失和停止位置没有设置或触摸。
领域 描述
&作为市场关闭 &表示该头寸按市价结算。停止丢失和停止位置没有设置或触摸。
&作为“停止”损失关闭 &nbsp;表示该位置因触摸停止丢失位置而关闭。
&以获利收市 &nbsp;表示该位置因触摸停止位置而关闭。

nbsp;

第3章。异步交易基础

异步操作的主题很复杂,需要单独的文章来了解详细信息。但是,由于HedgetTerminal的活动使用异步操作,因此有必要对EA使用此类请求提交的组织原则进行适当的概述。此外,关于这个主题的信息很少。

3.1。同步交易指令组织发送方案

MetaTrader 5提供了两个向服务器发送事务请求的功能。

  • OrderSend();
  • 订单发送异步()。

函数ordersend()接受由请求填充的mqltraderequest结构,并执行基本结构验证。如果基本验证成功,它将向服务器发送请求,等待结果,然后通过mqltraderesult结构返回结果,并将标志返回到自定义线程。If the basic validation fails, the function returns a negative number.

无法验证请求的原因还包括mqltraderesult。

以下场景是具有ordersend()函数的自定义MQL5程序线程的执行功能:

图例. 6. 组织并发送同步交易请求的方案

插图。6。用于组织和发送同步事务请求的方案。

如方案中所示,向服务器发送请求并在交换上执行事务的MQL5程序线程不能与系统的公共线程分离。

这就是为什么,当ordersend()完成时,我们可以分析事务请求的实际结果。自定义线程用红色箭头标记。它几乎是立即实现的。大部分时间花在交易所的交易操作上。因为连接了两个线程,所以在ordersend()函数的开始和结束之间花费了大量时间。由于实际事务操作是在单个线程内执行的,因此MQL5程序的逻辑可以是连续的。

3.2。一种组织和发送异步事务订单的方案

函数ordersendAsync()不同。与ordersend()一样,它接受事务请求mqltraderequest并返回指示其结果的标志。

但是,您不需要第一个示例。它不等待服务器执行事务请求,只从事务请求结果值的基本验证模块中获取返回值(基本验证在终端中)。以下场景说明了使用ordersendAsync()函数时自定义线程执行的过程:

图例. 7. 组织并发送异步交易请求的方案。

传说。7。用于组织和发送异步事务请求的方案。

一旦成功验证了一个事务请求,它将被并行发送到主线程和事务服务器。与第一种情况一样,通过网络传输事务请求并在交换上执行它们需要时间。但是定制线程几乎可以立即从ordersendAsync()函数获得结果。

上述方案表明,ordersendAsync()实际上形成了一个新的由事务服务器执行的并行线程,其执行结果由onTradeTransaction()或onTrade()函数获得。这些函数启动一个新的自定义线程。应在新线程中处理发送事务请求的结果。这大大增加了EA逻辑的复杂性,因为异步顺序发送使得不可能在单个线程中组织请求发送和检查它。例如,您不能将用于发送和检查请求的代码放在ontick()中。

让我们编写一个简单的测试EA来描述我们上面所说的:

//+------------------------------------------------------------------+
//|                                                    AsynchExp.mq5 |
//|                           Copyright 2014, Vasiliy Sokolov (C-4). |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
input bool UsingAsynchMode=true;
bool sendFlag=false;
//+------------------------------------------------------------------+
//| EA 即时报价函数                                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(sendFlag)return;
   printf("订单形成并发送到服务器...");
   MqlTradeRequest request={0};
   request.magic=12345;
   request.symbol = Symbol();
   request.volume = 0.1;
   request.type=ORDER_TYPE_BUY;
   request.comment= "异步测试";
   request.action = TRADE_ACTION_DEAL;
   request.type_filling=ORDER_FILLING_FOK;
   MqlTradeResult result;
   uint tiks= GetTickCount();
   bool res = false;
   if(UsingAsynchMode)
      res=OrderSendAsync(request,result);
   else
      res=OrderSend(request,result);
   uint delta=GetTickCount()-tiks;
   if(OrderSendAsync(request,result))
     {
      printf("订单已成功"+
             "发送到服务器。");
     }
  else
     {
     printf("订单无法发货。"+
             " 原因: "+(string)result.retcode);
     }
   printf("发送交易请求时间: "+(string)delta);
   sendFlag=true;
//---
  }

让我们确认EA开始使用usingasynchmode=false。

EA用0.1只手和许多仓库打开一支钢笔。事务请求使用ordersend()函数同步执行。以下是例行日志:

2014.11.06 17:49:28.442 AsynchExp (AUDCAD,H1)   发送交易请求时间: 94
2014.11.06 17:49:28.442 AsynchExp (AUDCAD,H1)   订单已成功发送到服务器。
2014.11.06 17:49:28.345 AsynchExp (AUDCAD,H1)   订单形成并发送到服务器...

订单请求在94毫秒内完成。This time tells us that the request passed the basic validation, was sent to the server, and was filled.

现在,让我们修改EA代码,将事务量更改为最大可能值dbl_max:

request.volume = DBL_MAX;

显然,这个值超出了真实范围。让我们尝试以同步模式执行此请求:

2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   发送交易请求时间: 0
2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   订单无法发货。原因: 10014
2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   订单形成并发送到服务器...

发送请求失败。失败的原因是错误10014。请求在基本验证期间失败,甚至没有发送到服务器,从0毫秒的请求执行时间可以清楚地看到。

再次,让我们修改请求。这次我们指定了一个足够大的容量,但没有超过15只手的限制。对于测试EA的1000美元账户来说,这实在是太多了。在一个账户上开立这样的头寸是不可能的。

让我们看看ordersend()返回了什么:

2014.11.06 17:59:22.643 AsynchExp (AUDCAD,H1)   发送交易请求时间: 78
2014.11.06 17:59:22.643 AsynchExp (AUDCAD,H1)   订单无法发货。原因: 10019
2014.11.06 17:59:22.550 AsynchExp (AUDCAD,H1)   订单形成并发送到服务器...

错误发生的时间不同:10019(当然,执行请求的资金不足)。注意,请求执行时间现在是79毫秒。它表示请求已发送到服务器,服务器返回错误。

现在让我们使用ordersendAsync()函数发送相同的15手请求量。与ordersend()一样,没有打开。但是让我们分析一下日志:

2014.11.06 18:03:58.106 AsynchExp (AUDCAD,H1)   发送交易请求时间: 0
2014.11.06 18:03:58.106 AsynchExp (AUDCAD,H1)   订单已成功发送到服务器。
2014.11.06 18:03:58.104 AsynchExp (AUDCAD,H1)   订单形成并发送到服务器...

这个日志是正确的!因为事务服务器检测到错误10019,所以不能在异步顺序发送模式下在当前线程中使用它。返回值仅指示请求已通过基本验证。为了得到真正的错误10019,我们需要在一个新的线程中分析结果。在onstradeTransaction()系统功能中,我们应该添加我们的EA:

void  OnTradeTransaction(const MqlTradeTransaction    &trans,
                         const MqlTradeRequest        &request,
                         const MqlTradeResult         &result)
  {
   uint delta = GetTickCount() - tiks;
   printf("服务器应答: " + (string)result.retcode + "; 时间: " + (string)delta);
  }

让我们再次运行EA并查看日志:

2014.11.06 18:17:00.943 AsynchExp (AUDCAD,H1)   服务器应答: 10019; 时间: 94
2014.11.06 18:17:00.854 AsynchExp (AUDCAD,H1)   发送交易请求时间: 0
2014.11.06 18:17:00.854 AsynchExp (AUDCAD,H1)   订单已成功发送到服务器。
2014.11.06 18:17:00.851 AsynchExp (AUDCAD,H1)   订单形成并发送到服务器...

收到错误10019,但不是在交货后立即收到。它在新的自定义线程中接收,该线程在OnTradeTransaction()中运行。


3.3。异步订单执行速度

交易者错误地认为异步请求以接近零的速度执行。

这来自于观察ordersendasync(),它的完整规则是小于一毫秒。实际上,正如我们上面所看到的,当在OnTradeTransaction()或OnTrade()中接收到服务器响应时,应该计算事务的实际执行时间。此测量结果显示实际速度,等于单阶同步执行速度。当发送一组事务时,您可以感受到执行时间的优势。在至少三种情况下,您需要发送多个请求:

  • 两个连续请求之间的间隔太短,因此在发送请求之前无法检查请求的结果。当收到后一个请求时,希望前一个请求已被执行。高频交易也采用类似的策略;
  • 您需要同时打开多个品种的仓库。例如,套利策略和要求不同的金融工具以当前价格打开复合头寸。这样一来,我们就不希望这种地位逐渐形成。
  • 它要求线程尽快完成,并等待向后的事件和用户命令。这个需求对于多线程和基础架构解决方案很重要。这就是HedgetTerminal使用异步请求的原因。如果HT同步发送请求,则在每个用户关闭或更改位置后暂停1-2秒,这是不可接受的。

记住在下多个订单时发送请求的限制。

在MetaTrader 5 Build 1010和更高版本中,限制为64个事务,其中四个事务是为用户保留的,其他事务是为EA保留的。此限制的目的是保护新手交易者不受程序严重错误的影响,并减少交易服务器上的垃圾负载。

这意味着,同时,例如在一个循环中,您可以通过调用sendOrderAsync()为多达60个交易订单发送适当的请求。在所有60个副本发送完毕后,事务缓冲区将满。我们需要等待确认来自服务器的事务已被处理。

处理后,将释放事务缓冲区中事务的位置,并可以占用新的事务请求。一旦缓冲区满了,新事务的空间就会慢慢释放,因为事务服务器需要时间处理每个事务,并且开始处理的TradeTransaction()事件提示会通过网络传输,这会导致额外的延迟。

因此,与请求数量的增加相比,发送请求的时间要求将非线性增加。以下是异步模式下发送订单的评估率。试验进行了几次,所示时间为平均值:

请求数 时间,毫秒
五十 五十
一百 一百八十
二百 两千一百
五百 九千
一千 二万三千

当请求数小于60时,脚本不会等待服务器响应,这就是为什么时间如此之短的原因。它大约等于发送单个请求所需的时间。实际上,为了得到一个近似的实际执行时间,平均请求执行时间加上表中指定的放置请求时间。

nbsp;

第4章。Meta TaRever 5集成开发环境中的多线程编程基础

mql5程序员知道线程不能通过mql程序直接控制。这种限制对于新手程序员很好,因为线程编程算法可能非常复杂。但是,在某些情况下,两个或多个EAS必须相互通信,例如,它们创建和读取全局数据。

HedgetTerminal就是这样一个EA。为了使用HedgetTerminalAPI库通知每个EA有关其他EA操作,HT通过多线程激活来组织数据交换。XML文件。这个解决方案非常出色,很少有MQL程序员使用它。因此,我们创建了一个类似于HedgetTerminal算法的多线程EA。这有助于更好地理解多线程编程,当然还有助于了解HedgetTerminal的工作原理。

4.1。以联合交换引号为例的多线程编程

我们将通过一些特殊的例子来了解多线程编程的基础知识:我们将编写一个从不同提供者(经济上)接收报价的收集器。

其想法是:假设我们有6-7个经济学家提供相同的金融工具。当然,不同经纪人的报价略有不同。分析这些开盘价之间的差异是套利策略的一种方式。此外,比较报价动态将有助于确定最佳和大多数垃圾供应商。例如,如果一个经纪人提供了最好的价格,我们更愿意和那个经纪人交易。我们不去寻找这些结果的实际价值,而只描述它们能够实现的机制。

下面是EA的屏幕截图,我们将在本章结尾处写到:

图例. 8. 报价收集器 UnitedExhangesQuotes 的外观。

图8。出现联合呼出引号。

EA将结果显示在一个由四列组成的表中,对行数没有限制。

每一行代表经纪人对各种产品的报价(在本例中为欧元兑美元)。询问和出价是经纪人的最佳出价和需求查询。屏幕截图中显示的价格略有不同。目前经纪人和其他公司之间的差异出现在D-ASK(增量ASK)栏中。同样,需求和价格之间的差异也在D-bid(增量投标)中显示。例如,在截图的同时,“Alpari有限公司”提供了最佳询问,而最昂贵的是“Bank VTB 24”。

MQL程序无法访问其他MetaTrader终端环境。换句话说,如果一个程序在一个终端上运行,它就不能从其他终端接收数据。但是,所有MQL程序都可以通过metatrader终端共享目录中的文件进行通信。如果任何程序将信息(如当前提供的信息)写入文件,则来自其他终端的MQL程序可以读取该信息。除外部dll外,MQL没有其他方法。所以我们将使用这个方法。

最大的困难是组织这样的访问。一方面,EA从其他供应商处读取报价,另一方面-将其自己供应商的报价写入同一文档。另一个问题是,事实上,在读取报价时,其他EAS可能正在将新的报价写入此文件。这种并行工作的结果是不可预测的。最好的情况是程序随后的崩溃和中断,最坏的情况将导致与报价显示相关的偶尔的无法解释的错误。

为了消除这些错误,或者至少最小化它们的可能性,我们必须制定一个清晰的计划。

首先,所有信息将以XML格式保存。此格式替换了笨拙的ini文件。XML允许灵活扩展其节点以形成复杂的数据结构,如类。接下来,我们来确定一般的读写算法。有两种基本操作:读取数据和写入数据。当任何MQL程序正在读或写时,没有其他程序可以访问此文件。因此,我们排除了这样的情况:其中一个程序读取数据,第二个程序试图修改数据。因此,访问数据并非总是可能的。

让我们创建一个特殊的类cquotelist,它将包含从该文件访问XML和报价数据的所有算法。

这个类的一个函数是TryGethandle(),它试图访问文件并在成功时返回其句柄。功能实现如下:

int CQuoteList::TryGetHandle(void)
{
   int attempts = 10;
   int handle = INVALID_HANDLE;
   // 我们尝试打开 'attemps' 次数
   for(att = 0; att < attempts; att++)
   {
      handle = FileOpen("Quotes.xml", FILE_WRITE|FILE_READ|FILE_BIN|FILE_COMMON);
      if(handle == INVALID_HANDLE)
      {
         Sleep(15);
         continue;
      }
      break;
   }
   return handle;
}

它尝试在读/写模式下多次打开文件。默认尝试次数为10次。

如果一次尝试失败,函数将冻结15毫秒,然后再次尝试打开文件,最多10次。

打开文件后,其句柄将传递给loadQuotes()函数。在本文的附件中可以找到函数和cquotelist类的完整列表。所以这里我们只描述一个函数的动作序列:

  1. TryGethandle()打开文件进行读写;
  2. XML文档通过使用XML解析器库加载到EA内存中。
  3. 基于加载的XML文档,形成一个新的报价数组来保存信息。
  4. 创建的数组包含属于当前EA的报价。其值已更新。
  5. Quote数组将转换回XML文档。The content of the open XML file will be covered by the XML document.
  6. 报价的XML文件已关闭。

函数loadQuotes()做得很好,但在大多数情况下,它只需要不到一毫秒的时间。

数据读取、数据更新和将来的保存被合并到一个模块中。这样做是有意的,以免在读写操作之间失去对所获取文件的控制。

一旦将报价加载到类中,就可以访问它们,就像元交易者5中的任何其他数据一样。这是通过在cquotesList类中实现的一个特殊的metatrader 5样式的程序接口来实现的。

函数调用和数据呈现在ontick()模块内执行。内容如下:

//+------------------------------------------------------------------+
//| EA 即时报价函数                                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
      return;
   if(!QuotesList.LoadQuotes())    
      return;   
   PrintQuote quote = {0};
   Panel.DrawAccess(QuotesList.CountAccess());
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   string brokerName = AccountInfoString(ACCOUNT_COMPANY);
   for(int i = 0; i < QuotesList.BrokersTotal(); i++)
   {
      if(!QuotesList.BrokerSelect(i))
         continue;
      if(!QuotesList.SymbolSelect(Symbol()))
         continue;
      quote.ask = QuotesList.QuoteInfoDouble(QUOTE_ASK);
      quote.bid = QuotesList.QuoteInfoDouble(QUOTE_BID);
      quote.delta_ask = ask - quote.ask;
      quote.delta_bid = quote.bid - bid;
      quote.broker_name = QuotesList.BrokerName();
      quote.index = i;
      Panel.DrawBroker(quote);
   }
  }

值得注意的是,这个示例代码可以在metatrader 4或metatrader 5上运行,而不需要进行任何额外的修改!

不同版本的终端上面板显示只需要稍微不同的整容处理。毫无疑问,这是一个明显的事实,有利于平台之间的代码迁移。

EA的运行可以动态观察。以下视频显示了EA在不同账户中的运营情况:

和NBSP;

读写文件有明显的优点,但也有一些缺点。

主要优点是:

  1. 灵活的。您可以保存和加载任何数据,甚至整个类;
  2. 速度比较快。整个读写周期几乎不超过1毫秒,这比每次需要80-150毫秒或更长时间的缓慢交易要好。
  3. 基于mql5语言的标准工具不需要调用dll。

这种解决方案的主要缺点是存储系统负载过重。当只有一个或两个代理提供报价时,读写操作的数量相对较少,但如果有大量的代理/品种,提供大量的报价流,读写操作的数量将变得非常大。在不到一小时的时间里,示范EA产生了超过90000个报价。XML文件读写。这些统计数据显示在EA面板的顶部:“I/O rewrite”表示读写总数,“fps”表示最近两次读写之间的速率,“avrg”表示每一次精彩读写的平均速率。

如果将文件保存在sd或hdd中,这些操作将对磁盘寿命产生负面影响。所以最好使用虚拟RAM磁盘进行数据交换。

与上面的例子不同,HedgetTerminal保守地使用activeposition。只写入无法全局访问的重要仓库更改的XML。因此,它产生的读/写操作比上面的例子少得多,所以它不需要任何特殊的条件,比如RAM磁盘。

4.2。使用多线程的EAS之间的交互

独立的MQL程序之间的实时交互是一个复杂但有趣的主题。这篇文章只包含对它的一个非常简短的描述,但是它值得一篇单独的文章。在大多数情况下,EAS之间不需要多线程交互。但是,这里列出了需要这种交互组织的任务和解决方案。

  • 事务复制器。任何事务复制程序都涉及至少两个EAS的并发启动,一个提供事务,另一个复制事务。在这种情况下,需要组织多线程来读/写一个公共数据文件,以提供事务数据和复制。
  • 组织EAS之间的全局数据交换和全局变量。MetaTrader 5中的标准全局变量仅适用于同一终端内的EAS。一个终端上声明的全局变量对其他终端不可用。但是,通过使用公共数据,您可以组织复杂的全局变量,以便它们可用于所有终端,甚至是不同版本的终端。
  • 套利策略。分析来自不同流动性提供者的报价。如果不同的经纪人提供的价格足够不同,交易员可以从创造套利策略中获利。分析人员还可以收集最佳的价格统计数据,客观地确定最佳的流动性提供者。

附件说明

这是对本条附件中文件的简要说明,以及该过程的汇编。

原型。mqh文件是HedgetTerminalAPI库函数的描述。该文件包括HedgetTerminalAPI库和函数原型的描述。它让EA知道库中有哪些函数和修饰符可用,如何调用它们,以及它们的返回值是什么。

将文件保存到c:/program files/metatrader 5/mql5/include,其中“c:/program files/metatrader 5/”是metatrader 5终端的安装目录。将文件复制到正确的位置后,可以在MQL程序中引用它。当您需要使用HedgetTerminalAPI库时,可以这样做。要引用prototypes.mqh文件,请将指定的包含目录的文件添加到代码中:

#include <Prototypes.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //...
   // 此处是您的程序内容。
   //...
  }

在上面的示例中,此指令用黄色标记,并调用“include&lt;ptotype”。MQH & GT;现在,上述脚本可以引用库函数并使用它们的函数。

请注意,在HedgetTerminalAPI库的开发过程中,文件原型可能发生了轻微的变化。通常,随着库版本的更新,您还需要更新原型文件,该文件描述了更改。如果有任何不适,请原谅我。在任何情况下,原型文件的最终版本都可以从库中手动安装(安装过程在第1.1节中描述)或从文章附件下载(预计附件将定期更新)。

chaos 2.mqh是chaos 2ea的源代码。其操作说明见第1.12节:“混沌II EA例程中的sendtraderrequest函数和hedgetraderrequest结构”。要成功编译代码,请将函数原型文件保存到相应的目录/include中,并将hedgetterminal api库保存到:c:/program files/metatrader 5/mql5/market/hedgetterminalapi.ex5。这里“c:/program files/metatrader 5/”是安装metatrader 5终端的目录名(终端数据文件夹)。

UnitedExchangeQuotes源代码是一个特殊的zip文档(UnitedExchange Quotes)。ZIP),包括第4章中的项目描述:“Meta Atter 5集成开发环境中的多线程编程基础”。此zip包含以下文档:

  • UnitedExchangeQuotes的核心文件。MQ5-EA。保存到:/metatrader 5/mql5/experts文件夹。在元编辑器中编译文件。
  • 多线程XML。MQH是包含访问XML文件的多线程算法的主文件。它组织独立线程之间的信息交换。保存到/metatrader 5/mql5/include。该文件中的算法基于雅莎开发的专用程序库,该程序库已在代码库中提供。但是,它对多线程操作做了一些更改。修订版包含在附件中。它由以下几行组成:
    • XmlBase.mqh;
    • xmldocument.mqh;
    • xmltattribute.mqh;
    • XMLNET.MQH。

    保存这些文件至 /Include 文件夹。

  • 面板。MQH包包含例程中描述的面板类。将文件保存到保存UnitedChangesQuotes的同一目录。mqh,即转到/experts文件夹。

文档中的所有文件都包含相对路径。例如,文件UnitedExchangeQuotes。mq5位于/mql5/experts文件夹中。这意味着它应该放在metatrader 5终端数据文件夹的同一个子目录中,例如c:/program files/metatrader 5/mql5/experts/unitedexchangequotes.mq5。

结论

我们研究了HedgetTerminal程序接口的工作细节。

它表明这个库的原理与MetaTrader 4的API非常相似。与MetaTrader 4 API一样,在开始交易操作(MetaTrader 4中的模拟“订单”)之前,您应该首先使用Transaction Select()选择它。在套期保值终端中,交易通常是双向头寸。一旦选择了一个仓位,您就可以获取它的属性或对它应用交易行为,例如设置止损仓位或关闭它。此操作的顺序几乎与处理订单的MetaTrader 4算法相同。

除了有关双向仓库数量及其属性的基本信息外,HedgetTerminal还提供了对MetaTrader 5不能直接使用的值的访问,并且需要复杂的分析和计算。例如,您可以通过查询属性来查看每个双向位置的滑动点。您可以检查所选职位的交易数量。所有这些计算和交易所需的匹配都是在HedgetTerminal启动时“幕后”执行的。这非常方便,因为负责交易的EA不需要计算任何东西。所有必要的信息都已经计算出来,可以通过一个简单直观的API来使用。

一般算法通过HedgetTerminalAPI使用,面板可以统一数据表示。因此,您可以从HedgeTerminal面板控制EA,EA所做的更改将直接显示在面板上。

本文由MetaQuotes Software Corp.翻译自俄语原文
,网址为https://www.mql5.com/ru/articles/1316。

附加文件下载zip prototype.mqh(15.3 kb)、chaos 2.mq5(23.56 kb)、unitedexchange quotes.zip(14.09 kb)

 

 


MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经(www.myfxtop.cn)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投