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

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

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

はじめに

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

前回の「市場シミュレーション:ポジション表示(II)」では、非常に単純なものを作成しました。その目的は、未決済ポジションに対応する価格ラインをチャート上に表示することです。このインジケータによって必要な情報を得ることはできますが、まだ実用段階には達していません。これは、いくつかの小さな問題を解決する必要があるためです。しかし、何も難しいことではありません。これは単に、コードの実装をもう少し先へ進める前に、読者の皆さんに理解しておいていただく必要があることです。

物事をもう少し単純にし、理解しやすくするために、今回はこの作業だけを扱う別のテーマとして見ていくことにします。それでは、これ以上時間をかけずに本題へ入りましょう。


ZOrderプロパティを理解する

読者の皆さんは、MQL5のオブジェクトには、コードの中でほとんど使用されず、明示的に定義されることもほとんどないプロパティが存在することに気づいているでしょうか。私自身、これまで非常に多くのコードを調べてきましたが、実際にこのオブジェクトプロパティを定義している人を見つけたことがありません。ここで言っているのは、ZOrderプロパティです。本連載を追ってきたのであれば、かなり以前から、このプロパティがC_Terminalクラスの中で定義されていることに気づいているでしょう。これは、C_Terminalクラスの手続きであるCreateObjectGraphics呼び出しを通じてオブジェクトを作成するときに使用されています。

そして、最近の記事では、特定の場面でこのプロパティに値を設定する必要があると説明しました。しかし、なぜでしょうか。チャートにオブジェクトを追加する多くのコードが、このプロパティを使用していない、より正確に言えば、このプロパティに値を定義していないからです。すべてのプログラマーが何をすべきか、あるいはすべきでないか、また、どのようにコードを書くべきか、あるいは書くべきでないかを私が決めるために、ここにいるわけではありません。私は、読者の皆さん、そしてこれらの処理が内部でどのように動作しているのかを本当に理解したいすべての方々に対して、実際に舞台裏で何が起こっているのかを示すために、ここにいます。

私がこれから説明する内容を理解しないまま、あるいはMetaTrader 5の使い方やMQL5でのプログラミングに関する特定の概念をきちんと理解しないままでは、間違いなくこのプラットフォームで非常に悪い経験をすることになるでしょう。そして、これらの概念の一つが、ZOrderプロパティに直接関係しています。

ここで説明する内容をできるだけ明確に示すために、以下のコードを使用します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. //+------------------------------------------------------------------+
07. input char user00 = 0;      //ZOrder to Chart #01
08. input char user01 = 0;      //ZOrder to Chart #02
09. //+------------------------------------------------------------------+
10. void ChartOfTest(string sz1, int x, int y, int zOrder)
11. {
12.     long id = ChartID();
13.     
14.     ObjectCreate(id, sz1, OBJ_CHART, 0, 0, 0);
15.     ObjectSetInteger(id, sz1, OBJPROP_XDISTANCE, x);
16.     ObjectSetInteger(id, sz1, OBJPROP_YDISTANCE, y);
17.     ObjectSetInteger(id, sz1, OBJPROP_ZORDER, zOrder);
18.     ObjectSetInteger(id, sz1, OBJPROP_SELECTABLE, true);
19. }
20. //+------------------------------------------------------------------+
21. int OnInit()
22. {
23.     ChartOfTest("Chart #01", 160, 140, user00);
24.     ChartOfTest("Chart #02", 200, 200, user01);
25.     
26.     return INIT_SUCCEEDED;
27. }
28. //+------------------------------------------------------------------+
29. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
30. {
31.     return rates_total;
32. }
33. //+------------------------------------------------------------------+
34. void OnDeinit(const int reason)
35. {
36.     ObjectsDeleteAll(ChartID(), "Chart #");
37. }
38. //+------------------------------------------------------------------+

インジケータのソースコード

この単純なコードは、ZOrderプロパティがMetaTrader 5におけるユーザー体験にどのような影響を与えるのか、そして実際にどのような影響を与えるのかを理解するために必要なことを、正確におこなってくれます。23行目と24行目では、2つのオブジェクトを作成し、現在開いているチャート上に配置するための関数を呼び出していることに注目してください。作成されるオブジェクトはOBJ_CHART型であり、14行目で確認できます。読者の皆さんの利便性を考慮して、07行目と08行目には2つの入力パラメータを用意しています。これらの目的は、オブジェクトのZOrder値を何度も手動で調整する必要がないようにすることです。

デフォルトでは、値は常に0です。したがって、今のところはそのままにしておきます。その結果は、次の図に示されています。

非常に単純で、期待どおりの分かりやすい結果です。「では、チャートの時間足を変更したらどうなるのでしょうか。チャートの背景を作成することを目的としたインジケータがある、このウィンドウでは何が起こるのでしょうか。OBJ_CHARTオブジェクトを操作できなくなるような状況になるのでしょうか。」これについては、次のアニメーションで確認できます。

背景画像、つまりOBJ_BITMAPLABELオブジェクトがOBJ_CHARTオブジェクトの上に配置されていることに注目してください。通常であれば、これは問題を引き起こすはずです。OBJ_CHARTオブジェクトの背景は、もはや黒ではありません。代わりに、OBJ_BITMAPLABELオブジェクトの上に、そのオブジェクトが配置されている領域の内容が表示されています。しかし、17行目ではZOrderに0という値を設定しています。一方、背景画像を持つオブジェクトのZOrder値は、それよりも低く、この場合は-1です。これは以前の記事で見たとおりです。そのため、この場合、OBJ_CHARTオブジェクトのクリックへの反応やオブジェクトの選択動作は影響を受けません。これは、上のアニメーションで確認できます。

しかし、私たちは、前面にあるオブジェクトが優先されているという明確な錯覚を持つことになります。これは、次のアニメーションに示されています。

先ほど述べたように、これは錯覚です。実際に前面にあるオブジェクトは、チャート全体を覆っている画像を含むOBJ_BITMAPLABELです。これによって、背景画像が作られています。しかし、この錯覚は、オブジェクトの優先順位を変更すると、さらに興味深いものになります。これを上記のコードでおこなう場合は、次のアニメーションを見てみることをお勧めします。

下側にあるオブジェクト、つまりCHART #01の優先順位を変更したことに注目してください。このオブジェクトの優先順位が高くなったため、アニメーションに示されている結果になります。これを理解できなかった場合は、ご自身のMetaTrader 5ターミナルで試してみてください。CHART #02に属する領域であり、同時にCHART #01にも属している領域をクリックした場合でも、CHART #01が選択されることに気づくでしょう。CHART #02を選択できるのは、CHART #01の外側にあり、なおかつCHART #02に属している領域をクリックした場合だけです。両方がまったく同じ領域に配置されている場合、CHART #02によって覆われていたとしても、CHART #01が優先されます。

OBJ_CHARTに対して、背景画像として機能しているOBJ_BITMAPLABELオブジェクトの値以下の値を設定してみると、間違いなくさらに興味深い結果になります。あるいは、次の画像に示されているショートカット/クイックコマンドを使用して、別のオブジェクトを配置してみてもよいでしょう。

次のことを試してみてください。もう1つOBJ_CHARTを追加します。ただし今回は、先ほど示したキーボードショートカットを使用して追加してください。そして、前のアニメーションで示したように、インジケータの値を変更しながら試してみてください。間違いなく、ZOrderプロパティに適切な値を使用することがどれほど重要なのか、少しずつ理解できるようになるでしょう。では、これを現在開発しているものの、具体的にどこで適用するのでしょうか。この点に最も適切に答え、あわせて話題を切り分けるために、この問題については新しいテーマで見ていくことにします。


得られた知識の適用

ここまで説明してきたことを知り、理解しておくことは、これから実装で何をおこなうのかを理解するうえで非常に重要です。後になれば、この知識は意味を失うことになります。なぜなら、私たちは別のアプローチを使用することになるからです。しかし、今の段階では重要です。特に、コードを自分自身の用途に合わせて変更したり、適応させたりする場合には重要になります。

前回の記事では、3本のラインを作成しました。1本はポジションのオープン価格、1本はストップロスレベル、そしてもう1本はテイクプロフィットレベルを示すラインです。素晴らしいです。ここまでは、おこなったことに何も問題はありません。オープン価格を示す水平線は、決して移動できないようにするべきです。この点については、すぐに修正します。一方、ストップロスとテイクプロフィットの水平線は、自由に移動できるようにします。そして、ここで問題が発生します。ストップロスラインとテイクプロフィットラインの両方が同じ価格にある場合です。

この場合、どちらか一方を選択しようとしたとき、どちらのラインを優先すべきでしょうか。これは、例えばラインが移動された、あるいは削除されたといった、特定のイベントをシステムが受け取れるようにするために必要です。皆さんは「ストップロスを優先すべきだ」と言うかもしれません。一方、別のトレーダーは「テイクプロフィットを優先すべきだ」と言うかもしれません。このような状況では、実際には誰が正しいのでしょうか。

問題が見えてきたでしょうか。ある意味では、両方のラインが同じZOrder値を持っている場合、ラインが作成された順番がこの判断に影響します。したがって、単純に作成順序を変更するだけで、多くの問題を解決できます。しかし、この状況をさらに極端なところまで持っていってみましょう。一部のトレーダー、特にヘッジ口座を使用しているトレーダーが、複数の未決済ポジションを持つことは決して珍しいことではありません。この場合、ストップロスラインとテイクプロフィットラインの間に異なるZOrderを設定する必要があるかもしれません。しかし、これをおこなえばストップロスラインとテイクプロフィットラインが重なるという問題は解消できますが、別の問題が発生します。例えば、2本のストップロスラインが偶然重なってしまう場合です。

ここで、ZOrderを変更することが、意味をなさなくなることに気づいたでしょうか。1つの問題を解決するために異なる値を使用したとしても、必ず別の解決すべき問題が存在することになります。しかし、リプレイ/シミュレーションシステムの開発と実装に関するいくつかの点について私に助言してくれている、何人かのプロトレーダーによれば、サーバー上にこうした記録エントリを2つ、3つ、あるいは多くても4つ以上保持しておく実用的な理由はありません。言い換えれば、彼らの意見では、ヘッジ口座を使用する場合でも、同時に2つ、3つ、あるいは多くても4つ以上の未決済ポジションを保持しておく実用的な意味はありません。ネッティング口座では、この問題自体が存在しないからです。この初期段階では、ヘッジ口座だけを対象として考えることにします。ただし、これはインジケータがネッティング口座で動作しないという意味ではありません。

それでは、前回の記事で確認したコードに戻りましょう。そこでは、ストップロスラインとテイクプロフィットラインに同じZOrderを設定していました。これを変更するには、コードに小さな変更を加える必要があります。変更は非常に小さいため、ここでは詳細には触れません。変更後のコードがどのようになるのかだけを確認してください。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Indicator for tracking an open position on the server."
04. #property version   "1.00"
05. #property indicator_chart_window
06. #property indicator_plots 0
07. //+------------------------------------------------------------------+
08. #define def_SufixLinePrice   "Price"
09. #define def_SufixLineTake    "Take"
10. #define def_SufixLineStop    "Stop"
11. //+------------------------------------------------------------------+
12. #include <Market Replay\Auxiliar\C_Terminal.mqh>
13. //+------------------------------------------------------------------+
14. input color user00 = clrRoyalBlue;          //Color Line Price
15. input color user01 = clrForestGreen;        //Color Line Take Profit
16. input color user02 = clrFireBrick;          //Color Line Stop Loss
17. //+------------------------------------------------------------------+
18. C_Terminal *Terminal;
19. struct st
20. {
21.     long       id;
22.     string     szPrefixName;
23. }glVariables;
24. //+------------------------------------------------------------------+
25. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n")
26. {
27.     if (price <= 0) return;
28.     (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02))));
29.     ObjectSetDouble(glVariables.id, szObjName, OBJPROP_PRICE, price);
30.     ObjectSetString(glVariables.id, szObjName, OBJPROP_TEXT, szDescription);
31.     ObjectSetString(glVariables.id, szObjName, OBJPROP_TOOLTIP, szDescription);
32.     ObjectSetInteger(glVariables.id, szObjName, OBJPROP_SELECTABLE, cor != user00);
33. }
34. //+------------------------------------------------------------------+
35. int OnInit()
36. {
37.     ZeroMemory(glVariables);
38.     Terminal = new C_Terminal();
39.     if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED;
40.     glVariables.id = (*Terminal).GetInfoTerminal().ID;
41.     glVariables.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET));
42.     CreateLineInfos(glVariables.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price.");
43.     CreateLineInfos(glVariables.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point.");
44.     CreateLineInfos(glVariables.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point.");
45.     
46.     return INIT_SUCCEEDED;
47. }
48. //+------------------------------------------------------------------+
49. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
50. {
51.     return rates_total;
52. }
53. //+------------------------------------------------------------------+
54. void OnDeinit(const int reason)
55. {
56.     delete Terminal;
57.     if (glVariables.id > 0)
58.         ObjectsDeleteAll(glVariables.id, glVariables.szPrefixName);
59. }
60. //+------------------------------------------------------------------+

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

ここで、ストップロスラインがテイクプロフィットラインよりも優先されるようになったことに注目してください。これは、ZOrderの値が高くなっていることで説明できます。したがって、2つの未決済ポジションがあり、ストップロスラインとテイクプロフィットラインが同じ価格にある場合、これらのラインを操作しようとすると、常にストップロスラインが優先されます。ここで注目してほしいのは、この結果につながった変更が28行目だけであることです。この行を前のコードの同じ行と比較して、変更内容を理解してください。

しかし、このインジケータは動作するものの、ヘッジ口座で使用するには適していません。システムが常に最初の未決済ポジション、または最後の未決済ポジションのどちらかを探すことになるからです。そして、これを変更するために何らかの処理をおこなったとしても、結局のところ、それが実際の効果をもたらすことはありません。より効果的な結果をもたらし、さらに汎用性の高い解決策を実装する必要があります。さらに、インジケータが各ラインの意味を理解できるように、異なる色を使用していることにも気づくでしょう。このような解決策は、テストには役立ちます。しかし、より一般的な用途にはまったく適していません。したがって、私たちにはまだ解決し、修正しなければならない問題が数多くあります。

この段階で、プログラミングを始めたばかりの多くの人は、途中で諦めてしまうことがあります。あるいは、あまりにも多くの変更を加えてしまい、最終的には自分が作成しているコードを制御できなくなってしまいます。そこで、すでにプログラミングの方法を知っており、適切な基礎知識を持っている方には、経験の少ない方々に対して辛抱強く接していただきたいと思います。これから、初心者がどのように考え始めればよいのかを示していきます。そうすることで、初心者の方々がプログラミングという営みの面白さに心から惹かれ、自分自身の解決策を作れるようになることを願っています。


状況の修正を始める

読者の皆さん、もし上に示したような、改善する必要のあるコードに遭遇した場合、何の基準もなく変更を始めてはいけません。まず、作業をいくつかの部分に分割することから始めてください。ただし、その間もコードが動作する状態を必ず維持してください。いいえ、もう一度繰り返します。各コンポーネントを分離するまでは、何も変更しないでください。これをおこなう最も単純で適切な方法の一つは、コードの一部をヘッダーファイルに移し、そのうえですぐに変更を始められるようにすることです。しかし、すべての部分を1つのクラスにまとめるほうがはるかに簡単なので、今回はそうすることにします。では、なぜコードをヘッダーファイルに分割するよりも、クラスに配置するほうが簡単なのでしょうか。

理由は単純です。コードをクラスに配置しておけば、後からそれをヘッダーファイルへ移すことができます。しかし、単純にコードをヘッダーファイルへ移しただけでは、管理が簡単になるわけではありません。これは、将来的に名前の競合が発生する可能性があるためです。このような競合は、多数のファイルを含むコードベースでは決して珍しいことではありません。一方、要素をクラスへ移す場合、競合が発生する可能性を大幅に減らすことができます。もちろん、競合が完全になくなるという意味ではありません。プログラミングを続けていけば、皆さん自身がそのことを理解するようになるでしょう。しかし、そのような競合が発生した場合でも、解決することははるかに簡単かつ迅速になります。これを理解したところで、リプレイ/シミュレーションシステム内の要素を、新しいクラスへ移すところから始めましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Indicator for tracking an open position on the server."
04. #property version   "1.00"
05. #property indicator_chart_window
06. #property indicator_plots 0
07. //+------------------------------------------------------------------+
08. #define def_SufixLinePrice   "Price"
09. #define def_SufixLineTake    "Take"
10. #define def_SufixLineStop    "Stop"
11. //+------------------------------------------------------------------+
12. #include <Market Replay\Auxiliar\C_Terminal.mqh>
13. //+------------------------------------------------------------------+
14. input color user00 = clrRoyalBlue;          //Color Line Price
15. input color user01 = clrForestGreen;        //Color Line Take Profit
16. input color user02 = clrFireBrick;          //Color Line Stop Loss
17. //+------------------------------------------------------------------+
18. class C_IndicatorPosition
19. {
20.     private    :
21.         struct st00
22.         {
23.             long      id;
24.             string    szPrefixName;
25.         }m_Infos;
26. //+------------------------------------------------------------------+
27.         void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n")
28.         {
29.             if (price <= 0) return;
30.             (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02))));
31.             ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price);
32.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription);
33.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription);
34.             ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != user00);
35.         }
36. //+------------------------------------------------------------------+
37.     public    :
38. //+------------------------------------------------------------------+
39.         C_IndicatorPosition(const long Id)
40.         {
41.             ZeroMemory(m_Infos);
42.             m_Infos.id = Id;
43.             m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET));
44.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price.");
45.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point.");
46.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point.");
47.         }
48. //+------------------------------------------------------------------+
49.         ~C_IndicatorPosition()
50.         {
51.             if (m_Infos.id > 0)
52.                 ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName);
53.         }
54. //+------------------------------------------------------------------+
55. };
56. //+------------------------------------------------------------------+
57. C_Terminal *Terminal;
58. C_IndicatorPosition *Positions;
59. //+------------------------------------------------------------------+*/
60. int OnInit()
61. {
62.     Terminal = new C_Terminal();
63.     if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED;
64.     Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID);
65.     
66.     return INIT_SUCCEEDED;
67. }
68. //+------------------------------------------------------------------+
69. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
70. {
71.     return rates_total;
72. }
73. //+------------------------------------------------------------------+
74. void OnDeinit(const int reason)
75. {
76.     delete Terminal;
77.     delete Positions;
78. }
79. //+------------------------------------------------------------------+

ポジション表示のソースコード

以下のコードでは、複雑さが増したように思うかもしれません。しかし実際には、以前クラスの外にあったコードを、単にクラスの内部へ配置しただけです。したがって、見た目とは反対に、複雑さを追加しているのではありません。むしろ、要素の実装方法を改善しています。以前から存在していたコードそのものは変わっていませんが、より適切に保護され、タスクをより効果的に分離できるようになっています。ここで「より高いレベルの保護」と言っているのは、CreateLineInfos関数が現在はprivateになっているからです。つまり、呼び出し側のコード、あるいはユーザーは、インジケータが具体的にどのような方法で作成されるのかを知る必要がありません。単にインジケータの作成を要求するだけです。その実装方法は時間の経過とともに大きく変わる可能性があります。しかし、ユーザーから見れば、その使い方は常に同じままです。

ただし、今説明したことをおこなっただけでは、私たちが抱えている問題は何も解決しません。これは、より効率的な方法でコードを実装しやすくしただけです。ここで、すべてのコードが以前とまったく同じままであり、その動作も一切変わっていないことに注目してください。これは、私たちが常に最初に確認しなければならないことです。コードをクラスの内部へ移した後でも、以前とまったく同じように動作することを確認するまでは、コードを変更してはいけません。その後に何をおこなうかは、皆さんが最初に何を必要としているのか、あるいは何をおこないたいのかによって決まります。しかし、すでにクラスを作成したのであれば、すぐにそれをヘッダーファイルへ移すことができます。こうすることで、メインコードを不必要に肥大化させることを避けられます。そのために、クラスと同じ名前を持つファイルを作成し、その中にクラスを配置します。これは優れたプログラミング上の慣例ではありますが、必須というわけではありません。それでは、新しいメインコードは以下のようになります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Indicator for tracking an open position on the server."
04. #property version   "1.00"
05. #property indicator_chart_window
06. #property indicator_plots 0
07. //+------------------------------------------------------------------+
08. #include <Market Replay\Auxiliar\C_Terminal.mqh>
09. #include <Market Replay\Order System\C_IndicatorPosition.mqh>
10. //+------------------------------------------------------------------+
11. input color user00 = clrRoyalBlue;          //Color Line Price
12. input color user01 = clrForestGreen;        //Color Line Take Profit
13. input color user02 = clrFireBrick;          //Color Line Stop Loss
14. //+------------------------------------------------------------------+
15. C_Terminal *Terminal;
16. C_IndicatorPosition *Positions;
17. //+------------------------------------------------------------------+
18. int OnInit()
19. {
20.     Terminal = new C_Terminal();
21.     if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED;
22.     Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID);
23.     
24.     return INIT_SUCCEEDED;
25. }
26. //+------------------------------------------------------------------+
27. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
28. {
29.     return rates_total;
30. }
31. //+------------------------------------------------------------------+
32. void OnDeinit(const int reason)
33. {
34.     delete Terminal;
35.     delete Positions;
36. }
37. //+------------------------------------------------------------------+

ポジション表示のソースコード

クラスのコードはどこへ行ったのでしょうか。現在は、上のコードの09行目で場所が指定されているファイル内にあります。ここで、クラスが、以前はメインコード側にあったいくつかのデータも一緒に持っていったことに注目してください。これは、先ほど説明したように、ユーザーがすべてがどのように構成されているのかを知る必要がないためです。ユーザーが知る必要があるのは、具体的に何を呼び出す必要があるのか、そしてクラスのコードにどのパラメータを渡す必要があるのかだけです。呼び出し側のコードは、その処理方法を意識する必要はありません。それでは、以下に示すC_IndicatorPositionヘッダーファイルのコードを見てみましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_SufixLinePrice   "Price"
05. #define def_SufixLineTake    "Take"
06. #define def_SufixLineStop    "Stop"
07. //+------------------------------------------------------------------+
08. class C_IndicatorPosition
09. {
10.     private    :
11.         struct st00
12.         {
13.             long      id;
14.             string    szPrefixName;
15.         }m_Infos;
16. //+------------------------------------------------------------------+
17.         void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n")
18.         {
19.             if (price <= 0) return;
20.             (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02))));
21.             ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price);
22.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription);
23.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription);
24.             ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != user00);
25.         }
26. //+------------------------------------------------------------------+
27.     public    :
28. //+------------------------------------------------------------------+
29.         C_IndicatorPosition(const long Id)
30.         {
31.             ZeroMemory(m_Infos);
32.             m_Infos.id = Id;
33.             m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET));
34.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price.");
35.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point.");
36.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point.");
37.         }
38. //+------------------------------------------------------------------+
39.         ~C_IndicatorPosition()
40.         {
41.             if (m_Infos.id > 0)
42.                 ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName);
43.         }
44. //+------------------------------------------------------------------+
45. };
46. //+------------------------------------------------------------------+
47. #undef def_SufixLinePrice
48. #undef def_SufixLineTake
49. #undef def_SufixLineStop
50. //+------------------------------------------------------------------+

C_IndicatorPosition.mqhのソースコード

コード自体は変わっていないことに注目してください。ファイルの末尾に3行だけ新しく追加されています。これらの目的は、このヘッダーファイルの内部でのみ意味を持つ定義を削除することです。言い換えれば、ここでは最小権限の原則を使用しています。より分かりやすく言えば、自分に関係のないことを、誰も知る必要はありません。これによって、皆さんが意図しない形で情報が漏れることを防げます。さらに、これはもちろん、優れたプログラミング上の慣例でもあります。なぜなら、ほかのコードで、ここで定義したものと同じ名前を使用する可能性があるからです。そして、ここでそれらを削除しておかなければ、別の場所で削除する必要が生じます。これは、非常に大規模なコードを改善するときに、多くの不便を生みます。定義の競合によってエラーが発生し、そのエラーが何の関係もないように見える場合があるからです。

よいでしょう。しかし、ここでヘッダーファイルのコードだけを見ると、メインコードに依存する値を宣言し、それらを使用していることに気づくでしょう。これは問題を引き起こします。なぜなら、メインコードを変更すると、このヘッダーファイルも変更しなければならなくなるからです。言い換えれば、これは非常に大きな作業量になります。したがって、この問題を修正する必要があります。そのためのよい解決策は、メインコードからこれらの値を独立して渡すようにすることです。そこで、変更をすでに適用した新しいヘッダーファイルを以下に示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_SufixLinePrice   "Price"
05. #define def_SufixLineTake    "Take"
06. #define def_SufixLineStop    "Stop"
07. //+------------------------------------------------------------------+
08. class C_IndicatorPosition
09. {
10.     private    :
11.         struct st00
12.         {
13.             long      id;
14.             string    szPrefixName;
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.             (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop))));
22.             ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price);
23.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription);
24.             ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription);
25.             ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice);
26.         }
27. //+------------------------------------------------------------------+
28.     public    :
29. //+------------------------------------------------------------------+
30.         C_IndicatorPosition(const long Id, color corPrice, color corTake, color corStop)
31.         {
32.             ZeroMemory(m_Infos);
33.             m_Infos.id = Id;
34.             m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET));
35.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice = corPrice, "Position opening price.");
36.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), m_Infos.corTake = corTake, "Take Profit point.");
37.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), m_Infos.corStop = corStop, "Stop Loss point.");
38.         }
39. //+------------------------------------------------------------------+
40.         ~C_IndicatorPosition()
41.         {
42.             if (m_Infos.id > 0)
43.                 ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName);
44.         }
45. //+------------------------------------------------------------------+
46. };
47. //+------------------------------------------------------------------+
48. #undef def_SufixLinePrice
49. #undef def_SufixLineTake
50. #undef def_SufixLineStop
51. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

コンストラクタは新たに3つの引数を受け入れる必要があることにご注意ください。これらの引数は各ラインの色を渡します。したがって、メインコードは以下の箇所を変更する必要があります。変更箇所は、以下のコード断片に示されています。

17. //+------------------------------------------------------------------+
18. int OnInit()
19. {
20.     Terminal = new C_Terminal();
21.     if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED;
22.     Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID, user00, user01, user02);
23.     
24.     return INIT_SUCCEEDED;
25. }
26. //+------------------------------------------------------------------+

インジケータからの断片

すべては単純です。これで、ほぼ完全に独立したコードができました。これは、まだメインコード側で、未決済ポジションが存在するかどうかについての情報を読み取っている部分に依存しているためです。これは、前のコード片の21行目でおこなわれています。そこで、この部分を修正しましょう。なぜなら、ヘッジ口座でもインジケータを動作させたいからです。このような口座では、同じ銘柄について、同時に複数の未決済ポジションを持つことができます。しかし、この問題を解決するためには、コードにやや大きな変更を加える必要があります。とはいえ、すでに処理を分離しているため、この変更をおこなうことははるかに簡単で、より快適になります。それでは、新しいインジケータのコードを以下に示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Indicator for tracking an open position on the server."
04. #property version   "1.00"
05. #property indicator_chart_window
06. #property indicator_plots 0
07. //+------------------------------------------------------------------+
08. #define def_ShortName "Position View"
09. //+------------------------------------------------------------------+
10. #include <Market Replay\Order System\C_IndicatorPosition.mqh>
11. //+------------------------------------------------------------------+
12. input color user00 = clrRoyalBlue;          //Color Line Price
13. input color user01 = clrForestGreen;        //Color Line Take Profit
14. input color user02 = clrFireBrick;          //Color Line Stop Loss
15. //+------------------------------------------------------------------+
16. C_IndicatorPosition *Positions;
17. //+------------------------------------------------------------------+*/
18. int OnInit()
19. {
20.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
21.     Positions = new C_IndicatorPosition(user00, user01, user02);    
22.     if (!Positions.CheckCatch())
23.     {
24.         ChartIndicatorDelete(ChartID(), 0, def_ShortName);
25.         return INIT_FAILED;
26.     }
27.     
28.     return INIT_SUCCEEDED;
29. }
30. //+------------------------------------------------------------------+
31. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
32. {
33.     return rates_total;
34. }
35. //+------------------------------------------------------------------+
36. void OnDeinit(const int reason)
37. {
38.     delete Positions;
39. }
40. //+------------------------------------------------------------------+

ポジション表示のソースコード

OnInit関数のコードに、非常に単純な変更が加えられていることに注目してください。基本的には、これでインジケータの初期化に失敗した場合、チャートから削除されるようになっています。これは20行目と24行目によって実現されています。特に24行目が、チャート上に残すことができない場合にインジケータを削除する処理をおこないます。では、具体的に何が原因で、インジケータをチャート上に残せなくなるのでしょうか。現時点では、残っている条件は1つだけです。これを理解するために、22行目で呼び出されているコードを見てみましょう。それでは、新しいクラスのコードへ進みます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_SufixLinePrice   "Price"
05. #define def_SufixLineTake    "Take"
06. #define def_SufixLineStop    "Stop"
07. //+------------------------------------------------------------------+
08. #include "..\Auxiliar\C_Terminal.mqh"
09. //+------------------------------------------------------------------+
10. class C_IndicatorPosition : private C_Terminal
11. {
12.     private    :
13.         struct st00
14.         {
15.             ulong     ticket;
16.             string    szPrefixName;
17.             color     corPrice, corTake, corStop;
18.         }m_Infos;
19. //+------------------------------------------------------------------+
20.         void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n")
21.         {
22.             if (price <= 0) return;
23.             CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop))));
24.             ObjectSetDouble(GetInfoTerminal().ID, szObjName, OBJPROP_PRICE, price);
25.             ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TEXT, szDescription);
26.             ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TOOLTIP, szDescription);
27.             ObjectSetInteger(GetInfoTerminal().ID, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice);
28.         }
29. //+------------------------------------------------------------------+
30.     public    :
31. //+------------------------------------------------------------------+
32.         C_IndicatorPosition(color corPrice, color corTake, color corStop)
33.             :C_Terminal()
34.         {
35.             ZeroMemory(m_Infos);
36.             m_Infos.corPrice = corPrice;
37.             m_Infos.corTake  = corTake;
38.             m_Infos.corStop  = corStop;
39.         }
40. //+------------------------------------------------------------------+
41.         ~C_IndicatorPosition()
42.         {
43.             if (m_Infos.ticket != 0)
44.                 ObjectsDeleteAll(GetInfoTerminal().ID, m_Infos.szPrefixName);
45.         }
46. //+------------------------------------------------------------------+
47.         bool CheckCatch(void)
48.         {
49.             for (int count = PositionsTotal() - 1; count >= 0; count--, m_Infos.ticket = 0)
50.                 if ((m_Infos.ticket = PositionGetTicket(count)) > 0)
51.                     if (PositionGetString(POSITION_SYMBOL) == GetInfoTerminal().szSymbol)
52.                     {
53.                         m_Infos.szPrefixName = IntegerToString(m_Infos.ticket);
54.                         if (ObjectFind(GetInfoTerminal().ID, m_Infos.szPrefixName + def_SufixLinePrice) < 0)
55.                             break;
56.                     }
57.             if (m_Infos.ticket == 0) return false;
58.             IndicatorSetString(INDICATOR_SHORTNAME, IntegerToString(m_Infos.ticket));
59.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice, "Position opening price.");
60.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), m_Infos.corTake, "Take Profit point.");
61.             CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), m_Infos.corStop, "Stop Loss point.");
62.             
63.             return true;
64.         }
65. //+------------------------------------------------------------------+
66. };
67. //+------------------------------------------------------------------+
68. #undef def_SufixLinePrice
69. #undef def_SufixLineTake
70. #undef def_SufixLineStop
71. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

コードが大きくなり、ますます複雑になっていることに注目してください。それでも、皆さんが実際に私たちが何をおこなっているのかを追えるように、一歩ずつ進めています。これは、このコードを深く理解することが、今後非常に重要になるからです。ですから、急がないでください。これからおこなわれる変更を、落ち着いて注意深く追ってください。理解ができるだけ完全なものになるように、変更点を少しずつ示しながら説明していきます。

前回このコードを見たときには、includeディレクティブはありませんでした。現在は、それが追加されています。そして、これこそが、メインコードに記述しなければならないコードを少なくすることを可能にしています。もちろん、コードを書くうえでのほかの面でも役立ちます。10行目では、継承によってC_TerminalクラスをC_IndicatorPositionクラスに組み込んでいます。この継承によって、少なくとも現時点では、C_IndicatorPositionクラスの内部からC_Terminalクラスのpublicメソッドを直接使用できるようになります。そのため、C_IndicatorPositionクラスには、MQL5に実際に存在する以上の関数があるように見えます。しかし、これはあくまでも錯覚です。実際に使用している関数は、C_Terminalクラスの中に存在しています。

ただし、C_IndicatorPositionクラスの外部から、同じようにC_Terminalクラスの関数を使用できるわけではないことを覚えておいてください。これは、継承がprivateになっているためです。これによって、C_IndicatorPositionクラスを介してC_Terminalクラスのメソッドへ外部からアクセスすることが防止されます。

しかし、C_Terminalクラスを継承しているため、C_IndicatorPositionクラスを使用する前に、まずC_Terminalを初期化する必要があります。これは33行目でおこなっています。ここで、コンストラクタから以前存在していたコードを削除し、クラス内部の変数を初期化するコードだけを残したことに注目してください。よいでしょう。それでは、47行目へ進みましょう。ここには、初期化時にインジケータをチャート上に残すべきかどうかを確認する関数があります。

ここから、49行目のループでヘッジ口座のポジションを考慮し始めます。ここまでは、ネッティング口座だけを対象としていました。ここでは、未決済ポジションの一覧を順番に調べ、まだインジケータに割り当てられていないポジションを探していることに注目してください。では、どのようにしてこれをおこなうのでしょうか。ループを1回実行するたびに、50行目のチェックをおこないます。このチェックが成功した場合、51行目で新たなチェックをおこないます。ここで注意してください。51行目の2つ目のチェックが成功した場合、53行目で、これから作成するオブジェクトの名前に使用する接頭辞を生成します。ここまでは、すべて以前と同じです。もちろん、1つだけ大きな違いがあります。これで、同じ銘柄について複数の未決済ポジションを考慮して処理できるようになりました。これは、まさにヘッジ口座で発生する状況です。

素晴らしいです。そして、ここから最も興味深い部分が始まります。では、インジケータは、そのポジションがすでに分析済みかどうかをどのように判断するのでしょうか。ここに注意してください。チャート上に存在する各ポジションインジケータは、1つの特定のポジションを追跡します。より正確に言えば、1つの特定のポジションを参照します。複数のポジションが未決済になっている場合があります。しかし、各インジケータが参照するのは1つのポジションだけです。同じインジケータが複数のポジションを追跡することはありません。したがって、そのポジションがすでに別のポジションインジケータによって分析されているのかどうかをインジケータ自身が判断できるようにするため、チャート上のオブジェクトを順番に調べます。これは54行目でおこなっています。

MetaTrader 5から、そのオブジェクトがすでにチャート上に存在すると報告された場合、同じ銘柄について別の未決済ポジションを探します。49行目のループが再び実行されます。この処理は、2つの条件のいずれかが成立するまで繰り返されます。1つ目は、未決済ポジションがもう存在しなくなることです。2つ目は、55行目が実行されることです。

いずれの場合でも、最終的には57行目が実行されます。ticket変数に値が入っていない場合はfalseを返し、インジケータをチャートから削除する必要があることをメインコードに伝えます。ticket変数に何らかの値が入っている場合は、後からそのインジケータを参照できるように、新しいショートネームを割り当てます。そして、先ほど説明したようにラインを作成します。最後に、63行目でtrueを返します。これによって、追加のチェックをおこなうことなく、ヘッジ口座でもネッティング口座でも同じようにインジケータが動作することを保証できます。


最終的な考察

本記事では、未決済ポジションのインジケータをヘッジ口座とネッティング口座の両方で本当に機能するものにするために、必要な変更を導入しました。実際、これは簡単に分かることですが、このインジケータは現在のところ、指定されたポジションに対応する価格にラインを表示する以外には何もしません。さらに、このインジケータには、ポジションがすでに存在しなくなったことを判断する機能もまだありません。トレーダーである皆さんがこの動作を実際に確認するには、チャートの時間足を変更する必要があります。

そうすると、インジケータはポジションが決済されたことを検出します。これが検出されると、インジケータはチャートから削除されます。そのため、追加の操作をおこなわなくても自然に削除されます。次の記事では、このインジケータにいくつかの新しい機能を追加し、さらに改善していきます。

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

添付されたファイル |
Anexo.zip (779.24 KB)
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
MQL5での量子ニューラルネットワーク(第1回):インクルードファイルの作成 MQL5での量子ニューラルネットワーク(第1回):インクルードファイルの作成
量子原理と人工知能に基づく取引システムを構築するための新しいアプローチを紹介しています。著者は、量子力学と最新のAIアーキテクチャを組み合わせることで、従来の機械学習の枠を超える独自のニューラルネットワークの開発について説明しています。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
取引におけるニューラルネットワーク:データ固有の依存性なしに時系列を一般化する(コアモデルモジュール) 取引におけるニューラルネットワーク:データ固有の依存性なしに時系列を一般化する(コアモデルモジュール)
Mamba4Castフレームワークについての理解をさらに深めていきます。今回は、提案されたアプローチの実践的な実装について詳しく見ていきます。Mamba4Castは、新しい時系列データごとに長時間の事前適応を目的として設計されたものではなく、すぐに導入して使用することを目的として設計されています。ゼロショット予測という概念により、モデルは追加学習やハイパーパラメータの調整をおこなうことなく、実世界のデータに対して高品質な予測を生成できます。