English Русский 中文 Español Deutsch Português
preview
リプレイシステムの開発 - 市場シミュレーション(第17回):ティックそしてまたティック(I)

リプレイシステムの開発 - 市場シミュレーション(第17回):ティックそしてまたティック(I)

MetaTrader 5 | 8 1月 2024, 16:48
188 0
Daniel Jose
Daniel Jose

はじめに

前回の「リプレイシステムの開発 - 市場シミュレーション(第16回):新しいクラスシステム」に基づいて、C_Replayクラスに必要な変更を加えました。これらの変更は、完了する必要があるいくつかのタスクを簡素化することを目的としています。したがって、かつては大きすぎたC_Replayクラスは、その複雑さを他のクラスに分散する単純化プロセスを経ました。これにより、リプレイ/シミュレーションシステムへの新しい機能と改善を実装することがはるかに簡単かつ簡単になります。この記事からこれらの改善が現れ始め、次の7回の記事まで拡張される予定です。

私たちが検討する最初の質問は、コードを見るだけで誰でも理解できる方法でモデル化するのが非常に困難です。このことを承知の上で、読者の皆様には、この記事全体で検討する説明に十分な注意を払っていただきたいと思います。非常に内容が豊富で複雑な説明なので、注意深く理解していれば理解できるでしょう。このようなことを言ったのは、今日の内容が不必要だと考える読者もいれば、非常に重要だと考える読者もいるからです。推論を理解できるように、資料は段階的に提示されます。

ここでの大きな問題は、これまでの記事はすべてチャートの構築のみに焦点を当てており、リプレイ/シミュレーションアセットが実際の市場で起こっていることと非常によく似た動作をするような方法でチャートを表示する必要があったことです。オーダーブックなどの他のツールを使用して取引する人がたくさんいることは知っています。私は個人的には、そのようなツールを使用することは良い習慣ではないと思います。他のトレーダーは、オーダーブック上の出来事と取引内容の間には何らかの相関関係があると信じています。人それぞれの考え方があっても大丈夫です。しかし、それにもかかわらず、多くの人が仕事で使用しているツールがあり、それがティックチャートです。それが何であるかわからない場合は、図01の画像を見てください。


図01

図01:ティックチャート

このチャートは、MetaTrader 5プラットフォームのいくつかの場所に表示されます。これらの場所についてのアイデアを提供するために、MetaTrader 5の標準バージョンに含まれるいくつかの場所について説明します。たとえば、気配値表示ウィンドウ(図1)、板情報(図02)と注文システム(図03)です。

これらの場所とは別に、何らかの指標を使用して同じ情報を確認することもできます。例は「一からの取引エキスパートアドバイザーの開発(第13回):Times And Trade (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/ja/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として渡します。

これが最初におこなう必要があることです。ここで、さらにいくつかの小さな変更を加える必要があります。この時点で、他の記事を読む前にこの記事を見た人にとっては、状況が少し混乱し始めるかもしれません。その場合は、一旦読むのをやめて、連載の最初の記事「リプレイシステムの開発 - 市場シミュレーション(第1回):最初の実験(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ポジションに含まれる値が無視されなくなったことに注意してください。さらに、変更によって関数内に新しいプロシージャが生成されなかったため、位置で許可されている場合、つまりデータ全体を読み取ってメモリに保存する場合は値を蓄積しなくなりました。むしろ、単純化しました。(この連載を読んでいれば)実際に何が起こっているのかを理解するのに問題はないと思いますが、これらの単純化がおこなわれたという事実は、コード内の他の場所に影響を及ぼします。これらの項目の一部は大きな影響を受けるため、コード全体が再び確実に動作するようになるまで、一部のコンポーネントを無効にする必要があります。

変更を加えてコードを安定させ、最終バージョンをすぐに表示することができました。しかし、変化を段階的に示すことは、学習中で、物事がどのように機能するかを詳細に理解したいと考えている人にとっては非常に価値があると思います。さらに、これらの変化を説明する別の理由があります。しかし何よりも、冷静かつ計画的に行動すれば、難しい問題の研究がより容易になります。さらに悪いことに、これらの微妙な点の多くが、プロのトレーダーであると主張する人々、つまり実際に金融市場で生計を立てていると主張する人々によって十分に説明されていないことです。しかし、そのような質問はこの連載の範囲を超えています。私たちの主な目標から逸脱しないようにしましょう。少しずつ実装を続けていきましょう。そうすれば、後ですべてがより意味のあるものになります。特に別の市場について話している場合、これも非常に興味深いものです。しかし、この驚きを台無しにしたくありません。記事を読み続ければ、私が何を言っているのか理解できるでしょう。

これらの最初の変更を加えた後、少し奇妙ではありますが、それでも必要な変更をさらに1つ加える必要があります。ボリュームが存在しない値(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...

}

この関数はもともとクラスのprivateであり、注目される点はありませんでした。現在protected関数に加えて、変数もあります。これはリプレイカウンタで使用される変数です。この変数は、その特定の関数によってのみ値が変更されることのみを目的としています。このループにより、チャートの左端の最初のバーに適切な値が設定されます。覚えておいてください。これで、価格値とともに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分足を構築するシステムの表示をブロックする必要がありました。完全に完成していなくても、追加のコードを追加する必要がありました。このコードは、チャートの左端にバーを配置したときに起こることと非常によく似た処理をおこないます。おそらく、コードの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
                        }

同じように見えますが、実際はそうではありません。違いがあります。2番目のコードに2つの新しい呼び出しがあることではありません。最初の呼び出しは、関数からメトリックコードを削除することにしたためだけに追加されました。メトリックコードは以下で確認できます。これはまさに元の関数にあったものです。

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はデータチェーンに干渉するため、これをおこなうには別の方法が必要です。ポジションに取引高があるかどうかを見ることで、終値として使用できる値であるかどうかを知ることができます。

これがこの新しい関数の重要なポイントです。新しい呼び出しが2つあります。最初のものはすでに見ました。しかし、2番目のケースでは、事態は非常に奇妙になります。

2番目の呼び出しのコードを以下に示します。

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関数を使用すると、相場がブローカーのサーバーから受信されたかのように相場をフィードできます。データはティックデータベースに直接書き込まれるのではなく、気配値ウィンドウに送信されます。次に、端末は相場監視からのティックをデータベースに保存します。1回の呼び出しで大量のデータが渡される場合、リソースを節約するために関数の動作が変更されます。256ティックを超える送信がおこなわれる場合、データは2つの部分に分割されます。最初の(大きい)部分はティックデータベースに直接記録されます(CustomTicksReplaceと同様)。最後の128ティックで構成される2番目の部分は気配値表示に送信され、端末はそこからティックをデータベースに保存します。

MqlTick構造体には、1970年1月1日からカウントされるtime(秒単位のティック時間)とtime_msc(ミリ秒単位のティック時間)という時間値を含む2つのフィールドがあります。追加されたティック内のこれらのフィールドは、次の順序で処理されます。

  1. ticks[k].time_msc!=0の場合、ticks[k].time fieldフィールドを埋めるためにそれを使用します。つまり、ティックにはticks[k].time=ticks[k].time_msc/1000(整数除算)が設定されます。
  2. Nicks[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呼び出しの時点での現在の取引サーバー時間(ミリ秒まで)がこれらのフィールドに書き込まれます。

tiny[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:ティックによって数量が変更された

フィールドの値が0以下の場合、対応するフラグは Nicks[k].flagsフィールドに書き込まれません。 

フラグTICK_FLAG_BUYおよびTICK_FLAG_SELLはカスタム銘柄の履歴には追加されません。

このメモで重要なことは、多くの人にとってはあまり意味がわからないかもしれませんが、これはまさに私が物事をうまく進めるために使用しているものであるということです。ここでは、ミリ秒単位の時間がゼロと異なる条件を指定します。ミリ秒単位の時間はゼロであり、ティック時間はゼロとは異なり、ミリ秒単位の時間とティック時間がゼロの場合です。大きな問題は、ファイルから実際のティックを使用する場合、ほとんどの場合、これらの条件がそれほど明確ではないため、これが問題になることです。ファイルから取得した実際のティックを使用して、このデータをティック情報に挿入しようとしても、望ましい結果は得られません

このため、多くの人がこのモデリングを実行しようとしますが、失敗します。これは単にドキュメントを理解していないことが原因です。しかし、まさにこの事実(ドキュメントに暗示されています)を使用して、上記のコードを作成しました。このコードでは、最初の条件を強制的に作成します。ここで、ミリ秒単位の時間値がゼロとは異なります。ただし、MetaTrader 5が計算を実行して時間値を生成するため、ミリ秒単位で時間を示す値には時間値も含まれている必要があることに注意してください。したがって、ミリ秒フィールドに指定された値に従ってパラメータを調整する必要があります。

このようにして、CustomTicksAdd関数は気配値表示にデータを挿入できるようになります。しかし、これだけではありません。このデータをシステムに入力すると、BID価格線、ASK価格、最終価格線も作成中のチャートに表示されます。言い換えれば、気配値表示にティックを挿入できるボーナスとして、チャート上の価格ラインも受け取りました。この種の機能が欠如しているため、この機能はありませんでした。ただし、システムはまだ完成していないため、打ち上げは先延ばしにしましょう。まだ確認、修正、組み立てが必要な箇所がいくつかあります。そのため、リプレイ/シミュレーションシステムのこの新しい段階をテストするために、REAL TICKSからのデータを使用および提供しています。


最終的な検討事項

必要な手順により、すでに提示されている内容に混乱が生じる可能性があるため、この記事は終わりに近づいています。次の記事では、現在のシステムで正しく動作しないいくつかの点を修正する方法を見ていきます。ただし、早送りや巻き戻しをしなければシステムを利用できます。これをおこなうと、気配値表示のティックデータや価格ライン情報がリプレイ/シミュレーションチャート上の現在の状況と一致しない可能性があります。

ご覧のとおり、私はミニインデックスタイプの契約だけを好みます。したがって、他の資産でもシステムをテストして頂ければと思います。これにより、リプレイ/シミュレーションシステムに投入した内容に関連してシステムがどのように動作するかが明確になるでしょう。ただ1つはっきりさせておきたいのは、早送りシステムにはまだいくつかの欠陥があるということです。したがって、少なくとも現時点では、この機能の使用を避けることをお勧めします。

読者に提供しているこれらのテストでは、選択した資産の流動性とボラティリティの両方に十分な注意を払ってほしいと思います。さまざまな資産のパフォーマンスを確認してください。1分間隔での取引が少ない資産では、リプレイ/シミュレーションシステムに問題があるようです。この部分は修正が必要なので、ある意味、今見るのは良いことです。バーのデザインは正しいようですが。この問題はすぐに修正します。読者の皆さんには、このバグを修正する前に、リプレイ/シミュレーターサービスが奇妙に見える理由を理解していただきたいと思います。本当にプログラミングを始めたい場合は、この理解が重要です。シンプルで簡単なプログラムの作成だけに留まらないでください。本当のプログラマーは、最初の困難の兆候で諦めるのではなく、問題が発生したときにそれを解決するものです。

ただし、気配値ウィンドウとメトリクスシステムによって提供される値の両方で時間を観察する場合、時間が1秒を超えると、リプレイ/シミュレーターサービスはシステムを適切に同期できません。これは修正する必要があり、すぐに修正する予定です。それまでは、このコードを研究してください。これは、気配値ウィンドウでのティックの研究と操作の点で非常に役立ちます。次の記事に続きます。すべてがさらに面白くなるでしょう。


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

添付されたファイル |
ニューラルネットワークが簡単に(第52回):楽観論と分布補正の研究 ニューラルネットワークが簡単に(第52回):楽観論と分布補正の研究
経験再現バッファに基づいてモデルが訓練されるにつれて、現在のActor方策は保存されている例からどんどん離れていき、モデル全体としての訓練効率が低下します。今回は、強化学習アルゴリズムにおけるサンプルの利用効率を向上させるアルゴリズムについて見ていきます。
MetaTrader 5でのモンテカルロ並べ替え検定 MetaTrader 5でのモンテカルロ並べ替え検定
この記事では、Metatrader 5のみを使用して、任意のエキスパートアドバイザー(EA)でシャッフルされたティックデータに基づいて並べ替え検定を実施する方法を見てみましょう。
ニューラルネットワークが簡単に(第53回):報酬の分解 ニューラルネットワークが簡単に(第53回):報酬の分解
報酬関数を正しく選択することの重要性については、すでに何度かお話ししました。報酬関数は、個々の行動に報酬またはペナルティを追加することでエージェントの望ましい行動を刺激するために使用されます。しかし、エージェントによる信号の解読については未解決のままです。この記事では、訓練されたエージェントに個々のシグナルを送信するという観点からの報酬分解について説明します。
リプレイシステムの開発 - 市場シミュレーション(第16回):新しいクラスシステム リプレイシステムの開発 - 市場シミュレーション(第16回):新しいクラスシステム
もっと仕事を整理する必要があります。コードはどんどん大きくなっており、今やらなければ不可能になります。分割して征服しましょう。MQL5では、このタスクを実行するのに役立つクラスを使用することができますが、そのためにはクラスに関する知識が必要です。おそらく初心者を最も混乱させるのは継承でしょう。この記事では、これらのメカニズムを実用的かつシンプルな方法で使用する方法を見ていきます。