English Русский Español Deutsch 日本語 Português
preview
开发回放系统 — 市场模拟(第 17 部分):跳价和更多跳价(I)

开发回放系统 — 市场模拟(第 17 部分):跳价和更多跳价(I)

MetaTrader 5示例 | 16 二月 2024, 14:00
365 2
Daniel Jose
Daniel Jose

概述

在上一篇文章“开发回放系统 — 市场模拟(第 16 部分):新类系统”中,我们针对 C_Replay 类进行了必要的修改。这些修改旨在简化我们需要完成的几项任务。因此,曾经的 C_Replay 类过于庞大了,经历了一次简化过程之后,其复杂性被分散到其它类中。这令实现新功能,以及针对回放/模拟系统的改进变得更加简单和容易。从本文开始,这些改进将开始出现,并在接下来的七篇文章里展开。

我们要看第一个问题,就是很难有一种只需查看代码就能让任何人都理解的建模方式。知晓这一点,我希望读者关注我们贯穿于这些文章中所研究的解释。如果您足够精心,您将能够遵循该解释,因其确实丰富而复杂。我言此,是因为今天的素材对某些人来说似乎没啥用,而对另一些人来说,却是至关重要的。素材会逐步呈现,如此这般您就能遵循推理。

最大的问题是,之前的所有文章都只关注图表的构建,并且该图表的呈现方式必须令回放/模拟资产的行为与真实市场中发生的实况非常相似。我知道有很多人会用其它一些工具进行交易,例如订单簿。虽然我个人不认为使用这样的工具是好的做法;其他交易者相信,订单簿上发生的事情与交易的内容之间存在一定的相关性。如果每个人都有自己的观点,那也无妨。但尽管如此,许多人在操作中还是会用到一种工具,那就是跳价图表。如果您不知道它是什么,可以看一下图例 01 中的图像。


图例 01

图例 01 - 跳价图表

该图表出现在 MetaTrader 5 平台的多个位置。为了让您对这些位置有所印象,我会提到 MetaTrader 5 标准版本中包含它的几处。例如,市场观察窗口,如图例 01 所示。市场深度(图例 02)和订单系统(图例 03)。

除了这些所在,您还可以使用某种指标来查看相同的信息。可从文章“从头开始开发交易智能系统(第 13 部分):时间和交易(II)”中找到这个示例。对于所有这些系统,我们开发的服务应该能够以相应的方式报告或传输跳价信息,但我们在所有这些图片中看到的并非是准确无误的信息。事实上,我们看到要价(ASK)和出价(BID)的数值都有变化。这是实际显示的内容。 


图例 02

图例 02 – 市场深度中的跳价图表


图例 03

图例 03 – 订单系统中的跳价图表


了解这一事实很重要。我不希望我们的系统中缺少此信息。原因在于提供尽可能接近真实市场的体验。此外,信息应该是正确的,即使系统用户并未实际用到它,也应该在那里。我不希望您认为开发这样的东西是不可能的,即使这并非很简单的任务。老实说,这项任务比看起来要困难得多,您很快就会明白其中的原因。在我们解释过后,一切都会变得更加清晰。我们将看到这项任务有多复杂、有多少小细节,其中一些,应该说,非常奇特。

在此,我们将开始实现这个系统,但会以最简单的方式。首先,我们将令其出现在市场观察窗口中(图例 01)。之后,我们尝试令其出现在其它地方。令其出现在市场观察窗口中将是一个挑战。同时,这也很有趣,因为当我们实现并使用间隔为 1-分钟的走势模拟时,“市场观察”窗口的跳价图表中将显示测试器创建的随机漫游。这一切都非常有趣。

但首事先办。尽管该任务看似很容易构造,但我还没有找到任何链接,可以真正帮助我实现它、令任务更容易、或带我进入下一步。事实上,我在各个地方搜索后,找到的唯一参考资料是 MQL5 文档,甚至该文档也未能把一些细节澄清。在本系列中,我的解释也是我在实现该系统时真正领悟的内容。我向那些对系统可能有不同的理解,或对此事有更多经验的人表示歉意。尽管我全力以赴,但真正令系统起作用的仅有如下展示的一种方式。故此,若有其它真实有效的方式,我对有关的建议或建议持开放态度。

按其复杂度,我们开始实现最疯狂的事情。在将要实现的系统中,在最初的时刻,我们不会用到模拟数据。因此,本文的附件包含 4 种不同资产 2 天的真实数据,如此我们至少有实验的基础。您不必信任我,恰恰与其对比。我希望您自己收集真实的市场数据,并自行在系统中进行测试。以这种方式,我们就能在实现仿真系统之前得出有关实际发生事项的结论。因为在现实中,一切都比表面所见疯狂得多。


实现第一个版本

在第一个版本中,我们将禁用一些资源,因为我不希望您笃信代码是完全正确的。实际上,它在计时器方面存在缺陷。在基于附带的真实数据测试时可以看到这一点。但现在我们可以忽略它,因为目前它不会对过程本身造成任何伤害。只是建立 1-分钟柱线所需的时间与真实市场不太一样。

因此,我们从服务文件中的一处小改动开始:

#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.17"
#property description "Replay-simulation system for MT5."
#property description "It is independent from the Market Replay."
#property description "For details see the article:"
#property link "https://www.mql5.com/en/articles/11106"
//+------------------------------------------------------------------+
#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string            user00 = "Mini Indice.txt";     //"Replay" config file
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;             //Initial timeframe for the chart
//input bool            user02 = false;                 //visual bar construction ( Temporarily blocked )
input bool              user03 = true;                  //Visualize creation metrics
//+------------------------------------------------------------------+
void OnStart()
{
        C_Replay        *pReplay;

        pReplay = new C_Replay(user00);
        if ((*pReplay).ViewReplay(user01))
        {
                Print("Permission received. The replay service can now be used...");
                while ((*pReplay).LoopEventOnTime(false, user03));
        }
        delete pReplay;
}
//+------------------------------------------------------------------+

此行受阻是因为需要更改某些细节,如此才能令柱线构造可视化正常工作。为此原因,在快进时,显示过程将不可见。为了控制这一点,我们在相应的参数里传递 true 或 false。

这是我们需要做的第一件事。现在,我们必须再做一些小的改动。在这一刻,对于那些尚未阅读系列文章前面内容的人来说,事情可能会开始变得有点混乱。如果是这种情况,那么我建议您暂停阅读,从本系列的第一篇文章开始阅读:“开发回放系统 — 市场模拟(第 01 部分):初步实验(I)”,因为了解已经完成的工作将有助于理解现在和将来会发生什么。

参考该建议,我们继续前进。我们现在要做的第一件事,就是修改从文件读取包含真实价格跳价的函数。原始程序如下所见。

inline bool ReadAllsTicks(void)
                        {
#define def_LIMIT (INT_MAX - 2)
                                string   szInfo;
                                MqlTick  tick;
                                MqlRates rate;
                                int      i0;
                                
                                Print("Loading ticks for replay. Please wait...");
                                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                i0 = m_Ticks.nTicks;
                                while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < def_LIMIT) && (!_StopFlag))
                                {
                                        ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
                                        szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
                                        tick.time = StringToTime(StringSubstr(szInfo, 0, 19));
                                        tick.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                        tick.bid = StringToDouble(FileReadString(m_File));
                                        tick.ask = StringToDouble(FileReadString(m_File));
                                        tick.last = StringToDouble(FileReadString(m_File));
                                        tick.volume_real = StringToDouble(FileReadString(m_File));
                                        tick.flags = (uchar)StringToInteger(FileReadString(m_File));
                                        if ((m_Ticks.Info[i0].last == tick.last) && (m_Ticks.Info[i0].time == tick.time) && (m_Ticks.Info[i0].time_msc == tick.time_msc))
                                                m_Ticks.Info[i0].volume_real += tick.volume_real;
                                        else
                                        {
                                                m_Ticks.Info[m_Ticks.nTicks] = tick;
                                                if (tick.volume_real > 0.0)
                                                {
                                                        ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
                                                        m_Ticks.nRate += (BuiderBar1Min(rate, tick) ? 1 : 0);
                                                        m_Ticks.Rate[m_Ticks.nRate] = rate;
                                                        m_Ticks.nTicks++;
                                                }
                                                i0 = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : i0);
                                        }
                                }
                                FileClose(m_File);
                                if (m_Ticks.nTicks == def_LIMIT)
                                {
                                        Print("Too much data in the tick file.\nCannot continue...");
                                        return false;
                                }
                                return (!_StopFlag);
#undef def_LIMIT
                        }

您会注意到,我们已经删除了代码的某些部分。最终代码如下所示 — 这是一段读取真实价格跳价的新函数。

inline bool ReadAllsTicks(const bool ToReplay)
                        {
#define def_LIMIT (INT_MAX - 2)
#define def_Ticks m_Ticks.Info[m_Ticks.nTicks]

                                string   szInfo;
                                MqlRates rate;
                                
                                Print("Loading replay ticks. Please wait...");
                                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < def_LIMIT) && (!_StopFlag))
                                {
                                        ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
                                        szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
                                        def_Ticks.time = StringToTime(StringSubstr(szInfo, 0, 19));
                                        def_Ticks.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                        def_Ticks.bid = StringToDouble(FileReadString(m_File));
                                        def_Ticks.ask = StringToDouble(FileReadString(m_File));
                                        def_Ticks.last = StringToDouble(FileReadString(m_File));
                                        def_Ticks.volume_real = StringToDouble(FileReadString(m_File));
                                        def_Ticks.flags = (uchar)StringToInteger(FileReadString(m_File));
                                        if (def_Ticks.volume_real > 0.0)
                                        {
                                                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
                                                m_Ticks.nRate += (BuiderBar1Min(rate, def_Ticks) ? 1 : 0);
                                                m_Ticks.Rate[m_Ticks.nRate] = rate;
                                        }
                                        m_Ticks.nTicks++;
                                }
                                FileClose(m_File);
                                if (m_Ticks.nTicks == def_LIMIT)
                                {
                                        Print("Too much data in the tick file.\nCannot continue...");
                                        return false;
                                }
                                return (!_StopFlag);
#undef def_Ticks
#undef def_LIMIT
                        }

注意,它不再忽略出价(BID)和要价(ASK)中包含的数值。此外,它也不再累加数值,也就是说,它读取整个数据,并将其存储在内存当中,因为修改不会在函数中生成任何新过程。它们反而进行了简化。我认为您(前提是您已读过本系列之前的文章)不会难以理解到底发生了什么,但事实上这些简会在代码的其它地方产生影响。其中一些项目会受到的影响很强烈,这就是为什么我们必须禁用某些组件,直到整段代码再次可靠地工作。

我们可以进行修改,稳定代码,并立即显示最终版本。但我认为,对于那些正在学习并真正想仔细搞清楚事情如何运作的人来说,逐步展示这些变化将具有重要价值。甚至,还有另一个原因可以解释这些变化。但综上所有,如果您冷静而系统地行动,那么研究再困难的问题也会变得更容易。而更糟糕的是,那些自称是专业交易员的人士,我指的是那些声称自己实际在金融市场上谋生的人,对许多微妙之处的解释都很肤浅。但这样的问题超出了本系列文章的范畴。我们不要偏离我们的主要目标:我们继续一点一点地实现任务,以后一切都会更有意义。特别是若当我们谈论的是另一个市场,这也非常有趣。但我不想破坏这个惊喜。如果您继续阅读这些文章,您就会明白我在说什么。

在首次进行这些修改之后,我们需要再进行一次稍微奇怪、但仍有必要的修改。现在我们有了剔除交易量的数值(BID 和 ASK 值),我们必须从某个点启动系统,我们于该处得到指定的交易量。

class C_ConfigService : protected C_FileTicks
{
        protected:
//+------------------------------------------------------------------+
                datetime m_dtPrevLoading;
                int      m_ReplayCount;
//+------------------------------------------------------------------+
inline void FirstBarNULL(void)
                        {
                                MqlRates rate[1];
                                
                                for(int c0 = 0; m_Ticks.Info[c0].volume_real == 0; c0++)
                                        rate[0].close = m_Ticks.Info[c0].last;
                                rate[0].open = rate[0].high = rate[0].low = rate[0].close;
                                rate[0].tick_volume = 0;
                                rate[0].real_volume = 0;
                                rate[0].time = m_Ticks.Info[0].time - 60;
                                CustomRatesUpdate(def_SymbolReplay, rate);
                                m_ReplayCount = 0;
                        }
//+------------------------------------------------------------------+

//... The rest of the class...

}

这个函数最初是类的私密函数,且无引人注目之处。除它现在变为一个受保护的函数之外,我们还有一个变量。这是回放计数器中使用的变量。该变量仅由该特定函数更改其数值。此循环将致图表最左侧的初始柱线赋予相应的数值。记住:我们现在有 BID 和 ASK 数值,以及价格值。就目前而言,BID 和 ASK 值对我们没有任何意义。

到此刻为止,一切都非常简单明了。现在,我们将转入讨论负责回放的类。这部分包含十分奇怪的东西,乍一看没有太大意义。我们在下一章节中看到这一点。


修改 C_Replay 类

此处的变化以一种很简单的方式开始,变得十分奇怪。我们从下面最简单的更改开始:

                void AdjustPositionToReplay(const bool bViewBuider)
                        {
                                u_Interprocess Info;
                                MqlRates       Rate[def_BarsDiary];
                                int            iPos,   nCount;
                                
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                if (m_ReplayCount == 0)
                                        for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
                                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)) FirstBarNULL(); 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
                        }

该原始函数仅负责创建图表上显示的柱线。我希望您看一下上面的代码,并将其与以下代码进行比较:

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

                                bool bNew;

                                if (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time))
                                {                               
                                        if (bViewMetrics) Metrics();
                                        m_MountBar.memDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                bNew = (def_Rate.tick_volume == 0);
                                def_Rate.close = (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close);
                                def_Rate.open = (bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (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;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate);
                                ViewTick();
                                m_ReplayCount++;
                                
#undef def_Rate
                        }

它们看起来是一样的,但事实上并非如此:存在一些差异。这并不是说第二段代码有两个新的调用。好吧,添加第一个调用只是因为我决定从函数中删除衡量代码。衡量代码如下所示。这正是原始函数中的内容。

inline void Metrics(void)
                        {
                                int i;
                                static ulong _mdt = 0;
                                
                                _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();
                                
                        }

事实上,最大的区别在于系统如何找到柱线收盘价。当没有来自 BID 和 ASK 值的影响时,很容易就知道采用哪个数值作为收盘价。但由于 BID 和 ASK 会干扰数据链,我们需要另一种方法来做到这一点。通过查看一次跳价是否含有任何交易量,我们就能知晓其值是否能用作收盘价。

这是这个新函数的关键点。我们有两个新的调用。我们已经查看过第一个。但在第二种情况下,事情真的变得很奇怪。

第二次调用的代码如下所示:

inline void ViewTick(void)
                        {
                                MqlTick tick[1];

                                tick[0] = m_Ticks.Info[m_ReplayCount];
                                tick[0].time_msc = (m_Ticks.Info[m_ReplayCount].time * 1000) + m_Ticks.Info[m_ReplayCount].time_msc;
                                CustomTicksAdd(def_SymbolReplay, tick);
                        }

这段代码也许看起来很奇怪,但无论如何它能工作。原因可以在 CustomTicksAdd 函数文档中找到。在解释为什么上述函数有效,以及为什么它理应如此之前,我会正如文档所述使用。

以下是文档的内容:

进一步说明

CustomTicksAdd 函数仅适用于在“市场观察”窗口中打开的自定义交易品种。如果品种未在市场观察中选择,则应使用 CustomTicksReplace 添加跳价。

CustomTicksAdd 函数允许投喂报价,就好像这些报价是从经纪商的服务器接收的一样。数据被发送到市场观察窗口,取代直接写入跳价数据库。然后,终端将市场观察中的跳价保存到数据库之中。如果在一次调用中传递大体量数据,则函数行为会发生变化,以便节省资源。如果传输的跳价超过 256 个,则数据将分为两部分。第一个(较大的)部分直接记录到跳价数据库(类似于 CustomTicksReplace)。由最后 128 个跳价组成的第二部分被发送到市场观察,终端从那里将跳价保存到数据库。

MqlTick 结构有两个时间形式的字段:time(以秒为单位的),和 time_msc(以毫秒为单位的跳价时间),它们从 1970 年 1 月 1 日开始计数。在所添加的跳价中,这些字段按以下顺序处理:

  1. 如果 ticks[k].time_msc!=0,我们用它来填充 ticks[k].time 字段,即为跳价设置了 ticks[k].time=ticks[k].time_msc/1000(整数除法)
  2. 如果 ticks[k].time_msc==0 且 ticks[k].time!=0,以毫秒为单位的时间乘以 1000 获得,即 ticks[k].time_msc=ticks[k].time*1000
  3. 如果 ticks[k].time_msc==0 且 ticks[k].time==0,则调用 CustomTicksApply 时,把以毫秒为单位的当前交易服务器时间写入这些字段。

如果 ticks[k].bid、ticks[k].ask、ticks[k].last 或 ticks[k].volume 的值大于零,则相应的标志组合将写入 ticks[k].flags 字段:

  • TICK_FLAG_BID — 跳价改变了 BID 价。
  • TICK_FLAG_ASK — 跳价改变了 ASK 价。
  • TICK_FLAG_LAST — 跳价改变了最后交易价格。
  • TICK_FLAG_VOLUME — 跳价改变了交易量。

如果字段的值小于或等于零,则不会将相应的标志写入 ticks[k].flags 字段。 

标志 TICK_FLAG_BUY 和 TICK_FLAG_SELL 不会添加到自定义品种的历史记录之中。

关于这个注意事项的重要一点是,它对很多人来说可能没有多大意义,但正如此我才能令一切顺利推进。在此,我们指定以下条件,自零时的毫秒值;零时毫秒值,跳价自零时的毫秒值;以及时间毫秒值和零时跳价毫秒值。最大的问题是,当我们取用文件中的真实跳价时,对于绝大多数人来说,这些条件并不是那么清晰,这对我们来说将是一个问题。如果有人尝试从文件获得真实跳价,并将此数据插入到跳价信息之中,他们不会获得所需的结果

出于这个原因,许多人也许会尝试进行这种建模,但他们都失败了,简单来说仅是因为他们未理解文档。但正是利用这个事实(文档中隐含的),我创建了上面的代码。在此代码中,我强制创建第一个条件。其中是自零时起以毫秒为单位的时间值。不过,要牢记,以毫秒为单位表示时间的值也必须包含时间值,因为 MetaTrader 5 将据其执行计算,以便生成时间值。因此,我们需要根据毫秒字段中指定的值来调整参数。

这样,CustomTicksAdd 函数就能够将数据插入到市场观察之中。但不仅如此:当您将这些数据输入系统时,出价、要价、和最后价格线也将出现在正在构建的图表上。换言之,作为能够在市场报价中插入跳价的奖励,我们还得到了图表上的价格线。我们以前没有它,只是缺乏此类功能,。但还不到庆祝的时候,因为系统还没有完成。还有一些东西需要检查、修复和组装。这就是为什么我们使用并提供来自真实跳价的数据来测试回放/模拟系统的这一新阶段。


后记

本文即将结束,因为所需的步骤可能会导致已经提出的素材出现一些混淆。故此,在下一篇文章中,我们将研究如何修复导致当前系统无法正常工作的一些内容。不过,您已能在不快进或快退的情况下使用该系统了。如果您这样做,市场观察中的跳价数据、或价格线信息可能与回放/模拟图表上的当前情况不匹配。

正如您所看到的,我更喜欢迷你指数类型的合约。由此,我希望您在其它资产上测试系统。这将澄清回放/模拟系统将如何与我们放入其中的内容相关。我只想澄清一件事:快进系统仍然存在一些缺陷。因此,我建议您至少暂时避免使用此功能。

在我为您提供的这些测试中,我希望您适当注意您选择的资产的流动性和波动性。检查不同资产的性能。注意,对于在 1-分钟间隔内交易较少的资产,回放/模拟系统似乎存在困难。在某种程度上,现在看到这一点是件好事,因为这部分需要修复。虽然柱线的设计似乎是正确的。我们将尽快进行此修复。亲爱的读者,我希望您在修复此错误之前了解为什么回放/模拟器服务看起来很奇怪。如果您真的想进入编程领域,这种理解是很重要的。不要止步于只创建简单易用的程序。真正的程序员是那些出现问题时加以解决的人,而不是那些一遇到困难就放弃的人。

不过,当观察市场观察窗口中的时间,和由衡量系统提供的输入值时,当时间大于 1 秒时,回放/模拟器服务无法正确同步系统。我们需要修复这个问题,且我们很快就会这样做。同时,研究此段代码,因为它在研究和处理市场观察窗口中的跳价方面非常实用。我们将在下一篇文章中继续。一切都会变得更加有趣。


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

附加的文件 |
最近评论 | 前往讨论 (2)
wrbjym78
wrbjym78 | 18 2月 2024 在 08:58

可以把我的交易策略编程应用到MT5吗

Sky All
Sky All | 19 2月 2024 在 01:33
wrbjym78 #:

可以把我的交易策略编程应用到MT5吗

您好,我是官网版主。


交易策略可以应用到MT5,您需要将要求和细节整理好。

如果您的理论很有趣,且有实操经验,可以在论坛里寻求帮助,感兴趣的大神会给予帮助。


如果需要快速的付费开发,建议您在自由职业者招募专员。以下是相关帖。

https://www.mql5.com/zh/forum/433240

使用格兹尔算法的循环分析 使用格兹尔算法的循环分析
在本文中,我们介绍了在Mql5中实现格兹尔算法(Goertzel algorithm)的代码实用程序,并探讨了将该技术用于分析报价的两种方法,以制定可能的策略。
MQL5中的结构及其数据打印方法 MQL5中的结构及其数据打印方法
在本文中,我们将研究MqlDateTime、MqlTick、MqlRates和MqlBookInfo结构,以及从它们打印数据的方法。为了打印结构的所有字段,有一个标准的ArrayPrint()函数,它以方便的表格格式显示数组中包含的数据以及处理结构的类型。
MQL5 中的范畴论 (第 11 部分):图论 MQL5 中的范畴论 (第 11 部分):图论
本文是以 MQL5 实现范畴论系列的续篇。于此,我们验证在开发交易系统的平仓策略时,图论如何与幺半群和其它数据结构集成。
离散哈特莱变换 离散哈特莱变换
在本文中,我们将探讨频谱分析和信号处理的方法之一——离散哈特莱变换(discrete Hartley transform,DHT)。它可以过滤信号,分析它们的频谱等等。DHT的性能不亚于离散傅立叶变换(discrete Fourier transform,DFT)。然而,与DFT不同的是,DHT只使用实数,这使得它在实践中更方便实现,并且它的应用结果更直观。