开发回放系统 — 市场模拟(第 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