English Русский 中文 Español Deutsch Português
preview
リプレイシステムの開発 - 市場シミュレーション(第15回):シミュレーターの誕生(V) - ランダムウォーク

リプレイシステムの開発 - 市場シミュレーション(第15回):シミュレーターの誕生(V) - ランダムウォーク

MetaTrader 5テスター | 8 1月 2024, 15:49
123 0
Daniel Jose
Daniel Jose

はじめに

市場リプレイシステムを開発するこの連載の最後の数回では、シミュレーションの方法、より正確には、特定の銘柄の一種の仮想シミュレーションを生成する方法について見てきました。可能な限り実際の動きに近づけることが目標です。ストラテジーテスターで使用されているものと非常によく似たシンプルなシステムから、非常に興味深いモデルへと大きく進歩ましたが、どのようなデータセットにも適合できるモデルを作成することはまだできていません。動的であると同時に、最小限のプログラミング作業で多くの可能なシナリオを生成できる安定性が必要です。

ここでは、「リプレイシステムの開発 - 市場シミュレーション(第14回):シミュレーターの誕生(IV)」稿にあった欠陥を修正します。ランダムウォークの動作原理を生み出しましたが、あらかじめ定義されたファイルやデータベースの値を扱う場合には、完全には適切ではありません。私たちのケースは特殊で、データベースは常に、使用し、従わなければならない測定基準を示します。以前検討し、開発したランダムウォークシステムは、実際の市場で観察される動きに非常に近い動きを生成することができますが、運動シミュレーターでの使用には適していません。すべてのケースでカバーしなければならない範囲を完全にカバーすることができないからです。非常にまれなケースとして、始値から高値または安値まで、レンジ全体を完全にカバーすることがあります。限界の一方に到達し、もう一方の限界に達すると方向が完全に変わります。最終的には、ほとんど魔法にかかったように、バーの終値と決定された価格で止まります。

不可能に思えるかもしれませんが、時にはそういうこともあります。しかし、偶然に頼ることはできません。可能な限りランダムであることと、許容範囲内であることが同時に必要なのです。また、バーのすべてのポイントを完全かつ包括的にカバーするという機能も果たすべきです。このように考え、いくつかの抽象的な数学的概念を分析することで、比較的魅力的な形の教師付きランダムウォークを生成することができます。少なくとも、すべての見どころや特定のポイントに到達することに関しては。

説明を複雑にしないために、不必要な数学的詳細には触れないことにします。しかし、コードと一緒に、これから使われる概念についても考えていくので、皆さんにも理解していただけると思います。理解していただける方は、私が紹介するもののちょっとしたバリエーションを開発することもできるでしょう。


アイデアと概念

この連載や記事をご覧になっている方ならお気づきかもしれませんが、私たちはまず、あらゆる価格帯をカバーできるシステムを作ろうと試行錯誤しましたた。そのために、ストラテジーテスターで使われているのと非常によく似たテクニックを使いました。このテクニックは、典型的なジグザグの動きをベースにしており、市場を研究する人々の間では非常に一般的で広く知られています。馴染みのない方、何のことかわからない方のために、その概略図を図01に示します。

図01

図01:典型的なジグザグの動き

このようなモデリングはストラテジーテスターには適していますが、リプレイ/シミュレーションシステムには必ずしも適切ではありません。理由は簡単で、このようなシミュレーションによって生成される注文数は、常に実際の注文数よりも大幅に少ないからです。しかし、これはモデルが無効であることを意味するのではなく、私たちが開発したいようなシステムに使用するには適していないことを意味するだけです。仮に、図01のような動きで、本当に取引されたチケットと同じ数のチケットを生成する方法が見つかったとしても、動き自体は十分に複雑ではないでしょう。これは単に実際の動きの脚色でしょう。これを違った形で実施する必要があります。

できるだけランダムで、同時にコードを複雑にしない方法を見つける必要があります。覚えておいていただきたいのは、コードの複雑さが急速に増しすぎると、すぐにメンテナンスや修正が不可能になるということです。常に、物事を可能な限りシンプルに保つよう努めなければなりません。そこで、単純にランダムな価格値を生成して、動きが限りなく現実に近くなるような、十分に複雑なレベルのシミュレーションができると考えるかもしれません。ただし、すべての値段と数を受け入れる余裕はありません。常にデータベースを尊重しなければなりません。常にです。

データベースを見ると、かなり多くの有用な情報が含まれていることに気づくかもしれません。図02は典型的なバーファイルの内容です。その中で、本当に興味深いいくつかの価値が強調されています。


図02

図02:1分足ファイルの典型的な内容

これらの値から、完全にランダムな価格値を生成して、与えられた範囲内に収まるようにすると(これを強調します)、完全にランダムなタイプの動きが得られます。同時に、このステップ自体も便利とは言い難いです。実際の市場でこのような動きが起こることはめったにありません。

何が生成されるかを見るために、もう一度グラフ形式に変換してみましょう。ある範囲内の価格を完全にランダムに生成した結果は次のようになります。


図03

図03:完全ランダム値の結果のグラフ

このようなグラフは、かなり簡単で、同時に非常に効果的な方法で得ることができます。エクセルでこれをおこなう方法はすでに見たので、このようなグラフを作成することができます。これにより、データベースの値のみに基づいて、作成中の動きをより正確に分析できるようになります。実際、完全にランダムな値をより速く分析できるようになるため、この方法を学ぶ価値はあります。このリソースがなければ、膨大なデータの中で完全に迷子になってしまいます。

このようなグラフの作り方を知っていれば、より効率的に状況を分析することができます。図03のグラフは、ランダム性が高いため、私たちのシステムには全く適していません。実際の動きに近いものを作るために、少なくともこのランダム性を抑える方法を探す必要があります。しばらくすると、昔のビデオゲームでよく使われた、キャラクターがゲームのシナリオの中をランダムに動いているように見えて、実はすべてがかなり単純なルールに従って起こっている、という方法を発見するでしょう。この方法は、ルービックキューブ(図04)のような非常に人気があり楽しい組み合わせゲームや、15パズル(図05)のような2Dゲームでも見ることができます。このゲームでは、問題を解決するためとゲーム内で駒を混ぜるためにスライディング駒が使用されます(図05)。




図05

図04:ルービックキューブ:ランダムウォーク運動の一例


図05

図05:15パズル:最も単純なランダムウォークシステム

これらのゲームは一見、ランダムウォークを使っていないように見えるが、実は使っています。しかし、ランダムウォーク法を使えば、より適切な方法を使うよりはるかに時間がかかるとはいえ、解くことは可能でしょう。ランダムに見える位置にピースを置くには、ランダムウォークを含む定式化を使う必要があります。その後、子供がするように、ピースを正しい場所に戻しながら、別の方法で解こうとします。ランダムウォークを使ったチケット生成システムも同様です。実際、ランダムウォークモーションシステムの背後にある数学は、基本的にどのケースでも同じであり、変わることはありません。

実際には、使用する方向システムだけが変わります。まったく意味のない動きを想像してみてください。しかし、この動きは3次元空間にあり、そのような数学には当てはまりません。正直なところ、実際にそうなることはほとんどありません。これらの多くにおいて、ランダムウォークを記述する数学は、時間の経過とともに生じる変動を説明することができます。したがって、この数学を「価格×時間」システムで使用すると、図06のようなグラフが得られます。


図06

図06:価格×時間のランダムウォーク

確率的な動きと同じ原則に従ったこの種のものは、市場が通常示す実際の動きに非常によく似ています。しかし、だまされてはいけません。上のグラフは実物資産ではありません。これは乱数発生器を用いて得られるもので、前回の価格に1単位を足したり引いたりすることで、新しい価格が発生します。足し算か引き算かを理解する方法は、使用するルールによって異なります。単に、ランダムに生成された奇数は足し算を意味し、偶数は引き算を意味すると言うことができます。シンプルでありながら、このルールによって望ましい結果を得ることができます。しかし、このルールは、ランダムに生成された5の倍数の数字が足し算を表し、そうでなければ引き算を表すように修正することができます。このルールを変えるだけで、グラフはまったく違うものになります。ランダム生成システムのトリガーとなる値によっても、状況は多少変わります。

図06のグラフは完全に自由なシステムには適していますが、シミュレーションシステムには使えません。これは、データベースで指定された値を尊重しなければならないからであり、これによってランダムウォークを生成するための上限と下限が明示されるからです。こうして、これらの制限を遵守するための補正をおこなった後、図06のグラフから図07のグラフに移行します。


図07

図07:制限内でのランダムウォーク

図07のグラフを生成するシステムは、シミュレーターでの使用にはるかに適しています。ここには、私たちが動ける限界を示すデータベースがあります。実際、同じように単純な数学を使って、許容できるレベルの複雑さを含むシステムを作ることに成功しています。同時にそれは完全に予測可能ではありません。つまり、正しい軌道に乗っています。図01のグラフから図07のグラフまで、変化したのは複雑さの度合いだけであることに注意してください。というより、最後のグラフに見られるランダム性の方が適切です。図01に示したシステムはストラテジーテスターには十分なものですが、図07に生成され提示されたグラフには、テスターに必要なものよりもはるかに多くのものが含まれています。しかし、シミュレーターにとっては、これらのことが最も重要なのです。

図07に見られるように、このように複雑さが増したとしても、シミュレーターで使うにはまだ十分ではありません。それを使うときに唯一確実なのは、スタート地点です。この場合、始値です。データベースで指定された他のポイント(終値、最大値、最小値)が影響を受けることを保証することはできません。というのも、シミュレーターが真に実行可能であるためには、データベースで指定されたすべてのポイントが絶対確実な形でタッチされなければならないからです。

これに基づいて、図07のグラフが図08のようなグラフになり、これらのポイントがある時点で実際に到達するという絶対的な確信が持てるようにする必要があります。


図08

図08:強制ランダムウォーク


この種の変更には、わずかな調整しか伴いません。しかし、チャートをよく見ると、その動きが何らかの形で指示されていることがはっきりとわかります。この場合であっても、確率的な動きに期待されるような、自然ではないがランダムなウォークがあることに気づかずにはいられません。さて、次の点に注目してください。上述したグラフはすべて同じデータベースを使用しています。それを図02に示します。しかし、最良の解決策は図08に示すもので、実際には図03ほど混乱せず、データベースからも離れていない動きがあります。この場合、すべてはバーを作るときに起こりうることとよく似ています。

示された概念に基づいて、前回の記事で説明した段階のコードが、シミュレーターで必要なものとは正反対であることを納得してもらえたと思います。これは、ランダム移動が発生した期間中に到達していないポイントがある可能性があるためです。しかし、これらのポイントを無理やりグラフに表示させるのではなく、すべてをより自然にするというアイデアはどうでしょうか。

次のトピックでは、この点について考えてみたいとおもいます。


強制ランダムウォーク

強制ランダムウォークと言っても、それに条件を課すわけではありません。ただし、ランダム性を維持しながらも、できるだけ自然に見えるように、動きを一定の方法で制限します。詳細が 1 つあります。動きを収束点に向けるということです。そして、ここが訪れるべきポイントです。図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;
                        }

何をやっているのかわからなくても、ご安心ください。すべて説明します。すでに述べたように、これは最初のバージョンです。まず、ランダムウォークがある地点に到達するのに必要なステップを計算します。ランダムウォークを実行する範囲の最大値と最小値を格納します。ここで、私たちの武勇伝が始まります。一定の数のポイントをランダムに歩き、各ステップで状況を修正し、ランダムウォークをブロックするのではなく、出口がない特定のチャンネルに誘導します。左右に揺れることはあっても、チャンネルから離れることはありません。ゼロモードの場合、プログラムの実行は目標点に達した時点で停止します。これは、無作為化をより確実にするために重要です。心配無用です。後ではっきりします。

ここで、やるべきことが1つあります。チャンネルが削減されるまでにどれくらいの時間がかかるかを計算したことを思い出してください。チャンネルを閉じる時です。チャンネルは徐々に閉じられていきますが、興味深い詳細があります。完全には閉じないということです。ボリンジャーバンドと同じように、価格がランダムウォークに従うことができる小さな狭いバンドを残します。しかし、最終的には、最終地点として示された地点、つまり終値になります。実際、これはチャンネルの境界を変えることで起こります。まず下部を閉じ、出口に到達したら上部から閉じ始めます。

いずれにせよ、最後のポイントは実質的にアクセス可能ですが、もしアクセスできないのであれば、理想に非常に近いものになるでしょう。しかし、この機能はどこに合うのでしょうか。これは、従来のランダムウォーク法に取って代わります。図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);

実施されているのは大きなジグザグですが、どうやってでしょうか🤔。分かりません🥺。一歩一歩進んでいきます。まず、ジグザグの最初のセクションが終了する限界点を計算します。これが終わるとすぐに、3つ目のジグザグセグメントのサイズを決定します。1つ細かいことを言えば、1つ目セグメントは3つ目セグメントと違ってサイズが決まっていません。いつ終わるかわかりません。この瞬間はランダムウォークプログラムによって決定されます。しかし、第1ステージが終了した時点で第2ステージを開始しなければならないので、ランダムウォーク関数が返す値がスタート地点として使われます。ここで、2つ目のランダムウォークを構築するための新しい呼び出しがあります。わかりにくいと思われるかもしれません。

心配無用です。ここで、ランダムウォークの最初のセクションの境界、入口と出口を設定します。ランダムウォーク作成関数を呼び、終点に到達するか、可能な限り終点に近づいた時点で戻ります。そして、次のステージの入口と出口を再び調整します。関数を呼び出してランダムウォークを作成し、端から端まで移動するようにします。しかし、これらの制限に達したとしても、関数は指定された数のポジションを回ってからしか戻りません。この後、3回目(最後)のランダムウォークの入口と出口を決定します。最後のセグメントを作るために呼び出します。つまり、大きなジグザグがあります。価格はある点から別の点へと動くのではなく、定義した範囲内で変動します。😁

上記のシステムを実行すると、図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;
                        }

これで状況がまったく新しくなります。したがって、古い限界を超えることは避けなければなりません。ただし、非常に微妙な点があります。ゼロから始まる新しい変数ができています。実際、ブーリアンアンサンブルのようなものです。この変数がすることは、信じられないようなことです。理解するためには細心の注意を払わなければなりません。この点は非常に明確で、非常に興味深くなります。ジグザグの2つ目のセグメントにいるとき、ある時点で価格は高値に触れてから安値に触れます。そして、それぞれのタッチで変数に特定の値を書き込みます。変数をビット単位ではなく、1つの全体として考えたらどうなるでしょうか。非常に具体的な値を得るでしょう。0、1、2、3のいずれかです。

でもちょっと待ってください。どうして3つもあるのでしょうか。🤔ポイントは、上限がタッチされたとき、最下位ビットを真にするブーリアン演算ORを実行することです。下限がタッチされたら、同じ操作をおこないますが、今度は2番目の下位ビットに対しておこないます。つまり、最初のビットがすでに真にセットされていて、次に2番目のビットを真にセットすると、すぐに1の値から3の値になり、カウントは3の値まで上がります。

私たちが確認するのはこの値なので、この事実を理解することは重要です。2つの異なるブール値を確認する代わりに、両方を表す1つの値を確認します。これがシステムロジックです😁。つまり、値が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;
                        }

少し奇妙に見えますが、次のようになります。最初のセグメントと2つ目のセグメントを実行すると、実際に実行されるコードは次のようになります。3つ目のセグメントに移ると、少し違った姿になります。

図式分析で「対称三角形」と呼ばれる図形を得るには、両辺を徐々に縮小していく必要があります。しかし、対称三角形もあれば非対称三角形もあるということを理解することは非常に重要です。始点が極点間の等距離にあるかどうかによって、対称三角形ができます。終値または出口が境界のひとつに非常に近い場合、三角形は非対称になります。そのために、足し算と引き算を交互に使います。これは、特にこのようなポイントでおこなわれます。データポイントを切り替える方法は、現在の減少値を確認することで実行されます。

こうして、図10に示すグラフによく似た最終的なグラフが得られます。以下に示します。

図10

図10:1分バーのランダムウォークグラフ


これまでのどのチャートよりもずっと自然に見えます。こうしてランダムウォークベースは終了し、リプレイ/シミュレーションシステムを作る次の段階に進むことができます。

システムが現在どうなっているかを理解することができます。

添付ファイルには、現在の開発段階のコード一式が含まれています。


結論

物事を説明したり創造したりするのに、複雑な数学を使う必要はありません。車輪が発明されたとき、PIに対応する値に基づいて半径を決定するために数学は使われませんでした。私たちがやったのは、望ましい動きを実現することだけでした。この記事では、子供向けのゲームで使われる単純な数学が、多くの人が不可能だと考える動きを説明し、表現できることを示しました。これらは、解決策は簡単なのに、高度な数学を使い続けています。物事は常にシンプルにするべきです。


MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/11071

添付されたファイル |
価格変動モデルとその主な規定(第3回):証券取引所の投機の最適なパラメータを計算する 価格変動モデルとその主な規定(第3回):証券取引所の投機の最適なパラメータを計算する
確率論に基づき著者が開発した工学的アプローチの枠組みの中で、利益を生むポジションを建てるための条件を見つけ、最適な(利益を最大化する)利食いと損切りの値を計算します。
エキスパートアドバイザーのQ値の開発 エキスパートアドバイザーのQ値の開発
この記事では、エキスパートアドバイザー(EA)がストラテジーテスターで表示できる品質スコアを開発する方法を見ていきます。Van TharpとSunny Harrisという2つの有名な計算方法を見てみましょう。
MQL5での発注を理解する MQL5での発注を理解する
取引システムを構築する際には、効果的に処理しなければならない作業があります。この作業は、注文の発注、または作成された取引システムに注文を自動的に処理させることです。これはあらゆる取引システムにおいて極めて重要だからです。この記事では、発注が効果的な取引システムを作成する作業のために理解する必要があるほとんどのトピックについて説明します。
MQL5の圏論(第20回):セルフアテンションとTransformerへの回り道 MQL5の圏論(第20回):セルフアテンションとTransformerへの回り道
ちょっと寄り道して、chatGPTのアルゴリズムの一部について考えてみたいとおもいます。自然変換から借用した類似点や概念はあるのでしょうか。シグナルクラス形式のコードを用いて、これらの疑問やその他の質問に楽しく答えようと思います。