English Русский 中文 Español Deutsch Português
preview
リプレイシステムの開発 - 市場シミュレーション(第20回):FOREX (I)

リプレイシステムの開発 - 市場シミュレーション(第20回):FOREX (I)

MetaTrader 5テスター | 12 1月 2024, 11:41
518 0
Daniel Jose
Daniel Jose

はじめに

前回の「リプレイシステムの開発 - 市場シミュレーション(第19回):必要な調整」で、より緊急性の高いあることを実装しました。ここで、この連載では当初から株式市場に焦点を当ててきましたが、外国為替市場も取り上げてみたいとおもいます。私が当初FXに興味がなかった理由は、この市場では常に取引が発生するため、テストや訓練のためにリプレイやシミュレーションをする意味がないからです。

これにはデモ口座を使うだけでいいのですが、株式市場にはない、この市場特有の問題があります。このため、暗号資産など他の種類の市場に適応させるために必要なシステムの修正方法を示すことは興味深いことです。

このように、MetaTrader 5プラットフォームは、その作成者が当初提案したよりも多くのアプリケーションにいかに多用途で適しているかが明らかになるでしょう。自分の想像力と特定の市場に関する知識だけが、能力の限界となります。


FXについて学ぶ

この記事の最初の目的は、外国為替取引のすべての可能性をカバーすることではなく、少なくとも1つのマーケットリプレイを実行できるようにシステムを適応させることです。シミュレーションはまた別の機会にしますが、ティックがなくバーだけでも、少しの努力で外国為替市場で起こりうる取引をシミュレートすることができます。シミュレーターをどのように適応させるかを検討するまでは、この状態が続くでしょう。システム内部でFXのデータに手を加えずに作業しようとすると、さまざまなエラーが発生します。そのようなエラーを避けるように努めても、エラーは必ず起こります。それらを克服することで、FX市場のリプレイシステムを構築することは可能ですが、そのためにはいくつかの調整をおこない、これまで取り組んできた概念の一部を変更しなければなりません。そうすれば、より多くのエキゾチックなデータを扱うことができるようになり、システムの柔軟性が高まるので、価値はあると思います。

記事の添付ファイルにはFX銘柄(通貨ペア)が含まれています。これらは実際のチックであるため、状況をイメージすることができます。間違いなく、FXはシミュレーションやリプレイをおこなうのが容易な市場ではありません。基本的な情報の種類は同じですが、FXには特有の特徴があります。したがって、それを観察し、分析することは興味深いのです。

この市場には特有の特徴があります。リプレイシステムを実装するためには、私たちが何について話しているのか、そして他の市場を知ることがいかに興味深いかを理解するために、これらの機能のいくつかを説明しなければなりません。


取引方法

外国為替市場では通常、BID値とASK値の間に実質的なスプレッドがない状態で取引がおこなわれる。ほとんどの場合、この2つの値は同じかもしれません。しかし、どうしてそんなことが可能なのでしょうか。どうして同じなのでしょう。BIDとASKの間に常にスプレッドがある株式市場とは異なり、外国為替市場ではこのようなことはありません。スプレッドがある場合もあれば、はるかに高い場合もありますが、通常、BID値とASK値は同じになります。株式市場出身者がFXを志す場合、取引戦略の大幅な変更が必要になることが多いため、混乱する可能性があります。

外国為替市場の主なプレーヤーは中央銀行であることにも留意する必要があります。 B3 (ブラジル証券取引所) で働く人たちは、中央銀行がドルに対して時々何をするのかをすでに見ており、よく知っています。このため、中央銀行が通貨に介入する可能性を恐れて、多くの人がこの資産の取引を避けています。以前は勝っていたポジションがすぐに大きな敗者に変わる可能性があるためです。経験の浅いトレーダーの多くはこの間に破産することが多く、場合によっては証券取引所や証券会社から訴訟を起こされることさえあります。これは、中央銀行が事前の警告もなく、ポジションを保有する人々に対して容赦なくおこなう介入のひとつによって起こり得ます。

しかし、私たちにとってはそんなことはどうでもいいのです。したがって、外国為替市場では、図01に見られるように、価格の表示はBID価格値に基づいています。


図01 

図01:外国為替市場でのチャート表示

例えば、これが最後に約定した取引の価格を使用するB3表示システムとどのように異なるかは、以下の図02で見ることができます(この記事を書いている時点のドル先物契約のデータ)。

図02 

図02:ブラジル証券取引所(B3)で取引されるミニドル建て契約

リプレイ/シミュレーションシステムは、この種の分析の利用を促進するために開発されました。つまり、最後に取引された価格を使用する場合、取引されたティックファイルでデータがどのようにレイアウトされるかに違いが生じます。それだけでなく、ティックファイルや1分足バーで実際に利用できる情報の種類にも大きな違いがあります。このようなバリエーションがあるため、シミュレーションには他のさらに複雑な問題が含まれるため、ここではリプレイの実行方法のみに焦点を当てることにします。しかし、この記事の冒頭で述べたように、1分足のバーデータを使って、トレード中に起こりそうなことをシミュレートすることは可能です。理論的な話だけでなく、FXと株式市場の情報の違いを、リプレイシミュレーションシステムが開発されたB3、つまりブラジル証券取引所のケースで研究してみましょう。図03では、FXの通貨ペアの1つに関する情報があります。

図03

図03:外国為替市場での実際の取引に関する情報

図04は同じタイプの情報を示していますが、今回はB3(ブラジル証券取引所)で取引されているミニドル先物契約の1つからのものです。

図04

図04:B3の実際のティック情報

まったく違います。外国為替市場には最終価格も取引量もありません。B3ではこれらの値が利用可能であり、多くの取引モデルは取引量と最終取引価格を使用しています。これまで述べてきたことはすべて、システムの構造上、大幅な変更を加えなければ他の種類の市場に対応できないことを示したにすぎません。市場の観点から問題を分割することも考えましたが、理由があってそれは現実的ではありません。このように分離するとプログラミングが大幅に簡素化されるため、プログラミングの観点からではなく、常にいずれかの市場に適応する必要があるため、ユーザビリティの観点からです。できることは中間点を見つけることですが、これは努力なしにはできないでしょう。システム全体をゼロから作り直すつもりはないし、そうするつもりもないので、こうした困難はできる限り最小限にとどめたいと思います。


FXをカバーするための実装を開始

最初にしなければならないのは、浮動小数点数システムを修正することです。しかし、前回の記事ではすでに浮動小数点システムに関していくつかの変更を加えています。また、FXには適していません。これは、精度が小数点以下4桁に制限されているためで、それ以上の桁数のセットを使用することをシステムに伝える必要があります。将来的な問題を避けるためにも、この問題を解決する必要があります。この修正は以下のコードでおこなわれます。

C_Replay(const string szFileConfig)
    {
        m_ReplayCount = 0;
        m_dtPrevLoading = 0;
        m_Ticks.nTicks = 0;
        Print("************** Serviço Market Replay **************");
        srand(GetTickCount());
        GlobalVariableDel(def_GlobalVariableReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        SymbolSelect(def_SymbolReplay, true);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
        CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
        CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

ここで MetaTrader 5に、浮動小数点システムに小数点以下の桁数が必要であることを知らせます。この場合、小数点以下8桁を使用しますが、これは幅広い条件をカバーするには十分すぎます。ひとつ重要なことがあります。B3は小数点以下4桁をうまく処理しますが、FXで使うには小数点以下5桁が必要です。8を使うことで、システムを自由にすることができます。しかし、常にそうであるとは限りません。まだ説明できない詳細があるので、後で変更しなければなりませんが、今はこれで十分です。

これが終われば、何らかの方法で物事が簡単になるでしょう。まず、次のシナリオを検討します。チャートに表示されるプレビュー バーは1分足です。ティックに関しては、別のファイルに存在する実際のティックになります。このように、最も基本的なシステムに戻ることになりますが、より包括的なシステムへとすぐに移行することになります。


基本作業

分析する市場の種類、つまりリプレイ用データの出所となる市場の種類をユーザーに選択させないために、最終価格や取引量の値がない場合もあれば、ある場合もあることを利用します。システムがこれをチェックできるようにするには、コードに何かを追加する必要があります。

まず、以下を加えます。

class C_FileTicks
{
    protected:
        enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX};
        struct st00
        {
            MqlTick   Info[];
            MqlRates  Rate[];
            int       nTicks,
                      nRate;
            bool      bTickReal;
            ePlotType ModePlot;
        }m_Ticks;

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

この列挙は、いくつかの分野での混乱を避けるのに役立つでしょう。基本的には、2つのタイプの市場に絞られます。その理由はすぐに理解できるでしょう。不必要な関数呼び出しを避けるために、システムに新しい変数を追加してみましょう。しかし、特定の表示方法を使用していることをシステムが認識できるようにする必要があります。このような問題でユーザーの生活を複雑にしたくないので、以下のコードを少し変更してみよう:

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 ticks for replay. Please wait...");
                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                m_Ticks.ModePlot = PRICE_FOREX;
                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 = (def_Ticks.time * 1000) + (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));
                        m_Ticks.ModePlot = (def_Ticks.volume_real > 0.0 ? PRICE_EXCHANGE : m_Ticks.ModePlot);
                        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
        }

まず、表示タイプをFOREXモデルに指定することから始めましょう。ただし、ティックファイルの読み込み中に取引数量を含むティックが見つかった場合、このモデルはEXCHANGEタイプに変更されます。これは、ユーザーの介入なしに起こることを理解することが重要です。しかし、ここで重要な点が生じます。このシステムは、リプレイの起動時に読み取りがおこなわれる場合にのみ機能します。シミュレーションの場合、状況は異なります。シミュレーションについては、今は扱いません。

このため、シミュレーションコードを作成するまでは 絶対にバーファイルだけを使用してください。ティックファイルは、本物でもシミュレートしたものでも、必ず使用してください。シミュレートされたティックファイルを作成する方法はありますが、この記事の範囲外なので詳しくは触れません。しかし、ユーザーを完全に暗闇に置き去りにしてはなりません。システムがデータを分析することはできても、どのようなディスプレイが使われているかをユーザーに示すことはできません。このように、銘柄ウィンドウを開くことで、表示形式を確認することができます。図01と図02の通りです。

これを可能にするためには、コードにもう少し手を加える必要があります。これには以下のような行が含まれます。

datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true)
    {
        int      MemNRates,
                 MemNTicks;
        datetime dtRet = TimeCurrent();
        MqlRates RatesLocal[];

        MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
        MemNTicks = m_Ticks.nTicks;
        if (!Open(szFileNameCSV)) return 0;
        if (!ReadAllsTicks(ToReplay)) return 0;
        if (!ToReplay)
        {
            ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
            ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
            CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
            dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
            m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
            m_Ticks.nTicks = MemNTicks;
            ArrayFree(RatesLocal);
        }else
        {
            CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX);
            CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID);
        }
        m_Ticks.bTickReal = true;

        return dtRet;
    };

これらの強調された線を加えることで、銘柄に関するより十分な情報を得ることができます。ただし、私がこれから説明することに注意を払ってください。ここで紹介する概念を少しでも理解していなければ、システムに騙されていると思うことになります。図05は、上記のコードが何をおこなっているかを示しています。他の記号でも試してみると、より明確になります。最初のポイントは、この特定の行に関するものです。銘柄に対してどのような計算ができるのか、あるいはどのような計算をするのかを示してくれます。記号を計算する方法はもっとたくさんあるのは事実ですが、ここではできるだけシンプルに、それでいて機能させることを念頭に置いているため、この2種類の計算だけに絞りました。

もっと詳しく知りたい場合や、他のタイプの計算を実装したい場合は、ドキュメントを参照してSYMBOL_TRADE_CALC_MODEを表示することができます。そこには各計算モードの詳細な説明があります。ここでは、最も単純な方法でのみ説明します。気が狂いそうなのは、2番目に強調されている行です。この行では、単に表示モードのタイプを示します。基本的にはこの2種類しかありません。ここでの問題は、この行そのものにあるのではなく、構成ファイルに、より正確には、現在の開発段階での構成ファイルの読み方にあります。

現在のシステムは、バーファイルを読み込んでからティックファイルを読み込むと問題が発生するように書かれています。何か間違ったことをしたからではなく、逆に私たちは正しい論理に従っています。しかし、この行はバーファイルがチャートに読み込まれた後に実行されるため、チャートに存在するコンテンツはすべて削除されます。この問題は、データがチャートに直接送られてくる際にバッファリングされないために発生します。ただし、この解決策については後述します。

個人的な意見ですが、もしこのシステムが私の個人的な使用のためだけのものであったなら、すべてが違った形でおこなわれたでしょう。単純に、ティックファイルがバーファイルの前に読み込まれるように、何らかのアラートを生成したでしょう。しかし、このシステムはプログラミングの知識がない人が使うことが多いので、このような問題を解決するのが適切だと思います。非常に興味深く、かなり役に立つトリックを学ぶことができるので、悪くはありません。

図05

図05:ティックの自動読み取り認識の表示

さて、システムがいくつかのものを識別できるようになったので、何とかしてディスプレイシステムに適応させる必要があります。

class C_Replay : private C_ConfigService
{
    private :
        long         m_IdReplay;
        struct st01
        {
            MqlRates Rate[1];
            datetime memDT;
            int      delay;
        }m_MountBar;
        struct st02
        {
            bool     bInit;
        }m_Infos;

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

すべてを設定し、設定を保存するために、まずこの変数を追加します。システムが完全に初期化されたかどうかが表示されます。しかし、私たちがそれをどう使うかには注意を払って頂きたいと思います。まず、適切な値で初期化します。それはコードの以下 にておこなわれます。

C_Replay(const string szFileConfig)
{
    m_ReplayCount = 0;
    m_dtPrevLoading = 0;
    m_Ticks.nTicks = 0;
    m_Infos.bInit = false;
    Print("************** Serviço Market Replay **************");
    srand(GetTickCount());
    GlobalVariableDel(def_GlobalVariableReplay);
    SymbolSelect(def_SymbolReplay, false);
    CustomSymbolDelete(def_SymbolReplay);
    CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
    CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
    CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
    SymbolSelect(def_SymbolReplay, true);
    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
    CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
    CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
    m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
}

なぜ変数をfalseに初期化するのでしょうか。システムはまだ初期化されていないが、チャートを画面に読み込めば初期化されるからです。チャートを初期化する関数でこれを指定するのかと思われるかもしれないです。そう思いませんか。チャートを初期化する関数の中ではできません。この初期化関数が完了するのを待ち、次の関数が呼ばれたら、システムが初期化されたことを示すことができます。しかし、なぜこの変数を使うのでしょうか。システムは本当に、すでに初期化されているかどうかを知らないのでしょうか。なぜ変数を使う必要があるのでしょうか。システムは初期化されていることを知っていますが、別の理由でこの変数が必要なのです。はっきりさせるために、状態を変化させる関数を見てみましょう。

bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
        {
                u_Interprocess Info;
                int iPos, iTest;

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

この機能には頭を悩ませています。そのため、システムが初期化されているかどうかを示す変数が必要なのです。注意:この関数を初めて実行したとき、変数はまだシステムが完全に初期化されていないことを示します。この時点で初期化が完了します。ここでは、正しい価格ラインが画面に表示されていることを確認します。システムが表示モードを FOREX と定義した場合、BIDとASKの価格行が表示され、最後の価格行は非表示になります。表示モードが「EXCHANGE」の場合は、その逆となります。この場合、BIDとASKの価格ラインは非表示になり、最新の価格ラインが表示されます。

別の設定を好むユーザーもいるという事実がなければ、すべてがとても素晴らしく、良いことでしょう。EXCHANGE表示スタイルで作業していたとしても、彼らはBIDラインかASKライン、場合によっては両方のラインを表示したいものです。そのため、ユーザーが好みに合わせてシステムを設定した後に一時停止し、その後システムを再起動すると、システムはユーザーの設定を無視して内部設定に戻ります。しかし、システムがすでに初期化されていることを指定する(そのために変数を使う)ことで、内部設定には戻らず、ユーザーが設定したままの状態になります。

しかし、なぜこの設定をViewReplay関数でおこなわないのかという疑問が生じます。その理由は、このチャートが実際にはそのような線の設定を受けていなかったからです。また、他の不愉快な問題も解決しなければなりません。助けとなる追加の変数が必要です。単純なプログラミングですべての問題が解決するわけではありません。


バーの表示

ようやくチャートに棒グラフを表示できるところまで来ました。しかし、この段階でこれをおこなおうとすると、配列の範囲エラーが発生します。したがって、実際にチャート上に棒グラフを表示する前に、システムにいくつかの修正を加える必要があります。

最初の修正は以下の関数にあります。

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) && (m_Ticks.ModePlot == PRICE_EXCHANGE))
                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);
                        CustomTicksDelete(def_SymbolReplay, m_Ticks.Info[iPos].time_msc, 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(false, false);
                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                Info.s_Infos.isWait = false;
                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
        }

このチェックにより、システムは最初の数ティックをスキップするかどうかを判断します。問題は、証券取引所と同じような表示をしているかどうかをチェックしないと、チェックに含まれるループが失敗してしまうことです。そして、これは範囲エラーとなります。しかし、2つ目のチェックを加えることで、この段階をパスすることができます。表示モードがFOREXで使用されているタイプと一致する場合、ループは実行されない。よって、次のステージに備えましょう。

次のステップでは、実際にティックをチャートに挿入します。ここで心配しなければならないのは、バーの終値をシステムに伝えることだけで、あとはバーのシミュレーション関数が処理します。この場合、為替モード表示もFOREX表示も同じになります。その実装コードを以下に示します。

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

        bool bNew;
        MqlTick tick[1];
        static double PointsPerTick = 0.0;

        if (bNew = (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)))
        {
            PointsPerTick = (PointsPerTick == 0.0 ? SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) : PointsPerTick);                    
            if (bViewMetrics) Metrics();
            m_MountBar.memDT = (datetime) macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
            def_Rate.real_volume = 0;
            def_Rate.tick_volume = 0;
        }
        def_Rate.close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close) :
                                                               (m_Ticks.Info[m_ReplayCount].bid > 0.0 ? m_Ticks.Info[m_ReplayCount].bid : 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);
        if (bViewTicks)
        {
            tick = m_Ticks.Info[m_ReplayCount];
            if (!m_Ticks.bTickReal)
            {
                static double BID, ASK;
                double  dSpread;
                int     iRand = rand();

                dSpread = PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? PointsPerTick : 0 ) : 0 );
                if (tick[0].last > ASK)
                {
                    ASK = tick[0].ask = tick[0].last;
                    BID = tick[0].bid = tick[0].last - dSpread;
                }
                if (tick[0].last < BID)
                {
                    ASK = tick[0].ask = tick[0].last + dSpread;
                    BID = tick[0].bid = tick[0].last;
                }
            }
            CustomTicksAdd(def_SymbolReplay, tick); 
        }
        m_ReplayCount++;

#undef def_Rate
    }

このラインはまさにそうです。使用されているディスプレイの種類に応じて、バーの終値が生成されます。そうでなければ、関数は以前と同じままです。こうすることで、FOREXマッピングシステムをカバーし、ティックファイルで提示されたデータでリプレイを実行できるようになります。シミュレーションはまだできません。

システムはすでに完成していると思うかもしれませんが、そうではありません。FOREXシミュレーションを考える前に、まだ2つの問題があります。

最初の問題は、リプレイ/シミュレーションの構成ファイルに作成ロジックが必要だということです。多くの場合、これによってユーザーは、本当に必要なものでなくてもシステムに適応せざるを得なくなる。これに加えて、もうひとつ問題があります。タイマーシステムです。2つ目の問題の理由は、銘柄や取引時間を扱っていて、その銘柄が何時間も不活発なままであったり、オークションにかけられたり、一時停止されたり、その他の理由で不活発なままであったりする可能性があるからです。しかし、そんなことよりも、このタイマーの問題を解決する必要があります。

2つ目の問題の方が緊急性が高いので、そちらから始めましょう。


タイマーの修正

システムとタイマーの最大の問題は、いくつかの銘柄で時々発生する条件にシステムが対応できないことです。流動性の極端な低下、取引の停止、競売、その他の理由です。何らかの理由でティックファイルが、例えば15分間銘柄をスリープさせるようタイマーに指示した場合、その間システムは完全にロックされます。

実際の市場では、これは特別な方法で解決されます。通常、銘柄が取引されていない場合、プラットフォームは当社に通知しますが、プラットフォームがこの情報を提供しない場合でも、当社は市場から通知を受け取ります。経験豊富なトレーダーであれば、銘柄を見て、この期間に何かが起こり、何もする必要がないことに気づくでしょう。しかし、マーケットリプレイを使用する場合、この状況は問題となり得ます。ユーザーがリプレイやシミュレーションの実行位置を閉じたり、変更したりできるようにしなければなりません。

この種の解決策は以前から使われており、実際、コントロール指標をこれに使うことができます。しかし、そのような思い切った措置をとらざるを得ないような前例はなく、流動性が極めて低い銘柄がオークションにかけられたり、関連イベントによって一時停止されたりするような状況を繰り返すことさえありました。これらすべてがいわゆる流動性リスクを生みますが、リプレイ/シミュレーションシステムではこのリスクを簡単に回避し、研究を続けることができます。しかし、これを効果的におこなうには、タイマーの動作方法を変更する必要があります。

次は新しい表示システムのループです。このコードが一見わかりにくいのは承知しています。

bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
    {
        u_Interprocess Info;
        int iPos, iTest;

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

取り外された部品はすべて他のコードに交換されています。こうして、バーの表示に関する最初の問題を解決することができます。これまではバックワード計算を使っていましたが、これからはフォワード計算を使います。これにより、アイドルモード中にチャート上で奇妙なことが起こるのを防ぐことができます。システムを起動すると、ティックを表示する前に必ずしばらく待つことに注意してください。以前はその逆でした(私の責任です)。タイマーの時間は数時間までと非常に長くなったので、タイマーをコントロールする別の方法を用意しました。よりよく理解するために、以前は時間が完全に過ぎるまでシステムがスタンバイモードのままだったことを知っておく必要があります。チャートを閉じたり、実行ポイントを変更したりといった変更を加えようとしても、システムは期待通りに反応しません。これは、先に添付したファイルでは、資産が長期間「非取引」のままであるセットを使用するリスクがなかったためです。しかし、この記事を書き始めたとき、システムはこんなエラーを示しました。そのため、訂正をおこないました。

タイマーは200ミリ秒以上になるまで作動します。この値は変更可能ですが、他のポイントでも変更するように注意してください。私たちは状況を改善し始めたが、それでもまだ、終わらせる前にもうひとつやらなければならないことがあります。チャートを閉じると、システムはループを抜けます。さて、呼び出しプログラムに戻りましょう。これにより、少なくとも理論上は、すべてが機能することが保証されます。これは、ユーザーがアイドル期間中に再びシステムを操作する可能性があるためです。残りの機能はほとんど変更されていないので、すべてが以前と同じように機能し続けます。しかし、ある期間中にコントロール指標の位置を変更するように指示すれば、以前は不可能だったことが可能になります。これは非常に重要なことで、資産によっては休眠状態に入り、かなり長い期間そのままになっていることがあるからです。これは実際の取引では許容されますが、リプレイ/シミュレーションシステムでは許されません


結論

すべての失敗にもかかわらず、システムでFOREXデータを使用する実験を開始することができます。まずは、このバージョンのリプレイ/シミュレーションシステムから見てみましょう。これを裏付けるために、添付ファイルにいくつかのFOREXデータを掲載します。システムにはまだ改善が必要な部分もあります。まだ詳細には触れたくないので(この記事で紹介していることのいくつかを根本的に変更する必要があるかもしれないので)、修正はここで終わりにします。

次回は、さらにいくつかの未解決の問題を取り上げます。これらの問題はシステムの使用を妨げるものではありませんが、FOREX市場データで使用するつもりであれば、いくつかの事柄が正しく表現されていないことに気づくでしょう。少し手を加える必要がありますが、それは次回の記事で。

構成ファイルの問題は、他の関連する問題と同様に、まだ解決する必要があります。このシステムは適切に機能し、外国為替市場から得たデータを再現することができるため、これらの長引く問題の解決は次回に持ち越すことにします。


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

添付されたファイル |
Market_Replay_yvg20.zip (14386.04 KB)
指標やEAのデータを表示するダッシュボードの作成 指標やEAのデータを表示するダッシュボードの作成
この記事では、指標とEAで使用するダッシュボードクラスを作成します。これは、エキスパートアドバイザー(EA)に標準指標を含めて使用するためのテンプレートを含む短い連載の紹介記事です。まず、MetaTrader 5データウィンドウに似たパネルを作成します。
リプレイシステムの開発 - 市場シミュレーション(第19回):必要な調整 リプレイシステムの開発 - 市場シミュレーション(第19回):必要な調整
ここでは、コードに新しい関数を追加する必要がある場合に、スムーズかつ簡単に追加できるように基礎を整えます。現在のコードでは、有意義な進歩を遂げるために必要な事柄の一部をまだカバーまたは処理できません。最小限の労力で特定のことを実装できるようにするには、すべてを構造化する必要があります。すべてを正しくおこなえば、対処が必要なあらゆる状況に非常に簡単に適応できる、真に普遍的なシステムを得ることができます。
信頼区間を用いて将来のパフォーマンスを見積もる 信頼区間を用いて将来のパフォーマンスを見積もる
この記事では、自動化された戦略の将来のパフォーマンスを推定する手段として、ブーストラッピング技術の応用について掘り下げます。
リプレイシステムの開発 - 市場シミュレーション(第18回):ティックそしてまたティック(II) リプレイシステムの開発 - 市場シミュレーション(第18回):ティックそしてまたティック(II)
明らかに、現在の指標は1分足を作成するのに理想的な時間からは程遠いです。それが最初に修正することです。同期の問題を解決するのは難しくありません。難しそうに思えるかもしれませんが、実際はとても簡単です。前回の記事の目的は、チャート上の1分足を作成するために使用されたティックデータを気配値ウィンドウに転送する方法を説明することであったため、必要な修正はおこないませんでした。