English Русский Español Deutsch 日本語 Português
preview
构建自动运行的 EA(第 12 部分):自动化(IV)

构建自动运行的 EA(第 12 部分):自动化(IV)

MetaTrader 5交易 | 29 五月 2023, 09:56
1 816 0
Daniel Jose
Daniel Jose

概述

在上一篇文章构建自动运行的 EA(第 10 部分):自动化(III)中,我们研究了如何构建一个健壮的系统,最大限度地减少可能影响程序的故障和漏洞。

我看到有人不时说,这种事情发生的几率是 500 万分之一,但即使百分比很低,这种可能性仍然存在。 既然我们知道这种情况可能发生,为什么不创造方法来最大限度地减少这种情况的损害或副作用呢? 为什么忽略可能发生的事情,而不修复它,或以某种方式阻止它,只是因为机会很低?

如果您正在关注这篇关于如何构建自动化 EA 的系列文章,您一定已经注意到创建手动使用的 EA 非常快速和简单。 但对于 100% 自动化的 EA,事情则并没有那么简单。 您也许已经注意到,我从未表达过我们将拥有一个 100% 万无一失的系统,且该系统可以在没有任何监督的情况下使用的想法。 事实上,我相信它已经变得很清楚,与许多人对自动 EA 的这个前提认知恰恰相反,您以为打开它,就可以放任它在那里滚动,而无需真正明白它正在做什么。

当我们谈论 EA 的 100% 自动化时,一切都变得严肃而复杂。 更重要的是,我们将始终受到运行时错误的影响,并且我们必须制作一个可以实时工作的系统。 这两件事结合在一起,再加上我们在系统中可能存在一些漏洞或故障,令那些将在 EA 运行期间需要监督 EA 的人来说,这项工作非常累人。

但作为一名程序员,您应该始终关注一些可能产生潜在问题的关键点,即使一切都看似完美和谐。 我并不是说您应该寻找可能不存在的问题。 这就是一位仔细和认真的专业人士理应会做的事情 — 在一个乍看没有缺陷的系统中寻找缺陷。

在当前开发阶段我们的 EA 旨在手动和半自动使用(使用盈亏平衡和尾随停止),没有像 BlockbusterDC 超级反派)那样的破坏性漏洞。 然而,如果我们以 100% 自动化使用它,情况就会发生变化,并且存在潜在危险故障的风险。

在上一篇文章中,我提出了这个问题,并留给您了解这个缺陷在哪里,以及它如何导致问题,如此我们还无法 100% 自动化我们的 EA。 您是否设法了解故障在哪里?以及如何触发故障? 好吧,如果答案是否定的,那没关系。 并不是每个人都能通过查看代码,并在手动或半自动使用它的过程中真正注意到故障。 但如果您尝试自动化代码,就会遇到严重的问题。 这个缺陷当然是最简单的缺陷,但却不是那么容易纠正,这是 100% 自动化 EA 所必需的。

因此,若要了解它的内容,我们需将事情划分为几个主题。 我认为您会更容易注意到一些看似不重要的东西,这可能会给您带来很大的烦恼。


理解问题

当我们为 EA 设置每天可以交易的最大交易量限制时,问题就开始了。 不要将每日最大交易量与可操作交易量混淆。 现在我们主要对每日最大交易量感兴趣。

为清晰起见,我们假设交易量是最小交易的 100 倍。 也就是说,EA 能够进行尽可能多的操作,直到达到此交易量。 因此,在 C_Manager 类中添加的最后一条规则是交易量不超过 100。

现在,我来们看看实际发生了什么。 为此,我们需分析我们允许交易的代码:

inline bool IsPossible(const bool IsPending)
                        {
                                if (!CtrlTimeIsPassed()) return false;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
                                if ((IsPending) && (m_TicketPending > 0)) return false;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return false;
                                }
                                
                                return true;
                        }

上面的代码可防止超过交易量。 但它实际上是如何发生的呢?

假设交易者以所需最小交易量的 3 倍手数启动 EA,EA 代码中定义的最大交易量为 100 倍(这是在代码编译期间完成的)。 这在之前的文章中都已经解释过。 经过 33 笔交易后,EA 将达到最小交易量的 99 倍,这意味着我们还可以再进行一笔交易。 然而,由于上面代码中高亮显示的行,交易者必须将交易量改为 1 倍才能达到最大限制。 否则,EA 将无法执行该操作。

这个想法是限制最大交易量,如此 EA 的损失就不会超过先前指定的参数(这必须始终是主要和最重要的问题)。 因为即使 EA 开仓量并未远高于规定交易量,我们仍然会有亏损,但这些都能以某种方式得到控制。

但您可能会想:我看不出这段代码有任何漏洞。 事实上,这段代码没有任何漏洞。 使用它的函数(如下所示)能够将 EA 的交易量限制在指定的最大限额。

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!IsPossible(true)) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!IsPossible(false)) return;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

但是错误在哪里呢? 实际上,这并不容易理解。 若要明白这个问题,我们需要思考 EA 如何不执行发送订单的操作。 因为 C_Manager 类的这段代码将设法防止 EA 触发。 当我们组合不同的订单类型时,就会出现问题。 此刻,我们将触发碎块问题。 有一些途径可以限制,或者避免触发这种类型。 它们如下:

  • 选择订单发送类型或型号。 在这种情况下,EA 将仅支持市价单或挂单。 这种类型的解决方案适用于某些自动交易模型,因为在某些情况下,仅按市价进行操作更合适、且更常见。 但在这种情况下,我们的系统所拥有的交易类型有限。 但此处的想法是展示如何创建一个可以覆盖尽可能多的案例的系统,故无需担心使用哪种订单类型。
  • 避免触发的另一种方式是更深入地卡频率,已入场的那些持仓,以及成为持仓哪些内容将会变更(在本例中为挂单)。 此解决方案是更好的选择,但它有一些激进因素,其令编程变得复杂。
  • 另一种方式(我们将要实现)是利用已开发的系统作为基础,如此我们将考虑正在做的事情,但我们不会对可以做什么做出假设。 但即便如此,我们也会尝试以某种方式令 EA 预测一天中整个时间段内的交易量。 以这种方式,我们可以避免触发,而不必限制 EA 系统可以支持的交易类型。

现在,考虑到当我们有订单组合时会出现问题的事实,我们要了解事情是如何真正发生的。 回到手数乘数 3 的例子,其中已经发生了 33 次操作,而每日限制为 100 倍,如果 EA 只处理市价单,一切都将完美控制。 但无论出于何种原因,如果我们在交易服务器上有一笔挂单,情况就不同了。 如果此挂单在最小交易量上再增加 3 个单位,那么它一旦倍激活,它将超过 EA 中允许的最大交易量。

您可以想象,这 2 个单位的交易量超过了 100 倍的限制并不多,但它并没有那么简单。 想想交易者将 EA 配置为交易 50 手的情况。 我们可以执行 2 笔市价订单,这超过了 100 的限制。 发送市价单后,EA 又发送一笔挂单,以便增加持仓量。 现在它将达到 100 的交易量。 但无论出于何种原因,此订单尚未执行,因为它仍然是挂单。 在某个时刻,EA 决定它可以发送另一个相同 50 手交易量的市价单。

C_Manager 类立即注意到 EA 已达到每日限量,因为它将已有 100 个单位。 但 C_Manager 类尽管知道有一笔挂单,但并不真正知道如何处理它。 在这种情况下,当该挂单在服务器上被执行时,EA 将超过 100 的规则,交易量为 150 个单位。 您明白问题所在吗? 前段时间我们放置了一个锁,以防止 EA 在账簿上有太多挂单,或以某个交易量开仓太多。 这个障碍被一个简单的事实打破了,即自动化 EA 的触发器没有预见到这种情况会发生。 有一个假设,即 C_Manager 类理应阻挡 EA,防止其超过定义的最大交易量。 但是该类失能了,因为我们把挂单和市价单结合起来了。 

许多程序员会简单地通过仅用市价单,或仅用挂单来解决此问题。 但这并不能让问题消失。 每个新的自动 EA 都必须经过相同的测试和分析模式,从而防止触发触发器。

尽管即使手动系统也可以观察到上述问题,但交易者犯错误的可能性要小得多。 交易者应受到谴责,而不是保护系统。 但对于 100% 自动化的系统,这是完全不可接受的。 因此,源于这般和其它一些原因,即使您已经对其进行了编程,也永远不要让 100% 自动化 EA 在没有监督的情况下运行。 您也许是一名优秀的程序员,但绝不建议任其独自行动。

如果您认为我在胡说八道,请考虑以下几点。 飞机上有自动驾驶系统,可以让它在没有任何人为干预的情况下起飞、行驶和降落。 但即便如此,机舱内总有合格的飞行员来操作飞机。 您想想为什么会这样? 该行业不会花费大量资金只为了开发自动驾驶仪,而必须培训一名飞行员来操作飞机。 因为这是没有意义的,除非该行业本身不依赖自动驾驶仪。 好好想想这一点。


修复崩溃

实际上,仅示意错误并不能解决它。 需要的不仅如此。 但事实上,我们知道缺陷,并明白它是如何触发的,及其后果,这意味着实际上我们可以尝试生成某种解决方案。

我们相信,C_Manager 类将能够避免违反交易量,这一事实令一切变得不同。 为了解决这个问题,我们需要在代码中做一些事情。 首先,我们往系统里添加一个新变量:

                struct st01
                {
                        ulong   Ticket;
                        double  SL,
                                TP,
                                PriceOpen,
                                Gap;
                        bool    EnableBreakEven,
                                IsBuy;
                        uint    Leverage;
                }m_Position, m_Pending;
                ulong   m_TicketPending;

通过生成某种预测,这个新变量能帮助我们解决交易量问题。 因为它的出现,代码的另一处就被删除了。

新变量加入后会进行一系列修改。 但我只会强调新添的部分。 您可以在附带的代码中更详细地查看所有修改。 首先要做的是创建一个例程来捕获挂单数据:

inline void SetInfoPending(void)
                        {
                                ENUM_ORDER_TYPE eLocal = (ENUM_ORDER_TYPE) OrderGetInteger(ORDER_TYPE);
                                
                                m_Pending.Leverage = (uint)(OrderGetDouble(ORDER_VOLUME_CURRENT) / GetTerminalInfos().VolMinimal);
                                m_Pending.IsBuy = ((eLocal == ORDER_TYPE_BUY) || (eLocal == ORDER_TYPE_BUY_LIMIT) || (eLocal == ORDER_TYPE_BUY_STOP) || (eLocal == ORDER_TYPE_BUY_STOP_LIMIT));
                                m_Pending.PriceOpen = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Pending.SL = OrderGetDouble(ORDER_SL);
                                m_Pending.TP = OrderGetDouble(ORDER_TP);
                        }

这与捕获持仓数据的例程不同。 主要区别在于我们要检查我们是买入还是卖出。 这是通过检查类型,来指明买入订单。 函数的其余部分是不言自明的。

我们需要另一个新函数:

                void UpdatePending(const ulong ticket)
                        {
                                if ((ticket == 0) || (ticket != m_Pending.Ticket) || (m_Pending.Ticket == 0)) return;
                                if (OrderSelect(m_Pending.Ticket)) SetInfoPending();
                        }

当挂单从服务器收到任何更新,并将信息传递给 EA 时,它就会更新数据。

为了令 EA 能够执行上述调用,我们需要向 OnTradeTransaction 处理程序添加一个新事件:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
        switch (trans.type)
        {
                case TRADE_TRANSACTION_POSITION:
                        manager.UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                        if (trans.order == trans.position) (*manager).PendingToPosition();
                        else (*manager).UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        (*manager).UpdatePending(trans.order);
                        break;
                case TRADE_TRANSACTION_REQUEST: if ((request.symbol == _Symbol) && (result.retcode == TRADE_RETCODE_DONE) && (request.magic == def_MAGIC_NUMBER)) switch (request.action)
                        {
                                case TRADE_ACTION_DEAL:
                                        (*manager).UpdatePosition(request.order);
                                        break;
                                case TRADE_ACTION_SLTP:
                                        (*manager).UpdatePosition(trans.position);
                                        break;
                                case TRADE_ACTION_REMOVE:
                                        (*manager).EraseTicketPending(request.order);
                                        break;
                        }
                        break;
        }
}

上面高亮显示的行将调用 C_Manager 类

如此,我们回到 C_Manager 类,继续实现交易量问题的解决方案。

如本节所述,我们需要创建一个更正系统,从而令系统能够达到更充分的安全性级别。 我们已注意到并忽略了很长时间的错误,就是负责更新开单的交易量。 这不会影响手动系统,但对于自动化系统来说则是致命的: 因此,要修复此错误,我们必须添加以下代码行

inline void LoadPositionValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = PositionsTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = PositionGetTicket(c0)) == 0) continue;
                                        if (PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
                                        if (PositionGetInteger(POSITION_MAGIC) != GetMagicNumber()) continue;
                                        if ((m_bAccountHedging) && (m_TicketPending > 0))
                                        {
                                                C_Orders::ClosePosition(value);
                                                continue;
                                        }
                                        if (m_Position.Ticket > 0) SetUserError(ERR_Unknown); else
                                        {
                                                m_Position.Ticket = value;
                                                SetInfoPositions();
                                                m_StaticLeverage = m_Position.Leverage;
                                        }
                                }
                        }

设计全自动系统是一项具有挑战性的任务。 对于手动或半自动系统,有没有上述代码行无丝毫区别。 但对于自动化系统来说,任何故障,无论多么小,都可能意味着发生灾难的可能性。 如若交易者不监督 EA,或不知道它实际在做什么,那就更是如此。 这肯定会让您在市场上赔钱。

下一步是修改订单发送和行情请求函数。 我们需要它们能够向调用方返回数值,同时还能够通知调用方正在发生的事情。 这就是代码:

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(true)) return bRet;
                                m_Pending.Ticket = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(false)) return bRet;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

请注意,我正在进行测试来检查单号是否仍然有效。原因是在净持账户中,您可以通过发送与持仓相等的交易量来平仓。 在这种情况下,如果平仓,我们需要从中删除数据,以提高系统的安全性和可靠性。 这是针对 100% 自动化的 EA,而对于手动 EA,这些事情是不必要的。

接下来,我们需要添加一种方法让 EA 知道要发送的交易量。 如果您想逆转当前系统中的持仓,您可以执行此操作,但是需要两次调用,或者更确切地说,需要向服务器提交两次请求,而不是单次请求。 目前您需要平仓,然后发送请求开新仓。 知道开仓交易量后,EA 就可知道要发送哪个交易量。

const uint GetVolumeInPosition(void) const
                        {
                                return m_Position.Leverage;
                        }

上面的这段简单代码足以实现这一点。 但是在类代码中没有办法真正反转持仓。

为了实现这一点,我们将再次修改订单发送函数。 但请注意,这不是手动或半自动 EA 需要具备的。 我们正在进行这些更改,因为我们需要这些来生成自动化 EA。 此外,在某些点上放置某种消息很有趣,这样监督 EA 的交易者就可以了解 EA 的实际操作。 即使我们在演示代码中不这样做,您也应该认真考虑正在做的这样事情。 因为盲目自大,仅仅观看图表,实际上不足以注意到一些奇怪的 EA 行为。

在实现所有这些更改之后,我们到达了我们的关键点,其确能将解决我们正在处理的问题。 我们为 EA 添加了一种能够将任意交易量发送到服务器的方法。 这仅在两次调用中完成,其中 C_Manager 类将授予对 EA 的访问权限。 因此,我们修复了 EA 最终可能超越代码中所指定最大交易量的问题。 现在我们将开始预测交易量,这可能会是将要入场或已开持仓的离场。

新的调用代码如下所示

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const uint LeverageArg = 0)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                m_Pending.Ticket = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type, const uint LeverageArg = 0)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

在此代码中,我们添加了该参数,但请注意其默认值为零。如果您在声明函数时未指定数值,它将采用构造函数调用期间指定的数值。

但在此步骤中不会进行任何处理,因为 C_Manager 类应该分析将要执行什么或是否允许,对于下挂单和发送执行市价单的请求都是常见的。 为了找出 EA 期望通过 C_Manager 启用哪个值,我们使用一个带有三元运算符的小测试来正确填充该值。现在我们来看看需要向函数添加什么才能正确设置测试:

inline bool IsPossible(const ENUM_ORDER_TYPE type, const uint Leverage)
                        {
                                int i0, i1;
                                
                                if (!CtrlTimeIsPassed()) return false;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
                                if ((m_Pending.Ticket > 0) || (Leverage > INT_MAX)) return false;
                                i0 = (int)(m_Position.Ticket == 0 ? 0 : m_Position.Leverage) * (m_Position.IsBuy ? 1 : -1);
                                i1 = i0 + ((int)(Leverage * (type == ORDER_TYPE_BUY ? 1 : -1)));
                                if (((i1 < i0) && (i1 >= 0) && (i0 > 0)) || ((i1 > i0) && (i1 <= 0) && (i0 < 0))) return true;
                                if ((m_StaticLeverage + MathAbs(i1)) > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return false;
                                }
                                
                                return true;
                        }

在对触发机制进行编程时,您应该注意一些事情,从而避免请求被拒绝。 如果是挂单,则必须在执行任何修改交易量的订单之前将其删除。 如果不这样做,请求将被拒绝。 为什么? 这似乎很奇怪。 故此,它确实值得解释。

原因是没有办法真正知道 EA 是否允许执行挂单。 但是,如果开仓交易量发生变化,并且系统使用挂单作为持仓止损的一种方式,则在更改交易量之前删除此订单非常重要。 无论如何,订单将被删除,以便放置包含正确交易量的新订单。

因此,很明显,C_Manager 类要求在更改开仓交易量时,应不存在挂单。 另一个原因是,如果存在多头持仓,并且您打算反转它,这可通过下一笔交易量大于持仓交易量的订单来完成。 如果挂单(最初是止损单)若继续存在,则在订单执行时也许会出现问题。 交易量将进一步增加。 所以,我们多了一个原因,即 C_Manager 在修改交易量实际上要求没有挂单。

我希望现在很清楚为什么该类在更改交易量时需要删除挂单。 不删除这一行,EA 不会向服务器发送请求。

现在我们有一个相当奇怪的计算,和一个更奇怪的测试,如果您不真正理解计算,却试图理解它,最终可能会让您头疼。 最后,还有另一个乍一看没有任何意义的测试。

我们仔细分析这一刻,以便每个人都能真正理解这种彻底的数学疯狂。 为了方便起见,我们看一些示例。 请在阅读每个示例时要非常小心,以便能够理解所有高亮显示的代码。

示例 1:

假设没有持仓,则 i1 值将等于 Leverage 变量中包含的绝对值;这是最简单的情况。 那么,我们是否在订单簿中开仓或下订单并不重要。 如果 i1 与 EA 累积值的总和小于指定的最大值,则请求将被发送到服务器。

示例 2:

假设我们有一笔交易量 X 的空头持仓,并且我们没有挂单。 在这种情形下,我们可以有若干种不同的场景:

  • 如果 EA 发送卖出交易量 Y 的订单,则 i1 值将是 X 和 Y 的总和。在这种情况下,订单可能无法成交,因为我们增加了空头持仓。
  • 如果 EA 发送买入交易量 Y 的订单,并且它小于交易量 X,则 i1 值将等于 X 和 Y 之间的差值。在这种情况下,订单因降低空头持仓而通过。
  • 如果 EA 的订单是买入 Y,而 Y 等于 X,则 i1 将为零。 在这种情况下,订单通过,且空头持仓平仓。
  • 如果 EA 发送买入交易量 Y 的订单,并且它大于交易量 X,则 i1 值将等于 X 和 Y 之间的差值。在这种情况下,订单可能不允许,因为我们将持仓由空头变成多头。

多头持仓也是如此。 然而,EA 的请求也会被修改,如此在最终我们将具有相同的行为,如 i1 变量所示。 请注意,在测试中,我们检查 i1 和 EA 累积值的总和是否小于允许的限制,我们调用 MathAbs 函数。 我们这样做是因为在某些情况下,我们会有负数值的 i1。 但我们要求它是正数值,如此才能令测试正常运行。

但我们仍有最后一个问题需要解决。 当我们反转持仓时,将无法正确更新交易量。 那为了解决这个问题,我们需要在分析系统里进行一个小的修改。 修改如下所示:

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                uint tmp = m_Position.Leverage;
                                bool tBuy = m_Position.IsBuy;
                                
                                m_Position.Leverage = (uint)(PositionGetDouble(POSITION_VOLUME) / GetTerminalInfos().VolMinimal);
                                m_Position.IsBuy = ((ENUM_POSITION_TYPE) PositionGetInteger(POSITION_TYPE)) == POSITION_TYPE_BUY;
                                m_Position.TP = PositionGetDouble(POSITION_TP);
                                v1 = m_Position.SL = PositionGetDouble(POSITION_SL);
                                v2 = m_Position.PriceOpen = PositionGetDouble(POSITION_PRICE_OPEN);
                                if (m_InfosManager.IsOrderFinish) if (m_Pending.Ticket > 0) v1 = m_Pending.PriceOpen;
                                m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_Pending.Ticket == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return (int)(tBuy == m_Position.IsBuy ? m_Position.Leverage - tmp : m_Position.Leverage);
                        }

首先,我们保存系统在更新之前的持仓。 在那之后,我们不会干涉任何事情,直到最后,直到我们检查持仓方向是否有任何变化。这个思路就是检查我们的持仓是多头还是空头。 这同样适用于相反的情况。 如果有变化,我们将返回开仓交易量。 否则,我们正常执行计算,以便搞清当前开仓的交易量。 以这种方式,我们就可以正确了解和更新 EA 已完成交易的交易量。


章末结束语

尽管当前的系统复杂了许多,因为我们需要确保自动化系统没有隐患,但您必定已经注意到,终端中几乎没有打印任何消息,而交易者本可基于这些消息监控 EA 的操作。 这极其重要。

不过,由于我在这里只展示了如何创建一个 100% 自动化的系统,我无法告诉您何时、何处 EA 内部相应地发生了什么。 不同的信息对于不同的人或多或少很重要。 但我不建议任何人简单地取得代码,并在真实帐户中将其直接启动,而并未进行多次测试和调整,以便搞清楚将会发生什么。

在下一篇文章中,我将展示如何设置和启动 EA,令其能够自主运行。 如此,在我提供解释的文章出来之前,请尝试研究这篇文章。 尝试理解简单的措施如何解决某些问题,这些问题通常比看起来要复杂得多。

我并非试图向您展示创建自动 EA 的最终方法。 我只是展示可用的许多可能方式之一。 我还挑明了使用自动 EA 相关的风险,以便让您明白如何将它们最小化,或降低到可接受的水平。

对于那些也许去尝试手动或自动化使用 EA 的人的最后一个提示。 如果系统不允许发送订单,并说交易量违反了用法规则,您需要做的就是更改以下值:

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 10);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

默认情况下,该值为 10。 但可能会发生这种情况,尤其是在外汇市场中,您需要采用更大的数值。 许多人通常采用 100。 如此,应采用比 10 大得多的值。 例如,如果要采用通常得交易量 10 的 100 倍,则输入值 1000。 所以代码将如下所示:

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 1000);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

故此,在 EA 阻止发送新订单之前,您最多可以发送 10 次指定交易量的订单。

在下面的视频中,您可以看到系统当前配置。



本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11305

附加的文件 |
种群优化算法:细菌觅食优化(BFO) 种群优化算法:细菌觅食优化(BFO)
大肠杆菌觅食策略激发出科学家创建 BFO 优化算法的灵感。 该算法包含原创思路和有前景的优化方法,值得深入研究。
重温默里(Murrey)系统 重温默里(Murrey)系统
图形价格分析系统在交易者中当之无愧地广受欢迎。 在本文中,我将讲述完整的默里(Murrey)系统,包括其著名的级别,以及其它一些评估当前价格位置,并据其做出交易决策的实用技术。
在 Linux 上利用 C++ 多线程支持开发 MetaTrader 5 概念验证 DLL 在 Linux 上利用 C++ 多线程支持开发 MetaTrader 5 概念验证 DLL
我们将开始探索如何仅基于 Linux 系统开发 MetaTrader 5 平台的步骤和工作流程,其中最终产品能在 Windows 和 Linux 系统上无缝运行。 我们将了解 Wine 和 Mingw;两者都是制作跨平台开发任务的基本工具。 特别是 Mingw 的线程实现(POSIX 和 Win32),我们在选择追随哪一个时需要仔细考虑。 然后,我们构建一个能在 MQL5 代码中所用的概念验证 DLL,最后比较两种线程实现的性能。 这一切都是为了您的基金能进一步扩张自己。 阅读本文后,您应该可以轻松地在 Linux 上构建 MT 相关工具。
构建自动运行的 EA(第 11 部分):自动化(III) 构建自动运行的 EA(第 11 部分):自动化(III)
如果没有健全的安全性,自动化系统就不会成功。 但是,如果不对某些事情有很好的理解,就无法确保安全性。 在本文中,我们将探讨为什么在自动化系统中实现最大安全性是一项挑战。