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

一からの取引エキスパートアドバイザーの開発(第21部):新規受注システム(IV)

MetaTrader 5トレーディングシステム | 27 9月 2022, 15:42
825 0
Daniel Jose
Daniel Jose

はじめに

前回の記事「一からの取引エキスパートアドバイザーの開発(第20回)」では、注文のビジュアルシステムを作成するために必要な主な変更点を検討しました。ただし、その後のステップでは説明が必要になるため、記事を何回かに分けて掲載することにしました。ここでは主な変更を完成します。かなりの数になりますが、どれも必要なものばかりです。全体的にはなかなか面白いものになりそうです。ただし、本当にシステムを完成させるためには、まだやるべきことが残っているので、ここで完結させるわけにはいきません。とにかく、この記事が終わるまでには、必要な機能はほぼ揃っているはずです。

まっすぐ実装に移りましょう。


1.0.実装

まず、注文の[閉じる][キャンセル]ボタンを追加してみましょう。ボタンを担当するクラスは以下に示されています。

1.0.1.C_Object_BtnBitMapクラス

このクラスには、下図に示すように、チャート上のビットマップボタンをサポートする役割があります。

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Base.mqh"
//+------------------------------------------------------------------+
#define def_BtnClose    "Images\\NanoEA-SIMD\\Btn_Close.bmp"
//+------------------------------------------------------------------+
#resource "\\" + def_BtnClose
//+------------------------------------------------------------------+
class C_Object_BtnBitMap : public C_Object_Base
{
        public  :
//+------------------------------------------------------------------+
		void Create(string szObjectName, string szResource1, string szResource2 = NULL)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_BITMAP_LABEL);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 0, "::" + szResource1);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 1, "::" + (szResource2 == NULL ? szResource1 : szResource2));
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, false);
                        };
//+------------------------------------------------------------------+
                bool GetStateButton(string szObjectName) const
                        {
                                return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE);
                        }
//+------------------------------------------------------------------+
};


このコードクラスを書くときに、位置決めクラスをC_Object_Baseクラスに移動できることに気づきました。C_Object_BackGroundクラス全体では、下位クラスに属するため、このコードは不要になります。これをコードの再利用といいます。この方法では、プログラミングの手間が省け、パフォーマンスも向上し、なによりも頻繁に修正をチェックすることでコードがより安定します。

[閉じる]ボタンを追加するためには、次をおこないます。

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_TradeLine.mqh"
#include "C_Object_BtnBitMap.mqh"
//+------------------------------------------------------------------+
class C_ObjectsTrade
{

// ... Class code ...

}

次のステップ:

enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE};

その次のステップ:

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose;


その次のステップ:

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
                        {
                                color cor1, cor2;
                                string sz0;
                                

// ... Internal function code ...

                                switch (it)
                                {
                                        case IT_TAKE:
                                        case IT_STOP:
                                                m_BackGround.Size(sz0, 92, 22);
                                                break;
                                        case IT_PENDING:
                                                m_BackGround.Size(sz0, 110, 22);
                                                break;
                                }
                                m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }


その次のステップ:

#define macroDelete(A)  {                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND));       \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE));         \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE));        \
                        }
                                        
inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL)
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it)
                                else
                                {
                                        macroDelete(IT_PENDING);
                                        macroDelete(IT_RESULT);
                                        macroDelete(IT_TAKE);
                                        macroDelete(IT_STOP);
                                }
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                        }
#undef macroDelete


そして、いよいよ最後のステップ:

#define macroSetAxleY(A)        {                                               \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y);    \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y);    \
                                }
                                                                        
#define macroSetAxleX(A, B)     {                                               \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B);    \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3);\
                                }
inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy)
                        {

// ... Internal code...
                                
                        }
#undef macroSetAxleX
#undef macroSetAxleY


このシステムを実行すると、次のような結果が得られます。


ただし、このボタンはまだ機能しません。MetaTrader 5はエキスパートアドバイザーによって処理されるボタンのイベントを生成しますが、この機能はまだ実装されていません。これには、この記事で少し後に戻ってきます。


1.0.2.C_Object_Editクラス

取引されている値をトレーダーに知らせることができなければ、このシステムには意味がありません。このためにはC_Object_Editクラスがあります。このクラスには後に機能性を高めるためにいくつかの変更を加える必要がありますが、今はこのままにしておきます。何が起こっているかをトレーダーに知らせます。これを実装するために、クラスに数行のコードを追加する必要があります。次は、新しいコードを含む最初のスニペットです。

void Create(string szObjectName, color cor, int InfoValue)
{
        C_Object_Base::Create(szObjectName, OBJ_EDIT);
        ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, "Lucida Console");
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, 10);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_CENTER);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clrBlack);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, clrBlack);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true);
        SetTextValue(szObjectName, InfoValue, cor);
}

ハイライトされたコードは、トレーダーが値を変更することを防ぎますが、言ったように、これは将来的に変更されるでしょう。これには他にもいくつかの変更が必要になりますが、現時点では関係ありません。

以下の関数でテキストを表示させることができます。この関数には、1つのこだわりがあります。

void SetTextValue(string szObjectName, int InfoValue, color cor = clrNONE)
{
        color clr;
        clr = (cor != clrNONE ? cor  : (InfoValue < 0 ? def_ColorNegative : def_ColoPositive));
        ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, IntegerToString(InfoValue < 0 ? -(InfoValue) : InfoValue));
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, clr);
}

選択されたコードは、入力値に基づいてテキストの背景色を表示します。値がマイナスかプラスかを常に推測したり、テキストにマイナスの値があるかどうかを判断するのに時間を費やしたくないため、これをここでおこなうことが重要です。見ればすぐに値がプラスかマイナスかがわかるので、とても便利です。このコードでは、色から値がマイナスかプラスかを即座に判断できるようになっています。ただし、あらかじめ定義されていない色であることが条件となります。これも後で役に立ちます。

次に、下にあるのがこのクラスの最後の関数です。

long GetTextValue(string szObjectName) const
{
        return (StringToInteger(ObjectGetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT)) * 
                                (ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR) == def_ColorNegative ? -1 : 1));
};


値を表現する場合、その書式により常に正値になることにお気づきかもしれません。しかし、オブジェクトの中身を確認するときは、正しい情報を持っていなければなりません。ここで、ハイライトされたコードが使用されます。色の情報はここで使われます。色が示す値が負であれば、EAに正しい情報を提供するために補正されます。色が正の値を示している場合は、その値が保存されます。

色の定義は、クラス自体にあります。他の色を設定したい場合は、これらを変更することができますが、前の関数が正しく動作するように、必ず異なる色を使用する必要があります。そうしないと、EAがあいまいな値を取得します。つまり、EAが負の値を正とみなしてしまい、EAがおこなうう解析全体に問題が生じる可能性があります。


1.0.3.C_Object_Labelクラス

このクラスは、現段階では必要ありません。実は、このクラスの動作はC_Object_BtnBitMapクラスと似ているので、作らないようにしようかと思っていました。でも、C_Object_Editクラスとは独立してテキスト情報を追加できるようにしたかったので、ここで新しいクラスを作成することにしました。

そのコードはものすごくシンプルです。以下をご覧ください。

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Edit.mqh"
//+------------------------------------------------------------------+
class C_Object_Label : public C_Object_Edit
{
        public  :
//+------------------------------------------------------------------+
                void Create(string szObjectName, string Font = "Lucida Console", string szTxt = "", int FontSize = 10, color cor = clrBlack)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_LABEL);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, Font);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, szTxt);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, FontSize);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor);
                        };
//+------------------------------------------------------------------+
};


残りの作業はすべて下位クラスのオブジェクトによってすでに実装されているので、このクラスには何も必要ないのです。

このように、OOPは非常に強力なツールです。。コードをクラス単位で整理すればするほど、似たようなクラスをプログラミングする必要がなくなります。

ただし、ちょっとした変更を実装する必要があります。実験しているうちに、パネルデータの解釈が非常に難しいことに気づいたので、以下のように変更しました。

   

こうすれば、大きな値を表示することが容易になります。上部は契約数またはポジションのレバレッジファクター、下部はポジションの結果を示しています。

これを実装するには、主にオブジェクトの位置決めを担当するC_Object_Baseクラスを変更する必要があります。変更点は以下のコードでハイライトされています。

virtual void PositionAxleY(string szObjectName, int Y, int iArrow = 0)
                        {
                                int desl = (int)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, (iArrow == 0 ? Y - (int)(desl / 2) : (iArrow == 1 ? Y : Y - desl)));
                        };


その後、次のステップであるC_ObjectsTradeクラスの変更に進みます。


1.0.4C_ObjectsTradeクラス

次に必要なオブジェクトの描画を完了しましょう。そうすれば、実際にチャートで目的の結果を得ることができます。提示されたすべての情報とそれに接続されたすべてのオブジェクトが得られます。難しくはありません。ステップごとにアクションを分析していきます。その方法を理解していれば、あとは指示に従って好きな情報を追加できるようになります。最初にやるべきことは、オブジェクトが応答する新しいイベントを定義することです。以下のコードでハイライトされています。

enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE, EV_EDIT, EV_VOLUME, EV_MOVE};

では、必要なオブジェクトを追加してみましょう。新しいオブジェクトは以下のコードでハイライトされています。

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;
C_Object_BtnBitMap      m_BtnClose;
C_Object_Edit           m_EditInfo,
                        m_InfoVol;
C_Object_Label          m_BtnMove;


その後、オブジェクトを作成し、画面上でどのように見えるかを定義します。オブジェクトは、表示される順番に作成する必要があります。最初に背景オブジェクトを作成し、次に背景の上に配置するオブジェクトを作成し、すべてのオブジェクトが作成されるまでこれを繰り返します。順番を間違えてオブジェクトが隠れてしまった場合は、このコードでその位置を変更するだけで大丈夫です。さて、コードは次のとおりです。

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
{
        color cor1, cor2, cor3;
        string sz0;
        int infoValue;
                                
        switch (it)
        {
                case IT_TAKE    :
                        infoValue = m_BaseFinance.FinanceTake;
                        cor1 = clrForestGreen;
                        cor2 = clrDarkGreen;
                        cor3 = clrNONE;
                        break;
                case IT_STOP    :
                        infoValue = - m_BaseFinance.FinanceStop;
                        cor1 = clrFireBrick;
                        cor2 = clrMaroon;
                        cor3 = clrNONE;
                        break;
                case IT_PENDING:
                        infoValue = m_BaseFinance.Leverange;
                        cor1 = clrCornflowerBlue;
                        cor2 = clrDarkGoldenrod;
                        cor3 = clrLightBlue;
                        break;
                case IT_RESULT  :
                default:
                        infoValue = m_BaseFinance.Leverange;
                        cor1 = clrDarkBlue;
                        cor2 = clrDarkBlue;
                        cor3 = clrSilver;
                        break;
                }                               
                m_TradeLine.Create(MountName(ticket, it, EV_LINE), cor2);
                if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE));
                m_BackGround.Create(sz0 = MountName(ticket, it, EV_GROUND), cor1);
                switch (it)
                {
                        case IT_TAKE:
                        case IT_STOP:
                        case IT_PENDING:
                                m_BackGround.Size(sz0, 92, 22);
                                break;
                        case IT_RESULT:
                                m_BackGround.Size(sz0, 84, 34);
                                break;
                }
                m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose);
                m_EditInfo.Create(sz0 = MountName(ticket, it, EV_EDIT), cor3, infoValue);
                m_EditInfo.Size(sz0, 60, 14);
                if (it != IT_RESULT) m_BtnMove.Create(MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2);
                else
                {
                        m_InfoVol.Create(sz0 = MountName(ticket, it, EV_VOLUME), clrNONE, infoValue);
                        m_InfoVol.Size(sz0, 60, 14);
                }
}

ハイライトされている行はすべて、前回の記事で紹介した最後のバージョンから追加されたコードです。これで、次の関数のコードを書くことができます。

#define macroDelete(A)  {                                                                       \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND));               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE));                 \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE));                \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT));                 \
                if (A != IT_RESULT)                                                             \
                        ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE));         \
                else                                                                            \
                        ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_VOLUME));       \
                        }
                                        
inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL)
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it)
                                else
                                {
                                        macroDelete(IT_PENDING);
                                        macroDelete(IT_RESULT);
                                        macroDelete(IT_TAKE);
                                        macroDelete(IT_STOP);
                                }
                                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                        }
#undef macroDelete


マクロを使うメリットに注目してください。パネルのオブジェクトをすべて削除できるように、ハイライトされた部分のみを追加する必要がありました。今は4つのパネルで6つのオブジェクトを使っています。これを別の方法で実装するとなると、手間がかかりすぎるため、エラーが発生する可能性が高くなります。位置決め関数を仕上げましょう。

#define macroSetAxleY(A)        {                                                                       \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y);                         \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y);                            \
                m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y);                            \
                m_EditInfo.PositionAxleY(MountName(ticket, A, EV_EDIT), y, (A == IT_RESULT ? -1 : 0));  \
                if (A != IT_RESULT)                                                                     \
                        m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE), y);                      \
                else                                                                                    \
                        m_InfoVol.PositionAxleY(MountName(ticket, A, EV_VOLUME), y, 1);                 \
                                }
                                                                        
#define macroSetAxleX(A, B)     {                                                               \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B);                 \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B);                    \
                m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3);                \
                m_EditInfo.PositionAxleX(MountName(ticket, A, EV_EDIT), B + 21);                \
                if (A != IT_RESULT)                                                             \
                        m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE), B + 80);         \
                else                                                                            \
                        m_InfoVol.PositionAxleX(MountName(ticket, A, EV_VOLUME), B + 21);       \
                                }
                                                                                
inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy)
                        {
                                double ad;
                                int x, y;
                                
                                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y);
                                macroSetAxleY(it);
                                macroSetAxleX(it, m_PositionMinimalAlxeX);
                                if (Leverange == 0) return;
                                if (it == IT_PENDING)
                                {
                                        ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal());
                                        ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))), x, y);
                                        macroSetAxleY(IT_TAKE);
                                        macroSetAxleX(IT_TAKE, m_PositionMinimalAlxeX + 110);
                                        ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)), x, y);
                                        macroSetAxleY(IT_STOP);
                                        macroSetAxleX(IT_STOP, m_PositionMinimalAlxeX + 220);
                                }
                        }
#undef macroSetAxleX
#undef macroSetAxleY


ここでも、ほとんどコードは追加されていません。それでも、マクロの使用により正しく配置されるため、この関数はすべての要素で動作できます。この段階でEAをコンパイルすると、次のような結果になります。


すべてが良さそうに見えますが、これらのコントロールはまだ機能しません。オブジェクトごとにイベントを実装する必要があります。イベントがなければ、このインターフェイスはほとんど役に立ちません。なぜなら、実際にできることは、もともと使われていた行を置き換えることだけだからです。


2.0.問題の解決方法

簡単なことなら、誰にでもできるはずです。ただし、解決すべき問題は常にあり、これは開発プロセスの一部でもあります。解き方を示さずに解答を示すこともできるでしょうが、これらの記事は、問題に対処し、実際にプログラミングを学ぶための動機付けにしたいのです。このセクションには何か面白いことがありそうです。


2.0.1.チャートの更新に合わせた調整

これが1番目の問題で、チャートの更新に伴ってオブジェクトの位置が更新されないことに起因しています。理解するために、下のGIFをご覧ください。

このようなことがあると頭に来ますが、解決方法はとてもシンプルです。MetaTrader 5自体は、チャートの更新が必要であることを通知するイベントを生成するので、必要なのは、イベントを捕捉して受注システムを更新することです。

CHARTEVENT_CHART_CHANGEイベント呼び出しで変更を捕捉する必要があります。更新には、EAコードにあるUpdatePosition関数を使うと簡単です。コードにたった1行を追加するだけです。これは、以下のようにC_OrderViewでおこなわれます。

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{

// ... Code ....
                                
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        SetPositionMinimalAxleX();
                        UpdatePosition();
                        break;

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

このシンプルな解決策には、1つの問題があります。1つの資産に対して多くの注文がある場合、時間がかかるため、EAは次の処理に移る前に更新に行き詰まる可能性があります。他にもより複雑なより速い解決策はありますが、このシステムにはこのソリューションで十分です。結果は以下のとおりです。


すべてが正しく思えると思うのですが、ここには誤りがあります。システムをテストしてみないとなかなかわかりませんが、このシステムには、直しても直しても、どうにもならない欠点が確かにあるのです。


2.0.2.EAによる自動選択を阻止する

上のGIFを見ると、選択しなかったのに、ストップの線が選択されているのがわかります。EAが毎回やっていることです。パネル作成時の設定によっては、チャートを移動するたびにRAがテイクプロフィットやポジションの線を選択するなどということが起こり得ます。

何が起こっているのか理解しようとすると気が狂いそうになりますが、解決策は先ほどよりもさらに簡単です。同じコードに1行追加するだけで、EAは自動的に線を選択しなくなります。修正方法は、以下のコードでハイライトされています。

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
{
        color cor1, cor2, cor3;
        string sz0;
        double infoValue;

// ... Internal code...

        Select(NULL);
}

こういうことは、開発中に必ず起こります。バグには、見つけやすいものと、あまり目立たないために最初は気づかないものがあります。とにかく、これは起こりうることです。そのため、プログラミングの学習は継続的におこなう必要があります。このような問題を自分で解決し、皆と共有して他の人が同じ問題を解決することができるようにすることができます。やってみてください。私自身は、そうやって実践的にプログラミングを学んできました。同じように、プログラムの作り方も学ぶことができます。一般に、ソースコードを入手して修正することは学習の一環です。これは機能的なコードでおこなうべきです。そうすれば、それがどのように構築されたかを理解するのがより簡単になります。各プログラマーが特定の問題をどのように解決したかを理解することで多くのことを学べるため、多くの場合、良い成果をもたらします。

これは勉強のモチベーションを上げるためのものだったので、本当に大事なことに移りましょう。


3.0.イベント処理

私たちの計画はポジションの結果を報告するシステムを構築することです。これは、Chart Tradeの該当箇所に置き換わりますが、Chart Tradeはポジションの合計結果を示すので、そのままにしておきます。口座がヘッジをサポートしている場合、1つはローカル値、もう1つは合計値であるため、注文システムで指定された値とは異なることになります。他の種類の口座ではこのような違いはないので、ご希望であれば、Chart Tradeから結果システムを削除することができます。

3.0.1.ポジション結果の表示

初めてコードを見る人は、どこで情報を探せばいいのかわからず迷ったり、元のコードがすでにやっていることを別の操作で作ってしまったりすることがあります。これは通常、多くの問題を引き起こします。元のコードが持っていなかった余分なバグを発生させる余分なコードを作成する可能性があるからです。これでは、「本当に必要なときだけプログラムする」という「REUSE」の法則に反します。そこで、MetaTrader 5の仕組みを知り、EAがすでにどのように動作しているかを知った上で、Chart Tradeに表示される結果が生成される場所を探すべきです。ポジションの結果が提示されているのであれば、それを使うべきだからです。以下のコードに注目してください。

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
        TimesAndTrade.Update();
}


次に、ハイライトされたポイントに移ります。ソースコードを以下に示します。

inline double CheckPosition(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())
                                {
                                        ticket = PositionGetInteger(POSITION_TICKET);
                                        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がどのように動作するのか、もう少し勉強されたほうがよいでしょう。確かに、作成中のオブジェクトを参照するリストや配列、構造体の類は作っていませんが、これは意図したものです。これで機能するとわかっているからです。この方法が本当に有効であることをお見せしましょう。チャート上に配置されるオブジェクトを格納するための構造体を一切使用せずに、特定のオブジェクトを参照することができます。本当に必要なことはオブジェクト名を正しくモデル化することです。それだけです。

質問:オブジェクト名はどのようにモデル化されたのですか

その答えは次の通りです。

1-ヘッダーの配列 このシーケンスにより、受注システムで使用されるオブジェクトが他のすべてのオブジェクトと区別される
2-文字制限 他の情報が続くことを示す
3-タイプ表示 TakeとStopを区別する
4-文字制限 2と同様
5-受注チケット、ポジションチケット 受注チケットの記憶-OCO受注データとの連携、受注の区別が可能
6 - リミッターキャラクター 2と同様
7-イベント表示 同じパネル内のオブジェクトを区別する

つまり、モデリングがすべてなのです。実際にプログラミングをしていない人には、繰り返し何かを作っているように見えても、実はユニークなものを作っているのです。それぞれのオブジェクトはユニークで、簡単なルールで参照することができます。そして、このルールは次のコードで作成されます。

inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev)
                        {
                                return StringFormat("%s%c%c%c%d%c%c", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)ev);
                        }


したがって、前のコードで、どの注文チケット、どの指標、どのイベントにアクセスしたいかを伝えれば、特定のオブジェクトの名前を受け取るので、その属性を操作することができます。これが最初のステップであることを理解した上で、もう1つ決断しなければならないことがあります。それは、この操作を、安全に、コードに大混乱をもたらさずモンスターに変身させずにおこなうにはどうしたらいいかということです。

やってみましょう。C_ObjectsTradeクラスで次のコードを追加します。

inline void SetResult(ulong ticket, double dVolume, double dResult)
                        {
                                m_InfoVol.SetTextValue(MountName(ticket, IT_RESULT, EV_VOLUME), (dVolume / Terminal.GetVolumeMinimal()), def_ColorVolumeResult);
                                m_EditInfo.SetTextValue(MountName(ticket, IT_RESULT, EV_EDIT), dResult);
                        }


次に、C_Routerクラスに移動して、ハイライトされたコードを追加します。

inline double CheckPosition(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())
                                {
                                        ticket = PositionGetInteger(POSITION_TICKET);
                                        SetResult(ticket, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT));
                                        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;
                        };

これで問題の1つが解決しましたが、まだ他の未解決の問題を抱えています。


3.0.2.未決注文の数量を表示する

次に、未決注文で表示される数量の問題を解決してみましょう。そのためには、C_ObjectsTradeクラスに新しい関数を作成する必要があります。

inline void SetVolumePendent(ulong ticket, double dVolume)
                        {
                                m_EditInfo.SetTextValue(MountName(ticket, IT_PENDING, EV_EDIT), dVolume / Terminal.GetVolumeMinimal(), def_ColorVolumeEdit);
                        }


これができたら、C_RouterクラスのUpdatePosition関数を使えば、スムーズに更新がおこなわれます。

void UpdatePosition(int iAdjust = -1)
{

// ... Internal code ....

        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                take = OrderGetDouble(ORDER_TP);
                stop = OrderGetDouble(ORDER_SL);
                bTest = CheckLimits(price);
                vol = OrderGetDouble(ORDER_VOLUME_CURRENT);

// ... Internal code...

                CreateIndicatorTrade(ul, price, IT_PENDING);
                SetVolumePendent(ul, vol);
                CreateIndicatorTrade(ul, take, IT_TAKE);
                CreateIndicatorTrade(ul, stop, IT_STOP);
        }
};


こうして、問題は解決されます。次にTake と Stop として示される値の問題を解決する必要があります。これは、これらの値がチャート上で注文を出した後では正しくないためです。


3.0.3.パネルの[閉じる]ボタンクリックイベント

注文またはそのストップレベルの1つを削除する唯一の安全な方法は、各数値の隅にある[閉じる]ボタンです。ただ、ここでは間違ったイベントが実装されています。これを修正しましょう。

クリックイベントは、実際にはC_OrderViewクラスで実装する必要があります。古いシステムをハイライトされたコードに置き換えます。

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   	ticket;
        double  	price, pp, pt, ps;
        eIndicatorTrade it;
        eEventType 	ev;
                                
        switch (id)
        {

// ... Internal code...
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetInfosOrder(sparam, ticket, price, it, ev))
                        {
                                switch (ev)
                                {
                                        case EV_CLOSE:
                                                if (OrderSelect(ticket)) switch (it)
                                                {
                                                        case IT_PENDING:
                                                                RemoveOrderPendent(ticket);
                                                                break;
                                                        case IT_TAKE:
                                                                ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL));
                                                                break;
                                                        case IT_STOP:
                                                                ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0);
                                                                break;
                                                }
                                                if (PositionSelectByTicket(ticket)) switch (it)
                                                {
                                                        case IT_RESULT:
                                                                ClosePosition(ticket);
                                                                break;
                                                        case IT_TAKE:
                                                                ModifyPosition(ticket, 0, PositionGetDouble(POSITION_SL));
                                                                break;
                                                        case IT_STOP:
                                                                ModifyPosition(ticket, PositionGetDouble(POSITION_TP), 0);
                                                                break;
                                                }
                                                break;

// ... Rest of the code...


このクラスではもう1つ追加するものがあります。トレーダーが誤ってポジションデータを報告するオブジェクトを削除してしまった場合、どうなるでしょうか。これを回避するために、以下のようなコードを追加します。

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eIndicatorTrade it;
        eEventType      ev;
                                
        switch (id)
        {
                case CHART_EVENT_OBJECT_DELETE:
                case CHARTEVENT_CHART_CHANGE:
                        SetPositionMinimalAxleX();
                        UpdatePosition();
                        break;

// ... Rest of the code...

こうすることで、オペレータが削除してはいけないものを削除しても、EAがすぐに削除されたオブジェクトを置き換えることができます。

現在のシステムの様子は次の動画でわかります。また、説明には影響しないマイナーチェンジであるため、記事で取り上げなかった変更もあります。




結論

システムは一見すると完成しており、このシステムで取引したいと思われるかもしれませんが、まだ完成していないことを警告しておきます。この記事では、より実用的で使いやすい受注システムを持つために、どのように追加・変更すればよいかを紹介する予定でしたが、ポジションを移動させるためのシステムがまだ不足しています。これは、EAを非常に有益で実用的、かつ直感的に使えるものにするのです。これは次回の記事に譲ることにします。

添付ファイルには現段階でのシステムが含まれています。


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

添付されたファイル |
知っておくべきMQL5ウィザードのテクニック(第01回):回帰分析 知っておくべきMQL5ウィザードのテクニック(第01回):回帰分析
今日のトレーダーは哲学者であり、ほとんどの場合(意識的かどうかにかかわらず...)新しいアイデアを探し、試し、変更するか破棄するかを選択します。これは、かなりの労力を要する探索的プロセスです。トレーダーの時間とミスを避ける必要性は明らかに重視されます。この連載では、MQL5ウィザードがトレーダーの主力であるべきであることを示します。なぜでしょうか。MQL5ウィザードを使用すれば、新しいアイデアを組み立てることで時間を節約できるだけでなく、コーディングの重複によるミスを大幅に減らすことができるため、最終的に、取引の哲学のいくつかの重要な分野にエネルギーを注ぐことができるからです。
一からの取引エキスパートアドバイザーの開発(第20部):新規受注システム(III) 一からの取引エキスパートアドバイザーの開発(第20部):新規受注システム(III)
新しい受注システムの導入を継続します。このようなシステムを作るには、MQL5を使いこなすだけでなく、MetaTrader 5プラットフォームが実際にどのように機能し、どのようなリソースを提供しているかを理解することが必要です。
データサイエンスと機械学習(第05回):決定木 データサイエンスと機械学習(第05回):決定木
決定木は、人間の思考方法を模倣してデータを分類します。木を作り、それを使ってデータを分類・予測する方法を見てみましょう。決定木アルゴリズムの主な目的は、不純物を含むデータを純粋なノードまたはそれに近いノードに分離することです。
一からの取引エキスパートアドバイザーの開発(第19部):新規受注システム(II) 一からの取引エキスパートアドバイザーの開発(第19部):新規受注システム(II)
今回は、「見てわかる」タイプのグラフィカルな受注システムを開発します。なお、今回はゼロから始めるのではなく、取引する資産のチャート上にオブジェクトやイベントを追加して既存のシステムを修正します。