
构建自动运行的 EA(第 15 部分):自动化(VII)
概述
在上一篇文章构建自动运行的 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


你好,我在EURUSD 1M图表上使用你提供的EA,在使用过程中,遇到了ClosePosition函数无法成功平仓
我猜想是不是因为账号类型为Hedging,平仓必须采用将action设定为TRADE_ACTION_CLOSE_BY,而不是 TRADE_ACTION_DEAL