English Русский Español Deutsch 日本語 Português
preview
开发回放系统 — 市场模拟(第 13 部分):模拟器的诞生(III)

开发回放系统 — 市场模拟(第 13 部分):模拟器的诞生(III)

MetaTrader 5测试者 | 8 一月 2024, 17:07
269 0
Daniel Jose
Daniel Jose


概述

上一篇文章 开发回放系统 — 市场模拟(第 12 部分):模拟器的诞生(II) 只是本文的准备。 今天,我们将对模拟系统进行一些更改,从而令数据达成更大的一致性。 与此同时,我们还将进行一些重大修改,令系统在处理方面更加高效。 在创建回放/模拟系统的后续环节中,我们将需要它。 关键是:为了令系统能真正起到回放或模拟器作用,我们要求它的行为具有一致性,或者至少尽可能保持行为的一致。 我们不能让一个系统在某个时间点按某种方式工作,然后在另一个时间点以完全不同和不可预测的方式工作。

在文章 “开发回放系统 — 市场模拟(第 02 部分):首次实验(II)” 中,我们创建的系统一直沿用至今,但当涉及模拟和生成伪随机数据之时,它就不再适用了。 实话说,即使我们当作回放来用(使用真实跳价),当前系统也并不完全合适。 如果期望的资产或日间具有极高波动性,则尤其如此。 在这种场景下,当前用来创建和显示 1-分钟柱线的系统效率非常低,有时还会导致同步问题。 换言之,建立柱线本需 1-分钟,然有时可能需要更长时间,这会给我们留下错误印象,即高波动性走位很容易跟踪或做交易,但这并不是真的。

这个问题的解决方案远非如此简单,因为我们不得不改变它的实际构建方式。 您也许认为这项任务很容易,但这并非如此。 它涉及某种建模类型,如果我们不知道自己在做什么,就会变得非常困难。 甚至我(就是正在向您展示如何做到这一点的我)也花费了很长时间才意识到柱线的构建系统有误。 当我进入建模阶段时,我才注意到这一点,其中差别随时间推移才真正显现,因为它涉及一些我们稍后才会看到的计算。 但即便现在,也不要以为我能解决这个问题。 这个问题稍后会在施建的下一阶段得以解决。 我们首先进行一些调整,并引入一个构建 1-分钟柱线的新系统。


新的市场回放服务

为了实际构建 1-分钟柱线,以便我们可在需要时检查它们,我们要对回放服务进行一些修改。 我们要修改的第一件事就是服务文件。 下面是新的回放服务文件的完整全貌。

#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"
#property description "Replay-simulation system for MT5."
#property description "It is independent from the Market Replay."
#property description "For details see the article:"
#property description "https://www.mql5.com/en/articles/11034"
#property link "https://www.mql5.com/en/articles/11034"
//+------------------------------------------------------------------+
#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string            user00 = "Config.txt";  //"Replay" config file.
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;     //Initial timeframe for the chart.
input bool              user02 = true;          //Visual bar construction.
input bool              user03 = true;          //Visualize creation metrics.
//+------------------------------------------------------------------+
void OnStart()
{
        C_Replay        Replay;
        
        Replay.InitSymbolReplay();
        if (Replay.SetSymbolReplay(user00))
        {
                Print(<"Wait for permission from [Market Replay] indicator to start replay...");
                if (Replay.ViewReplay(user01))
                {
                        Print("Permission received. The replay service can now be used...");
                        while (Replay.LoopEventOnTime(user02, user03));
                }
        }       
        Replay.CloseReplay();
        Print("Replay service completed...");
}
//+------------------------------------------------------------------+

您能看到这要简单得多,至少表面上看如此。 所有的复杂性都转移到对象类中,对此有个很好的理由:时间。 这里仍存在一些微妙的问题,通篇文章我都会解释这些问题,不过,如同旧服务文件的情况,一些操作会占用系统的宝贵毫时。 尽管试图提高 MetaTrader 5 平台的效率,但所有这些花费的毫时最终都会导致性能下降,由此实际处理和绘制 1-分钟柱线就要更长的时间。

不过,如果您细察的话,您会看到已为用户添加了一个新变量。 这令我们能够创建并检查创建 1-分钟柱线所需或所花费的时间。 注意,我们现在仅有一个调用会阻塞柱线创建循环。 除了在两种非常特殊的情况下,否则该调用不会返回。 但很快您就会看到我们正在谈论的是什么状况。 然后这个循环将像无限循环一样运行,但实际上它由函数调控,该函数将位于对象类内部。 由于这种简化,实际文件只有这个。 至于对象类,它的复杂性有所增加。 结果就是,以前是公开的若干个函数不再公开,现在是类的私密函数。 这称为方法隐身。 故此,仅有的真正公开元素是出现在服务文件中的函数,如上图所示。

这样的话,我们就能开始看到已经发生的变化。 这令服务文件更大程度的简化。 第一处修改如下所示:

                bool 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);
                                while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
                                
                                return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
                        }

基本上代码与以前相同,但我们加进了以前在服务文件代码中运行的测试。 这样的话,服务文件就不再需要知道用哪个图表 ID 来显示回放资产。 因此,该处期望一个由指标创建的全局终端变量。 但是,如果用户关闭图表或停止服务,则此循环结束。 不过,如果一切正常,且变量已定义,而用户尚未关闭图表或服务,我们会得到 TRUE 值,故服务将转入下一步,如下所示。

                bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
                        {

                                u_Interprocess Info;
                                int iPos, iTest;
                                
                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;
                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               
                                return (m_ReplayCount == m_Ticks.nTicks);
                        }

这段例程比看起来要多得多。 您也许认为它一直在进进出出。 但实际上,它只在两种情况下返回到服务文件代码。 第一种是用户是否暂停创建 1-分钟柱线。 第二种是当服务停止时,要么是因为图表关闭,要么是因为我们不再有可用的跳价。 这就是说,当服务因任何原因停止时。 在任何其它情况下,如果达到跳价的末尾,我们的值为 TRUE,而在任何其它情况下,我们的值为 FALSE。 FALSE 值将终止回放/模拟系统,如在服务代码中所见。

现在我们来看看当该例程只是卡在它所含的内部循环中时,剩下的时间会发生什么。 是的,我们有 2 个循环,每个循环负责一些非常具体的事情。 我们把重点放在这两者上,以便弄清楚这里发生了什么。 第一个循环高亮显示如下:

// ... declaring variables ...

                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;

//... the rest of the code (will be discussed later) ...

这段代码有一个循环,会在两种状况下工作。 第一种状况是,如果服务尚未由关闭图表来终止。 请注意,我们也在测试一个变量。 那么我们该怎么办呢? 注意,在循环中,我们会检查一些条件来更改变量的值,但这些检查从何而来? 这些检查以前是在服务代码内启动的。 不过,有一个问题。 每次运行这些检查时,它们都要要花费几毫秒,但需要最多机器周期的检查则是图表是否打开。

在系统实际开始绘制 1-分钟柱线之前进行检查可以节省机器周期。 不过,我们需要一条循环之外的途径。 那么,如果用户启动服务,我们将指示循环应当结束。 为了确保它走在柱线构造系统前头,我们将测试变量设置为正值。 现在,如果出于某种原因循环结束,且用户尚未开始构建柱线,我们将返回 FALSE。 以这种方式,服务就知道回放/模拟需要完结。

不过,如果满足触发条件,我们实际上要做两件事:第一件是找到回放/模拟的起点。 本文稍后将讨论负责此操作的函数。 第二件事是重置延迟系统。 这样我们就可以进入如下所示的第二个循环。

// ... Code from the previous loop...

                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               

// ... The rest of the function ...

第二个循环负责系统的正常功能。 此处,我们将实际构建 1-分钟柱线。 在没有更多数据可供使用,或系统被终止之前,系统不会退出循环。 还有另一个退出条件 — 当用户暂停时。 在这种情况下,该函数将停止,然后再次被调用,返回到上面所示的第一个循环。 现在我们看看为什么这个系统比以前版本的回放/模拟服务效率更高。 在此,我们要调用一个稍后将看到的函数。 它将创建 1-分钟柱线。 不要担心它是如何工作的 — 仅需知道施建是在其它地方完成的。

但现在一个重要问题浮现:如果仔细观察,您会发现每个跳价,无论它们是真实交易亦或模拟,都必须在特定时间创建。 相关片段如下图高亮所示:

注意,我们有小时、分钟和秒,这对我们来说并无意义:在回放/模拟中,对我们来说真正重要的是秒之后的数字,即毫秒。 如果您查看一下这些数字,您会注意到它们也许看起来很长。 然而,我们真正需要明白的不是以毫秒为单位的时间,而是最后一次跳价的时间与我们即将显示内容之间的差值。 在某些情况下,差值非常小,有时不到 2 毫秒。 以前的系统无法处理这个问题。 我们需要一个不同的、更快的系统。 尽管我们进行了尝试,但在时间如此短的情况下,它无法正常工作,如上图所示。 一次调用和下一次调用之间的时间远小于 10 毫秒。

但按照这种绘制 1-分钟柱线的新方法,我们可在现代计算机上将其减少至不到 1 毫秒。 而且,由于柱线构建过程已经变得非常快速,正如我们稍后将看到的那样,我们不再需要求助 OpenCL 来获得相应的性能。 我们仅用 CPU 就能达成这一点,无需 GPU。 不过,请注意,我们的每次跳价都不会再延迟。 我们积累了一点东西,然后短暂休息一下。 累积值和暂停值可以更改,允许我们进行优调。 这样的话,我们就能得到十分适合的结果,如下面的视频所示。 它展示系统设法在 1-分钟柱之间达成的时间。

精度并不完美,但我们能够调整该值,从而获得更精准的时间。 利用操作系统的内部计数器不能达成精确定时,因为该计数器无法以良好的精度处理低于 16 毫秒的时间。 不过,请注意,这项工作还处于研究阶段,尚未完成。 也许要花费一些时间才能找到改善这种状况的途径,但就目前而言,我认为这已经足够了。



我们还需要检查图表是否打开,这是间或完成的。 不过,由于我们并不经常这样做,因此造成的延迟会低得多。 好吧,所有调用总是会产生一个小的延迟。 我们还需要更新位置值,并捕获控制指标的当前状态,这也会导致轻微的执行延迟。 此外,检查图表是否打开会导致性能略有损失。 不过,严格来说这个更低得多,因为每秒钟才调用几次。

据此,我们能完结这部分了。 但首先,我们来看一下这个 1-分钟柱线构建系统的其它两项功能。 第一个是寻找回放/模拟应该开始的点。

                void AdjustPositionToReplay(const bool bViewBuider)
                        {
                                u_Interprocess Info;
                                MqlRates       Rate[def_BarsDiary];
                                int            iPos,   nCount;
                                
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
                                iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
                                Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
                                if (iPos < m_ReplayCount)
                                {
                                        CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX);
                                        if ((m_dtPrevLoading == 0) && (iPos == 0))
                                        {
                                                m_ReplayCount = 0;
                                                Rate[m_ReplayCount].close = Rate[m_ReplayCount].open = Rate[m_ReplayCount].high = Rate[m_ReplayCount].low = m_Ticks.Info[iPos].last;
                                                Rate[m_ReplayCount].tick_volume = Rate[m_ReplayCount].real_volume = 0;
                                                CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                                        }else
                                        {
                                                for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--);
                                                m_ReplayCount++;
                                        }
                                }else if (iPos > m_ReplayCount)
                                {
                                        if (bViewBuider)
                                        {
                                                Info.s_Infos.isWait = true;
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                        }else
                                        {
                                                for(; Rate[0].time > m_Ticks.Info[m_ReplayCount].time; m_ReplayCount++);
                                                for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
                                                CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
                                        }
                                }
                                for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay();
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.isWait = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                        }

相较于以前的版本,它并未经历重大变化,但不同于以前版本,在此版本中,我们不再返回任何值。 更改了柱线创建,令其更加高效。 这看似是一件小事,但这样做事实上却有很大帮助。 现在还有一个小细节:上面的函数不再公开。 它不能再从我们的对象类之外访问。 这同样适用于负责创建 1-分钟柱线的下一个函数。

inline void CreateBarInReplay(const bool bViewMetrics = false)
                        {
#define def_Rate m_MountBar.Rate[0]

                                static ulong _mdt = 0;
                                int i;
                                
                                if (m_MountBar.bNew = (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)))
                                {
                                        if (bViewMetrics)
                                        {
                                                _mdt = (_mdt > 0 ? GetTickCount64() - _mdt : _mdt);
                                                i = (int) (_mdt / 1000);
                                                Print(TimeToString(m_Ticks.Info[m_ReplayCount].time, TIME_SECONDS), " - Metrica: ", i / 60, ":", i % 60, ".", (_mdt % 1000));
                                                _mdt = GetTickCount64();
                                        }
                                        m_MountBar.memDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                def_Rate.close = m_Ticks.Info[m_ReplayCount].last;
                                def_Rate.open = (m_MountBar.bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (m_MountBar.bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (m_MountBar.bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low);
                                def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                                def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
                                def_Rate.time = m_MountBar.memDT;
                                m_MountBar.bNew = false;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate, 1);
                                m_ReplayCount++;
                                
#undef def_Rate
                        }

它几乎不需要任何解释。 于此,我们一根接一根地创建 1-分钟柱线,然后将它们发送到图表中。 我们还未就跳价操作,也就是说,我们还不能利用一些 MetaTrader 5 资源进行回放/模拟。 我们会在以后实现它。 那现在就不要担心这些。 与此同时,我们还运作所有必要的检查和测量,以便判定是否值得开始构建新柱线。 如果您有意,将来可以舍弃这部分,因为它所做的只是显示一根柱线与前一根柱线之间的时差度量。 这在现在非常有用,因为它可以帮助我们优调创建循环中的延迟值。

这部分 1-分钟柱线讲义到此结束。 现在,系统能在非常合理的时间内呈现它们,至少对于测试过的交易和模拟跳价而言。 接下来,我们将应对与跳价回放相关的另一个问题。 至于跳价本身,此刻我们没有任何其它问题。


查看随机游走图形

到目前为止,这项工作一直充满乐趣,甚至令人享受。 然而,我们现在面临的一些事情或许对某些人来说非常困难,但它确实需要完成:模拟 1-分钟柱线上也许以任何方式出现的所有跳价。 我建议您专注下面介绍的解释。 为了简单起见,我不会在此展示模拟系统的最终版本。 最终版本会在稍后展示。 原因是所有这些都十分复杂,无法一次性展现。

我们真正打算、以及即将创造的是所谓的随机游走。 这种随机游走有一些规则。 与通常的编程不同,在此我们不能允许系统完全随机化。 我们需要创建一些数学规则来尝试引导走势。 不要误会我的意思。 随机游走实际上是一种完全随机性、且不可预测的走势,至少在短期内如此。 但因为我们不会创建完全不可预测的走势,而且因为我们知道它从哪里开始,在何处结束,故该系统不是完全随机的。 不过,我们仍会在柱线中加入一些随机性。

有若干个想法可以用来更容易地创建真正的随机游走。 取决于具体情况,某些方式能比其它的更佳。 经验较少的程序员也许会认为,使用随机数生成器,并执行某种转换,以便将数值限制在特定范围内就足够了。 这种方式,虽然并非完全错误,但确实有一些缺点。 如果您在图表上查看由这种移动造成的数据,您会得到类似于下图的内容:

您也许会认为这张图形(是的,这就是稍后我会给展示给您的图形)看起来根本不像是随机走势。 它看起来更像是一团糟,但实际上它是一种随机走势,是经由时间点之间跳跃达成的。 为了达成这一步,我们将使用通过 MetaTrader 5 平台获得的以下数据行。 记住,每行代表一根 1-分钟的柱线。

这些数据将在附件中提供,以便您可自行分析。 现在我们继续厘清为什么上面的图表与我们所预期的如此出乎意料地不同。 若要明白这一点,您需要知晓它是如何创建的。 一开始,我们就要在服务文件内定义两件事。 我们将暂时使用它们,且在将来的源代码中它们不会出现。

#define def_TEST_SIMULATION
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"

// ... The rest of the code...

这个定义将允许我们创建一个模拟检查,如此我们就可以分析生成的走势图。 另一方面,此文件将包含与 1-分钟柱线内的模拟走势相对应的数据。 稍后我们将看到何时及何处创建此文件。 在服务文件中声明头文件之前,一旦有了这个定义,我们就可以在 MQH 文件中使用此定义。 现在我们转入 C_Replay.Mqh 文件,以便了解我们将如何获取数据。

为了真正捕捉事物,从而了解模拟器如何在 1-分钟柱线内创建走势,我们将调用以下函数。

                bool LoadBarsToTicksReplay(const string szFileNameCSV)
                        {
                                int file;
                                MqlRates rate[1];
                                MqlTick tick[];
                                
                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Converting bars to ticks. Please wait...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                Simulation(rate[0], tick);
#ifdef def_TEST_SIMULATION
        FileClose(file);
        file = FileOpen(def_FILE_OUT_SIMULATION, FILE_ANSI | FILE_WRITE);
        for (long c0 = 0; c0 < m_Ticks.nTicks; c0++)
                FileWriteString(file, StringFormat("%0.f\n", m_Ticks.Info[c0].last));
        FileClose(file);
        ArrayFree(tick);
        
        return false;
#endif
                                        }
                                        FileClose(file);
                                        ArrayFree(tick);

                                        return (!_StopFlag);
                                }
                                
                                return false;
                        }

对于经验不足的程序员来说,一个可能有趣的细节是模拟器是否总是在 1-分钟的柱线内执行相同类型的走势模拟。 事实上,不是。 我们将尝试创造一种方式,令每根柱线都是独一无二的,并具有独特的走势。 不过,如果您希望柱线之间的走势始终相同,或者至少相似,您只需将系统设置为始终从某个值开始模拟即可。 这可在模拟器调用之前添加一个调用,设置一个固定值来做到,如下所示:

// ... Code ...

                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Converting bars to ticks. Please wait...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                srand(5);
                                                Simulation(rate[0], tick);

// ... The rest of the code...

但是,我们回到我们的源代码。 由于我们在服务文件中会有一个定义,因此当执行到达给定点时,我们将在服务中创建一个特殊文件,其包含跳价数组中存在的所有元素,在本例中为虚拟数组。 无论模拟函数内部发生什么,这都会发生。 我们可以使用 EXCEL 以图形方式检查正在发生的事情。 注意,在这种情况下,我们会从服务收到一条出错消息。 如果检查定义已存在,则应忽略该出错消息。 另一件需要知晓的重要事情就是我们将使用 EXCEL,因为它更容易创建图表。 我们甚至可以使用 MetaTrader 5 来做到这一点,但考虑到生成的信息量,结果会令人困惑。 故此,在 EXCEL 中进行可视化更简单。 您可以使用任何其它程序来生成图形。 重要的是什么,那就是您可以生成并可视化由模拟器生成的图形。 如果您不知道在 EXCEL 中如何做到,您可以观看下面的视频,我在那里展示了它。



知道如何创建图表非常重要,因为您需要某种方式来检查走势是否创建正确。 仅仅观察正在创建的柱线走势,并不足以判定它是否真的具有相应的随机性。 程序员圈子里另一个非常普遍的观点是,试图把模拟过程中执行的计算复杂化,也并不能保证我们实际拥有一个真正的随机走势,譬如随机游走。 观看上面的视频。 它很短,然可在接下来的阶段为您提供很多帮助。 我将展示图表,我们需要了解为什么它看起来像这样。 这将允许您执行本地检查。


结束语

为了保持简洁,且不至于令您头晕脑胀,亲爱的读者,本文有关在随机游走模型的实现里我们能看到什么的话题,至此告一段落。 在下一篇文章中,我们将实现一个随机游走模型,其中生成的图形看起来会有所不同。 我们还将看到这种“随机游走”引起的问题,及其背后的思想。 但鉴于我们已拥有的东西,对许多人来说却是全新的、且相当复杂,我不想让事情进一步复杂化,即使附带的代码会告诉您一些我在下一篇文章才会解释的事情。

不要着急。 您应该首先学习并理解今天所解释的内容。 因为如果没有这种知识,以及对其正确的理解,我们将不可能学会不久将来需要完成的事情。 

还有一个细节:为了构建服务,并令回放/模拟系统工作时不生成用于图形分析的数据,请禁用测试指令,如下面的代码所示。 这里我指的是服务文件。

//#define def_TEST_SIMULATION // <<-- Leave this line as it is to be able to use the replay/simulation service....
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"

如果您做不到上述事情,则当您尝试查看额外加入图表的更多数据时,该服务会一直报错。 所以需注意这一点。

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

附加的文件 |
为智能系统制定品质因数 为智能系统制定品质因数
在本文中,我们将见识到如何制定一个品质得分,并由您的智能系统从策略测试器返回。 我们将查看两种著名的计算方法 — Van Tharp 和 Sunny Harris。
MQL5 中的范畴论 (第 9 部分):幺半群(Monoid)— 动作 MQL5 中的范畴论 (第 9 部分):幺半群(Monoid)— 动作
本文是以 MQL5 实现范畴论系列的延续。 在这里,我们继续将“幺半群 — 动作”当为幺半群变换的一种手段,如上一篇文章所涵盖的内容,从而增加了应用。
开发回放系统 — 市场模拟(第 14 部分):模拟器的诞生(IV) 开发回放系统 — 市场模拟(第 14 部分):模拟器的诞生(IV)
在本文中,我们将继续探讨模拟器开发的新阶段。 这次,我们会见到如何有效地创建随机游走类型的走势。 这种类型的走势非常引人入胜,因为它是构成资本市场上所发生一切的基础。 此外,我们将开始了解一些对于进行市场分析至关重要的概念。
神经网络变得轻松(第四十五部分):训练状态探索技能 神经网络变得轻松(第四十五部分):训练状态探索技能
在没有明确奖励函数的情况下,实用的训练技能就是分层强化学习的主要挑战之一。 以前,我们已领略了解决此问题的两种算法。 但环境研究的完整性问题仍然悬而未决。 本文演示了一种不同的技能训练方式,其可取决于系统的当前状态直接使用。