English Русский Español Português
preview
初級から中級まで:関数ポインタ

初級から中級まで:関数ポインタ

MetaTrader 5 |
14 0
CODE X
CODE X

はじめに

前回の「初級から中級まで:オブジェクト(II)」では、チャート上に表示されたオブジェクトを操作するために利用できる最初の種類のイベントについて扱いました。

しかし前回扱ったイベントは、MetaTrader 5がユーザーのチャート操作に対して常にデフォルトで発生させるタイプのイベントです。ここではキー入力イベントを指します。このイベントはOnChartEventハンドラで容易に捕捉できるため、「キーボード入力を扱うにはこの方法しかない」と考えるかもしれません。しかし実際にはそうではありません。MetaTrader 5のスクリプトはOnChartEventハンドラを直接使用・サポートしているわけではありませんが、それでもキーボードによって特定のオブジェクトプロパティを制御する仕組みをスクリプトと組み合わせて実現することは可能です。ただし、これはやや特殊なアプローチになります。

したがって、マウスイベントの扱いを見る前に、まずスクリプトにおけるキーボードイベントの処理方法について確認していきます。MetaTrader 5、つまりMQL5は本来このような用途を想定して設計されているわけではなく、価格チャートを扱うための環境です。そのため、実現できることとできないことを正しく理解することが重要です。ここには明確な制約が存在します。


スクリプトにイベントは存在するのか

まず明確にしておく必要があります。スクリプトにイベントを持たせることはできません。 しかしこれは、キーボード入力を扱うスクリプトを作成できないという意味ではありません。ただし、この点が最も難しい部分になりますが、MQL5は特定の実装方法を想定して設計されていないため、純粋なMQL5だけで開発する場合にはさまざまな制約や困難が生じます。

では、スクリプト内でキーボードイベントをどのように捕捉するのでしょうか。結論から言えば、直接的に捕捉することはできません。実際にできるのは、特定のキーを検出してフィルタリングし、キーボードイベントハンドラに似た仕組みを作ることです。概念的にはOnChartEventでの処理に近いものの、スクリプト内でそれを直接使っているわけではありません。

より理解しやすくするために、前回の記事で扱ったコードを使用します。これにより、どのような問題が発生する可能性があるのかを具体的に確認できます。該当するコードを以下に示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_KEY_UP          38
05. #define def_KEY_DOWN        40
06. //+----------------+
07. #define macro_NameObject  "Demo" + (string)ObjectsTotal(0)
08. //+------------------------------------------------------------------+
09. string  gl_Objs[2];
10. //+------------------------------------------------------------------+
11. int OnInit()
12. {
13.     ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0);
14.     ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue);
15.     ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0);
16.     ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple);
17. 
18.     return INIT_SUCCEEDED;
19. };
20. //+------------------------------------------------------------------+
21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
22. {
23.     return rates_total;
24. };
25. //+------------------------------------------------------------------+
26. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
27. {
28.     static int p = 0;
29.     MqlRates rate[1];
30. 
31.     switch(id)
32.     {
33.         case CHARTEVENT_KEYDOWN:
34.             switch ((int)lparam)
35.             {
36.                 case def_KEY_DOWN:
37.                     p = (p < Bars(_Symbol, _Period) ? p + 1 : p);
38.                     break;
39.                 case def_KEY_UP:
40.                     p = (p > 0 ? p - 1 : p);
41.                     break;
42.                 default:
43.                     return;
44.             }
45.             Comment(StringFormat("Current bar analyzed: %d", p));
46.             CopyRates(_Symbol, _Period, p, rate.Size(), rate);
47.             ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close);
48.             ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close);
49.             break;
50.     }
51.     ChartRedraw();
52. };
53. //+------------------------------------------------------------------+
54. void OnDeinit(const int reason)
55. {
56.     Comment("");
57.     for (uint c = 0; c < gl_Objs.Size(); c++)
58.         ObjectDelete(0, gl_Objs[c]);
59.     ChartRedraw();
60. };
61. //+------------------------------------------------------------------+

コード01

コード01を実行すると、以下の結果が得られます。

アニメーション01

ここで疑問となるのは、「コード01と同じ結果をアニメーション01のように実現するには、スクリプトを使ってどのように実装すればよいのか」という点です。まさにここからが本題になります。

この目的を達成するためには、MQL5ライブラリのさまざまな関数呼び出しを利用する必要があります。ただし今回の解説ではMQL5のみに集中するため、他の手法は使用しません。CやC++ベースの手法を用いればこの問題はより簡単に解決できる可能性がありますが、本記事は初級〜中級レベルを対象としているため、そのような方法は扱いません。

将来的に、他言語でコンパイルされたプログラムを利用する必要があるような、より高度なコードの開発方法を紹介する場合には、同じ問題をより簡潔に解く方法を示すこともあるかもしれません。ただしその場合は難易度が大きく上がります。なぜなら、言語間連携を実現し問題を解決するためには、MQL5だけでなく別のプログラミング言語についても十分な理解が必要になるからです。

それでは本題に戻ります。まず最初に、ここではアプリケーションとして構造立てたコードは使いません。目的はアプリケーションを作ることではなく、問題の解き方を示すことだからです。そのため、まずはアクションプランを定義する必要があります。その第一歩として、初期スクリプトがどのような形になるかを決めます。

ご覧の通り、コードおよびアニメーションには2つのオブジェクトがあります。1つは水平線、もう1つは垂直線です。これらはバーの生成時刻および終値に紐づけられます。そしてスクリプトが正しく実行を終了した際には、このスクリプトによって生成された2つのオブジェクトを削除する必要があります。

以上で初期のアクションプランが完成しました。それでは、この方針に従ってソースコードの実装に進みます。以下に示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_KEY_UP          38
05. #define def_KEY_DOWN        40
06. //+----------------+
07. #define macro_NameObject  "Demo" + (string)ObjectsTotal(0)
08. //+------------------------------------------------------------------+
09. string gl_Objs[2];
10. //+------------------------------------------------------------------+
11. void OnStart(void)
12. {
13.     int p = 0;
14.     MqlRates rate[1];
15. 
16.     ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0);
17.     ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue);
18.     ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0);
19.     ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple);
20. 
21.     Comment(StringFormat("Current bar analyzed: %d", p));
22.     CopyRates(_Symbol, _Period, p, rate.Size(), rate);
23.     ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close);
24.     ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close);
25. 
26.     Comment("");
27.     for (uint c = 0; c < gl_Objs.Size(); c++)
28.         ObjectDelete(0, gl_Objs[c]);
29.     ChartRedraw();
30. }
31. //+------------------------------------------------------------------+

コード02

コード02は、今回のアプローチの基礎となるものです。しかしこれをそのまま実行しても、結果を目視で確認することはできません。理由は、オブジェクトが生成され、配置され、そして削除されるまでの処理が非常に短時間で完了してしまい、その存在を確認する前に処理が終わってしまうためです。

ここでコード02の重要なポイントに注目してください。コード02の各行を注意深く見ると、コード01とまったく同じ記述が含まれていることが分かります。しかし結果としては何も表示されません。その理由は、コード01におけるOnChartEventイベントハンドラ内の21〜24行目に相当する処理部分が、ここではあまりにも高速に実行されてしまい、ユーザーが操作する余地がないためです。この部分こそが、キーボードイベント処理を実装するために介入すべき重要な箇所です。

以上の通り、すでにソースコードの全体像は把握できていますので、ここからはタスクをより小さな単位に分解していきます。モノリシックなコードを作成する作業は非常に負担が大きいためです。したがって、コード02は修正され、コード03へと発展していきます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_KEY_UP          38
05. #define def_KEY_DOWN        40
06. //+----------------+
07. #define macro_NameObject  "Demo" + (string)ObjectsTotal(0)
08. //+------------------------------------------------------------------+
09. string gl_Objs[2];
10. //+------------------------------------------------------------------+
11. void OnStart(void)
12. {
13.     Init();
14.     KeyEvent(def_KEY_DOWN);
15.     Deinit();
16. }
17. //+------------------------------------------------------------------+
18. void Init(void)
19. {
20.     ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0);
21.     ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue);
22.     ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0);
23.     ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple);
24. }
25. //+------------------------------------------------------------------+
26. void KeyEvent(int lparam)
27. {
28.     static int p = 0;
29.     MqlRates rate[1];
30. 
31.     switch (lparam)
32.     {
33.         case def_KEY_DOWN:
34.             p = (p < Bars(_Symbol, _Period) ? p + 1 : p);
35.             break;
36.         case def_KEY_UP:
37.             p = (p > 0 ? p - 1 : p);
38.             break;
39.         default:
40.             return;
41.     }
42.     Comment(StringFormat("Current bar analyzed: %d", p));
43.     CopyRates(_Symbol, _Period, p, rate.Size(), rate);
44.     ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close);
45.     ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close);
46. }
47. //+------------------------------------------------------------------+
48. void Deinit(void)
49. {
50.     Comment("");
51.     for (uint c = 0; c < gl_Objs.Size(); c++)
52.         ObjectDelete(0, gl_Objs[c]);
53.     ChartRedraw();
54. }
55. //+------------------------------------------------------------------+

コード03

これでかなりすっきりしたコードになりました。コード03を見ることで、私たちが本当にやるべきことが明確になります。14行目にはループ構造が必要であり、それによってオブジェクトと対話し、アニメーション01のように制御できるようにする必要があります。では、その実現方法はどのようにすればよいのでしょうか。ここからが最も興味深い部分です。その前に1点注意しておきます。ループは危険です。これはすでに説明されており、安全にループを終了する方法についても述べました。

また、すべてのスクリプトは、MetaTrader5がチャートを再構築する必要があるタイミング、例えばユーザーがチャートの時間足を変更した場合などに即座にチャートから削除されます。しかし重要なのは、単にスクリプトが削除されるだけでは不十分だという点です。スクリプトによって作成されたオブジェクトも同時に削除する必要があります。そうしないと、MetaTrader 5がチャートを再描画し始めた時点で、それらのオブジェクトは再びチャート上に現れます。

これは少し衝撃的かもしれませんが、スクリプト内でオブジェクトを扱う際には特に注意が必要であるということを理解してほしいのです。スクリプトの終了後に生成したオブジェクトをチャート上へ残したくないのであれば、削除する必要があります。

それではループに進みます。ここでの問題はループそのものを作ることではなく、CPUリソースを過剰に消費しないように制御することです。キーボードイベントは、どれほど頻繁に入力されても常時発生するものではありません。そのため、ループの内部に適切な要素を組み込むことで、CPU負荷を抑えたスクリプトを実現できます。これらの検討を踏まえると、次のようになります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_KEY_UP          38
05. #define def_KEY_DOWN        40
06. //+----------------+
07. #define macro_NameObject  "Demo" + (string)ObjectsTotal(0)
08. //+------------------------------------------------------------------+
09. string gl_Objs[2];
10. //+------------------------------------------------------------------+
11. void OnStart(void)
12. {
13.     Init();
14.     while (!IsStopped())
15.     {
16.         if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))
17.             KeyEvent(def_KEY_UP);
18.         if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))
19.             KeyEvent(def_KEY_DOWN);
20.         Sleep(100);
21.     }
22.     Deinit();
23. }
24. //+------------------------------------------------------------------+
25. void Init(void)
26. {
27.     ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0);
28.     ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue);
29.     ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0);
30.     ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple);
31. }
32. //+------------------------------------------------------------------+
33. void KeyEvent(int lparam)
34. {
35.     static int p = 0;
36.     MqlRates rate[1];
37. 
38.     switch (lparam)
39.     {
40.         case def_KEY_DOWN:
41.             p = (p < Bars(_Symbol, _Period) ? p + 1 : p);
42.             break;
43.         case def_KEY_UP:
44.             p = (p > 0 ? p - 1 : p);
45.             break;
46.         default:
47.             return;
48.     }
49.     Comment(StringFormat("Current bar analyzed: %d", p));
50.     CopyRates(_Symbol, _Period, p, rate.Size(), rate);
51.     ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close);
52.     ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close);
53. }
54. //+------------------------------------------------------------------+
55. void Deinit(void)
56. {
57.     Comment("");
58.     for (uint c = 0; c < gl_Objs.Size(); c++)
59.         ObjectDelete(0, gl_Objs[c]);
60.     ChartRedraw();
61. }
62. //+------------------------------------------------------------------+

コード04

では、14行目のループに注目してください。このようにすることで、アニメーション02で確認できるような動作を得ることができます。

アニメーション02

興味深い動作ではないでしょうか。しかし、これで終わりではありません。まだ別の動作例があります。以下のアニメーションを確認してください。

アニメーション03

ここで重要な点に注意してください。チャートの時間足を変更すると、MetaTrader5はチャートを一度削除し、直ちに再生成します。そしてMetaTrader5ではスクリプトはデフォルトで再読み込みされないため、この時点でスクリプトは終了し、スクリプトによって生成されたオブジェクトもチャートから削除されます。これはまさに、先ほどコード04で意図していた動作です。

ここでなぜ16行目と18行目の関数を使うのかを疑問に思うかもしれません。その理由は、MQL5標準ライブラリ以外のものを一切使用しないためです。MQL5には、キーボード入力を直接取得するための汎用的な関数や手続きが用意されていないため、より一般的な方法でキー入力を読み取ることはできません。そのため、コード01で示したOnChartEventを用いてCHARTEVENT_KEYDOWN相当の仕組みを構築することになります。

ただし、これはキーボード入力の取得自体が不可能という意味ではありません。そのような処理をおこなうにはMQL5の標準機能外の仕組みが必要になりますが、本記事のような初級〜中級レベルの解説では扱いません。

さて、コード04では特定のキー種別を分かりやすくするために定数、あるいは列挙型のような形を使っている点に気付くと思います。しかし、必ずしもコード04のように厳密に構成する必要はありません。改善の余地があります。そして今回は、単なる簡単な修正にとどめるのではなく、MQL5で利用可能な別の機能を紹介する機会とします。この機能は非常に限定的な用途を持つものであり、紹介する機会は多くありませんが、今回がその一例です。それでは話題を整理するために、新しいセクションへ進みます。


関数ポインタ

これは、MQL5において非常に限定的な目的を持つメカニズムを説明できる数少ない機会のひとつです。前のトピックでは、いわばこれまでインジケーターとして提示してきたものの代替的なバージョンを実装しました。その流れの中で、ここでは「関数ポインタ」について説明する機会が生まれます。これは非常に興味深い言語機能ですが、特に初心者にとっては、いくつかの概念を明確に理解していない場合に大きな混乱を引き起こす要素でもあります。

まず最初に、ここしばらくの間、あるトピックを理解するための前提知識について明示的に触れてきませんでした。これは、各トピックが常に直近の記事の内容と結びついていたためです。しかし今回のケースは例外です。ここで扱う概念は、比較的古い記事に含まれているものです。具体的には「初級から中級まで:変数(III)」を指します。この記事では変数および定数に関する議論が完結しています。そこで扱われた概念の理解は、これからおこなう内容を理解する上で非常に重要になります。

それではまず、非常に単純で、一見すると滑稽にも思える例から始めます。しかしこれは、これから学ぶ構文の扱い方を理解する上で重要な助けになります。以下にソースコードを示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. string Msg_01(const int value)
05. {
06.     return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value;
07. }
08. //+------------------------------------------------------------------+
09. string Msg_02(const int value)
10. {
11.     return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value;
12. }
13. //+------------------------------------------------------------------+
14. void OnStart(void)
15. {
16.     Print(Msg_01(171));
17.     Print(Msg_02(-375));
18. }
19. //+------------------------------------------------------------------+

コード05

コード05は非常に単純なため、ここでは詳細な説明はおこないませんが、その実行結果は以下で確認できます。

図01

コード05自体は単純ですが、次のより複雑な内容へ進む前に理解しておくべき重要な要素を含んでいます。ここでの主な疑問は、「4行目で定義された関数と9行目で定義された関数をどのような仕組みで接続できるのか」という点です。つまり、関数名を直接指定することなく、どの関数を使用するかを選択できる仕組みです。

おそらくここで「オーバーロード」を思い浮かべるかもしれません。これは「初級から中級まで:オーバーロード」で説明した内容です。しかし、オーバーロードはここで意図している用途には適用できません。少なくとも、私たちが求めている形では機能しません。実際に最も近い考え方は「配列」です。「初級から中級まで:配列(IV)」では、整数や浮動小数点数といった基本的な値を配列に格納する方法を説明しました。しかし配列にはそれ以外のデータ型も格納できます。この場合、それは関数や手続きです。「待ってください。どうやってですか。関数を配列に入れる?そんなの意味が分からない」

まさにそこで、変数という概念を正しく理解する必要があると述べた理由が出てきます。ポインタと呼ばれる特別な型の変数が存在します。MQL5では安全性や設計上の理由から、CやC++のような完全なポインタ操作は提供されていません。そのため、プログラマが自由にポインタを扱う機会はほとんどありません。しかし、MQL5の中でも限定的ながらポインタが生成・使用されるケースは存在します。そして実際には、MQL5におけるポインタはCやC++のポインタとは異なる性質を持っています。そのため、この言語ではポインタの話を耳にすることがほとんどありません。

ここで理解しておいてほしいのは次の一点です。関数や手続きはメモリ上の特定のアドレスに存在しており、そのアドレスはポインタと呼ばれる特別な変数に格納されます。ポインタはプログラミングにおいて非常に強力な仕組みですが、同時に最も理解が難しく、扱いが複雑な概念でもあります。なぜならポインタを操作するということは、データの種類に依存せずメモリの中身そのものを直接扱うことを意味するからです。

ただし、ここでは深く踏み込みません。必要がないためです。重要なのは次の理解です。

ポインタとは変数であり、その変数はメモリ領域を指し示します。そしてその領域に実行可能なコードが存在する場合、その場所へプログラムの実行を転送することができます。

これだけ理解できていれば、これからおこなう内容には十分です。では、ポインタとは何かが分かったところで、次の疑問は「どのようにしてポインタを作成、あるいは定義するのか」という点です。これはMQL5言語の構文に依存します。そのため、ここでテンプレートの理解が必要になります。「初級から中級まで:テンプレートとtypename (V)」ではテンプレートの仕組みと、それを用いた関数や手続きのオーバーロードについて説明しました。しかし初心者が混乱するのはまさにこの点です。データ型そのものをテンプレートとして定義することも可能であり、ポインタはまさにその一例です。ただしここでのポインタは関数や手続きを表すデータ型テンプレートという特殊な形になります。「なるほど、これはますます複雑で意味が分からなくなってきた」

心配する必要はありません。ここで急ぐ必要はありません。最初は難しく感じるのが当然です。そのため、この言語機能についてはここで初めて本格的に扱っています。ポインタと呼ばれるこの言語機能の使い方を真に理解するには、いくつかの異なる概念を習得する必要があります。

コード05では、4行目と9行目の関数定義が意図的にこのような形になっています。これはポインタを定義する際に使用するテンプレート構造を理解しやすくするためです。注意深く見ると分かるように、本質的には関数名が異なるだけであり、処理内容そのものは重要ではありません。重要なのは宣言の構造です。これらの関数は戻り値の型、引数の数、型が非常に近いため、ポインタを定義する条件として適しています。それでは、このポインタを定義するために、次の行を追加します。ここからコード05を段階的に変更しながら、何が起きているのかを正確に理解できるように進めていきます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. typedef string (*FnPtr)(const int);
05. //+------------------------------------------------------------------+
06. string Msg_01(const int value)
07. {
08.     return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value;
09. }
10. //+------------------------------------------------------------------+
11. string Msg_02(const int value)
12. {
13.     return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value;
14. }
15. //+------------------------------------------------------------------+
16. void OnStart(void)
17. {
18.     Print(Msg_01(171));
19.     Print(Msg_02(-375));
20. }
21. //+------------------------------------------------------------------+

コード06

他にやっていることを辞めて気を取られないようにしてください。そして、これから説明する内容を注意深く聞いてください。もしこの導入部分を理解できなければ、以降の内容は完全に分からなくなります。コード06では、コード05には存在しなかった4行目を追加しています。この4行目こそが、今回使用するポインタを定義している箇所です。

この宣言をよく見ると、6行目および11行目に記述されているものと非常に似ています。ただし、変数名は省略されており、その代わりに関数名の位置が(FnPtr)に置き換えられています。この(FnPtr)という部分は任意の名前を付けることができます。しかし、この後で重要になるため、この部分の扱いには注意が必要です。

これで第一段階の説明は完了です。次に行うのは、コード06の4行目で定義した型を使って配列を宣言することです。ここで重要なのは、その場所でポインタが定義されているという点です。第二段階の内容は次のコードで示されます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. typedef string (*FnPtr)(const int);
05. //+------------------------------------------------------------------+
06. string Msg_01(const int value)
07. {
08.     return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value;
09. }
10. //+------------------------------------------------------------------+
11. string Msg_02(const int value)
12. {
13.     return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value;
14. }
15. //+------------------------------------------------------------------+
16. void OnStart(void)
17. {
18.     FnPtr FnMsg[2];
19. 
20.     Print(Msg_01(171));
21.     Print(Msg_02(-375));
22. }
23. //+------------------------------------------------------------------+

コード07

このコード07に示されているように、18行目では、4行目で定義された型のポインタを使用するための配列が定義されています。作業を簡略化するために、要素数2の静的配列として定義しています。もしここで何が起きているのか疑問がある場合は、以前の記事を参照してください。すでに説明済みの内容については、ここでは詳しく掘り下げません。ここまでの内容は問題なく理解できているはずです。良い進み具合です。それでは次のステップに進みます。次の内容は以下のコードで確認できます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. typedef string (*FnPtr)(const int);
05. //+------------------------------------------------------------------+
06. string Msg_01(const int value)
07. {
08.     return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value;
09. }
10. //+------------------------------------------------------------------+
11. string Msg_02(const int value)
12. {
13.     return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value;
14. }
15. //+------------------------------------------------------------------+
16. void OnStart(void)
17. {
18.     FnPtr FnMsg[2];
19. 
20.     FnMsg[0] = Msg_01;
21.     FnMsg[1] = Msg_02;
22. 
23.     Print(Msg_01(171));
24.     Print(Msg_02(-375));
25. }
26. //+------------------------------------------------------------------+

コード08

「これは何でしょう。こんなコードはコンパイルできないのでは。」と思うかもしれません。

もちろんコンパイルできます。では、なぜできるのでしょうか。ここでおこなわれていることを理解できていないのではないでしょうか。ここでは、配列の要素を定義しているだけです。これが理解できないようであれば、このトピックの最初から読み直し、説明された内容を再確認してください。4行目ではデータ型を定義しています。18行目では、その4行目で定義したデータ型と同じ型を持つ変数を定義しています。そして20行目および21行目では、18行目で宣言した変数の要素に値を代入しています。それだけのことです。すべては非常に単純で明確です。最も難しい部分は次のステップで示されます。それでは、以下のコードを見てください。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. typedef string (*FnPtr)(const int);
05. //+------------------------------------------------------------------+
06. string Msg_01(const int value)
07. {
08.     return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value;
09. }
10. //+------------------------------------------------------------------+
11. string Msg_02(const int value)
12. {
13.     return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value;
14. }
15. //+------------------------------------------------------------------+
16. void OnStart(void)
17. {
18.     FnPtr FnMsg[2];
19. 
20.     FnMsg[0] = Msg_01;
21.     FnMsg[1] = Msg_02;
22. 
23.     Print(Msg_01(171));
24.     Print(Msg_02(-375));
25. 
26.     for (uchar c = 0, i = 15; c < FnMsg.Size(); c++, i += 30)
27.         Print(FnMsg[c](i));
28. }
29. //+------------------------------------------------------------------+

コード09

コード09を実行すると、ターミナルには次のようなメッセージが表示されます。

図02

「これは一体何だ。何が起きているんだ。」ここまで来ると、本当に理解が難しい領域に達しています。ある意味では、このコードは少しやりすぎかもしれません。なぜなら、もしこれまでの手順を飛ばしてコード09を最初から理解しようとすれば、これまでの人生で最も混乱する可能性が高いからです。というのも、図02に表示されている最初の2行は理解できるかもしれませんが、私が指摘している残りの2行はどこから来たかわかりますか。ここがまさに核心です。それでもMQL5におけるポインタは、本来は非常に単純に説明でき、理解も難しいものではありません。しかし、仕組みを完全に理解しないままポインタを含むコードを見ると、非常に混乱しやすいのも事実です。

ここで注意してください。図02でハイライトされている2行は、コード09の27行目で定義されています。しかし「どうやって」という疑問が生じます。ここで混乱が発生します。18行目の宣言ではポインタ型が使用されているため、27行目を見たときには特定のデータ型が返されると予想するかもしれません。しかし実際には、コンパイラはこれを関数呼び出しとして解釈しています。これが、この宣言がこのような形になっている理由です。そして、その結果が図02に示されている通りになります。関数名自体は重要ではありません。27行目の記述によって、どの関数を呼び出すかが決定されます。なぜなら20行目および21行目で、配列の要素に関数そのものを代入しているからです。

おそらくこう思っているのでしょう。「人生が複雑だというのに、なぜこんな複雑なことをする必要があるのか。技術的に可能だとしても、ここまで必要なのか。ほんとに、プログラマっていうのは変わっている。」

確かに、プログラマの中にはどこかの時点で少し「おかしくなる」人もいるかもしれません(笑)。しかし全体としては、私たちはただ少しだけ風変わりなだけの、基本的には「いい奴」です。とはいえこの段階に到達すると、以前は不可能だったことがすでに実現可能になっていることに気付くはずです。なぜなら、あなたはこのトピックで示された内容をすでに見ているからです。そしてここから、前回のトピックで示したコード04を、この新しい概念を使って更新していきます。その結果として、次のコードが得られます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_KEY_UP          38
05. #define def_KEY_DOWN        40
06. //+----------------+
07. #define macro_NameObject  "Demo" + (string)ObjectsTotal(0)
08. //+------------------------------------------------------------------+
09. string gl_Objs[2];
10. //+------------------------------------------------------------------+
11. typedef void (*ProcPtr)(void);
12. typedef void (*KeyEvent)(int &);
13. //+------------------------------------------------------------------+
14. void OnStart(void)
15. {
16.     ProcPtr proc[3];
17. 
18.     proc[0] = Init;
19.     proc[1] = Loop;
20.     proc[2] = Deinit;
21. 
22.     for (uint c = 0; c < proc.Size(); c++)
23.         proc[c]();
24. }
25. //+------------------------------------------------------------------+
26. void Init(void)
27. {
28.     ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0);
29.     ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue);
30.     ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0);
31.     ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple);
32. }
33. //+------------------------------------------------------------------+
34. void Loop(void)
35. {
36.     KeyEvent    key[3];
37.     int         pos = 0;
38. 
39.     key[0] = Bar_NONE;
40.     key[1] = Bar_Next;
41.     key[2] = Bar_Prev;
42. 
43.     while (!IsStopped())
44.     {
45.         key[TerminalInfoInteger(TERMINAL_KEYSTATE_UP) ? 1 : (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN) ? 2 : 0)](pos);
46.         Sleep(100);
47.     }
48. }
49. //+------------------------------------------------------------------+
50. void Event(const int arg)
51. {
52.     MqlRates rate[1];
53. 
54.     Comment(StringFormat("Current bar analyzed: %d", arg));
55.     CopyRates(_Symbol, _Period, arg, rate.Size(), rate);
56.     for (uint c = 0; c < gl_Objs.Size(); c++)
57.         ObjectMove(0, gl_Objs[c], 0, rate[0].time, rate[0].close);
58. }
59. //+------------------------------------------------------------------+
60. void Deinit(void)
61. {
62.     Comment("");
63.     for (uint c = 0; c < gl_Objs.Size(); c++)
64.         ObjectDelete(0, gl_Objs[c]);
65.     ChartRedraw();
66. }
67. //+------------------------------------------------------------------+
68. void Bar_Prev(int &arg)
69. {
70.     arg = (arg < Bars(_Symbol, _Period) ? arg + 1 : arg);
71.     Event(arg);
72. }
73. //+------------------------------------------------------------------+
74. void Bar_Next(int &arg)
75. {
76.     arg = (arg > 0 ? arg - 1 : arg);
77.     Event(arg);
78. }
79. //+------------------------------------------------------------------+
80. void Bar_NONE(int &arg)
81. {}
82. //+------------------------------------------------------------------+

コード10

コード10を実行した結果は、アニメーション01で示したものとまったく同じになります。そのため、ここでは繰り返し掲載しません。ただし、コード10では少しだけ自由な実装を取り入れています。これは、「同じ結果に到達する方法は必ずしも1つではない」ということを理解していただくためです。同じ結果を得る方法は数多く存在します。より単純な方法もありますが、その場合はコードの拡張性がやや低くなります。一方で、多少複雑な方法であっても、後から機能を追加したり変更したりする際には非常に柔軟に対応できます。

いずれにしても、コード10には特別に理解が難しい要素は含まれていないと思います。特に、ここまでの内容を学んだ直後であれば、まだ記憶も新しいはずです。ただし、コード10には、一見するとあまり合理的に見えない部分がいくつかあります。そこで、それらについて簡単に説明しておきます。

まず1つ目は、OnStart手続き内の処理です。ここでは16行目で手続きの配列を宣言しています。その直後の18行目、19行目、20行目で、それぞれの配列要素に実行する手続きを割り当てています。そして、その後のループによって、これらの手続きを特定の順序で実行しています。一見すると少し不自然に感じるかもしれません。しかし、本連載の目的は、読者の皆さんに「プログラマとして考える方法」を身につけていただくことにあります。

ここで、常に同じ順序で実行しなければならない小さな処理の連続を想像してみてください。もし実行する手続きをすべて1行ずつ順番に記述していた場合、コード量が増えるだけでなく、実行順序を変更したり修正したりする作業も非常に面倒になります。しかし、配列のインデックスを数式で求められるのであれば、手続きそのものを配列の要素として定義できます。そうすることで、ループを利用して任意の順序で手続きを実行できるようになります。これは通常の手法では実現が難しい柔軟性をもたらします。

次に、34行目にある手続き「Loop」についてです。この手続きも基本的な目的は同じですが、実現方法は少し異なります。39行目から41行目では、押されたキーに応じて実行する手続きを定義しています。そして45行目では、三項演算子を利用して適切な手続きを呼び出しています。

このようなアプローチ、あるいは問題解決に対する考え方は、ときとして非常に煩雑な処理を驚くほどシンプルなものへと変えてくれます。なぜなら、後から仕様変更が必要になった場合でも、修正箇所を最小限に抑えられるからです。その結果、新しい機能の追加や既存機能の変更を、より迅速かつ効率的におこなえるようになります。


最後に

ここで示した以上のことも実現できますが、本記事で扱った内容だけでも、多くの方にとってはまったく新しいものだったのではないかと思います。そのため、ここで取り上げた内容はしっかりと学習するべきです。本記事で示したすべてのポイントを理解する必要があります。

添付ファイルには、本記事で紹介した主要なコード例が含まれています。したがって、この機会に、ここで示した内容を実際に試してみてください。また、これまでの記事で説明した内容についても振り返り、それらの事実や概念を今回見てきた内容とどのように組み合わせて利用できるのか考えてみてください。ここまでに習得した知識をすべて活用できるようなものを、ぜひ自分で実装してみてください。次回の記事では、再びオブジェクトに関連するイベントについて取り上げます。ただし今回は、キーボードに関する部分は十分に学習できたので、主なテーマはマウスの利用になります。

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

添付されたファイル |
Anexo.zip (3.35 KB)
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
MetaTrader 5における季節性に基づくFXスプレッド取引の有効性評価 MetaTrader 5における季節性に基づくFXスプレッド取引の有効性評価
日足における季節性取引アプローチの有効性を検証します。対象は個別の金融商品およびスプレッドの両方であり、特に繰り返し現れる月次サイクルの特定と、それを現行年の取引へ応用する可能性に重点を置いています。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
イーグル戦略最適化(ES) イーグル戦略最適化(ES)
イーグル戦略最適化(ES)は、鷲の狩猟行動に着想を得た最適化アルゴリズムです。マンテーニャ法によるレヴィ飛行を用いた大域探索と、ホタルアルゴリズムによる集中的な局所探索(local exploitation)を交互に実行することで、探索と活用のバランスを実現します。本アルゴリズムは、数学的根拠に基づく探索戦略と、2つの自然現象を統合したバイオインスパイアードなアプローチを兼ね備えています。