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

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

MetaTrader 5トレーディングシステム | 27 9月 2022, 10:54
342 0
Daniel Jose
Daniel Jose

はじめに

前回の「一からの取引エキスパートアドバイザーの開発(第19回)」では、新しい注文システムの運用を可能にするために実装したコードの変更に焦点を当てました。この変更がおこなわれたことで、本当の問題に100%集中することができるようになりました。ティック値や、Xを稼ぐために注文を出す場所やYを失わないためにストップロスを設定する場所をわからずに取引する人が100%視覚的に理解できる注文システムを実装することです。

このようなシステムを作るには、MQL5を使いこなすだけでなく、MetaTrader 5プラットフォームが実際にどのように機能し、どのようなリソースを提供しているかを理解することが必要です。


1.0.計画

1.0.1.指標の設計

ここでの考えは、アイデアだけでなく、実際にどのようにシステムを実装するかをこの記事で紹介することです。以下の画像に示されるようなものを作成します。

私が説明しなくても、とてもわかりやすいと思います。閉じるボタン、値、ポイントがあり、ドラッグして注文しやすくなっています。ただし、これですべてではありません。ストップロスがストップゲインに変わった場合は次のように処理されます。

よって、あるポジションをいつ、いくらで、どこで持つ価値があるのかどうかが簡単にわかるのです。

上の図は、OCO注文やOCOポジションリミットの対象だけを示していますが、始値に関する部分も同様に重要なので、忘れてはいません。

未決注文の場合、次のように表示されます。

これは、ポジションによって少し違って見えるでしょう。

ただし、その比率はあまりよくありません。まあ、これが実行されるアイデアです。色については、ここで紹介したものを使うことにしますが、好きなものをお選びください。

計画を進めていくと、基本的に各指標に5つのオブジェクトを用意することになるのに気づきます。MetaTrader 5は、各指標に対して5つのオブジェクトを同時に処理する必要があるということです。OCO注文の場合、MetaTrader 5は、OCOポジションの場合と同様に、1つの注文またはポジションにつき15個のオブジェクトを処理する必要があります。つまり、4つの保留中のOCO注文と1つのオープンOCOポジションがある場合、MetaTrader5は、チャート上にある他のオブジェクトとは別に、25のオブジェクトを処理しなければならないことを意味します。そして、これはプラットフォームで1つのアセットしか使用していない場合です。

というのも、取引する商品ごとに必要なメモリや処理量を把握することが重要だからです。これは最近のコンピュータでは問題ないのですが、ハードウェアに何を要求するのかを具体的に知る必要があります。従来は、注文のポイントごとに1つのオブジェクトが画面に表示されているだけでしたが、各ポイントに5つのオブジェクトがあり、それらが何らかの形でつながっていることが必要になりました。この接続はプラットフォームで実装されるので、私たちはどのように接続するか、それぞれのオブジェクトがトリガーしたときにどうするかを指示するだけです。


1.0.2.オブジェクトの選択

次に問題となるのは、使用するオブジェクトの選択です。これは単純な質問のように思えるかもしれませんが、実際にどのように実装するかを決定する、非常に重要な質問です。最初の選択肢は、画面上のオブジェクトの配置方法に基づいています。

これを実装する方法は2つあります。幸いなことに、MetaTrader 5はその両方をカバーしています。1番目は時間と価格座標による位置決めを使用し、2番目はデカルトのXとY座標を使用します。

ただし、そのうちの1つを詳しく説明する前に、時間と価格の座標を使うモデルを即座に破棄します。一見、理想的な形ではありますが、多くのオブジェクトが互いにつながり、一緒にいなければならない状況では役に立ちません。したがって、直交座標系を使うことになります。

以前の記事では、すでにこのシステムを見て、オブジェクトを選択する方法について説明しました。詳しくは、「単一チャート上の複数インジケータ(第05部)」をご覧ください。

計画が完了したので、いよいよコーディングそのもの、つまり実際に実装する段階に入ります。


2.0.実装

このシステムを単に実装することは目的ではありません。その中で何が起こっているのかを説明することで、検討したシステムを基に読者が自分自身のシステムを作ることができるように、少しずつ詳細を説明していきます。そうすることで、どのように作成されているのかが理解できます。システムは同じ原則に従い、共通のコードを持っているので、未決注文に関連するすべての機能は、ポジションにも機能します。


2.0.1.インターフェイスフレームワークの作成

この最初のステップの結果は、以下のようになります。これは、私がこのコードを開発して皆さんと共有することを決めたときのように、皆さんがワクワクするようにメリットを示す、私なりの方法です。これからプログラミングを学びたい方、より深い知識を身につけたい方のモチベーションになればと思います。


上の画像を見ると、これまで作成したコードを除いて、通常の方法で機能を作成したように思われるかもしれませんが、そうではなく、これまで作られてきたものをそのまま使います。

前回の記事で紹介したコードを使い、若干の変更を加えることにします。コードの新機能に注目しましょう。まず、3つの新しいクラスを追加します。


2.0.1.1.C_Object_Baseクラス

まず、新しいC_Object_Baseクラスを作成することから始めます。これは私たちのシステムの中で最も低いクラスです。クラスの最初のコードは以下の通りです。

class C_Object_Base
{
        public  :
//+------------------------------------------------------------------+
void Create(string szObjectName, ENUM_OBJECT typeObj)
{
        ObjectCreate(Terminal.Get_ID(), szObjectName, typeObj, 0, 0, 0);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTABLE, false);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTED, false);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, true);
        ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TOOLTIP, "\n");
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, false);
        ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TOOLTIP, "\n");
        PositionAxleY(szObjectName, 9999);
};

// ... The rest of the class code

あるのは、物事をより簡単にする一般的なコードです。同じクラスには、標準のXとYの位置決めコードがあります。

void PositionAxleX(string szObjectName, int X)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XDISTANCE, X);
};
//+------------------------------------------------------------------+
virtual void PositionAxleY(string szObjectName, int Y)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, Y);
};

Yの位置決めコードは、特定のオブジェクトに依存しますが、オブジェクトが特定のコードを持たない場合でも、クラスは一般的なコードを提供します。下記は一般的な色指定メソッドです。

virtual void SetColor(string szObjectName, color cor)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor);
}

そして、オブジェクトの寸法は次のように定義します。

void Size(string szObjectName, int Width, int Height)
{
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XSIZE, Width);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE, Height);
};

C_Object_Baseクラスについては、今はこれですべてすが、後でまた紹介します。


2.0.1.2.C_Object_BackGroundクラス

さて、2つのグラフィカルオブジェクトをサポートするために、他の2つのクラスを作成しましょう。1番目がC_Object_BackGroundです。他の要素を受け取るための背景ボックスを作成するもので、そのコードはとても簡単です。以下で全部です。

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Base.mqh"
//+------------------------------------------------------------------+
class C_Object_BackGround : public C_Object_Base
{
        public:
//+------------------------------------------------------------------+
		void Create(string szObjectName, color cor)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_RECTANGLE_LABEL);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clrNONE);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, cor);
                        }
//+------------------------------------------------------------------+
virtual void PositionAxleY(string szObjectName, int Y)
                        {
                                int desl = (int)(ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE) / 2);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, Y - desl);
                        }
//+------------------------------------------------------------------+
};

最小限のコードでオブジェクトを組み立てるために、継承を使用しています。このように、必要に応じてクラスを修正してモデル化することで、後でこのような調整をする必要がないようにします。このクラスは、Y軸の値を知るだけで、自動的に自分自身を正しい位置に配置します。サイズを確認して、渡された軸の中央に位置するように自分自身を配置します。


2.0.1.3.C_Object_TradeLineクラス

C_Object_TradeLineクラスは、これまで注文の価格線の位置を示すために使用されていた水平の線を置き換える役割を果たします。このクラスは非常に興味深いので、そのコードを見てみましょう。以下のコードにあるように、private静的変数を持っています。

#property copyright "Daniel Jose"
#include "C_Object_BackGround.mqh"
//+------------------------------------------------------------------+
class C_Object_TradeLine : public C_Object_BackGround
{
        private :
                static string m_MemNameObj;
        public  :
//+------------------------------------------------------------------+

// ... Internal class code

//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+
string C_Object_TradeLine::m_MemNameObj = NULL;
//+------------------------------------------------------------------+

どのように宣言し、どのように正しく初期化するかを示すためにハイライトされています。static変数の挙動を置き換えるためにグローバル変数を作ることもできますが、私は物事の制御を維持したいのです。この方法では、それぞれのオブジェクトが必要なものをすべて持っており、情報はそれらに格納されています。また、あるオブジェクトを別のオブジェクトに置き換えたい場合も簡単におこなえます。

次に注意すべきなのはオブジェクトを生成するコードです。

void Create(string szObjectName, color cor)
{
        C_Object_BackGround::Create(szObjectName, cor);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH));
        SpotLight(szObjectName);
};

正しく実装するために、C_Object_BackGroundクラスを使用し、実際に線となるボックスを作成します。繰り返しになりますが、これは他の種類のオブジェクトを使用すると現在と同じ動作ができなくなるためです。必要な動作をするオブジェクトはC_Object_Backgroundクラスに存在するものだけです。ここでのニーズに合わせて改造することで、線を作ることになります。

次に、線のハイライトを担当するコードを見てみましょう。

void SpotLight(string szObjectName = NULL)
{
        if (szObjectName != NULL) ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE, (szObjectName != NULL ? 4 : 3));
        if (m_MemNameObj != NULL) ObjectSetInteger(Terminal.Get_ID(), m_MemNameObj, OBJPROP_YSIZE, 3);
        m_MemNameObj = szObjectName;
};

このコードは非常に興味深いものです。なぜなら、ある線をハイライトするときに、どの行がハイライトされたかを知る必要はなく、オブジェクト自身がそれをおこなってくれるからです。また、新しい線がハイライトされると、ハイライトされていた線は自動的にその地位を失い、新しい線がその位置を獲得します。ハイライトする必要のある線がない場合は、この関数を呼び出すだけで、ハイライトを除去してくれます。

このことを知った上で、上記のコードと以下のコードを併用して、古い選択コードを消すことができます。こうすることで、MetaTrader 5がどの指標を操作しているのかを知らせてくれます。

string GetObjectSelected(void) const { return m_MemNameObj; }

もう1つ、注目するべき関数があります。これはY軸に沿った線の位置決めをおこないます。以下です。

virtual void PositionAxleY(string szObjectName, int Y)
{
        int desly = (m_MemNameObj == szObjectName ? 2 : 1);
        ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, Y - desly);
};

この関数も、BackGroundオブジェクトで示した関数と同様に、線がハイライトされているかどうかに応じて、正しい位置になるように自分自身で調整します。

2つのオブジェクトを完全に完成しましたが、実際に(上図のように)画面上で見る前に、C_ObjectsTradeクラスでいくつかのことをおこなう必要があります。


2.0.2.C_ObjectsTradeクラスの変更点

修正する内容は一見するとそれほど複雑ではありませんが、同じコードを繰り返す数が時には意欲を失わせかねないので、これを回避する方法を考えてみました。まず最初にイベント列挙型を作成します。マクロを使用していますが、マクロだらけのコードを追うのが難しいようであれば、マクロから関数やプロシージャに切り替えて、極端な場合はマクロを適切な内部コードに置き換えてしまってもかまいません。長年やっているので、私はマクロを使用することを好みます。

まず、イベント列挙を作成します。

enum eEventType {EV_GROUND = 65, EV_LINE};

オブジェクトが作成されればここに新しいイベントを追加しなければなりませんが、それは何か重要なものでなければなりません。ただし、各オブジェクトは1種類のイベントしか持たず、そのイベントはMetaTrader 5によって生成されます。そうでなければ、コードはイベントが正しく処理されることを確認するだけです。

これが終わったら、それぞれのオブジェクトにアクセスするための変数を作成します。

C_Object_BackGround     m_BackGround;
C_Object_TradeLine      m_TradeLine;

これらはクラスのグローバルスコープにありますが、privateです。使用するすべての関数で宣言することもできますが、クラス全体がオブジェクトの世話をするので、あまり意味がありません。

前回の記事のコードを変更します。

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

ハイライトされた部分は以前のバージョンにはありませんでしたが、MetaTrader 5が何が起こっているのかを私たちに知らせてくれるます。

また、新しい関数もあります。

void SetPositionMinimalAxleX(void)
{
        m_PositionMinimalAlxeX = (int)(ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS) * 0.2);
}

これはオブジェクトのX軸に沿った始点を作成します。それぞれのオブジェクトには一定のポイントがありますが、ここでは最初のリファレンスを提供します。上にあるこのコードのポイントを変えるだけで、開始位置を変更するように修正できます。

選択関数には多くの変更がありましたが、この後も少し変更されます。今は以下の通りです。

inline void Select(const string &sparam)
{
        ulong tick;
        double price;
        eIndicatorTrade it;
        eEventType ev;
        string sz = sparam;
                                
        if (!GetInfosOrder(sparam, tick, price, it, ev)) sz = NULL;
        m_TradeLine.SpotLight(sz);
}

また、指標を作成する関数も変更されています。

inline void CreateIndicatorTrade(ulong ticket, double price, eIndicatorTrade it, bool select)
{
        if (price <= 0) RemoveIndicatorTrade(ticket, it); else
        {
                CreateIndicatorTrade(ticket, it, select);
                PositionAxlePrice(price, ticket, it, -1, -1, 0, false);
        }
}

ただし、上のコードはそれほど重要ではありません。実際にすべての難しい作業をおこなうのは以下のコードです。

inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it)
{
        color cor1, cor2;
        string sz0;
                                
        switch (it)
        {
                case IT_TAKE    :
                        cor1 = clrPaleGreen;
                        cor2 = clrDarkGreen;
                        break;
                case IT_STOP    :
                        cor1 = clrCoral;
                        cor2 = clrMaroon;
                        break;
                case IT_PENDING:
                default:
                        cor1 = clrGold;
                        cor2 = clrDarkGoldenrod;
                        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:
                        m_BackGround.Size(sz0, 92, 22);
                        break;
                case IT_PENDING:
                        m_BackGround.Size(sz0, 110, 22);
                        break;
        }
}

この関数では、オブジェクトの色や作成順序を決定し、サイズを決定しています。指標に追加されるオブジェクトは、すべてが中央に配置され、常にチェックされるように、この関数を使用して配置する必要があります。関数で指標を作り始めると、保全しにくいタイプのコードになってしまい、適切なチェックが欠けてしまう可能性があります。うまくいったと思い込んで実際の口座で使ってみると、あるとき突然、正常に動作していないことに気づくかもしれません。ここでアドバイスがあります。常に同じ仕事をするものの中で関数を組み立てるようにしてください。 最初は無意味に思えても、変化するものを常にチェックしているので、時間が経つにつれて意味があるようになります。

以下は、次に変更された関数です。

#define macroDelete(A)  {                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND));       \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE));         \
                        }
                                        
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

次もそうですが、ちょっとつまりません。この関数は、作成したオブジェクトを1つずつ選択するのが自分であるように動作します。これは、各指標に適用されます。考えてみてください。マクロを使ってこの作業を簡単しなければ、悪夢と化すでしょう。この関数をコーディングすると、末尾の各指標が5つのオブジェクトを持つことになり、非常に面倒です。OCO注文の場合、1セットあたり3つの指標を使うので、15個のオブジェクトを使うことになり、その場合、(名前だけの違いなので)間違う可能性が大きくなります。そこで、マクロの助けを借りて、ハイライトされたコードに縮小します。最後に5つのオブジェクトだけをコーディングするのです。ただし、これは上に示した結果を得るための最初の段階に過ぎません。

最初の段階を完了させるために、もう1つ、同じように面倒な関数を用意しました。マクロを使わないのであれば、マクロの代わりにプロシージャを使うこともできますが、ここではこの方法を選択しました。

#define macroSetAxleY(A)        {                                               \
                m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \
                m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y);    \
                                }
                                                                        
#define macroSetAxleX(A, B)     {                                               \
                m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \
                m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B);    \
                                }

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 + 120);
                                        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

前の物がつまらなかったと思う方は、これをご覧ください。この場合、作業は2倍になりますが、ハイライトされたコードのおかげで納得のいくものになります。

他にも小さな変更を加える必要がありましたが、それらはあまり言及する価値がありません。このコードを実行すると、まさに期待どおりの結果が得られます。つまり、表示が画面に表示されます。


結論

システムが完成して、注文を直接チャートに完全に表示できるようになるまでにはまだまだかかりますが、今は、コードの他の場所に非常に大きな変更を加える必要があるため、一度にすべてをおこなう必要があります。

その変更は非常に深いものになるので、次回の記事のためにとっておきます。そして、何か問題が起きたら、システムを思い通りに変更できるまで、一歩戻ってやり直さなければなりません。そうすれば、自分が使いやすいようにカスタマイズして、快適に使うことができます。次回のシステムは下図のようになります。


簡単に実装できそうですが、実はたくさんの変更が必要です。では、次の記事でお会いしましょう。

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

添付されたファイル |
一からの取引エキスパートアドバイザーの開発(第21部):新規受注システム(IV) 一からの取引エキスパートアドバイザーの開発(第21部):新規受注システム(IV)
まだ完成していないものの、ようやくビジュアルなシステムが動き出します。ここでは主な変更を完成します。かなりの数になりますが、どれも必要なものばかりです。全体的にはなかなか面白いものになりそうです。
一からの取引エキスパートアドバイザーの開発(第19部):新規受注システム(II) 一からの取引エキスパートアドバイザーの開発(第19部):新規受注システム(II)
今回は、「見てわかる」タイプのグラフィカルな受注システムを開発します。なお、今回はゼロから始めるのではなく、取引する資産のチャート上にオブジェクトやイベントを追加して既存のシステムを修正します。
知っておくべきMQL5ウィザードのテクニック(第01回):回帰分析 知っておくべきMQL5ウィザードのテクニック(第01回):回帰分析
今日のトレーダーは哲学者であり、ほとんどの場合(意識的かどうかにかかわらず...)新しいアイデアを探し、試し、変更するか破棄するかを選択します。これは、かなりの労力を要する探索的プロセスです。トレーダーの時間とミスを避ける必要性は明らかに重視されます。この連載では、MQL5ウィザードがトレーダーの主力であるべきであることを示します。なぜでしょうか。MQL5ウィザードを使用すれば、新しいアイデアを組み立てることで時間を節約できるだけでなく、コーディングの重複によるミスを大幅に減らすことができるため、最終的に、取引の哲学のいくつかの重要な分野にエネルギーを注ぐことができるからです。
機械学習や取引におけるメタモデル:取引注文のオリジナルタイミング 機械学習や取引におけるメタモデル:取引注文のオリジナルタイミング
機械学習におけるメタモデル:人間がほとんど介在しない取引システムの自動作成 - いつ、どのように取引をおこなうかはモデルが自ら決定します。