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

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

MetaTrader 5示例 | 10 七月 2023, 08:55
2 002 1
Daniel Jose
Daniel Jose

概述

在上一篇文章构建自动运行的 EA(第 14 部分):自动化(VI)中,我们研究了 C_Automaton 类,并讨论了其基础知识。 但由于使用 C_Automaton 类来自动化系统并不像看起来那么容易,在本文中,我们将看看如何执行此操作的示例。

在此,我们将看到 3 种不同的模型。 本文将重点介绍如何调整 C_Automaton 类,从而实现每个模型。 有关该系统的更多详细信息,您可阅读之前的文章,因为在本文中,我们只讨论适配,即依赖部分。

这个话题实际上很长,如此就让我们进入实施示例。 


自动化示例 1:9-周期指数移动平均线

该示例所用的 EA 代码,在文后附带的 EA_v1.mq5 文件中提供。 我们从类构造函数开始:

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                            bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                            const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, 0, 0, Leverage, IsDayTrade, 0, false, 10),
                         m_TF(iPeriod),
                         m_Handle(INVALID_HANDLE)
                        {
                                m_Infos.Shift      = iShift;
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                ArraySetAsSeries(m_Buff, true);
                                m_nBars  = iBars(NULL, m_TF);
                                m_Handle = iMA(NULL, m_TF, 9, 0, MODE_EMA, PRICE_CLOSE);
                        }

我们研究一下与默认系统相比的差异:

  • 系统将没有止损或止盈,这意味着系统没有盈利或亏损限制,因为 EA 自身会根据价格变动生成这些限制。
  • 系统不会创建尾随停止、盈亏平衡或挂单
  • 我们将使用一个句柄,它将是基于收盘价的 9-周期指数移动平均线指标

其余的已在早前解释过了,所以我们可以迈入机制计算过程。 相关方式在上一篇文章中讨论过,故请阅读它来获取更多详细信息。 但是,一旦定义了计算方法,就会产生以下代码:

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                        
                                if (((iRet = iBars(NULL, m_TF)) > m_nBars) && (m_Handle != INVALID_HANDLE))
                                {
                                        if (CopyBuffer(m_Handle, 0, 0, m_Infos.Shift + 1, m_Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                        m_nBars = iRet;
                                        if (m_Buff[0] > m_Buff[m_Infos.Shift]) return TRIGGER_BUY;
                                        if (m_Buff[0] < m_Buff[m_Infos.Shift]) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

听起来很复杂? 其实不然。 只要您遵照上一篇文章中讨论的流程图创建了规则,那就十分简单。 首先,我们尝试读取指标内容;如果做不到,我们返回一个空触发器,且信号也许会丢失。

在某些情况下,特别是当丢失的信号是离场信号时,系统可能会开始产生损失。 但是,我们不要草率地假设。 我认为这解释了为什么您不应该让 EA 脱离监督。 一个可能的解决方案是在出现任何错误时向 C_Manager 类发送平仓信号。 但正如我之前提到的,我们不应该做出假设,所以是否添加这个信号取决于您的需求。

然后我们更新柱线计数器,以便信号仅在下一根柱线上触发。 然而,仅当有响应来自指标时,才会发生。 否则,将在下一个 OnTime 事件中再次检查信号。 故此,我们真的不应该对正在发生的事情做出假设。 注意一切发生的顺序。 如果柱数的更新发生在前,我们将错过下一次 OnTime 事件。 然而,若我们滞后更新它,我们可以接收 OnTimer 事件,从而尝试再次读取指标。

现在我们进行计算,来判定是买入还是卖出。 若要理解这种计算,有必要搞明白指数移动平均线是如何计算出的。 与其它移动平均线不同,指数移动平均线对价格变化的反应更快。 因此,一旦它开始倾斜,基于我们在构造函数中定义的收盘价的位置,我们实际上能知道柱线收盘是在其上方、亦或是下方。

但是这个计算有一个细节:如果我们将当前平均值与前值进行比较,它只会足够快地报告此信息。 因此,任何微小的变化都可能导致触发器发生。 如果您要降低敏感度级别,您必须将 m_Infos.Shift 变量中包含的值从 1 改为 2,或改为更大的值。 这将模拟移动平均偏移,来捕获某种类型的走势,从而降低或增加系统的灵敏度。

这种类型的偏移在某些环境中很常见,例如乔·迪·那不勒斯(Joe di Napoli)。 许多人认为有必要查看柱线相对于移动平均线的位置,但实际上只需要相应地调整 MA 即可了解柱线是否正在跟随形态。 在乔·迪·那不勒斯设置的情况下,我们应该在移动平均线上计算之字折线,以便在相应的点激活触发器。 至此,我们不需要查看柱线,而只需要平均值。

上述计算中的一个重要细节:计算中的零点表示缓冲区的最新值,即指标计算的最后一个值。 

如果最后一个值高于前值,则 EA 应立即买入如果它更低,则 EA 应该卖出

这里有一个奇怪的事实:这个系统类似于著名的拉里·威廉姆斯(Larry Williams)的系统。 有些人使用一个额外的元素:您可以在触发移动平均线信号那根柱线的最高点或最低点放置挂单,替代立即买入或卖出。 鉴于 C_Manager 类保证服务器上只有一笔挂单,我们只需要更改 Triggers 函数,而无需改变计算。 故此,系统将依据生成信号的柱线数据发送挂单,替代直接请求市价交易。

在附件中未提供代码,但它将如下所示:

inline virtual void Triggers(void) final
                        {
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (GetVolumeInPosition() == 0)
                                                {
                                                        DestroyOrderPendent();
                                                        CreateOrder(ORDER_TYPE_BUY, iHigh(NULL, m_TF, 0));
                                                }
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (GetVolumeInPosition() == 0)
                                                {
                                                        DestroyOrderPendent();
                                                        CreateOrder(ORDER_TYPE_SELL, iLow(NULL, m_TF, 0));
                                                }
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                        };

我们在 C_Manager 类代码中添加了一个新函数;它在附件中提供。 现在请注意,正在创建的订单与市价入场不同。 我们现在有一笔入场订单,基于柱线价格之一的限制。 随着形势的演化,它将发生变化。 如果订单未在创建触发器的柱线上执行,则在下一根柱线上,订单将自动重置。 请注意,附件中没有提供此模型,我只是在展示它的潜力。

我想您已经明白这个系统非常灵活。 但我们依然只是触及了此类系统可以实现的表面。 为了描绘另一个应用程序,我们来看第二个示例。


自动化示例 02:利用 RSI 或 IFR

我们曾见识过基于 MA 的系统是如何工作的。 现在,我们来看另一个指标的使用情况。 在这个例子中,我们将使用一个非常流行的指标。 然而,该方法适用于任何其它指标或振荡器,这在于该思路是相当通用的。

此示例的 EA 代码可在本文随附的 EA_v2.mq5file 中找到。 我们再次从构造函数开始:

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                            bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                            const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, FinanceStop, 0, Leverage, IsDayTrade, Trailing, true, 10),
                         m_TF(iPeriod),
                         m_Handle(INVALID_HANDLE)
                        {
                                m_Infos.Shift      = iShift;
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                ArraySetAsSeries(m_Buff, true);
                                m_nBars = iBars(NULL, m_TF);
                                m_Handle = iRSI(NULL, m_TF, 14, PRICE_CLOSE);

                        }

我们看看它与第一个示例的区别。 您可以重复与上一个示例中相同的过程,也可以将上一个示例调整为如下所示:

  • 此处,我们已经定义了一个止损值,在开仓后起作用;
  • 指定盈亏平衡和尾随停止值
  • 我们将使用挂单作为停止点
  • 指示使用基于收盘价的 14-周期 RSI
  • 此处输入超买和超卖值,并存储供以后使用

您看到这个过程有多简单吗? 您可以简单地使用另一个指标,替代 iRSI。 下一步是以下计算:

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                        
                                if (((iRet = iBars(NULL, m_TF)) > m_nBars) && (m_Handle != INVALID_HANDLE))
                                {
                                        if (CopyBuffer(m_Handle, 0, 0, m_Infos.Shift + 1, m_Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                        m_nBars = iRet;
                                        if ((m_Buff[0] > m_Buff[m_Infos.Shift]) && (m_Buff[0] > m_Infos.OverSold) && (m_Buff[m_Infos.Shift] < m_Infos.OverSold)) return TRIGGER_BUY;
                                        if ((m_Buff[0] < m_Buff[m_Infos.Shift]) && (m_Buff[0] < m_Infos.OverBought) && (m_Buff[m_Infos.Shift] > m_Infos.OverBought)) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

在此处的计算中,我们继续执行与上一个示例相同的测试,并提供一些其它详细信息。 您可能已经注意到以下细节:我们分析指标的走势是向下还是向上。这个因素非常重要,因为它也许指示调整。 这就是为什么我们有一个偏移来注意到这种类型的走势。 但无论如何,我们检查指标是否指向超卖或超买设置,然后再根据退出该区域的指示制定第二个决定。这就给出了买入或卖出触发器

在其余方面,我们没有看到任何大的变化。 如您所见,该过程非常简单,且依据分析不会有太大变化。 我们只需定义一个触发器来激发动作,令其适配自动化 EA 所需的任何模型和方法。

不过,与前面的示例不同,在此示例中,我们希望实现盈亏平衡走势和尾随停止。 这个想法是,当有可以带来利润的走势时,我们能以更可观的盈利方式平仓。 此修改是在代码里添加以下内容实现的:

inline virtual void Triggers(void) final
                        {
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (m_Memory != TRIGGER_BUY) ToMarket(ORDER_TYPE_BUY);
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (m_Memory != TRIGGER_SELL) ToMarket(ORDER_TYPE_SELL);
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                                TriggerTrailingStop();
                        };

请注意,该过程与原始程序几乎没有变化。 然而,我们添加了此调用,它将实现盈亏平衡和尾随停止。 如您所见,这一切都归结为调用的时间和地点,因为系统已经拥有所有详细信息供以后使用,并且可以在每种必要情况下适应我们的需求。

如此,我们来看另一个示例,以便更好地理解这些思路。


自动化示例 3:移动平均线交叉

此示例在文件 EA_v3.mq5 中提供。 但我们不会只停留在最基本的自动化过程上:您可以在 C_Manager 类中看到一些细微的变化。 第一个是创建一个例程,让自动化系统知道是否有多头或空头持仓。 它如下所示:

const bool IsBuyPosition(void) const
                        {
                                return m_Position.IsBuy;
                        }

事实上,此函数不会确认仓位是否未平仓,而仅在变量指示买入或卖出时才返回。 这个思路实际上不是检查是否有持仓,而是在买入或卖出后返回,因此函数很简单。 但是,如果您的自动化系统需要验证,您可以检查是否有持仓。 无论如何,这个函数作为我们的示例已经足够了。 这是因为下面的函数:

                void LockStopInPrice(const double Price)
                        {
                                if (m_InfosManager.IsOrderFinish)
                                {
                                        if (m_Pending.Ticket == 0) return;
                                        if ((m_Pending.PriceOpen > Price) && (m_Position.IsBuy)) return;
                                        if ((m_Pending.PriceOpen < Price) && (!m_Position.IsBuy)) return;
                                        ModifyPricePoints(m_Pending.Ticket, m_Pending.PriceOpen = Price, m_Pending.SL = 0, m_Pending.TP = 0);
                                }else
                                {
                                        if (m_Position.SL == 0) return;
                                        if ((m_Position.SL > Price) && (m_Position.IsBuy)) return;
                                        if ((m_Position.SL < Price) && (!m_Position.IsBuy)) return;
                                        ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, Price, m_Position.TP);
                                }
                        }

此函数的意图是在某个点位设置止损价。 事实上,当盈亏平衡已被激活时,它执行与尾随停止代码相同的操作。 然而,在某些类型的交易中,我们不会在持仓上实现盈亏平衡,而是根据某些准则移动止损,例如前一根柱线的最高点或最低点、移动平均线指示的价格、或遵照其它自动化机制。 在这种情况下,我们需要一个特殊函数来执行此任务。请注意,有一些机制可以防止数值沿指定方向移动,进而增加损失。这种类型的锁定非常重要,特别是如果您想根据移动平均线发送数值。

有基于此,我们现在得到以下尾随停止函数的代码:

inline void TriggerTrailingStop(void)
                        {
                                double price, v1;
                                
                                if ((m_Position.Ticket == 0) || (m_InfosManager.IsOrderFinish ? m_Pending.Ticket == 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_InfosManager.IsOrderFinish ? m_Pending.PriceOpen : m_Position.SL);
                                        if (v1 > 0) if (MathAbs(price - v1) >= (m_Position.Gap * 2)) 
                                                LockStopInPrice(v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1)));
                                        {                                               
                                                price = v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1));
                                                if (m_InfosManager.IsOrderFinish) ModifyPricePoints(m_Pending.Ticket, m_Pending.PriceOpen = price, m_Pending.SL = 0, m_Pending.TP = 0);
                                                else    ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                                }
                        }

删除的划掉的部分已被删除,因为现在有一个专门的调用能更好地重用代码

现在我们已经实现了对 C_Manager 类的修改,我们看看如何创建第三个示例,从修改开始。 这些可能因情况而异。 不过,您应该始终注意为创建自动化而完成的计划。 由于这里的自动化需要比前面的情况更多的东西,我们来看看哪些需要修改。 这些修改对于同时使用 2 个指标的任何模型来说都足够了。

我们从声明变量开始:

class C_Automaton : public C_Manager
{
        protected:
                enum eTrigger {TRIGGER_NONE, TRIGGER_BUY, TRIGGER_SELL};
        private :
                enum eSelectMedia {MEDIA_FAST, MEDIA_SLOW};
                struct st00
                {
                        int     Shift,
                                nBars;
                        double  OverBought,
                                OverSold;
                }m_Infos;
                struct st01
                {
                        double  Buff[];
                        int     Handle;
                }m_Op[sizeof(eSelectMedia) + 1];
                int     m_nBars;
                ENUM_TIMEFRAMES m_TF;

此处,我们有一个枚举,它将帮助我们以高级语言访问 MA 数据,从而避免在针对计算实现编程时出错。

下一件我们要做的事就是允许我们访问指标的缓冲区和句柄的结构但请注意,结构被声明为数组,并且该数组的大小正好是枚举中存在的数据量加 1。 换言之,无论我们将使用多少元素,我们需要做的就是将它们添加到枚举之中。 以这种方式,数组就能适配我们将要构建的最终模型。

在某种程度上,这是比默认类模型更好的选择。 但是由于我们可以实现一个更简单的模型,所以首先实现了它。 故此,在我看来,一切都变得更加清晰和易于理解。

现在,您知道如何以非常简单的方式向 C_Automaton 类添加多个指标。 但我们来看看如何在类构造函数中进行实际初始化:

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                                                bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                                                const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, FinanceStop, FinanceTake, Leverage, IsDayTrade, Trailing, true, 10),
                         m_TF(iPeriod)
                        {
                                for (int c0 = sizeof(eSelectMedia); c0 <= 0; c0--)
                                {
                                        m_Op[c0].Handle = INVALID_HANDLE;
                                        ArraySetAsSeries(m_Op[c0].Buff, true);
                                }
                                m_Infos.Shift      = (iShift < 3 ? 3 : iShift);
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                m_nBars = iBars(NULL, m_TF);
                                m_Op[MEDIA_FAST].Handle = iMA(NULL, m_TF, 9, 0, MODE_EMA, PRICE_CLOSE);
                                m_Op[MEDIA_SLOW].Handle = iMA(NULL, m_TF, 20, 0, MODE_SMA, PRICE_CLOSE);
                        }

这就是魔幻开始的地方。 看看我如何按默认情况初始化所有指标,无论它们的数量如何。 最简单的方法是使用循环。 您不必担心枚举中的指标数量。 这并不重要,因为这个循环就能够妥妥管理它们。

现在遇到一点确实需要我们注意。 在此阶段,所有指标将采用相同的时间帧,但您也许需要为不同的指标设置不同的时间帧。 在这种情况下,您需要调整构造函数代码。 然而,修改必须是最低的,并且仅适用于您所创建的特定模型。

但是,在捕获稍后要使用的句柄时要小心。您必须捕获它们,如此就可正确实例化每个指标。 如果未正确完成此操作,则您的模型很可能有问题。 在此,我表示我将在系统中使用 9-周期指数移动平均线,和 20-周期算术移动平均线。但是您可取不同的指标进行组合,当然,前提是您的系统操作需要它们。

重要提示! 这里有一件重要的事情需要注意如果您所用的是您创建的自定义指标,它不必一定置于资产图表上。 但由于您并非是从标准指标中得到它,故需要句柄启动它,从而获取此自定义指标的数据,此时应该调用 iCustom 函数。 请参阅文档中如何调用此函数,以便能够访问您的自定义指标。 再次说明,它不一定需要在图表上 。

注意这一刻,因为它真的很重要。 在 EA 中,我们并未告知偏移值,我们实际上无法使用默认值。 是因为如果我们采用默认值,我们就很难检查实际的均线交叉。 我们必须指定一个最小偏移值,这个值为 3。 我们甚至可以使用 2,如此令触发器更敏感。不过,我们不能指定数值 1,因为这样我们就不能够正确地进行分析。 为了理解原因,我们来看看计算是如何执行的。

一旦构造函数正确初始化了我们将要用到的数据,我们就需要打造负责计算的部分,以便触发机制可以令 EA 自动运行。 如果机制有多个指标,系统的工作方式应与仅使用一个指标时略有不同。 这可以从以下代码中看出:

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                bool bOk = false;
                                        
                                if (iRet = iBars(NULL, m_TF)) > m_nBars)
                                {
                                        for (int c0 = sizeof(eSelectMedia); c0 <= 0; c0--)
                                        {
                                                if (m_Op[c0].Handle == INVALID_HANDLE) return TRIGGER_NONE;
                                                if (CopyBuffer(m_Op[c0].Handle, 0, 0, m_Infos.Shift + 1, m_Op[c0].Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                                bOk = true;
                                        }
                                        if (!bOk) return TRIGGER_NONE; else m_nBars = iRet;
                                        if ((m_Op[MEDIA_FAST].Buff[1] > m_Op[MEDIA_SLOW].Buff[1]) && (m_Op[MEDIA_FAST].Buff[m_Infos.Shift] < m_Op[MEDIA_SLOW].Buff[m_Infos.Shift])) return TRIGGER_BUY;
                                        if ((m_Op[MEDIA_FAST].Buff[1] < m_Op[MEDIA_SLOW].Buff[1]) && (m_Op[MEDIA_FAST].Buff[m_Infos.Shift] > m_Op[MEDIA_SLOW].Buff[m_Infos.Shift])) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

它利用一个循环来执行任务,故不必担心所用指标的数量。 不过,在构造函数中正确初始化所有这些触发器非常重要,因为没有这一步,计算阶段将无法生成买入或卖出信号的触发器。

首先,我们检查指标 ID 是否正确初始化。 如果还没有,那么我们将没有有效的触发器。 一旦完成此检查后,我们开始从指标缓冲区捕获数据如果您对调用此函数有任何疑问,我建议您阅读文档中有关 CopyBuffer 函数的信息。 此功能在取用自定义指标时特别实用。

一旦我们有了所有指标及其各自的缓冲区,我们就可以进入计算部分本身。但是等一下...计算之前的代码是干什么? 此代码是为了避免我们放置枚举器空列表时的情况。 在这种情况下,不会触发计算系统。 如果没有这段代码,即使我们有一个枚举器的空列表,也可以触发计算。 而这将完全破坏系统的健壮性。 但是我们回到计算系统,现在,因为我们所用的是均线交叉,所以我们于此必须非常小心。

请注意,我们不会鲁莽地检查缓冲区中存在的零(最新)值。 原因在于时间帧真正收盘之前,我们可能会出现均线的虚假交叉,这可能会导致意外触发。

系统是否仅在生成新柱线时检查缓冲区? 答案是肯定的,但如果均线在柱线形成的确切时间交叉,系统就会触发一笔订单。 因此,我们忽略最近的值,并分析前一个值。 这就是为什么我们将偏移设置为至少 2,以便获得最高灵敏度,或像本例中所做的那样设置为 3,以便交叉可以远离正在形成的柱线。 但是您可以尝试使用其它计算方法。 这个仅用于演示,绝不应在实盘账户上使用。

为了完成模型的最后一步,我们看看关于系统的其它内容:

inline virtual void Triggers(void) final
                        {
#define def_HILO 20
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (m_Memory != TRIGGER_BUY) ToMarket(ORDER_TYPE_BUY);
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (m_Memory != TRIGGER_SELL) ToMarket(ORDER_TYPE_SELL);
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                                LockStopInPrice(IsBuyPosition() ?  iLow(NULL, m_TF, iLowest(NULL, m_TF, MODE_LOW, def_HILO, 0)) : iHigh(NULL, m_TF, iHighest(NULL, m_TF, MODE_HIGH, def_HILO, 0)));
#undef def_HILO
                        };

这个函数的最大优点正是这段代码,其中我们有一个非常奇怪的尾随停止。 因此,根据情况,订单或止损价格将处于高点或低点,具体取决于我们是买入还是卖出。 采用的数值与许多 B3 交易者已知的 HILO 指标非常相似。 对于那些不熟悉它的人来说,该指标会在一定数量的柱线内寻找价格高点或低点。 此段代码负责此操作:此处我们查找 LO 值,并在此处查找 HI在这两种情况下,HILO 都是 20

第三个示例到此完成。


结论性观点

在这个小序列中,我展示了如何开发一个自动运行的 EA。 我尝试以一种有趣和简单的方式来演示它。 即便有了这个演绎,仍然需要一些研究和一些时间,才能真正学会如何开发 EA。

我所展示出的主要故障、问题和困难,均涉及一名程序员在创建自动运行 EA 时的管辖工作。 但我也为您展示出,这可以带来很多知识,改变您实际观察市场的方式。

我尝试以这样一种方式呈现事物,即您可以实际创建一个安全,可靠和强大的系统。 与此同时,它应该是模块化的、紧凑的和非常轻巧的。 您应该能够将其与许多其它事物结合运用。 拥有一个不允许您同时操作各种事物的系统是没有用处的。 因为若只交易一种资产,您肯定不能真正获得丰厚的利润。

也许大多数读者会对序列中的最后一篇文章感兴趣,我用 3 个实际示例解释了这个思路。 然而,请注意,为了发挥本文的优势,必须了解文章序列整体。 但以一种非常简单的方式,我相信我已经设法传达了这样一种思想,即没有必要成为编程天才,或专攻几门课程并毕业。 您真正需要了解的是 MetaTrader 5 平台,和 MQL5 语言的工作原理。

我还表述了当 MQL5 或 MetaTrader 5 不未供您想要使用的指标时,如何为高效的工作系统创建特定环境。 这是在示例 3 中完成的,我展示了如何创建内部 HILO 指标。 但无论如何,系统应始终正确实现和测试。 因为创建一个最终不能给您带来任何利润的出色系统是没有意义的。

总结本系列文章,我想强调的是,我还没有涵盖所有可能的选项。 我们的初衷不是深入研究所有细节,直到建立用于创建自动化 EA 的建模库。 我将在一个新系列文章中回到这个主题,我们将讨论为市场初学者开发一个实用的工具。

请记住,任何自动 EA 的真正测试都不是在 MetaTrader 5 策略测试器中进行的,而是拥有完备市场操作的模拟账户上进行的。 在那里,EA 进行测试时,可在没有任何隐藏其真实性能的设置下运行。 本系列到此结束,下次再见。 本系列中讨论的所有代码都附带于后,因此您可以研究和分析它们,以真正了解自动 EA 的工作原理。


重要提示:在没有相应知识的情况下,请勿使用附件中提供的 EA。 这些 EA 仅供演示和教学用途。

如果您打算在实盘账户上使用它们,那么您将自行承担风险,因为它们可能会对您的资金造成重大损失。


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

附加的文件 |
最近评论 | 前往讨论 (1)
Liang Sun
Liang Sun | 21 7月 2023 在 12:41

你好,我在EURUSD 1M图表上使用你提供的EA,在使用过程中,遇到了ClosePosition函数无法成功平仓

我猜想是不是因为账号类型为Hedging,平仓必须采用将action设定为TRADE_ACTION_CLOSE_BY,而不是 TRADE_ACTION_DEAL

种群优化算法:和弦搜索(HS) 种群优化算法:和弦搜索(HS)
在本文中,我将研究和测试最强大的优化算法 — 和弦搜索(HS),其灵感来自寻找完美声音和声的过程。 那么现在什么算法在我们的评级中处于领先地位?
构建自动运行的 EA(第 14 部分):自动化(VI) 构建自动运行的 EA(第 14 部分):自动化(VI)
在本文中,我们将把本系列中的所有知识付诸实践。 我们最终将建立一个 100% 自动化和功能性的系统。 但在此之前,我们仍然需要学习最后一个细节。
利用 MQL5 矩阵的反向传播神经网络 利用 MQL5 矩阵的反向传播神经网络
本文讲述在 MQL5 中利用矩阵来应用反向传播算法的理论和实践。 它还提供了现成的类,以及脚本、指标和智能交易系统的示例。
多层感知器和反向传播算法(第 3 部分):与策略测试器集成 - 概述(I) 多层感知器和反向传播算法(第 3 部分):与策略测试器集成 - 概述(I)
多层感知器是简单感知器的演变,可以解决非线性可分离问题。 结合反向传播算法,可以有效地训练该神经网络。 在多层感知器和反向传播系列的第 3 部分当中,我们将见识到如何将此技术集成到策略测试器之中。 这种集成将允许使用复杂的数据分析,旨在制定更好的决策,从而优化您的交易策略。 在本文中,我们将讨论这种技术的优点和问题。