English Русский Español Deutsch Português
preview
MQL5をマスターする — 初心者からプロまで(第7回):MQLアプリケーションのデバッグの原則

MQL5をマスターする — 初心者からプロまで(第7回):MQLアプリケーションのデバッグの原則

MetaTrader 5 — 例 |
21 2
Oleh Fedorov
Oleh Fedorov

はじめに

本連載の前回の記事では、MQL5でエキスパートアドバイザー(EA)を構築するための原則について解説しました。 当初、この記事では、サーバーからの応答を含む回復可能なエラーに対処できる、マーケット向けの信頼性の高いEAを取り上げる予定でした。 実際に、すでに執筆を始めていました。

しかし、これまでの記事を読んだ人たちがプログラミングを始め、私にDMで質問を送ってくるようになりました。 もちろん、それらは初心者によくある質問であり、私は喜んで答えていました(今でもそうです)。 そして、こうした質問に答えているうちに、より高度なプログラミングの概念に進む前に、学習者が自分のコードや他人のコードにあるエラーをどのように修正できるのか、説明する必要があるとはっきり気づきました。 そこで、「信頼性の高いEA」については次の記事で取り上げることにし、今回はデバッグについて説明することにしました。

誰にでも間違いはあります。他の人より頻繁に間違える人もいますが……。 何かを始めたばかりの人は、プロならすぐに気づき、自然と避けるような、よくあるミスを数多く犯すものです。 しかし、プロであっても間違えることはあります。 プロのミスは、単に発見しにくく、より深刻な結果を招くことが多いのです。 この点で、プログラミングは他の人間の活動と何ら変わりません。 ただし、生活における他の多くの分野とは異なり、プログラムのコードが利用でき、十分な時間があれば、プログラミングのエラーは比較的簡単に修正できます。

プログラム内のエラーを修正するプロセスは、デバッグと呼ばれます。 100行を超えるコードでプログラムを作成する場合、デバッグはほぼ必須の工程です。 間違いを犯さずに生きることはできませんが、それを修正することは私たちの「神聖な」権利です。 そこでこの記事では、MetaEditorに組み込まれたツールを使用して、MQL5プログラムをデバッグするための基本を解説します。 プロの方にとって、ここで大きな発見はないと思います。何しろ、このシリーズは初心者向けですから。 ただし、何か見落としていたり、間違っていたりする点があれば、いつものように、どなたでもコメントをお寄せください。


エラーメッセージを調べる

最も単純なエラーには、タイプミスや構文エラーがあります。 たとえば、MQL4で書かれたインジケータを書き換える場合、iMAやObjectFindなどの関数呼び出しをいくつか修正する必要があるでしょう。これらの関数名は両方の言語バージョンに存在しますが、引数の数が異なり、戻り値の意味も異なる場合が多いためです。 こうしたケースは非常にわかりやすいものです。コンパイラが自動的にエラーを検出し、該当するウィンドウに詳細な情報をすぐに表示してくれるからです。 たとえば、次のコードをコンパイルしてみてください。

//+------------------------------------------------------------------+
//|                                            ErrorsResearching.mq5 |
//|                                       Oleg Fedorov (aka certain) |
//|                                   mailto:coder.fedorov@gmail.com |
//+------------------------------------------------------------------+
#property copyright "Oleg Fedorov (aka certain)"
#property link      "mailto:coder.fedorov@gmail.com"
#property version   "1.00"
#property indicator_chart_window

int g_someInt; // No issues here

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
 {
//--- indicator buffers mapping
  g_some_Int = 5; // There is an error here. The variable name is specified incorrectly
//---
  return(INIT_SUCCEEDED);
 }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
 {
//---

//--- return value of prev_calculated for next call
  return(rates_total);
 }
//+------------------------------------------------------------------+

例1: コンパイルエラーを引き起こすコード

この例をコンパイルした結果を図1に示します。 関数内で変数名が誤って指定されているため、コンパイラはエラーを報告します。

エラーメッセージ(未宣言の変数)

図1: エラーメッセージ(未宣言の変数)

図1に示したウィンドウの各要素は明白ですが、詳しく見ておく価値があります。 最初の列にはエラーの説明が表示されます。 また、左側には、そのエラーがどのカテゴリに属するかを示すマークが表示されます。 具体的には、次のとおりです。

  • 警告は黄色の三角形で示されます。原則として、このマークが付いた行は無視しても構いません。ただし、場合によってはプログラムのロジックが正常に機能しなくなる原因となるため、慎重に分析する必要があります。
  • エラーは赤い「停止」マークで示されます。これがあるとコンパイルできません。
  • 情報メッセージは灰色の点で示されます。これらはエラーではなく、単に情報を伝えるものです。

エラーの説明に続く列には、エラーが発生したファイルの名前が表示され、最後の2列には、それぞれエラーの行番号と列番号が表示されます。 これらの情報を合わせることで、コンパイラが問題を「認識している」正確な位置がわかります。 ほとんどの場合、該当する行をダブルクリックするだけで、その位置に移動できます。

場合によっては、1つのエラーに対してコンパイラが2つ以上のメッセージを生成することがあります。 たとえば、例1の代入操作を、例2に示すようにグローバルスコープに移動した場合です。

#property indicator_chart_window

int g_someInt; // No issues here
g_someInt = 5; // There is an error here. Operations cannot be performed 
               // (except for initialization) in the global scope

例2: グローバルスコープでの値の代入の試み

例2をコンパイルしようとした結果を図2に示します。

1つのエラーが複数のメッセージを引き起こすことがある

図2: 1つのエラーが複数のメッセージを引き起こすことがある

この場合、コンパイラは変数を新しい宣言として解釈しようとします。 しかし、その宣言では変数の型が省略されているだけでなく、同じ名前の変数がすでに存在しています(1行上で宣言されています)。 この場合、代入文をいずれかの関数内に戻すだけで、両方のエラーが解消されることは明らかです。

この例から、重要なことがわかります。エラーは通常、上から下へ1つずつ修正し、修正するたびにコンパイルを試みるべきです。 もちろん、このルールは絶対的なものではありません。 たとえば、コンパイラが同じ種類のメッセージ(不明な変数や未宣言の関数など)を複数生成した場合は、まとめて修正しても構いません。

最後に、このセクションの締めくくりとして、エラーメッセージを理解するのに十分な英語力がまだない方のために、主要な用語を簡単にまとめた用語集を掲載します。 この用語集は、学習を始めたばかりの方がコンパイラのメッセージを理解するための補助を目的としています。一般的な辞書が不要になるわけではありませんが、英語を十分に学ぶ余裕がまだない初心者には役立つでしょう。

英語
ロシア語 説明
unexpected неожиданный 通常、文末のセミコロンなど、必要な操作や重要な文字が抜けている場合に発生します。 たとえば、例2では、変数の宣言時にデータ型が省略されているとコンパイラが判断します。
undeclared неописанный 通常、変数名を間違えた場合や、別の場所で呼び出されている関数の宣言を忘れた場合に発生します。
unbalanced
несбалансированный 通常は括弧を指します。 開き括弧があるのに閉じ括弧がない場合、またはその逆の場合です。
missing отсутствует ンパイラが重要なものを見つけられなかったことを意味します。
already уже́ 通常、「すでに存在する」という文脈で用いられます。
wrong ошибочный, неправильный 関数呼び出し時のパラメータ数が正しくないことを示すメッセージで、よく使われます。
invalid некорректный, недопустимый たとえば、次のコード
int array_variable[];
array_variable = 5;
では「invalid array access(無効な配列アクセス)」というエラーが発生します。これは、配列の要素ではなく、配列変数そのものに数値を代入しようとしているためです。

表1: エラーメッセージに出てくる英単語の簡易用語集


実行時エラー

コンパイルエラーを解決したところで、本格的なデバッグ作業が始まります。次に、プログラムが正しく動作しているかどうかを確認する必要があるためです。 プログラマーは不注意から、アルゴリズムの設計時に重要な手順を省略したり、「コピー&ペースト」によって誤った変数を変更したり、処理の順序を間違えたりすることがよくあります……。

実は、表1の最後の行にあるサンプルコードには、2つのエラーがあります。 このコードの2行目に角括弧と要素番号を追加しても、プログラムは正常に動作しません。 問題は、配列array_variableが動的配列として宣言されているにもかかわらず、そのサイズがどこにも指定されていないため、何も書き込めないことです。

残念ながら、形式上はすべてが言語の構文に準拠しているため、コンパイラはこのエラーを検出しません。 しかし、実行時には、プログラムが「配列のインデックスが有効範囲外です」(「array out of range」)というエラーメッセージを表示します。 こうしたメッセージや、プログラムから出力されるその他すべてのメッセージは、ターミナルの[Experts]タブで確認できます(図3)。

実行時エラーメッセージ

図3: 実行時エラーメッセージ

ちなみに、この例のような「範囲外」エラーは非常によく発生します。 特に多いのは、次のようなケースです。

  • 特定の期間の価格データがまだ読み込まれていないにもかかわらず、プログラムがそのデータを読み取ろうとする。
  • 配列のインデックスが正しく指定されていない。たとえば、以下の例の3行目(コメントアウトされている行)のように、配列の最後のインデックスが配列の長さより1小さいことをプログラマーが忘れてしまう場合があります。
    int testArray[3]={3,4,5};
    int size = ArraySize(testArray);
    // Print (testArray[size]); // Error! The program is attempting to access a nonexistent array element
    Print (testArray[size-1]); // Everything is fine

    例3: 配列のインデックスを誤って指定する典型的なケース

アルゴリズム上のエラーがすべて、重大なプログラムエラーのメッセージにつながるわけではありません。 多くの場合、インジケータが単に「描画されない」、あるいは意図したものとはまったく異なるものを描画します。また、過去データを見ると明らかに売買の機会があるにもかかわらず、EAが取引をおこなわないこともあります。スクリプトがひどく「フリーズ」し、ターミナルを再起動せざるを得なくなることさえあります……。 そうなれば、エラーの原因を突き止める必要があります。 以下では、私がエラーを見つけるためによく使う代表的な方法を紹介します。


デバッグはどこから始めるか

このセクションでは、経験豊富なプログラマーにとっては説明するまでもないほど明白な推奨事項を紹介します。 しかし、私が直接話した人やフォーラムでやり取りした人を見る限り、初心者は同じ間違いを繰り返し、いつも同じところでつまずき、同じ質問をさまざまな言い方で尋ねます。「どこから始めればよいのでしょうか?」そこで……。

まずは最初から始めましょう。 自分で書いたコードであれば、もちろん自分が誰よりもよく理解しているはずです。 他人が書いたコードであれば、まず、そのコードが何をするのか理解するように努めてください。 ただし、最初から細かい点にとらわれる必要はありません。 まずは全体を俯瞰してみましょう。

  • EAやインジケータでは、すべてがOnInit関数から始まります。一方、スクリプトやサービスではOnStart関数から始まります。 これらの関数はそれぞれ、プログラムの起動時に1回だけ実行されます(ただし、サービスには通常、取引中の特定のイベントを処理するための無限ループが含まれています)。
  • インジケータでは、OnCalculate関数がティックごとに呼び出され、EAでは、OnTick関数が呼び出されます。 また、EAやインジケータではタイマーを設定でき、OnTimer関数によって一定の間隔で処理を実行できます。
  • これら5つの関数(OnInit、OnStart、OnCalculate、OnTick、OnTimer)はエントリーポイントです。 他人のコードを分析するときは、まずこれらの関数から確認しましょう。 コメントがあれば、大きな助けになります。まずはコメントに目を通してください。 コメントによって、主要なロジックブロックが示されている可能性があります。 コメントがない場合は、どの関数が使われているかを手早く確認し、それぞれの名前から役割を推測してみましょう。 コードが適切に構成され、作者がすべてを1つの関数に詰め込んでいなければ、ラッキーです。 そうでなくても問題ありませんが、デバッグに時間がかかる可能性があります。 いずれにしても、アルゴリズムの実行に含まれる手順を理解するように努めてください。たとえば、「パラメータの計算」「条件の確認」(描画するかどうかなど)、「すべてのバーを順に処理する」といった比較的大きな処理ブロックです。 もちろん、このリストはすべてを網羅しているわけではなく、取り組む課題によって大きく異なります。
  • ツールバーの専用ボタン(関数一覧を表示するボタン)(関数のリストを表示するボタン)またはキーボードショートカットのAlt+Mを使って、このファイル内で定義されている追加の関数の一覧を表示し、それぞれの名前から用途を推測してみてください。
  • 初心者にとっては、コメントがない場合、重要な箇所に処理ブロックの名前をコメントとして追加すると非常に役立ちます。 また、アルゴリズムのフローチャートを作成することも有効です(たとえば、DRAKON言語や、使いやすいと思う他のツールを利用できます)。 繰り返しますが、この段階では、比較的大きなブロックを使って図を描くのが最善です。
  • インジケータにインジケータバッファを追加した後、描画が突然まったくおこなわれなくなった場合は、次の点を確認してください。
    • インジケータバッファを配列に割り当てる際(SetIndexBuffer関数を呼び出す際)に、番号の割り当てがずれていないことを確認します。つまり、番号が0から始まり、欠番なく連続している必要があります。
    • indicator_buffersプロパティにはバッファの総数が正しく設定され、indicator_plotsプロパティには画面上に表示されるバッファの数が正しく設定されていることを確認します。
    • indicator_typeやindicator_colorなどのプロパティについても、番号が正しく設定されていることを確認します。ただし、これらの番号は1から始まります。また、プロパティを使う場合も組み込み関数を使う場合も、表示対象となるすべてのバッファを定義していることを確認してください。
デバッグ対象のプログラムのロジックを理解し、インジケータに最も単純なタイプミスがないことも確認した。それでもプログラムが期待どおりに動作せず、実行時エラーも出力されない。その段階になって初めて、デバッグを始めてみましょう。


デバッグ出力の使用

エラーを見つける最も古い方法は、プログラム内の重要な箇所でPrint関数(またはPrintFormat関数。どちらを使うかは好みによります)を使ってメッセージを出力することです。

昔は、プログラムが期待どおりに動作しない理由を突き止める唯一の方法がこれでした。 しかし、今日でもこのデバッグ方法は広く使われています。シンプルですばやく実行できるうえ、出力結果をドキュメントとして保存しておけば、公共交通機関で移動しているときや公園のベンチに座っているときでも確認できるからです。

言うまでもなく、こうしたメッセージは有益であり、プログラムのどこを実行しているのか、そこで具体的に何が起きているのかを、プログラマーが明確に把握できるものでなければなりません。 メッセージの種類や出力元を示すために、「~~」など、日常的にはあまり使われない特殊な文字の組み合わせを含めることもよくあります。 こうしたメッセージの使い方は非常にシンプルです。コード内の重要な箇所にメッセージを配置し、それを使って実行中のプログラムの状態を監視します。

メッセージを使って、次のコードの問題を理解し、修正してみましょう。

void OnStart()
 {
//---
  int i;
//---
  Print("Program starting, 'i' hasn't initialized");
  for(i=0;i<3;i++);
   {
    Print("In the cycle 'i="+i+"'");
   }
  Print("After cycle 'i="+i+"'");
 }

例4: 正しく動作しないプログラムのコード

練習として、このプログラムがいくつのメッセージを表示するのか、また、具体的にどのメッセージを表示するのかを考えてみてください。 以下の説明を読む前に、これらの質問に答えておくことをお勧めします。

このコードを書いたとき、プログラムは5つのメッセージを出力することを意図していたと考えられます。つまり、ループの外で2つ、ループの中で3つです。 しかし、実際にそのように動作するでしょうか。 確認してみましょう。

Program starting, 'i' hasn't initialized
In the cycle 'i=3'
After cycle 'i=3'

例5: 例4のプログラムの出力

予想外の結果になりました……。 なぜメッセージが3つしか表示されないのでしょうか。 数値は0から2になるはずなのに、なぜループ内で「3」が出力されるのでしょうか。 2行目と3行目の数値は同じになるべきなのでしょうか。 考えてみましょう。

  1. プログラムの3つの段階、つまりループの前、ループ内、ループの後は、すべて実行されています。これはよい兆候です。
  2. 第1段階はエラーなく実行されています。
  3. 第3段階では期待どおりの結果が得られているため、ここにも問題はないと考えられます。
  4. 第2段階では第3段階と同じ数値が出力されています。つまり、ループは動作しています……ただし、完全に正しく動作しているわけではありません。

以上のことから、問題はfor(i=0; …) という式と、必要なデータを出力する処理との間にあると考えられます。 では、その間に何があるのか、詳しく見てみましょう……。 実際、そこには中括弧とセミコロンがあります。

中括弧の対応に問題はありません。そうでなければ、表示された警告ではなく、コンパイルエラーが発生していたはずです。 中括弧は、1つ以上の単純な文を1つのブロックにまとめるためのもので、この場合もその役割を正しく果たしています。中括弧の内側には、必要なものが過不足なく含まれています。

セミコロンは文の終わりを示します。 この場合は……空の文です。 つまり、このループは変数を1ずつ3回増加させるだけで、必要な処理を実行していません。その後になって初めて、ループで更新された変数の値がユーザーに表示されます。 セミコロンを削除すれば、正常に動作するはずです。 確認してみましょう。

Program starting, 'i' hasn't initialized
In the cycle 'i=0'
In the cycle 'i=1'
In the cycle 'i=2'
After cycle 'i=3'

例6: 修正した例の出力

うまくいきました。 問題は解決しました。

しかし、この方法でデバッグすると、エンドユーザーにとってまったく役に立たないメッセージがコード内に大量に残ってしまいます。 自分用のアプリケーションを開発しているのでなければ、デバッグの完了後に、こうしたメッセージをすべて見つけて削除する必要があるでしょう。 しかし、通常の検索機能を使って削除するのは不便です(もちろん、そうせざるを得ない場合もあります)。 そのため、経験豊富なプログラマーは、コードにデバッグ出力を追加する際、特別なプリプロセッサディレクティブと組み合わせて使うことがよくあります。

ループカウンタの値を順番に出力することが目的で、完成したアプリケーションでは他のメッセージが不要だとすると、適切で便利なコードは次のようになります。

#define DEBUG
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
 {
//---
  int i;
//---
#ifdef DEBUG
  Print("Program starting, 'i' hasn't initialized");
#endif

  for(i=0;i<3;i++) // NO semicolon!!!
   {
    Print("In the cycle 'i="+i+"'");
   }

#ifdef DEBUG
  Print("After cycle 'i="+i+"'");
#endif

 }

例7: 条件付きコンパイルディレクティブの使用

この例では、DEBUGという名前のマクロを宣言し、デバッグ時にのみ必要となるプログラムの部分について、DEBUGマクロが存在する場合に限り、メッセージ出力コードをコンパイル対象のファイルに含めるようプリプロセッサに指示しました。 マクロの存在は、#ifdefディレクティブを使用して確認します。 つまり、プログラム内にDEBUGという名前のマクロが存在しない場合、 #ifdefと#endifの間に記述されたコードはコンパイルされません。 この方法なら、プロジェクトに複数のファイルが含まれている場合でも、プロジェクト内の1か所で最初の行を削除または追加するだけで、これらのセクションを有効または無効にできます。

さて、この章を締めくくるにあたり、例4のこのエラーは、コンパイル時にコンパイラが伝えようとしていた警告にすぐ注意を払っていれば、もっと簡単に発見できたはずだと述べておきます。

empty controlled statement found        ErrorsResearching.mq5   23      19

例8: 空の制御文に関するコンパイラの警告

このメッセージは、コンパイラが「空の制御文」を検出したことを示しています。このようなメッセージが表示された場合、指定された位置に余分なセミコロンがある可能性が高いです。 必ず確認してください。


MQL5プログラムで対話型デバッガを使用する

MetaEditorには、多くの最新のIDEと同様に、対話型デバッガが組み込まれています。 「対話型」とは、デバッグ中にデバッガとやり取りできることを意味します。 この仕組みの基本原理は、デバッグ用の特別なモードでプログラムをコンパイルすることです。 このモードでは、エディタ上でプログラムの実行を1行ずつ直接監視し、プログラムの重要な箇所で変数がどのように変化するかを確認できます。

しかし、特別な対策を講じなければ、プログラムはあまりにも速く実行されてしまい、何も確認できません。 そこで、エディタでは「ブレイクポイント」(英語:breakpoint)と呼ばれる特別なマーカーを設定します。 これにより、プログラムの関数内で実行を一時停止し、その時点での変数の値を確認できます。 ブレイクポイントで各変数の値を確認したら、次のステートメントを実行するか、次のブレイクポイントまで実行を「再開」できます。

デバッグ作業そのものは直感的に理解できると思いますが、念のため、どのボタンをクリックすればよいかを説明します。

  • ブレイクポイントは通常、実行の開始時または一時停止した後に設定します。
  • ブレイクポイントを設定または解除するには、関数内の任意の行にカーソルを置き、次のいずれかの方法を使用します。
    • 現在のプログラムの行番号が表示されている列をダブルクリックします。

      ダブルクリックによるブレイクポイントのオンオフ

      図4: ダブルクリックによるブレイクポイントのオンオフ

    • <F9>キーを押します。
    • 対象の行のコンテキストメニューを使用するか、メインメニューから[デバッグ]→[ブレイクポイントのオンオフ]を選択します(図5)。

      ブレイクポイントのメニュー

      図5: [ブレイクポイントのオンオフ]メニュー項目

  • 複数のブレイクポイントを設定したものの、もう必要ない場合は、デバッグメニューを使用するか(図5では、必要な項目は右側のメニューの赤枠のすぐ下にあります)、<Ctrl>+<Shift>+<F9>キーを押すことで、すべてのブレイクポイントを一度に削除できます。

図6に示すツールバーのセクションを使用して、プログラムをデバッグモードで操作できます。

デバッグツールバー

図6: デバッグ用ツールバーの一部

左側のボタン(過去データでデバッグを開始する)は過去データを使ったデバッグを開始し、右側のボタン(ライブ市場データでデバッグを開始する)はリアルタイムの市場データを使ったデバッグを開始します。 青いボタンをクリックするとストラテジーテスターが開き、緑のボタンをクリックするとターミナル上で新しいチャートが直接開きます。 各モードには、次のような違いがあります。

  • デバッグ対象のプログラムの動作を理解する、つまり0番バーでステップ実行する場合、ボタン間に本質的な違いはありません。ただし、テスターとターミナルの間には大きな違いがあります。
  • テスターでは、実行中のプログラム以外の分析ツールを使用できません。
  • 一方、テスターではプログラムのパラメータ最適化を実行できます。
  • 長期間にわたってプログラムがどのように動作するかを、ステップごとに確認できます。
  • 初期読み込み時のデータ読み込みは、ターミナルのほうが速くなります。
  • 特にインジケータやスクリプトなど、取引をおこなわないプログラムは、リアルタイムの市場データでデバッグするのが一般的に便利です。
  • ただし、自動売買のデバッグは、損失を避けるためにも、過去データでおこなうほうがよいでしょう。

どのデバッグモードを選んでも、プログラムが開始されると、パネルの右側にあるコードをステップ実行するためのボタン(図5で枠に囲まれたボタン)が使用可能になります。 これらのボタンはそれぞれプログラムの次のステップを実行しますが、実行方法が異なります。

  • 一番左のボタン(ステップイン)はソースコードを参照できる各関数に入りながら次のステップを実行します。
  • 中央のボタン(ステップオーバー)は、組み込みのターミナル関数かユーザー定義関数かにかかわらず、各関数の内部に入らず、外側から1ステップで実行します。
  • このグループの一番右のボタン(ステップアウト)をクリックすると、現在の関数を1ステップで完了し、その関数の呼び出し元に戻ることができます。

現在のデバッグセッションを終了するをクリックすると、現在のデバッグセッションが終了します。

一時停止をクリックすると、実行中のプログラムが予測できない位置で一時停止します。通常は、OnCalculate 関数の開始時や、ターミナルから定期的に呼び出されるその他の関数の開始時に停止します。

デバッグウィンドウの下部パネルでは、左側にコールスタック全体が表示されます。これは、現在の関数に到達するまでに呼び出された関数の一覧です。 右側には、その関数内のすべての変数が一覧表示されます。 プログラム内で変数の値が変わると、このウィンドウの値も自動的に更新されます。

コールスタックと変数の一覧

図7: デバッグモード時の下部パネル - 変数一覧とコールスタック

このパネルの右側には、評価する式を追加することもできます。たとえば、変数名(グローバル変数など)や、算術演算、論理演算、ビット演算などの式です。 式を一覧に追加するには、次の方法があります。

  • プログラムの本文内でその式を選択し、<Shift>+<F9> を押します。
  • 選択した式を右クリックし、コンテキストメニューから[ウォッチに追加](英語:"Add Watch")を選択します。
  • 変数一覧の空行をダブルクリックし、式を直接入力します。

配列や構造体の内容を表示するには、それをダブルクリックします。 ただし、インジケータ内のhighやtimeなどの配列は、展開しないほうがよいでしょう。場合によっては、高性能なコンピューターでもかなり長い時間フリーズしてしまうことがあります。 代わりに、配列名とインデックスを別の行に入力することをお勧めします。たとえば、time[i]のようにします。

構造体やクラスの特定のメンバーを表示したい場合は、オートコンプリートが便利です。名前の末尾にピリオドを付けるか、<Ctrl>+<Space>を押すと、通常のポップアップヒントが表示されます。 しかし残念ながら、関数呼び出しを追加することはできません。

練習として、ZigZag などの標準インジケータを詳しく調べてみることをお勧めします。 「Indicators → Examples → ZigZag.mq5」ファイルを開き、エディタ上で以下の各セクションに記載された操作を実行してください。

  1. まず、このインジケータにどのようなユーザー定義関数があるかを確認します。 関数に移動するには、一覧内の関数名をクリックするだけです(図8)。

    ZigZagインジケータの関数一覧

    図8: ZigZagインジケータの関数一覧

  2. ブレイクポイントを設定しましょう。 まずOnInit関数の先頭に設定します(執筆時点で、私の環境では41行目です)。次にOnCalculate関数の先頭(66行目)と、極値の検索が始まる位置(118行目)に設定し、ステップイン(ステップイン)とステップオーバー(ステップオーバー)の動作の違いを確認します。

  3. これでプログラムを実行できます。 この場合、左側のボタンならどちらを使ってもかまいません。 ここでは、緑のボタン(ライブ市場データでデバッグを開始する)をクリックすることにします。

  4. OnInit関数内の最初のブレイクポイントに到達したら、関数の内容をざっと確認し、処理が単純で、すべて理解できることを確かめます。その後、ボタン(ステップアウト)をクリックして、次の関数に進みます。

  5. 推奨どおりに設定していれば、次に停止するのはOnCalculate関数の先頭です。 この関数には、インジケータ開発者にとって役立つ手法が数多く含まれているため、詳しく調べる価値があります。 自由に確認しながら、(ステップイン)または(ステップオーバー)のボタンを使って次のステップに進んでください。
  6. for、while、do … while ループを調べるには、それぞれのループを最初から最後まで数回ステップ実行します。その後、各ループの終了後にブレイクポイントを設定し、ボタン(過去データでデバッグを開始する)または(ライブ市場データでデバッグを開始する)を使って、118行目に到達するまで実行を続けます。
  7. ボタン(ステップイン)(ステップイン)を使って、次のループを少なくとも1回ステップ実行し、コールスタック(図7の左側)の項目がどのように変化するかを確認してください。
  8. ボタン(ステップオーバー)(ステップオーバー)を使って同じループを2回目に実行し、その違いを確認してください。
  9. インジケータの残りのコードには、デバッグ手法に関して新しい要素はありません。これまでに学んだ手法を使って、何が起きているのかを確認してください。
  10. デバッグを終了するには、「現在のデバッグセッションを終了する」(デバッグ終了)ボタンをクリックします。


プロファイリング

1つのチャート上で複数のプログラムが動作し、さらにそのようなチャートが多数ある場合、それぞれのプログラムが高速に動作することが重要になります。 しかし、プログラマーが非効率なアルゴリズムを使用すると、問題自体は解決できていても、実行速度が遅すぎて実用に耐えないことがあります。 これは特に、旧型のネットブックのような低スペックのマシンで顕著です。こうしたマシンも、トレーダーの間では今なお現役で使われていることがあります。

プロファイリングは、プログラムの実行を遅くしている関数を特定するためにおこないます。より正確には、各関数の処理にどれだけの時間が費やされているかを調べるものです。 プロファイリング中、MetaEditorはプログラム内の各関数の実行時間を計測し、計測結果に十分な差がある場合には、それを専用のグラフとして表示します。

それでは、先ほどと同じZigZagインジケータの動作をプロファイリングしてみましょう。 実行するには、図9に示すツールバーか、図10に示すデバッグメニューを使用します。 プロファイリングはそれほど頻繁に行う操作ではないため、開発者は専用のキーボードショートカットを用意していません。

プロファイリング用コントロールパネル

図9:プロファイリング用コントロールパネル

デバッグメニューのプロファイリングセクション

図10: デバッグメニューのプロファイリングセクション

デバッグと同様に、プロファイリングもリアルタイムの市場データまたは過去データを使って実行できます。それぞれ、緑色と青色のボタンに対応しています。 また、2番目のコマンドであるボタン(ボタン:プロファイリングを停止)をクリックすると、プロファイリングを終了できます(図9と図10では、このボタンは無効になっています)。

手順は簡単です。

  • [Start]ボタンをクリックして、プロファイリングを開始します。
  • プログラムの処理が最後まで進むように、しばらく待ちます。
  • [Stop]ボタンをクリックして、プロファイリングを終了します。
  • 結果のグラフを確認し、改善できる箇所があれば修正します(ZigZagの場合は、おそらく修正の必要はないでしょうが……)。

かなり古いネットブックで標準のZigZagインジケータをプロファイリングしたところ、図11のような結果が得られました。

標準のZigZagインジケータのプロファイリング結果

図11: 標準のZigZagインジケータのプロファイリング結果

結果を見ると、OnInit内ではSetIndexBufferが処理時間の大部分を占めていることが分かります。一方、HighestとLowestは非常に高速であるため、この比較グラフには表示されていません。 予想どおり、最も時間がかかっていたのはOnCalculateでした。実際の処理の大部分がこの関数で行われるうえ、OnInitよりもはるかに頻繁に呼び出されるためです。 この関数のパフォーマンスは、大幅に改善できる可能性があります。

MetaQuotesの開発者たちは、このインジケータのコードを非常によく最適化しています。 しかし……もしかすると、これよりも高速なアルゴリズムを考案するのは、あなたかもしれません。


結論

アプリケーションのデバッグは、ソフトウェア開発のライフサイクルにおいて、最も難しい工程の一つです。 プロジェクトによっては、開発作業全体の70%、場合によってはそれ以上の時間がデバッグに費やされることもあります。 正常に動作し、何千人ものユーザーに利用されているプログラムであっても、エラーが潜んでいる可能性があります。 まして、初心者が書いたコードであれば、なおさらです。 この記事で紹介したデバッグ手法が、プログラミング初心者の皆さんにとって、自分のコードに潜むエラーをより簡単かつ効率的に見つけ、他の人が書いたアルゴリズムをより効果的に学ぶための助けになれば幸いです。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/18075

最後のコメント | ディスカッションに移動 (2)
Everrest
Everrest | 29 10月 2025 において 14:03
オレグさん、こんにちは。どのようにご連絡すればよろしいでしょうか? トレーニングについて興味があり、アドバイザーの作成 について具体的なご依頼があります。
Oleh Fedorov
Oleh Fedorov | 29 10月 2025 において 14:27
Everrest アドバイザーの作成 について具体的な依頼があります。
こんにちは。一番簡単なのは、ダイレクトメッセージを送っていただくことです。必ず確認しています :-)
初級から中級まで:ランダムアクセス(II) 初級から中級まで:ランダムアクセス(II)
2つのわずかに異なるアプローチが、パフォーマンスやディスクI/Oの設計を含む実装全体の方針に、どのような大きな違いをもたらすのかを考察します。また、アプリケーション間の互換性の問題を防ぐうえで、これらのアプローチがどのように役立つのかについても解説します。
初級から中級まで:オブジェクトイベント(II) 初級から中級まで:オブジェクトイベント(II)
オブジェクトによって発生する残り3種類のイベントが、どのように機能するのかを見ていきましょう。 これらを理解することは非常に興味深いものになるはずです。というのも、最後には、多くの人にとって突拍子もないことに思えるかもしれませんが、実際には十分に実現可能で、非常に驚くべき結果をもたらす機能を実装するからです。
初級から中級まで:サンドボックスの操作 初級から中級まで:サンドボックスの操作
MetaTrader 5をベースプラットフォームとして、サンドボックスの内容を調べ、さらにはその中で操作をおこなうための2つの方法について解説します。 本記事の内容を理解することは、今後の記事で取り上げる内容を理解するうえで不可欠です。
取引におけるニューラルネットワーク:インテリジェントな予測パイプライン(Time-MoE) 取引におけるニューラルネットワーク:インテリジェントな予測パイプライン(Time-MoE)
時系列予測タスク向けに適応された最新のTime-MoEフレームワークを詳しく見ていきましょう。 アーキテクチャの主要な構成要素を段階的に実装しながら、随所で解説と実践的な例を紹介します。 このアプローチにより、モデルの仕組みを理解するだけでなく、その原理を実際の取引シナリオに応用することも可能になります。