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

开发回放系统 — 市场模拟(第 15 部分):模拟器的诞生(V)- 随机游走

MetaTrader 5测试者 | 10 一月 2024, 09:33
783 0
Daniel Jose
Daniel Jose

概述

在本系列的最后几篇文章当中,我们开发了一个市场回放系统,我们见识到如何模拟,或者更准确地说,如何生成特定交易品种的虚构模拟。 目标是令走势尽可能接近真实。 尽管我们已经取得了重大进展,从一个简陋的系统(与策略测试器中所用系统非常相似),到一个非常有趣的模型,然我们尚未能够创建一个适合任何数据集的模型。 它需要是动态的,于此同时要足够稳定,以便能够以最少的编程工作量生成众多可能的场景。

于此,我们将纠正文章 “开发回放系统 — 市场模拟(第 14 部分):模拟器的诞生(IV)” 中的缺陷。 尽管我们已经生成了可运作的随机游走原理,但在处理预定义文件或数据库中的数值时,它还是不完全胜任。 我们的情况很特殊:数据库将始终指示必须使用和遵循的衡量度。 虽然我们之前研究并开发的随机游走系统能生成与真实市场中观察到的非常相似的走势,但它不适合在走势模拟器中使用。 这是因为它不能完全覆盖所有情况须覆盖的范围。 在极稀有情况下,我们能完全覆盖整个范围,从开盘价开始到最高价或最低价,并在达到其中一个界限时完全改变方向,朝另一个极限而去。 结束时,几乎是神奇的,它会找到并停由柱线收盘价判定的价位。

这看似是不可能的,但有时它会发生。 但我们不能指望机遇。 我们同时需要它在允许的范围内尽可能地随机。 它还应该履行其功能 — 完整和全面地覆盖柱线的所有点位。 按这种方式思考,并分析一些抽象的数学概念,我们可以生成一种具有相对吸引力的监督随机游走形式。 至少就事实而言,所有感兴趣的和确定的点位都将触及。

这个思路本身很简单,尽管很不寻常;我不会深入不必要的数学细节,以免令讲解复杂化。 不过,与代码相伴,我们还要研究所用的概念,如此您也能理解它,甚至那些已理解的人能够就我所讲述内容进行细微的修改。


理解思路和概念

如果您一直在跟随本系列的文章,您也许已注意到,我们由实验开始,并尝试创建一个可以涵盖所有价位的系统。 为此,我们采用了一种与策略测试器中所用非常相似的技术。 这种技术基于典型的之字折线走势,其在市场研究圈内非常普遍,并广为人知。 对于那些不熟悉它、或不知道它是什么的人,请参见图例 01,展示其流程表达。

图例 01

图例 01 — 典型之字折线走势

虽然这种建模适用于策略测试器,而对于回放/模拟系统,它并非始终胜任。 原因很简单:这种模拟生成的跳价数量总是明显少于实际数量,但这并不意味着模型无效,只是它不适合用在我们打算开发的系统。 即使我们能找到一种途径,令图例 01 所示的走势生成与实际交易相当的跳价数量,但走势本身的复杂度也不够。 这只是真实走势的简单改编。 我们需要以不同的方式实现这一点。

我们需要寻找一种尽可能产生随机性的方式,但与此同时又不会令代码复杂化。 记住:如果代码的复杂度增长过快,它很快就会变得难以维护和修复。 我们应该始终努力保持事情尽可能简单。 因此,您也许会认为我们可以简单地生成随机价格数值,使之模拟足够高的复杂度,走势即可尽可能接近真实。 不过,我们不能接受任性地生成价格、或跳价数量,我们必须始终遵守数据库。 始终如此。

当您查看数据库时,您可能会注意到它包含了很多有用的信息,事实上,它之所以存在,是因为它是必要的。 图例 02 展示了柱线文件的典型内容。 一些真正感兴趣的数值以高亮显示。


图例 02

图例 02 — 1-分钟柱线文件的典型内容。

如果我们取这些数值,并完全随机生成价格值,我强调,维持在给定的范围内,您就能得到完全随机的走势类型。 与此同时,这一步本身也远非便捷。 在实际市场中,这种走势罕有发生。

为了查看正在生成的内容,我们再次将内容转换为图形格式。 在给定范围内完全随机生成价格的结果如下所示:


图例 03

图片 03 - 完全随机值的结果图形

这样的图形能用一种相当简单、同时非常有效的方法获得。 我们已经见识过如何在 Excel 中执行此操作,那么您也可以像这样生成图形。 这令您仅基于数据库中的数值就能更准确地分析您正在创建的走势。 确定的是,学习如何做到这一点是值得的,因为它可以更迅捷地分析完全随机数值。 如果缺少这种资源,我们将完全迷失在海量数据之中。

知晓如何构建这样的图形,您可以更有效地分析形势。 并且您会明白,图例 03 中显示的图形并不完全适合我们的系统,因为它存在高度的随机性。 然后,我们需要寻找一种途径,至少能压制住这种随机性,以便创造与真实走势更近似的东西。 片刻之后,您会发现一种在老式视频游戏中经常用到的方法,在游戏场景中角色似乎能随机移动,但发生的一切实际上都遵循相当简单的规则。 这种方法也在非常流行和有趣的混搭系统中看到,例如魔方(图例 04);甚至 2D 游戏中,譬如拼图解谜,游戏中的滑块既用于解决问题,亦可令局面混杂(图例 05)。




图例 05

图片 04 - 魔方 - 随机游走走势的一个示例。


图例 05

图片 05 - 15 拼图:最简单的随机游走系统

虽然从表面看,这些游戏看似未用到随机游走,但确实它们这样做了。 尽管有可能采用随机游走方法解决谜题,哪怕比采用更合适的方法花费更长时间,但不会这样解决谜题。 为了将棋子放置在随机出现的位置,有必要使用涉及随机游走的公式。 之后,您尝试使用不同的方法去解谜,如同孩子所为,将零件放回正确之处。 这同样适用于采用随机游走的跳价生成系统。 事实上,随机游走移动系统背后的数学原理在所有情况下基本相同,它不会改变。

真正变化的仅是我们将要用到的导向系统。 试想一个完全没有意义的走势,但这个走势处于 3D 空间中,且不适合进行这种数学归纳。 坦率地讲,这种情况实际上很少发生。 其中许多可用涉及描述随机游走的数学知识来解释随时间发生的变化。 因此,在“价格 x 时间”系统中运用这种数学知识,我们就会得到一个类似于图例 06 所示的图形。


图例 06

图片 06 — 价格 x 时间的随机游走

这种事物遵循与随机走势相同的原理,与市场通常展现的真实走势非常相似。 不过,不要被欺骗。 上图并非真正的资产。 它是由随机数生成器获得的:在先前的价格上增加或减去一个单位,从而生成一个新价格。 以这样方式,我们就明白取决于我们采用的规则,是做加法亦或减法。 您可以简单地说,任何随机生成的奇数都意味着加法,任何偶数都意味着减法。 尽管其为简化,但这条规则可令您得到期待的结果。 不过,可以修改该规则,譬如任何随机生成的数字,若是 5 的倍数则表示加法,否则为减法。 简单地改变这个规则的结局就是一个完全不同的图形。 即使是触发随机生成系统的数值也会在一定程度上改变事态。

虽然图例 06 中的图形适用于完全自由的系统,但我们不能在模拟系统中使用它。 这是因为我们必须遵守数据库中指定的数值,因为其为我们生成的随机游走提供了明确的上、下边界。 故此,在进行调整以确保符合这些界限之后,我们从图例 06 所示的图形转移到图例 07 所示的图形。


图例 07

图片 07 — 限定内的随机游走

生成图例 07 所示图形的系统更适合在模拟器中使用。 在此,我们有一个数据库,它指明了我们可以移动的界限。 事实上,运用等同的简单数学,我们设法创建了一个包含可接受复杂度的系统,同时它也不是完全可预测的,即我们走在正确的轨道上。 请注意,在图形中,从图例 01 所示的图形到图例 07 中的图形,唯一变化的是复杂程度。 或者更确切地说,最后一个图形中呈现的随机性更合适。 尽管图例 01 中所示的系统对于策略测试器来说已经足够了,但图例 07 中生成和呈现的图形所包含的所需内容比测试器要多得多。 然而,对于模拟器来说,这些事情都是致为重要的。

即便复杂程度有所增加,如图例 07 所示,但在模拟器中用它仍嫌不足。 我们唯一可以确定的是使用它时的起点。 在这种情况下,我们所说的是开盘价。 它不能保证数据库中的任何其它特殊点(收盘、最大、最小)会不会受到影响。 这是一个问题,因为要令模拟器真正可行,数据库中的所有特殊点都必须绝对触及。

有基于此,我们需要以某种方式确保图例 07 中的图形变成图例 08 中所示的图形,其中我们已绝对确信这些点会真实地在某个时间点到达。


图例 08

图片 08 — 强制随机游走


这种类型的更改只涉及不多的一些调整。 但如果您仔细观察图表,您能清楚地看到走势在某种程度上是有方向的。 即使在这种情况下,一个没有帮助但定会注意到的就是,我们有一个随机游走,虽然不太自然,但仍是随机的,就像人们所期望的随机走势。 现在我要提请大家注意以下几点:上面讨论的所有图形都基于相同的数据库。 如图例 02 所示。 然而,最好的解决方案如图例 08 所示,其中我们实际上拥有的走势不像图例 03 那样令人困惑、且与数据库相距甚远。 在这种情况下,一切都与构建柱线时可能发生的情况非常相似。

基于所示的概念,我想我已经说服了您,即上一篇文章中所述阶段的代码,并不完全是我们在模拟器中需要的。 这是因为在随机走势发生的期间某些点位可能没有达到。 然而,与其强迫这些点位出现在图形上,您如何才会喜欢让一切更自然的想法?

我们将在下一个主题中研究这一点。


强制随机游走

称其为“强制随机游走”并不意味着我们会对其施加条件。 不过,我们将以某种方式限制走势,令其看起来尽可能的自然,同时仍保持其随机性。 有一个细节:我们将把走势引导到收敛点。 这是您需要访问的点。 如此,我们得到了图例 01 和 07 之间的组合,这非常有趣,不是吗? 但实际上,我们不会使用往常的、如图例 01 所示的创建路径。 我们采取的方式略有不同。

为了真正理解这一点,我们来看看从上一篇文章中找到的随机游走代码。 它如下所示:

inline void Simulation(const MqlRates &rate, MqlTick &tick[])
                        {
#define macroRandomLimits(A, B) (int)(MathMin(A, B) + (((rand() & 32767) / 32767.0) * MathAbs(B - A)))

                                long    il0, max;
                                double  v0, v1;
                                bool    bLowOk, bHighOk;
                                
                                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 3 : def_BarsDiary), def_BarsDiary);
                                m_Ticks.Rate[++m_Ticks.nRate] = rate;
                                max = rate.tick_volume - 1;     
                                v0 = 4.0;
                                v1 = (60000 - v0) / (max + 1.0);
                                for (int c0 = 0; c0 <= max; c0++, v0 += v1)
                                {
                                        tick[c0].last = 0;
                                        tick[c0].flags = 0;
                                        il0 = (long)v0;
                                        tick[c0].time = rate.time + (datetime) (il0 / 1000);
                                        tick[c0].time_msc = il0 % 1000;
                                        tick[c0].volume_real = 1.0;
                                }
                                tick[0].last = rate.open;
                                tick[max].last = rate.close;
                                for (int c0 = (int)(rate.real_volume - rate.tick_volume); c0 > 0; c0--)
                                        tick[macroRandomLimits(0, max)].volume_real += 1.0;                                     
                                bLowOk = bHighOk = false;
                                for (int c0 = 1; c0 < max; c0++)
                                {                               
                                        v0 = tick[c0 - 1].last + (m_PointsPerTick * ((rand() & 1) == 1 ? 1 : -1));
                                        if (v0 <= rate.high)
                                                v0 = tick[c0].last = (v0 >= rate.low ? v0 : tick[c0 - 1].last + m_PointsPerTick);
                                        else
                                                v0 = tick[c0].last = tick[c0 - 1].last - m_PointsPerTick;
                                        bLowOk = (v0 == rate.low ? true : bLowOk);
                                        bHighOk = (v0 == rate.high ? true : bHighOk);
                                }                                       
                                il0 = (long)(max * (0.3));
                                if (!bLowOk) tick[macroRandomLimits(il0, il0 * 2)].last = rate.low;
                                if (!bHighOk) tick[macroRandomLimits(max - il0, max)].last = rate.high;                         
                                for (int c0 = 0; c0 <= max; c0++)
                                {
                                        ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
                                        m_Ticks.Info[m_Ticks.nTicks++] = tick[c0];
                                }
#undef macroRandomLimits
                        }                       

我们实际上需要以某种方式修改上面高亮显示的这段代码。 通过正确的方式,我们可以依据随机游走的原则维护系统,同时控制路径生成过程。 由此,我们就能访问所有提供的点位。 记住,开盘点位始终都会用到。 那好,我们关注其它 3 个点位:最高价、最低价和收盘价。 故此,我们要做的是:首先,我们将创建一个基本函数来替换上面的代码,但我们将以一种更有趣的方式进行。

为了令我们的生活更轻松,我们首先创建一个新函数,该函数负责生成随机游走。 下面是它的第一个版本。

inline long RandomWalk(long pIn, long pOut, const MqlRates &rate, MqlTick &tick[], int iMode)
                        {
                                double vStep, vNext, price, vHigh, vLow;
                                
                                vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / m_PointsPerTick);
                                vHigh = rate.high;
                                vLow = rate.low;
                                for (long c0 = pIn, c1 = 0, c2 = 0; c0 < pOut; c0++, c1++)
                                {
                                        price = tick[c0 - 1].last + (m_PointsPerTick * ((rand() & 1) == 1 ? -1 : 1));
                                        price = tick[c0].last = (price > vHigh ? price - m_PointsPerTick : (price < vLow ? price + m_PointsPerTick : price));
                                        switch (iMode)
                                        {
                                                case 0:
                                                        if (price == rate.close)
                                                                return c0;
                                                case 1:
                                                case 2:
                                                        break;
                                        }
                                        if ((int)floor(vNext) < c1)
                                        {
                                                if ((++c2) <= 3) continue;
                                                vNext += vStep;
                                                if (rate.close > vLow) vLow += m_PointsPerTick); else vHigh -= m_PointsPerTick;
                                        }
                                }
                                
                                return pOut;
                        }

不清楚我在做什么? 不用担心。 我会解释一切。 如前所述,这是第一个版本。 首先,我们计算运行随机游走所需采取的步骤,如此即可到达某个点位。 我们保存实现随机游走范围的最大值和最小值。 现在开始我们的传奇。 我们随机贯穿一定数量的点,在每一步我们都会调整这种形势,不是要阻止随机游走,而是将其引导到无法退出的某个通道中。 它也许会在两侧间振荡,但不会有离开通道的情况。 当我们处于零模式时,当抵达目标点时,程序执行停止。 这对于确保更大的随机化非常重要。 别担心,它以后会变得更清楚。

现在我们需要做一件事:记住,我们经过计算来寻找,穿过通道需要多长? 来到开始关闭通道的时候了。 该通道是逐渐关闭的,且有一个有趣的细节。 我们实际上并不会彻底关闭它。 我们会留下一个小的窄带,价格可在其内跟随随机游走,类似于布林带。 但在结尾处它实际上将位于指明的最终点之处,即收盘价。 事实上,这是通过更改通道边界才会发生。 首先我们闭合底部,当我们到达出口点时,我们开始从顶部闭合。

最后一个点位实际上可经由一条或多条途径访问,但如果无法访问,那么它非常接近理想态。 但这个函数用在哪里适合呢? 它将取代旧的随机游走方法。 我们将结合图例 01 和图例 07 中发生的情况。 结果是与图例 08 非常相似的图像。

此处是新的模拟函数。

inline void Simulation(const MqlRates &rate, MqlTick &tick[])
                        {
#define macroRandomLimits(A, B) (int)(MathMin(A, B) + (((rand() & 32767) / 32767.0) * MathAbs(B - A)))

                                long    il0, max, i0, i1;
                                bool    b1 = ((rand() & 1) == 1);
                                double  v0, v1;
                                MqlRates rLocal;
                                
                                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 3 : def_BarsDiary), def_BarsDiary);
                                m_Ticks.Rate[++m_Ticks.nRate] = rate;
                                max = rate.tick_volume - 1;     
                                v0 = 4.0;
                                v1 = (60000 - v0) / (max + 1.0);
                                for (int c0 = 0; c0 <= max; c0++, v0 += v1)
                                {
                                        tick[c0].last = 0;
                                        tick[c0].flags = 0;
                                        il0 = (long)v0;
                                        tick[c0].time = rate.time + (datetime) (il0 / 1000);
                                        tick[c0].time_msc = il0 % 1000;
                                        tick[c0].volume_real = 1.0;
                                }
                                tick[0].last = rate.open;
                                tick[max].last = rate.close;
                                for (int c0 = (int)(rate.real_volume - rate.tick_volume); c0 > 0; c0--)
                                        tick[macroRandomLimits(0, max)].volume_real += 1.0;                                     
                                i0 = (long)(MathMin(max / 3.0, max * 0.2));
                                i1 = max - i0;
                                rLocal = rate;  
                                rLocal.open = rate.open;
                                rLocal.close = (b1 ? rate.high : rate.low);
                                i0 = RandomWalk(1, i0, rLocal, tick, 0);
                                rLocal.open = tick[i0].last;
                                rLocal.close = (b1 ? rate.low : rate.high);
                                RandomWalk(i0, i1, rLocal, tick, 1);
                                rLocal.open = tick[i1].last;
                                rLocal.close = rate.close;
                                RandomWalk(i1, max, rLocal, tick, 2);
                                for (int c0 = 0; c0 <= max; c0++)
                                {
                                        ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
                                        m_Ticks.Info[m_Ticks.nTicks++] = tick[c0];
                                }
#undef macroRandomLimits
                        }                       

高亮的代码段替换了旧方法。 现在出现了一个重要的问题:高亮部分有什么作用? 您能仅通过查看它的代码来回答吗?

如果您能理解,太棒了,我由衷地祝贺! 如果不能,那我们就来看看这段代码。

                                i0 = (long)(MathMin(max / 3.0, max * 0.2));
                                i1 = max - i0;
                                rLocal = rate;  
                                rLocal.open = rate.open;
                                rLocal.close = (b1 ? rate.high : rate.low);
                                i0 = RandomWalk(1, i0, rLocal, tick, 0);
                                rLocal.open = tick[i0].last;
                                rLocal.close = (b1 ? rate.low : rate.high);
                                RandomWalk(i0, i1, rLocal, tick, 1);
                                rLocal.open = tick[i1].last;
                                rLocal.close = rate.close;
                                RandomWalk(i1, max, rLocal, tick, 2);

正在实现的是一个大型之字折线,但如何做到🤔? 我看不出来🥺! 我们将一步一步来。 首先,我们计算限定点,如此之字折线的第一部分就能结束。 一旦完成,我们立即判定第三个之字折线段的尺寸。 一个细节:与第三段不同,第一段没有固定的尺寸。 它能够随时结束。 该时刻由随机游走程序判定。 但鉴于在第一阶段结束时我们就必须开始第二阶段,故采用随机游走函数返回的数值作为起点。 在此,我们有一个新的调用来构建第二段随机游走。 听起来令人困惑?

别担心,我们会到那里的。 现在我们设置随机游走第一段的边界、进入点和退出点。 我们调用随机游走创建函数,该函数在到达端点、或尽可能接近端点时返回。 然后,我们再次调整下一阶段的进入点和退出点。 调用该函数以创建随机游走,如此从一端移动到另一端。 但即使达到这些界限,该函数也只会在访问指定数量的位置后返回。 此后,我们判定第三段,也就是最后一次随机游走的入口点和出口点。 我们发出一个调用,将构建最后一段。 换言之,我们得到一个大型的之字折线。 取代价格从一个点移动到另一个点,它会在我们定义的界限内波动 😁。

当我们运行上述系统时,我们会得到一个与图例 08 中所示图形非常相似的图形。 考虑到上述方法的简单性,这非常好,但所有这些我们都能加以改进。 如果您仔细观察,您会发现图例 08 中所示的图形几处看似撞墙了。 说来也怪,有时在真实市场里也会发生。 特别是,当其中一方(买方或卖方)不允许价格超过某个点位时,就会在订单簿中发生著名的订单大战。

但如果我们打算在系统中得到更自然的走势,我们就不仅要改变模拟函数本身,而且还有执行函数,它负责创建随机游走。 不过,在我们的完整故事里有一处细节。 我们不应该尝试创建一种新的随机游走方法。 您只需要创建一种方式来打开和关闭边界,这样走势就会自行发生。 如果操作得当,最终结果将更加自然,就好像在任何时间点,我们都没有引导走势一样。

执行此操作的第一个尝试是添加边界触碰检查。 一旦发生这种情况,神奇的事情就会开始。 所以,这是新的随机游走方法 — 这并非一个新方法,只是对原始方法的改编。 记住:我只解释新部分 😉。

inline long RandomWalk(long pIn, long pOut, const MqlRates &rate, MqlTick &tick[], int iMode)
                        {
                                double vStep, vNext, price, vHigh, vLow;
                                
    char  i0 = 0;
                                
                                vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / m_PointsPerTick);
                                vHigh = rate.high;
                                vLow = rate.low;
                                for (long c0 = pIn, c1 = 0, c2 = 0; c0 < pOut; c0++, c1++)
                                {
                                        price = tick[c0 - 1].last + (m_PointsPerTick * ((rand() & 1) == 1 ? -1 : 1));
                                        price = tick[c0].last = (price > vHigh ? price - m_PointsPerTick : (price < vLow ? price + m_PointsPerTick : price));
                                        switch (iMode)
                                        {
                                                case 0:
                                                        if (price == rate.close)
                                                                return c0;
                                                        break;
                                                case 1:
                                                        i0 |= (price == rate.high ? 0x01 : 0);
                                                        i0 |= (price == rate.low ? 0x02 : 0);
                                                        vHigh = (i0 == 3 ? rate.high : vHigh);
                                                        vLow = (i0 ==3 ? rate.low : vLow);
                                                        break;
                                                case 2:
                                                        break;
                                        }
                                        if ((int)floor(vNext) < c1)
                                        {
                                                if ((++c2) <= 3) continue;
                                                vNext += vStep;
                                                if (rate.close > vLow) vLow = (i0 == 3 ? vLow : vLow + m_PointsPerTick); else vHigh = (i0 == 3 ? vHigh : vHigh - m_PointsPerTick);
                                        }
                                }
                                
                                return pOut;
                        }

现在我们有了一个全新的状况。 因此,我们必须避免超越旧的限定。 但有一个非常微妙的点。 我们有一个从零开始的新变量。 事实上,它就像一个布尔融合。 这个变量的作用是不可思议的。 所以您必须非常细心地理解这一点。 该点的定义非常明确、非常有趣。 当我们处于之字折线的第二段时,在某个时候价格会触及高点,然后是低点。 之后,在每次触碰时,我们都会往变量里写入一个特定的值。 如果我们不将该变量视为比特单元,而是将其视为一个整体,会发生什么? 我们将得到一个非常特殊的值。 此值可以是 0、1、2、或 3。

但是等一下,怎么能是 “3” 🤔? 关键是,当触及高点时,我们执行布尔运算“或(OR)”,这样我们令其最低有效位为 true。 当触及低位时,我们执行相同的操作,但现在是第二个有效位。 也就是说,如果第一位已经设置为 true,然后我们将第二位设置为 true,则数值立i即从 1 变为 3,因此计数值上升至 3。

理解这一事实很重要,因为我们需要检查该数值。 替代检查 2 个不同的布尔数字,我们只需检查一个数值,就能代表两者。 这就是系统逻辑 😁。 如此,如果该值为 3,则我们所用的边界得到一个扩展。 或者,用更易于理解的术语来说,如果我们不执行此检查,边界应闭合,随机游走将压缩到仅 3 个可能的走势位置。 但通过这样做,我们令走势再次自然发展。 既然已经达到界限,那么进一步限制走势就没有意义了。

“激增”在这些点上发生。 然后,当函数尝试减少限定时,它就不再能够这样做。 现在,随机游走将沿着整个边界进行,令其更加自然。 如果我们现在使用与本文开头相同的数据库运行系统,我们将得到一个与图例 09 中所示图形非常相似的图形 😊。

图例 09

图例 09 — 带有“激增”的随机游走


这张图看起来比图例 08 中的图形要自然得多。 然而,即使是最后这张看起来很完美的图形,仍然有一处不那么自然。 它在最后一段里。 注意图形的末尾:给定走势的可能限定,它看起来有些勉强。 这件事应该加以解决,这就是我们现在要做的。

为了解决这个问题,我们需要在负责随机游走的函数中添加几行。 以下是该系统的最终版本:

inline long RandomWalk(long pIn, long pOut, const MqlRates &rate, MqlTick &tick[], int iMode)
                        {
                                double vStep, vNext, price, vHigh, vLow;
                                char i0 = 0;
                                
                                vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / m_PointsPerTick);
                                vHigh = rate.high;
                                vLow = rate.low;
                                for (long c0 = pIn, c1 = 0, c2 = 0; c0 < pOut; c0++, c1++)
                                {
                                        price = tick[c0 - 1].last + (m_PointsPerTick * ((rand() & 1) == 1 ? -1 : 1));
                                        price = tick[c0].last = (price > vHigh ? price - m_PointsPerTick : (price < vLow ? price + m_PointsPerTick : price));
                                        switch (iMode)
                                        {
                                                case 0:
                                                        if (price == rate.close)
                                                                return c0;
                                                        break;
                                                case 1:
                                                        i0 |= (price == rate.high ? 0x01 : 0);
                                                        i0 |= (price == rate.low ? 0x02 : 0);
                                                        vHigh = (i0 == 3 ? rate.high : vHigh);
                                                        vLow = (i0 ==3 ? rate.low : vLow);
                                                        break;
                                                case 2:
                                                        break;
                                        }
                                        if ((int)floor(vNext) < c1)
                                        {
                                                if ((++c2) <= 3) continue;
                                                vNext += vStep;
                                                if (iMode == 2)
                                                {
                                                        if ((c2 & 1) == 1)
                                                        {
                                                                if (rate.close > vLow) vLow += m_PointsPerTick; else vHigh -= m_PointsPerTick;
                                                        }else
                                                        {
                                                                if (rate.close < vHigh) vHigh -= m_PointsPerTick; else vLow += m_PointsPerTick;
                                                        }
                                                } else
                                                {
                                                        if (rate.close > vLow) vLow = (i0 == 3 ? vLow : vLow + m_PointsPerTick); else vHigh = (i0 == 3 ? vHigh : vHigh - m_PointsPerTick);
                                                }
                                        }
                                }
                                
                                return pOut;
                        }

看似有点奇怪,但请看以下内容:当我们执行第一段和第二段时,实际将要执行的代码如下所示。 当我们进入第三段时,我们的图例会略有不同。

为了得到一个图例,即图形分析中所称的对称三角形,有必要逐渐减少两条侧边。 但重点是要明白我们也许得到一个对称三角形非对称三角形。 根据起点在极值点之间是否等距,我们将构造一个对称三角形。 如果闭合点或退出点非常接近其中一个边界,则三角形将是不对称的。 为此,我们将交替使用加法和减法。 特别是在这些点上会这样做。 切换数据点的方法是通过检查当前降低值来实现的。

因此,我们将得到一个与图例 10 所示图形非常相似的最终图形。 它如下所示:

图例 10

图例 10 — 1-分钟柱线的随机游走图形


它看起来比我们以前见过的任何其他图表都要自然得多。 因此,我们完结了随机游走的基础部分,我们可以转进到创建回放/模拟系统的下一阶段。

由此,您可以了解系统当前如何运行。

附件包含当前开发状态的完整代码。


结束语

您真的无需使用复杂的数学知识来解释或创造东西。 当最初轮子被发明时,并未运用数学知识,基于相应 PI 值判定其半径应该是多少。 我们所做的只是实现所需的走势。 在这篇文章中,我展示了儿童游戏中所用的简单数学,也可以解释和表述许多人认为不可能的走势。 即使解决方案很简单,他们仍在使用花哨的数学。 始终尽力让事情变得简单。


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

附加的文件 |
开发回放系统 — 市场模拟(第 16 部分):新的类系统 开发回放系统 — 市场模拟(第 16 部分):新的类系统
我们需要更好地组织我们的工作。 代码正在快速增长,如果现在不做,那么以后就变得更不可能了。 我们分而治之。 MQL5 支持类,可协助实现此任务,但为此,我们需要对类有一定的了解。 大概最让初学者困惑的是继承。 在本文中,我们将看到如何以实用和简单的方式来运用这些机制。
MQL5 中的范畴论 (第 10 部分):幺半群组 MQL5 中的范畴论 (第 10 部分):幺半群组
本文是以 MQL5 实现范畴论系列的延续。 在此,我们将”幺半群-组“视为常规化幺半群集的一种手段,令它们在更广泛的幺半群集和数据类型中更具可比性。
GUI:利用 MQL 创建您自己的图形库的提示和技巧 GUI:利用 MQL 创建您自己的图形库的提示和技巧
我们将通览 GUI 函数库的基础知识,以便您能理解它们如何工作,甚至着手打造您自己的函数库。
利用 MQL5 的交互式 GUI 改进您的交易图表(第一部分):可移动 GUI(I) 利用 MQL5 的交互式 GUI 改进您的交易图表(第一部分):可移动 GUI(I)
凭借我们的利用 MQL5 创建可移动 GUI 的综合指南,令您的交易策略或实用程序焕发出呈现动态数据的力量。 深入了解图表事件的核心概念,并学习如何在同一图表上设计和实现简单、多个可移动的 GUI。 本文还探讨了往 GUI 上添加元素的过程,从而增强其功能和美观性。