English Русский Español Deutsch 日本語 Português
preview
开发回放系统(第 68 部分):取得正确的时间(一)

开发回放系统(第 68 部分):取得正确的时间(一)

MetaTrader 5示例 |
370 0
Daniel Jose
Daniel Jose

概述

在文章“开发回放系统(第 66 部分):玩转服务(七) “中,我演示了如何让鼠标指标告知我们当前柱结束和新柱开始在图表上绘制之前还剩多少时间。虽然这种方法效果很好,效率也很高,但它有一个问题。尽管许多人认为问题在于方法本身,但实际上与交易品种的流动性有关。如果您仔细阅读了文章中提供的解释,您可能已经注意到鼠标指标仅在收到新报价时更新信息。

发生这种情况是因为我们依赖 MetaTrader 5 来触发 OnCalculate 事件,该事件允许指标以秒为单位接收新值,以便计算当前柱的剩余时间。正如文章中所示,该值是通过点差设置的。然而,由于我们依赖于点差服务提供的值,我们无法准确报告流动性低的资产(或流动性低期间)的剩余持仓时间。当资产进入竞价状态时,问题变得更加明显,因为市场可能会暂停几分钟。不幸的是,对于竞价场景没有可靠的解决方法。即使对于流动性良好的资产,竞价期间也不会触发 OnCalculate 事件。因此,必须更新回放/模拟服务,以确保用户在这两种情况下都能得到正确的通知。首先,流动性较低,每秒仅发生几笔交易。第二,当资产进入竞价时。我们必须先解决这两个问题,才能进入下一阶段的开发。


实现低流动性解决方案

第一种情况是流动性低,解决起来相对简单明了,因为它主要需要更改服务代码,至少在一般情况下是这样。最终可能还需要其他地方做出改变。但目前,我们将尝试通过仅修改回放/模拟服务的源代码来解决它。

为了有效地确定这是否可能,我们将暂时搁置我们的原始源代码。我们将创建一个单独的测试代码来验证服务在非活动状态下是否仍可以更新鼠标指标以显示当前柱关闭前的剩余时间。

我们需要开发的代码应该尽可能简单,不要太复杂或繁琐。目标是测试是否确实有可能在不影响图表上显示的信息的情况下将数据从服务器发送到指标。用于此目的的源代码如下所示:

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. //+------------------------------------------------------------------+
06. #include <Market Replay\Defines.mqh>
07. //+------------------------------------------------------------------+
08. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != ""))
09. //+------------------------------------------------------------------+
10. void OnStart()
11. {
12.    long id;
13.    int handle;
14.    MqlRates Rate[1];
15.    
16.    Print("Starting Test Service...");
17.    SymbolSelect(def_SymbolReplay, false);
18.    CustomSymbolDelete(def_SymbolReplay);
19.    CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
20.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.5);
21.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 5);
22.    Rate[0].close = 105;
23.    Rate[0].open = 100;
24.    Rate[0].high = 110;
25.    Rate[0].low = 95;
26.    Rate[0].tick_volume = 5;
27.    Rate[0].spread = 1;
28.    Rate[0].real_volume = 10;
29.    Rate[0].time = D'10.03.2023 09:00';
30.    CustomRatesUpdate(def_SymbolReplay, Rate, 1);
31.    Rate[0].time = D'10.03.2023 09:30';
32.    CustomRatesUpdate(def_SymbolReplay, Rate, 1);
33.    SymbolSelect(def_SymbolReplay, true);
34.    id = ChartOpen(def_SymbolReplay, PERIOD_M30);
35.    Sleep(1000);
36.    if ((handle = iCustom(NULL, 0, "\\Indicators\\Mouse Study.ex5")) != INVALID_HANDLE)
37.       ChartIndicatorAdd(id, 0, handle);
38.    IndicatorRelease(handle);
39.    Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS));
40.    while (def_Loop)
41.    {
42.       CustomRatesUpdate(def_SymbolReplay, Rate, 1);
43.       Sleep(250);
44.       Rate[0].spread++;
45.       if (Rate[0].spread == 60)
46.       {
47.          Rate[0].time += 60;
48.          Rate[0].spread = 0;
49.          Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS));
50.          EventChartCustom(id, evSetServerTime, (long)(Rate[0].time), 0, "");
51.       }
52.    }
53.    ChartClose(id);
54.    SymbolSelect(def_SymbolReplay, false);
55.    CustomSymbolDelete(def_SymbolReplay);
56.    Print("Finished Test Service...");   
57. }
58. //+------------------------------------------------------------------+

测试服务源代码

请记住,为了使下面描述的过程正常工作,必须编译鼠标指标并将其位于我在解释过程中显示的特定目录中。否则,测试将会失败。因此,让我们了解需要执行什么,以及如果测试证明对我们的预期目的有效,我们以后如何利用它来发挥我们的优势。

前四行是与生成的可执行文件类型相关的声明和属性设置。在第 6 行,我们包含一个头文件,只是为了避免必须在此脚本中声明其他组件。第 8 行定义了我们即将实现的循环的控制指令。事实上,前 8 行中唯一真正重要的行是第 2 行。其他代码可以省略,并将它们各自的代码插入到适当的位置。但是,第 2 行至关重要,因为它告知 MetaTrader 5 生成的可执行文件应该是一项服务。否则,它将被解释为脚本,这不是我们在这里要测试的。

从第 10 行开始,事情变得更加有趣。在第 12 行和第 14 行之间,我们声明了将要使用的变量。然后,从第 17 行到第 21 行,我们开始初始化自定义交易品种的创建。这些都是标准步骤,因此我们可以继续前进。现在,在第 22 行和第 28 行之间,我们为该柱形创建一个 RATE 结构。这里真正重要的是第 29 行,我们在此定义创建柱形的时间戳。在第 30 行中,我们指定 MetaTrader 5 应该用来生成它的柱形数据。现在请密切注意!在第 31 行,我们使用相同的数据创建第二个柱形,但这次偏移了 30 分钟。出现这种偏移的原因是图表将以 30 分钟的时间范围打开。如果您在第 31 行创建柱形时使用任何其他时间值,鼠标指标将计算距离该柱形关闭还剩多少时间。所以,要注意这个细节。另一个关键点:不要包含任何以秒为单位的值。该值必须以分钟为单位定义,而不能以秒为单位。下一步是在第 32 行创建柱形。

在第 34 行,我们打开图表。正如本系列上一篇文章所讨论的,在服务可以向图表添加任何内容之前,无论是对象还是指标,都必须等待一段时间。这使得 MetaTrader 5 平台有足够的时间来正确构建图表并将其呈现在屏幕上。这个暂停发生在第 35 行,我们等待 1 秒钟才继续执行。

再次强调,需要注意第 36 行。这里,我们尝试创建一个句柄以便将鼠标指标放置在图表上。iCustom 函数返回添加指标所需的句柄(如果找到指标)。如果未找到,该函数将返回无效句柄,我们不会继续使用它。因此,鼠标指标必须位于指定位置。否则,测试将会失败。如果成功找到指标,我们就将其添加到图表中。这是在第 37 行完成的。由于 iCustom 返回的句柄之后不再需要,我们在第 38 行释放它。

现在开始真正的测试。我们真正感兴趣的部分是,我们确定这种方法是否有效。如果是这样,我们将有办法解决低流动性情况下的问题。如果没有,我们需要探索其他解决方案。那么,让我们了解一下这个测试是如何进行的。它发生在一个循环中,从第 40 行开始,到第 52 行结束。剩下的代码(第 53 至 56 行)只是结束了测试,与本文没有特别关系。让我们专注于测试本身。


了解测试如何进行

它从第 40 行开始,我们从这里进入循环。如果图表是打开的,并且用户没有停止服务,则循环将无限期运行。一旦进入循环,我们就到达第 42 行。此行强制更新柱形数据。然而,需要注意的是,实际上并没有新的信息被传递给柱形,只有我们为了测试目的而改变的信息。这是我们所做的。在第 43 行,我们添加了一个短暂的延迟,以防止更新发生得太快。注意延迟值:我们不需要等待整整 1 秒。这只需要四分之一秒,当您稍后在 MetaTrader 5 中观察结果时,这一点很重要。

然后,在第 44 行,我们增加点差值。这会让鼠标指标感觉到已经过去了 1 秒,尽管实际延迟时间更短。其目的与平台如何触发 OnCalculate 事件直接相关。到目前为止,预期行为如下:一旦打开图表并显示鼠标指标,MetaTrader 5 应该生成 OnCalculate 事件,从而允许当前柱的剩余时间相应减少。您可以自己检查一下。然而,我已经测试过了,并确认它按预期工作。出于好奇,我决定看看当价差值超过 60 时会发生什么。为什么这个值如此重要?因为一旦达到 60,就会出现一个新的一分钟柱,即使您使用每月的时间范围。实际图表时间范围在这里无关紧要。就我们的目的而言,我们只需要让 MetaTrader 5 获悉新的一分钟柱已形成。

因此,我们必须确保我们的代码正确处理这一点。MetaTrader 5 无法自行解决这种情况。第 45 行检查迭代计数是否已达到 60。当它发生时,第 47 行会在原始柱时间上增加 60 秒(一分钟)。这样,下次执行第 42 行的函数时,MetaTrader 5 就会识别出一个新的一分钟柱。但这还不够。在第 48 行,我们重置迭代计数器,以便开始新的循环。为了验证,第 49 行打印了发送到 MetaTrader 5 的值。

这里有一个关键点:到目前为止,只有 MetaTrader 5 已获悉有新柱存在。然而,正如我们在文章中看到的,我们实现了在鼠标指标中显示剩余时间的系统,我们不能依赖 OnCalculate 函数提供的时间值。至少现在还没有。我目前正在探索解决这一限制的方法,这最终将使第 50 行变得不必要。在实施这样的解决方法之前,第 50 行必须强制 MetaTrader 5 生成自定义事件。这会通知鼠标指标已形成新的柱形。因此,该指标可以正确识别当前时间点,并准确地告知我们在当前柱关闭和新柱开始之前还剩多少时间。

如果您不想在 MetaTrader 5 中编译和测试此服务,也没问题。在下面的视频中,我演示了该服务的行为方式以及该测试代码是否适用于回放/模拟服务。根据我的评估,这次测试效果非常好。它证明了将测试机制集成到回放/模拟器应用程序中的能力和可行性。这将使我们能够保持准确的时间跟踪,即使在流动性低的时期或交易活动较少的交易品种下也是如此,确保用户了解图表上出现新柱形之前还剩多少时间。


为了将测试的机制集成到回放/模拟器应用程序中,我们需要稍微修改代码的特定部分。此部分位于头文件 C_Replay.mqh 中。但在我们实现这些更改之前,我们需要先了解另一个概念。为了更好地组织解释,让我们转到一个新的主题。


时间,时间,时间 —— 我们如何理解它?

虽然某些事情一开始可能看起来很混乱,但我需要向你展示一些看起来非常复杂的东西,但这是完全可能的,在逻辑上也是合理的。这一切都围绕着所涉及的数据结构展开。

如果您查看 OnCalculate 函数,您会发现价差值是以 int 形式提供的。在 64 位处理器上,这意味着我们正在处理 32 位数据。同时,如果您检查 datetime 类型,您会发现它使用 64 位。好的。但这些细节对我们有何帮助?请耐心,亲爱的读者,请耐心。我们现在需要做一些数学运算,但并不复杂,只是一些基本的计算。

我们需要的是让服务强制 MetaTrader 5 生成某种事件来更新鼠标指标中的计时器。这是既定的,我们已经以两种不同的方式处理了。一种是通过调用 OnCalculate 函数,另一种是通过我们定期触发的自定义事件。要了解服务中处理此问题的位置,请查看 C_Replay.mqh 头文件中的代码:

068. //+------------------------------------------------------------------+
069. inline void CreateBarInReplay(bool bViewTick)
070.          {
071.             bool    bNew;
072.             double dSpread;
073.             int    iRand = rand();
074. 
075.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
076.             {
077.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
078.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
079.                {                  
080.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
081.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
082.                   {
083.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
084.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
085.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
086.                   {
087.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
088.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
089.                   }
090.                }
091.                if (bViewTick)
092.                {
093.                   CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
094.                   if (bNew) EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)m_Infos.Rate[0].time, 0, "");
095.                }
096.                m_Infos.Rate[0].spread = (int)macroGetSec(m_MemoryData.Info[m_Infos.CountReplay].time);
097.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
098.             }
099.             m_Infos.CountReplay++;
100.          }
101. //+------------------------------------------------------------------+

来自 C_Replay.mqh 文件的代码

在文章开发回放系统(第 66 部分):玩转服务(七)中已经解释过 ,但在这里我们要加强解释。每次调用此过程并在图表上当前绘制的柱形上添加新的分时报价时,我们都会在第 96 行将一个值(以秒为单位)添加到价差中。这使我们能够计算当前柱的剩余时间。好吧。此外,每当出现新的一分钟柱时,第 94 行的条件就会变为 true,从而强制 MetaTrader 5 触发自定义事件。此事件允许鼠标指标重新调整其内部时间并正确检测新柱的生成时间。这样做的原因是为了避免添加在鼠标指标本身内非常密集地执行的额外调用。我在前面提到的文章中对此进行了更详细的解释。

然而,通过这种方式处理,我们既浪费时间又浪费空间。与当前方法相比,我们可以更高效地使用更少的资源传输更多信息。现在进入计算部分。我们只需要以一秒为间隔跟踪时间的流逝;我们不需要更快。这给了我们一个明确的起点:一分钟有 60 秒,一小时有 60 分钟,一天有 24 小时。每天总计 86400 秒(大约如此,因为一天并不正好是 24 小时,但为了我们的目的,我们假设是 24 小时)。好吧。32 位无符号整数最大可容纳 4,294,967,295 的值。假设我们有一个无符号值(我们有),我们可以在单个 32 位字段中存储大约 49 天的秒数。这是一个要点,在单个价差值(32 位)内,我们可以表示大约 49 天。然而,在实践中,我们的回放/模拟器应用程序不太可能用于跨越连续一天或两天的研究。因此,我们可以完全通过点差字段同步必要的数据,而无需强制 MetaTrader 5 发出自定义事件只是为了通知鼠标指标已形成新的柱形。

现在事情变得有趣了,不是吗?为了提高效率,我将更进一步,实施一个更复杂的模型,以完全避免 49 天的限制。实际上,我怀疑任何人都不会达到这个上限,但我仍然会实施其他东西。我们将采用一种位级控制策略,为我们提供更大的灵活性。这种方法需要对鼠标指标进行一些调整。但与它带来的好处相比,这并不太复杂。

但这真的能给我们带来我们想要的解决方案吗?为了找到答案,我们需要查看另一个代码片段,它也来自 C_Replay.mqh 头文件。如下所示:

207. //+------------------------------------------------------------------+
208.       bool LoopEventOnTime(void)
209.          {         
210.             int iPos;
211. 
212.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
213.             {
214.                UpdateIndicatorControl();
215.                Sleep(200);
216.             }
217.             m_MemoryData = GetInfoTicks();
218.             AdjustPositionToReplay();
219.             EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)macroRemoveSec(m_MemoryData.Info[m_Infos.CountReplay].time), 0, "");
220.             iPos = 0;
221.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
222.             {
223.                if (m_IndControl.Mode == C_Controls::ePause) return true;
224.                iPos += (int)(m_Infos.CountReplay < (m_MemoryData.nTicks - 1) ? m_MemoryData.Info[m_Infos.CountReplay + 1].time_msc - m_MemoryData.Info[m_Infos.CountReplay].time_msc : 0);
225.                CreateBarInReplay(true);
226.                while ((iPos > 200) && (def_CheckLoopService))
227.                {
228.                   Sleep(195);
229.                   iPos -= 200;
230.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks);
231.                   UpdateIndicatorControl();
232.                }
233.             }
234. 
235.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
236.          }
237. };
238. //+------------------------------------------------------------------+

来自 C_Replay.mqh 文件的代码

很好,当资产流动性低时,我们的问题就出现了。也就是说,在连续流中,每秒都不会产生分时报价。在某些时候,流动性可能足以每秒产生一个分时报价,这对于我们的目的是理想的。然而,有时也无法达到这种流动性水平。即便如此,您仍可以观察到,在第 225 行,我们正在调用前面讨论过的代码片段。即使两次分时报价之间的时间超过一秒,第 225 行上的调用实际上仍会发生。所以是的,使用价差来传输时间信息确实是一个可行的解决方案。然而,实施所有必要的机制可能有些复杂。因此,本文不可能涵盖所有细节。尽管如此,让我们从这里的基础开始吧。


开始新阶段

我们要做的第一步是修改 Defines.mqh 头文件。具体修改如下:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.    #define macro_DEBUG_MODE(A) \
08.                Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.    #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay      "RePlay"
14. #define def_MaxPosSlider      400
15. #define def_MaskTimeService   0xFED00000
16. //+------------------------------------------------------------------+
17. union uCast_Double
18. {
19.    double   dValue;
20.    long     _long;                                  // 1 Information
21.    datetime _datetime;                              // 1 Information
22.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
23.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
24.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
25. };
26. //+------------------------------------------------------------------+
27. enum EnumEvents    {
28.          evHideMouse,               //Hide mouse price line
29.          evShowMouse,               //Show mouse price line
30.          evHideBarTime,             //Hide bar time
31.          evShowBarTime,             //Show bar time
32.          evHideDailyVar,            //Hide daily variation
33.          evShowDailyVar,            //Show daily variation
34.          evHidePriceVar,            //Hide instantaneous variation
35.          evShowPriceVar,            //Show instantaneous variation
36.          evSetServerTime,           //Replay/simulation system timer
37.          evCtrlReplayInit,          //Initialize replay control
38.                   };
39. //+------------------------------------------------------------------+

Defines.mqh 文件源代码

请注意,第 36 行被删除,这意味着它在文件的最终版本中不再存在。同样,我们现在在第 15 行有了一个新的定义,它将作为一个掩码,使我们能够更舒适地工作。仅仅删除负责更新计时器的事件第 36 行,就意味着需要进行一些调整,使新代码与更新的消息处理策略兼容。

正如我们对 Defines.mqh 文件所做的更改一样,我们在另一个头文件中进行了额外的更新,这次是 Macros.mqh。该文件的更新版本如下所示:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define macroRemoveSec(A) (A - (A % 60))
05. #define macroGetDate(A)   (A - (A % 86400))
06. #define macroGetSec(A)    (A - (A - (A % 60)))
07. #define macroGetTime(A)   (A % 86400)
08. //+------------------------------------------------------------------+
09. #define macroColorRGBA(A, B) ((uint)((B << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16)))
10. #define macroTransparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0)
11. //+------------------------------------------------------------------+

Macros.mqh 文件的源代码

在这里,我们只添加了第 7 行,它引入了一个宏,使我们能够从datetime类型的变量中提取时间值。由于这是一个简单的添加,我们可以继续查看另一个头文件。这次的头文件是 C_Stuty.mqh,下面可以查看全文。该文件是鼠标指标的一部分。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\C_Mouse.mqh"
005. //+------------------------------------------------------------------+
006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_"
007. //+------------------------------------------------------------------+
008. class C_Study : public C_Mouse
009. {
010.    private   :
011. //+------------------------------------------------------------------+
012.       struct st00
013.       {
014.          eStatusMarket  Status;
015.          MqlRates       Rate;
016.          string         szInfo,
017.                         szBtn1,
018.                         szBtn2,
019.                         szBtn3;
020.          color          corP,
021.                         corN;
022.          int            HeightText;
023.          bool           bvT, bvD, bvP;
024.          datetime       TimeDevice;
025.       }m_Info;
026. //+------------------------------------------------------------------+
027.       void Draw(void)
028.          {
029.             double v1;
030.             
031.             if (m_Info.bvT)
032.             {
033.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
034.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo);
035.             }
036.             if (m_Info.bvD)
037.             {
038.                v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
039.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
040.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
041.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
042.             }
043.             if (m_Info.bvP)
044.             {
045.                v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
046.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
047.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
048.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
049.             }
050.          }
051. //+------------------------------------------------------------------+
052. inline void CreateObjInfo(EnumEvents arg)
053.          {
054.             switch (arg)
055.             {
056.                case evShowBarTime:
057.                   C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise);
058.                   m_Info.bvT = true;
059.                   break;
060.                case evShowDailyVar:
061.                   C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
062.                   m_Info.bvD = true;
063.                   break;
064.                case evShowPriceVar:
065.                   C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
066.                   m_Info.bvP = true;
067.                   break;
068.             }
069.          }
070. //+------------------------------------------------------------------+
071. inline void RemoveObjInfo(EnumEvents arg)
072.          {
073.             string sz;
074.             
075.             switch (arg)
076.             {
077.                case evHideBarTime:
078.                   sz = m_Info.szBtn1;
079.                   m_Info.bvT = false;
080.                   break;
081.                case evHideDailyVar:
082.                   sz = m_Info.szBtn2;
083.                   m_Info.bvD   = false;
084.                   break;
085.                case evHidePriceVar:
086.                   sz = m_Info.szBtn3;
087.                   m_Info.bvP = false;
088.                   break;
089.             }
090.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
091.             ObjectDelete(GetInfoTerminal().ID, sz);
092.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
093.          }
094. //+------------------------------------------------------------------+
095.    public   :
096. //+------------------------------------------------------------------+
097.       C_Study(long IdParam, string szShortName, color corH, color corP, color corN)
098.          :C_Mouse(IdParam, szShortName, corH, corP, corN)
099.          {
100.             if (_LastError != ERR_SUCCESS) return;
101.             ZeroMemory(m_Info);
102.             m_Info.Status = eCloseMarket;
103.             m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1));
104.             m_Info.corP = corP;
105.             m_Info.corN = corN;
106.             CreateObjInfo(evShowBarTime);
107.             CreateObjInfo(evShowDailyVar);
108.             CreateObjInfo(evShowPriceVar);
109.          }
110. //+------------------------------------------------------------------+
111.       void Update(const eStatusMarket arg)
112.          {
113.             int i0;
114.             datetime dt;
115.                      
116.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
117.             {
118.                case eCloseMarket   :
119.                   m_Info.szInfo = "Closed Market";
120.                   break;
121.                case eInReplay      :
122.                case eInTrading   :
123.                   i0 = PeriodSeconds();
124.                  dt = (m_Info.Status == eInReplay ? (datetime) m_Info.TimeDevice + GL_TimeAdjust : TimeCurrent());
125.                   dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent());
126.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
127.                   if (dt > 0) m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS);
128.                   break;
129.                case eAuction      :
130.                   m_Info.szInfo = "Auction";
131.                   break;
132.                default            :
133.                   m_Info.szInfo = "ERROR";
134.             }
135.             Draw();
136.          }
137. //+------------------------------------------------------------------+
138. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
139.          {
140.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
141.             switch (id)
142.             {
143.                case CHARTEVENT_CUSTOM + evHideBarTime:
144.                   RemoveObjInfo(evHideBarTime);
145.                   break;
146.                case CHARTEVENT_CUSTOM + evShowBarTime:
147.                   CreateObjInfo(evShowBarTime);
148.                   break;
149.                case CHARTEVENT_CUSTOM + evHideDailyVar:
150.                   RemoveObjInfo(evHideDailyVar);
151.                   break;
152.                case CHARTEVENT_CUSTOM + evShowDailyVar:
153.                   CreateObjInfo(evShowDailyVar);
154.                   break;
155.                case CHARTEVENT_CUSTOM + evHidePriceVar:
156.                   RemoveObjInfo(evHidePriceVar);
157.                   break;
158.                case CHARTEVENT_CUSTOM + evShowPriceVar:
159.                   CreateObjInfo(evShowPriceVar);
160.                   break;
161.                case (CHARTEVENT_CUSTOM + evSetServerTime):
162.                   m_Info.TimeDevice = (datetime)lparam;
163.                   break;
164.                case CHARTEVENT_MOUSE_MOVE:
165.                   Draw();
166.                   break;
167.             }
168.             ChartRedraw(GetInfoTerminal().ID);
169.          }
170. //+------------------------------------------------------------------+
171. };
172. //+------------------------------------------------------------------+
173. #undef def_ExpansionPrefix
174. #undef def_MousePrefixName
175. //+------------------------------------------------------------------+

C_Study.mqh 文件源代码

应从原始文件中删除此文件中所有删除的行。虽然这些行以前确实是用于允许自定义事件保持指标计时器同步的旧代码的一部分,但这不再是必要的,因为我们现在正在为此任务实现一种不同的方法。但是,重要的是要观察此头文件中的特定细节。查看第 124 行并将其与第 125 行进行比较。尽管显示整行都被替换了,但实际上发生的情况是变量 m_Info.TimeDevice 被删除了。这是因为它不再需要了,它以前被用来保持计时器同步。现在,这给我们带来了一个新问题,可以说,它存在于鼠标指标本身的代码中。该指标的完整代码如下:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "This is an indicator for graphical studies using the mouse."
04. #property description "This is an integral part of the Replay / Simulator system."
05. #property description "However it can be used in the real market."
06. #property version "1.68"
07. #property icon "/Images/Market Replay/Icons/Indicators.ico"
08. #property link "https://www.mql5.com/pt/articles/"
09. #property indicator_chart_window
10. #property indicator_plots 0
11. #property indicator_buffers 1
12. #property indicator_applied_price PRICE_CLOSE
13. //+------------------------------------------------------------------+
14. double GL_PriceClose;
15. datetime GL_TimeAdjust;
16. //+------------------------------------------------------------------+
17. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
18. //+------------------------------------------------------------------+
19. C_Study *Study       = NULL;
20. //+------------------------------------------------------------------+
21. input color user02   = clrBlack;                         //Price Line
22. input color user03   = clrPaleGreen;                     //Positive Study
23. input color user04   = clrLightCoral;                    //Negative Study
24. //+------------------------------------------------------------------+
25. C_Study::eStatusMarket m_Status;
26. int m_posBuff = 0;
27. double m_Buff[];
28. //+------------------------------------------------------------------+
29. int OnInit()
30. {
31.    ResetLastError();
32.    Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04);
33.    if (_LastError != ERR_SUCCESS) return INIT_FAILED;
34.    if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
35.    {
36.       MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
37.       OnBookEvent((*Study).GetInfoTerminal().szSymbol);
38.       m_Status = C_Study::eCloseMarket;
39.    }else
40.       m_Status = C_Study::eInReplay;
41.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
42.    ArrayInitialize(m_Buff, EMPTY_VALUE);
43.    
44.    return INIT_SUCCEEDED;
45. }
46. //+------------------------------------------------------------------+
47. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[],
48.                 const double& high[], const double& low[], const double& close[], const long& tick_volume[], 
49.                const long& volume[], const int& spread[]) 
50. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double& price[])
51. {
52.    GL_PriceClose = close[rates_total - 1];
53.    GL_PriceClose = price[rates_total - 1];
54.    GL_TimeAdjust = (spread[rates_total - 1] < 60 ? spread[rates_total - 1] : 0);
55.    if (_Symbol == def_SymbolReplay)
56.       GL_TimeAdjust = iSpread(NULL, PERIOD_M1, 0) & (~def_MaskTimeService);
57.    m_posBuff = rates_total;
58.    (*Study).Update(m_Status);   
59.    
60.    return rates_total;
61. }
62. //+------------------------------------------------------------------+
63. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
64. {
65.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
66.    (*Study).SetBuffer(m_posBuff, m_Buff);
67.    
68.    ChartRedraw((*Study).GetInfoTerminal().ID);
69. }
70. //+------------------------------------------------------------------+
71. void OnBookEvent(const string &symbol)
72. {
73.    MqlBookInfo book[];
74.    C_Study::eStatusMarket loc = m_Status;
75.    
76.    if (symbol != (*Study).GetInfoTerminal().szSymbol) return;
77.    MarketBookGet((*Study).GetInfoTerminal().szSymbol, book);
78.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading);
79.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
80.       if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
81.    if (loc != m_Status) (*Study).Update(m_Status);
82. }
83. //+------------------------------------------------------------------+
84. void OnDeinit(const int reason)
85. {
86.    if (reason != REASON_INITFAILED)
87.    {
88.       if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
89.          MarketBookRelease((*Study).GetInfoTerminal().szSymbol);
90.    }
91.    delete Study;
92. }
93. //+------------------------------------------------------------------+

鼠标指标源代码

有时,这段代码的行为方式在测试过程中会让我感到非常沮丧。还有其他一些元素,我已经测试过,但稍后会深入探讨,它们令人难以置信地愤怒,主要是因为它们没有意义。这些东西应该奏效,但由于一些奇怪和无法解释的原因,它们就是行不通。我将把这些主题留到另一篇文章中讨论,以便我可以更冷静地解释它们。亲爱的读者,现在我想提请你注意必须做出的一些关键改变。

让我们从结尾开始,这样我们就能理解开头。看一下第 47 行到第 49 行,它们已被删除并由第 50 行取代。换句话说,OnCalculate 事件处理程序不再接收所有这些参数。你可能想知道我为什么做出这样的改变。尤其是在我之前提到做这样的事情不是一个好主意之后。当我介绍服务代码时,我会解释这种战术转变背后的原因,但不是在这里。现在请注意,第 52 行已被第 53 行替换,第 54 行已被第 55 行和第 56 行替换。除此之外,还有另一个变化:引入第 12 行。但在进入第 12 行之前,让我们仔细看看第 55 行和第 56 行发生了什么。

当服务更新 RATE 时(我们稍后会看到具体如何完成),MetaTrader 5 会触发 Calculate 事件,然后由 OnCalculate 函数处理。好吧。然而,如果图表时间范围不是一分钟,rates_total 值将不会更新,直到形成新的柱形。由于我们仅在回放/模拟期间使用价差值,因此我们会检查交易品种是否与预期交易品种匹配。如果是 true,我们就使用 iSpread 函数获取价差值。为什么我们要这样做而不是仅仅使用 OnCalculate 参数,以及第 56 行中的计算,将在下一篇文章中解释。

也就是说,因为我们现在使用 iSpread 函数来检索正确的值,所以我们不再需要使用其所有先前的参数调用 OnCalculate。因此,第 53 行正确地捕获了价格值。现在,问题是:哪个价格?我们可以使用的不止一个。这就是第 12 行的作用所在。作为指标的属性,它定义了我们实际将使用的价格。在这种情况下,我们选择使用每个条形图的收盘价。这使得鼠标指标与图表上先前显示的值兼容。


最后的探讨

在本文中,我讨论了一些尚未解决的问题,我将在后续的文章中更深入地讨论这些问题。如果你只看这篇文章,这里提到的一些事情可能没有意义。例如,为什么我们使用 iSpread,而不是简单地读取通过 MetaTrader 5 已经提供的 OnCalculate 传入的价差值。我们还有另一个主题尚未讨论:当资产进入竞价模式时会发生什么。当使用自定义交易品种时,这是一个特别奇怪的情况。并不是因为鼠标指标无法显示资产何时处于竞价模式。问题在于其他方面。 

该功能已经实现并运行。您可以通过查看鼠标指标代码中的 OnBookEvent 函数自行检查。您会发现,我们只需要报告值 BOOK_TYPE_BUY_MARKETBOOK_TYPE_SELL_MARKET ,鼠标指标就会显示该资产正在竞价中。但我将在另一篇文章中详细解释如何做到这一点。目前就这些。下期再见。

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

附加的文件 |
Anexo.zip (420.65 KB)
将互信息作为渐进特征选择的准则 将互信息作为渐进特征选择的准则
在本文中,我们展示了基于最优预测变量集与目标变量之间互信息渐进特征选择的MQL5实现。
数据科学和机器学习(第 31 部分):利用 CatBoost AI 模型进行交易 数据科学和机器学习(第 31 部分):利用 CatBoost AI 模型进行交易
CatBoost AI 模型最近在机器学习社区中广受欢迎,因为它们的预测准确性、效率、及针对分散和困难数据集的健壮性。在本文中,我们将详细讨论如何实现这些类型的模型,进而尝试进击外汇市场。
您应当知道的 MQL5 向导技术(第 43 部分):依据 SARSA 进行强化学习 您应当知道的 MQL5 向导技术(第 43 部分):依据 SARSA 进行强化学习
SARSA 是 “State-Action-Reward-State-Action” 的缩写,是另一种能在实现强化学习时运用的算法。故此,正如我们在 Q-学习 和 DQN 中看到的那样,我们考察了如何在向导汇编的智能系统中探索和实现它,将其作为独立模型,而不仅仅是一种训练机制。
价格行为分析工具包开发(第二部分):分析注释脚本 价格行为分析工具包开发(第二部分):分析注释脚本
秉承我们简化价格行为分析的核心理念,我们很高兴推出又一款可显著提升市场分析能力、助力您做出精准决策的工具。该工具可展示关键技术指标(如前一日价格、重要支撑阻力位、成交量),并在图表上自动生成可视化标记。