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

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

MetaTrader 5交易 | 11 五月 2023, 12:48
1 291 3
Daniel Jose
Daniel Jose

概述

在上一篇文章构建自动运行的 EA(第 9 部分):自动化 (I)中,我们研究了如何构建盈亏平衡和尾随停止系统,它们采用两种不同模式。 其中一个使用 OCO 持仓止损线,而另一个则采用挂单作为停止价位。 正如我之前解释的,这些方法中的每一种都有其优点和缺点。

尽管 EA 采用的是相当简单的自动化系统,并提供详细的计算说明,但它还并不是真正的自动化。 尽管它已具有初步自动化,但它的操作仍然是手动的,或者更准确地说,以“半手动”的方式操作。 这是因为负责移动价位或停止订单的部分,仍由 EA 本身根据用户指定的设置执行。

在本文中,我们将研究如何为 EA 再添加更多的自动化级别,从而它可以全时驻留在使用它的图表上。 但请勿担心,这并不意味着它会每天 24 小时连续发送订单或开仓。 我们将看到如何控制系统的交易量,从而防止 EA 过度交易,并且不允许它的交易量超过最大限制。

我将在这里展示的自动化类型,是许多平台为交易者提供的一种非全时交易方式。 您定义好允许发送订单或开仓的时间,在此预定义的时间内,理论上您可以使用该平台的全部基本功能。

我所说的是理论上的,当使用该平台将,若因您禁用系统,也会令整套方法空耗精力。 每个人都知道什么时候该休息,但与人类交易者不同,EA 并不按这种方式行事。

我必须指出,您必须仔细检查在 EA 中添加或删除的所有内容,以便它可以遵照预先定义的范畴内工作。 不过,我们永远不应该让 EA 在无人监督的情况下运行。 始终牢记:永远不要允许 EA 在无人监督的情况下运行

如何实现进度控制?

有多种方法可以做到这一点。 实现这种类型的控制,更多地取决于程序员希望如何将其置于 EA 代码之中,而非需要完成哪种编程。 由于这些文章的意图是展示如何以最简单的方式构建自动化 EA,我们应创建可轻松删改的代码。 与此同时,如果我们想控制自己的交易方法,我们可在手动 EA 中使用它,但这应该由用户决定。 


规划

开发系统很有趣,因为每个交易者都会想到不同的方式和方法来推广某个概念,甚至系统本身应如何工作。 即使成果非常相似或彼此接近,它们的实现途径也可能不同。

然而,在 MQL5 中,我怀念的是 C++ 中存在的面向对象编程形式,即所谓的多重继承。 不过,如果不正确地应用这种方法,可能会出现严重的问题。 故此,在使用多重继承时,编程时应非常小心。 但即使没有这个 C++ 特性,我们也可以生成一些类型的代码,在继承的系统中保留所需的内容。

若要理解使用多重继承与否之间的区别,请参看下图。 请注意,C_ControlOfTime 是我们将用来控制允许 EA 运行的时间段的类名称

图例 01

图例 01. 多重继承建模

图例 02

图例 02. 非多重继承建模

注意,图例 01 和图例 02 的区别在于,在第一个图例中,C_Manager 类通过继承,派生了 C_Orders 和 C_ControlOfTime 类中实现的方法,导致 C_Manager 类快速增长。 由于我们无法在 MQL5 中做到这一点,我们就用图例 02 所示的另一种方式。 类 C_ControlOfTime 继承自 C_Orders。

但我们为什么颠倒继承顺序呢? 这是因为我不希望 EA 直接访问 C_Orders 类。 不过,我们需要访问 C_ControlOfTime 类的实现。 关于编程的最好的事情是,我们通常可以采用不同的方式,但最终会得到与其他程序员所创建的完全相同的功能。

我在这里展示的只是实现相同结果的众多可能方法之一。 真正重要的是结果。 如何实现这一点并不重要,只要保持代码的完整性即可。 您可以创建自己的技术,以及实现某些事情的方式,因为编程允许我们这样做。


实现前需要审查的更多细节

在定义了图例 02 中所示的类建模的想法之后,我们进入了第二个规划阶段,即我们将创建时隙控制类。

现在我们需要定义确定工作时间范围如何,即交易者如何能轻松地设置调度表。 一种方式是包含 EA 所需的时间范围数据的文件。

不过,对于这种事情设置,使用文件与否颇有争议。 若用文件,我们最终可以让交易者更自由地在同一天内定义多个时间段。 这在某些情况下可能是合理的,但最终可能会令简单的任务变得更加困难。 EA 若在某些时间段内表现不佳,就会给我们带来很大的压力。

另一方面,在文件中定义调度表的方式最终可能会令大多数交易者的简单任务复杂化。 这是由于在某些情况下,EA 只会在一个时间段内工作。

在绝大多数情况下,这将是一个比看起来更普遍的事实。 既如此,我们能做得更好一点,我们可以保持在中间地带。 MetaTrader 5 平台允许我们在文件中保存和加载所需的设置。 故此,您只需按给定时间段创建配置。 例如,您可以在上午使用一个配置,在下午使用另一个配置。 为了按时间控制系统来阻挡 EA,例如,当交易者想要休息一下时,交易者可以直接将设置文件上传到 MetaTrader 5 平台,配置将由平台自身维护。 在我看来,这有很大的帮助,因为它为我们省去了为此目的创建其它配置文件的麻烦。

图例 03

图例 03. EA 设置

图例 03 示意 EA 设置系统。 EA 配置完毕后,您可以按下 <保存> 按钮保存设置。 当您需要上传保存的配置时,请按下 <打开> 按钮。 由此,我们得到了一个需要更少代码的系统,同时令整体代码非常可靠。 一部分工作将由 MetaTrader 5 平台自身完成,这令我们能够节省测试时间,确保一切正常。

最后一个需要判定的细节涉及时间段之外的订单或持仓。 我们能为它们做什么? EA 将无法在允许的时间段之外开仓或发送订单。 然而,在工作时段范围之外,EA 也应能够管理订单,或关闭服务器上已有的持仓。 如果需要,您可以禁用或更改我所用的策略。 我为您保留选择权,根据自己的交易政策进行设置。


C_ControlOfTime 类的诞生

第一件事要做的是在 C_ControlOfTime.mqh 头文件中创建以下代码:

#include "C_Orders.mqh"
//+------------------------------------------------------------------+
class C_ControlOfTime : protected C_Orders
{
        private :
                struct st_00
                {
                        datetime Init,
                                 End;
                }m_InfoCtrl[SATURDAY + 1];
//+------------------------------------------------------------------+
        public  :

//... Class functions ...

};

我们正在添加 C_Orders.mqh 头文件,从而能够访问 C_Orders 类。 如此。C_ControlOfTime 将继承来自 C_Orders 类中受保护的方法。我已经在本系列的另一篇文章“构建自动运行的 EA(第 05 部分):手动触发器(II)”中解释了使用这种继承类型的后果。

现在,在控件类代码的私密部分,我们加入一个结构,该结构将作为包含 7 个元素的数组。 但是为什么不定义 7 来替代使用那个疯狂的语句呢?这是因为 SATURDAY 的值在 MQL5 语言内部定义为 ENUM_DAY_OF_WEEK 枚举值。 因此,我们为什么要用周内的天数来访问数组就变得更加清晰。

这是语言级别的丰富,因为对于那些阅读代码的人来说,单词比数值更具表现力。 此结构只有两个元素:一个指示 EA 操作的时刻,另一个指示 EA 不再工作的时刻,这两个元素的类型都是 datetime

一旦数组定义完毕后,我们可以移步到处理类中的第一段代码,即如下所示的类构造函数:

                C_ControlOfTime(const ulong magic)
                        :C_Orders(magic)
                        {
                                ResetLastError();
                                for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++) ZeroMemory(m_InfoCtrl[c0]);
                        }

对于许多人来说,下面的代码可能看起来很奇怪,但它只是一种比我们习惯所用的更高级的编程形式。 我说它是高级编程的事实与我的编程经验无关。 正如我所说:

“使用自然语言的代码比使用数值的代码更容易理解。

此构造函数中的循环是不言自明的。 这些东西实际上定义了代码是高级还是低级。

我相信您很容易就能理解这个构造函数。 之前我曾解释过构造函数中的代码是如何工作的。 如果您不确定,请阅读本系列的前几篇文章。 这里唯一的区别实际上是我们指示变量的循环,该变量将从值 SUNDAY 开始,以 SATURDAY 结束。 这里没有其它复杂的东西。

循环正常工作,只因为在枚举中,星期日被定义为一周的第一天,星期六是最后一天。 不过,如果将星期一设置为一周的第一天,则在 MetaTrader 5 平台中执行代码时循环将失败,并会抛出错误。 因此,使用高级代码时必须小心,因为如果配置不正确,代码可能会生成若干的运行时错误。

一旦此操作完成后,我们可以移步至类中的下一个函数:

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
                        {
                                string szRes[];
                                bool bLocal;
                                
                                if (_LastError != ERR_SUCCESS) return;
                                if ((index > SATURDAY) || (index < SUNDAY)) return;
                                if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
                                {
                                        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
                                        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
                                        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
                                        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
                                }
                                if ((_LastError != ERR_SUCCESS) || (!bLocal))
                                {
                                        Print("Error in the declaration of the time of day: ", EnumToString(index));
                                        ExpertRemove();
                                }
                        }

这部分需要更详细的分析,因为对于那些经验不足的人来说,这并不那么简单。 我知道这一点,因为我曾处于您的立场上,我亲爱的读者。 为了更详细地解释上面的代码,我们将其分解为更小的部分。

if (_LastError != ERR_SUCCESS) return;
if ((index > SATURDAY) || (index < SUNDAY)) return;

只要有可能,您都应该检查系统是否有错误,因为这段代码非常敏感,并且由于我们正在处理的东西的性质,而更容易出错。 我们首先检查在调用此特定函数之前是否有任何故障。 如果有任何故障,该函数将立即关闭。

还需要进行第二次检查以避免随机故障。 如果出于某种原因,我们根据指示周内某天的编号进行调用,并且处理器仅将其视为数字,因为它不在星期六星期日之间的范围内,我们不会继续执行函数中后面的代码行。

一旦迈出了第一步,它就被接受,并且代码通过了第一次测试,我们将对调用者传递的内容进行翻译:

if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
{
        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
}

在此,我们有更有趣的代码,特别是对于那些开始学习编程的人。 在此,我们使用 StringSplit 函数将我们从调用方收到的信息“拆分”为两部分。 指示信息将在哪里被断开的字符是减号(-)。 我们希望并期望看到的是两条信息。 如果我们只得到一个不同的数字,这将被视为错误,如以下示例所示:

12:34 - - 18:34 <-- This is an error
12:32  -  18:34 <-- Information is correct
12:32     18:34 <-- This is an error
       -  18:34 <-- This is an error
12:34  -        <-- This is an error

信息的内部内容对于 StringSplit 函数无关紧要。 它只根据指定的分隔符“拆分”数据。 此函数在某些情况下非常有用,因此研究它的正确用法很重要。 因为分隔字符串中的信息有很大帮助。

得到这两条信息后,我们调用 StringToTime 函数将它们转换为日期格式码。

还有一个重要的细节:我们所提供的信息仅包含小时和分钟。 我们对特定日期不感兴趣。 但这并不妨碍我们指定一个特定的日期。 不过,根据实现,日期将被忽略。 您只需要输入名称。 StringToTime 函数将自动添加当前日期。 这实际上不是问题,但有一件事我们稍后会考虑。

为了剔除由 StringToTime 函数添加的当前日期值,我们使用因式分解,其结果只保留调用方指定的时间值。 若您并不想真的删除 StringToTime 函数添加的日期(该日期始终是当前日期),只需删除因式分解语句即可。

在转换值后,我们进行了第一次测试。 为了避免以后出现问题,这一点非常重要。 我们将检查开始时间是否小于或等于指定的结束时间。 如果此检查成功,我们以后就不必担心此问题。 如果检查失败,我们将通知用户这些数值不符合要求。

我们还有另一项检查,由于我们没有指定日期,平台将生成运行时错误。 这种类型的错误非常烦人,但我们必须以正确的方式应对它。 当检测到此类错误时,我们将简单地删除所发生的指示。 由于我们事先知道没有通知日期,因此才会生成此错误。

重要提示:每当在测试 EA 时,您注意到触发了运行时错误,这不会违反 EA 的完整性,也不会令其不稳定或不安全,请在生成错误的代码之后添加这种类型的测试,以最大程度地减少将来产生的错误。 一些运行时错误可以忽略,因为它们不是那么严重,也不会以任何方式影响 EA 操作。 然而,某些错误必须以不同的方式处理,因为它们会导致 EA 在图表上停止运作。 这称为加强健壮性,因为我们知道可能会发生故障,但我们也知道它不会损害系统。

实现数值转换后,我们有以下代码:

if ((_LastError != ERR_SUCCESS) || (!bLocal))
{
        Print("Error in the declaration of the time of day: ", EnumToString(index));
        ExpertRemove();
}

可以注意到这里的一些重要事情:如果系统在数值转换过程中产生任何严重错误,则此常量变量的值将与 ERR_SUCCESS 不同。 这表明我们不能信任用户所用或输入的数据。 如果此处的变量为 “false” 值,则情况也是如此,它表示在某处导致其失败。 无论如何,我们会在终端中打印消息来通知交易者。 此外,在 MetaTrader 5 平台中会生成关闭 EA 的请求。

我希望对函数如何工作的解释非常清楚,因为它已经够详细了。 不过,这并非全部,我们还有另一个函数要讨论

virtual const bool CtrlTimeIsPassed(void) final
                        {
                                datetime dt;
                                MqlDateTime mdt;
                                
                                TimeToStruct(TimeLocal(), 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));
                        }

重要的是,在您尝试了所有可能的变体之前,不要认为接受某些东西是理所当然的,这些变体可以产生相同的结果,但却是以不同的、更简单的方式。 这也许看起来令人沮丧,但它是解决问题和寻找更好解决方案的宝贵方法。

在进入正题之前,我们来了解一下上面的函数。 它捕获本地时间和日期,并将这些值转换为结构,以便拆分数据。 然后我们设置一个仅包含小时和分钟的值 — 这些是我们在上一个函数中所用的值。 这就是我前面提到的问题。 如果您没有在转换函数中删除日期值,则需要在此处添加日期值。 一旦我们得到这些值后,我们将检查它们是否在该范围之内。 如果是的,我们将返回 true;如果它们超出当天定义的范围,我们将返回 false。

现在又遇到了一个问题。 为什么我们必须做所有这些捕获、重构的工作,以便检查当地时间是否在日值范围内? 使用代码不是更简单吗,我们捕获本地时间并根据定义的范围进行检查? 是的,这样确实会简单得多。 但是...总有一个但是,我们有一个小问题:周内的星期几

如果您仅根据当天设置 EA 操作范围,那很好。 只要知道当地时间就足够了。 但我们定义一周七天的值。 知道一周中哪一天的最简单方式是准确使用上述函数中完成的所有工作。

所以实际上,这完全取决于您如何实现系统。 如果您简单地实现它,则需要创建和定义的函数和过程就会简单得多。 如果您正在创建一个应用更广泛的系统,则函数和过程就要复杂得多。

这就是为什么在实际开始编码之前,思考和分析很重要的原因。 否则,您最终可能会陷入死胡同,新构建的代码会令旧代码无法满足要求。 那么,您不得不改变旧的代码,接下来您就明白,您已陷入了这样的混乱之中,需要丢弃一切,从头开始。

在改变主题之前,我想强调上述函数中的一个细节。 如果您希望 EA 每天 24 小时保持在线,则平台在此期间需一直在运行,您可能会担心 EA 在每天交接之际不知道何时再次开始操作。 这实际上不会发生,因为在每次调用函数时,它将重新评估整个状况,并以周内的当前日期来验证 EA 是否能够执行某些操作。

例如,假设您告诉 EA 它可以在周一的 04:15 到 22:50 之间交易,周二在 3:15 到 20:45 之间交易。 您可以在星期一就打开它,让它一直运行到星期二。 一旦一天转折,从星期一切换到星期二,它将自动开始检查星期二允许运行的时间段。 有因于此,我决定采用周模式,替代基于当前日期的定义。

也许我错过了这个类的一些细节,但我不想令函数如何操作的解释过于复杂。 我们来仔细查看应对函数继承时的一个非常重要的细节。 如果您仔细观察,您会发现 SetInfoCtrlCtrlTimeIsPass 函数都有非常奇怪的声明。 它们为什么有这样的声明? 它们的目的是什么? 这些声明高亮如下:

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
//+------------------------------------------------------------------+
virtual const bool CtrlTimeIsPassed(void) final
//+------------------------------------------------------------------+

在此,每个词都有一个原因。 这里没有放置任何东西来装饰代码,尽管有些人习惯于这样做,但那是另一回事。

在这些声明中,真正重要的是保留的 "final" 一词。 从宣言中是否存在 "virtual" 一词来看,这是一个大问题。 事实是,当您创建一个类时,您可以通过多种方式对其进行处理:覆盖方法、在子类中修改父类函数的执行方式、基于更原始的工作创建新窗体、等等。 如上所示,将 “final” 关键字添加到类中的函数声明时,您告诉编译器该子类不能以任何方式修改它。 它甚至不能覆盖在父类中编写的同名函数,这很常见。

我将再次重申,因为它很重要:通过在声明中使用 “final” 关键字,您告诉编译器该子类不能以任何方式修改它,甚至不能重盖在父类中编写的同名继承函数,该函数在声明中接收 “final” 关键字。

因此,我们保证,继承该类及其方法和变量的类如果于此尝试任何修改这些方法,则将被视为错误,并且程序不会被编译。 'virtual' 一词正是为了促进修改的可能性,但 final 这个关键字阻碍阻止了这种变化。 谁真正制定规则谁有最终发言权。 如此,每当要确保函数不会在子类中进行不必要的修改时,请添加声明来锁定这种修改的可能性,如上所示。

如果您用到许多类,并且具有深层次的继承,这将为您省去很多麻烦。


将 C_ControlOfTime 与 C_Manager 链接,并在 EA 当中使用

现在,最后,我们可以将 C_ControlOfTime 类与 C_Manager 类链接起来,EA 将具有一种新的工作参数类型。 为此,我们将以下更改添加到 C_Manager 类代码中:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Orders.mqh"
#include "C_ControlOfTime.mqh"
//+------------------------------------------------------------------+
#define def_MAX_LEVERAGE                10
#define def_ORDER_FINISH                false
//+------------------------------------------------------------------+
class C_Manager : private C_Orders
class C_Manager : public C_ControlOfTime

从原始代码中删除了划掉的行,并在其位置上出现了新行。 但请注意,C_Manager 类将公开继承 C_ControlOfTime 类。 我们只需将 C_ControlOfTime 类中的所有内容添加到 C_Manager 类之中。 这样就扩展了 C_Manager 类的能力,且无需增加其代码。 如果我们不再需要继承 C_ControlOfTime 类添加的能力,我们需要做的就是从 C_Manager 类中删除此继承,以及任何可能的引用点。 就这么简单。

因此,我们不会更改 C_Manager 类的可靠性级别,因为它这样就可继续最大的安全性、稳定性和健壮性,就好像什么都没发生过一样。 如果 C_ControlOfTime 类开始导致 C_Manager 类不稳定,我们可以简单地删除 C_ControlOfTime 类,C_Manager 类就会再次回复稳定。

我认为这样就能解释为什么我喜欢以类的形式创建所有内容,而不是零散的函数。 事情正在迅速增加和改善,我们始终拥有该语言可以为我们提供的最高级别的稳定性和可靠性。

现在,由于需要以某种方式引用构造函数,我们看一下下面的新类构造函数:

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

// ... The rest of the constructor code....

                        }
//+------------------------------------------------------------------+

在此,我们和声明一样。 划掉的线已被删除。 取而代之的是一行新代码,它将数据传递给 C_ControlOfTime 类构造函数。 此构造函数将引用 C_Orders 类的构造函数,以便它接收发送订单所需的魔幻数字。

现在,为了终结本主题,以下是实际使用时间控制的要点:

//+------------------------------------------------------------------+
                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;
                                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);
                        }
//+------------------------------------------------------------------+  
                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;
                                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);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

EA 或 C_Manager 类中没有其它函数会用到这些时间范围。 但如果您愿意,可以加入这些控制,即通过将高亮显示的代码行精准添加到需要控制的函数,例如尾随停止触发器。 如果您不想在其中的某些函数中使用该控制,只需删除添加的代码行即可。 您喜欢代码的规划方式吗? 但若没有 EA 代码中的另一个要点,所有这些都无法真正发挥作用。

EA 代码中唯一真正必要的更改如下所示:

#include <Generic Auto Trader\C_Manager.mqh>
#include <Generic Auto Trader\C_Mouse.mqh>
//+------------------------------------------------------------------+
C_Manager *manager;
C_Mouse  *mouse;
//+------------------------------------------------------------------+
input int       user01   = 1;                   //Leverage Factor
input double    user02   = 100;                 //Take Profit ( FINANCE )
input double    user03   = 75;                  //Stop Loss ( FINANCE )
input bool      user04   = true;                //Day Trade ?
input color     user05  = clrBlack;             //Price Line Color
input color     user06  = clrForestGreen;       //Take Line Color 
input color     user07  = clrFireBrick;         //Stop Line Color
input double    user08  = 35;                   //BreakEven ( FINANCE )
//+------------------------------------------------------------------+
input string    user90  = "00:00 - 00:00";      //Sunday
input string    user91  = "09:05 - 17:35";      //Monday
input string    user92  = "10:05 - 16:50";      //Tuesday
input string    user93  = "09:45 - 13:38";      //Wednesday
input string    user94  = "11:07 - 15:00";      //Thursday
input string    user95  = "12:55 - 16:25";      //Friday
input string    user96  = "00:00 - 00:00";      //Saturday
//+------------------------------------------------------------------+
#define def_MAGIC_NUMBER 987654321
//+------------------------------------------------------------------+
int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)
        {
                switch (c0)
                {
                        case SUNDAY     : szInfo = user90; break;
                        case MONDAY     : szInfo = user91; break;
                        case TUESDAY    : szInfo = user92; break;
                        case WEDNESDAY  : szInfo = user93; break;
                        case THURSDAY   : szInfo = user94; break;
                        case FRIDAY     : szInfo = user95; break;
                        case SATURDAY   : szInfo = user96; break;
                }
                (*manager).SetInfoCtrl(c0, szInfo);
        }
        (*manager).CheckToleranceLevel();
        EventSetMillisecondTimer(100);

        return INIT_SUCCEEDED;
}

只需要添加用户与代码交互的点。 这个循环将捕获数据,并将其抛到系统中,以便 C_Manager 类可以取用它,从而控制实际的 EA 操作时间。 在代码的这一部分中,其行为易于理解,不需要任何其它解释。


结束语

在本文中,我向您展示了如何添加控制,从而允许 EA 在特定时间范围内运行。 虽然系统非常简单,但它需要最后一个解释。

如果在交互系统中输入的时间大于 24 小时,则会更正到最接近 24 小时的时间。 也就是说,如果您希望 EA 运行到 22:59(如果您在操作外汇),您应该小心且准确地指定此数值。 如果您键入 25:59,系统将其改为 23:59。 尽管这般键入错误并不常见,但仍可能会发生。

我没有添加任何额外的检查来分析这种情况,因为它很少发生,但我想对此加以注释,并示意对这种情况的可能检查。 这可以在下面看到。 附件里的代码已经进行了这些更改。

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
                        {
                                string szRes[], sz1[];
                                bool bLocal;
                                
                                if (_LastError != ERR_SUCCESS) return;
                                if ((index > SATURDAY) || (index < SUNDAY)) return;
                                if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
                                {
                                        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
                                        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
                                        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
                                        for (char c0 = 0; (c0 <= 1) && (bLocal); c0++)
                                                if (bLocal = (StringSplit(szRes[0], ':', sz1) == 2))
                                                        bLocal = (StringToInteger(sz1[0]) <= 23) && (StringToInteger(sz1[1]) <= 59);
                                        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
                                }
                                if ((_LastError != ERR_SUCCESS) || (!bLocal))
                                {
                                        Print("Error in the declaration of the time of day: ", EnumToString(index));
                                        ExpertRemove();
                                }
                        }

有必要添加一个新变量,以及高亮显示的代码来拆分时间数据,为的是检查输入的时间是否低于 24 小时内的最大可能值。


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

附加的文件 |
最近评论 | 前往讨论 (3)
crt6789
crt6789 | 13 5月 2023 在 13:01
这是使用的电脑本地时间吗?是不是还得转换成交易图表上显示的服务器时间才能对应得上交易走势图?
Daniel Jose
Daniel Jose | 14 5月 2023 在 19:25
crt6789 # :
Esta é a hora local do computador usada? Ele precisa ser convertido no horário do servidor exibido no gráfico de negociação para corresponder ao gráfico de tendências de negociação?

要指示的时间是当地时间...但您可以将其更改为服务器时间,只需更改代码中的一行...

crt6789
crt6789 | 20 5月 2023 在 06:25
Daniel Jose #:

要指示的时间是当地时间...但您可以将其更改为服务器时间,只需更改代码中的一行...

请问:如果要把每天的交易时间分段应该怎么改写呢?比如分成三段:早盘,午盘,夜盘。或者说,亚盘,欧盘,美盘。早盘   03:00——06:30 , 午盘  08:00——11:30  ,夜盘  15:30——23:00 
您应该知道的 MQL5 向导技术(第 05 部分):马尔可夫(Markov)链 您应该知道的 MQL5 向导技术(第 05 部分):马尔可夫(Markov)链
马尔可夫(Markov)链是一个强大的数学工具,能够针对包括金融在内的各个领域的时间序列数据进行建模和预测。 在金融时间序列建模和预测中,马尔可夫链通常用于模拟金融资产随时间的演变,例如股票价格或汇率。 马尔可夫链模型的主要优点之一是其简单性和易用性。
构建自动运行的 EA(第 09 部分):自动化(I) 构建自动运行的 EA(第 09 部分):自动化(I)
尽管创建自动 EA 并非一项非常困难的任务,但在缺乏必要知识的情况下可能会犯许多错误。 在本文中,我们将研究如何构建初级自动化,其中包括创建一个触发器来激活盈亏平衡和尾随停止价位。
学习如何基于鳄嘴(Gator)振荡器设计交易系统 学习如何基于鳄嘴(Gator)振荡器设计交易系统
这是我们关于学习如何基于流行技术指标设计交易系统系列的一篇新文章,将介绍鳄嘴(Gator)振荡器技术指标,以及如何通过简单的策略创建交易系统。
DoEasy. 控件(第三十一部分):滚动条控件内内容的滚动 DoEasy. 控件(第三十一部分):滚动条控件内内容的滚动
在本文中,我将实现通过按钮滚动水平滚动条容器内容的功能。