English Русский 中文 Español Deutsch Português
preview
初級から中級まで:構造体(III)

初級から中級まで:構造体(III)

MetaTrader 5 |
18 0
CODE X
CODE X

はじめに

前回の「初級から中級まで:インジケーター(IV)」では、初心者にとっては非常に難しい内容を、できるだけシンプルかつ分かりやすい形で実装する方法を紹介しました。これにより、誰でも自分のアイデアを簡単に形にする方法が分かるようになったはずです。その目的は、特定の取引システムを色で表示するインジケーターを作成することでした。そこで私たちは、インサイドバーパターンの実装方法を紹介しましたが、この知識は、ローソク足のパターンが売買シグナルの有無を示すあらゆる手法に応用できます。

非常に興味深い内容だったと思います。しかし、ここでさらに別の問題についても触れておきたいと思います。これは前回の記事の最後でも少し触れたテーマです。しかし、この記事で掘り下げていくテーマに移る前に、一度簡単な振り返りをおこないましょう。

構造に関する記事、特に「初級から中級まで:構造体(II)」では、構造体を使ってレコードブロックを作成する方法について説明しました。これらの記事では、構造体とは複数の情報を論理的かつシンプルにまとめることができる特別なデータ型であると述べました。ただし、その時点では他の応用例を示していなかったため、構造体の詳細に深く踏み込む必要はありませんでした。しかし前回の記事によって、より発展的な内容に進むための基礎が整いました。つまり、構造体は単なるデータ整理のためだけでなく、他の目的にも活用できるということを示したいのです。

このような前置きを踏まえた上で、いよいよ本題に入り、「なぜクラスが登場したのか」を理解していきます。そのために、まず最初のテーマから説明していきます。


構造化されたコード

私が最も気になるのは、コードが整理されておらず、さまざまな処理が混在している状態です。古き良きBASICの時代には、明確な構造を持たないコードにも一定の合理性がありました。それは言語そのものの性質によるものです。また、BATCHのようなスクリプト言語(SQLのようなものを含む)では、構造がそれほど厳密に定義されていない場合もあります。これは多くの場合、処理内容が単純であり、コード自体も短いことが多いためです。

しかし、MQL5のようにより高度な目的を持つ言語になると状況は変わります。この場合、数行のコードだけで目的を達成することは難しく、ヘッダーファイルを使って複雑さを隠したとしても限界があります。しかし、プログラミングを煩雑で退屈なものにしてしまう要素も存在します。その一つが、より適切に構造化できるはずのコード断片を初心者プログラマーが繰り返してしまう傾向です。

そして、ここで私が言う「構造化されたコード」とは、コードを整理された状態に保つために関数や手続きを使うことを指しているのではありません。この場合に意味しているのは、保守性や扱いやすさを向上させるために、コードを構造体の中に組み込むということです。多くのプログラミング指導者は、関数や手続きを使えばコードはすでに構造化されていると言います。しかし、それは必ずしも真に構造化されていることを意味しません。それは単に整理されているというだけであり、それ以上ではありません。

真に構造化されたコードを実現するためには、多くの人が極端だと考えたり、少なくとも複雑すぎると感じたりする手法や概念を使う必要があります。その一つがクラスです。しかし、まだクラスについて語る時期ではありません。なぜなら、クラスはある日突然プログラマーたちが目覚めて、「これからキャリアを始める人たちを困らせるものを作ろう。その方が自分たちが一番優秀に見えるからだ」と考えて生まれたものではないからです。そうではなく、クラスは本当に構造化されたコードを作るという問題を解決するために登場したのです。

この制約を理解するためには、より複雑な問題に踏み込む必要があります。ですので、ここから先は準備をしてください。これ以降は非常に興味深く、そして楽しくなっていきます。

もしかすると、私が何を伝えようとしているのか分からず、懐疑的に感じているかもしれません。しかし心配しないでください。私たちは一歩ずつ進みます。なぜなら、これから説明していくオブジェクト指向プログラミングを理解することが極めて重要だからです。そうでなければ、混乱してしまうでしょう。

前回の記事では、以下のコードを見ました。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. input bool user01 = true;       //Show time scale
05. input bool user02 = true;       //Show price scale
06. //+----------------+
07. struct st_Mem
08. {
09.     long    View_DateScale,
10.             View_PriceScale;
11. }gl_StyleGraphic;
12. //+------------------------------------------------------------------+
13. int OnInit()
14. {
15.     gl_StyleGraphic.View_DateScale = ChartGetInteger(0, CHART_SHOW_DATE_SCALE);
16.     gl_StyleGraphic.View_PriceScale = ChartGetInteger(0, CHART_SHOW_PRICE_SCALE);
17. 
18.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, user01);
19.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, user02);
20. 
21.     return INIT_SUCCEEDED;
22. };
23. //+------------------------------------------------------------------+
24. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
25. {
26.     return rates_total;
27. };
28. //+------------------------------------------------------------------+
29. void OnDeinit(const int reason)
30. {
31.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, gl_StyleGraphic.View_DateScale);
32.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, gl_StyleGraphic.View_PriceScale);
33.     ChartRedraw();
34. };
35. //+------------------------------------------------------------------+

コード01

このコードは非常にシンプルです。しかし、もし本当に興味があるのであれば、チャートのプロパティについて調べ、コードを使ってその外観を直接作成したり管理したりしようとするかもしれません。もしそれができたのであれば、将来優れたプロフェッショナルになりたい人にとって必要な条件の一つを満たしているという意味で、私はあなたを称賛します。

しかし、このコードは一見すると単純であるにもかかわらず、多くの人はこれを「構造化されたコード」だと考えています。私の考えでは、コード01で見ているものは厳密には構造化されたコードではなく、むしろ「よく整理されたコード」に過ぎません。シンプルではありますが、私の観点からすると、構造化コードを説明するのに理想的な例とは言えません。そのため、さらに単純なものに目を向けることにします。それがスクリプトです。スクリプトはStartイベントにのみ反応し、このイベントが終了するとコードの実行も終了します。これ以上単純なものはありません

それでは、以下のコードから始めましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     struct st_Mem
07.     {
08.         long    View_DateScale,
09.                 View_PriceScale;
10.     }StyleGraphic;
11. 
12.     StyleGraphic.View_DateScale = ChartGetInteger(0, CHART_SHOW_DATE_SCALE);
13.     StyleGraphic.View_PriceScale = ChartGetInteger(0, CHART_SHOW_PRICE_SCALE);
14. 
15.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, false);
16.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, false);
17.     ChartRedraw();
18. 
19.     Sleep(2000);
20. 
21.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, StyleGraphic.View_DateScale);
22.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, StyleGraphic.View_PriceScale);
23.     ChartRedraw();
24. }
25. //+------------------------------------------------------------------+

コード02

このコードは図01のものよりもはるかに単純で、目的も同じです。つまり、価格軸と時間軸を一度非表示にし、その後再表示することです。このスクリプトをチャート上で実行すると、下のアニメーションに似たものが表示されます。

アニメーション01

ご覧のとおり、すべては非常にシンプルです。19行目は、スケールをしばらく非表示にする処理を担当しています。この部分では、処理を再開する前におよそ2秒間待機するよう指定しています。しかし、ここで注目してほしいのは2つのポイント、17行目と23行目です。前回の記事で、このコマンドをコード内で使用すべき理由について説明しました。しかし今回はスクリプトを扱っており、タイムアウトが短いため、これらの行に含まれているコマンドは非常に重要です。これがなければ、価格軸と時間軸が一度消えて再び表示される様子を見ることは、おそらくできなかったでしょう。

ここまでは問題ありません。しかし、コード02は構造体を06行目で宣言しているにもかかわらず、私の考えでは「構造化されたコード」ではありません。そこで、多くの人が構造化コードだと考えるものを書き始めることにしましょう。そのために、コード02を以下のようなバージョンに修正します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Mem
05. {
06.     long    View_DateScale,
07.             View_PriceScale;
08. };
09. //+------------------------------------------------------------------+
10. void OnStart(void)
11. {
12.     st_Mem StyleGraphic;
13. 
14.     StyleGraphic = SAVE();
15. 
16.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, false);
17.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, false);
18.     ChartRedraw();
19. 
20.     Sleep(2000);
21.     
22.     RESTORE(StyleGraphic);
23. }
24. //+------------------------------------------------------------------+
25. st_Mem SAVE(void)
26. {
27.     st_Mem local;
28. 
29.     local.View_DateScale = ChartGetInteger(0, CHART_SHOW_DATE_SCALE);
30.     local.View_PriceScale = ChartGetInteger(0, CHART_SHOW_PRICE_SCALE);
31. 
32.     return local;
33. }
34. //+------------------------------------------------------------------+
35. void RESTORE(const st_Mem &arg)
36. {
37.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, arg.View_DateScale);
38.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, arg.View_PriceScale);
39. 
40.     ChartRedraw();
41. }
42. //+------------------------------------------------------------------+

コード03

コード03でも、アニメーション01と同じ結果になりますが、わずかなニュアンスの違いがあります。コード02とは異なり、ここはより整理されています。なぜなら、25行目の関数はさまざまなタイミングで使用でき、また35行目の手続きも任意の時点で呼び出すことができるからです。このような設計および実装の主な利点は、コード03で見られるように、チャートのプロパティ値をどの値が変更されるかを気にすることなく保存したり復元したりできる点にあります。これにより、常に同じ種類のコードを含む大量の行を作成する必要がなくなります。

しかし、このアプローチには小さな問題があり、それはコード03からも確認できます。その問題とは次の通りです。私たちはデータを含む構造体を扱っています。この点までは問題ありません。しかし、構造体内のデータを直接扱うことが目的であるにもかかわらず、なぜ25行目に関数を、35行目に手続きを実装する必要があるのでしょうか。

最初のうちは、この点は初心者や経験のあるプログラマーにとっても、それほど気になるものではありません。特に単純なコードを扱っている場合にはなおさらです。しかし、より複雑なコードに取り組み始めると、これは大きな問題になります。なぜなら、データと直接関係のない要素を取り込むようになってしまうからです。

コード03は、この点を説明するうえで非常に適しており、これから私たちがおこなおうとしていることの概念を理解する助けにもなります。次の点に注目してください。私たちはコード内のさまざまな場所で使用できる構造体を持っています。この構造体の目的は、ある時点でのチャートの状態を保存し、復元することです。したがって、上記のようにコードを実装することができます。

しかし、新しい要素を追加していくと、SAVE関数とRESTORE手続きは徐々にその文脈から外れていきます。また、同じ名前でありながら、チャートプロパティの保存や復元とは異なる目的を持つ別の関数や手続きを作成する必要が出てくるかもしれません。その場合、オーバーロードを使うこともできますが、それはコードを不必要に複雑にしてしまいます。そしてまさにこの時点で、そのコードが本当に構造化されているのかどうかという疑問が生まれるのです。

構造化されたコードにおいては、SAVEおよびRESTORE関数はコード03のように単に宣言されるのではなく(つまり04行目で定義された構造体の文脈と適切に結びついていない形ではなく)、その構造体の文脈の中において宣言されるというより、構造体の中で実装されることになります。この点は断片的には示すことができないため、他のコードで議論されている解決策へ直接進むことにします。ただし今回は、完全に構造化されたモデルとしてそれを示します。これは、以下のコードで見ることができます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_PropertyGraphics
05. {
06.     long    View_DateScale,
07.             View_PriceScale;
08. //+----------------+
09.     void SAVE(void)
10.     {
11.         View_DateScale = ChartGetInteger(0, CHART_SHOW_DATE_SCALE);
12.         View_PriceScale = ChartGetInteger(0, CHART_SHOW_PRICE_SCALE);
13.     }
14. //+----------------+
15.     void RESTORE(void)
16.     {
17.         ChartSetInteger(0, CHART_SHOW_DATE_SCALE, View_DateScale);
18.         ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, View_PriceScale);
19. 
20.         ChartRedraw();
21.     }
22. //+----------------+
23. };
24. //+------------------------------------------------------------------+
25. void OnStart(void)
26. {
27.     st_PropertyGraphics StyleGraphic;
28. 
29.     StyleGraphic.SAVE();
30. 
31.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, false);
32.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, false);
33.     ChartRedraw();
34. 
35.     Sleep(2000);
36.     
37.     StyleGraphic.RESTORE();
38. }
39. //+------------------------------------------------------------------+

コード04

なんだか混乱してきました。構造体というのはレコードのようなデータをまとめて扱うための、少し特殊な変数のようなものだと思っていました。でも、コード04で示されている内容は、正直なところ少し理解しづらいように感じられます。」

さて、ここで一段階レベルを上げることになります。ここまでの内容を学習して十分に理解していない場合、この先に進むのは少し難しく感じられるかもしれません。というのも、これ以降は扱う概念が一気に増え、理解の幅も急速に広がっていくためです。とはいえ、コード04を必要以上に恐れる必要はありません。できるだけ学習しやすい形で説明していきますので、余計な混乱を避けながら、本質的な部分に集中していきましょう。

それでは、コード04で何が起きているのかを確認していきます。まず前提として、コード04はこれまで見てきたコードと同じ結果を得るものです。ただし大きな違いとして、グラフィックプロパティを保存するための関数や手続きが、それを扱う構造体の内部に含まれている点が挙げられます。これがいわゆるこうした構造化の考え方です。

まずは04行目の構造体はいったん脇に置き、OnStartイベントハンドラに注目してください。27行目では、これまでと同様に構造体型の変数を宣言しています。つまり、ここでの変数生成の方法自体はこれまでと変わりません。次に29行目と37行目に注目してみましょう。これらを見て、「29行目では何かを保存し、37行目ではそれを復元しているのだろう」と自然に理解できるかと思います。もしそう感じたのであれば、その理解は正しい方向にあります。

もう一度、同じ行を見てみましょう。ここで「保存」や「復元」をおこなっている対象は何でしょうか。それは間違いなく、27行目で宣言した変数です。ここが重要なポイントです。詳しく見てみましょう。まず27行目では変数を宣言しています。そして29行目が実行されると、その同じ変数の保存処理がおこなわれます。ここで重要なのは、この変数が「グラフィックプロパティのレコード型」であるという点です。

したがって29行目では、グラフィックプロパティそのものを保存するよう要求していることになります。これにより31行目および32行目では、プロパティの値を変更したとしても、その変更による影響を気にする必要がなくなります。「なるほど、少し分かってきました。では37行目を実行すると、保存されていた同じプロパティを復元するよう要求し、その結果として29行目から37行目の間におこなわれたすべての変更が破棄される、ということですね。これは正しい理解でしょうか。また、ここでおこなわれている処理はOnStartイベントハンドラの中で正しく理解できていると言えるのでしょうか。」はい、その理解で問題ありません。

「なるほど。ただ、ここで疑問があります。コード03とコード04を比較すると、SAVEおよびRESTORE手続きの定義方法が異なっています。同じ目的と動作を持つはずなのに、なぜこのような違いが生まれるのでしょうか。まだその点がよく理解できていません。」

この点に気づいていただけて嬉しく思います。なぜなら、OnStartコードの理解ができている今こそ、04行目で宣言されている構造体について説明するタイミングだからです。したがって今回は、コード04の他の部分はすべて無視し、構造体ブロックのみに注目してください。

まずご注意いただきたいのは、この構造体の名前は説明を簡単にするために変更しているという点です。ただし、実際には任意の名前を使用して構いません。重要なのは、この構造体がグローバルレベルで定義されているという点です。場合によってはヘッダーファイル内に配置されていることもありますが、ここではその詳細には触れません。まずはこれから説明する内容に集中してください。

また、これまでのコードと同様に、同じ変数をそのまま使用しています。これらの変数は引き続きアクセス可能であり、コード03と同様に問題なく動作します。実際にこれらを操作することも可能ですが、それについては別の機会に説明します。ここで重要なのはサブルーチン内の動作です。今回の構造体には、09行目にSAVE手続き、15行目にRESTORE手続きが定義されています。これらはコード03で見たものと非常によく似ています。

しかし、ここからが重要なポイントです。View_DateScaleおよびView_PriceScaleという変数は、コード03のように個別に宣言する必要がなくなっています。これらの変数がコンパイラやプログラマーから見た文脈の一部として扱われているためです。この場合のコンテキストとは、まさにst_PropertyGraphics構造体そのものを指します。

つまり、構造体という意味のある文脈の中で処理を行っているため、変数を個別に宣言する必要がありません。これはコード03とは明確に異なります。コード03では29行目、30行目、37行目、38行目のように、それぞれの変数についてデータ型を明示的に指定する必要がありました。なぜなら、その時点ではSAVEおよびRESTOREのサブルーチンが構造体の文脈の外にあったためです。しかしコード04では状況が異なります。これらのサブルーチンは構造体の内部に属しているため、このような明示的な宣言は不要になります。

これこそが構造化プログラミングと呼ばれる考え方です。すべての要素をその完全な文脈の中で扱うことができるため、コード全体を追い回しながら理解する必要がなくなり、何がおこなわれているのかをより明確に把握できるようになります。

ぜひ実際にコードを変更しながら試してみてください。その方が、この仕組みをより深く理解できるはずです。なお、この内容を終える前に、もう一つ別のコードを紹介したいと思います。ただし、それは別のテーマに関係するため、新しいトピックとして扱うことにします。


構造体におけるシンプルな操作

前のトピックでは、構造化されたコードを作成することによって、コードがどのように「コンテキスト」を持つようになるかについて説明しました。しかし、その一方で、どれほど優れた仕組みであっても、いくつか注意すべき点があります。そして、まさにこの「注意が必要である」という性質こそが、クラスを導入する必要性につながっていきました。この点については、また別の機会に説明することにしましょう。ここでは、そうした「注意点」のいくつかを見ていきます。

まず大前提として、構造体におけるデータは非常に単純なルールに従います。それは、データが「public(公開)」か「private(非公開)」のいずれかであるということです。通常、単純な構造体を定義する場合には、データはすべてpublicとして扱われます。しかし、構造化プログラミングをおこなう場合には、必ずしもデータを公開したいわけではありません。このような場合には、データの整合性を維持し、ライフサイクル全体を通して安全性を保ことが重要になります。

この整合性を確保するために、データへのアクセス方法(アクセス修飾子)を変更することができます。ここで注意していただきたいのは、これから説明する内容は、先ほどのような単純な構造では使用すべきではないという点です。あくまで、構造化されたコードを実装し、その動作をより細かく制御したい場合にのみ使用してください。必要な知識がないまま使ってしまうと、本来はシンプルであるはずの仕組みをかえって複雑にしてしまう可能性があります。

それでは、理解を深めるために簡単なコード例を見ていきましょう。ただしこれは、今回説明している内容に適した形のシンプルな例になります。以下をご覧ください。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_PropertyGraphics
05. {
06.     long    View_DateScale,
07.             View_PriceScale,
08.             Chart_Mode;
09. //+----------------+
10.     void SAVE(void)
11.     {
12.         View_DateScale = ChartGetInteger(0, CHART_SHOW_DATE_SCALE);
13.         View_PriceScale = ChartGetInteger(0, CHART_SHOW_PRICE_SCALE);
14.         Chart_Mode = ChartGetInteger(0, CHART_MODE);
15.     }
16. //+----------------+
17.     void RESTORE(void)
18.     {
19.         ChartSetInteger(0, CHART_SHOW_DATE_SCALE, View_DateScale);
20.         ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, View_PriceScale);
21.         ChartSetInteger(0, CHART_MODE, Chart_Mode);
22. 
23.         ChartRedraw();
24.     }
25. //+----------------+
26. };
27. //+------------------------------------------------------------------+
28. void OnStart(void)
29. {
30.     st_PropertyGraphics StyleGraphic;
31. 
32.     StyleGraphic.SAVE();
33. 
34.     StyleGraphic.Chart_Mode = CHART_LINE;
35. 
36.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, false);
37.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, false);
38.     ChartSetInteger(0, CHART_MODE, CHART_BARS);
39.     ChartRedraw();
40. 
41.     Sleep(2000);
42.     
43.     StyleGraphic.RESTORE();
44. }
45. //+------------------------------------------------------------------+

コード05

コード05はコード04と非常に似ています。しかし、今回説明したい内容を示すために、構造体へ新しいグラフィックプロパティを追加しています。このプロパティはコード05の08行目で宣言されています。また、それに伴いSAVEおよびRESTORE手続きにも新しい行が追加されていますが、これらの変更は詳細に説明するほど重要なものではありません。同様にOnStart手続きにもいくつかの小さな変更がありますが、いずれも単純であり、詳しい解説は不要です。しかし、このスクリプトをチャート上で実行すると、次のような結果が表示されます。

アニメーション02

「少し待ってください。これは予想外の結果です。なぜなら、コード04と同じ原則と考え方で処理しているはずなのに、チャートが元の状態に復元されていないからです。おそらく、新しく追加したプロパティに問題があるのではないでしょうか。」その通りです。ただし、問題の本質は単純に「新しいプロパティそのもの」ではありません。この問題は一般的に「データリーク」あるいは「カプセル化の破綻」と呼ばれるものです。つまり、本来であればおこなえないはずの操作がおこなわれてしまっている状態です。これがまさにアニメーション02で示されている結果につながっています。

ここで重要なのは、32行目でグラフィックプロパティを保存し、43行目でそれを復元しているという点です。これはコード04とまったく同じ仕組みです。しかし問題となるのは34行目です。「特に問題は見当たらないように思います。もしこの行を削除すれば、すべて元通りになるのではないでしょうか。」はい、確かに34行目を削除すれば結果は正常に戻ります。しかし、本質的な問題はそこではありません。

ここで重要なのは、StyleGraphicという変数にデータがどのように格納されているかという点です。この変数は、06行目から08行目の間に定義されている小さなレコード(構造体)データベースと直接結びついています。そのため、これらのレコードに対しておこなった変更は、そのままStyleGraphic変数の内容に影響を与えます。

つまり、43行目でデータの復元を要求した時点では、ログに保存されているすべてのデータが使用されることになります。しかし34行目でおこなわれた変更によって、本来保存されていたレコードの一部が失われています。その結果、保存時とは異なる値が復元され、それがチャートに適用されることで、誤った情報のまま状態がリセットされてしまうのです。

この問題は非常に深刻であると同時に、実際の開発現場でも頻繁に発生するものです。これを解決するためには、構造体内の値の扱い方そのものを見直す必要があります。ここで重要なのは、デフォルトではすべての値がpublicであるという点です。しかし、コード05を以下のように変更した場合、状況は大きく変わることになります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_PropertyGraphics
05. {
06.     private:
07. //+----------------+
08.         long    View_DateScale,
09.                 View_PriceScale,
10.                 Chart_Mode;
11. //+----------------+
12.     public:
13. //+----------------+
14.         void SAVE(void)
15.         {
16.             View_DateScale = ChartGetInteger(0, CHART_SHOW_DATE_SCALE);
17.             View_PriceScale = ChartGetInteger(0, CHART_SHOW_PRICE_SCALE);
18.             Chart_Mode = ChartGetInteger(0, CHART_MODE);
19.         }
20. //+----------------+
21.         void RESTORE(void)
22.         {
23.             ChartSetInteger(0, CHART_SHOW_DATE_SCALE, View_DateScale);
24.             ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, View_PriceScale);
25.             ChartSetInteger(0, CHART_MODE, Chart_Mode);
26. 
27.             ChartRedraw();
28.         }
29. //+----------------+
30. };
31. //+------------------------------------------------------------------+
32. void OnStart(void)
33. {
34.     st_PropertyGraphics StyleGraphic;
35. 
36.     StyleGraphic.SAVE();
37. 
38.     StyleGraphic.Chart_Mode = CHART_LINE;
39. 
40.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, false);
41.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, false);
42.     ChartSetInteger(0, CHART_MODE, CHART_BARS);
43.     ChartRedraw();
44. 
45.     Sleep(2000);
46.     
47.     StyleGraphic.RESTORE();
48. }
49. //+------------------------------------------------------------------+

コード06

この場合、コードのコンパイル時に次のような警告が表示されます。

図01

エラー内容に注目してください。38行目では、すでにpublicではなくなっている値へアクセスしようとしています。これは、06行目でprivate指定を追加したことによって発生しています。しかし、コード06から38行目を削除すれば、コードは問題なくコンパイルできるようになります。なぜならSAVEおよびRESTOREのサブルーチンはpublicとして定義されており、さらに12行目でpublic指定を追加しているため、それらの処理自体は外部からアクセス可能な状態になっているからです。ここまでの内容は少し混乱して感じられるかもしれません。しかし、この仕組みは実際にはそれほど複雑ではありません。むしろここから先は、「public」と「private」というアクセス制御を取り入れた構造化プログラミングの形に入っていく段階だと考えてください。


まとめ

今回の記事はここまでとなります。今回の内容は特別な意味を持っています。なぜなら、この段階からいわゆる「構造化プログラミング」の領域に入っていくからです。ここでは、小さなデータ構造を作成し、それらを中心に処理のまとまりを構築していきます。そしてその中で、データの処理や維持、解析をおこなうために、専用の手続きや関数を使っていくことになります。

初めてこのような実装方法に触れる場合、多くの人にとっては非常に複雑で分かりにくく感じられるかもしれません。しかし心配する必要はありません。実際には思っているほど難しいものではありません。また、今回扱った主要なコードはアプリケーション内で確認できるようになっているため、各要素をじっくり学習したり練習したりすることができます。

この内容を正しく理解することは非常に重要です。ここを理解できていれば次のステップが大幅に簡単になるからです。さらに、今後説明する内容の理解もよりスムーズになります。とはいえ、今回はあくまで導入に過ぎません。

次回の記事では、今回扱った内容をさらに詳しく掘り下げていきます。ただし、その前提となる基礎部分を疎かにしないようにしてください。この基礎をしっかり理解することで、その後の内容がより明確に理解できるようになります。

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

添付されたファイル |
Anexo.zip (2.89 KB)
リスク管理(第5回):リスク管理システムをエキスパートアドバイザーに統合する リスク管理(第5回):リスク管理システムをエキスパートアドバイザーに統合する
本記事では、これまで開発したリスク管理システムを実装し、さらに別記事で解説したOrder Blocksインジケーターを追加します。加えて、バックテストを実行し、リスク管理システムの有無による結果の違いを比較することで、動的リスク管理の影響を評価します。
取引におけるニューラルネットワーク:多変量時系列のデュアルクラスタリング(最終回) 取引におけるニューラルネットワーク:多変量時系列のデュアルクラスタリング(最終回)
DUETフレームワークの著者らによって提案されたアプローチの実装を引き続き進めます。本フレームワークは、時間方向とチャネル方向のクラスタリングを組み合わせることで、時系列データに潜在するパターンを抽出する革新的な手法を提供します。
取引におけるニューラルネットワーク:周波数領域における異常検出(CATCH) 取引におけるニューラルネットワーク:周波数領域における異常検出(CATCH)
CATCHフレームワークは、フーリエ変換と周波数パッチングを組み合わせることで、従来手法では捉えきれない市場異常を高精度に検出します。本記事では、このアプローチが金融データに潜む隠れたパターンをどのように明らかにするのかを解説します。
取引におけるニューラルネットワーク:市場異常の適応型検出(最終回) 取引におけるニューラルネットワーク:市場異常の適応型検出(最終回)
時系列データにおける異常検知のための高度なツールであるDADAフレームワークの基盤となるアルゴリズムの構築を続けます。このアプローチにより、ランダムな変動と重要な逸脱を効果的に区別することができます。従来の手法とは異なり、DADAはさまざまなデータタイプに動的に適応し、それぞれのケースにおいて最適な圧縮レベルを選択します。