English Русский Español Deutsch Português
preview
市場シミュレーション:ポジション表示(VI)

市場シミュレーション:ポジション表示(VI)

MetaTrader 5 — テスター |
17 0
Daniel Jose
Daniel Jose

はじめに

皆さん、市場リプレイ/シミュレーションシステムの連載の新しい記事へようこそ。

前回の「市場シミュレーション:ポジション表示(V)」では、アプリケーション内で何が起きているのかを把握するために必要なデータの取得方法について説明しました。 このデータを利用することで、OnTradeTransaction関数を使ったシステムの実装が可能になります。 ただし、前回の記事では、エキスパートアドバイザー(EA)側での実装方法だけを紹介しました。 前回紹介したデータやEAのコードをご覧になって、全体的にかなり複雑で分かりにくいと感じた方もいるかもしれません。また、実際のところ、どれほど実用的なのだろうかと疑問に思った方もいるでしょう。 多少の疑念を持つことは悪いことではありません。 まったく新しい仕組みに触れるときには、疑問を持つことも大切ですし、むしろ望ましいことです。

そこで今回は、前回の記事で紹介したデータの意味を理解するための第一歩を踏み出します。 今回の内容は、最初は少し分かりにくく感じるかもしれません。そのため、後ほど時間をかけてこの記事を読み返し、実際にいくつかテストをおこないながら、説明している内容を理解していただきたいと思います。 仕組みを十分に理解しないまま、この記事で紹介する内容を実際の取引口座で使用することは、絶対に避けてください。データの解釈を誤ったり、操作を間違えたりする可能性があり、損失につながるおそれがあります。せっかく開発しているシステムを使う際に、そうした不愉快な経験をしてほしくありません。

もう一度強調します。これから作成するシステムは、その仕組みを十分に理解するまでは使用しないでください。 そのためにも、実際にコードを動かし、積極的に試行錯誤しながら学んでください。今回のコードは、できる限り分かりやすく、学習に役立つことを重視して作成しています。 ここでは処理効率は二の次です。 最も重要なのは、誰もが処理の内容を理解できるよう、コードをできるだけシンプルにすることです。


これから何をするのか見てみましょう

皆さんの多くは、いつも同じような作業を繰り返している、あるいはそうした作業を見慣れているのではないでしょうか。 もちろん、そのやり方が間違っているわけではありません。しかし、別の方法があるのなら、一度試してみてもよいのではないでしょうか。 今回理解していただきたいのは、まさにこの点です。 EAの本来の役割は、チャート上にオブジェクトを配置したり、計算を実行したりすることではありません。 もちろん、そうした処理をEAでおこなうこともできますし、 それ自体が間違いというわけでもありません。 しかし、EAの最も重要な役割は、取引サーバーとの適切な連携を担うことです。 そのため、各チャートには1つのEAを配置するだけで十分です。 オブジェクトの作成やチャート上での操作までEAに担当させることは、必ずしも最善の方法とは言えません。

もう一度強調しておきますが、こうした処理をEAで行うこと自体に問題があるわけではありません。 ただし、皆さんが複数のEAを使用している可能性も考えてみてください。それぞれのEAは、特定の市場や状況に応じて使い分けるために設計されているかもしれません。 すべての機能を1つのコードにまとめてしまうのは、あまり実用的ではありません。たとえば、それぞれのEAにポジション表示機能を実装したとします。その後、別のEAでその機能を改良した場合、既存のすべてのEAに同じ修正を反映させなければなりません。EAごとにコードを移植していく必要があるため、古いバージョンを使い続けることにもなりかねません。

そこで、私はこの処理をインジケータに担当させることを提案します。 ただし、最近の記事を読んでいただいた方ならお気づきのように、インジケータには実行できない処理もあります。 特定の処理を無理にインジケータに実行させようとすることはできませんし、推奨もできません。 しかし、チャート上に保有ポジションを表示するためのインジケータを作成することは可能です。 そのためには、インジケータをより一貫性のある、分かりやすい方法で利用できるようにする仕組みを開発する必要があります。

その仕組みの一つが、インジケータとEAの間でメッセージをやり取りする機能です。 この仕組みは、すでにしばらく前から利用しています。 というのも、Chart TradeインジケータとMouse Studyインジケータも、EAとの通信に同じ仕組みを使用しているからです。 しかし、ポジション表示インジケータがEAと連携するための仕組みについては、まだ実装していません。正確に言えば、その開発に着手すらしていないのです。


小さな改善

まず最初に取り組むのは、新しいポジションが建てられるたびに、EAがチャート上にポジション表示インジケータを追加できるようにすることです。 これは比較的簡単に実装できます。EAをチャートに配置したときに使用している仕組みを、そのまま利用できるからです。 これまでの記事で説明したように、EAをチャートに配置すると、保有中のポジションを検索し、あらかじめ指定された条件に一致するものを探します。 この仕組みによって、EAは対象となるポジションに対応したインジケータを、チャート上に正しく配置できます。

また、これまでの記事で紹介したとおり、EAをチャートから削除すると、チャート上に配置されていたポジション表示インジケータも削除されます。 これは、 EAが正常に削除された場合には問題なく機能します。 しかし、プログラムがクラッシュした場合は事情が異なります。 その状態でEAを再びチャートに配置すると、前回の実行時に作成されたインジケータが残っている可能性があります。そのため、これらを削除する必要があります。 ここで問題となるのは、チャート上に残された古いポジション表示インジケータです。 なお、ここではトレーダーがチャート上のオブジェクトには一切手を加えていないものと仮定します。 つまり、EAがチャートから消えていることに気づいたものの、何らかのポジション表示インジケータだけがチャート上に残っている、という状況です。

EAを再びチャートに配置した時点では、以前とは状況が変わっている可能性があります。 もう少し具体的に説明しましょう。クラッシュが発生する前、EAはミニ契約を監視するように設定されていたとします。 ところが、トレーダーがEAを再びチャートに配置する際には、通常サイズの契約を監視する設定に変更しているかもしれません。 しかし、チャート上には、以前の設定に基づいてミニ契約のポジションを示すインジケータが残っています。 このような状態では、表示内容と現在のEAの設定が一致せず、問題が発生する可能性があります。 そこで、こうした問題を防ぐために、EAの初期化処理にコードを1行追加します。 この1行によって、チャート上に残っている古いポジション表示インジケータをすべて削除できるようにします。

非常にシンプルで、分かりやすい処理です。 以下のコード断片で、具体的な実装方法を確認してみましょう。

15. //+------------------------------------------------------------------+
16. C_Orders       *Orders;
17. C_Terminal     *Terminal;
18. //+------------------------------------------------------------------+
19. int OnInit()
20. {
21.     Terminal = NULL;
22.     Orders = new C_Orders(0xC0DEDAFE78514269);
23.     
24.     return INIT_SUCCEEDED;
25. }
26. //+------------------------------------------------------------------+
27. void OnTick() {}
28. //+------------------------------------------------------------------+
29. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
30. {
31.     int handle;
32.     
33.     (*Orders).DispatchMessage(id, lparam, dparam, sparam);
34.     switch (id)
35.     {
36.         case CHARTEVENT_CHART_CHANGE:
37.             if (Terminal != NULL) break;
38.             else
39.             {
40.                 ulong ul;
41.                 Terminal = new C_Terminal(0, 0, user00);
42.                 for (int count = PositionsTotal() - 1; count >= 0; count--)
43.                 {
44.                     ul = PositionGetTicket(count);
45.                     if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol)
46.                     {
47.                         ChartIndicatorDelete(0, 0, IntegerToString(ul));
48.                         continue;
49.                     }
50.                     handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul);
51.                     ChartIndicatorAdd(0, 0, handle);
52.                     IndicatorRelease(handle);
53.                 }
54.             }
55.         case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
56.             EventChartCustom(0, evEA_At_ChartTrade, user00, 0, "");
57.             break;
58.     }
59. }
60. //+------------------------------------------------------------------+

EAのコード抜粋

前のコード断片の47行目に注目してください。 この1行こそが、先ほど説明した問題の発生を防いでいます。 つまり、EAをチャートに再度配置すると、47行目の処理により、初期化の基準を満たさない可能性のある古いインジケータが削除されます。 言い換えれば、トレーダーが契約タイプを変更した場合、EAはその変更を検知し、現在の設定に合わないポジション表示インジケータをすべて削除します。 一方、インジケータが現在の契約タイプと一致していれば、何もする必要はありません。 最悪の場合、EAでエラーが発生することがありますが、これは許容できるものであり、無視しても問題ありません。

これで、 チャート上に表示されるデータの整合性がある程度確保できました。次のステップに進みましょう。 次は、EAとポジション表示インジケータの間でメッセージを送受信する仕組みを実装します。


EAとポジション表示インジケータ間の連携機能の実装

ここからは、この2つのアプリケーション間で、最初の情報交換を開始します。 メッセージの送受信には、検討しなければならないさまざまな要素があります。そのため、一つずつ段階を追って説明していきます。 まずは基本的な部分から始め、少しずつ詳細に踏み込んでいきましょう。最終的には、完全に機能するポジション表示インジケータを完成させます。

現在のポジション表示インジケータには、水平線、いわゆるHLINEオブジェクトしか含まれていません。そのため、やり取りするメッセージの数はそれほど多くありません。 ただし、コードにはいくつか追加の変更が必要です。 まずは、以下に示すヘッダーファイル Defines.mqh から変更していきましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.     #define macro_DEBUG_MODE(A) \
08.                     Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.     #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay          "RePlay"
14. #define def_MaxPosSlider          400
15. #define def_MaskTimeService       0xFED00000
16. #define def_IndicatorTimeFrame    (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. #define def_IndexTimeFrame        4
18. //+------------------------------------------------------------------+
19. union uCast_Double
20. {
21.     double   dValue;
22.     long     _long;                                  // 1 Information
23.     datetime _datetime;                              // 1 Information
24.     uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
25.     ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
26.     uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
27. };
28. //+------------------------------------------------------------------+
29. enum EnumEvents     {
30.             evTicTac,                        //Event of tic-tac
31.             evHideMouse,                     //Hide mouse price line
32.             evShowMouse,                     //Show mouse price line
33.             evHideBarTime,                   //Hide bar time
34.             evShowBarTime,                   //Show bar time
35.             evHideDailyVar,                  //Hide daily variation
36.             evShowDailyVar,                  //Show daily variation
37.             evHidePriceVar,                  //Hide instantaneous variation
38.             evShowPriceVar,                  //Show instantaneous variation
39.             evCtrlReplayInit,                //Initialize replay control
40.             evChartTradeBuy,                 //Market buy event
41.             evChartTradeSell,                //Market sales event 
42.             evChartTradeCloseAll,            //Event to close positions
43.             evChartTrade_At_EA,              //Event to communication
44.             evEA_At_ChartTrade,              //Event to communication
45.             evChatWriteSocket,               //Event To Mini Chat
46.             evChatReadSocket,                //Event To Mini Chat
47.             evUpdate_Position                //Event to communication
48.                         };
49. //+------------------------------------------------------------------+
50. enum EnumPriority {                          //Priority list on objects
51.             ePriorityNull = -1,
52.             ePriorityDefault = 0,
53.             ePriorityChartTrade = 5,
54.             ePriorityOrders = 10
55.                         };
56. //+------------------------------------------------------------------+

Defines.mqh

このファイルでは、ポジション表示インジケータの更新処理を呼び出すきっかけとなるイベントを定義する、新しい列挙要素を追加します。 このイベントは47行目で定義されています。 これで準備が整ったので、次はEAのコードに進みましょう。 以下に、EAのコード全体を示します。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property icon "/Images/Market Replay/Icons/Replay - EA.ico"
004. #property description "Demo version between interaction"
005. #property description "of Chart Trade and Expert Advisor"
006. #property version   "1.118"
007. #property link "https://www.mql5.com/pt/articles/13203"
008. //+------------------------------------------------------------------+
009. #include <Market Replay\Order System\C_Orders.mqh>
010. #include <Market Replay\Auxiliar\C_Terminal.mqh>
011. //+------------------------------------------------------------------+
012. enum eTypeContract {MINI, FULL};
013. //+------------------------------------------------------------------+
014. input eTypeContract user00 = MINI;                 //Cross order in contract
015. //+------------------------------------------------------------------+
016. C_Orders           *Orders;
017. C_Terminal         *Terminal;
018. //+------------------------------------------------------------------+
019. int OnInit()
020. {
021.    Terminal = NULL;
022.    Orders = new C_Orders(0xC0DEDAFE78514269);
023.    
024.    return INIT_SUCCEEDED;
025. }
026. //+------------------------------------------------------------------+
027. void OnTick() {}
028. //+------------------------------------------------------------------+
029. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
030. {
031.    int handle;
032.    
033.    (*Orders).DispatchMessage(id, lparam, dparam, sparam);
034.    switch (id)
035.    {
036.            case CHARTEVENT_CHART_CHANGE:
037.                    if (Terminal != NULL) break;
038.                    else
039.                    {
040.                            ulong ul;
041.                            Terminal = new C_Terminal(0, 0, user00);
042.                            for (int count = PositionsTotal() - 1; count >= 0; count--)
043.                            {
044.                                    ul = PositionGetTicket(count);
045.                                    if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol)
046.                                    {
047.                                            ChartIndicatorDelete(0, 0, IntegerToString(ul));
048.                                            continue;
049.                                    }
050.                                    handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul);
051.                                    ChartIndicatorAdd(0, 0, handle);
052.                                    IndicatorRelease(handle);
053.                            }
054.                    }
055.            case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
056.                    EventChartCustom(0, evEA_At_ChartTrade, user00, 0, "");
057.                    break;
058.    }
059. }
060. //+------------------------------------------------------------------+
061. void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
062. {
063.    if (Terminal == NULL) return;
064.    static ulong ticket = 0;
065.    switch (trans.type)
066.    {
067.            case TRADE_TRANSACTION_HISTORY_ADD:
068.                    ticket = (trans.order != trans.position ? trans.position : 0);
069.                    break;
070.            case TRADE_TRANSACTION_REQUEST:
071.                    if ((request.symbol == (*Terminal).GetInfoTerminal().szSymbol) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
072.                    {
073.                            case TRADE_ACTION_DEAL:
074.                                    if (ticket) EventChartCustom(0, evUpdate_Position, ticket, 0, "");
075.                                    else
076.                                    {
077.                                            int handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", result.order);
078.                                            ChartIndicatorAdd(0, 0, handle);
079.                                            IndicatorRelease(handle);
080.                                    }
081.                                    ticket = 0;
082.                                    break;
083.                            case TRADE_ACTION_SLTP:
084.                                    EventChartCustom(0, evUpdate_Position, request.position, 0, "");
085.                                    break;
086.                    }
087.                    break;
088.    };
089. }
090. //+------------------------------------------------------------------+
091. void OnDeinit(const int reason)
092. {
093.    ulong ul;
094.    
095.    switch (reason)
096.    {
097.            case REASON_REMOVE:
098.            case REASON_INITFAILED:
099.                    EventChartCustom(0, evEA_At_ChartTrade, -1, 0, "");
100.                    break;
101.    }
102.    if (Terminal != NULL) for (int count = PositionsTotal() - 1; count >= 0; count--)
103.    {
104.            ul = PositionGetTicket(count);
105.            if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) continue;
106.            ChartIndicatorDelete(0, 0, IntegerToString(ul));
107.    }
108.    delete Orders;
109.    delete Terminal;
110. }
111. //+------------------------------------------------------------------+

Expert Advisor.mq5

このコードの大部分については、すでに前のセクションで説明しました。 したがって、ここでは新しく追加された部分、つまりOnTradeTransaction関数に注目しましょう。 現時点では、まだ理解できていない箇所があっても心配する必要はありません。 すべて順を追って説明していきます。 ここで特に注目してほしいのは、EventChartCustom関数です。 この関数は74行目と84行目に登場します。 この2か所の処理によって、EAはポジション表示インジケータにイベントの発生を通知し、インジケータに更新が必要であることを知らせます。

インジケータ側でこの通知をどのように処理するかについては、今はまだ気にしなくて構いません。 まずは、EAがどのタイミングでインジケータに更新を要求するのかを確認してください。 より深く理解するには、前回の記事で説明した内容を把握しておく必要があります。 今回の説明に出てくる内容について疑問があれば、前回の記事を参照してください。

ここでは、3つの異なる状況について検討します。 最初の状況は、Chart Tradeインジケータを使って、EAに成行買いまたは成行売りを実行させた場合です。 このとき、静的変数ticketの値はゼロになります。 これは、68行目でtrans.orderとtrans.positionの値が同じになるためです。 この状態で、 サーバーからTRADE_TRANSACTION_REQUEST通知が送られてくると、その操作に対応する処理が実行されます。 ここで注意してください。この時点では、ポジションを新規に建てているところです。 そのため、サーバーがTRADE_ACTION_DEALを生成しても、74行目の条件判定は成立しません。 これは、変数ticketの値がゼロだからです。 この場合、76行目から80行目までのコードが実行されます。 ここで使われているのは、EAの初期化時にも使用したコードです。MetaTrader 5に対して、チャートにポジション表示インジケータを追加するよう指示する処理です。

結果として、TRADE_ACTION_DEALがなぜ発生したのかを特定できないため、変数ticketをゼロにリセットします。 これで処理は完了です。 チャートにはポジション表示インジケータが表示され、MetaTrader 5がこの表示モードに対応していた場合と同じ情報を確認できるようになります。 もう一度説明しておきますが、MetaTrader 5ではクロスオーダー方式が完全にはサポートされていません。 そのため、この取引モードを使用する場合は、チャート上にデータを表示するための仕組みを別途用意する必要があります。 その役割を担うのが、ポジション表示インジケータです。

これでポジションが作成され、チャート上にも表示されました。 しばらくして、トレーダーがポジション数量を増やすことにしたとします。 その場合、再びChart Tradeインジケータを使って、ポジション数量の増加をEAに要求します。 ここで重要な点があります。 ヘッジ口座では、この新規注文、つまりポジション数量を増やす要求によって、既存ポジション数量が実際に変更されるわけではありません。 実際には、新しいポジションが別途作成されます。 このシステムではヘッジ口座とネッティング口座を区別していないため、ヘッジ口座でポジション数量の増加を要求した場合の処理は、先ほど説明した新規ポジションの作成時とまったく同じです。 一方、同じ要求、つまりポジション数量の増加をネッティング口座で実行した場合は、状況が少し異なります。 この場合、サーバーが TRADE_TRANSACTION_HISTORY_ADDイベントを生成すると、trans.orderとtrans.positionの値が異なります。 これは、保有中のポジションに何らかの変更が加えられたことを意味します。 このとき、変数ticketの値はゼロではなくなります。 該当するポジションのチケット番号がticketに設定されるためです。 その後、サーバーは受け取った要求に応じて、TRADE_TRANSACTION_REQUESTイベントを生成します。

このイベントが発生し、TRADE_ACTION_DEALアクションが処理されると、74行目の条件判定が成立します。 するとカスタムイベントが発行され、ポジション表示インジケータに更新が必要であることが通知されます。 ここで注意してほしいのは、このイベントを特定のインジケータやアプリケーションに紐付けていないという点です。 チャート上に配置されているすべてのアプリケーションが、同じカスタムイベントを受信します。 ただし、各アプリケーション内でイベントをフィルタリングするため、実際にメッセージを認識して処理するのは、該当するインジケータまたはアプリケーションだけです。 この時点で、EAの仕事は完了です。 実際のところ、EAはポジション表示インジケータがこのメッセージを理解できるかどうかを知りません。 そもそも、そのインジケータがチャート上に存在しているかどうかさえ把握していません。 しかし、EAにとってそれは重要ではありません。EAは自分の役割を果たした。それで十分なのです。 ポジションのテイクプロフィット(利確)やストップロス(損切り)の値を変更した場合も、同じように処理されます。 この場合は、83行目にあるTRADE_ACTION_SLTPアクションが実行されます。 84行目では、ポジション数量を増やした場合や、ポジション数量を部分的に減らした場合と同じ処理をおこなっています。 これは、 ネッティング口座を使用している場合に該当します。

では、ポジションを決済した場合はどうなるのでしょうか。読者の皆さんは、EAのコードに、チャートからポジション表示インジケータを削除するための処理が用意されていると考えるかもしれません。 しかし、EAのコードを見ても、そのような処理は見当たりません。 それでは、EAはどのようにしてインジケータをチャートから削除するのでしょうか。 仕組みはとても簡単です。 ネッティング口座でポジション数量を変更した場合と同じ処理をおこないます。 つまり、68行目でtrans.orderとtrans.positionの値が異なります。 これによって74行目の条件判定が成立し、インジケータの更新要求が送信されます。 ただし、この時点ではポジションはすでに決済されています。そのため、更新処理の結果、インジケータはチャートから削除されます。 この仕組みを完全に理解するには、ポジション表示インジケータ側のコードを確認する必要があります。 各テーマを分けて説明するため、詳しくは次のセクションで取り上げます。


改良版ポジション表示インジケータ

ここまでで、現在のポジション表示インジケータのコードが2つの部分から構成されていることは理解できたと思います。 まずは、最初のコード例を以下に全体を示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Positions.ico"
04. #property description "Indicator for tracking an open position on the server."
05. #property description "This should preferably be used together with an Expert Advisor."
06. #property description "For more details see the same article."
07. #property version   "1.118"
08. #property link "https://www.mql5.com/pt/articles/13203"
09. #property indicator_chart_window
10. #property indicator_plots 0
11. //+------------------------------------------------------------------+
12. #define def_ShortName "Position View"
13. //+------------------------------------------------------------------+
14. #include <Market Replay\Order System\C_IndicatorPosition.mqh>
15. #include <Market Replay\Defines.mqh>
16. //+------------------------------------------------------------------+
17. input ulong user00 = 0;                 //For Expert Advisor use
18. input color user01 = clrRoyalBlue;      //Color Line Price
19. input color user02 = clrForestGreen;    //Color Line Take Profit
20. input color user03 = clrFireBrick;      //Color Line Stop Loss
21. //+------------------------------------------------------------------+
22. C_IndicatorPosition *Positions = NULL;
23. //+------------------------------------------------------------------+
24. int OnInit()
25. {
26.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
27.     Positions = new C_IndicatorPosition(user01, user02, user03);
28.     if (!Positions.CheckCatch(user00))
29.     {
30.         ChartIndicatorDelete(0, 0, def_ShortName);
31.         return INIT_FAILED;
32.     }
33. 
34.     return INIT_SUCCEEDED;
35. }
36. //+------------------------------------------------------------------+
37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
38. {
39.     return rates_total;
40. }
41. //+------------------------------------------------------------------+
42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
43. {
44.     (*Positions).DispatchMessage(id, lparam, dparam, sparam);
45. };
46. //+------------------------------------------------------------------+
47. void OnDeinit(const int reason)
48. {
49.     delete Positions;
50. }
51. //+------------------------------------------------------------------+

Position View.mq5

これがインジケータのメインコードです。 基本的に、ここでの変更点はOnChartEvent関数だけです。 C_IndicatorPositionクラス内部の情報を利用する必要があるため、このメインコード内では具体的な処理をおこないません。 ここでは、単にDispatchMessageプロシージャを呼び出すだけです。 したがって、このコードだけを見ても、EAとの連携がどのように実現されているのかは分かりません。 それでは次に、ヘッダーファイルC_IndicatorPosition.mqhを見てみましょう。以下にコード全体を示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_SufixTake    "Take"
05. #define def_SufixStop    "Stop"
06. //+------------------------------------------------------------------+
07. #include "..\Auxiliar\C_Terminal.mqh"
08. //+------------------------------------------------------------------+
09. class C_IndicatorPosition : private C_Terminal
10. {
11.     private    :
12.         struct st00
13.         {
14.             ulong     ticket;
15.             color     corPrice, corTake, corStop;
16.             string    szShortName;
17.         }m_Infos;
18. //+------------------------------------------------------------------+
19.         inline void MoveLineInfos(string szObjName, const double price)
20.         {
21.             szObjName = m_Infos.szShortName + szObjName;
22.             if (price <= 0) ObjectDelete(0, szObjName);
23.             else ObjectSetDouble(0, szObjName, OBJPROP_PRICE, price);
24.         }
25. //+------------------------------------------------------------------+
26.         void CreateLineInfos(string szObjName, const color cor, const string szDescription = "\n")
27.         {            
28.             szObjName = m_Infos.szShortName + szObjName;
29.             CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop))));
30.             ObjectSetString(0, szObjName, OBJPROP_TEXT, szDescription);
31.             ObjectSetString(0, szObjName, OBJPROP_TOOLTIP, szDescription);
32.             ObjectSetInteger(0, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice);
33.         }
34. //+------------------------------------------------------------------+
35.     public    :
36. //+------------------------------------------------------------------+
37.         C_IndicatorPosition(color corPrice, color corTake, color corStop)
38.             :C_Terminal()
39.         {
40.             ZeroMemory(m_Infos);
41.             m_Infos.corPrice = corPrice;
42.             m_Infos.corTake  = corTake;
43.             m_Infos.corStop  = corStop;
44.         }
45. //+------------------------------------------------------------------+
46.         ~C_IndicatorPosition()
47.         {
48.             if (m_Infos.ticket != 0)
49.                 ObjectsDeleteAll(0, IntegerToString(m_Infos.ticket));
50.         }
51. //+------------------------------------------------------------------+
52.         bool CheckCatch(ulong ticket)
53.         {
54.             m_Infos.szShortName = IntegerToString(m_Infos.ticket = ticket);            
55.             if (!PositionSelectByTicket(m_Infos.ticket)) return false;
56.             if (ObjectFind(0, m_Infos.szShortName) >= 0)
57.             {
58.                 m_Infos.ticket = 0;
59.                 return false;
60.             }
61.             IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName);
62.             CreateLineInfos(NULL, m_Infos.corPrice, "Position opening price.");
63.             CreateLineInfos(def_SufixTake, m_Infos.corTake, "Take Profit point.");
64.             CreateLineInfos(def_SufixStop, m_Infos.corStop, "Stop Loss point.");
65.             EventChartCustom(0, evUpdate_Position, ticket, 0, "");
66.             
67.             return true;
68.         }
69. //+------------------------------------------------------------------+
70.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
71.         {
72.             switch (id)
73.             {
74.                 case CHARTEVENT_CUSTOM + evUpdate_Position:
75.                     if (lparam != m_Infos.ticket) return;
76.                     if (!PositionSelectByTicket(m_Infos.ticket))
77.                     {
78.                         ChartIndicatorDelete(0, 0,  m_Infos.szShortName);
79.                         return;
80.                     };
81.                     MoveLineInfos(NULL, PositionGetDouble(POSITION_PRICE_OPEN));
82.                     MoveLineInfos(def_SufixTake, PositionGetDouble(POSITION_TP));
83.                     MoveLineInfos(def_SufixStop, PositionGetDouble(POSITION_SL));
84.                 break;
85.             }
86.         }
87. //+------------------------------------------------------------------+
88. };
89. //+------------------------------------------------------------------+
90. #undef def_SufixTake
91. #undef def_SufixStop
92. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

一つの決まった方法ですべてを実装するべきだと思うかもしれませんが、ここでは一般的な方法とは少し異なるアプローチを採用します。 実際、この段階ではコードに大きな変更を加えていませんが、いくつか新しい要素が追加されていることに気づくと思います。 まず、19行目に新しいプロシージャがあります。 このプロシージャの目的は、水平価格ラインの表示位置を変更することです。 このプロシージャについて、特に複雑な説明は必要ありません。価格がゼロまたはマイナスの場合、ラインオブジェクトが削除されるだけです。 これは、上に示したコードの22行目で実行されます。

ただし、通常、価格がマイナスになることはありませんが、インデックスの場合は事情が異なることを覚えておいてください。より正確には、2つ以上の資産を組み合わせて新しい値を算出する場合、マイナスの値が発生することがあります。 もっとも、ここでいうマイナス値は、価格そのものがマイナスになるという意味ではありません。マイナスのインデックス値、あるいは係数を指しています。 値の相互関係に基づくチャートを利用する際に問題が生じる場合は、このポジション表示インジケータを調整する必要があります。 現段階では、比率ではなく価格を対象として説明を進めます。 MetaTrader 5で値の相関関係を扱う方法については、後ほど説明します。

まずは一つずつ進めていきましょう。 最初に、ポジション表示インジケータを動作させる方法について説明します。 ネッティング口座を使用している場合に該当します。 23行目でHLINEオブジェクトに表示位置を指定します。 これだけです。 次に、52行目の関数について、興味深い点を見ていきましょう。 65行目でカスタムイベントが発生していることに注目してください。 なぜこのような実装にしているのでしょうか。 理由は簡単です。「一度書いて、何度もテストする」ためです。 このインジケータはカスタムメッセージ、つまりイベントを受信することになるため、チャートに配置したときにも、EAから更新要求を受け取ったときと同じように動作するよう設定できます。

前のセクションで説明したとおり、EAはポジション表示インジケータが処理を正常に実行したかどうかを確認しません。 EAはイベントを発行するだけで、次の処理に進みます。 残りの処理はすべて、ポジション表示インジケータの役割です。 そのため、明確な実装方針を持っておくことが望ましいでしょう。 ポジション表示インジケータを手動でチャートに追加した場合は正常に動作するのに、EAからの要求を解釈できないのであれば、EAとインジケータの間で通信に問題が発生していると、すぐに判断できます。 なぜなら、インジケータを手動でチャートに追加した場合には、イベントが正常に処理されることを確認できているからです。

つまり、EAがポジション表示インジケータに処理を実行させるためにイベントを発行するのと同じように、インジケータ自身も、チャートに配置できるかどうかを確認するために、同様のイベントを発行します。 この仕組みによって、どちらの場合も、上に示したコードの70行目にある関数が呼び出されます。

70行目のコードは、なかなか興味深いものです。 現時点で注目するのは、evUpdate_Position イベントの処理だけです。このイベントの処理は74行目から始まります。 75行目では、イベント呼び出し時に指定されたポジションのチケット番号が、インジケータ側で想定しているチケット番号と一致するかどうかを確認します。 チケット番号が異なる場合は、ここで処理を終了します。 イベント通知はチャート上のすべてのアプリケーションに送信されるため、チケット番号が一致していることを確認する必要があります。 だからこそ、データを使用する前にイベントを適切にフィルタリングすることが重要なのです。 フィルタリングが完了したら、76行目で該当するポジションが存在するかどうかを確認します。 EAは、ポジションの更新、決済、新規作成のいずれをおこなうべきかを指示するわけではありません。 EAが伝えるのは、単に「ポジション表示インジケータ、処理をお願いします」ということだけです。 あとは必要な処理をすべて実行するように、 というわけです。

その後の処理はすべてインジケータの役割です。必要な操作をすべて実行し、その結果を検証しなければなりません。 ポジションが存在しない場合は、78行目でインジケータを停止し、チャートから削除します。 ただし、ポジションが作成または更新されると、価格ラインが移動します。 これは81~83行目でおこなわれます。 この仕組みがどのように動作するのかを見ていきましょう。 ただし、上記のコードには問題があります。 次のセクションで扱うコードを読み進める前に、この点を理解しておくことが重要です。


本当にそうしなければならないのでしょうか?

先ほど紹介したC_IndicatorPositionクラスのコードには、問題があることが分かりました。 正確には、深刻な問題というよりも、使い勝手の面での不便さです。 問題となるのは、ストップロスとテイクプロフィットのラインを削除した場合、あるいは最初からチャートに表示しない設定にしていた場合です。その後、これらのラインをチャートに追加したくなっても、ポジションが更新されてしまうため、直接追加することができません。 先ほど説明したクラスでは、この操作に対応できないのです。 しかし、コードにいくつか変更を加えることで、この操作も可能になります。 以下に、その変更内容を示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_SufixTake    "Take"
05. #define def_SufixStop    "Stop"
06. //+------------------------------------------------------------------+
07. #include "..\Auxiliar\C_Terminal.mqh"
08. //+------------------------------------------------------------------+
09. class C_IndicatorPosition : private C_Terminal
10. {
11.     private    :
12.         struct st00
13.         {
14.             ulong     ticket;
15.             color     corPrice, corTake, corStop;
16.             string    szShortName;
17.         }m_Infos;
18. //+------------------------------------------------------------------+
19.         inline void MoveLineInfos(const string szDefine, const double price)
20.         {
21.             string szName = m_Infos.szShortName + szDefine;
22.             if (price <= 0) ObjectDelete(0, szName);
23.             else
24.             {
25.                 if (ObjectFind(0, szName) < 0)
26.                 {
27.                     if (szDefine == def_SufixTake) CreateLineInfos(def_SufixTake, m_Infos.corTake, "Take Profit point."); else
28.                     if (szDefine == def_SufixStop) CreateLineInfos(def_SufixStop, m_Infos.corStop, "Stop Loss point."); else
29.                     CreateLineInfos(NULL, m_Infos.corPrice, "Position opening price.");
30.                 }                    
31.                 ObjectSetDouble(0, szName, OBJPROP_PRICE, price);
32.             }
33.         }
34. //+------------------------------------------------------------------+
35.         void CreateLineInfos(string szObjName, const color cor, const string szDescription = "\n")
36.         {            
37.             szObjName = m_Infos.szShortName + szObjName;
38.             CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop))));
39.             ObjectSetString(0, szObjName, OBJPROP_TEXT, szDescription);
40.             ObjectSetString(0, szObjName, OBJPROP_TOOLTIP, szDescription);
41.             ObjectSetInteger(0, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice);
42.         }
43. //+------------------------------------------------------------------+
44.     public    :
45. //+------------------------------------------------------------------+
46.         C_IndicatorPosition(color corPrice, color corTake, color corStop)
47.             :C_Terminal()
48.         {
49.             ZeroMemory(m_Infos);
50.             m_Infos.corPrice = corPrice;
51.             m_Infos.corTake  = corTake;
52.             m_Infos.corStop  = corStop;
53.         }
54. //+------------------------------------------------------------------+
55.         ~C_IndicatorPosition()
56.         {
57.             if (m_Infos.ticket != 0)
58.                 ObjectsDeleteAll(0, IntegerToString(m_Infos.ticket));
59.         }
60. //+------------------------------------------------------------------+
61.         bool CheckCatch(ulong ticket)
62.         {
63.             m_Infos.szShortName = IntegerToString(m_Infos.ticket = ticket);            
64.             if (!PositionSelectByTicket(m_Infos.ticket)) return false;
65.             if (ObjectFind(0, m_Infos.szShortName) >= 0)
66.             {
67.                 m_Infos.ticket = 0;
68.                 return false;
69.             }
70.             IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName);
71.             EventChartCustom(0, evUpdate_Position, ticket, 0, "");
72.             
73.             return true;
74.         }
75. //+------------------------------------------------------------------+
76.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
77.         {
78.             switch (id)
79.             {
80.                 case CHARTEVENT_CUSTOM + evUpdate_Position:
81.                     if (lparam != m_Infos.ticket) return;
82.                     if (!PositionSelectByTicket(m_Infos.ticket))
83.                     {
84.                         ChartIndicatorDelete(0, 0,  m_Infos.szShortName);
85.                         return;
86.                     };
87.                     MoveLineInfos(NULL, PositionGetDouble(POSITION_PRICE_OPEN));
88.                     MoveLineInfos(def_SufixTake, PositionGetDouble(POSITION_TP));
89.                     MoveLineInfos(def_SufixStop, PositionGetDouble(POSITION_SL));
90.                 break;
91.             }
92.             ChartRedraw();
93.         }
94. //+------------------------------------------------------------------+
95. };
96. //+------------------------------------------------------------------+
97. #undef def_SufixTake
98. #undef def_SufixStop
99. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

上記のコードは、前のセクションで説明した元のコードと置き換える必要があります。 ご覧のとおり、変更点はわずかですが、 セクション冒頭で説明した状況にポジション表示インジケータが対応できるようにするには、これだけで十分です。 それでは、以前のコードと比べて何が変更されたのかを見ていきましょう。

実際には、61行目に示されている関数では、HLINEオブジェクトを新たに作成しなくなりました。 この結果を引き起こしていたコード行は削除されています。 正確には、削除されたのではなく、別の場所に移動されています。 それ以外のコードは変更されていません。 しかし、19行目を見ると、これまでにはなかった非常に興味深い変更が加えられていることが分かります。

以前は、価格ラインを移動するよう指示すると、コードは単純にオブジェクトに移動を命じるだけでした。 しかし、現在は追加の処理が加わっています。 それが25行目です。 この処理によって、必要に応じてラインを再作成できるようになります。 つまり、トレーダーがラインを削除すると、EAはポジション表示インジケータにデータを更新するよう指示します。 すると、インジケータもトレーダーが削除したラインを削除します。 一方、トレーダーが再びラインを設定し、新しいストップロスまたはテイクプロフィットの価格を指定すると、EAはポジション表示インジケータに再度データの更新を指示します。 この段階で25行目が実行されると、ポジション表示インジケータはHLINEオブジェクトが存在しないことを検出し、新しいポジションの状態に応じて適切に再作成します。 その後、初めてポジション表示インジケータがラインを正しい位置に配置します。 この処理は31行目でおこなわれます。

ここまで説明した動作は、以下の動画で確認できます。この動画を見ると、現在のポジション表示インジケータがどのように機能するのかがよく分かります。


まとめ

この記事では、ポジション表示インジケータが取引サーバー上の実際の状況を正確に反映できるよう、いくつかの改善をおこないました。これは、ポジションの有無だけでなく、それぞれのポジションの現在の状態についても同様です。 あらかじめお断りしておきますが、ここで紹介するアプリケーションは、MetaTrader 5に標準搭載されている機能を置き換えることを目的としたものではありません。 また、十分な注意と慎重な判断なしに使用することも避けてください。これらは、あくまでもシステムの仕組みを学ぶための学習用コードです。

私があえて「学習用コード」と呼んでいるのには理由があります。機能によっては、メッセージを使って実装することが必ずしも最適な方法とは限らないからです。 なお、チャート上に配置されたポジション表示インジケータは、自らが応答するためのメッセージを生成することに注意してください。 この仕組みは、本来必要のない呼び出しを追加することになるため、コードの応答性を多少低下させます。

いずれにしても、コードの提示方法を変更する予定はありません。 このコードを試してみたいものの、すべてのファイルの作成方法が分からない場合(記事には完全なコードが掲載されています)、添付ファイルからコンパイル済みのアプリケーションを入手できます。 これらをMetaTrader 5で使用するだけで、メッセージがどのように交換されるかを確認できます。 これらのアプリケーションは、ヘッジ口座とネッティング口座の両方でテスト済みであり、問題なく動作します。 特別な変更は必要ありません。添付ファイルを解凍し、MQL5フォルダに展開するだけです。 そうすれば、動画で示されているものと同じ結果が得られます。

ファイル 説明
Experts\Expert Advisor.mq5
Chart TradeとEAの連携を示します(操作にはMouse Studyが必要)。
Indicators\Chart Trade.mq5 送信する注文を設定するためのウィンドウを作成します(操作にはMouse Studyが必要)。
Indicators\Market Replay.mq5 市場のレプレイ/シミュレーションサービスを操作するためのコントロールを作成します(操作にはMouse Studyが必要)。
Indicators\Mouse Study.mq5 グラフィカルなコントロールとユーザーとの対話を実現します。市場のレプレイシステムと実際の市場の両方で必要です。
Indicators\Order Indicator.mq5 成行注文を表示し、それらの操作や制御を可能にします。
Indicators\Position View.mq5 保有ポジションを表示し、それらの操作や制御を可能にします。
Services\Market Replay.mq5 市場のレプレイ/シミュレーションサービスを作成し、管理する、システム全体の中核となるファイルです。

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

添付されたファイル |
Anexo.zip (779.24 KB)
取引におけるニューラルネットワーク:異質性を考慮したメタパラメータ学習(HimNet) 取引におけるニューラルネットワーク:異質性を考慮したメタパラメータ学習(HimNet)
時空間適応の柔軟性と高い計算効率を兼ね備えたHimNetフレームワークをご紹介します。このフレームワークは、金融時系列データの高精度かつ安定した予測を可能にします。 主要な構成要素がどのように連携し、複雑なアルゴリズムを実用的で扱いやすいアーキテクチャへと落とし込んでいるのかを詳しく解説します。
線形自己回帰モデルの残差を用いた非線形U-Transformerの学習 線形自己回帰モデルの残差を用いた非線形U-Transformerの学習
線形自己回帰モデルと、残差分析に用いるU-Transformerアーキテクチャを組み合わせた、為替レート予測のための革新的なハイブリッドシステムを紹介します。 このシステムは、シグナルの品質に応じて使用する予測ソースを自動的に切り替えるほか、ナンピンやピラミッディング戦略を含む、一連の取引ロジックを備えています。 このアプローチの最大の利点は、ニューラルネットワークを線形モデルの残差を使って学習させる点にあります。これにより、ニューラルネットワークが取り組むべき課題を単純化し、過学習のリスクを低減できます。 システム全体はMQL5で実装されており、市場環境の変化に自動的に適応しながら、実際の取引に利用できるよう設計されています。
MetaTrader 5における取引銘柄の時間帯別値動きと銘柄間スプレッドの分析 MetaTrader 5における取引銘柄の時間帯別値動きと銘柄間スプレッドの分析
移動平均線を組み込んだProSpread季節性指数インジケータは、価格変動における季節性パターンを特定し、特定の取引時間帯における値動きを分析するテクニカル分析ツールです。 単一の取引銘柄だけでなく、2つの資産間のスプレッドにも対応しており、価格が特定の方向に動く統計的な確率も可視化します。
夢最適化アルゴリズム(DOA) 夢最適化アルゴリズム(DOA)
議論の的となっており、まだ十分に研究されていない現象である「人間の夢」のメカニズムに着想を得た、集団ベースの最適化アルゴリズムです。 記憶特性の異なるエージェントグループ、余弦波による移動の変調、そして99対1という特異なフェーズ配分。これらの特徴が、取引戦略の最適化効率にどのような影響を与えるのかを検証します。