English Русский 中文 Español Deutsch Português
preview
プロのプログラマーからのヒント(第III部): ロギングSeqログ収集および分析システムへの接続

プロのプログラマーからのヒント(第III部): ロギングSeqログ収集および分析システムへの接続

MetaTrader 5統計と分析 | 30 5月 2022, 11:13
369 0
Malik Arykov
Malik Arykov

目次


はじめに

ロギングとは、アプリケーションの動作を分析するためのメッセージの出力です。MQL5のPrint関数とPrintFormat関数は、出力メッセージをエキスパート操作ログに保存します。エキスパート操作ログはUnicode形式のテキストファイルです。ログが上書きされるのを回避するために、新しいMQL5/Logs/yyyymmdd.logファイルが毎日作成されます。

開いているすべてのチャートのすべてのスクリプトとエキスパートアドバイザーは、1つのファイルに「ログを書き込みます」。ログの一部はディスクキャッシュに残ります。つまり、エクスプローラーからログファイルを開くと、キャッシュがあるため、最新の情報は表示されません。キャッシュをファイルに保存させるには、ターミナルを閉じるか、[エキスパート]タブのコンテキストメニューを使用して、その中の[開く]を選択します。これにより、ログファイルを含むディレクトリが開きます。

特にターミナル内では、これらのログを分析するのは簡単ではありませんが、そのような分析は非常に重要です。連載第I部では、ターミナルログの情報の検索、選択、表示を簡素化する方法の1つを示しましたが、この記事では、次の方法を紹介します。

  • ログ出力を統合する(Loggerクラス)
  • ログをSeqログ収集および分析システムに接続する
  • Seqでオンラインでメッセージ(イベント)を表示する
  • 通常のMetaTrader 5ログをSeqにインポートする(Pythonパッケージ)


Seq: ログを収集および分析するためのシステム

Seqは、アプリケーションログをリアルタイムで検索および分析するためのサーバーで、適切に設計されたユーザーインターフェイス、JSON形式のイベントストレージ、SQLクエリ言語により、複雑なアプリケーションやマイクロサービスの問題を識別および診断するための効果的なプラットフォームになっています。

Seqにメッセージを送信するには、次のことを行う必要があります。

  1. コンピュータにSeqをインストールします。
    インストール後、SeqUIは次の場所で利用できるようになります。
    http://localhost:5341/#/events
  2. 次の行をファイルc:/windows/system32/drivers/etc/hostsに追加します。
    127.0.0.1 seqlocal.net
    MetaTrader 5ターミナル設定にURLを追加できるようになります。
  3. Seqでタイムゾーンの使用を無効にして、メッセージ時間を「現状のまま」表示します。
    - UIシーケンスに移動します。
    - admin /Preferences/Preferencesに移動します。
    - [タイムスタンプをCoordinated Universal Time(UTC)で表示]を有効にします。
  4. 次のアドレスをMT5/Tools/Options/Expert Advisorsに追加します。
    http://seqlocal.net
    WebRequest関数がこのURLを使用できるようになります。
メッセージ(イベント)をオンラインで確認するには、[Tail]ボタンをクリックして、UIシーケンスからオンラインモードを有効にする必要があります。


Loggerクラス

考え方は次のように単純です。統一かつ構造化された情報を取得するには、同じ方法で形成して表示する必要があります。 この目的のために、完全に自律的なLoggerクラスを使用します。#includeファイルのような追加の依存関係はないので、クラスは「そのまま」使用できます。

// Message levels
#define LEV_DEBUG "DBG"   // debugging (for service use)
#define LEV_INFO "INF"    // information (to track the functions)
#define LEV_WARNING "WRN" // warning (attention)
#define LEV_ERROR "ERR"   // a non-critical error (check the log, work can be continued)
#define LEV_FATAL "FTL"   // fatal error (work cannot be continued)

メッセージレベルは、メッセージの重大度と緊急性の大まかな目安を示します。エキスパート操作ログでレベルを読みやすくして、レベルを強調表示して配置するために、3文字のプレフィックス(DBG、INF、WRN、ERR、 FTL)を使用します。 

  • DEB(UG) はプログラマーを対象としており、多くのロギングシステムでは、コンソールに出力されず、ファイルに保存されます。DEBUGメッセージは他のメッセージよりも頻繁に表示され、通常、パラメータ付きの関数の名前やその呼び出し結果が含まれています。
  • INF(O)はユーザーを対象としています。これらのメッセージは、DEBUGメッセージほど頻繁に表示されません。これらには、アプリケーションの操作に関する情報が含まれており、メニュー項目のクリック、トランザクション結果などのユーザーアクション、つまりユーザーが理解できるすべてのものである可能性があります。
  • WAR(NING)は、この情報に注意を払う必要があることを示します。例は、取引の開始または終了、未決注文のトリガーなどです。 
  • ERR(OR)は、重大ではないエラーが発生したが、アプリケーションが引き続き機能することを意味します。例ば、注文が拒否されたか実行されなかったために、注文の価格またはストップレベルが無効になった場合です。
  • FAT(AL)は重大なエラーを示し、その後、通常モードでのアプリケーションの動作は保証されません。緊急にアプリケーションを停止してエラーを修正する必要があります。

読みやすさとコード削減のために、メッセージは次のマクロ置換によって出力されます。

// Message output macros
#define LOG_SENDER gLog.SetSender(__FILE__, __FUNCTION__)
#define LOG_INFO(message) LOG_SENDER; gLog.Info(message)
#define LOG_DEBUG(message) LOG_SENDER; gLog.Debug(message)
#define LOG_WARNING(message) LOG_SENDER; gLog.Warning(message)
#define LOG_ERROR(message) LOG_SENDER; gLog.Error(message)
#define LOG_FATAL(message) LOG_SENDER; gLog.Fatal(message)

したがって、各メッセージには、ファイルまたはモジュールの名前、関数の名前、メッセージ自体が表示されます。メッセージを作成するには、PrintFormat関数を使用することをお勧めします。各値は「/」で区切ることが望ましいです。この手法により、すべてのメッセージが統一され、構造化されます。

演算子の例

LOG_INFO(m_result);
LOG_INFO(StringFormat("%s / %s / %s", StringSubstr(EnumToString(m_type), 3), 
    TimeToString(m_time0Bar), m_result));

エキスパートログに出力されるオペレーターデータ

Time                     Source              Message
---------------------------------------------------------------------------------------------------------------------
2022.02.16 13:00:06.079  Cayman (GBPUSD,H1)  INF: AnalyserRollback::Run Rollback, H1, 12:00, R1, D1, RO, order 275667165
2022.02.16 13:00:06.080  Cayman (GBPUSD,H1)  INF: Analyser::SaveParameters Rollback / 2022.02.16 12:00 / Rollback, H1, 12:00, R1, D1, RO, order 275667165

MetaTrader 5で出力されるメッセージの特定の特徴は、Time列ではTimeLocalが指定され、情報が実際にはサーバー時間TimeCurrentに属するということです。したがって、時間を強調する必要がある場合は、メッセージ自体で時間を指定する必要があります。これは2番目のメッセージに示されています。ここで、13:00は現地時間、12: 00はサーバー時間(実際のバーの営業時間)です。

Loggerクラスの構造は次のとおりです。

class Logger {
private:
    string  m_module;  // module or file name
    string  m_sender;  // function name
    string  m_level;   // message level
    string  m_message; // message text
    string  m_urlSeq;  // url of the Seq message service
    string  m_appName; // application name for Seq
    // private methods
    void Log(string level, string message);
    string TimeToStr(datetime value);
    string PeriodToStr(ENUM_TIMEFRAMES value);
    string Quote(string value);
    string Level();
    void SendToSeq();
public:
    Logger(string appName, string urlSeq);
    void SetSender(string module, string sender);
    void Debug(string message) { Log(LEV_DEBUG, message); };
    void Info(string message) { Log(LEV_INFO, message); };
    void Warning(string message) { Log(LEV_WARNING, message); };
    void Error(string message) { Log(LEV_ERROR, message); };
    void Fatal(string message) { Log(LEV_FATAL, message); };
};

extern Logger *gLog; // logger instance

すべてが簡潔で読みやすく、不要な詳細が含まれていません。gLogロガーインスタンスの宣言に注意してください。同じタイプと識別子を持つ「extern」として宣言された変数は、同じプロジェクトの異なるソースファイルに存在する可能性があります。外部変数は初期化できますが、一度だけです。したがって、プロジェクトファイルにロガーを作成した後、gLog変数は同じオブジェクトを指します。

// -----------------------------------------------------------------------------
// Constructor
// -----------------------------------------------------------------------------
Logger::Logger(string appName, string urlSeq = "") {
    m_appName = appName;
    m_urlSeq = urlSeq;
}

ロガーコンストラクタは、次の2つのパラメータを受け取ります。

  • appName - Seqのアプリケーション名。Seqシステムは、オンラインモードでさまざまなアプリケーションからログを受信できるので、メッセージをフィルタリングするためにappNameが使用されます。
  • urlSeq - SeqサービスのURL。特定のポート(http://localhost:5341/#/events)でリッスンしているローカルサイトにすることができます。

urlSeqパラメータはオプションです。指定しない場合、メッセージはエキスパートログにのみ出力されます。urlSeqが定義されている場合、イベントはWebRequestを介してSeqサービスに追加で送信されます。

// -----------------------------------------------------------------------------
// Set the message sender          
// -----------------------------------------------------------------------------
void Logger::SetSender(string module, string sender) { 
    m_module = module; // module or file name
    m_sender = sender; // function name
    StringReplace(m_module, ".mq5", "");
}

SetSender関数は、2つの必須パラメータを取得し、メッセージの送信者を設定します。「.mq5」ファイル拡張子がモジュール名から削除されます。ロギング演算子LOG_LEVELがクラスメソッドで使用されている場合、クラス名が関数名に追加されます(例: TestClass::TestFunc)。

// -----------------------------------------------------------------------------
// Convert time to the ISO8601 format for Seq
// -----------------------------------------------------------------------------
string Logger::TimeToStr(datetime value) {
    MqlDateTime mdt;
    TimeToStruct(value, mdt);
    ulong msec = GetTickCount64() % 1000; // for comparison
    return StringFormat("%4i-%02i-%02iT%02i:%02i:%02i.%03iZ", 
        mdt.year, mdt.mon, mdt.day, mdt.hour, mdt.min, mdt.sec, msec);
}

Seqの時間タイプは、ISO8601形式(YYYY-MM-DDThh:mm:ss[.SSS])である必要があります。MQL5の日時タイプは、最大1秒まで計算されます。Seqの時間は、最大1ミリ秒で表されます。したがって、システムの起動(GetTickCount64)から経過したミリ秒数は、指定された時間に強制的に追加されます。このメソッドでは、メッセージの時間を相互に比較できます。

// -----------------------------------------------------------------------------
// Convert period to string
// -----------------------------------------------------------------------------
string Logger::PeriodToStr(ENUM_TIMEFRAMES value) {
    return StringSubstr(EnumToString(value), 7);
}

期間はシンボリック形式でSeqに渡されます。任意の期間のシンボリック表現には、「PERIOD_」というプレフィックスが付いています。したがって、期間を文字列に変換する場合、プレフィックスは単純に切り捨てられます。たとえば、PERIOD_H1は「H1」に変換されます。

SendToSeq関数は、メッセージ (イベントを登録するため)をSeqに送信するために使用されます。

// -----------------------------------------------------------------------------
// Send message to Seq via http
// -----------------------------------------------------------------------------
void Logger::SendToSeq() {

    // replace illegal characters
    StringReplace(m_message, "\n", " ");
    StringReplace(m_message, "\t", " ");
    
    // prepare a string in the CLEF (Compact Logging Event Format) format
    string speriod = PeriodToStr(_Period);
    string extended_message = StringFormat("%s, %s / %s / %s / %s",
        _Symbol, speriod, m_module, m_sender, m_message);
    string clef = "{" +
        "\"@t\":" + Quote(TimeToStr(TimeCurrent())) + // event time
        ",\"AppName\":" + Quote(m_appName) +          // application name (Cayman)
        ",\"Symbol\":" + Quote(_Symbol) +             // symbol (EURUSD)
        ",\"Period\":" + Quote(speriod) +             // period (H4)
        ",\"Module\":" + Quote(m_module) +            // module name (__FILE__)
        ",\"Sender\":" + Quote(m_sender) +            // sender name (__FUNCTION__)
        ",\"Level\":" + Quote(m_level) +              // level abbreviation (INF)
        ",\"@l\":" + Quote(Level()) +                 // level details (Information)
        ",\"Message\":" + Quote(m_message) +          // message without additional info
        ",\"@m\":" + Quote(extended_message) +        // message with additional info
    "}";

    // prepare data for POST request
    char data[]; // HTTP message body data array
    char result[]; // Web service response data array
    string answer; // Web service response headers
    string headers = "Content-Type: application/vnd.serilog.clef\r\n";
    ArrayResize(data, StringToCharArray(clef, data, 0, WHOLE_ARRAY, CP_UTF8) - 1);

    // send message to Seq via http
    ResetLastError();
    int rcode = WebRequest("POST", m_urlSeq, headers, 3000, data, result, answer);
    if (rcode > 201) {
        PrintFormat("%s / rcode=%i / url=%s / answer=%s / %s", __FUNCTION__, 
            rcode, m_urlSeq, answer, CharArrayToString(result));
    }
}

まず、新しい行とタブがスペースに置き換えられます。次に、メッセージパラメータを"key": "value"のペアとして持つJSONレコードが形成されます。@プレフィックスが付いたパラメータは必須(サービス)です。残りはユーザー定義で、名前とその番号はプログラマーが決定します。パラメータとその値は、SQLクエリで使用できます。

メッセージ時間@t = TimeCurrent()に注意してください。ターミナルとは対照的に、サーバー時間を修正しますが、ローカルは修正しません(TimeLocal())。次に、リクエスト本文が作成され、WebRequestを介してSeqサービスに送信されます。

// -----------------------------------------------------------------------------
// Write a message to log
// -----------------------------------------------------------------------------
void Logger::Log(string level, string message) {
    
    m_level = level;
    m_message = message;
    
    // output a message to the expert log (Toolbox/Experts)
    PrintFormat("%s: %s %s", m_level, m_sender, m_message);
    
    // if a URL is defined, then send a message to Seq via http
    if (m_urlSeq != "") SendToSeq();
}

この関数には、メッセージの重大度レベルとメッセージ文字列の2つの必須パラメータがあります。メッセージはエキスパート操作ログに出力されます。レベルの後にはコロン文字が続きます。これは、Notepad ++が線を強調表示するために特別に行われました(WRN: -黄色に黒、ERR: -赤に黄色)。


Loggerクラスのテスト

TestLogger.mq5スクリプトは、クラスをテストするために使用されます。ロギングマクロはさまざまな機能で使用されます。 

#include <Cayman/Logger.mqh>

class TestClass {
    int m_id;
public:
    TestClass(int id) { 
        m_id = id;
        LOG_DEBUG(StringFormat("create object with id = %i", id));
    };
};

void TestFunc() {
    LOG_INFO("info message from inner function");
}

void OnStart() {

    string urlSeq = "http://seqlocal.net:5341/api/events/raw?clef";
    gLog = new Logger("TestLogger", urlSeq);
    
    LOG_DEBUG("debug message");
    LOG_INFO("info message");
    LOG_WARNING("warning message");
    LOG_ERROR("error message");
    LOG_FATAL("fatal message");
    
    // call function
    TestFunc();
    
    // create object
    TestClass *testObj = new TestClass(101);

    // free memory
    delete testObj;
    delete gLog;
}

エキスパートログのメッセージの表示。メッセージには、レベルとメッセージ送信者(所有者)が明確に示されます。

2022.02.16 20:17:21.048 TestLogger (USDJPY,H1)  DBG: OnStart debug message
2022.02.16 20:17:21.291 TestLogger (USDJPY,H1)  INF: OnStart info message
2022.02.16 20:17:21.299 TestLogger (USDJPY,H1)  WRN: OnStart warning message
2022.02.16 20:17:21.303 TestLogger (USDJPY,H1)  ERR: OnStart error message
2022.02.16 20:17:21.323 TestLogger (USDJPY,H1)  FTL: OnStart fatal message
2022.02.16 20:17:21.328 TestLogger (USDJPY,H1)  INF: TestFunc info message from inner function
2022.02.16 20:17:21.332 TestLogger (USDJPY,H1)  DBG: TestClass::TestClass create object with id = 101

Notepad++エディターでのメッセージの表示

Notepad++でのメッセージの表示


Seqでのメッセージの表示

Seqでのメッセージの表示


MetaTrader5ログをSeqにインポートする

ログをSeqにインポートするために、Pythonでseq2logパッケージを作成しました。この記事では説明しません。パッケージには、README.mdファイルが含まれています。コードには詳細なコメントが含まれています。seq2logパッケージは、エキスパート操作ログMQL5/Logs/yyyymmdd.logから任意のログをインポートします。重要度レベルのないメッセージには、INFレベルが割り当てられます。

seq2logはどこで使用できるのでしょうか。たとえば、フリーランスの開発者は、クライアントにエキスパートログを送信するように依頼できます。テキストエディターでログを分析することは可能ですが、SeqではSQLクエリを使用する方が便利です。最も頻繁に使用されるクエリまたは複雑なクエリをSeqに保存し、クエリ名を1回クリックするだけで実行できます。

    Run: py log2seq appName pathLog
    where log2seq - package name
        appName - application name to identify events in Seq
        pathLog - MetaTrader 5 log path
    Example: py log2seq Cayman d:/Project/MQL5/Logs/20211028.log


終わりに

この記事では、Loggerクラスとその使用方法について説明します。

  • 重大度レベルの構造化メッセージをログに記録する
  • Seqログ収集および分析システムにメッセージ(イベント)を登録する

Loggerクラスのソースコードとそのテストが添付されています。さらに、添付ファイルには、既存のMetaTrader 5ログをSeqにインポートするために使用されるlog2seqパッケージのソースPythonコードが含まれています。

Seqサービスを使用すると、専門家レベルでログを分析できます。Seqサービスでは優れたデータサンプリングおよび視覚化機能が提供されてます。さらに、Loggerクラスのソースコードを使用すると、Seqで図を描画するために、視覚化のために特別に設計されたデータをログメッセージに追加できます。これにより、アプリケーションログのデバッグ情報を確認することをお勧めします。実際に適用してみてください。ご健闘をお祈りします。


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

添付されたファイル |
log2seq.zip (8.32 KB)
Logger.mqh (15.58 KB)
TestLogger.mq5 (2.83 KB)
エンベロープによる取引システムの設計方法を学ぶ エンベロープによる取引システムの設計方法を学ぶ
この記事では、バンドで取引する方法の1つを紹介します。今回はエンベロープについて検討し、それに基づいてストラテジーを作成するのがいかに簡単であるかを見ていきます。
DirectXチュートリアル(第I部): 最初の三角形の描画 DirectXチュートリアル(第I部): 最初の三角形の描画
これはDirectXの紹介記事で、APIを使用した操作の詳細について説明しており、コンポーネントが初期化される順序を理解するのに役立つはずです。この記事には、DirectXを使用して三角形をレンダリングするためのMQL5スクリプトを作成する方法の例が含まれています。
移動平均でできること 移動平均でできること
この記事では、移動平均指標を適用するいくつかの方法について考察しています。曲線分析が含まれるそれぞれの方法には、アイデアを視覚化する指標が付属しています。ほとんどの場合、ここで紹介されているアイデアは、その尊敬すべき著者に帰属しています。私の唯一の仕事は、それらをまとめて、主要なアプローチを確認し、うまくいけば、より合理的な取引決定を下せるようにすることでした。この記事は、MQL5の初心者向けです。
ボリンジャーバンドによる取引システムの設計方法を学ぶ ボリンジャーバンドによる取引システムの設計方法を学ぶ
この記事では、取引の世界で最も人気のある指標の1つであるボリンジャーバンドについて学びます。テクニカル分析を検討し、ボリンジャーバンド指標に基づいてアルゴリズム取引システムを設計する方法を確認します。