规划
为了验证这一点,我们必须对方法进行一些修改。 如果这个过程成功了,我们就不必再困惑于回放创建系统了。 我们就能专注于其它问题,并帮助我们使用真实跳价数值或模拟值进行研究或训练。 拼装 1 分钟柱线的方式保持不变。 这将是本文的主要焦点。
我们将最大程度使用一种通用方式,而我发现的最好的方式就是使用类似客户端-服务器的系统。 我已经在之前的文章“从头开始开发智能系统(第 16 部分):访问 Web 上的数据(II)”中解释过相关技术。在那篇文章中,我展示了在 赫兹量化中传输信息的三种途径。 此处,我们将采用这些途径之一,即服务。 因此,市场回放将成为 赫兹量化的一个服务。
您也许会认为我将从头开始创建所有内容。 但我为什么要做这样的事情呢? 基本上,系统已经在运行,然并未达到期望的 1 分钟时间。 您也许会问:“您认为将系统更改为服务可以解决此问题吗?“ 事实上,简单地用服务替换系统并不能解决我们的问题。 但是,如果我们从一开始就将 1 分钟柱线的创建与 EA 系统的其余部分隔离开来,那么我们以后的工作就会减少,因为 EA 本身会导致柱线构建的执行略有延迟。 我稍后会解释其中的原因。
您现在明白我们为什么要使用服务了吗? 它比上面讨论的其它方法更实用。 我们能够如我在有关如何在 EA 和服务之间交换消息的文章中解释的方式来控制它:从头开始开发交易 EA(第 17 部分):访问网络上的数据(III)。 但在这里我们不在意如何生成此控制,我们只希望服务能生成放置在图表上的柱线。 为了令事情更有趣,我们将以更具创造性的方式使用该平台,而不仅仅是使用 EA 和服务。
提醒一下,在最后一次尝试减少时间时,我们得到了以下结果:
这是我们得到的最佳时间。 在此,我们马上就要打碎这个时间。然而,我不希望您完全依附于这些值或此处显示的测试。 这一系列与创建回放/模拟器系统相关的文章已经处于更高级的阶段,我多次更改了一些概念,以便系统能真实地按预期工作。 即使此时一切似乎都足够了,但在内心深处,我犯了一些与计时测试相关的错误。 这种错误或误解,在一个早期的系统中,并不容易被注意到。 随着本系列文章的发展,您会注意到这个与时间相关的问题要复杂得多,它涉及的不仅仅是让 CPU 和 MetaTrader 5 平台在图表上提供一定数量的数据,如此您就可以沉浸在回放/模拟器系统中。
所以您不要从字面上理解在这里看到的一切。 追随本系列文章,因为我们在这里要做的事情并不简单或容易做到。
实现
我们从创建系统的基础开始。 这些包括:
- 创建 1 分钟柱线的服务
- 用于启动服务的脚本
- 用于模拟的 EA(这将在后面讨论)
定义行情回放服务
为了正确操控该服务,我们需要更新我们的 C_Replay 类。 但是这些变化非常小,所以我们不会深入到细节。 基本上,这些是返回代码。 不过,有一点值得单独注意,因为它实现了其他一些东西。 代码如下:
#define macroGetMin(A) (int)((A - (A - ((A % 3600) - (A % 60)))) / 60) int Event_OnTime(void) { bool isNew; int mili; static datetime _dt = 0; if (m_ReplayCount >= m_ArrayCount) return -1; if (m_dt == 0) { m_Rate[0].close = m_Rate[0].open = m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last; m_Rate[0].tick_volume = 0; m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60; CustomRatesUpdate(def_SymbolReplay, m_Rate, 1); _dt = TimeLocal(); } isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt; m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt); mili = m_ArrayInfoTicks[m_ReplayCount].milisec; while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec) { m_Rate[0].close = m_ArrayInfoTicks[m_ReplayCount].Last; m_Rate[0].open = (isNew ? m_Rate[0].close : m_Rate[0].open); m_Rate[0].high = (isNew || (m_Rate[0].close > m_Rate[0].high) ? m_Rate[0].close : m_Rate[0].high); m_Rate[0].low = (isNew || (m_Rate[0].close < m_Rate[0].low) ? m_Rate[0].close : m_Rate[0].low); m_Rate[0].tick_volume = (isNew ? m_ArrayInfoTicks[m_ReplayCount].Vol : m_Rate[0].tick_volume + m_ArrayInfoTicks[m_ReplayCount].Vol); isNew = false; m_ReplayCount++; } m_Rate[0].time = m_dt; CustomRatesUpdate(def_SymbolReplay, m_Rate, 1); mili = (m_ArrayInfoTicks[m_ReplayCount].milisec < mili ? m_ArrayInfoTicks[m_ReplayCount].milisec + (1000 - mili) : m_ArrayInfoTicks[m_ReplayCount].milisec - mili); if ((macroGetMin(m_dt) == 1) && (_dt > 0)) { Print("Elapsed time: ", TimeToString(TimeLocal() - _dt, TIME_SECONDS)); _dt = 0; } return (mili < 0 ? 0 : mili); }; #undef macroGetMin
高亮显示的部分已添加到 C_Replay 类的源代码之中。 我们要做的是定义延迟时间,也就是说,我们将明确地采用在该行中获得的值,但以毫秒为单位。 不要忘记,这个时间并非准确,因为它还取决于一些变量。 不过,我们将尝试将其维持在尽可能接近 1 毫秒。
考虑到这些更改,我们来查看下面的服务代码:
#property service #property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ #include <Market Replay\C_Replay.mqh> //+------------------------------------------------------------------+ input string user01 = "WINZ21_202110220900_202110221759"; //File with ticks //+------------------------------------------------------------------+ C_Replay Replay; //+------------------------------------------------------------------+ void OnStart() { ulong t1; int delay = 3; if (!Replay.CreateSymbolReplay(user01)) return; Print("Waiting for permission to start replay ..."); GlobalVariableTemp(def_GlobalVariable01); while (!GlobalVariableCheck(def_SymbolReplay)) Sleep(750); Print("Replay service started ..."); t1 = GetTickCount64(); while (GlobalVariableCheck(def_SymbolReplay)) { if ((GetTickCount64() - t1) >= (uint)(delay)) { if ((delay = Replay.Event_OnTime()) < 0) break; t1 = GetTickCount64(); } } GlobalVariableDel(def_GlobalVariable01); Print("Replay service finished ..."); } //+------------------------------------------------------------------+