English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
preview
一からの取引エキスパートアドバイザーの開発(第24部):システムの堅牢性の提供(I)

一からの取引エキスパートアドバイザーの開発(第24部):システムの堅牢性の提供(I)

MetaTrader 5トレーディング | 31 10月 2022, 10:25
285 0
Daniel Jose
Daniel Jose

はじめに

単純だと思う人がいても単純ではないこともあります。受注システムもその1つです。「一からの取引エキスパートアドバイザーの開発」稿でおこなったように、より控えめなシステムを作成することもできます。この記事で作成したのは、多くの人にとって有用であっても他の人にとっては十分ではない基本的なシステムです。その結果、ある瞬間にすべてが変化し始めました。これは、この連載の新しい受注システムに関する最初の部分が誕生したときです。「一からの取引エキスパートアドバイザーの開発(第18回)」稿で参照できます。ここから、MetaTrader 5に対応しながらEAで管理できるシステムの開発を開始しました。このシステムのアイデアは、チャート上の注文を制限しないことでした。当初、このシステムはかなり大胆に思えました。オブジェクトがEAではなくMetaTrader 5によって維持されるシステムを作成するという事実自体が私には無意味で非効率的であるように思われたことを認めなければなりません。

ただし、システムは開発中で、「一からの取引エキスパートアドバイザーの開発(第23回)」稿では、注文、ポジション、ストップレベル(テイクプロフィットとストップロス)の管理を容易にするゴーストシステムを開発しました。開発するのはとても面白かったのですが、問題がありました。MetaTrader 5でサポートされているオブジェクトの数と比較して、使用および可視化されているオブジェクトの数を見ると、サポートされるオブジェクトの数は常により多くなるため、間違いなく驚かれることでしょう。

多くの場合、問題はそれほど深刻ではなく、許容範囲ですが、2つの問題があって、市場のボラティリティが高い際にシステムがあまり安定しない原因となります。状況によっては、ユーザーは誤った行動を強いられました。トレーダーが指値注文を追加するとシステムがそれをサーバーに送信しますが、サーバーが応答するのに通常よりも時間がかかる場合があることが原因です。システムはある瞬間に注文があることを示し、別の瞬間に注文がないことを示します。そして、それがポジションでおこなわれた場合(注文とポジションの違いについてはドキュメントを参照)、サーバーがコマンドを期待どおりに実行したかどうかがわからないためにさらに面倒であることが判明しました。

この問題を解決するにはいくつかの方法があります。より単純なものもあれば、より複雑なものもあります。どちらにしても、EAは信頼できる必要があります。さもなければ、いかなる状況でも使用されるべきではありません。


1.0. 計画

ここでの大きな問題は、スピード信頼性という2つの品質を持つシステムを設計することです。一部の種類のシステムでは、両方を達成することは非常に困難であるか、不可能ですらあります。そのため、多くの場合、物事のバランスを取ろうとしますが、それはお金、特に私たちのお金に関するものなので、これらの資質を持たないシステムを取得することで、それを危険にさらしたくはありません.ここではリアルタイムで動作するシステムを扱っていることを覚えておく必要があります。これは、開発者が入り込むことができる最も困難なシナリオです。常に非常に高速なシステムを持つように努める必要があるため、システムを改善しようとしたときに崩壊しないように十分な信頼性を示しながら、イベントに即座に反応する必要があります。したがって、この作業が非常に困難であることは明らかです。

スピードは、関数が最も適切な方法で呼び出されて実行されるようにすること、さらに不必要な時間に不必要な呼び出しを避けることで達成できます。これにより、言語内で可能な限り高速なシステムが提供されます。ただし、さらに高速なものが必要な場合は、機械語レベルにまで下がる必要があります。つまり、アセンブリ言語ですが、これは多くの場合不要です。C言語を使用して、同様に良い結果を得ることができます。

望ましい堅牢性を実現する方法の1つは、コードを可能な限り再利用して、常にさまざまな場合にテストされるようにすることですが、これは1つの方法にすぎません。別の方法は、OOP(オブジェクト指向プログラミング)を使用することです。継承の場合を除いて、各オブジェクトクラスがオブジェクトクラスデータを直接操作しないように、これが正しく適切におこなわれれば、非常に堅牢なシステムを構築するのに十分です。これにより実行速度が低下する場合がありますが、この低下は非常に小さいため、クラスによって提供されるカプセル化によって生成される指数関数的な増分のために無視できます。このカプセル化により、必要な堅牢性が得られます。

ご覧のとおり、スピードと堅牢性の両方を達成するのはそれほど簡単ではありませんが、素晴らしいことは、一見したのと異なり、多くのものを犠牲にする必要がないということです。システムのドキュメントを確認して、改善のために何を変更できるかを確認するだけです。車輪の再発明を試みていないという単純な事実は、すでに良いスタートですが、プログラムとシステムは常に改善されていることを忘れないでください。したがって、私たちは常に利用可能なものを可能な限り使用するように努めるべきであり、最後の場合には、実際に車輪を再発明する必要があります.

この記事でおこなった変更を提示する必要がないと思う方や、実際にはコードを移動せずにコードを大幅に変更していると思う方に説明させてください。何かをコーディングするとき、最終的なコードがどのように機能するかはまったくわかりません。あるのは、達成すべき目標だけです。この目標が達成されると、どのように達成したかを調べ、より良いものにするために物事を改善しようとします。

商用システムの場合、実行可能ファイルやライブラリに変更を加えてアップデートとしてリリースします。商用システムであるため、ユーザーは目的に到達するためのパスを実際に知る必要はありません。実際、知らないのは良いことです。ただ、オープンなシステムで、最初からすぐに効率の良いシステムができると思ってはほしくありません。このように考えるのは適切ではなく、侮辱でさえあります。プログラマーや開発者の使用する言語の知識がどれだけ豊富であってい、時間の経過とともに改善できることが常にあるからです。

したがって、このシーケンスを3、4の記事に要約できるとは思わないでください。その場合は、コードを単純に作成し、最も適切と思われる方法でコードを作成して、商用リリースする方がよいでしょう。.これは私の意図ではありません。私は他のより経験豊富なプログラマーのコードを見てプログラミングを学びましたが、これが持つ価値を知っています。単に完成した解決策を取り上げて、それがどのように機能するかを理解しようとするよりも、物事が時間の経過とともにどのように発展するかを知ることがはるかに重要です.

これらの観察の後、開発に移りましょう。


2.0.実装

2.0.1.位置指標の新しいモデリング

新しいコード形式で最初に注意すべきことは、マクロになった関数の変更です。

inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev, bool isGhost = false)
{
        return StringFormat("%s%c%c%c%llu%c%c%c%s", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)(isGhost ? ev + 32 : ev), def_SeparatorInfo, (isGhost ? def_IndicatorGhost : def_IndicatorReal));
}


予約語「inline」のおかげで、コンパイラがこのコードを参照されるすべての場所で使用したとしても、この関数がコード内で何度も呼び出されることはありません。実際にできるだけ速く実行されるようにする必要がありまう。新しいコードは次のようになります。

#define macroMountName(ticket, it, ev, Ghost) 								 \
		StringFormat("%s%c%llu%c%c%c%c%c%c%c", def_NameObjectsTrade, def_SeparatorInfo,          \                                                                                                                                                                                                                                                                                                
                                                       ticket, def_SeparatorInfo,                        \                                                                                                                                                                                                                                        
                                                       (char)it, def_SeparatorInfo,                      \ 
                                                       (char)(Ghost ? ev + 32 : ev), def_SeparatorInfo,  \ 
                                                       (Ghost ? def_IndicatorGhost : def_IndicatorReal))

旧バージョンのマクロとこのバージョンのデータは異なるので注意してください。この変更には理由があります。これについては、この記事の後半で説明します。

ただし、この変更により、別の関数のコードにも小さな変更を加える必要があります。

inline bool GetIndicatorInfos(const string sparam, ulong &ticket, eIndicatorTrade &it, eEventType &ev)
                        {
                                string szRet[];
                                char szInfo[];
                                
                                if (StringSplit(sparam, def_SeparatorInfo, szRet) < 2) return false;
                                if (szRet[0] != def_NameObjectsTrade) return false;
                                ticket = (ulong) StringToInteger(szRet[1]);
                                StringToCharArray(szRet[2], szInfo);
                                it = (eIndicatorTrade)szInfo[0];
                                StringToCharArray(szRet[3], szInfo);
                                ev = (eEventType)szInfo[0];

                                return true;
                        }


ここでの変更はインデックスのみで、何がチケットで何が指標かを示すために使用されます。複雑なことは何もありません。実行する必要がある簡単な詳細は1つだけです。そうしないと、この関数を使用するときにデータに一貫性がなくなります。

システムが完璧に機能していたのになぜこれらの変更が必要なのかという疑問を持たれるかもしれません。確かにシステムは完璧に機能しましたが、私たちが制御できないものがあります。たとえば、MetaTrader 5の開発者がEAで使用されていない機能を改善した場合、私たちにとって有益ではありません。ルールは、車輪の再発明を避けて、代わりに利用可能なリソースを使用することです。したがって、言語(この場合はMQL5)によって提供される関数を常に使用するようにし、独自の関数の作成を避ける必要があります。これはばかげているように思えるかもしれませんが、実際に立ち止まって考えてみると、プラットフォームによっていくつかの機能が改善されていることがわかります。これらの同じ機能を使用すると、余分な努力をせずに、パフォーマンスが向上し、プログラムのセキュリティが向上します。

目的は手段を正当化するということです。しかし、上記の変更によってEAはMQL5ライブラリの改善から利益を得られるでしょうか。その答えは得られないです。上記の変更は、MQL5およびMetaTrader 5開発者による将来の改善を効果的に使用できるように、オブジェクト名のモデリングが正しいことを確認するために必要です。以下は、有用なアイテムの1つです。

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
                ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, (ticket > 1 ? '*' : def_SeparatorInfo)));
        else ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)it));
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
        m_InfoSelection.bIsMovingSelect = false;
        ChartRedraw();
}


覚えていないか見たことがない方のために、同じコードの以前のバージョンを以下に示します。コードは次のようになります。

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
{
#define macroDestroy(A, B)      {                                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND, B));                            \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE, B));                              \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE, B));                             \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT, B));                              \
                if (A != IT_RESULT)     ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE, B));      \
                else ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_PROFIT, B));                       \
                                }

        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
        {
                macroDestroy(IT_RESULT, true);
                macroDestroy(IT_RESULT, false);
                macroDestroy(IT_PENDING, true);
                macroDestroy(IT_PENDING, false);
                macroDestroy(IT_TAKE, true);
                macroDestroy(IT_TAKE, false);
                macroDestroy(IT_STOP, true);
                macroDestroy(IT_STOP, false);
        } else
        {
                macroDestroy(it, true);
                macroDestroy(it, false);
        }
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
#undef macroDestroy
}


コードがよりコンパクトになったように見えるかもしれませんが、それだけではありません。コードの削減は当然のことですが、真実はもっと深いところにあります。古いコードは、プラットフォームリソースをより適切に使用する新しいコードに置き換えられましたが、以前に使用されていたオブジェクト名のモデルではこの改善が見込めなかったため、MQL5関数の恩恵を受けることが期待できるようにモデリングを変更しました。この関数が何らかの理由でより改善された場合、EA構造に変更を加える必要なく、EAはこの変更の恩恵を受けるでしょう。ObjectsDeleteAll関数のことです。この関数を正しく使用すれば、MetaTrader 5がクリーンアップをおこないます。多くの詳細を指定する必要はありません。1つまたは複数のオブジェクトの名前を指定するだけで、後はMetaTrader 5に任せます。この関数が使用されるポイントは、新しいコードで強調表示されます。使用される接頭辞を通知するためにモデリングをおこなったことに注目してください。これは、オブジェクト名モデリングを変更する前には不可能でした。

新しいコードフラグメントの1つの詳細に注目していただきたいと思います。以下で強調表示されています。

if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
        ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, (ticket > 1 ? '*' : def_SeparatorInfo)));


強調表示された部分を追加したのはなぜだとお思いでしょうか。

これは、システムが値1で始まるチケットを作成した場合、指値注文が出されるとすぐに、すべてのオブジェクトが画面から削除されるからです。明確かと思います。指値注文を出すために使用される入力の値は1です。つまり、指標0は実際には0ではなく1の値を持ちます。0はEAで他のテストを実行するために使用されるためです。このため、初期値は1です。ここで問題が発生します。取引システムがチケット1221766803を作成したとします。その場合、このチケットを表すオブジェクトには、プレフィックスとして SMD_OT#1221766803が含まれます。EAがObjectsDeleteAll関数を実行して指標0を削除すると、オブジェクト名はSMD_OT#1になり、新しく作成されたシステムを含め、この値で始まるすべてのオブジェクトが削除されます。この問題を解決するために、名前の末尾に余分な文字を追加してObjectsDeleteAll関数に通知するように名前を少し調整して、指標0を削除するか、別の指標を削除するかを関数が認識できるようにします。

したがって、指標0を削除した場合、関数は値SMD_OT#1#を受け取ります。これにより、問題が回避されます。同時に、上記の例の場合、関数はSMD_OT#1221766803*という名前を取得します。単純なことのように思えますが、このため、なぜEAが新しく出された注文の指標オブジェクトを削除し続けるのか、当惑する可能性があります。

次に、興味深い詳細についてお話しましょう。関数の最後にChartRedrawの呼び出しがあります。ここで何故使用されているのでしょうか。MetaTrader 5自体がチャートを更新するはずです。確かに更新しますが、それがいつ起こるかは正確にはわからないのです。もう1つの問題があります。チャート上にオブジェクトを配置または削除する呼び出しはすべて同期的です。つまり、オブジェクトは特定の時間に実行されますが、これは必ずしも予期した時間とは限りません。ただし、受注システムはオブジェクトを使用して注文を表示または管理するため、オブジェクトがチャート上にあることを確認する必要があります。MetaTrader 5がすでにオブジェクトをチャートに配置またはチャートから削除したと考えることはできません。それを確認する必要があります。プラットフォームにこの更新を強制するのはそのためです。

ChartRedrawを呼び出すと、プラットフォームにチャート上のオブジェクトのリストが強制的に更新されるため、特定のオブジェクトがチャートに存在するかどうかを確認できます。これでもはっきりしない場合は、次のトピックに移りましょう。


2.0.2.より少ないオブジェクトでより高速に

以前のバージョンの初期化関数はぎこちないものでした。多くの繰り返しチェックがあり、いくつかは重複していました。いくつかのささいな問題に加えて、システムは既存の容量をほとんど再利用しませんでした。したがって、新しいモデリングを活用するために、初期化中に作成されるオブジェクトの数を減らすことにしました。システムは次のようになります。

void Initilize(void)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_OBJECT_DESCR, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_DRAG_TRADE_LEVELS, false);
        for (int c0 = OrdersTotal(); c0 >= 0; c0--) IndicatorInfosAdd(OrderGetTicket(c0));
        for (int c0 = PositionsTotal(); c0 >= 0; c0--) IndicatorInfosAdd(PositionGetTicket(c0));
}


すべてが変わったように見えますが、実際そうです。十分に使用されなかった関数を再利用するようになっています。これはチャートに指標を追加する関数です。この特別な機能を見てみましょう。

inline void IndicatorAdd(ulong ticket)
{
        char ret;
                                
        if (ticket == def_IndicatorTicket0) ret = -1; else
        {
                if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_PENDING, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_RESULT, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                if ((ret = GetInfosTradeServer(ticket)) == 0) return;
        }
        switch (ret)
        {
                case  1:
                        CreateIndicatorTrade(ticket, IT_RESULT);
                        PositionAxlePrice(ticket, IT_RESULT, m_InfoSelection.pr);
                        break;
                case -1:
                        CreateIndicatorTrade(ticket, IT_PENDING);
                        PositionAxlePrice(ticket, IT_PENDING, m_InfoSelection.pr);
                        break;
        }
        ChartRedraw();
        UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
}

上記のコードをよく見てください。コードに不要なチェックが含まれているように見える場合がありますが、それらは非常に単純な理由で存在します。この関数は、指値注文またはポジション指標を実際に作成する唯一の方法です。強調表示された2つの行は、指標が存在するかどうかを確認します。これをおこなうために、行として使用されるオブジェクトに値が格納されているかどうかがチェックされます。ここでは、オブジェクトが配置されている価格の値です。指示されたオブジェクトがチャート上にある場合、この値は非ゼロでなければなりません。それ以外の場合はすべて、オブジェクトが存在しないか、その他の理由でゼロになりますが、これは問題ではありません。チャートの更新を強制しなければならない理由が明確になったでしょうか。これがおこなわれなかった場合、EAは不必要にオブジェクトを追加するため、プラットフォームが未知の時間にこのアクションを実行するのを待つことはできません.チャートが更新されていることを確認する必要があります。そうしないと、これらのチェックが完了したときに、オブジェクトの現在の状態と実際には一致しないものが報告されて、システムの信頼性が低下します。

これらのチェックによりEAの速度が遅くなるように見えますが、これは概念的な間違えです。このようなチェックをおこない、作成キューに既にある可能性のあるオブジェクトをプラットフォームに強制的に作成させようとしない場合は、プラットフォームに「今すぐ更新」と伝えます。次に、必要なときにオブジェクトが作成済みかどうかを確認し、作成済みの場合は必要に応じて使用します。これは「正しいプログラミング」と呼ばれます。このようにして、プラットフォームの動作を軽減し、オブジェクトが作成されたかどうかの不必要なチェックを回避するため、処理したいデータがあることがわかっているため、EAの信頼性が向上します。

指定されたチケットに一致するオブジェクトが存在しないことが確認されるため、オブジェクトが作成されます。指標0を作成しているか、他の指標を作成しているかの最初に別のチェックがあることに注意してください。これにより、MetaTrader 5でサポートされている不要なオブジェクトがないことが保証されます。チャートで実際に使用するオブジェクトのみが表示されます。指標0を作成する場合、非常に特殊で特定の条件で作成するため、それ以上のテストは必要ありません。オブジェクト0は、SHIFTまたはCTRL+マウスを使用して注文を配置するために使用されます。心配する必要はありません。動作についてはすぐに確認します。

上記のコードには重要な点が1つあります。なぜUpdate関数を呼び出す前にチャートを更新するのでしょうか。無意味です。これを理解するために、以下のUpdateIndicators関数を見てみましょう。

void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
{
        double pr;
        bool b0 = false;
                                
        pr = macroGetLinePrice(ticket, IT_RESULT);
        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
        SetTextValue(ticket, IT_PENDING, vol);
        if (tp > 0)
        {
                if (b0 = (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_TAKE, EV_LINE, false), OBJPROP_PRICE) == 0 ? true : b0))
                        CreateIndicatorTrade(ticket, IT_TAKE);
                PositionAxlePrice(ticket, IT_TAKE, tp);
                SetTextValue(ticket, IT_TAKE, vol, (isBuy ? tp - pr : pr - tp));
        }
        if (sl > 0)
        {
                if (b0 = (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_STOP, EV_LINE, false), OBJPROP_PRICE) == 0 ? true : b0))
                        CreateIndicatorTrade(ticket, IT_STOP);
                PositionAxlePrice(ticket, IT_STOP, sl);
                SetTextValue(ticket, IT_STOP, vol, (isBuy ? sl - pr : pr - sl));
        }
        if (b0) ChartRedraw();
}

この関数は基本的に、リミットを指す指標を処理します。次に、強調表示された2つの行を見てください。チャートが更新されていない場合、これらの行はトリガーされず、値0が返されます。更新された場合、残りのコードは機能せず、リミット指標は画面に正しく表示されません。

しかし、リミット指標を作成する前に、本当に作成する必要があるのか、単に調整する必要があるのかを理解するためにいくつかのチェックをおこなう必要があります。これは、基本オブジェクトを作成するときと同じ方法でおこなわれます。ここでも、オブジェクトを作成するときにチャートを強制的に更新して、チャートが常に最新の状態になるようにします。

強制更新がこれほど多いのはなぜで、本当に必要なのかと疑問に思われるかもしれません。 そして、これに対する答えは、はっきりとした「はい、必要です」になります。そして、その理由は以下の関数です:。

inline double SecureChannelPosition(void)
{
        double Res = 0, sl, profit, bid, ask;
        ulong ticket;
                                
        bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
        ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                IndicatorAdd(ticket = PositionGetInteger(POSITION_TICKET));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (ask < sl) ClosePosition(ticket);
                }else
                {
                        if ((bid > sl) && (sl > 0)) ClosePosition(ticket);
                }
                Res += profit;
        }
        return Res;
};


この関数について特別なことは何もないと思うかもしれません。確かでしょうか。あります。この関数には重要なポイントがあります。オブジェクトがチャート上にあることを確認する必要があります。そうしないと、オブジェクトを作成するすべてのコードが数回呼び出され、MetaTrader 5によって管理される大きなキューが作成され、一部のデータが失われたり最新でなくなる可能性があります。これにより、システムが不安定になり、安全性と信頼性が低下します。オブジェクトを作成する関数の呼び出しが強調表示されています。MetaTrader 5が戦略的な瞬間にチャートを更新するように強制しなかった場合、問題が発生する可能性があります。これは、上記の関数がOnTickイベントによって呼び出され、ボラティリティが高い期間中、OnTickからの呼び出しの数が非常に多くなるためです。これにより、キュー内に過剰なオブジェクトが発生する可能性があります。これはまったくもって良くありません。データはChartRedraw呼び出しによって強制的に更新され、ObjectGetDoubleによって検証されるため、キューにオブジェクトが多すぎくなる可能性が低くなります。

システムの仕組みを見なくても、次のように考えるかもしれません。「TradeLineオブジェクトが誤って削除された場合、EAがこれに気づき、ObjectGetDoubleによるチェックが失敗して指標が失敗した場合に指標が再作成されるのは良いことです。」 これがアイデアですが、ユーザーがオブジェクトが何であるかを実際に知らずにオブジェクトのリストウィンドウに存在するオブジェクトを削除することはお勧めしません。これは、オブジェクト(TradeLineを除く)を削除すると、EAは指標がないことに気付かない可能性があるためです。そこにあるボタン以外にアクセスする方法がないため、アクセスする手段がありません。

上記のスクリプトは、その直後にあり、クラス内のメッセージフロー全体を維持する役割を担う関数がなかったら、本当に悪夢だったでしょう。ただし、これはまだ唯一のエントリポイントではありません。DispatchMessage関数です。見てみましょう。

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;
        bool    bKeyBuy,
                bKeySell,
                bEClick;
        datetime        dt;
        uint            mKeys;
        char            cRet;
        eIndicatorTrade it;
        eEventType      ev;
                                
        static bool bMounting = false, bIsDT = false;
        static double valueTp = 0, valueSl = 0, memLocal = 0;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Mouse.GetPositionDP(dt, price);
                        mKeys   = Mouse.GetButtonStatus();
                        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse click
                        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
                        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed
                        if (bKeyBuy != bKeySell)
                        {
                                if (!bMounting)
                                {
                                        Mouse.Hide();
                                        bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        m_InfoSelection.it = IT_PENDING;
                                        m_InfoSelection.pr = price;
                                }
                                m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
                                m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
                                m_InfoSelection.bIsBuy = bKeyBuy;
                                if (!bMounting)
                                {
                                        IndicatorAdd(m_InfoSelection.ticket = def_IndicatorTicket0);
                                        m_TradeLine.SpotLight(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_LINE, false));
                                        m_InfoSelection.bIsMovingSelect = bMounting = true;
                                }
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0))
                                {
                                        RemoveIndicator(def_IndicatorTicket0);
                                        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
                                }
                        }else if (bMounting)
                        {
                                RemoveIndicator(def_IndicatorTicket0);
                                Mouse.Show();
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetIndicatorInfos(sparam, ticket, it, ev))
                        {
                                if (GetInfosTradeServer(ticket) == 0) break;
                                CreateIndicatorTrade(ticket, it);
                                if ((it == IT_PENDING) || (it == IT_RESULT))
                                        PositionAxlePrice(ticket, it, m_InfoSelection.pr);
                                ChartRedraw();
				m_TradeLine.SpotLight();
                                m_InfoSelection.bIsMovingSelect = false;
                                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        ReDrawAllsIndicator();
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:
                                        if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
                                        {
                                                case IT_PENDING:
                                                case IT_RESULT:
                                                        if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                                                        break;
                                                case IT_TAKE:
                                                case IT_STOP:
							m_InfoSelection.ticket = ticket;
							m_InfoSelection.it = it;
                                                        m_InfoSelection.bIsMovingSelect = true;
                                                        SetPriceSelection(0);
                                                        break;
                                        }
                                        break;
                                case EV_MOVE:
                                        if (m_InfoSelection.bIsMovingSelect)
                                        {
                                                m_TradeLine.SpotLight();
                                                m_InfoSelection.bIsMovingSelect = false;
                                        }else
                                        {
                                                m_InfoSelection.ticket = ticket;
                                                m_InfoSelection.it = it;
                                                if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0))
                                                m_TradeLine.SpotLight(macroMountName(ticket, it, EV_LINE, false));
                                        }
                                        break;
                        }
                        break;
        }
}


この関数は非常に多くの変更を経てきたので、内部で何が起こっているかを説明するために小さな部分に分解します。プログラミングの経験があれば、その機能を理解するのは難しくありません。ただし、MQL5の初心者や熱狂的なプログラマーの場合、この機能を理解するのは少し難しい場合があるため、次のトピックで冷静に説明します。


2.0.3.DispatchMessage関数の分解

このトピックでは、DispatchMessage関数で何が起こるかについて説明します。コードを見るだけでその仕組みを理解できれば、このトピックから新しいことは何も学べません。

ローカル変数の次にあるのは静的変数です。

static bool bMounting = false, bIsDT = false;
static double valueTp = 0, valueSl = 0, memLocal = 0;


これらはクラス内でprivate変数として宣言できますが、コードのこの時点でのみ使用されるため、クラス内の他の関数がこれらの変数を参照しても意味がありません。関数が再度呼び出されたときに値を記憶する必要があるため、静的として宣言する必要があります。「static」キーワードを追加しないと、関数が終了するとすぐに値が失われます。これが完了すると、MetaTrader 5がEAに示すイベントの処理を開始します。

最初のイベントは次のとおりです。

case CHARTEVENT_MOUSE_MOVE:
        Mouse.GetPositionDP(dt, price);
        mKeys   = Mouse.GetButtonStatus();
        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse click
        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed


ここでは、マウスと、マウスに関連付けられている(キーボードの)いくつかのキーからデータを収集して分離します。それが完了すると、テストで始まる長いコードがあります。

if (bKeyBuy != bKeySell)


SHIFTキーまたはCTRLキーを1つずつ押すと、EAは特定の価格で注文したいことを理解します。その場合は、さらに確認します。

if (!bMounting)
{
        Mouse.Hide();
        bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl);
        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
        m_InfoSelection.it = IT_PENDING;
        m_InfoSelection.pr = price;
}


指標0がまだ設定されていない場合、このテストは成功します。マウスが非表示になり、Chart Tradeの値が取得されます。これらの値は、トレーダーがChartTradeを通じて示したレバレッジレベルに基づいてポイントに変換されます。注文が配置される初期値が表示されます。このシーケンスは、使用サイクルごとに1回だけ実行する必要があります。

次の手順は、テイクプロフィットレベルとストップロスレベルを作成し、売買するかどうかを示すことです。

m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
m_InfoSelection.bIsBuy = bKeyBuy;


マウスを別の価格帯に移動すると、テイクプロフィットとストップロスも移動する必要があるため、それらはサイクル外で作成されます。しかし、上記のコードがアセンブリテスト内にないのはなぜでしょうか。その理由は、マウスを動かさずにSHIFTキーを放してCTRLキーを押す、またはその逆の場合、画面上に指標が表示されている間に、テイクプロフィットとストップロス指標の値が交換されるためです。これを回避するには、この断片をテストから除外する必要がありますが、代わりに、以下に示す新しいアセンブリテストをおこなう必要があります。

if (!bMounting)
{
        IndicatorAdd(m_InfoSelection.ticket = def_IndicatorTicket0);
        m_TradeLine.SpotLight(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_LINE, false));
        m_InfoSelection.bIsMovingSelect = bMounting = true;
}

なぜ2つのテストがあるのでしょうか。1つだけでもいいでしょうか。1つが理想的ですが、上記のコードで強調表示されている関数では、これをおこなうことができません。この事実を理解するには、IndicatorAddを調べる必要があります。指標0を作成した後、それを選択済みとして設定し、既に実行され、ビルドされていることを示します。したがって、次の行で移動できます。

MoveSelection(price);


ただし、SHIFTまたはCTRLを押して指値注文を出すのと同じ基準内であっても、最終手順があります。

if ((bEClick) && (memLocal == 0))
{
        RemoveIndicator(def_IndicatorTicket0);
        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
}


これにより、指標注文がターゲットに正確に追加されます。2つの条件を満たす必要があります。1つ目はマウスの左ボタンのクリックで、2つ目は一度に同じ価格でそれをしなかったことです。つまり、同じ価格で2つ以上の注文を出すには、別の呼び出しでこの新しい注文を出す必要があります。これは、同じ呼び出しでは発生しないためです。 

チャートから指標0を削除すると同時に、適切に入力されたパラメータを含む注文が取引サーバーに送信されます。

次の手順に移りましょう。

if (bKeyBuy != bKeySell)
{

// ... code described so far ....

}else if (bMounting)
{
        RemoveIndicator(def_IndicatorTicket0);
        Mouse.Show();
        memLocal = 0;
        bMounting = false;
}

指標0が設定されていても、SHIFTまたはCTRLだけが押されたために条件が満たされない場合、強調表示されたコードが実行されてオブジェクトのリストから指標0が削除され、同時にマウスがリセットされ、静的変数が初期状態のままになります。つまり、システムはクリーンになります。

マウスイベント処理内の次の最後の手順を以下に示します。

if (bKeyBuy != bKeySell)
{

// ... previously described code ...

}else if (bMounting)
{

// ... previously described code ...

}else if ((!bMounting) && (bKeyBuy == bKeySell))
{
        if (bEClick) SetPriceSelection(price); else MoveSelection(price);
}



強調表示されたコードは、メッセージ処理の最後のマウス手順です。指標0もSHIFTキーもCTRLキーも異なる状態に設定していない場合、つまり同時に押すか離すことができる場合、左クリックすると価格が指標に送信され、マウスだけを動かすと価格は指標を動かすために使用されます。しかし、ここで、どの指標かという疑問があります。心配する必要はありません。どの指標かはすぐにわかりますが、ご参考までに、指標0はこの選択を使用しません。わからない場合は、このセクションの最初に戻って、このメッセージ処理のしくみを読んでください。

以下が次のメッセージです。

case CHARTEVENT_OBJECT_DELETE:
        if (GetIndicatorInfos(sparam, ticket, it, ev))
        {
                if (GetInfosTradeServer(ticket) == 0) break;
                CreateIndicatorTrade(ticket, it);
                if ((it == IT_PENDING) || (it == IT_RESULT))
                        PositionAxlePrice(ticket, it, m_InfoSelection.pr);
                ChartRedraw();
		m_TradeLine.SpotLight();
                m_InfoSelection.bIsMovingSelect = false;
                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
        }
        break;


上記で、EAには指標の誤った削除を防ぐための小さなセキュリティシステムがあると言ったのを覚えていますか。このシステムは、オブジェクトが削除されたときにMetaTrader 5によって送信されるイベントに関するメッセージを処理するためのコードに含まれています。

これが発生すると、MetaTrader 5はsparamパラメータを使用して、削除されたオブジェクトの名前を報告します。削除されたオブジェクトは、それが指標であったかどうか、また指標であった場合はどのオブジェクトに対してチェックされます。どのオブジェクトが影響を受けたかは問題ではありません。私たちが知りたいのは、どの指標が影響を受けたかです。その後、指標に関連する注文またはポジションがあるかどうかを確認し、ある場合は指標全体を再度作成します。極端な場合、影響を受ける指標が基本指標であった場合、指標が何であるかに関係なく、MetaTrader 5に即座に指標をチャートに配置するよう強制します。選択された指標を削除し、指標閾値データの更新を発注します。 

次に処理するイベントは非常に単純です。画面上のすべての指標のサイズを変更するように要求するだけです。そのコードを以下に示します。

case CHARTEVENT_CHART_CHANGE:
        ReDrawAllsIndicator();
        break;

これがオブジェクトクリックイベントです。

case CHARTEVENT_OBJECT_CLICK:
        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
        {
//....
        }
        break;


上記のように開始します。MetaTrader 5は、どのオブジェクトがクリックされたかを教えてくれるので、EAはどのタイプのイベントを処理するかを確認できます。これまでのところ、CLOSEとMOVEの2つのイベントがあります。最初に、画面上の指標の終了を閉じて定義するCLOSEイベントについて考えてみましょう。

case EV_CLOSE:
        if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
        {
                case IT_PENDING:
                case IT_RESULT:
                        if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                        break;
                case IT_TAKE:
                case IT_STOP:
			m_InfoSelection.ticket = ticket;
			m_InfoSelection.it = it;
                        m_InfoSelection.bIsMovingSelect = true;
                        SetPriceSelection(0);
                        break;
        }
        break;


CLOSEイベントは、チケットを使用して、サーバーを検索して決済が必要であるものを探し、決済するものがあるかどうかを確認します。これは、この時点でサーバーが既にこれを実行しているのにEAがそれについてはまだわかっていない可能性があるためです。決済するものがあるので、正しく実行して、必要なチェックと、チャートから指標を決済または削除するようにクラスに通知する正しい方法を取得します。

これで、このトピックの最後の手順に到達しました。これを以下に示します。

case EV_MOVE:
        if (m_InfoSelection.bIsMovingSelect)
        {
                m_TradeLine.SpotLight();
                m_InfoSelection.bIsMovingSelect = false;
        }else
        {
                m_InfoSelection.ticket = ticket;
                m_InfoSelection.it = it;
                if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0))
                m_TradeLine.SpotLight(macroMountName(ticket, it, EV_LINE, false));
        }
        break;


MOVEはまさにこれをおこなうイベント、移動する指標を選択します。選択するだけで、移動自体はマウス移動イベント中に実行されます。トピックの冒頭で、指標0を処理しない条件があると述べたことを思い出してください。それでも、何かは動きます。この何かは、この時点でMOVEイベントで示されます。ここで、移動するものが選択されているかどうかを確認します。その場合、選択されていた指標は選択されなくなり、マウス移動イベントを受信せず、新しい指標は選択されません。この場合、マウスデータを受け取る新しい指標からのデータは構造体に保存され、この指標は選択されたことを示す変更を受け取ります。この変化は線の太さに見られます。


2.0.4.新しいマウスオブジェクトクラス

上記で説明した改善点に加えて、言及に値する他の改善点があります。

ほとんどのトレーダーはEAに実装されたマウスベースの指標システムを必要としませんが、システムが完全に機能することを必要とし、望んでいるトレーダーもいます。しかし、トレーダーはマウス指標を構成するオブジェクトの一部を誤って削除する可能性があり、それが失敗につながります。幸いなことに、EVENTシステムを使用することでこれを回避できます。オブジェクト削除イベントが検出されてEAに送信されると、オブジェクトが属するクラスはオブジェクトを再作成できるため、システムが安定します。ただし、ポイントのリストをできるだけ小さくし、必要に応じて作成し、不要になったら削除することをお勧めします。これまでやってきたことですが、Mouseクラスがありませんでした。

定数名を作成するシステムを置き換えるために、いくつかの定義を作成することから始めましょう。

#define def_MousePrefixName "MOUSE "
#define def_NameObjectLineH def_MousePrefixName + "H"
#define def_NameObjectLineV def_MousePrefixName + "TMPV"
#define def_NameObjectLineT def_MousePrefixName + "TMPT"
#define def_NameObjectBitMp def_MousePrefixName + "TMPB"
#define def_NameObjectText  def_MousePrefixName + "TMPI"


その後、新しい初期化関数は次のようになります。

void Init(color c1, color c2, color c3)
{
        m_Infos.cor01 = c1;
        m_Infos.cor02 = c2;
        m_Infos.cor03 = c3;
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);
        Show();
}


以前のバージョンよりもはるかにシンプルになっていることに注意してください。この時点で、マウスシステムを表示する呼び出しがあります。呼び出しは、前のコードで強調表示された所で実行されます。価格軸に表示システムを実際に作成するコードを呼び出します。

inline void Show(void)
{
        if (ObjectGetDouble(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_PRICE) == 0)
        {
                ObjectCreate(Terminal.Get_ID(), def_NameObjectLineH, OBJ_HLINE, 0, 0, 0);
                ObjectSetString(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_TOOLTIP, "\n");
                ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_BACK, false);
        }
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_COLOR, m_Infos.cor01);
}

このコードは非常に興味深いものです。マウスポインタオブジェクトが価格に存在するかどうかを確認します。チェックが成功した場合は、チャートに線があるか、マウスに関連する何かがあることを意味するので、横線の色を調整するだけです。このチェックはなぜおこなうのでしょうか。これを理解するには、マウスに接続されたオブジェクトを非表示または削除する関数を見てください。以下が関数です。

inline void Hide(void)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(Terminal.Get_ID(), def_MousePrefixName + "T");
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
}


これは興味深い操作スタイルです。マウスに接続され、指定された名前を持つすべてのオブジェクトがMetaTrader 5チャートから削除されるため、オブジェクトのリストは常に小さくなります。ただし、水平線は削除されず、色のみが変更されます。したがって、マウスを表示する関数は、オブジェクトを作成する前にチェックを実行します。これは、実際にはオブジェクトのリストから除外されているわけではなく、非表示になっているだけだからです。ただし、他のすべてのオブジェクトはオブジェクトのリストから削除されます。研究中にこれらの他のオブジェクトをどのように使用するのでしょうか。研究は単に詳細を知りたいだけの短い時間であるため、リスト内のオブジェクトを1~2回だけ保持しても意味がありません。それらを作成し、調査をおこない、リストから削除することをお勧めします。これにより、より信頼性の高いシステムが得られます。

これはばかげているように思えるかもしれませんが、私たちが示す受注システムはオブジェクトの使用に基づいており、リスト内のオブジェクトが多いほど、特定のオブジェクトにアクセスしたいときにリストを検索するためにMetaTrader 5がしなければならない作業が増えます。チャートやオブジェクトのリストに余分なオブジェクトを残さず、システムをできるだけ軽量に保つべきです。

ここで、次のように始まるDispatchMessage関数に注目してください。

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        int     w = 0;
        uint    key;
        static int b1 = 0;
        static double memPrice = 0;


その直後に、最初のイベントの処理を開始するコードがあります。

switch (id)
{
        case CHARTEVENT_MOUSE_MOVE:
                Position.X = (int)lparam;
                Position.Y = (int)dparam;
                ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);
                ObjectMove(Terminal.Get_ID(), def_NameObjectLineH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
                if (b1 > 0) ObjectMove(Terminal.Get_ID(), def_NameObjectLineV, 0, Position.dt, 0);
                key = (uint) sparam;
                if ((key & 0x10) == 0x10)    //Middle button....
                {
                        CreateObjectsIntern();
                        b1 = 1;
                }


マウスの中ボタンを押すと、呼び出しが生成されますが、今はそうではありません。次に、この関数が何をするかを見ていきます。MetaTrader 5でサポートされているオブジェクトのリストにないため、存在しないオブジェクトを移動しようとしていることに注意してください。この呼び出しは、マウスの中央ボタンが押されたときにのみ発生します。トレーダーがスタディの生成に関与するセット内にいる時点を制御するb1変数に注意してください。

ユーザーがマウスの左ボタンをクリックして最初の手順が完了するとすぐに、次のコードが実行されます。

if (((key & 0x01) == 0x01) && (b1 == 1))
{
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
        ObjectMove(Terminal.Get_ID(), def_NameObjectLineT, 0, Position.dt, memPrice = Position.price);
        b1 = 2;
}

トレンド線を配置し、b1変数の値が変更される次の手順を呼び出します。この時点で、次の断片に進むことができます。

if (((key & 0x01) == 0x01) && (b1 == 2))
{
        ObjectMove(Terminal.Get_ID(), def_NameObjectLineT, 1, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectText, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectMove(Terminal.Get_ID(), def_NameObjectBitMp, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectBitMp, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
        ObjectSetString(Terminal.Get_ID(), def_NameObjectText, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
        ObjectMove(Terminal.Get_ID(), def_NameObjectText, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectText, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
}


この上記の断片は、実際に調査を画面に表示するものです。この断片にあるこれらのオブジェクトはすべてこのルーチン内で作成および破棄され、調査が終了すると存在しなくなります。これはあまり効率的ではないように見えますが、調査段階では処理時間の減少または増加に気付きませんでした。実際、受注システムにわずかな改善が見られましたが、これは非常に微妙なものであり、実際には比較見積もりの誤差の範囲内です。これらの変更によって実際に処理が改善されたとは言えません。

ただし、マウスの左ボタンが押されている間は調査が実行されることに注意してください。離すとすぐに、次の断片が実行されます。

if (((key & 0x01) != 0x01) && (b1 == 2))
{
        b1 = 0;
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
        Hide();
        Show();
}
Position.ButtonsStatus = (b1 == 0 ? key : 0);


ここでは、調査の作成に使用されたすべてのオブジェクトをオブジェクトのリストから削除します。もう一度画面にマウスラインを表示してみましょう。強調表示されたコードは、マウスボタンをキャプチャするときにEA内の関数またはサブルーチンが誤った読み取りを取得するのを防ぐ、優れたアイデアです。調査がおこなわれている場合、EAはボタンの状態を無視する必要があります。この目的のために、強調表示された行を使用します。これは完全な解決策ではありませんが、何もないよりはましです。

調査を実行するオブジェクトを作成するコードは考慮していません。しかし、これはかなり単純な機能なので、この記事では取り上げません。


結論

マイナーな変更に見えるかもしれませんが、それらはすべてシステム自体に大きな違いをもたらします。覚えておくべきことが1つあります。コマンドシステムは画面上のグラフィカルオブジェクトに基づいているため、EAが処理するオブジェクトが多いほど、特定のオブジェクトを要求したときのパフォーマンスが低下します。状況をさらに複雑にするために、システムはリアルタイムで動作します。つまり、EAのシステムが高速であるほど、パフォーマンスが向上します。したがって、EAがしなければならないことが少ないほど良いのです。理想的には、受注システムでのみ機能する必要があり、他のすべてを別のレベルに引き上げる必要があり、MetaTrader 5がそれを処理する必要があります。もちろん、これは徐々におこないます。多くの小さな変更を加える必要がありますが、それほど複雑なことはありません。これは、EAの信頼性を向上させることに専念する次のいくつかの記事でおこなわれます。

確かに言えることは、将来、EAは受注システムのみを担当するようになるということです。次の記事では、EAに非常に興味深い最終的な外観を与えます。受注システムは多量のオブジェクトが生成されるため、EAの操作中にリストに存在するオブジェクトの数をさらに減らします。MetaTrader 5で作成される負荷を最小限に抑えるようにこのシステムを変更する方法がわかります。

このため、コード自体が変更される可能性があるため、この記事には変更を添付していません。ご心配なさらないでください。次の記事は待つ価値があります。これらの変更により、EAの全体的なパフォーマンスが大幅に向上します。それでは、連載の次の記事でお会いしましょう。


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

DoEasy-コントロール(第12部):基本リストオブジェクト、ListBoxおよびButtonListBox WinFormsオブジェクト DoEasy-コントロール(第12部):基本リストオブジェクト、ListBoxおよびButtonListBox WinFormsオブジェクト
この記事では、WinFormsオブジェクトリストの基本オブジェクトと、2つの新しいオブジェクトを作成します。ListBoxとButtonListBoxです。
標準偏差による取引システムの設計方法を学ぶ 標準偏差による取引システムの設計方法を学ぶ
これは、MetaTrader 5取引プラットフォームで最も人気のあるテクニカル指標による取引システムの設計方法に関する連載の新しい記事です。この新しい記事では、標準偏差指標による取引システムの設計方法を学びます。
ニューラルネットワークが簡単に(第21部):変分オートエンコーダ(Variational autoencoder、VAE) ニューラルネットワークが簡単に(第21部):変分オートエンコーダ(Variational autoencoder、VAE)
前回の記事で、オートエンコーダアルゴリズムについて学びました。他のアルゴリズム同様、このアルゴリズムには長所と短所があります。元の実装では、オートエンコーダは、訓練標本からオブジェクトを可能な限り分離するために使用されます。今回はその短所への対処法についてお話します。
DoEasy-コントロール(第11部):WinFormsオブジェクト—グループ、CheckedListBox WinFormsオブジェクト DoEasy-コントロール(第11部):WinFormsオブジェクト—グループ、CheckedListBox WinFormsオブジェクト
この記事では、WinFormsオブジェクトのグループ化と、CheckBoxオブジェクトリストオブジェクトの作成について検討します。