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

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

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

はじめに

皆さん、こんにちは。そして、リプレイ/シミュレーションシステムの作成方法に関する連載の新しい記事へようこそ。

前回の「市場シミュレーション:ポジション表示(III)」では、オブジェクトに対してZOrderプロパティをいつ、そしてなぜ設定する必要があるのかを理解することが、いかに重要であるかを説明しました。ここまで私は、これから何を実現しようとしているのかについて、あえて詳しく説明せずに進めてきました。そのため、これまでの記事を読んだ皆さんの中には、「これは確かに面白いけれど、実際にはあまり役に立たないのではないか」と思われた方もいるかもしれません。しかし、ここからが本番です。これから、ポジションインジケータの実装を続けるためには欠かすことのできない作業に取りかかります。そのためには、すでに存在している4つのアプリケーションを連携させる必要があります。ただし、読者の皆さんは、まだ保留注文(Pending Order)について心配する必要はありません。保留注文をどのように扱うのかについては、今後の段階で明らかにしていきます。ですから、ここでは一つずつ、順番に進めていきましょう。

この段階で最も重要なのは、保留注文を処理することではありません。ポジションを適切に管理できるようにすることです。次の点を覚えておいてください。すでに、成行価格でポジションを新規に建てたり、決済したりできるエキスパートアドバイザー(EA)があります。また、簡単なチャート上の分析・描画をおこなうマウスインジケータ(Mouse Studyインジケータ)もあります。もちろん、このインジケータを変更して、より複雑なスタディを作成することも可能です。さらに、注文をEAへ送信し、買い注文なのか売り注文なのか、そして取引数量を指定できるChart Tradeもあります。しかし、この3つのアプリケーションだけでは、実用的なシステムを完成させるにはまだ1つ足りません。その不足しているアプリケーションこそが、現在実装しているポジションインジケータです。

現段階では、ポジションインジケータはまだ、興味深い試みの域を出ないアプリケーションです。しかし、すでに作成した3つのアプリケーションと組み合わせることで、システム全体が非常に興味深いものになります。ここで、皆さんは次のように疑問に思うかもしれません。「では、EAの中でC_IndicatorPositionクラスを使用するのでしょうか。」いいえ、そうはしません。ポジションインジケータはEAから分離したままにします。このようにすることで、皆さん自身のEAに、このポジションインジケータを追加して利用できるようになります。

もちろん、皆さんのEAがポジションインジケータを利用できるようにするためには、いくつかの調整が必要になります。しかし、私を信じてください。これは、同じ機能を持つEAを何本も作成するよりも、はるかに簡単です。ポジションインジケータに変更を加えた場合、その変更は、そのインジケータを利用しているすべてのEAに直ちに反映されます。これは、それぞれのEAが独自のポジションインジケータを持っている場合とは大きく異なります。

今回提案している考え方、そしてその実装方法は、決して新しいものではありません。実際、これは異なるプログラムから共通コンポーネントを利用できるようにする仕組みそのものです。たとえば、コンピューターゲームがDirectXを利用していることを考えてみてください。DirectXがアップデートされると、DirectXライブラリを利用しているすべてのゲームやプログラムも、その更新されたライブラリを利用できるようになります。もし、それぞれのアプリケーションを、DirectXが改善されるたびに個別に手作業で更新しなければならないとしたら、まったく現実的ではありません。したがって、このモデルを「問題になる可能性があるもの」と考えるべきではありません。むしろ、時間をかけて、より便利で安全なアプリケーションを構築していくための解決策として捉えるべきです。あるアプリケーションで行われた修正や改善が、MetaTrader 5上で利用されているアプリケーションの連携チェーン全体に反映されるようになります。それでは、始めていきましょう。


EAの現在の状態を確認する

コードに変更を加える前に、まず現在のEAのメインコードがどのような状態になっているのかを確認する必要があります。これは、最後にコードを変更してから、かなり長い時間が経過しているためです。そこで、以下のコードを見て、現在のEAの実装状態を確認してみましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - EA.ico"
04. #property description "Demo version between interaction"
05. #property description "of Chart Trade and Expert Advisor"
06. #property version   "1.84"
07. #property link "https://www.mql5.com/pt/articles/12598"
08. //+------------------------------------------------------------------+
09. #include <Market Replay\Order System\C_Orders.mqh>
10. //+------------------------------------------------------------------+
11. enum eTypeContract {MINI, FULL};
12. //+------------------------------------------------------------------+
13. input eTypeContract user00 = MINI;         //Cross order in contract
14. //+------------------------------------------------------------------+
15. C_Orders    *Orders;
16. long        GL_ID;
17. //+------------------------------------------------------------------+
18. int OnInit()
19. {
20.     GL_ID = 0;
21.     Orders = new C_Orders(0xC0DEDAFE78514269);
22.     
23.     return INIT_SUCCEEDED;
24. }
25. //+------------------------------------------------------------------+
26. void OnTick() {}
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29. {
30.     (*Orders).DispatchMessage(id, lparam, dparam, sparam);
31.     switch (id)
32.     {
33.         case CHARTEVENT_CHART_CHANGE:
34.             if (GL_ID > 0)    break; else GL_ID = ChartID();
35.         case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
36.             EventChartCustom(GL_ID, evEA_At_ChartTrade, user00, 0, "");
37.             break;
38.     }
39. }
40. //+------------------------------------------------------------------+
41. void OnDeinit(const int reason)
42. {
43.     switch (reason)
44.     {
45.         case REASON_REMOVE:
46.         case REASON_INITFAILED:
47.             EventChartCustom(GL_ID, evEA_At_ChartTrade, -1, 0, "");
48.             break;
49.     }
50.     
51.     delete Orders;
52. }
53. //+------------------------------------------------------------------+

オリジナルのEAコード

驚きました。!このコードが最後に変更されたのは、本当に第84回の記事だったのでしょうか。はい、そのとおりです。それ以降、コードには一切変更を加えていません。しかし、ここから再びこのコードの作業に戻っていくことにします。このコードに変更を加える前に、まず次の点を理解しておく必要があります。13行目に注目してください。ここには、ユーザー、つまりトレーダーとの唯一のやり取りが存在します。このやり取りは、EAと何らかの形で連携するすべてのアプリケーションに対して、フルコントラクトで動作するのか、それともミニコントラクトで動作するのかを知らせるためのものです。

B3(ブラジル証券取引所)で取引をしていない方にとっては、この設定はあまり意味を持たないかもしれません。しかし、皆さんが何を取引しているかにかかわらず、チャートにはChart Tradeインジケータと、このEAの両方を配置する必要があります。EAをチャートに追加すると、MetaTrader 5上で動作しているすべてのアプリケーションに対してメッセージを送信します。この仕組みについては、本連載の以前の記事ですでに説明しています。もし、ここで何を説明しているのか理解できない場合は、過去の記事を読み返して、このテーマについてより深く理解するようにしてください。このメッセージングの仕組みを理解することは、後々非常に重要になります。この仕組みを理解していなければ、次回以降の記事の内容についていくことができなくなります。

さて、このメッセージが送信されると、すべてのアプリケーションがEAから何を期待されているのかを理解できるようになります。このEAは、ユーザーやトレーダーから直接コマンドを受け取るわけではありません。コマンドの送受信は、すべてメッセージを介しておこなわれます。そのため、コードが一直線に、そして完全に順序立てて実行されることを期待しないでください。ここでは、かなり多くのイベント駆動型のやり取りが発生します。

では、この仕組みの中で、ポジションインジケータはどこに登場するのでしょうか。EAは、チャート上にポジションインジケータが存在していることを知らないのに、どうやってそれを利用できるのでしょうか。実際のところ、EAも、ほかのアプリケーションと同様に、ほかのアプリケーションが存在していることを認識していません。すべてのアプリケーションは、トレーダーがそれぞれをチャートに追加し、それぞれが正しく役割を果たすことを期待しているだけです。しかし、ここには重要な点が1つあります。1つのポジションインジケータは、1つの特定のポジションにのみ対応するため、その銘柄にポジションが存在する前に、あらかじめインジケータをチャートへ追加しておくことはできません。ほかのインジケータとは異なり、このポジションインジケータは、EAによって自動的に追加および削除されます。つまり、この処理にトレーダーが介入する必要はありません。すべてEA自身が処理します。

ただし、私はポジションインジケータをEAの中に組み込むつもりはありません。その理由は、ポジションインジケータを変更したり改善したりした場合でも、EAを再コンパイルすることなく、すべてのEAでその変更を利用できるようにしたいからです。しかし、すべてがシームレスに動作するようにするためには、いくつかの変更が必要になります。そして読者の皆さんには、ここで説明する内容を十分に注意して読んでいただきたいと思います。そうしなければ、システム全体が正常に動作せず、チャート上に表示されるべきデータも提供されなくなってしまいます。もう1つ重要な点があります。現時点では、価格ラインの詳細について心配する必要はありません。今、最初にやらなければならないのは、EAとポジションインジケータが相互にやり取りできる仕組みを作ることです。その後で、ポジションインジケータとマウスインジケータの連携について扱います。このマウスインジケータが、価格ラインの管理を担当することになります。


連携の実装を開始する

それでは、まずポジションインジケータのメインコードにいくつか変更を加えるところから始めましょう。ポジションインジケータが今後、トレーダーやユーザーによってチャートに追加されるものではなくなる(正確には、追加されるべきではなくなる)ためです。この処理はEAが担当します。しかし、EAが、どのインジケータがどのポジションを表しているのかを識別できるようにするためには、インジケータ側のコードとEA側のコードの両方に、いくつか変更を加える必要があります。

ここまでで、何を実現しなければならないのかが理解できたと思います。それでは、新しいポジションインジケータのコードから始めましょう。以下をご覧ください。

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

ポジション表示インジケータコード

コードにいくつか小さな変更が加えられていることに注目してください。これらの変更は非常に単純なものなので、詳しく説明する必要はありません。ただし、15行目では、現在、EAからデータが渡されることを前提としている点に注意してください。トレーダー自身がこのデータを入力することもできます。しかし、この情報は変更しないことをお勧めします。色の変更自体は可能ですが、現時点では色で各水平線を識別しているため、同じ色を割り当てないよう注意してください。いずれにしても、可能な限り、EAから渡されるべきデータは変更しないようにしてください。特に、このデータは、チャート上に両方のコンポーネントが存在しているときに、EAとインジケータの間で情報をやり取りするために使用されます。

また、26行目では、check関数にパラメータを渡していることにも注目してください。以前は、このパラメータは存在していませんでした。それでは、以下に全文を示す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.         }m_Infos;
17. //+------------------------------------------------------------------+
18.         void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n")
19.         {
20.             if (price <= 0) return;
21.             CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop))));
22.             ObjectSetDouble(GetInfoTerminal().ID, szObjName, OBJPROP_PRICE, price);
23.             ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TEXT, szDescription);
24.             ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TOOLTIP, szDescription);
25.             ObjectSetInteger(GetInfoTerminal().ID, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice);
26.         }
27. //+------------------------------------------------------------------+
28.     public    :
29. //+------------------------------------------------------------------+
30.         C_IndicatorPosition(color corPrice, color corTake, color corStop)
31.             :C_Terminal()
32.         {
33.             ZeroMemory(m_Infos);
34.             m_Infos.corPrice = corPrice;
35.             m_Infos.corTake  = corTake;
36.             m_Infos.corStop  = corStop;
37.         }
38. //+------------------------------------------------------------------+
39.         ~C_IndicatorPosition()
40.         {
41.             if (m_Infos.ticket != 0)
42.                 ObjectsDeleteAll(GetInfoTerminal().ID, IntegerToString(m_Infos.ticket));
43.         }
44. //+------------------------------------------------------------------+
45.         bool CheckCatch(ulong ticket)
46.         {
47.             string szName = IntegerToString(m_Infos.ticket = ticket);
48.             
49.             if (!PositionSelectByTicket(m_Infos.ticket)) return false;
50.             if (ObjectFind(GetInfoTerminal().ID, szName) >= 0)
51.             {
52.                 m_Infos.ticket = 0;
53.                 return false;
54.             }
55.             IndicatorSetString(INDICATOR_SHORTNAME, szName);
56.             CreateLineInfos(szName, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice, "Position opening price.");
57.             CreateLineInfos(szName + def_SufixTake, PositionGetDouble(POSITION_TP), m_Infos.corTake, "Take Profit point.");
58.             CreateLineInfos(szName + def_SufixStop, PositionGetDouble(POSITION_SL), m_Infos.corStop, "Stop Loss point.");
59.             
60.             return true;
61.         }
62. //+------------------------------------------------------------------+
63. };
64. //+------------------------------------------------------------------+
65. #undef def_SufixTake
66. #undef def_SufixStop
67. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

コードにはいくつか変更が加えられていますが、その内容は比較的単純なので、詳しく説明する必要はありません。ただし、先ほど説明したように、現在はcheck関数が引数を受け取るようになっています。では、この関数にどのような変更が加えられたのかを確認してみましょう。そのために、45行目にあるCheckCatch関数の定義を見てください。

関数が開始するとすぐに、47行目があることに気づくと思います。ここでは、EAから渡されるデータを変換し、これから作成するオブジェクトの名前として使用できるようにしています。続いて49行目では、ポジションが存在するかどうかを確認しています。ここで、EAがポジションの存在を通知し、それを保証してくれるにもかかわらず、なぜもう一度この確認を行うのかを説明しておく必要があります。では、私は、このポジションインジケータをEAの一部として組み込むつもりはありません。したがって、トレーダー、あるいは単なるユーザーがこのインジケータをチャートに直接追加しようとした場合でも、そのポジションが実際に存在していることを確認する必要があります。ここで注意していただきたいのは、今回は前回の記事で行ったように、インジケータ自身からこのパラメータを取得しているわけではないということです。

ポジションが存在することを確認したら、さらに新しいチェックをおこないます。これは50行目で実行されています。この場合のチェックは、チャート上の各ポジションに対して、インジケータのインスタンスが1つだけ存在することを保証するために使用しています。同じ確認を行う方法はほかにもあります。ここでは、特定の名前を持つラインが存在するかどうかを確認しています。すべてが正常に動作すれば、このインジケータは前回の記事で説明したものと同じように動作します。それでは、EAのコードへ進み、EAがチャートに追加されたときに、システム全体がどのように動作するのかを確認してみましょう。新しいEAは以下のとおりです。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - EA.ico"
04. #property description "Demo version between interaction"
05. #property description "of Chart Trade and Expert Advisor"
06. #property version   "1.116"
07. #property link "https://www.mql5.com/pt/articles/13168"
08. //+------------------------------------------------------------------+
09. #include <Market Replay\Order System\C_Orders.mqh>
10. //+------------------------------------------------------------------+
11. enum eTypeContract {MINI, FULL};
12. //+------------------------------------------------------------------+
13. input eTypeContract user00 = MINI;         //Cross order in contract
14. //+------------------------------------------------------------------+
15. C_Orders    *Orders;
16. long        GL_ID;
17. //+------------------------------------------------------------------+
18. int OnInit()
19. {
20.     GL_ID = 0;
21.     Orders = new C_Orders(0xC0DEDAFE78514269);
22.     
23.     return INIT_SUCCEEDED;
24. }
25. //+------------------------------------------------------------------+
26. void OnTick() {}
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29. {
30.     (*Orders).DispatchMessage(id, lparam, dparam, sparam);
31.     switch (id)
32.     {
33.         case CHARTEVENT_CHART_CHANGE:
34.             if (GL_ID > 0)    break;
35.             else
36.             {
37.                 GL_ID = ChartID();
38.                 for (int handle, count = PositionsTotal() - 1; count >= 0; count--)
39.                 {
40.                     handle = iCustom(_Symbol, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", PositionGetTicket(count));
41.                     ChartIndicatorAdd(GL_ID, 0, handle);
42.                     IndicatorRelease(handle);
43.                 }
44.             }
45.         case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
46.             EventChartCustom(GL_ID, evEA_At_ChartTrade, user00, 0, "");
47.             break;
48.     }
49. }
50. //+------------------------------------------------------------------+
51. void OnDeinit(const int reason)
52. {
53.     switch (reason)
54.     {
55.         case REASON_REMOVE:
56.         case REASON_INITFAILED:
57.             EventChartCustom(GL_ID, evEA_At_ChartTrade, -1, 0, "");
58.             break;
59.     }
60.     
61.     delete Orders;
62. }
63. //+------------------------------------------------------------------+

Expert Advisor.mq5

コードを見てみると、この記事の冒頭で示したコードと比べて、変更点はごくわずかであることが分かります。変更がこれほど少ない本当の理由は、システムを正しく動作させるために、それほど多くの作業を必要としないことを読者に示すためです。変更、正確にはコードへの追加部分は、すべて38行目から43行目の間にあります。しかし、これだけでもEAが、少なくともポジションインジケータとの接続を確立するには十分です。実際、ここでおこなっている処理は、以前はインジケータのコード内部でおこなっていたものです。このコードでは、未決済ポジション1つにつき1つのポジションインジケータをチャートに追加します。

しかし、ここで重要な点に注意してください。私たちはすべての未決済ポジションを確認していますが、ポジションの絞り込みはおこなっていません。単純に、EAが実行されているチャートへインジケータを追加しています。しかし、あるポジションが、そのチャート上で表示されるべき銘柄とは異なる銘柄に対応している可能性があります。ここで、「これは単なる私のミスではないのか」と思うかもしれません。しかし、そうではありません。これは意図的におこなっています。なぜなら、このようなミスは非常によくあるものであり、それに気づいたときには、すでに信頼性の低い仕組みが使われてしまっているということを示したいからです。

ここで問題なのは、MQL5で利用できる_Symbol変数を使ってチャートの銘柄名にアクセスしていることではありません。本当の問題は、PositionGetTicket関数から取得したポジションが、そのチャートの銘柄に属しているのか、あるいは何らかの形で関連しているのかを確認していないことです。インジケータのコードでは、この確認をおこなっていました。これは、常に注意しておかなければならないポイントです。テスト中は、デモ口座で問題なく動作しているように見えるかもしれません。しかし、実際の口座へ移行したとき、実装時の不注意なプログラミングや不十分なテストによって、実際に資金を失う可能性があります。ですから、読者の皆さんがまだプログラミングを学んでいる途中であったり、新しい概念を習得している段階であったりするなら、コードを変更するときには十分注意してください。自分が加えた変更は必ず徹底的に確認し、疑問を解消するためにドキュメントも注意深く読んでください。

さて、ここまで読んでいただくと、プログラミングには、一見しただけでは分からないほど複雑な部分があることが少しずつ分かってきたと思います。しかし、それを理由に落胆したり、プログラミングを諦めたりする必要はありません。大切なのは、状況に適応し、正しい道筋を見つけることです。ここで皆さんは、次のように考えているかもしれません。「それなら、チケットで指定されたポジションの銘柄と、使用したい銘柄が一致しているかどうかを確認すれば、それで十分なのではないですか。」ある意味では、そのとおりです。

しかし、私が「ある意味では」と言うのは、皆さんがどの市場で取引しているのか分からないからです。これは、この記事の冒頭でもすでに説明しました。B3(ブラジル証券取引所)を中心に、フルコントラクトとミニコントラクトが存在するケースがあります。また、現在のコントラクトに対応する履歴データもあります。トレーダーの中には、取引に履歴データを使用する人もいます。一方で、実際に取引したい銘柄そのもののチャートを使用する人もいます。

この状況は一見すると複雑に思えるかもしれません。「なぜ、特定のコントラクトや銘柄のチャートとは異なるチャートを使って取引しようとするのか」と疑問に思うかもしれません。しかし、これは常にトレーダー自身が選択することです。私たちがトレーダーを制限することはできません。プログラマーとしての私たちの役割は、トレーダーのニーズに応えることです。

ここで、さらに別の疑問が生まれます。「正しい銘柄がどれなのかを知る方法がないのなら、この状況をどのように処理すればよいのでしょうか。」しかし、読者の皆さん、この状況はそれほど複雑ではありません。はい、対応する銘柄のデータがどこにあるのかは分かっています。それは、Chart Tradeインジケータに表示されています。そして、ここから状況が本当に興味深くなります。プログラマーには2つの選択肢があります。1つ目は、Chart Tradeパネルに対して、「正しい銘柄はどれなのか、そしてチャート上にどのポジションを表示すべきなのか」を問い合わせる方法です。ここで、皆さんはこう考えるかもしれません。「なぜChart Tradeにそんなことを問い合わせる必要があるのでしょうか。データがChart Tradeオブジェクトに表示されているのなら、そのオブジェクトの内容を読み取ればいいだけではないですか。」

確かに、そのように考えるのが自然です。データはChart Tradeオブジェクトの中にあります。しかし、そのデータへアクセスするのは、それほど簡単ではありません。Chart Tradeからこのデータを取得するためには、専用のメッセージを新たに作成する必要があります。しかし、そうするとEAのコードが大幅に複雑になってしまいます。ただし、もっと簡単な別の方法があります。とはいえ、正しい手順で進める必要があります。そうしなければ、かなり以前に作成され、テストもおこなわれたカプセル化を壊してしまうことになります。落ち着いて考え、コードを分析してみてください。すると、Chart Tradeパネルが銘柄のデータをC_Terminalクラスから取得していることに気づくはずです。より正確には、C_Terminalクラスの47行目にあるCurrentSymbol関数から取得しています。

ここに危険があります。特に、プログラミングを学び始めたばかりの人にとっては注意が必要です。多くの初心者プログラマーは、EAからこの関数へ直接アクセスしようとするでしょう。しかし、それはできません。なぜなら、この関数はprotectedセクションにあるからです。そこで、次のように考えるかもしれません。「それならpublic領域へ移動すればいいのではないか。」これは間違いです。そして、もう1つの間違いは次のようなものです。この関数を見ると、値を返すことも、引数を受け取ることもしていないことが分かります。そこで、「正しい銘柄のデータを返すように変更しよう」と考えるかもしれません。しかし、そうすると、この関数を完全に壊してしまうことになります。なぜなら、将来的にはこの部分が変更され、さらに多くの機能を追加できるようになる可能性があるからです。したがって、正しい解決策は、C_Terminalクラスの47行目にある関数には手を加えないことです。

初心者であれば、ここで次のように考えてしまうかもしれません。「では、この問題は解決できないのではないでしょうか。どの銘柄が使用されるのか分からないのですから。」しかし、それは問題の捉え方が間違っています。はい、解決策はあります。しかも、その方法はシンプルで、エレガントで、効率的です。さらに、すでに作成され、テスト済みのコードに副作用を発生させることもありません。私たちがおこなう必要があるのは、C_Terminalクラスのコンストラクタへ移動し、そこで変更を加えることだけです。そのソースコードを、次のコード断片で確認してみましょう。

106. //+------------------------------------------------------------------+        
107.         C_Terminal(const long id = 0, const uchar sub = 0)
108.             {
109.                 m_Infos.ID = (id == 0 ? ChartID() : id);
110.                 m_Mem.AccountLock = false;
111.                 m_Infos.SubWin = (int) sub;
112.                 CurrentSymbol(false);
113.                 ZeroMemory(m_Mouse);
114.                 m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);

C_Terminal.mqhファイルの元のコード断片

必要な変更を以下に示します。

106. //+------------------------------------------------------------------+        
107.         C_Terminal(const long id = 0, const uchar sub = 0, const bool bFull = false)
108.             {
109.                 m_Infos.ID = (id == 0 ? ChartID() : id);
110.                 m_Mem.AccountLock = false;
111.                 m_Infos.SubWin = (int) sub;
112.                 CurrentSymbol(bFull);
113.                 ZeroMemory(m_Mouse);
114.                 m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);

C_Terminal.mqhファイルの変更後のコード断片

2つのコード断片の違いに気づきましたか。非常に小さく、シンプルで、しかもエレガントな変更です。これで、C_Terminalクラスを利用して、EAから直接銘柄名を取得できるようになりました。あるいは、このデータをポジションインジケータへ渡し、インジケータ側でこの処理を行わせることもできます。どちらの場合でも、EAはこれで、ポジションインジケータに対して、フルコントラクトを表示するのか、それともミニコントラクトを表示するのかを指定できるようになります。これは、コントラクトの履歴を直接利用することによって実現しています。ここで、「それではChart Tradeで指定されている内容と競合して、問題が発生するのではないか」と思うかもしれません。しかし、実際には問題ありません。取得されるデータは、Chart Tradeで指定されている内容と完全に一致します。

それでは、この解決策を実装してみましょう。今回は、インジケータ内でテストを行うつもりはありません。ポジションインジケータの本来の目的は別にあるからです。そのため、データの確認はEA側で直接おこなうことにします。それでは、修正後のEAのコードを以下に示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - EA.ico"
04. #property description "Demo version between interaction"
05. #property description "of Chart Trade and Expert Advisor"
06. #property version   "1.116"
07. #property link "https://www.mql5.com/pt/articles/13168"
08. //+------------------------------------------------------------------+
09. #include <Market Replay\Order System\C_Orders.mqh>
10. #include <Market Replay\Auxiliar\C_Terminal.mqh>
11. //+------------------------------------------------------------------+
12. enum eTypeContract {MINI, FULL};
13. //+------------------------------------------------------------------+
14. input eTypeContract user00 = MINI;         //Cross order in contract
15. //+------------------------------------------------------------------+
16. C_Orders       *Orders = NULL;
17. C_Terminal     *Terminal = NULL;
18. //+------------------------------------------------------------------+
19. int OnInit()
20. {
21.     Orders = new C_Orders(0xC0DEDAFE78514269);
22.     
23.     return INIT_SUCCEEDED;
24. }
25. //+------------------------------------------------------------------+
26. void OnTick() {}
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29. {
30.     (*Orders).DispatchMessage(id, lparam, dparam, sparam);
31.     switch (id)
32.     {
33.         case CHARTEVENT_CHART_CHANGE:
34.             if (Terminal != NULL) break;
35.             else
36.             {
37.                 ulong ul;
38.                 Terminal = new C_Terminal(0, 0, user00);
39.                 for (int handle, count = PositionsTotal() - 1; count >= 0; count--)
40.                 {
41.                     ul = PositionGetTicket(count);
42.                     if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) continue;
43.                     handle = iCustom(_Symbol, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul);
44.                     ChartIndicatorAdd((*Terminal).GetInfoTerminal().ID, 0, handle);
45.                     IndicatorRelease(handle);
46.                 }
47.             }
48.         case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
49.             if (Terminal != NULL)
50.                 EventChartCustom((*Terminal).GetInfoTerminal().ID, evEA_At_ChartTrade, user00, 0, "");
51.             break;
52.     }
53. }
54. //+------------------------------------------------------------------+
55. void OnDeinit(const int reason)
56. {
57.     switch (reason)
58.     {
59.         case REASON_REMOVE:
60.         case REASON_INITFAILED:
61.             if (Terminal != NULL)
62.                 EventChartCustom((*Terminal).GetInfoTerminal().ID, evEA_At_ChartTrade, -1, 0, "");
63.             break;
64.     }
65.     
66.     delete Orders;
67.     delete Terminal;
68. }
69. //+------------------------------------------------------------------+

Expert Advisor.mq5

これこそがプログラミングの醍醐味です。前のコードと、この記事の別の箇所で示したEAのコードを比較してみてください。プログラミングがいかに美しく、そして魅力的なものなのかが分かると思います。C/C++におけるポインタについて少し知識があれば、ここMQL5では、グローバル変数を削除して、クラスの宣言だけを残すことができました。実際には、これらはクラスオブジェクトへのポインタに相当するものです。しかし、それで問題ありません。MQL5ではC/C++とまったく同じ方法でポインタを使用するわけではありません。それでも、C/C++で利用できる仕組みの一部を応用することはできます。もし、このEAで何が起きているのか理解できなくても心配しないでください。ここでは、このコードを理解できるように説明していきます。

10行目では、先ほど私たちの目的に合わせて変更したC_Terminal.mqhヘッダーファイルをインクルードしていることに注目してください。17行目では、クラスへアクセスできるようにグローバル変数を宣言しています。ただし、これは必須ではありませんが、ここではポインタ変数がNULLという値で初期化されていることを明示的に示したいと思います。この値を指定しなかった場合でも、コンパイラが暗黙的に初期化してくれます。しかし、ここではどのような初期化をおこなっているのかを明確にすることも重要です。

さて、OnInit関数の実行が完了すると、MetaTrader 5はCHART_CHANGEイベントを発生させます。このイベントは28行目のOnChartEvent関数によって受け取られます。ここからEAによるイベント処理が始まります。この関数が最初に実行されたとき、Terminalポインタの値はNULLになっています。したがって、34行目の条件はfalseとなり、35行目が実行されます。ここから魔法が始まります。ここで使用しているコードは、先ほどEAで見たコードと非常によく似ていることに注目してください。しかし、38行目ではC_Terminalクラスのコンストラクタを呼び出し、どちらのコントラクトタイプを使用するのかを指定しています。

その結果、コンストラクタによって正しい銘柄名が生成されます。そして41行目で、チケットの値を取得します。これは、ポジションに関するその他のデータを取得する関数を呼び出す前におこなう必要があります。この点についてはMQL5のドキュメントにも記載されていますので、より詳しい情報についてはドキュメントを確認してください。その後、42行目で、ポジションが属している銘柄名が、EAが期待している銘柄名と一致するかどうかを確認できます。一致しなければ39行目へ戻ります。逆に、つまり銘柄名が期待しているものと一致していれば、先ほどと同じようにポジションインジケータを呼び出します。

ここで、49行目と61行目に注目してください。次の質問に答えてみてください。なぜ私は、この2つの行でチェックをおこなっているのでしょうか。少し考えてから、下に示す答えを読んでください。さて、少なくとも一度は自分で考えてみてくれたことを願います。これらの行が存在する理由は、まさにEAのコードの信頼性を高めるためです。C_Terminalクラスが正しく初期化される前に、そのクラスの関数や手続きを呼び出そうとした場合でも、ポインタの値とNULLを比較することで、EAがMetaTrader 5のターミナル上で単純にエラーを発生させてしまうのを防ぐことができます。このチェックを行うことで、実行フローが想定どおりに維持されることを保証できます。

C/C++のプログラマー、特にC言語のプログラマーの多くは、ポインタを使用するときに、このようなチェックをおこなわないことがあります。そのため、「Cで書かれたプログラムには起動時のエラーが多い」と考える人もいます。しかし、そのようなエラーは言語そのものに原因があるわけではありません。原因は、プログラムがどのように書かれているのか、あるいはなぜそのような書き方がされたのかにあります。場合によっては、システムの脆弱性を探すプログラムが、システムの所有者が意図していない処理を実行する目的で、意図的に作成されることさえあります。


最終的な考察

この記事では、これまで完全に独立していたさまざまなアプリケーションを、少しずつ連携させ始めました。Chart Trade、マウスインジケータ、そしてEAにはすでに一定の関係がありましたが、これまでは、取引サーバー上で開かれているポジションをチャート上に直接表示する方法がありませんでした。また、反対方向の注文を組み合わせるシステムが使用されることもあります。ここからは、それが可能になります。そして、これによって新しいアイデアや実装につながる多くの可能性が開かれます。まだコンポーネントを実際に動作させ始めたばかりですが、進むべき方向はすでに見えてきています。

確かに、最初の段階では、このシステムは非常に原始的に見えるかもしれません。そして、もっと高度な状態まで実装してから紹介することもできました。しかし、私はそれが適切だとは考えませんでした。そのようにしてしまうと、多くの初心者プログラマーが、コードの中で実際に何が起きているのかを理解できないままになってしまうからです。覚えておいてください。変更は段階的におこなうことで、誰もがその変化についていけるようにしています。この教材には教育的な目的があります。MetaTrader 5で何かを実現するための、唯一の方法を示すことを目的としているわけではありません。ここまでの内容が、本当に知識を求めている皆さんのお役に立つことを願っています。次回の記事では、今回紹介したアイデアをさらに発展させていきます。それでは、また次回お会いしましょう。

ファイル 説明
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/13168

添付されたファイル |
Anexo.zip (779.24 KB)
人工原子アルゴリズム(A3) 人工原子アルゴリズム(A3)
化学プロセスから着想を得たメタヒューリスティック最適化手法であるA3アルゴリズムをMQL5で実装します。調整可能なパラメータは2つだけであり、さらに小規模な集団を使用することで、十分な解の品質を維持しながら、高速に動作することを実現しています。
MQL5での量子ニューラルネットワーク(第3回):量子ビットに基づく仮想量子プロセッサ MQL5での量子ニューラルネットワーク(第3回):量子ビットに基づく仮想量子プロセッサ
数学的アナロジーではなく、量子原理に基づく本格的な量子シミュレータを使用した取引システムの構築に焦点を当てています。このシステムでは、3つの仮想量子ビット、量子ゲート、重ね合わせの原理を使用して市場を分析します。また、MQL5で実装されたMetaTrader 5用自動売買EAとして実装されています。最大の成果は、単なるシミュレーションから、金融情報処理における実際の量子原理の利用へ移行したことです。
機械学習におけるガウス過程(第2回):MQL5での分類モデルの実装とテスト 機械学習におけるガウス過程(第2回):MQL5での分類モデルの実装とテスト
MQL5におけるガウス過程ライブラリの主要なインターフェースであるIKernel、ILikelihood、IInferenceの実装について見ていきます。また、合成データを使用してその動作を確認し、分類用および回帰用のインジケータを実装します。これらのインジケータでは、新しいバーが追加されるたびにモデルを再学習するオンラインモードでの動作を確認します。
MQL5における建値機能の実装(第2回):ATRとRRRに基づく建値機構 MQL5における建値機能の実装(第2回):ATRとRRRに基づく建値機構
MQL5におけるATRおよびRRRRベースの建値機構の実装を完了し、パラメータを再入力することなく建値モードを簡単に切り替えられるクラスを新たに実装します。各建値方式の有効性を評価するため、複数のバックテストを実施し、アルゴリズム取引の観点からそれぞれのメリットとデメリットを分析します。