构建自动运行的 EA(第 06 部分):账户类型(I)

概述

在上一篇文章构建自动运行的 EA(第 05 部分):手工触发器(II)中,我们开发了一个相当简单的 EA,但具有很高的健壮性和可靠性。 它可用于任何资产的交易,包括外汇和股票品种。 但它尚未完成自动化,完全由手工控制。

当前状态下,我们的 EA 已能在任何状况下工作,但尚未准备好自动化。 我们仍然需要在几点上努力。 在我们添加盈亏平衡或尾随停止之前,尚有一些工作要做,因为如果我们太早加入这些机制,我们以后将不得不取消一些事情。 故此,我们将采取稍微不同的路径,首先研究创建一个通用 EA。

C_Manager 类的诞生

C_Manager 类将是 EA 和订单系统之间的隔离层。 与此同时,该类将开始为我们的 EA 推进某种自动化,允许它自动执行某些操作。

现在我们来看看类的构建是如何开始的。 其初始代码如下所示:

#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Orders.mqh" //+------------------------------------------------------------------+ class C_Manager : private C_Orders {         private :                 struct st00                 {                         double  FinanceStop,                                 FinanceTake;                         uint    Leverage;                         bool    IsDayTrade;                 }m_InfosManager;         public  : //+------------------------------------------------------------------+                 C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade)                         :C_Orders(magic)                         {                                 m_InfosManager.FinanceStop = FinanceStop;                                 m_InfosManager.FinanceTake = FinanceTake;                                 m_InfosManager.Leverage    = Leverage;                                 m_InfosManager.IsDayTrade  = IsDayTrade;                         } //+------------------------------------------------------------------+                 ~C_Manager() { } //+------------------------------------------------------------------+                 void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)                         {                                 C_Orders::CreateOrder(type, Price, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);                         } //+------------------------------------------------------------------+                   void ToMarket(const ENUM_ORDER_TYPE type)                         {                                 C_Orders::ToMarket(type, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);                         } //+------------------------------------------------------------------+ };

您在上面的代码中看到的是我们将要构建的基本结构。 请注意,EA 在发送订单时不再需要提供某些信息。 一切管理都在这个 C_Manager 类中进行。 实际上,在构造函数调用中,我们传递所需的所有值,以便创建订单或发送订单开立市价持仓。

但我希望您注意一个事实:C_Manager 类继承自 C_Orders 类,但这个继承是私密的。 为什么? 原因是安全性和更高的可靠性。 当把该类放置在这里作为 “syndicator” 类型时,我们希望它是 EA 和负责发送订单的类之间的唯一通信点。

由于 C_Manager 将控制订单系统的访问,能够发送、关闭或修改订单和持仓,因此我们为 EA 提供了某种访问订单系统的方法。 但这种访问将是受限的。以下是 EA 用于访问订单系统的两个初始函数。 如您所见,它们比 C_Orders 类中的限制要多得多,但它们更安全。

为了理解我们在这里实现的事物级别,我们将上一篇文章中的 EA 代码与当前文章进行比较。 我们只创建了 C_Manager 类。 查看 EA 中存在的两个函数发生了什么。

int OnInit() {         manager = new C_Orders(def_MAGIC_NUMBER);         manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04);         mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);         return INIT_SUCCEEDED; }

以前的代码已被删除,并替换为具有大量参数的新代码。 但这仅是一个小细节。 主要的事情(在我看来,这会令一切风险更甚)如下所示:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {         uint    BtnStatus;         double  Price;         static double mem = 0;                  (*mouse).DispatchMessage(id, lparam, dparam, sparam);         (*mouse).GetStatus(Price, BtnStatus);         if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))         {                 if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  (*manager).ToMarket(ORDER_TYPE_BUY, user03, user02, user01, user04);                 if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))(*manager).ToMarket(ORDER_TYPE_SELL, user03, user02, user01, user04);                 if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  (*manager).ToMarket(ORDER_TYPE_BUY);                 if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))(*manager).ToMarket(ORDER_TYPE_SELL);         }         if ((def_SHIFT_Press(BtnStatus) != def_CTRL_Press(BtnStatus)) && def_BtnLeftClick(BtnStatus))         {                 if (mem == 0) (*manager).CreateOrder((def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), mem = Price, user03, user02, user01, user04);                 if (mem == 0) (*manager).CreateOrder((def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), mem = Price);         }else mem = 0; }

有些部份已被简单地删除。 反过来,您可以看到新代码要简单得多。但不仅如此。 使用类来执行“管理员”工作可以保证 EA 能准确使用在类初始化时定义的那些参数。 故此,不会有在其中一个调用中放置错误或无效信息的风险。 一切都集中在一个地方,即在 C_Manager 类当中,该类现在充当 EA 和 C_Orders 之间的通信中介。 这显著提高了 EA 代码的安全性和可靠性级别。

NETTING、EXCHANGE 或 HEDGING 账户... 这就是问题所在

尽管许多人忽略或未意识到这一事实,但这里有一个严重的问题。 正因为如此,EA 可能会、也可能不会很好地工作 — 这就是账户类型。 大多数交易者和 MetaTrader 5 平台用户不知道市场上有三种类型的账户。 但对于那些想要开发在全自动模式下运行的智能系统的人来说,这些知识至关重要。

在本系列文章中,我们将讨论两种账户类型:净持结算和对冲。 原因很简单:EA 针对净持结算账户的操作方式与证券账户相同。

即使 EA 具有简单的自动化,例如盈亏平衡或尾随停止激活,它在净持结算账户上与在对冲账户上运行时的操作事实上完全不同。 原因在于交易服务器的操作方式。 在净持结算账户上,交易服务器会随着您增加或减少持仓而创建摊平价格。

而对冲账户在服务器上不会这样做。 它分别处理所有持仓,因此您可以同时拥有同一资产的多头和空头持仓。 这永远不会发生在净持结算账户上。 如果您尝试以相同的手数开立相反的仓位,服务器实际上是平仓。

出于这个原因,我们必须知道 EA 是为净持结算、还是为对冲账户设计的,因为操作原理完全不同。 但这仅适用于自动化 EA,或具有一定自动化级别的 EA。 对于手工 EA,这无关紧要。

因为这个事实,我们若不排除编程或可用性方面的一些困难,就难以创建任何自动化级别。

在此,我们需要稍微进行一点标准化。 换句话说,我们需要确保 EA 能够以标准化的方式在任何账户类型上操作。 确实,这将降低 EA 的能力。 然而,自动 EA 理应具有很大的自由度。 最好的方法是限制 EA,令其行为良好。 如果它偏离了一些,就必须禁止它、或至少受到一些惩罚。

标准化的方式是 EA 在对冲账户上的操作方式与净持结算账户上类似。 我知道这可能看起来令人困惑和复杂,但我们真正想要的是允许 EA 只有一笔持仓和一笔挂单,换言之,它极端有限,并且无法做任何其它事情。

因此,我们将以下代码添加到 C_Manager 类之中:

class C_Manager : private C_Orders {         private :                 struct st00                 {                         double  FinanceStop,                                 FinanceTake;                         uint    Leverage;                         bool    IsDayTrade;                 }m_InfosManager; //---                 struct st01                 {                         ulong   Ticket;                         double  SL,                                 TP,                                 PriceOpen,                                 Gap;                         bool    EnableBreakEven,                                 IsBuy;                         int     Leverage;                 }m_Position;                 ulong           m_TicketPending;                 bool            m_bAccountHedging; double m_Trigger;

在该结构中,我们创建了处理持仓可能需要的所有内容。它已经具有一些与第一级自动化相关的东西,例如盈亏平衡和尾随停止。 挂单将采用更简单的方式存储,即单号。但是,如果我们将来需要更多数据,我们也能实现它。 现在这就足够了。 我们还有另一个变量能告诉我们所用的是对冲账户亦或是净持结算账户。 它在某些时刻会特别实用。 像往常一样,还添加了另一个变量,其在此阶段不会用到,但我们稍后在创建盈亏平衡和尾随停止触发器时将需要它。

这就是我们如何开始令事情正常化。 之后,我们可以对类构造函数进行修改,如下所示:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)                         :C_Orders(magic),                         m_bAccountHedging(false),                         m_TicketPending(0),                         m_Trigger(Trigger)                         {                                 string szInfo;                                                                  ZeroMemory(m_Position);                                 m_InfosManager.FinanceStop = FinanceStop;                                 m_InfosManager.FinanceTake = FinanceTake;                                 m_InfosManager.Leverage    = Leverage;                                 m_InfosManager.IsDayTrade  = IsDayTrade;                                 switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))                                 {                                         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:                                                 m_bAccountHedging = true;                                                 szInfo = "HEDGING";                                                 break;                                         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:                                                 szInfo = "NETTING";                                                 break;                                         case ACCOUNT_MARGIN_MODE_EXCHANGE:                                                 szInfo = "EXCHANGE";                                                 break;                                 }                                 Print("Detected Account ", szInfo);                         }

我将为那些没有编程经验的人一点一点地展示代码。 我希望我不会对此感到太无聊,因为我希望每个人都能够理解我们正在这里做什么。 以下就是解释。 这些行通知编译器我们希望在构造函数代码执行开始之前初始化这些变量。当创建一个变量时,编译器通常会为其分配零值。

在这些行中,我们告诉编译器,当我们创建变量值时它应是什么值。 在此刻,我们重置结构的所有内容。 以这种方式,我们就能用更少的代码来获得更快的结果。 在此,我们注意到我们正在操控对冲账户的事实。 如果在某些时候需要此信息,我们有一个变量来说明这一点。 我们已在此处通知终端找到了哪种帐户类型。这样做是为了指示类型,以防用户事先并不知晓。

但在我们查看这些过程之前,思考以下事项:如果 EA 发现多笔持仓(对冲账户),或多笔挂单该怎么办? 那会发生什么呢? 在这种情况下,我们将收到一个错误,因为 EA 将无法处理多于一笔的持仓和订单。 为了解决这个问题,我们在代码中创建以下枚举:

class C_Manager : private C_Orders {         enum eErrUser {ERR_Unknown};         private : // ... The rest of the code... };

在此,我们将使用枚举,因为向其添加新的错误代码更容易。为此,我们只需要指定一个新名称,编译器就会为代码生成一个值,同时不会因为疏忽而产生数值重叠的风险。 请注意,枚举位于私密代码部分之前,因此它将是公开的。 但是要在类的外部访问它,我们需要使用一个小细节来通知编译器哪个枚举是正确的。 当我们想要使用与特定类相关的枚举时,这特别有用。 现在我们来看一下该过程,如何加载可能留在图表上的内容,以及 EA 在开始工作之前必须恢复的内容。 第一个如下:inline void LoadOrderValid(void)                         {                                 ulong value;                                                                  for (int c0 = OrdersTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)                                 {                                         if ((value = OrderGetTicket(c0)) == 0) continue;                                         if (OrderGetString(ORDER_SYMBOL) != _Symbol) continue;                                         if (OrderGetInteger(ORDER_MAGIC) != GetMagicNumber()) continue;                                         if (m_TicketPending > 0) SetUserError(ERR_Unknown); else m_TicketPending = value;                                 }                         }

我们来看看这段代码是如何工作的,以及为什么它看起来如此不寻常。 在此,我们使用循环来读取订单簿中的所有挂单。 如果有订单存在,OrdersTotal 函数将返回一个大于零的数值。 索引始终从零开始。 它来自 C/C++。 但我们有两个令循环结束的条件:首先,c0 变量的值小于零,其次 - _LastError 是来自 ERR_SUCESS 的不同形式 ,这表明 EA 中发生了一些故障。

因此,我们进入循环,并捕获由变量 c0 指示的索引处第一笔订单,OrderGetTicket 将返回单号值或零。 如果它为零,我们将返回循环,但现在我们从变量 c0 中减一。

由于 OrderGetTicket 加载订单值,且系统不会区分它们,因此我们需要过滤所有内容,如此 EA 就只得到我们的特定订单。 因此,我们将用到的第一个过滤器是资产名称;为此,我们将按资产的排列顺序与正在运行的 EA 进行比较。如果它们不同,订单将被忽略,如果匹配,我们将其返回。

下一个过滤器是魔幻数字,因为订单簿也许有手工下达的订单,或由其它 EA 下达的订单。 我们可以根据每个 EA 所独有的魔幻数字来核实订单是否由我们的 EA 所下达。 如果魔幻数字与 EA 所用的不同,则应忽略该笔订单。 然后我们回到开头寻找新订单。

现在我们来到了十字路口。如果以前 EA 下过订单,但出于某种原因被从图表中删除(稍后我们将看到原因是什么),那么 EA 重启后它也会被记忆,即变量会指示挂单的单号具有非零值。 然后,如果又遇到第二笔订单,它将被视为出错。 该函数将使用枚举来显示已发生错误。

在此,我用公共值 ERR_Unknown,但您可以创建一个值来指定错误,该错误将显示在 _LastError 值当中。 SetUserError 函数负责在 _LastError 变量中设置错误值。但如果一切正常,并且包含订单单号的变量设置为零,则经过所有过滤之后找到的订单值将保存在 m_TicketPending 变量中,以供进一步使用。 这就是已完成过程需要我们解释的地方。 我们来研究下一段负责搜索任何持仓的模块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赫兹量化软件

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值