単一チャート上の複数インジケータ(第06部):MetaTrader 5をRADシステムに変える(II)
はじめに
前回の記事では、MetaTrader 5のオブジェクトを使用してChart Tradeを作成し、プラットフォームをRADシステムに変える方法を紹介しました。このシステムは非常によく機能しており、読者の多くは、提案されたシステムの機能を拡張できるようなライブラリを作成することをお考えになったのではないでしょうか。これに基づいて、より直感的で使い勝手の良いEAを開発することも可能でしょう。
これはとても良いアイデアなので、機能を追加し始める方法を段階的に説明することにしました。ここでは、2つの新しく主要な機能を実装しようと思います(この情報は、他の機能を私たちが必要とする方法で実装するための基礎となるものです)。要素そのものは多様な使い方ができるため、制限となるのは私たちの創造性だけです。
計画
IDEは次の画像のように変更されます。
ご覧のように、デザインそのものに小規模な手直しが施されています。2つの新しいエリアが追加されました。1つは資産の名前を受け取り、もう1つは1日の累積値を受け取ります。あってもなくてもいいですし、判断に影響することもありませんが、どっちにしても面白いかもしれません。IDEに機能を追加する最も簡単で正しい方法を紹介するので、新しいインターフェイスでオブジェクトのリストを開いてみましょう。次が表示されます。
丸で囲んだ2つのオブジェクトはイベントに関連付けられていないため、IDEでは機能しません。他のすべてのオブジェクトは、すでに特定のイベントに正しく関連付けられており、これらのイベントがEAで発生したときに正しく実行するように強制することができます。つまり、IDEインターフェイスは自由に変更することができますが、その機能がまだ実装されていない場合、チャート上にオブジェクトが表示されるだけになります。取引する資産の名前はEDIT 00オブジェクトで受け取られ、オブジェクトの中央に表示される必要があります。一定期間の累積値はEDIT 01オブジェクトで受け取られます。その日のうちに利益が出ているのか損失が出ているのかを日足を使って知ることができます。値が負の場合と正の場合では違う色が使用されます。
どちらの値も明らかにユーザーが変更することはできないので、下図のようにプロパティを読み取り専用にしておきます。
ただし、情報の表示方法を指定することはできないので、テキストがオブジェクトの中央に表示されるように配置することはできません。テキストを中央に配置するプロパティはあるため、必要であれば、コードを使用してこれをおこなうことができます。詳細はオブジェクトのプロパティを参照してください。テーブルのENUM_ALIGN_MODEには、両端揃えのテキストを使用できるオブジェクトが含まれています。
どのような修正を加えるにしても、まず計画を立て、新しい機能、その表現方法、ユーザーとの対話方法を定義する必要があります。これにより、MetaTrade r5独自のインターフェイスを用いて、適切なオブジェクトの選択とその設定を可能な限りおこなうことができます。その結果、すぐに使えるようなIDEが完成し、MQL5のコードを通して調整するだけで、IDEが100%機能するようになります。では、修正に進みましょう。
修正内容
コードが継ぎ合わせにならないように、どの機能がすでに存在し、実際にどの機能を実装する必要があるかをできるだけ整理しておく必要があります。多くの場合、既存のコードに小規模な手直しを加え、新しくしてテストするだけで十分なことがあります。この新しいコードは、これから実装するものに再利用されます。テストが必要なのは、完全に新しい機能を作るために追加する小さな制御機能だけです。優秀なプログラマーは必ずと言っていいほど、既存のコードに制御点を追加して、何らかの形で再利用しようとします。
変更点1:資産名の追加
この部分を実現するためには、大幅な変更は必要ありませんが、適切な場所で実装することが必要です。まず、列挙に新しい値を追加します。ソースコードは以下の通りです。
enum eObjectsIDE {eRESULT, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};
以下は新しいコードで、強調表示されている部分が追加された部分です。新しい値は列挙の最初にも最後にも追加しません。これは、すでに存在し動作している他のコード部分に触るのを避けるためです。
enum eObjectsIDE {eRESULT, eLABEL_SYMBOL, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};
新しい値を列挙の最初か最後に追加した場合、これらの制限が使用されていた場所をすべて見つけて変更する必要があります。多くの場合、変更漏れが発生して、発見が困難なミスにつながります。新しく追加したコードが原因でエラーが発生したように見えると思っても、実は変更漏れが原因になるのです。そのため、変化は極値の間のどこかに加えます。
その直後にメッセージを追加します。そうしないと、実行時にエラーが発生することがあります。次の行をソースコードに追加します。
static const string C_Chart_IDE::szMsgIDE[] = { "MSG_RESULT", "MSG_NAME_SYMBOL", "MSG_BUY_MARKET", "MSG_SELL_MARKET", "MSG_DAY_TRADE", "MSG_CLOSE_POSITION", "MSG_LEVERAGE_VALUE", "MSG_TAKE_VALUE", "MSG_STOP_VALUE" };
ご覧のように、順番を守って同じ場所に追加しています。ただし、現時点では、定数はどのオブジェクトがメッセージを受け取るかを確認するためだけに使用されるので、どこに追加しても違いがありません。構造上の理由で、2番目のメッセージとして追加しました。
次に、MetaTrader 5に戻り、以下のように変更してみます。
さて、IDE内のオブジェクトはすでにメッセージを受信するオブジェクトとして認識されているので、あとはメッセージを送信するプロシージャを作成するだけです。メッセージテキストを一度だけ追加し、MetaTrader 5のチャート上にIDEが配置されると同時に送信できるようにする必要があります。これは、オブジェクトクラスのCreate関数の末尾に必要なコードを追加するだけで実現できますが、ここでも、コードが修正だらけの継ぎ合わせにならないように、新しいコードをDispatchMessage関数の中に追加することにします。以下は修正前の関数です。
void DispatchMessage(int iMsg, string szArg, double dValue = 0.0) { if (m_CountObject < eEDIT_STOP) return; switch (iMsg) { case CHARTEVENT_CHART_CHANGE: if (szArg == szMsgIDE[eRESULT]) { ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen)); ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2)); } break; case CHARTEVENT_OBJECT_CLICK: // ... The rest of the code... } }
以下は修正後です。
void DispatchMessage(int iMsg, string szArg, double dValue = 0.0) { if (m_CountObject < eEDIT_STOP) return; switch (iMsg) { case CHARTEVENT_CHART_CHANGE: if (szArg == szMsgIDE[eRESULT]) { ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen)); ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2)); }else if (szArg == szMsgIDE[eLABEL_SYMBOL]) { ObjectSetString(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_TEXT, Terminal.GetSymbol()); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_ALIGN, ALIGN_CENTER); } break; case CHARTEVENT_OBJECT_CLICK: // ... The rest of the code } }
送信関数を作成した後、このメッセージを送信するポイントを選択します。最適な場所は、オブジェクトクラスのCreate関数の最後なので、コードは最終的には次のようになります。
bool Create(int nSub) { m_CountObject = 0; if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false; FileReadInteger(m_fp, SHORT_VALUE); for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = ""; m_SubWindow = nSub; m_szLine = ""; while (m_szLine != "</chart>") { if (!FileReadLine()) return false; if (m_szLine == "<object>") { if (!FileReadLine()) return false; if (m_szLine == "type") { if (m_szValue == "102") if (!LoopCreating(OBJ_LABEL)) return false; if (m_szValue == "103") if (!LoopCreating(OBJ_BUTTON)) return false; if (m_szValue == "106") if (!LoopCreating(OBJ_BITMAP_LABEL)) return false; if (m_szValue == "107") if (!LoopCreating(OBJ_EDIT)) return false; if (m_szValue == "110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) return false; } } } FileClose(m_fp); DispatchMessage(CHARTEVENT_CHART_CHANGE, szMsgIDE[eLABEL_SYMBOL]); return true; }
実際に追加されたものは、緑色で強調表示されています。ほとんど変更なしでメッセージの流れをすでに100%実装したので、次に実装する必要のあるメッセージに移ることができます。
変更点2:その日の積算値を追加する(カバレッジポイント)
ここでも、資産名を追加したときと同じロジックに従います。新しいコードは次のようになります。
enum eObjectsIDE {eRESULT, eLABEL_SYMBOL, eROOF_DIARY, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP}; // ... Rest of the code static const string C_Chart_IDE::szMsgIDE[] = { "MSG_RESULT", "MSG_NAME_SYMBOL", "MSG_ROOF_DIARY", "MSG_BUY_MARKET", "MSG_SELL_MARKET", "MSG_DAY_TRADE", "MSG_CLOSE_POSITION", "MSG_LEVERAGE_VALUE", "MSG_TAKE_VALUE", "MSG_STOP_VALUE" };
その後、新しいメッセージでIDEを変更します。
これで新しいIDEが完成しました。次に、1日の累積値を含むメッセージを作成するコードを実装します。まず、この機能をどのクラスに実装するかを考えなればなりません。多くの人はこの関数をC_Chart_IDEクラスに置くでしょうが、整理上の理由から、注文を扱う関数と一緒にした方が良いと思うので、C_OrderViewクラスに実装します。以下がそのコードです。
double UpdateRoof(void) { ulong ticket; int max; string szSymbol = Terminal.GetSymbol(); double Accumulated = 0; HistorySelect(macroGetDate(TimeLocal()), TimeLocal()); max = HistoryDealsTotal(); for (int c0 = 0; c0 < max; c0++) if ((ticket = HistoryDealGetTicket(c0)) > 0) if (HistoryDealGetString(ticket, DEAL_SYMBOL) == szSymbol) Accumulated += HistoryDealGetDouble(ticket, DEAL_PROFIT); return Accumulated; }
コードの実装が完了したので、次にメッセージをシステムに追加します。オペレーターの負担を軽減するために、すでに確定した結果を報告するコードを追加しています。そのコードは次のとおりです。
void DispatchMessage(int iMsg, string szArg, double dValue = 0.0) { static double AccumulatedRoof = 0.0; bool b0; double d0; if (m_CountObject < eEDIT_STOP) return; switch (iMsg) { case CHARTEVENT_CHART_CHANGE: if ((b0 = (szArg == szMsgIDE[eRESULT])) || (szArg == szMsgIDE[eROOF_DIARY])) { if (b0) { ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen)); ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2)); }else { AccumulatedRoof = dValue; dValue = 0; } d0 = AccumulatedRoof + dValue; ObjectSetString(Terminal.Get_ID(), m_ArrObject[eROOF_DIARY].szName, OBJPROP_TEXT, DoubleToString(MathAbs(d0), 2)); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eROOF_DIARY].szName, OBJPROP_BGCOLOR, (d0 >= 0 ? clrForestGreen : clrFireBrick)); }else if (szArg == szMsgIDE[eLABEL_SYMBOL]) { ObjectSetString(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_TEXT, Terminal.GetSymbol()); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_ALIGN, ALIGN_CENTER); } break; case CHARTEVENT_OBJECT_CLICK: // .... The rest of the code.... } }
強調表示されている部分は、上記のようなシステムを支えています。このように実装しなければ、情報を正しく更新するために、システムに2つのメッセージを送る必要がありました。このコードのように実装すれば、ポジションと当日の結果の両方を1つのメッセージで追跡することができます。
EAのもう1つの修正は、OnTrade関数に関連しています。次のようになります。
void OnTrade() { SubWin.DispatchMessage(CHARTEVENT_CHART_CHANGE, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY], NanoEA.UpdateRoof()); NanoEA.UpdatePosition(); }
このシステムは動作しますが、OnTrade関数の実行時間には注意が必要です。これは、OnTickと合わせてEAのパフォーマンスを低下させる可能性があります。コードをOnTickに含むのはあまり良いとは言えず、最適化が重要です。ただし、OnTradeでは、ポジションに変化があったときに実際に関数が呼び出されるため、より簡単です。それを知った上で、2つの選択肢があります。1つ目は、UpdateRoofの位置を変更して実行時間を制限することです。もう1つは、OnTrade関数自体を変更することです。実用的な理由からUpdateRoof関数を修正し、その結果、少なくともポジションがあるときの実行時間がわずかに改善されます。新しい関数は次のとおりです。
double UpdateRoof(void) { ulong ticket; string szSymbol = Terminal.GetSymbol(); int max; static int memMax = 0; static double Accumulated = 0; HistorySelect(macroGetDate(TimeLocal()), TimeLocal()); max = HistoryDealsTotal(); if (memMax == max) return Accumulated; else memMax = max; for (int c0 = 0; c0 < max; c0++) if ((ticket = HistoryDealGetTicket(c0)) > 0) if (HistoryDealGetString(ticket, DEAL_SYMBOL) == szSymbol) Accumulated += HistoryDealGetDouble(ticket, DEAL_PROFIT); return Accumulated; }
強調表示されている行は、元の関数に追加されたコードです。大した違いはないように見えても、大きな違いがあります。理由を見てみましょう。初めてコードを参照するときは、memMaxstatic変数とAccumulatedは、指定された期間の注文履歴に値がなければゼロに設定されています。テストはこれを反映して、ルーチンは終了しますが、もしデータがあればそれがテストされ、memMaxとAccumulatedは両方とも新しい状態を反映します。これらの変数がstaticであるということは、呼び出しと呼び出しの間にその値が保持されることです。したがって、資産の自然な動きの結果としてポジションの値が変化したとき、OnTrade関数を呼び出すイベントが生成されます。この時点でUpdateRoof関数を新たに呼び出し、ポジションが決済されていなかった場合は制御点に戻るので、戻り処理を高速化することができます。
結論
この記事では、RADシステムに新しい機能を追加することで、IDEインターフェイスの作成に最適なライブラリを作成し、より簡単でミスの少ない対話・制御インターフェイスの構築を実現する方法をご紹介しました。ここで説明したのはMQL5との操作だけですが、これからは、ご自分の創造力次第で、外部のライブラリにも同じアイデアを取り入れて、IDEを作成する可能性を大きく広げることができます。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/10301
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索