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

中一些功能涉及面向对象的编程模型。 实际上,此模型是迄今为止最适合创建需要良好安全级别的程序的模型。 有因于此,从本系列开始,您一定已经注意到,所有的编程都集中在类的使用上,这意味着正是应用了面向对象的编程。 我知道这种类型的编程一开始可能看起来令人困惑且难以学习,但相信我,如果您真正付出努力,并学会将代码创建为类,您将受益匪浅。

我这样说是为了向你们展示,作为有抱负的专业程序员,应如何创建您的代码。 您必须习惯使用 3 个文件夹,其中将保存代码。 无论程序多么复杂或简单,您都必须始终分为 3 个步骤工作:

  1. 在第一阶段,即开发文件夹中,我们创建、修改和测试所有代码。 任何新功能或更改都必须仅在此文件夹中的代码中进行。
  2. 构建并测试代码后,必须将其移至第二个文件夹,即工作文件夹。 在此文件夹中,代码也许仍包含一些错误,但您不应在此处修改它。 如果您需要编辑在此文件夹中的代码,请将其移回开发文件夹。 一个细节:如果您编辑代码只是纠正一些发现的缺陷,而不进行任何其它更极端的修改,则可以将此工作文件夹的代码保留在其中,并接受适当的更正。
  3. 最后,在不同情况下反复运行代码若干次,而无需任何新的更改后,将其移至第三个也是最后一个“稳定”文件夹。 此文件夹中存在的代码现在已经证明无缺陷,并且对于为其设计的任务非常有用和高效。 切勿在此文件夹中添加新的代码。

如果您应用这种方式一段时间,您最终会创建一个非常有趣的函数和过程数据库,您就能够非常快速和安全地编程。 这种事情正得到高度赞赏,尤其是在金融市场这样的活动中,没有人会有兴趣使用无法应对市场存在风险的代码。 鉴于我们始终在一个不接受运行时错误的领域工作,并且一切都发生在最坏的场景类型(即实时)中,因此您的代码必须能够随时抵抗意外事件。

综上所述,都是为了达到以下几点,如图例 01 所示:

图例 01. 手动控制系统

许多人并不完全明白他们正在进行的编程究竟做什么或创建什么。 这是因为他们中的许多人并不真正理解系统内部发生了什么,且最终认为平台应该提供交易者想要做的事情。 然而,如果您查看图例 01,您就明白该平台并不会专注于提供交易者想要的东西。 取而代之,它应该提供与交易者互动的方式,同时能与交易服务器稳定、快速、高效地互动。 与此同时,平台必须保持运行,并且必须支持与交易者交互的元素。

请注意,没有一支箭超越其顶点,这表明这是一个手动交易系统。 另外,请注意,EA 由平台提供服务,而不是绕其它路,交易者不直接与 EA 通信。 尽管也许看起来很奇怪,但实际上交易者通过平台访问 EA,而平台控制 EA 的手段则是向它发送交易者创建或执行的事件。 作为响应,EA 向平台发送请求,必须经由平台发送到交易服务器。 当服务器响应时,它会将这些响应返回给平台,平台再将它们转发给 EA。 EA 在分析和处理服务器的响应后,向平台发送一些信息,以便它可以向交易者示意正在发生的事情,或交易者所提交请求的结果。

许多人没有意识到这样的事情。 如果 EA 中发生任何故障,平台并不会有此问题。 问题出在 EA 上,但经验不足的交易者可能会错误地责怪平台没有做到他们想干的事。

如果您不曾作为程序员参与平台的开发和维护,那么您不应该试图影响它的工作方式。 确保代码响应平台的相应需求。

我们终于遇到一个问题:为什么要创建一个手动 EA,并运营一段时间,然后才将其自动化? 准确的原因是这样 — 我们创造了一种方式来实际测试代码,并准确创建我们需要的东西,不多也不少。

为了正确自动化系统,且在不影响控制点和订单系统哪怕一行代码的情况下,我们需要添加一些功能,以及略微的修改。 因此,在上一篇文章中创建的代码将放置在工作文件夹中,在上一篇文章中创建的代码将放置在稳定文件夹中,本文中提供的代码将移至开发文件夹之中。 通过这种方式,开发过程得到了扩展和演变,而我们有了速度更快的编码。 如果出现一些问题,我们总能够退回两个版本,其操作并无异常。

实现修改

我们实际要做的第一件事是修改计时系统。 此修改显示在下面的代码中:

virtual const bool CtrlTimeIsPassed(void) final
                        {
                                datetime dt;
                                MqlDateTime mdt;
                                
                                TimeToStruct(TimeLocal(), mdt);
                                TimeCurrent(mdt);
                                dt = (mdt.hour * 3600) + (mdt.min * 60);
                                return ((m_InfoCtrl[mdt.day_of_week].Init <= dt) && (m_InfoCtrl[mdt.day_of_week].End >= dt));
                        }

被删除的行已替换为高亮显示的行。 我们为什么要进行这些修改? 原因有二:首先,我们将两个调用替换为一个。 在删除的行中,我们首先有一个调用来获取时间,然后另一个调用来将其转换为结构。 第二个原因是 TimeLocal 实际上返回的是计算机时间,而不是市场观察元素中显示的时间,如图例 02 所示。

图例 02. 服务器在上次更新中提供的时间。

如果通过 NTP 服务器(保持最新时间的官方服务器)进行同步,则用计算机时间不是问题。 然而,大多数情况下人们不用此类服务器。 故此,时间控制系统可能会令 EA 更早进入或退出。 为了避免这种不便,有必要进行更改。

进行的这些变化不是为了从根本上修改代码,而是为交易者提供所期望的更大程度的稳定性。 因为如果 EA 比预期更早进入和退出,交易者可能会认为平台或代码中存在错误。 但原因实际上是由于计算机和交易服务器之间缺乏时间同步造成的。 交易服务器很可能使用 NTP 服务器来维持官方时间,而进行操作的计算机可能并未用到此服务器。

以下更改是在订单系统中实现的:

                ulong ToServer(void)
                        {
                                MqlTradeCheckResult     TradeCheck;
                                MqlTradeResult          TradeResult;
                                bool bTmp;
                                
                                ResetLastError();
                                ZeroMemory(TradeCheck);
                                ZeroMemory(TradeResult);
                                bTmp = OrderCheck(m_TradeRequest, TradeCheck);
                                if (_LastError == ERR_SUCCESS) bTmp = OrderSend(m_TradeRequest, TradeResult);
                                if (_LastError != ERR_SUCCESS) MessageBox(StringFormat("Error Number: %d", GetLastError()), "Order System", MB_OK);
                                if (_LastError != ERR_SUCCESS) PrintFormat("Order System - Error Number: %d", _LastError);
                                return (_LastError == ERR_SUCCESS ? TradeResult.order : 0);
                        }

这也是令系统可用于其预期目标的必要修改,以便 EA 可以实现自动化,且不会有太多麻烦。 事实上,删除的代码由高亮显示的代码替换,不是因为代码可以更快或更稳定,而是因为必须处理相应错误的发生。 当我们有一个自动化的 EA 时,某些类型的故障可以被忽略,正如我们在之前的文章中所讨论的那样。

问题是删除的行会始终启动一个消息框,通知有关错误,但在某些情况下,代码可以正确应对错误,故不需要该消息框。 在这种情况下,我们可以简单地在终端中打印一条消息,以便交易者可以采取相应的行动。

请记住,100% 自动 EA 不能等待交易者做出决定。 尽管这可能需要一会儿时间,但如果不报告发生了什么样的问题,您就不能做事。 一旦再次修改代码,则是为了提高敏捷性。 没有重大更改,故无需将系统置于更密集的测试阶段,其原本是排查可能由修改引起的故障。

但与上面所做的这些修改不同,现在我们添加的其它修改需要深入测试,因为其将影响系统的工作方式。

为自动化铺平道路

现在即将进行的修改将令我们能够有效地创建一个 100% 自动化系统。 如果没有这些修改,我们将在下一篇文章中被束缚双手,在那里我将展示如何将已测试过的 EA(我希望您正在执行所有必需的测试,从而了解一切实际工作原理)转变为自动化 EA。 为了实现必要的修改,我们需要删除,或最好说,修改某些内容,并添加其它内容。 我们从修改开始。 下面的代码描述了将要修改的内容:

//+------------------------------------------------------------------+
#include "C_ControlOfTime.mqh"
//+------------------------------------------------------------------+
#define def_MAX_LEVERAGE                10
#define def_ORDER_FINISH                false
//+------------------------------------------------------------------+
class C_Manager : public C_ControlOfTime

这两个定义将不复存在。 取而代之的是出现 2 个新变量,交易者无法修改这些变量,但可以由您(程序员)定义。 为什么要做出这样的改变? 原因在于当我们进行这些修改时,原本的定义将被变量替换,我们将在速度方面有所损失。 即使仅是几个机器周期,实际上也会有很小的性能损失,因为访问常量值比访问变量要快得多。 但作为回报,我们将在类重用中获得收益,您将在下一篇文章中更好地理解这一点。 相信我,易用性和便携性方面的差异弥补了性能的些微损失。 故此,上述两行已替换为以下内容:

class C_Manager : public C_ControlOfTime
{
        enum eErrUser {ERR_Unknown, ERR_Excommunicate};
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage,
                                MaxLeverage;
                        bool    IsDayTrade,
                                IsOrderFinish;                                          
                }m_InfosManager;

当编写代码时要小心:作为程序员,您不应在初始化这两个变量的位置之外更改这两个变量的值。 非常小心不要这样做。 初始化它们的位置恰好在类构造函数中,如以下代码所示:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger, const bool IsOrderFinish, const uint MaxLeverage)
                        :C_ControlOfTime(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.IsOrderFinish    = IsOrderFinish;
                                m_InfosManager.MaxLeverage      = MaxLeverage;
                                m_InfosManager.FinanceStop      = FinanceStop;
                                m_InfosManager.FinanceTake      = FinanceTake;
                                m_InfosManager.Leverage         = Leverage;
                                m_InfosManager.IsDayTrade       = IsDayTrade;

构造函数现在将收到两个初始化变量的新参数。 之后,我们将在定义被实例化的位置进行修改。 这些更改将在以下几处上进行:

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                int tmp = m_Position.Leverage;
                                
                                m_Position.Leverage = (int)(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 (def_ORDER_FINISH) if (m_TicketPending > 0) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Position.EnableBreakEven = (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                if (m_InfosManager.IsOrderFinish) if (m_TicketPending > 0) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_TicketPending == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return m_Position.Leverage - tmp;
                        }

inline void TriggerBreakeven(void)
                        {
                                double price;
                                
                                if (PositionSelectByTicket(m_Position.Ticket))
                                        if (PositionGetDouble(POSITION_PROFIT) >= m_Trigger)
                                        {
                                                price = m_Position.PriceOpen + (GetTerminalInfos().PointPerTick * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH)
                                                if (m_InfosManager.IsOrderFinish)
                                                {
                                                        if (m_TicketPending > 0) m_Position.EnableBreakEven = !ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                }else m_Position.EnableBreakEven = !ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                        }

                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                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 (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                tmp = C_Orders::ToMarket(type, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                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));
                        }

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                        {
// ... The rest of the code ...

                void UpdatePosition(const ulong ticket)
                        {
                                int ret;
                                double price;
                                
                                if ((ticket == 0) || (ticket != m_Position.Ticket) || (m_Position.Ticket == 0)) return;
                                if (PositionSelectByTicket(m_Position.Ticket))
                                {
                                        ret = SetInfoPositions();
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                price = m_Position.PriceOpen + (FinanceToPoints(m_InfosManager.FinanceStop, m_Position.Leverage) * (m_Position.IsBuy ? -1 : 1));
                                                if (m_TicketPending > 0) if (OrderSelect(m_TicketPending))
                                                {
                                                        price = OrderGetDouble(ORDER_PRICE_OPEN);
                                                        C_Orders::RemoveOrderPendent(m_TicketPending);
                                                }
                                                if (m_Position.Ticket > 0)      m_TicketPending = C_Orders::CreateOrder(m_Position.IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY, price, 0, 0, m_Position.Leverage, m_InfosManager.IsDayTrade);
                                        }
                                        m_StaticLeverage += (ret > 0 ? ret : 0);
                                }else
                                {
                                        ZeroMemory(m_Position);
                                        if ((def_ORDER_FINISH) && (m_TicketPending > 0))
                                        if ((m_InfosManager.IsOrderFinish) && (m_TicketPending > 0))
                                        {
                                                RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                        }
                                }
                                ResetLastError();
                        }

inline void TriggerTrailingStop(void)
                        {
                                double price, v1;
                                
                                if ((m_Position.Ticket == 0) || (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.SL == 0)) return;
                                if ((m_Position.Ticket == 0) || (m_InfosManager.IsOrderFinish ? m_TicketPending == 0 : m_Position.SL == 0)) return;
                                if (m_Position.EnableBreakEven) TriggerBreakeven(); else
                                {
                                        price = SymbolInfoDouble(_Symbol, (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : (m_Position.IsBuy ? SYMBOL_ASK : SYMBOL_BID)));
                                        v1 = m_Position.SL;
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                                if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                        if (v1 > 0) if (MathAbs(price - v1) >= (m_Position.Gap * 2)) 
                                        {
                                                price = v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH)
                                                if (m_InfosManager.IsOrderFinish)
                                                        ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                else    ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                                }
                        }

绝对所有移除的部分都已被高亮显示的片段替换。 通过这种方式,我们不仅提升了急需的类重用,而且还在可用性方面对其进行了改进。 尽管在本文中对此并不十分清晰,但在下一篇文章中,您将看到如何完成此操作。

除了已经进行的修改之外,我们还需要添加一些内容,以便自动化系统能最大程度地访问订单发送系统,从而提高类的重用率。 为此,我们首先添加以下函数:

inline void ClosePosition(void)
	{
		if (m_Position.Ticket > 0)
		{
			C_Orders::ClosePosition(m_Position.Ticket);
			ZeroMemory(m_Position.Ticket);
		}                               
	}

这个函数在某些操作模型中是必需的,所以我们需要把它包含在 C_Manager 类代码当中。 不过,在我们添加此函数后,编译器在尝试编译代码时会生成几个警告。 参见如下图例 03.

图例 03. 编译警告

与可以忽略的编译器警告(尽管强烈建议不要这样做)不同,图例 03 中的警告可能对程序造成潜在危害,并可能导致生成的代码无法正常工作。

理想情况下,当您注意到编译器已生成此类警告时,应尝试纠正生成这些警告的故障。 有时它很容易解决,有时它却有点复杂。 这也许是一种更改类型,其中部分数据在转换过程中丢失。 无论走那条路,查看编译器生成此类警告的原因始终很重要,即使代码已被编译完毕也应如此。

编译器警告的存在表明代码中某些内容运行不顺利,因为编译器难以理解您正在编程的内容。 如果编译器不能理解它,就不会生成 100% 可靠的代码。

一些编程平台允许您关闭编译器警告,但我个人不建议这样做。 如果您想拥有 100% 可靠的代码,最好启用所有警告。 随着时间的推移,您将意识到,保留标准平台设置是确保代码更可靠的最佳方式。

我们有两种选择来解决上述警告。 第一种是将当前引用 C_Orders 类中存在的 ClosePosition 函数调用替换为 C_Manager 类中添加的新函数。 这是最好的选择,因为我们将检查 C_Manager 中存在的调用。 第二个选项是告诉编译器调用将引用 C_Orders 类。

但是我将修改代码,以便调用新创建的函数。 故此,生成警告的问题点将得到解决,编译器能明白我们正在尝试做什么。

                ~C_Manager()
                        {
                                if (_LastError == (ERR_USER_ERROR_FIRST + ERR_Excommunicate))
                                {
                                        if (m_TicketPending > 0) RemoveOrderPendent(m_TicketPending);
                                        if (m_Position.Ticket > 0) ClosePosition(m_Position.Ticket);
                                        ClosePosition();
                                        Print("EA was kicked off the chart for making a serious mistake.");
                                }
                        }

最简单的方法是在析构函数中解决此问题,但有一个棘手的部分,如下所示:

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                if (PositionSelectByTicket(m_Position.Ticket)) ClosePosition(m_Position.Ticket);
                                                ClosePosition();
                                                if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); else RemoveOrderPendent(m_TicketPending);
                                                ZeroMemory(m_Position.Ticket);
                                                m_TicketPending = 0;
                                                ResetLastError();
                                        }else   SetUserError(ERR_Unknown);
                                }else m_Position.Ticket = (m_Position.Ticket == 0 ? m_TicketPending : m_Position.Ticket);
                                m_TicketPending = 0;
                                if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket);
                                CheckToleranceLevel();
                        }

我删除了一些代码行,并加入了平仓的调用。但是,如果挂单因任何原因变为持仓,我们必须如同原始代码中那样删除该笔持仓。 但该笔持仓尚未被 C_Manager 类所捕获。 在这种情况下,我们告诉编译器调用应引用 C_Orders 类,如高亮显示的代码所示。

以下是我们需要进行的另一处修改:

inline void EraseTicketPending(const ulong ticket)
                        {
                                if ((m_TicketPending == ticket) && (m_TicketPending > 0))
                                {
                                        if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); 
                                        else RemoveOrderPendent(m_TicketPending);
                                        m_TicketPending = 0;
                                }
                                ResetLastError();
                                m_TicketPending = (ticket == m_TicketPending ? 0 : m_TicketPending);
                        }

划掉的代码,即原始代码,已由稍微复杂的代码所取代;但它赋予我们更强的能力删除挂单,或者,若它已成为持仓,则删除它。 以前,我们只是响应 MetaTrader 5 平台通知我们的事件,导致为挂单指示的单号值设置为零,如此就可发送新的挂单。 但现在我们要做的远不止这些,因为我们需要在 100% 自动化系统中使用此类功能。

通过实现这些小的更改,我们获得了系统范围的奖励,这将增加代码重用和测试。

最后阶段之前的最终改进

在最后阶段之前,我们可以实现更多改进,从而提高代码重用率。 第一处显示在以下代码中:

                void UpdatePosition(const ulong ticket)
                        {
                                int ret;
                                double price;
                                
                                if ((ticket == 0) || (ticket != m_Position.Ticket) || (m_Position.Ticket == 0)) return;
                                if (PositionSelectByTicket(m_Position.Ticket))
                                {
                                        ret = SetInfoPositions();
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                price = m_Position.PriceOpen + (FinanceToPoints(m_InfosManager.FinanceStop, m_Position.Leverage) * (m_Position.IsBuy ? -1 : 1));
                                                if (m_TicketPending > 0) if (OrderSelect(m_TicketPending))
                                                {
                                                        price = OrderGetDouble(ORDER_PRICE_OPEN);
                                                        C_Orders::RemoveOrderPendent(m_TicketPending);
                                                        EraseTicketPending(m_TicketPending);
                                                }
                                                if (m_Position.Ticket > 0)      m_TicketPending = C_Orders::CreateOrder(m_Position.IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY, price, 0, 0, m_Position.Leverage, m_InfosManager.IsDayTrade);
                                        }
                                        m_StaticLeverage += (ret > 0 ? ret : 0);
                                }else
                                {
                                        ZeroMemory(m_Position);
                                        if ((m_InfosManager.IsOrderFinish) && (m_TicketPending > 0))
                                        {
                                                RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                        }
                                        if (m_InfosManager.IsOrderFinish) EraseTicketPending(m_TicketPending);
                                }
                                ResetLastError();
                        }

可以从这些改进中受益的另一处是以下代码:

                ~C_Manager()
                        {
                                if (_LastError == (ERR_USER_ERROR_FIRST + ERR_Excommunicate))
                                {
                                        if (m_TicketPending > 0) RemoveOrderPendent(m_TicketPending);
                                        EraseTicketPending(m_TicketPending);
                                        ClosePosition();
                                        Print("EA was kicked off the chart for making a serious mistake.");
                                }
                        }

最后一处,这也受益于代码重用:

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                ClosePosition();
                                                EraseTicketPending(m_TicketPending);
                                                if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); else RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                                ResetLastError();
                                        }else   SetUserError(ERR_Unknown);
                                }else m_Position.Ticket = (m_Position.Ticket == 0 ? m_TicketPending : m_Position.Ticket);
                                m_TicketPending = 0;
                                if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket);
                                CheckToleranceLevel();
                        }

为了完成改进和修改的问题,有一个小细节。 例如,EA 的最大确定交易量可能是最小交易量的 10 倍。 但如果交易者在配置交易量时,在执行第三次操作时指示 3 倍杠杆值,则 EA 将非常接近允许的最大交易量。 那么,如果您发送第四个请求,它就突破了允许的最大交易量。

这似乎是一个小缺陷,许多人可能认为它没有什么危险。 从某种意义上说,我同意这个想法,因为第五笔订单永远不会被接受。 但编程时定义的交易量是 10 倍。 故此,当第四笔订单被接受时,EA 已执行的交易量 12 倍,超过最大配置交易量的 2 倍。 这是因为交易者配置了 3 倍的杠杆,但如果他指示了 9 倍的杠杆呢? 在这种情况下,交易者希望 EA 只执行一笔交易。

因此,想象一下用户看到 EA 开立第二笔交易时的惊讶,它超过了最大交易量的 8 倍。 看到这种情况的人甚至可能心脏病发作。

如您所见,尽管这是一个潜在风险不大的漏洞,但它仍然不应该忽视,特别是对于自动化 EA。 这对于手动 EA 来说没有问题,因为交易者会加以检查,从而确保不会以相同杠杆等级创建另一次入场。 无论如何,我们应该为 EA 提供某种模块来应对这种情况。 这应该在下一篇文章之前实现。 这样我以后就不必担心这类事情。

为了修复这个问题,我们将添加几行代码,如下所示:

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {                               
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        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 (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        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 代码的其余部分不需要知道它的存在。 这是新函数:

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;
	}

我们来理清这里发生了什么。 现在,按发送订单顺序,所有代码行均汇集在上面的代码当中。 不过,发送挂单和市价单之间几乎没有区别,这种差异是由这个参与点判定的。 故此,我们应该检查它是挂单还是市价单。 为了区分这两种类型的订单,我们使用一个参数,允许您将所有代码合并为一。 如果无法发送订单,则返回 false。 如果可以发送订单,则返回 true。

新的函数代码如下所示:

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!IsPossible(true)) return;
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        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;
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        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));
                        }
//+------------------------------------------------------------------+

调用现在取决于每个特定情况,而所有划掉的行都已从代码中删除,因为它们不再有存在的意义。 这就是我们如何通过剔除冗余组件来开发安全、可靠、稳定和健壮的程序:通过分析可以更改和改进的内容,以及尽可能多地测试和重用代码。

当您看到完成的代码时,您往往会觉得它就是以这种方式诞生的。 然而,需要若干个步骤才能达到最终形式。 这些步骤包括持续测试和实验,以最大程度地减少故障和潜在缺陷。 这个过程是曲折渐进的,需要奉献精神和毅力才能令代码达到所需的品质。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值