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

リプレイシステムの開発 - 市場シミュレーション(第19回):必要な調整

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

はじめに

連載のこれまでの記事から、追加のポイントをいくつか実装する必要があることが明らかだと思います。これは、特に将来の改善を考慮して、作業をより適切に整理するために絶対に必要です。1つの資産を操作するためだけにリプレイ/シミュレーションシステムを使用する予定がある場合は、これから実装するものの多くは必要ありません。これらは脇に置いておいても構いません。つまり、必ずしも構成ファイルに存在する必要はないということです。

ただし、1つの資産だけでなく、複数の異なる資産、またはかなり大規模なデータベースを使用する可能性が非常に高くなります。この場合、物事を整理する必要があるため、この目標を達成するために追加のコードを実装する必要があります。ただし、非常に特殊なケースでは、ソースコードですでに利用可能なものを暗黙的な方法で単純に使用することもできます。これはただ明るみに出る必要があるだけです。

私は常に物事をきちんと整理しておくことを好みます。おそらく多くの人が同じことを考え、そうしようとしていると思います。この機能を実装する方法を理解しておくとよいでしょう。さらに、研究や分析に使用する特定の資産に特定のパラメータが必要な場合に、システムに新しいパラメータを追加する方法を学習します。

ここでは、コードに新しい関数を追加する必要がある場合に、スムーズかつ簡単に追加できるように基礎を整えます。現在のコードでは、有意義な進歩を遂げるために必要な事柄の一部をまだカバーまたは処理できません。最小限の労力で特定のことを実装できるようにするには、すべてを構造化する必要があります。すべてを正しくおこなえば、対処が必要なあらゆる状況に非常に簡単に適応できる、真に普遍的なシステムを得ることができます。これらの側面の1つが次の記事のトピックになります。幸いなことに、気配値表示ウィンドウにティックを追加する方法を示した過去2つの記事のおかげで、物事は概ね計画通りに進んでいます。これらの記事を見逃した場合は、次のリンクを使用してアクセスできます。「リプレイシステムの開発 - 市場シミュレーション(第17回):ティックそしてまたティック(I)」「リプレイシステムの開発 - 市場シミュレーション(第18回):ティックそしてまたティック(II)」。これら2つの記事は、今後の記事でおこなう内容についての貴重な情報を提供します。

ただし、いくつかの非常に具体的な詳細がまだ不足しているため、これらはこの記事で実装されます。さらに、他にも非常に複雑な問題があり、それらに対処して問題を解決する方法を説明する別の記事が必要になります。では、この記事で説明するシステムの実装を開始しましょう。まずは、使用するデータの構成を改善することから始めます。


ディレクトリシステムの実装

ここでの問題は、このシステムを本当に導入する必要があるかどうかではなく、なぜそれを導入する必要があるのか​​ということです。開発の現在の段階では、ディレクトリシステムを使用できます。ただし、リプレイ/シミュレーションサービスの実装にはさらに多くの作業をおこなう必要があります。つまり、単に新しい変数を構成ファイルに追加するだけではなく、もっと多くの作業が必要になるということです。私が何を言っているのかを理解するには、次の画像をご覧ください。

図01

図01:現在のシステムのディレクトリにアクセスする方法


図02

図02:ディレクトリにアクセスする別の方法


リプレイ/シミュレーションシステムの観点からは、図01は図02と同じ動作をしますが、図02に示されているものを使用して設定する方がはるかに実用的であることがすぐにわかります。これは、データを配置するディレクトリを一度指定するだけで済み、残りは再生/モデリングシステムが処理するためです。図02に示すシステムを使用すると、たとえば移動平均の使用を追加するなど、非常に大規模なデータベースを使用している場合に、データを検索する場所の指定を忘れたり誤ったりすることがなくなります。この書き込みエラーが発生すると、次の2つの状況が発生する可能性があります。

  • 最初のケースでは、システムは単にデータにアクセスできないという警告を発行します。 
  • 2番目のケースはより深刻で、間違ったデータが使用されます。

ただし、ディレクトリを1か所に設定できるため、このような種類のエラーが発生する可能性は大幅に低くなります。全く起こらないというわけではありませんが、可能性は低いでしょう。オブジェクトをさらに具体的なディレクトリに整理して、図01と図02を組み合わせることができることを思い出してください。ただし、ここではすべてをより単純なレベルに留めておきます。データ処理や組織のスタイルに合った方法で自由に実装してください。

理論については見てきましたが、今度は実際にそれをおこなう方法を見てみましょう。このプロセスは、少なくとも私たちがまだおこなっていないことに比べれば、比較的シンプルで簡単です。まず、以下のコードに示すように、クラスに新しいprivate変数を作成します。

private :
    enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
    string m_szPath;


この変数をこの位置に追加すると、クラスのすべての内部プロシージャから見えるようになります。ただし、クラス外からはアクセスできません。これにより、過度の変更が防止されます。これは、クラスの一部の内部プロシージャでは、気づかないうちに変数の値を変更してしまう可能性があるためです。また、コードが期待どおりに機能しない理由を理解するのが難しい場合もあります。

これが完了したら、構成ファイル内の新しいコマンドの認識を開始するようにクラスに指示する必要があります。この手順は非常に特定の時点で実行されますが、追加する内容によって異なる場合があります。今回の場合、これを以下に示す順序で実行します。

inline bool Configs(const string szInfo)
    {
        const string szList[] = {
                                "POINTSPERTICK",
                                "PATH"
                                };
        string  szRet[];
        char    cWho;
                
        if (StringSplit(szInfo, '=', szRet) == 2)
        {
            StringTrimRight(szRet[0]);
            StringTrimLeft(szRet[1]);
            for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
            switch (cWho)
            {
                case 0:
                    m_PointsPerTick = StringToDouble(szRet[1]);
                    return true;
                case 1:
                    m_szPath = szRet[1];
                    return true;                                    
            }
            Print("Variable >>", szRet[0], "<< undefined.");
        }else
            Print("Definition of configuratoin >>", szInfo, "<< invalid.");
                                
        return false;
    }

すべてのコードが改善を受けるように構造化されていると、これがどれほど簡単になるかに注目してください。ただし、注意が必要です。予防策を講じていれば、必要なものすべてを問題なくコードに追加できます。

最初におこなうことは、シリアルデータ配列内の構成ファイルで使用するコマンドの名前またはラベルを追加することです。これらはすべて大文字で記述する必要があることに注意してください。大文字と小文字を区別することもできますが、その場合、ユーザーがコマンドを入力して構成ファイルに配置することがより困難になります。システムを使用するのが自分だけで、同じラベルを異なる値で使用したい場合は、大文字と小文字を区別するシステムを使用するのが良いでしょう。そうしないと、このアイデアによって作業全体が複雑になってしまいます。個人的には、同じラベルを異なる意味で使用することは、物事を難しくするだけだと思います。よってそのようなことはしません。

ラベルがコマンドマトリックスに追加されたら、その機能を実装する必要があります。これはこの時点で正しくおこなわれます。そのように単純です。これはチェーンの2番目であり、チェーンは0から始まるため、値1を使用して、その特定の機能を実装していることを示します。ディレクトリ名を指定するだけなので、コマンドは非常に簡単です。最後に、呼び出し元にtrueを返します。これにより、コマンドが認識され、正常に実装されたことが示されます。

システムに追加をおこなう手順は、次のとおりです。これをおこなうと、構成ファイルで提供されたデータを使用できるようになります。ただし、言い忘れた点が1つあります。非常に単純ですが、注目に値します。場合によっては、追加された新しいリソースが問題を引き起こしているように見えても、実際には正しく初期化されていないだけである可能性があります。この場合、privateグローバル変数を追加するたびに、それがクラスコンストラクタで適切に初期化されていることを確認する必要があります。これは、新しい変数を初期化している以下のコードで確認できます。

C_ConfigService()
	:m_szPath(NULL)
	{
	}

これにより、まだ値が割り当てられていない変数の既知の値が確実に得られます。状況によっては、この詳細は重要ではないように見えるかもしれませんが、他の状況では、深刻な問題を回避し、時間を節約できるため、優れたプログラミング手法とみなされます。この作業が完了すると、クラス コンストラクタで変数が初期化され、構成ファイルでの指定内容に基づいて変数に値を割り当てる方法が確立されたので、この値を使用します。この値は、データベースの読み込みを制御する1つの関数でのみ使用されます。

これを実装する方法を見てみましょう。

bool SetSymbolReplay(const string szFileConfig)
    {
        #define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo)
        int        file,
                iLine;
        char    cError,
                cStage;
        string  szInfo;
        bool    bBarsPrev;
        C_FileBars *pFileBars;
        
        if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
        {
            Print("Failed to open the configuration file [", szFileConfig, "]. Closing the service...");
            return false;
        }
        Print("Loading ticks for replay. Please wait....");
        ArrayResize(m_Ticks.Rate, def_BarsDiary);
        m_Ticks.nRate = -1;
        m_Ticks.Rate[0].time = 0;
        iLine = 1;
        cError = cStage = 0;
        bBarsPrev = false;
        while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0))
        {
            switch (GetDefinition(FileReadString(file), szInfo))
            {
                case Transcription_DEFINE:
                    cError = (WhatDefine(szInfo, cStage) ? 0 : 1);
                    break;
                case Transcription_INFO:
                    if (szInfo != "") switch (cStage)
                    {
                        case 0:
                            cError = 2;
                            break;
                        case 1:
                            pFileBars = new C_FileBars(macroFileName);
                            if ((m_dtPrevLoading = (*pFileBars).LoadPreView()) == 0) cError = 3; else bBarsPrev = true;
                            delete pFileBars;
                            break;
                        case 2:
                            if (LoadTicks(macroFileName) == 0) cError = 4;
                            break;
                        case 3:
                            if ((m_dtPrevLoading = LoadTicks(macroFileName, false)) == 0) cError = 5; else bBarsPrev = true;
                            break;
                        case 4:
                            if (!BarsToTicks(macroFileName)) cError = 6;
                            break;
                        case 5:
                            if (!Configs(szInfo)) cError = 7;
                            break;
                    }
                break;
            };
            iLine += (cError > 0 ? 0 : 1);
        }
        FileClose(file);
        switch(cError)
        {
            case 0:
                if (m_Ticks.nTicks <= 0)
                {
                    Print("No ticks to use. Closing the service...");
                    cError = -1;
                }else if (!bBarsPrev) FirstBarNULL();
                break;
            case 1  : Print("Command in line ", iLine, " cannot be recognized by the system...");    break;
            case 2  : Print("The system did not expect the content of the line ", iLine);                  break;
            default : Print("Error in line ", iLine);
        }
                                                        
        return (cError == 0 ? !_StopFlag : false);
#undef macroFileName
    }

これを独自の方法で同時に複数の異なる場所で使用するため、コーディングを容易にするためにマクロを使用することにしました。黄色でマークされたすべてのポイントは、マクロに含まれるコードを正確に受け取ります。これにより、同じ内容を何度も記述する必要がなくなるため、作業が大幅に簡素化されます。これにより、複数の異なる場所で使用されるコードを保守または変更する場合に発生する可能性のあるエラーも回避できます。次に、マクロが何をおこなうかを詳しく見てみましょう。

#define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo)

変数を特定の値で初期化したことを覚えていらっしゃるでしょうか。変数を使用しようとした瞬間に、その変数にどのような値が含まれているかを正確に確認します。これがコンストラクタで初期化したパスと同じである場合、パスが定義されています。それが構成ファイル内で見つかったものである場合は、別のパスになりますが、最終的にはファイルにアクセスできる名前が得られます。

このシステムは非常に汎用的であるため、すでに完成しコンパイルされたシステムの他の部分を変更することなく、いつでもディレクトリを変更できます。こうすることで、構成ファイルを変更するときにすべてのコードを再コンパイルする必要がなくなります。作業対象のディレクトリを変更するために必要な唯一のことは、構成ファイル内の次のフラグメントを使用することです。

[Config]
Path = < NEW PATH >

ここで、<NEW PATH>には新しいアドレスが含まれます。これは今後構成ファイルで使用されます。ディレクトリ構造を含む可能性のあるデータベースを操作する場合、作業が大幅に軽減されるため、これは非常に優れています。必要なファイルを見つけやすくするために、ディレクトリ内のデータを体系化して整理する必要があることに注意してください。

これが完了したら、次のステップに進み、実装する必要があるいくつかのことを最終決定します。これについては次のトピックで説明します。


カスタム銘柄データの調整

注文システムを実装するには、最初に3つの基本値、 最小ボリュームティック値ティックサイズが必要です。現在、これらの値のタイプのうち1つだけが実装されていますが、その値が構成ファイルに設定されていない可能性があるため、その実装は本来あるべきものとは異なります。このため、予想される市場の動きをシミュレートすることのみを目的とした合成銘柄を作成する作業が複雑になります。

必要な修正をおこなわないと、後で注文システムを操作するときにシステム内のデータが不整合になる可能性があります。このデータが正しく構成されていることを確認する必要があります。そうすれば、すでにかなり長いコードが含まれているシステムに何かを実装しようとするときに問題が発生しなくなります。したがって、作業の次の段階で問題が発生しないように、間違った点を修正し始めます。問題があるならば、それを別の性質のものにしましょう。注文システムは、市場リプレイ/シミュレーションサービスの作成に使用されるサービスと実際には対話しません。私たちが遭遇する唯一の情報はチャートと銘柄名であり、それ以上は何もありません。少なくとも今のところはそれが私の意図です。本当に成功するかどうかは分かりません。

このシナリオでは、最初におこなう必要があるのは、絶対に必要な3つの値を初期化することです。ただし、ゼロデータに設定されます。これを段階的に考えてみましょう。まず、問題を解決する必要があります。これは以下のコードでおこなわれます。

C_Replay(const string szFileConfig)
    {
        m_ReplayCount = 0;
        m_dtPrevLoading = 0;
        m_Ticks.nTicks = 0;
        Print("************** Market Replay Service **************");
        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.0);
        CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

ここでは初期値をゼロに設定しますが、おまけとしてカスタム銘柄の説明も提供します。これは必須ではありませんが、銘柄のリストが表示されたウィンドウを開いて、一意の名前を持つ銘柄が表示されると興味深い場合があります。おそらくすでにお気づきかと思いますが、以前に存在した変数は使用されなくなります。以下のコードに示すように、特定の場所で宣言された変数。

class C_FileTicks
{
    protected:
        struct st00
        {
            MqlTick  Info[];
            MqlRates Rate[];
            int      nTicks,
                     nRate;
            bool     bTickReal;
        }m_Ticks;
        double       m_PointsPerTick;
    private :
        int          m_File;

この変数が表示されるすべてのポイントは、銘柄に含まれ定義されている値を参照する必要があります。これで新しいコードができましたが、基本的にこの値はリプレイ/シミュレーションシステム全体の2か所で言及されました。最初のものを以下に示します。

inline long RandomWalk(long pIn, long pOut, const MqlRates &rate, MqlTick &tick[], int iMode)
    {
        double vStep, vNext, price, vHigh, vLow, PpT;
        char i0 = 0;

        PpT = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
        vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / PpT);
        vHigh = rate.high;
        vLow = rate.low;
        for (long c0 = pIn, c1 = 0, c2 = 0; c0 < pOut; c0++, c1++)
        {
            price = tick[c0 - 1].last + (PpT * ((rand() & 1) == 1 ? -1 : 1));
            price = tick[c0].last = (price > vHigh ? price - PpT : (price < vLow ? price + PpT : 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 += PpT; else vHigh -= PpT;
                    }else
                    {
                        if (rate.close < vHigh) vHigh -= PpT; else vLow += PpT;
                    }
                } else
                {
                    if (rate.close > vLow) vLow = (i0 == 3 ? vLow : vLow + PpT); else vHigh = (i0 == 3 ? vHigh : vHigh - PpT);
                }
            }
        }

        return pOut;
    }

同じコードを複数の場所で繰り返したくないので、ローカル変数を使用します。ただし、原則は同じです。銘柄内で定義された値を参照します。この値が参照する2番目のポイントは C_Replayクラス内にあります。ただし、実際的な理由から、ランダムウォークを作成したときとは対照的に、上で示したものとは少し異なることをおこないます。チャート内の情報を表示および使用すると、不必要な呼び出しが多すぎるため、パフォーマンスが低下する傾向があります。これは、ランダムウォークの作成中に、各バーがそれに3回のアクセスを生成するという事実によるものです。

ただし、作成されると、数千のティックが含まれる可能性があり、それらはすべてわずか3回の呼び出しで作成されます。これにより、プレゼンテーションやプロット中にパフォーマンスがわずかに低下する傾向がありますが、これが実際にどのように機能するかを見てみましょう。実際のティックファイルを使用する場合、つまり再生する場合、そのような速度の低下は発生しません。これは、実際のデータを使用する場合、システムは1分足をプロットし、その情報を気配値ウィンドウのティックチャートに転送するために追加情報を必要としないためです。これについては、以前の2つの記事で説明しました。

ただし、1分足を使用してティックを生成する場合、つまりシミュレーションを実行する場合は、ティックのサイズを知る必要があるため、この情報はサービスが適切な動きモデルを作成するのに役立ちます。この動きは気配値表示ウィンドウに表示されます。ただし、変換は C_FileTicksクラスで実行されるため、バーの作成にはこの情報は必要ありません。

この詳細を理解した上で、指定されたチャートを生成する関数を検討し、実行中に受信される呼び出しの数を確認する必要があります。以下はシミュレーション中に使用される関数です。

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 (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;
        }
        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);
        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
        }

ここでは静的ローカル変数を宣言します。これは、ティックサイズを固定する関数への不必要な呼び出しを避けるために使用されます。このキャプチャはサービスの存続期間中および実行時に1回だけおこなわれ、変数は指定された場所でのみ使用されます。したがって、この機能を超えて拡張する意味はありません。ただし、変数が実際に使用されるこの場所は、シミュレーションモードを使用している場合にのみ使用できることに注意してください。リプレイモードでは、この変数は実際には使用されません。

これにより、ティックサイズの問題も解決されました。解決すべき問題はあと2つ残っています。ただし、ティックの問題はまだ完全には解決されていません。初期化に問題があります。アプローチは同じなので、他の2つの問題を解決しながらこれを解決します。


最後に作成する詳細

問題は、実際に何を調整する必要があるかです。確かに、カスタム銘柄でいくつかのことを調整できるのは事実ですが、それらのほとんどは私たちの目的には必要ありません。私たちは本当に必要なものだけに集中する必要があります。また、この情報が必要なときに、シンプルかつ普遍的な方法で入手できるように設定する必要もあります。近いうちに発注システムを作り始めるからと言っていますが、実際にそんなに早く実現するとは思えません。いずれにせよ、EAはリプレイ/シミュレーションと互換性があり、デモ口座と実際の口座の両方で実際の市場での使用に適しているようにしたいと考えています。これをおこなうには、実際の市場に存在するのと同じレベルの必要な情報をコンポーネントが持つ必要があります。

この場合、それらをゼロ値で初期化する必要があります。これにより、カスタム銘柄のこれらの値が実際の銘柄の値と一致することが保証されます。さらに、値をゼロに初期化すると、後でテストできるようになり、銘柄構成で考えられるエラーを実装してテストする作業がはるかに簡単になります。

C_Replay(const string szFileConfig)
    {
        m_ReplayCount = 0;
        m_dtPrevLoading = 0;
        m_Ticks.nTicks = 0;
        Print("************** Market Replay Service **************");
        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");
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

ここでは、将来、非常に近い瞬間に本当に必要となる値を実行します。これらの値は、ティックサイズ、ティック値、ボリューム(この場合はステップ)です。ただし、このステップは使用すべき最小ボリュームに相当することが多いため、最小ボリュームの代わりにこのステップを設定しても問題はないと思います。また、次の段階では、このステップが私たちにとってさらに重要になるためです。それには別の理由があります。最小ボリュームを調整しようとしましたが、何らかの理由で調整できませんでした。MetaTrader 5は、最小ボリュームを設定する必要があるという事実を単純に無視しました。

これが完了したら、別のことをおこなって、これらの値が実際に初期化されているかどうかを確認する必要があります。これは次のコードでおこなわれます。

bool ViewReplay(ENUM_TIMEFRAMES arg1)
   {
#define macroError(A) { Print(A); return false; }
   u_Interprocess info;
   
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
        macroError("Configuração do ativo não esta completa, falta declarar o tamanho do ticket.");
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
        macroError("Configuração do ativo não esta completa, falta declarar o valor do ticket.");
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
        macroError("Configuração do ativo não esta completa, falta declarar o volume mínimo.");
   if (m_IdReplay == -1) return false;
   if ((m_IdReplay = ChartFirst()) > 0) do
   {
        if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
        {
            ChartClose(m_IdReplay);
            ChartRedraw();
        }
   }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
   Print("Aguardando permissão do indicador [Market Replay] para iniciar replay ...");
   info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
   ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
   ChartRedraw(m_IdReplay);
   GlobalVariableDel(def_GlobalVariableIdGraphics);
   GlobalVariableTemp(def_GlobalVariableIdGraphics);
   GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
   while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
   
   return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
#undef macroError
  }

不必要な繰り返しを避けるために、マクロを使用します。エラーメッセージが表示され、システム障害メッセージが表示されて終了します。ここでは、構成ファイルで宣言および初期化する必要がある値を1つずつ確認します。これらのいずれかが初期化されていない場合は、カスタム銘柄を正しく設定するようにユーザーに通知されます。これがないと、リプレイ/シミュレーションサービスは機能し続けません。この時点から、それは機能し、市場を適切にモデル化または再現するために必要なデータを注文システム(この場合は作成中のEA)に提供できるとみなされます。これにより、注文の送信をシミュレートできるようになります。

これは良いことですが、これらの値を初期化するには、以下に示すようにシステムにいくつかの追加をおこなう必要があります。

inline bool Configs(const string szInfo)
    {
        const string szList[] = {
                                "PATH",
                                "POINTSPERTICK",
                                "VALUEPERPOINTS",
                                "VOLUMEMINIMAL"
                                };
        string  szRet[];
        char    cWho;
        
        if (StringSplit(szInfo, '=', szRet) == 2)
        {
            StringTrimRight(szRet[0]);
            StringTrimLeft(szRet[1]);
            for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
            switch (cWho)
            {
                case 0:
                    m_szPath = szRet[1];
                    return true;
                case 1:
                    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
                    return true;
                case 2:
                    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
                    return true;
                case 3:
                    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
                    return true;                                    
            }
            Print("Variable >>", szRet[0], "<< undefined.");
        }else
            Print("Definition of configuratoin >>", szInfo, "<< invalid.");

        return false;
    }

システムに何かを追加するのは非常に簡単です。ここでは、銘柄の再生またはシミュレーションを構成するファイルを編集するだけで構成できる2つの新しい値を追加しました。これは、対応する値を期待する関数の呼び出しを生成するティック値と、その値を調整する内部関数も呼び出すステップサイズ値です。その他の追加は次のステップでおこないます。


最終的な検討事項

値が適切かどうかはまだテストしていません。したがって、注文システムの使用時にエラーが発生しないように、構成ファイルを編集するときは注意してください。

とにかく、添付のカスタム銘柄を使用して、状況がどのようになっているかを確認できます。

重要な注意点:システムが実際に機能しているという事実にもかかわらず、これは完全に真実ではありません。現時点では、外国為替データを使用してリプレイまたはシミュレーションを実行することはできません。外国為替市場は、システムがまだ処理できないものをいくつか使用しているためです。これを実行しようとすると、リプレイモードでもシミュレーションモードでも、システム配列で範囲エラーが発生します。しかし、私は外国為替市場データを操作できるように修正に取り組んでいます。

次の記事では、外国為替のトピックについて検討していきます。


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

添付されたファイル |
リプレイシステムの開発 - 市場シミュレーション(第20回):FOREX (I) リプレイシステムの開発 - 市場シミュレーション(第20回):FOREX (I)
この記事の最初の目的は、外国為替取引のすべての可能性をカバーすることではなく、少なくとも1つのマーケットリプレイを実行できるようにシステムを適応させることです。シミュレーションはまた別の機会にしますが、ティックがなくバーだけでも、少しの努力で外国為替市場で起こりうる取引をシミュレートすることができます。シミュレーターをどのように適応させるかを検討するまでは、この状態が続くでしょう。システム内部でFXのデータに手を加えずに作業しようとすると、さまざまなエラーが発生します。
リプレイシステムの開発 - 市場シミュレーション(第18回):ティックそしてまたティック(II) リプレイシステムの開発 - 市場シミュレーション(第18回):ティックそしてまたティック(II)
明らかに、現在の指標は1分足を作成するのに理想的な時間からは程遠いです。それが最初に修正することです。同期の問題を解決するのは難しくありません。難しそうに思えるかもしれませんが、実際はとても簡単です。前回の記事の目的は、チャート上の1分足を作成するために使用されたティックデータを気配値ウィンドウに転送する方法を説明することであったため、必要な修正はおこないませんでした。
指標やEAのデータを表示するダッシュボードの作成 指標やEAのデータを表示するダッシュボードの作成
この記事では、指標とEAで使用するダッシュボードクラスを作成します。これは、エキスパートアドバイザー(EA)に標準指標を含めて使用するためのテンプレートを含む短い連載の紹介記事です。まず、MetaTrader 5データウィンドウに似たパネルを作成します。
ニューラルネットワークが簡単に(第53回):報酬の分解 ニューラルネットワークが簡単に(第53回):報酬の分解
報酬関数を正しく選択することの重要性については、すでに何度かお話ししました。報酬関数は、個々の行動に報酬またはペナルティを追加することでエージェントの望ましい行動を刺激するために使用されます。しかし、エージェントによる信号の解読については未解決のままです。この記事では、訓練されたエージェントに個々のシグナルを送信するという観点からの報酬分解について説明します。