
开发回放系统 — 市场模拟(第 08 部分):锁定指标
概述
在上一篇文章开发回放系统 — 市场模拟(第 07 部分):第一次改进(II)中,我们曾做过一些修正和调整。 不过,仍然还有错误,如该文所附的视频所示。
在本文中,我们将亲眼见证如何修复此错误。 虽然表面上看这似乎很简单,但我们需要遵循若干个步骤。 这个过程将是奇妙和有趣的。 我们的目标是令指标专门应用于特定的图表和品种。 即使用户尝试,他们也无法将指标应用于另外的图表,或在一个会话中多次打开它。
我鼓励您继续阅读,因为内容承诺非常实用。
将指标锁定在特定品种上。
第一步是将控制指标链接到进行市场回放的品种。 这一步虽然看起来很简单,但对于开发我们的主要任务是必要的。 我们来看看指标代码在上下文中会是什么样子:
#property copyright "Daniel Jose" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ #include <Market Replay\C_Controls.mqh> //+------------------------------------------------------------------+ C_Controls Control; //+------------------------------------------------------------------+ int OnInit() { u_Interprocess Info; IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); if (_Symbol != def_SymbolReplay) { ChartIndicatorDelete(ChartID(), 0, def_ShortName); return INIT_FAILED; } if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0; Control.Init(Info.s_Infos.isPlay); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { Control.DispatchMessage(id, lparam, dparam, sparam); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { switch (reason) { case REASON_REMOVE: case REASON_CHARTCLOSE: if (_Symbol != def_SymbolReplay) break; GlobalVariableDel(def_GlobalVariableReplay); ChartClose(ChartID()); break; } } //+------------------------------------------------------------------+
我们首先检查针对的品种是否要进行市场回放。 如果不是这种情况,指标将自动关闭。 请注意,知道指标的名称很重要。 因此,初始化期间执行的第一个函数调用我们的指标,这令我们能够轻松删除它。
现在重要的一点是:当您将其从图表中删除时,MetaTrader 5 会生成 DeInit 事件。 此事件触发 OnDeInit 函数,而标记为 REASON_REMOVE 的事件表示从图表中删除指标。 这是因为该品种与指标设计绑定的品种不同。 如果我们不重新检查并阻止代码运行,品种图表将关闭。 不过,幸亏我们的检查,它将保持开放状态。
如果指标代码与上一篇文章中提供的代码不同,请不要感到惊讶:上一篇文章重点在于其它改进和修复。 不过,在编写了文章和代码,并录制了本文随附的视频后,我意识到尽管其中一个问题已解决,但另一个问题仍未被发现。 这就是为什么我不得不修改代码。
尽管进行了修改,但我们不会在此处详述所做的全部修改。 必须删除很大一部分,因为它对于此处讨论的锁定方面无效。 因此,上面的代码与以前的代码有很大差别。 不过,我相信上一篇文章中讲演的知识在某些时候可能对某些人有用。 我保存了那篇文章,以表明有时我们都会犯错误,但我们仍然应该努力把事情做好。
因此,我们来建立第一个锁定步骤,即确保控制指标仅存在于市场回放品种的图表上。 然而,该衡量值并不会阻止向同一图表或不同图表添加多个指标,因此必须进行调整。
我们应该避免在同一图表上多次使用指标。
我们已经解决了一个问题,现在我们来应对另一个问题。 这里有各种解决方案,这取决于我们真正想要和愿意做什么。 就个人而言,我没有看到这个问题的理想和最终解决方案。 不过,我将尝试提出一种方式,读者能感到熟悉和理解。 最重要的是,该解决方案将完全基于 MQL5。 我甚至考虑过使用外部编码的可能性,但最终决定使用纯 MQL5。 诉诸外部编码,并利用 DLL 进行锁定的想法很诱人,但这太容易了。
我认为在诉诸外部 DLL 来填补 MQL5 语言无法胜任的空白之前,我们在 MQL5 中还有很多东西需要学习。 这将提供一个在使用外部代码时看起来“更干净”的解决方案。 只是,这并无助于更好地理解 MQL5。 此外,这可能会强化 MetaTrader 5 是一个受限平台的误解。 对平台的误解和效力利用不足助长了这种误解。
为了应用我们提议的解决方案,您不得不进行一些修改,并撤回其它更改。 第一步是更改 InterProcess.mqh 头文件,令其拥有以下结构:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_GlobalVariableReplay "Replay Infos" #define def_GlobalVariableIdGraphics "Replay ID" #define def_SymbolReplay "RePlay" #define def_MaxPosSlider 400 #define def_ShortName "Market Replay" //+------------------------------------------------------------------+ union u_Interprocess { union u_0 { double df_Value; long IdGraphic; }u_Value; struct st_0 { bool isPlay; int iPosShift; }s_Infos; }; //+------------------------------------------------------------------+
对于许多不熟悉编程的人来说,这似乎有点奇怪,但令人惊讶的是,上述结构仅占用 8 字节的内存。 您也许已经注意到,从上一篇文章的结构中删除了一个变量。 原因是我们将不再使用这种锁定方法。 我们将采取一种不同的方式,稍微复杂一些,但对于把控制指标限定于单个图表方面则有效得多。 这将是一个非常具体和明确的回放服务。
注意: 如果 MetaTrader 5 平台和 MQL5 语言的开发人员为该服务提供了向特定图表添加指标的能力,或者允许该服务在图表上调用和执行脚本,那将会很有趣。 使用脚本,我们可以将指标添加到特定图表当中,但目前服务无法做到这一点。我们可以打开图表,但不能向其内添加指标。 当尝试执行此动作时,即使我们使用 MQL5 函数,也始终显示错误消息。 在撰写本文时,MetaTrader 5 版本为 build 3280。
重要提示:在撰写本章的这个阶段,这是一个更高级的阶段,由此我才能够实现这一点。 不过,当我撰写这篇文章时,我找不到任何可以帮助解决这个问题的参考资料。 因此,请关注这个回放/模拟系列,看看我是如何想出解决方案的。
在这种关联境况下,通过运行下面的脚本,我们就能够打开指标,并将其添加到图表之中:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { ENUM_TIMEFRAMES time = PERIOD_D1; string szSymbol = "EURUSD"; long id = ChartOpen(szSymbol, time); ChartRedraw(id); ChartIndicatorAdd(id, 0, iCustom(szSymbol, time, "Media Movel.ex5")); }
然而,如果我们将相同的脚本转换为服务,我们就不会得到相同的结果。
#property service //+------------------------------------------------------------------+ void OnStart() { ENUM_TIMEFRAMES time = PERIOD_D1; string szSymbol = "EURUSD"; long id = ChartOpen(szSymbol, time); ChartRedraw(id); ChartIndicatorAdd(id, 0, iCustom(szSymbol, time, "Media Movel.ex5")); }
请注意,这里唯一的变化是 compilation 属性,它现在指定编译后的代码作为一个服务。 只需用一个保留字就能将脚本转换为服务,且可完全改变代码的工作方式,即使它执行的任务与以前相同。 故此,您需要用到模板来将指标添加到图表之中。 如果可以通过服务添加指标,我们就可将指标编译为内部服务资源。 因此,当打开图表时,它将直接从服务接收指标,而无需与其它指标混合。
即便我们防止(如上所示)向图表添加与回放品种无关的指标,用户也可以将指标插入到以市场回放为品种的图表上。 而这是不允许的。 如此,一旦我们对头文件 Interprocess.mqh 进行了修改,我们就能专注于服务代码。 更确切地说,我们将移步到头文件 C_Replay.mqh。
简而言之,于此我们的所作所为:我们要告诉指标是否服务正处于激活状态。 当它处于激活状态时,我们将指明哪个图表是主图表。 为此,我们需要修改代码,现在如下所示:
long ViewReplay(ENUM_TIMEFRAMES arg1) { u_Interprocess info; if ((m_IdReplay = ChartFirst()) > 0) do { if (ChartSymbol(m_IdReplay) == def_SymbolReplay) { ChartClose(m_IdReplay); ChartRedraw(); } }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1); ChartApplyTemplate(m_IdReplay, "Market Replay.tpl"); ChartRedraw(m_IdReplay); GlobalVariableDel(def_GlobalVariableIdGraphics); GlobalVariableTemp(def_GlobalVariableIdGraphics); GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value); return m_IdReplay; }
首先,我们清除全局终端变量。 然后,我们再次创建相同的变量,确保它现在只是个临时变量。 然后我们将服务打开的图表的标识符写入此变量。 如此,我们已经大大简化了指标的工作,因为我们只需要分析服务记录的这个值。
然而,我们不应忘记,当我们终止服务时,我们还需要删除我们创建的其它全局变量,如下面的代码所示:
void CloseReplay(void) { ArrayFree(m_Ticks.Info); ChartClose(m_IdReplay); SymbolSelect(def_SymbolReplay, false); CustomSymbolDelete(def_SymbolReplay); GlobalVariableDel(def_GlobalVariableReplay); GlobalVariableDel(def_GlobalVariableIdGraphics); }
通过这些修改,我们可以向指标添加新控制。
int OnInit() { u_Interprocess Info; IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); if ((_Symbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics))) { ChartIndicatorDelete(ChartID(), 0, def_ShortName); return INIT_FAILED; } if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0; Control.Init(Info.s_Infos.isPlay); return INIT_SUCCEEDED; }
即使我们尝试在市场回放品种中添加控制指标,除非回放服务创建一个全局终端变量,否则我们将无法这样做。 只有当此变量存在时,才有可能在回放品种图表上运行指标。 不过,此操作仍然无法解决我们的问题。 我们还需要执行少量检查。
接下来,我们将实现一个检查,在开始阶段将指标链接到相应的图表。 第一步如下所示:
int OnInit() { #define macro_INIT_FAILED { ChartIndicatorDelete(ChartID(), 0, def_ShortName); return INIT_FAILED; } u_Interprocess Info; IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); if ((_Symbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics))) macro_INIT_FAILED; Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics); if (Info.u_Value.IdGraphic != ChartID()) macro_INIT_FAILED; if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0; Control.Init(Info.s_Infos.isPlay); return INIT_SUCCEEDED; #undef macro_INIT_FAILED }
由于初始化函数中经常重复相同的代码,我决定定义一个辅助宏定义。 这将令我们能够避免在编写代码时可能出现的错误。 现在,我们迈入锁定的第一阶段。 创建品种图表时,我们通过全局终端变量传递该图表的 ID。 这样,我们就可以随时捕获该值,来确认控制指标确实位于应由回放服务生成的预期图表上。 如果尝试将指标添加到与回放服务创建的图表不同的图表上时,则此操作将被拒绝。
虽然这在宽广的状况下都有帮助,但我们仍然会遇到一个问题:向同一图表添加其它控制指标的能力。 为了最终解决这个问题,我们将采用一种稍微超常规的方式。 请记住,在撰写本文时,该平台的版本如下图所示:
在您阅读本文时,该平台可能已经进行了重大更新,并令此处介绍的机制过时了。 但我们看一下允许您配置控制指标的机制,如此新的控制指标就不能添加到同一图表之中,从而将其限制于特定图表。 如果指标被移除,则图表将关闭,回放服务也即刻关闭。
在继续实际代码之前,请务必注意,我将采用的机制基于布尔逻辑。 如果您不熟悉这些概念,我建议您对该主题进行一些研究,因为这些概念对于任何代码的创建和开发都至关重要。 有些人也许认为最简单的解决方案是使用 DLL,以此更直接的方式解决这种情况。 我部分同意,但在外部程序中创建解决方案有许多缺点。
这不会让我们完整准确地了解 MQL5 语言的局限性,如此我们就无法找到改进它的建议。 许多人声称 C/C++ 是最强力的语言,嗯,他们说得对。 然而,它并没有作为一个单独实体出现;随着开发人员探索其极限,它也在不断发展,并获得新能力。 当触及这些界限,并且无法实现所需的功能时,就会创建新的功能,令以前无法实现的项目变得可行。 这就是为什么 C/C++ 已被证明是一种可靠的语言,几乎可以胜任任何项目。
我确信 MQL5 具有与 C/C++ 相同的品质和潜力。 我们只需要尽可能多地学习和测试 MQL5 语言。 然后,当触及这些界限时,开发人员可以为 MQL5 提供改进和新功能。 随着时间的流逝,它就能成为针对 MetaTrader 5 的极其强力的应用程序开发语言。
为了理解我们实际要做什么,我们需要了解语言的当前局限性,即某些信息无法进行抽象。 请注意,我并未说这样做是不可能的,我的意思是我们不能创建一个抽象,来让我们的工作更容易。 这些是不同的概念。 开发数据和信息的抽象是一回事,能够以我们需要的方式操纵数据是另一回事。 不要把这些事情混淆。
在 C/C++ 中,我们可以创建一个数据抽象,允许我们在一串比特位序列中隔离一个特定的比特位。 这很容易达成,见下文:
union u01 { double value; struct st { ulong info : 63; bool signal; }c; }data;
尽管这看起来很奇怪和愚蠢,但我们正在创造一种数据抽象形式,令我们能够识别甚至改变信息的标志。 这段代码此时此地并无大用,但让我们考虑另一点。 假设我们想要发送信息,我们使用比特来控制某些东西。 我们可以有这样的东西:
struct st { bool PlayPause; bool Reservad : 6; bool RS_Info; }ctrl;
在这种场景下,抽象级别将帮助我们隔离我们实际想要访问的比特位,从而令代码更易于阅读。 然而,如前所述,目前 MQL5 不允许我们使用所表述的抽象级别。 我们必须采取不同的方法,因为纯粹的抽象是不可能的,但如果我们了解语言的局限性,我们仍然可以操纵数据。 因此,我们求助于布尔逻辑来处理本来应通过抽象处理的数据。 不过,使用布尔逻辑令程序更加难以解释。 因此,我们从具有抽象的高级系统转向低级系统,其中抽象被简化为布尔逻辑。
我们稍后将回到这个讨论。 但您会看到,原因将比现在显示的更合理。 我提到的一切似乎都不重要,但如果您看一下最终的控制指标代码,您就会明白我想要说明什么。 通常并不是说 MQL5 不能做某些事情。 事实上,许多程序员不想进入更深层次,在那里根本不存在数据抽象,这就某些东西无法创建。
以下是当前开发阶段控制指标的完整而全面的代码。 该代码可以锁定图表上的指标,并禁止用户在一个 MetaTrader 5 会话中添加其它控制指标。
#property copyright "Daniel Jose" #property description "This indicator cannot be used\noutside of the market replay service." #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ #include <Market Replay\C_Controls.mqh> //+------------------------------------------------------------------+ C_Controls Control; //+------------------------------------------------------------------+ #define def_BitShift ((sizeof(ulong) * 8) - 1) //+------------------------------------------------------------------+ int OnInit() { #define macro_INIT_FAILED { ChartIndicatorDelete(id, 0, def_ShortName); return INIT_FAILED; } u_Interprocess Info; long id = ChartID(); ulong ul = 1; ul <<= def_BitShift; IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); if ((_Symbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics))) macro_INIT_FAILED; Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics); if (Info.u_Value.IdGraphic != id) macro_INIT_FAILED; if ((Info.u_Value.IdGraphic >> def_BitShift) == 1) macro_INIT_FAILED; IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName + "Device"); Info.u_Value.IdGraphic |= ul; GlobalVariableSet(def_GlobalVariableIdGraphics, Info.u_Value.df_Value); if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0; Control.Init(Info.s_Infos.isPlay); return INIT_SUCCEEDED; #undef macro_INIT_FAILED } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { Control.DispatchMessage(id, lparam, dparam, sparam); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { u_Interprocess Info; ulong ul = 1; switch (reason) { case REASON_CHARTCHANGE: ul <<= def_BitShift; Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics); Info.u_Value.IdGraphic ^= ul; GlobalVariableSet(def_GlobalVariableIdGraphics, Info.u_Value.df_Value); break; case REASON_REMOVE: case REASON_CHARTCLOSE: if (_Symbol != def_SymbolReplay) break; GlobalVariableDel(def_GlobalVariableReplay); ChartClose(ChartID()); Control.Finish(); break; } } //+------------------------------------------------------------------+
在理解到底发生了什么之时,您遇到麻烦了吗? 无法了解我们的控制指标如何工作? 嗯,这就是关键点。 此代码中没有抽象,而这易于理解。 这是因为 MQL5 不允许我们达成与 C/C++ 相同的抽象级别。 因此,我们被迫求助于布尔逻辑,尽管它更复杂,但令我们能够操纵数据,并达成与使用抽象相同的结果。
如果您抵近观察,您会发现双精度类型需要 8 个字节来存储其值。 同样,long(有符号)或 ulong(无符号)类型也占用相同的 8 个字节。 考虑到通过 ChartID 获取的图表 ID 返回 long 类型,我们有 1 个额外的位,正好用到指示符号位。 我们要做的就是用这个特定的比特位将指标锁定在图表上。 我们将操纵指标名称,从而防止将另一个控制指标添加到同一个图表当中。 如何? 按照解释。
首先,我们判定我们将拥有多少比特位,以及我们将用到哪些比特位。 故此,无论我们用到的是 64 位、32 位还是 128 位系统,此定义都能适配相应的类型和长度。 尽管我们知道它是 64 位的,但我希望设置是灵活的,而不是静态的。 因此,我们从该值中减去 1,从而隔离了 long 的符号位。
接下来,我们激活这 64 位中最低有效位。 完成此操作后,我们将得到值 1,这将是我们的起点。 接下来,我们向左移动 63 位,得到值 0x8000000000000000000000000,其中最高有效位的值为 1,即 true。 如果我们直接设置此值,则可以避免此步骤,但输入错误的风险很高。 通过这样做,我们可以将出错的可能性降到最低。
一旦我们得到这个值,我们有两个选择。 第一个是锁定系统。 第二种选择是解锁系统,允许 MetaTrader 5 在必要时尽快在图表上重新应用指标。 第二种选择更简单,我们先来看看它。
为了解锁系统,我们获取包含图表 ID 的全局终端变量的值,并对该值执行 XOR(异或) 运算,以便保留除最高有效位之外的所有比特位。 理想情况下,我们应该先执行 NOT(非) 操作,然后执行 AND(与) 操作,替代 XOR(异或)操作。 这将清除最高有效位中所含的任何信息。 不过,由于此操作仅在给定比特位已包含某些信息时发生,因此我认为使用 XOR 操作没有任何问题。 如果您遇到问题,请将 XOR 操作替换为以下行:
Info.u_Value.IdGraphic &= (~ul);
无论如何,我们已经达成了我们的目标:重置最高有效位。 以这种方式,我们可以在 MetaTrader 5 尝试将控制指标返回到图表之前将值存储回全局终端变量之中。 如此就完成了锁定系统最简单的部分,现在我们继续讨论更复杂的部分。
在此阶段要做的第一件事是检查图表品种是否与回放品种匹配,以及回放服务是否正常工作。 如果不满足这些条件中的任何一个,则该指标将从图表中删除。 然后,我们捕获全局终端变量中包含的值,该变量提供回放服务创建的图表 ID。 下一步,我们将此值与图表窗口的 ID 进行比较:如果它们不同,指标也会被删除。 接下来,我们取结果值,并将其向右移动 63 位,检查该位是否处于激活状态。 如是,则将再次删除标识符。
虽然这似乎已经足够了,但我们还需要解决另一个问题。 这个特殊的问题让我增加了一些工作量,保持所有内容都限于 MQL5 内。 我之所以这么说,是因为我不得不在代码中添加一行特定的代码。 没有它,每次我试图阻止系统向图表添加指标时,它仍然会添加一个指标。 即使它并没有引起问题,但它在指标窗口中仍然可见,这让我很恼火。 就在那时,我萌生了更改指标名称的想法,但只有第一个指标才会收到这个新名称。 此指标在服务创建时与图表一起生成。
当模板应用于图表时,指标会自动激活。 说到模板,还有一点。 但首先,我们先完成这个解释。 最后,在完成所有这些步骤后,我们执行一个 OR 操作,并将结果保存在全局终端变量中,从而锁定控制指标。 为了结束这个主题,需要再做一个修正。 如果我们不进行最终的修改,我们所做的所有工作都将毫无用处,也无法正常运行。 我可以跳过这些信息,吹嘘自己做了许多人认为不可能的事情。 然后,如果有人试图做同样的事情,他们可能会失败。 但我不是来吹牛,或标榜我是不可或缺的。 我不是在寻找这样的认可,与之对比,我想表明,超越许多人认为可能的事情是可能的,有时可以在意想不到的地方找到问题的解决方案。
如果您按照提供的所有说明进行操作,并尝试执行整个过程,除了一个方面之外,您会发现自己几乎可以在所有方面取得成功。 无论您多么努力,都无法阻止将新的控制指标加载到回放服务创建的图表之中。 您可能会问:“但为什么呢? 我遵循了所有步骤,甚至为此添加了一行特殊代码。 您是在开玩笑吗?
我很想说这是个玩笑,但并非如此。 事实上,您将无法避免这种情况。 这是由于系统中的“故障”(注意引号)。 我不确定究竟是什么原因导致的,或者它是如何发生的,但请参看下面的代码:
long ViewReplay(ENUM_TIMEFRAMES arg1) { u_Interprocess info; if ((m_IdReplay = ChartFirst()) > 0) do { if (ChartSymbol(m_IdReplay) == def_SymbolReplay) { ChartClose(m_IdReplay); ChartRedraw(); } }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1); ChartApplyTemplate(m_IdReplay, "Market Replay.tpl"); ChartRedraw(m_IdReplay); GlobalVariableDel(def_GlobalVariableIdGraphics); GlobalVariableTemp(def_GlobalVariableIdGraphics); GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value); return m_IdReplay; }
执行此行时会发生一些奇怪的事情。 我冥思苦想了很长时间,直到我决定打开模板来了解它是如何工作的。 那时我才意识到,我们应该强制系统不要使用模板,而是自动创建指标,并将其应用于图表,正如我在文章开头所解释的那样。 我在那里曾展示过使用服务不允许我们向图表添加指标,而图表只有在我们使用脚本时才能添加。 系统工作正常,但当我使用模板时,它不起作用了。
我努力寻找问题所在。 我没有找到任何人知道答案。 然而,利用自多年编程中获得的技术和技能,我辨别出问题所在。 如果查看模板文件中以前的文章和数据,可以找到以下数据:
<indicator> name=Custom Indicator path=Indicators\Market Replay.ex5 apply=0 show_data=1 scale_inherit=0 scale_line=0 scale_line_percent=50 scale_line_value=0.000000 scale_fix_min=0 scale_fix_min_val=0.000000 scale_fix_max=0 scale_fix_max_val=0.000000 expertmode=0 fixed_height=-1
从表面看,似乎一切都是正确的,事实上也确实如此。 然而,上一个片段中修改了某些内容,系统开始按预期工作,设法锁定控制指标,且不允许添加其它指标。
更正后的版本如下所示:
<indicator> name=Custom Indicator path=Indicators\Market Replay.ex5 apply=1 show_data=1 scale_inherit=0 scale_line=0 scale_line_percent=50 scale_line_value=0.000000 scale_fix_min=0 scale_fix_min_val=0.000000 scale_fix_max=0 scale_fix_max_val=0.000000 expertmode=0 fixed_height=-1
我不会向您展示确切的修改点。 我希望您能自行查看,并尝试理解。 但不必担心,随附的代码版本提供了正确的系统操作。
结束语
正如我早前提及的,我可以跳过这些信息,当有人问我为什么他们不能让系统工作时,我可以装作自己是一个更好的程序员。 但我不喜欢吹牛。 我希望人们学习、理解、并有动力去寻找解决方案。 只要有可能,分享您的知识,因为这就是我们为如何进化做出贡献。 隐瞒知识不是优越感的标志,而是恐惧或缺乏信心的标志。
我已经克服了这个阶段。 这就是我解释如何工作的原因。 我希望更多人能受到启发,去做同样的事情。 拥抱大家,下一篇文章见。 我们的工作才刚刚开始。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10797
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.


