English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
エラーとログの発見

エラーとログの発見

MetaTrader 5 | 6 10月 2015, 16:44
2 199 0
Дмитрий Александрович
Дмитрий Александрович

はじめに

読者のみなさん、こんにちは!

Expert Advisors、スクリプト、インディケータ内エラーを発見するいくつかの方法とログ手法を考察していきます。また、ログ閲覧プログラム、LogMonもご提供します。

エラー発見はプログラミングと切っても切れないものです。コードの新規ブロックを書くと、それが正しく動作し、エラーがないか確認することが必要です。3つの方法でプログラム内のエラーを発見することができます。

  1. 最終結果を評価する
  2. 順番にデバッグする
  3. ログにロジカルな手順を書く

それぞれの方法をみていきます。


1. 最終結果の評価

この手法ではプログラムの作業結果またはコードの一部を分析します。簡単なコードを取り上げます。わかりやすいように、そこにには誤りを仕込んであります。

void OnStart()
  {
//---
   int intArray[10];
   for(int i=0;i<9;i++)
     {
      intArray[i]=i;
     }
   Alert(intArray[9]);

  }

コンパイルし実行すると、画面には"0"が表示されます。結果を分析すると、得るべき値は"9"であることが判ります。ここで、このプログラムは期待どおりの動作をしていない、という結論に至ります。このエラー発見手法はよく使われるもので、エラー場所は発見できません。エラー発見の第二の方法については、デバッグ機能を使用します。


2. 段階的デバッグ

この手法によりプログラムロジックが破たんしているエラー箇所が正確に発見できます。MetaEditorでは、'for' ループ内部に区切り点を入れ、デバッグを開始、 i 変数にウォッチを追加します。

デバッグ

次に、プログラム作業の全体を配慮し、『デバッグを再開する』をクリックします。"i"変数が"8"という値なのを確認し、ループを出ます。ここでエラーはその行にあるという結論を得ます。

for(int i=0;i<9;i++)

つまり、 i の値と数字 9 が比較されるべきであるのです。この行を"i<9" " to "i<10" or to "i<=9"と修正し、結果を確認します。数字9を取得しました。まさに予想どおりです。デバッグにより、プログラムが実行時にどのように動作しているかを知り、そして問題を修正することができました。以下はこの手法の欠点です。

  1. 直感的に、エラーがどこで発生したか不明である。
  2. ウォッチリストに変数を加え、各ステップ後に閲覧する必要がある。
  3. この手法では書き終わったプログラムの実行時にエラーを検出することはできません。EAの実アカウントまたはデモ アカウントでトレーディングしているときなどにはエラー検出できないということです。

では、三番目の手法を見てみましょう。


3. ログへのロジカルな手順の書き込み

この手法を使うと、プログラムにかなりな手順を記録できます。たとえば、初期化、取引、インディケータ計算などです。コード1行でスクリプトをグレードアップします。各ループで i 変数値を印刷するのです。

void OnStart()
  {
//---
   int intArray[10];
   for(int i=0;i<9;i++)
     {
      intArray[i]=i;
      Alert(i);
     }
   Alert(intArray[9]);

  }

実行し、出力されたログを見ます。- 番号「0 1 2 3 4 5 6 7 8 0」です。なぜそうなったか推論し、前のやり方同様スクリプトを修正します。

本手法のメリットとデメリット

  1. +プログラムをいちいち実行する必要がない、よってかなりの時間節約となる。
  2. +たいていの場合エラー箇所がわかる。
  3. +プログラム実行中でもログを続けることができる。
  4. +のちの分析や比較に備えログを保存することができる。(たとえば、ファイルに書き込むとき。以下を参照ください。)
  5. -ログにデータを書き込むオペレータ追加によりソースコードが膨らむ。
  6. -プログラム実行時間が増加した。(主に最適化には重要。)

まとめ

最初のエラー発見方法はエラーが実際どこで起こっているのか追跡することはできません。基本的にスピード重視で使用します。二番目の方法。- 段階的デバッグによりエラーの正確な発生場所は見つけられますが、時間がかかりすぎます。それにもし望むコードブロックを通過してしまったら、また最初からやり直しとなります。

そして、三番目の方法です。 - ロジカルな手順をログに記録することで、プログラムの動作分析が素早く行え結果を保存することができます。ログにExpert Advisors、インディケータ、スクリプトのイベントを書いていると、エラーを見つけるのは簡単で、エラーが発生した正しい条件を探す必要はありません。また、何時間もかけてデバッグする必要もありません。次に詳細を記録してそれらを比較します。また、いちばん便利で速い方法をご提案します。


ログの必要があるのはいつですか?

ロギングの理由を述べます。

  1. プログラムの誤ったふるまい
  2. 長すぎる実行時間(最適化)
  3. 実行時間監視(ポジションのオープン/クローズ、実行された内容などを印刷します。)
  4. MQL5の学習、たとえば印刷配列
  5. 選手権前のExpert Advisorsチェックなど


ロギング手法

ログにメッセージを書き込む方法はたくさんありますが、あらゆる場所で使われているものもあれば、特別な場合だけ必要となるものもあります。たとえば、eメールを使ったりICQを介してログを送信することは必ずしも必要ではありません。

MQL5プログラミングで使用されるもっとも一般的な手法をリストアップします。

  1. Comment() 関数の使用
  2. Alert() 関数の使用
  3. Print() 関数の使用
  4. FileWrite() 関数を使用してログをファイルに書き込みます。

次に、コードを交えてそれぞれの手法例を提供するとともに、その特徴について述べます。ソースコードはかなり抽象的なので、基本に忠実にいきます。


Comment()関数の使用

void OnStart()
  {
//---
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      Comment("Variable i: ",i);
      Sleep(5000);
     }
   Alert(intArray[9]);
  }

左上隅に"i" 変数の現在値があります。

Comment()

これで実行中プログラムの現在の状態を監視することができます。それではこの手法のメリット、デメリットです。

  1. +値を即座に見ることができる
  2. -出力規制
  3. -特定のメッセージを選ぶことはできない
  4. -実行中の動作状況をくまなく見ることはできない。現在状況のみ
  5. -比較的遅い
  6. -常に読みだした情報を見る必要があるため、動作の継続的監視には不適切

しかし、この Comment() 関数はExpert Advisorの現在の状態を表示するには有用です。たとえば、「2件の取引がオープン」または「GBRUSDをロット0.7で買い」など。


Alert()関数の使用

この関数はメッセージを音声通知付きで別ウィンドウに表示します。コード例です。

void OnStart()
  {
//---
   Alert("Start script");
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      Alert("Variable i:", I);
      Sleep(1000);
     }
   Alert(intArray[9]);
   Alert("Stop script");
  }
コード実行結果です。

Alert()

上出来です。すべてが、たちまち明確で音声まで伴っています。ではメリットとデメリットです

  1. +常にメッセージが全て記録される
  2. +音声による通知
  3. +すべて "Terminal_dir\MQL5\Logs\data.txt"ファイルに書き込まれている
  4. - すべてのスクリプト、Expert Advisors、インディケータからのメッセージはひとつのログに書き込まれている
  5. - ストラテジーテスタでは動作しない
  6. - 頻繁に呼ばれると、長時間にわたり端末をフリーズしてしまう(たとえば、毎ティックごと、ループ配列の印刷ごと)
  7. - グループメッセージは不可
  8. - ログファイルの閲覧は面倒
  9. - 標準的な「データフォルダ」以外のフォルダにメッセージの保存ができない

6番目のポイントは実際のトレーディングではたいへん重要です。特にストップ ロスをスキャルピングしたり変更する場合などです。かなりたくさんデメリットがあります。みなさんは他にも見つけることができるかもしれませんが、ここではもう十分だと思います。


Print()関数の使用

この関数はログメッセージを『エキスパート』と呼ばれる特別なウィンドウに書き込みます。それでは、そのコードです。

void OnStart()
  {
//---
   Print("Старт скрипта");
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      Print("Variable i: ",i);
     }
   Print(intArray[9]);
   Print("Stop script");
  }

Print()

見てのとおり、この関数はちょうど Alert() 関数のように呼び出されますが、メッセージはすべて『エキスパート』タブと "Terminal_dir\MQL5\Logs\data.txt" ブの通知なしにすべてのメッセージが書かれます。この手法のメリット、デメリットを考えてみます。

  1. +常にメッセージが全て記録される
  2. +すべて "Terminal_dir\MQL5\Logs\data.txt"ファイルに書き込まれている
  3. + プログラム動作の継続的ロギングに適している
  4. - すべてのスクリプト、Expert Advisors、インディケータからのメッセージはひとつのログに書き込まれている
  5. - グループメッセージは不可
  6. - ログファイルの閲覧は面倒
  7. - 標準的な「データフォルダ」以外のフォルダにメッセージの保存ができない

MQL5のプログラマーはこの手法をよく使うようです。処理が速く数量の多いログレコードには適しています。


ファイルへのログ書き込み

ロギングの最後の方法を考えます。 - ファイルへのメッセージ書き込みです。今までのものと比べるとこの手法はずっと複雑ですが、適切に準備をすると、確実に書き込み速度も速く、ログや通知も見やすくなっています。以下はファイルへのログ書き込みのもっともシンプルなコードです。

void OnStart()
  {
//--- Open log file
   int fileHandle=FileOpen("log.txt",FILE_WRITE|FILE_TXT|FILE_SHARE_READ|FILE_UNICODE); 
   FileWrite(fileHandle,"Start script");
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      FileWrite(fileHandle,"Variable i: ",i);
      // Sleep(1000);
     }
   FileWrite(fileHandle,intArray[9]);
   FileWrite(FileHandle,"Stop script");
   FileClose(fileHandle); // close log file
  }

実行し、"Terminal_dir\MQL5\Files"フォルダにブラウズし、テキストエディタの "log.txt"ファイルを開きます。以下がそのコンテンツです。

ファイルにログします。

見たとおり、アウトプットは当然の結果で余分なメッセージはなく、ファイルに書きこんだとおりのものです。このメリット、デメリットを考えます。

  1. + 速い
  2. + 書きたいことだけ書き出す
  3. + 異なるプログラムから別のファイルにメッセージを書くことができるので、ログの交点を排除する
  4. - ログに新規メッセージの通知がない
  5. - 特定メッセージまたはメッセージ分類を区別できない
  6. - ログを開くのに時間がかかる。まずフォルダ閲覧してファイルを開く必要がある

まとめ

上述の手法にはどれも欠点がありますが、修正できるものもあります。最初のロギングの3つは修正不可能です。そのふるまいに影響を与えることはできません。しかし、後の手法ファイルへのログの書き込みは一番修正がきくものです。いつどのようにメッセージを記録するか決めることができます。単独の数字を表示したいなら、もちろん最初の3つの手法を使えば簡単にできます。しかし、コードの多い複雑なプログラムの場合は、ロギングせずそれを使うのは難しいでしょう。


ロギングの新アプローチ

これから、ファイルへのロギング改善方法をお見せし、ログ閲覧の便利なツールを提供します。これはウィンドウズ用アプリケーションで、私がC++ 言語で書き、LogMonと呼んでいるものです。

まずクラスを書くことから始めます。それはすべてのロギングを行います。次のようなものです。

  1. ログおよびその他ログ設定を書くファイルロケーションを保持する
  2. 与えられた名前、日時に応じたログファイルを作成する
  3. 渡されたパラメータをログ行に変換する
  4. ログメッセージに時刻を追加する
  5. メッセージの色を追加する
  6. メッセージのカテゴリーを追加する
  7. メッセージをキャッシュし、n-秒ごとまたはn-メッセージごとに一回書く

MQL5はオブジェクトを基にした言語で、スピードはC++ 言語とたいして変わるところはありません。よってクラスは特にMQL5用に書きます。では始めましょう。


ファイルへのログ書き込みのクラス実装

ここでは個別のインクルードファイルに mqh 拡張子を伴ってクラスを入れます。以下が通常のクラスのストラクチャです。

CLogger

詳細コメント付クラスのソースコードです。

//+------------------------------------------------------------------+
//|                                                      Clogger.mqh |
//|                                                             ProF |
//|                                                          http:// |
//+------------------------------------------------------------------+
#property copyright "ProF"
#property link      "http://"

// Max size of cache (quantity)
#define MAX_CACHE_SIZE   10000
// Max file size in megabytes
#define MAX_FILE_SIZEMB 10
//+------------------------------------------------------------------+
//|   Logger                                                         |
//+------------------------------------------------------------------+
class CLogger
  {
private:
   string            project,file;             // Name of project and log file
   string            logCache[MAX_CACHE_SIZE]; // Cache max size
   int               sizeCache;                // Cache counter
   int               cacheTimeLimit;           // Caching time
   datetime          cacheTime;                // Time of cache last flush into file
   int               handleFile;               // Handle of log file
   string            defCategory;              // Default category
   void              writeLog(string log_msg); // Writing message into log or file, and flushing cache
public:
   void              CLogger(void){cacheTimeLimit=0; cacheTime=0; sizeCache=0;};    // Constructor
   void             ~CLogger(void){};                                               // Destructor
   void              SetSetting(string project,string file_name,
                                string default_category="",int cache_time_limit=0); // Settings
   void              init();                   // Initialization, open file for writing
   void              deinit();                 // Deinitialization, closing file
   void              write(string msg,string category="");                                         // Generating message
   void              write(string msg,string category,color colorOfMsg,string file="",int line=0); // Generating message
   void              write(string msg,string category,uchar red,uchar green,uchar blue,
                           string file="",int line=0);                                             // Generating message
   void              flush(void);              // Flushing cache into file

  };
//+------------------------------------------------------------------+
//|  Settings                                                        |
//+------------------------------------------------------------------+
void CLogger::SetSetting(string project_name,string file_name,
                        string default_category="",int cache_time_limit=0)
  {
   project=project_name;             // Project name
   file=file_name;                   // File name
   cacheTimeLimit=cache_time_limit;  // Caching time
   if(default_category=="")          // Setting default category
     {  defCategory="Comment";   }
     else
     {defCategory = default_category;}
  }
//+------------------------------------------------------------------+
//|  Initialization                                                  |
//+------------------------------------------------------------------+
void CLogger::init(void)
  {
   string path;
   MqlDateTime date;
   int i=0;
   TimeToStruct(TimeCurrent(),date);                            // Get current time
   StringConcatenate(path,"log\\log_",project,"\\log_",file,"_",
                     date.year,date.mon,date.day);              // Generate path and file name
   handleFile=FileOpen(path+".txt",FILE_WRITE|FILE_READ|
                       FILE_UNICODE|FILE_TXT|FILE_SHARE_READ);  // Open or create new file
   while(FileSize(handleFile)>(MAX_FILE_SIZEMB*1000000))        // Check file size
     {
      // Open or create new log file
      i++;
      FileClose(handleFile);
      handleFile=FileOpen(path+"_"+(string)i+".txt",
                          FILE_WRITE|FILE_READ|FILE_UNICODE|FILE_TXT|FILE_SHARE_READ);
     }
   FileSeek(handleFile,0,SEEK_END);                             // Set pointer to the end of file
  }
//+------------------------------------------------------------------+
//|   Deinitialization                                               |
//+------------------------------------------------------------------+
void CLogger::deinit(void)
  {
   FileClose(handleFile); // Close file
  }
//+------------------------------------------------------------------+
//|   Write message into file or cache                               |
//+------------------------------------------------------------------+
void CLogger::writeLog(string log_msg)
  {
   if(cacheTimeLimit!=0)  // Check if cache is enabled
     {
      if((sizeCache<MAX_CACHE_SIZE-1 && TimeCurrent()-cacheTime<cacheTimeLimit)
         || sizeCache==0) // Check if cache time is out or if cache limit is reached
        {
         // Write message into cache
         logCache[sizeCache++]=log_msg;
        }
      else
        {
         // Write message into cache and flush cache into file
         logCache[sizeCache++]=log_msg;
         flush();
        }

     }
   else
     {
      // Cache is disabled, immediately write into file
      FileWrite(handleFile,log_msg);
     }
   if(FileTell(handleFile)>(MAX_FILE_SIZEMB*1000000)) // Check current file size
     {
      // File size exceeds allowed limit, close current file and open new
      deinit();
      init();
     }
  }
//+------------------------------------------------------------------+
//|   Generate message and write into log                            |
//+------------------------------------------------------------------+
void CLogger::write(string msg,string category="")
  {
   string msg_log;
   if(category=="")                // Check if passed category exists
     {   category=defCategory;   } // Set default category

// Generate line and call method of writing message
   StringConcatenate(msg_log,category,":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",msg);
   writeLog(msg_log);
  }
//+------------------------------------------------------------------+
//|    Generate message and write into log                           |
//+------------------------------------------------------------------+
void CLogger::write(string msg,string category,color colorOfMsg,string file="",int line=0)
  {
   string msg_log;
   int red,green,blue;
   red=(colorOfMsg  &Red);           // Select red color from constant
   green=(colorOfMsg  &0x00FF00)>>8; // Select green color from constant
   blue=(colorOfMsg  &Blue)>>16;     // Select blue color from constant
                                     // Check if file or line are passed, generate line and call method of writing message
   if(file!="" && line!=0)
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",
                        "file: ",file,"   line: ",line,"   ",msg);
     }
   else
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",msg);
     }
   writeLog(msg_log);
  }
//+------------------------------------------------------------------+
//|    Generate message and write into log                           |
//+------------------------------------------------------------------+
void CLogger::write(string msg,string category,uchar red,uchar green,uchar blue,string file="",int line=0)
  {
   string msg_log;

// Check if file or line are passed, generate line and call method of writing message
   if(file!="" && line!=0)
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",
                        "file: ",file,"   line: ",line,"   ",msg);
     }
   else
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",msg);
     }
   writeLog(msg_log);
  }
//+------------------------------------------------------------------+
//|    Flush cache into file                                         |
//+------------------------------------------------------------------+
void CLogger::flush(void)
  {
   for(int i=0;i<sizeCache;i++) // In loop write all messages into file
     {
      FileWrite(handleFile,logCache[i]);
     }
   sizeCache=0; // Reset cache counter
   cacheTime=TimeCurrent(); // Set time of reseting cache
  }
//+------------------------------------------------------------------+

MetaEditorではインクルードファイル(.mqh)を作成し、クラスのソースコードをコピーし"CLogger.mqh"の名前で保存します。各手法とその適用方法についてもっとお話します。


CLoggerクラスの使用

このクラスを使いログにメッセージを記録し始めるには、Expert Advisor、インディケータ、スクリプトにクラスファイルをインクルードする必要があります。

#include <CLogger.mqh>

次に、このクラスのオブジェクトを作成します。

CLogger logger;

すべての処理は"logger"オブジェクトを用いて行います。ここで、"SetSetting()" メソッドを呼び設定を調整します。このメソッドには、プロジェクト名とファイル名を渡す必要があります。また、2つ選択可能なパラメータがあります。-初期設定のカテゴリ名とキャッシュがファイルに書き込まれる前に保管される間のキャッシュ ライフタイム(秒単位で)です。ゼロを指定したら、メッセージはすべて一度だけ書かれます。

SetSetting(string project,             // Project name
           string file_name,           // Log file name
           string default_category="", // Default category
           int cache_time_limit=0      // Cache lifetime in seconds
           );

コール例

logger.SetSetting("MyProject","myLog","Comment",60);

結果、メッセージは"Client_Terminal_dir\MQL5\Files\log\log_MyProject\log_myLog_date.txt"ファイルに書き込まれます。各設定カテゴリは"Comment"で、キャッシュライフタイムは60秒です。それから、ログファイルを開くまたは作成するのに init() メソッドを呼ぶ必要があります。コール例はシンプルです。パラメータを渡す必要がないからです。

logger.init();

このメソッドはパスとログファイル名を生成し、それを開き、最大サイズを超えていないか確認します。サイズが以前に設定した定数値を越えていたら、もうひとつ別のファイルが開き、その名前に 1 が連結されます。そしてふたたび、ファイルが正しいサイズで開かれるまでサイズがチェックされます。

その後、ポインタがファイルの終わりに移動されます。これでオブジェクトはログを書く準備完了です。書き込みメソッドをオーバーライドしました。このおかげで、メッセージの異なるストラクチャを設定することができます。以下は書き込みメソッドの呼び出しとファイル内結果の例です。

// Write message with default caegory
logger.write("Test message");
// Write message with "Errors" category
logger.write("Test message", "Errors");
// Write message with "Errors" category, that will be highlighted with red color in LogMon
logger.write("Test message", "Errors",Red);
// Write message with "Errors" category, that will be highlighted with red color in LogMon
// Also message will contain current file name and current line
logger.write("Test message", "Errors",Red,__FILE__,__LINE__);
// Write message with "Errors" category, that will be highlighted with GreenYellow color in LogMon
// But now we specify each color independently as: red, green, blue. 0-black, 255 - white
logger.write("Test message", "Errors",173,255,47);
// Write message with "Errors" category, that will be highlighted with GreenYellow color in LogMon
// But now we specify each color independently as: red, green, blue. 0-black, 255 - white
// Also message will contain current file name and current line
logger.write("Test message", "Errors",173,255,47,__FILE__,__LINE__);

ログファイルは以下の行で構成されます。

Comment:|:23:13:12    Test message
Errors:|:23:13:12    Test message
Errors:|:255,0,0:|:23:13:12    Test message
Errors:|:255,0,0:|:23:13:12    file: testLogger.mq5   line: 27   Test message
Errors:|:173,255,47:|:23:13:12    Test message
Errors:|:173,255,47:|:23:13:12    file: testLogger.mq5   line: 29   Test message

見てのとおり、すべてとてもシンプルです。どこででもよいので、要求されるパラメータを使ってwrite() メソッドを呼びます。そうするとメッセージがファイルに書き込まれます。プログラムの最後に2つのメソッド - flush() および deinit() の呼び出しを挿入する必要があります。

logger.flush();  // Forcibly flush cache to hard disk
logger.deinit(); // Close the log file

以下はログのループにある番号を書くスクリプトの簡単な例です。

//+------------------------------------------------------------------+
//|                                                   testLogger.mq5 |
//|                                                             ProF |
//|                                                          http:// |
//+------------------------------------------------------------------+
#property copyright "ProF"
#property link      "http://"
#property version   "1.00"
#include <Сlogger.mqh>
CLogger logger;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+

void OnStart()
  {
//---
   logger.SetSetting("proj","lfile");      // Settings
   logger.init();                          // Initialization
   logger.write("Start script","system");  
   for(int i=0;i<100000;i++)               // Write 100000 messages to the log
     {
      logger.write("log: "+(string)i,"Comment",100,222,100,__FILE__,__LINE__);
     }
   logger.write("Stop script","system"); 
   logger.flush();                         // Flush buffer
   logger.deinit();                        // Deinitialization
  }
//+------------------------------------------------------------------+

スクリプトは3秒で実行され、ファイルを2つ作成します。

ログファイル

ファイルコンテンツ

ログファイルコンテンツ

1000,00件のメッセージです。また、動作はきわめて速いです。このクラスは変更でき、新しい機能を加えたり、最適化したりできます。


メッセージ出力レベル

プログラムに書くように、いくつかのタイプのメッセージを表示する必要があります。

  1. 重要なエラー(プログラムが適切に動作しません)
  2. 需要でないエラーの通知、トレーディング処理、その他(プログラムは一時的エラーを経験します。またはプログラムは重要な処理をし、それはユーザーが知るべきものです。)
  3. デバッグ情報(配列および変数内容、実作業では不要なその他情報)

どのメッセージをソースコードの変更なしに印刷したいか調整できるとなお良いです。シンプル関数をめざし、クラスやメソッドは使いません。

メッセージ出力レベルを保存する変数パラメータを宣言します。変数の数字が大きいほど、メッセージカテゴリは多く表示されます。メッセージ出力を完全に無効にしたいなら、値"1"を割り当てます。

input int dLvl=2;

以下はCLoggerクラスのオブジェクトを作成してから宣言する関数のソースコードです。

void debug(string debugMsg,             // Message text
          int lvl        )              // Message level
{
   if (lvl<=dLvl)                       // Compare message level with level of messages output
   {
       if (lvl==0)                      // If message is critical (level = 0)
       {logger.write(debugMsg,"",Red);} // mark it with red color
       else
       {logger.write(debugMsg);}        // Else print it with default color
   }
}

一例です。最重要メッセージにレベル"0" を指定し、もっとも使わないものになにか数字(ゼロからの昇順で)を指定します。

debug("Error in Expert Advisor!",0);      // Critical error
debug("Stop-Loss execution",1);      // Notification
int i = 99;
debug("Variable i:"+(string)i,2); // Debugging information, variable contents


LogMonを使用した簡単なログ閲覧

さて、数千行にもおよぶログファイルがあります。そこに情報をみつけるのはかなり困難です。カテゴリに分けられてもいませんし、お互いを差別化もしていません。この問題を解決しようとしました。すなわち、CLoggerクラスで生成されたログ閲覧プログラムを書いたのです。これから簡単にLogMonプログラムについてお話します。それはWinAPIを使用し、 C++ 言語で書かれています。それで速度は速く、サイズは小さいのです。プログラムは全く自由です。

プログラムで作業するには以下が必要です。

  1. "Client_Terminal_dir\MQL5\Files\"フォルダにコピーし、実行します。- 通常モードで
  2. "Agents_dir\Agent\MQL5\Files\"フォルダにコピーし、実行します。- 検証または最適化で

プログラムのメインウィンドウは以下のようなものです。

LogMonメインウィンドウ

メインウィンドウにはツールバーとツリー表示のウィンドウが含まれます。項目を増やすには、それをマウスの左ボタンでダブルクリックします。リスト内フォルダ - "Client_Terminal_dir\MQL\Files\log\" フォルダにあるプロジェクト SetSetting() メソッドを使ってCLoggerクラスにプロジェクト名を設定します。フォルダリスト内のファイルは実際のログファイルです。ログファイル内のメッセージは write() メソッドで指定したカテゴリに分けられます。括弧内の数字は、そのカテゴリのメッセージ数です。

ツールバーのボタンを左から右に見ていきます。


プロジェクトまたはBログファイル削除ボタン。ツリー表示リセットも同様です。

このボタンを押すと、以下のウィンドウが表示されます。

削除、フラッシュ

「削除とフラッシュ」ボタンを押すと、ファイルまたはフォルダ スキャンのスレッドがすべて停止し、ツリー憑依がリセットされ、選択したファイルまたはプロジェクトの削除が促されます。(ただそれを選択するエレメントをクリックします。-チェックボックスを選択する必要はありません。)「リセット」ボタンはファイルまたはフォルダ スキャンのスレッドをすべて停止し、ツリー表示を消去します。


"About"ダイアログボックス表示ボタン

はプログラムと著者の簡単な情報を表示します。


プログラムウィンドウ表示ボタンは常に一番上

プログラムウィンドウは他のどんなウィンドウはよりも上に配置します。


ログファイルで新規メッセージの監視をアクティブにするボタン

このボタンはプログラムウィンドウをトレイに隠し、システムトレイログファイルの新規メッセージ監視をアクティブにします。プロジェクト/ファイルカテゴリ選択には、それがスキャンされます。必要なエレメントの隣にあるチェックボックスを選択します。

メッセージ カテゴリの隣にあるチェックボックスを選択したら、このプロジェクト/ファイルカテゴリ内の新規メッセージについて通知されます。ファイルの隣にあるチェックボックスを選択したら、このプロジェクト/ファイルカテゴリ内の新規メッセージについて通知されます。最後にプロジェクトの隣にあるチェックボックスを選択したら、このプロジェクト/ファイルカテゴリ内の新規メッセージについて通知がtriggerされます。


監視

監視をアクティブにすると、プログラムウィンドウがトレイに最小化されます。そして、選択したエレメントに新規メッセージが表示され、メインのアプリケーションウィンドウは通知音とともに最大化されます。通知を無効にするには、マウスの左ボタンでリストのどこかをクリックします。監視を停止するには、トレイのプログラムアイコンをクリックします。LogMonアイコン. ご自身で通知音を変更するには、 .wav ファイルを"alert.wav"名で、プログラム実行ファイルと同じフォルダに配置します。


ログカテゴリ閲覧

特定のカテゴリを閲覧するにはただそれをクリックします。するとメッセージボックスが表示されます。

LogMon検索

このウィンドウでは、メッセージ検索が可能です。ウィンドウは常に一番上に置き、自動スクロール切り替えをします。メッセージの色は CLogger クラスのwrite() メソッドにより個別に設定します。メッセージの背景は選択した色で強調表示します。

メッセージをダブルクリックすると、別ウィンドウを開きます。メッセージが長すぎてダイアログボックスに収まらない場合は便利です。

LogMonメッセージ

ログファイルを閲覧し監視する便利なツールが手に入りました。このプログラムが、MQL5を作成し使用されるとき、みなさんの助けとなることを願っています。


おわりに

プログラムのロギングイベントはたいへん便利なものです。潜んでいるエラーを特定し、プログラムを改善する機会を与えてくれます。本稿では、もっともシンプルにファイルにロギングするメソッドとプログラム、ログの監視と閲覧についてお話しました。

なにかコメントやご提案があればお寄せください!

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

添付されたファイル |
clogger.mqh (8.72 KB)
testlogger.mq5 (1.29 KB)
logmon_source_en.zip (119.02 KB)
logmonen.zip (88.78 KB)

この著者による他の記事

Simulink:Expert Advisors開発者へのガイド Simulink:Expert Advisors開発者へのガイド
私はプロのプログラマではありません。そのため、トレーディングシステムの作業をする際、「シンプルから複雑へ」という原則は私にとって重要なものです。では私にとってシンプルとは正確にどういうことを言うのでしょうか?まず、システム作成プロセスの視覚化、そしてその動作のロジックです。また、手書きコードを最小限に抑えることです。本稿では、Matlabパッケージを基にトレーディングシステムを作成し検証することを目指しています。そしてIMetaTrader 5用のExpert Advisorを書いていきます。検証段階ではMetaTrader 5からの履歴データを使用します。
自動トレーディングシステム選手権2010に向けたExpert Advisor迅速作成法 自動トレーディングシステム選手権2010に向けたExpert Advisor迅速作成法
自動トレーディングシステム選手権2010に参加するためのエクスパート開発をめざし、すぐに使えるExpert Advisorテンプレートを使用します。Even novice MQL5プログラマの初心者でもこのタスクをこなすことは可能です。というのも戦略のために基本クラス、関数、テンプレートがすでに準備されているからです。よってみなさんのトレーディングの考えに合う最低限のコードを書いて実装すれば十分です。
「新規バー」イベントハンドラ 「新規バー」イベントハンドラ
MQL5プログラミング言語はまったく新しいレベルで問題解決をする能力があります。 そういったタスクにして、もオブジェクト指向プログラミングのおかげでそれはすでに高いレベルに引きあげることができるのです。本稿では、かなり力強い多目的ツールに変換されたチャートの新規バーチェックの特にシンプルな例を取り上げます。どんなツールでしょうか?本稿でみつけてください。
Expert Advisor動作中のバランス曲線勾配調整 Expert Advisor動作中のバランス曲線勾配調整
トレードシステムのルールを見つけ、それをExpert Advisorにプログラムするのが仕事の半分です。Expert Advisorはトレーディング結果を集積するので、いくらかの処理を修正する必要があります。本項では、バランス曲線の勾配測定のフィードバックを作成することで、Expert Advisorのパフォーマンスを向上させる方法の一つについて述べます。