English Русский Deutsch 日本語
preview
精通日志记录(第七部分):如何在图表上显示日志

精通日志记录(第七部分):如何在图表上显示日志

MetaTrader 5示例 |
224 0
joaopedrodev
joaopedrodev

引言

说实话,开发进入心流状态时做出的某些东西,我原本根本没打算写出来。它们通常是灵光一现的结果,是为了解决某个具体痛点而打磨出的细节。没错,本文正是这样一个案例。我甚至曾想过:“不,这太简单了,根本不值得分享……”。但它的实用性和体验之好远超预期,如果藏着掖着,简直是一种罪过。

如果您读到了这里,您可能已经听说过Logify。这是一套专为MQL5 EA开发量身定制的完整日志管理库。它的初衷是打破MT5原生日志系统的局限,赋予开发者更强的掌控力、条理性和专业感。

在本系列的第一篇文章精通日志记录(第一部分):MQL5 中的基本概念与入门中,我们迈出了构建该库的第一步。我们谈讨了基础理论,剖析了为什么盲从MT5的标准日志等于自讨苦吃,并着手打造一个健壮、可定制且易扩展的解决方案。

正是在这个过程中,我偶然萌生了一个原本不在路线图上的想法。在我自己使用这个库时,我逐渐意识到,在终端里大海捞针般地寻找日志、打开“专家”选项卡、在繁杂的信息中过滤消息。更糟的是,关键报错可能一闪而过,让我们错失排错的良机。这就像经典的“大海捞针”……只不过这次,草堆还着火了。

那一刻我恍然大悟:“要是日志能出现在它们该出现的地方呢?就在图表上,在交易者眼皮底下,在机器人‘活’着的地方。”注意,我说的是摒弃那些散乱的标签、闪烁的箭头,或者只会让界面更乱的图形对象。我指的是一种更加优雅、低调且功能强大的方案:利用老牌的 Comment() 函数。

没错,就是那个大多数人都会刻意忽略、只用来调试变量、调试完就删除的函数。但只要稍加改造,它就能摇身一变,成为简洁、易读、实时刷新且极具价值的日志控制台。

为了避免听起来像在推销,请直接看看它的实际效果:

老实说,我本没打算写这篇文章。这原本只是一个额外的功能,几乎算是个人的突发奇想。但它的效果太好了,既实用又令人满意,以至于把它藏在代码库里实在说不过去。如果您喜欢这种简单、智能且能真正解决问题的方案,请继续读下去。从今天起,您再也不会用老眼光看待 Comment() 函数了。让我们把您的图表变成一个日志控制台吧。


创建新的处理程序

既然明白了在图表上优雅展示日志的用意,那就动手吧。我们的目标是在 Logify 库中创建一个新的专用处理程序,负责捕获日志并使用 Comment() 函数将其直接显示在图表上。

我们将在库的 handlers 文件夹下创建一个名为 LogifyHandlerComment.mqh 的新文件。所有将传统日志转换为图表动态显示的逻辑都将集中在这里,它就像是一个附着在您机器人上的控制台。完成这一步后,您的库文件结构应如下所示:

在这个文件中,我们将声明一个名为 CLogifyHandlerComment 的新类。与 Logify 的其他处理程序一样,它继承自基类 CLogifyHandler 。这保持了架构的一致性、模块化,并符合我们从第一篇文章开始就建立的规范。

第一步(老规矩)是定义模块的基本属性:叫什么,干什么。这始于类的构造函数,我们将其中名称定义为 "comment",这正是库内用于激活此类日志输出的标识符。

以下是我们类的初始骨架,所有核心方法 Emit()、Flush() 和 Close() 均已声明,等待后续实现:

//+------------------------------------------------------------------+
//|                                         LogifyHandlerComment.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "joaopedrodev"
#property link      "https://www.mql5.com/en/users/joaopedrodev"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "LogifyHandler.mqh"
//+------------------------------------------------------------------+
//| class : CLogifyHandlerComment                                    |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CLogifyHandlerComment                              |
//| Heritage    : CLogifyHandler                                     |
//| Description : Log handler, inserts data into chart comment.      |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogifyHandlerComment : public CLogifyHandler
  {
public:
                     CLogifyHandlerComment(void);
                    ~CLogifyHandlerComment(void);
   
   virtual void      Emit(MqlLogifyModel &data);         // Processes a log message and sends it to the specified destination
   virtual void      Flush(void);                        // Clears or completes any pending operations
   virtual void      Close(void);                        // Closes the handler and releases any resources
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLogifyHandlerComment::CLogifyHandlerComment(void)
  {
   m_name = "comment";
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CLogifyHandlerComment::~CLogifyHandlerComment(void)
  {
  }
//+------------------------------------------------------------------+
//| Processes a log message and sends it to the specified destination|
//+------------------------------------------------------------------+
void CLogifyHandlerComment::Emit(MqlLogifyModel &data)
  {
  }
//+------------------------------------------------------------------+
//| Clears or completes any pending operations                       |
//+------------------------------------------------------------------+
void CLogifyHandlerComment::Flush(void)
  {
  }
//+------------------------------------------------------------------+
//| Closes the handler and releases any resources                    |
//+------------------------------------------------------------------+
void CLogifyHandlerComment::Close(void)
  {
  }
//+------------------------------------------------------------------+



规划相关设置:大小、边框和标题

在直接实现主函数之前,我们需要解决任何重视灵活性的系统都必须面对的一个基本问题:如何配置它?好消息是,选项非常简单,且完全符合 Logify 的设计理念。没有晦涩难懂的配置,也没有让人摸不着头脑的参数。这里的重点在于图形控制和布局。

我们的可视化日志处理程序将提供四个主要配置参数:

  • size –定义希望在图表上看到的日志行数。换句话说,就是可见消息窗口的大小。
  • frame_style – 图表上日志周围边框的样式。您可以选择:none(无边框,简洁直接)、single(单线边框)或 double(双线边框)。
  • direction – 消息的显示方向:从上到下,或从下到上。
  • title – 显示在框架顶部的标题。您可以在此处添加 EA 的名称。

基于这些参数,我们创建了一个名为 MqlLogifyHandleCommentConfig 的结构体。它封装了所有配置,并在 CLogifyHandlerComment 类内部使用。以下是该配置的核心代码:

//+------------------------------------------------------------------+
//| ENUMS                                                            |
//+------------------------------------------------------------------+
enum ENUM_LOG_FRAME_STYLE
  {
   LOG_FRAME_STYLE_NONE = 0,           // No rotation
   LOG_FRAME_STYLE_SINGLE,             // Rotate based on date
   LOG_FRAME_STYLE_DOUBLE,             // Rotate based on file size
  };
enum ENUM_LOG_DIRECTION
  {
   LOG_DIRECTION_UP = 0,               // Up
   LOG_DIRECTION_DOWN,                 // Down
  };
//+------------------------------------------------------------------+
//| Struct: MqlLogifyHandleComment                                   |
//+------------------------------------------------------------------+
struct MqlLogifyHandleCommentConfig
  {
   int size;                           // Space in lines that it will occupy
   ENUM_LOG_FRAME_STYLE frame_style;   // Display grid
   ENUM_LOG_DIRECTION direction;       // Direction
   string title;                       // log title
   
   //--- Default constructor
   MqlLogifyHandleCommentConfig(void)
     {
      size = 20;
      frame_style = LOG_FRAME_STYLE_SINGLE;
      direction = LOG_DIRECTION_UP;
      title = "LOGIFY";
     }
   
   //--- Destructor
   ~MqlLogifyHandleCommentConfig(void)
     {
     }

   //--- Validate configuration
   bool ValidateConfig(string &error_message)
     {
      //--- Saves the return value
      bool is_valid = true;
      
      //--- Check if size is greater than 0
      if(size <= 0)
        {
         size = 20;
         error_message = "Size must be greater than 0.";
         is_valid = false;
        }
      
      //--- Check len
      if(StringLen(title) > 40)
        {
         error_message = "Title is too long for frame. Max 40 chars.";
         is_valid = false;
        }
      
      //--- No errors found
      return(is_valid);
     }
  };

接着,CLogifyHandlerComment 类将此配置作为私有属性 (m_config)。它还暴露了两个必不可少的方法来操作配置:SetConfig() 用于验证并设置配置,GetConfig() 用于返回当前配置。

class CLogifyHandlerComment : public CLogifyHandler
  {
private:
   
   MqlLogifyHandleCommentConfig m_config;
   
public:
                     CLogifyHandlerComment(void);
                    ~CLogifyHandlerComment(void);
   
   //--- Configuration management
   void              SetConfig(MqlLogifyHandleCommentConfig &config);
   MqlLogifyHandleCommentConfig GetConfig(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLogifyHandlerComment::CLogifyHandlerComment(void)
  {
   m_name = "comment";
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CLogifyHandlerComment::~CLogifyHandlerComment(void)
  {
  }
//+------------------------------------------------------------------+
//| Set configuration                                                |
//+------------------------------------------------------------------+
void CLogifyHandlerComment::SetConfig(MqlLogifyHandleCommentConfig &config)
  {
   m_config = config;
   
   //--- Validade config
   string err_msg = "";
   if(!m_config.ValidateConfig(err_msg))
     {
      Print("[ERROR] ["+TimeToString(TimeCurrent())+"] Log system error: "+err_msg);
     }
   
   //--- Resize
   ArrayResize(m_logs, m_config.size);
  }
//+------------------------------------------------------------------+
//| Get configuration                                                |
//+------------------------------------------------------------------+
MqlLogifyHandleCommentConfig CLogifyHandlerComment::GetConfig(void)
  {
   return(m_config);
  }
//+------------------------------------------------------------------+


实现“瀑布”式位移

为什么我们需要这个“瀑布”式位移?很简单。如果我们希望日志在图表上显示时,最新的消息位于底部,而旧的消息逐渐向上移动直至消失,我们就需要一个表现得像动态队列的结构。如果没有这个机制,每条新日志要么直接覆盖前一条,要么无限累积,把您的图表搞得一团糟。

想象一下:图表上的可用空间不是无限的。视觉上,只能容纳比如 10 行文本,再多就会重叠或超出屏幕。因此,我们需要确保每当新消息出现时,它占据队列的首位,将其他消息向上推。如果已经达到限制(例如 10 行),最旧的那条就直接被丢弃。

这种行为在许多应用程序中都很经典,在计算机科学中被称为“循环缓冲区”、“滑动队列”,或者更技术性地说法是“在线性数组中移动元素”。在这里,为了简化,我们称之为“瀑布”式位移,因为信息确实像水流一样,一行接一行地流动。

这实际上是如何运作的?逻辑出奇地简单,但极其高效:

  1. 我们维护一个名为 m_logs[] 的数组,作为我们的“可视化控制台”。该数组大小固定,例如 10 个元素,即图表上的 10 行。
  2. 每当有新的日志消息到达时,它需要出现在列表的最前面(数组的位置 0)。
  3. 为此,我们移动现有元素:位置 8 的移到 9,位置 7 的移到 8,依此类推……直到位置 0 的被推到位置 1。 
  4. 完成后,位置 0 空出,我们在那里插入全新的消息,让它闪耀在屏幕顶端。

视觉上,这就像多米诺骨牌效应。每条新消息将前一条向下推一个位置。空间耗尽时,队尾的那条就直接掉落,从屏幕和内存里彻底消失。简单、清爽且高效。

如果没有这个机制,每条新日志都会直接在 Comment() 中覆盖前一条;或者反过来,我们会不受控制地累积行数,污染图表。这两种情况都不可取。因此,“瀑布”式位移不仅仅是美观问题,更是确保图表日志实用性和可读性的必要功能。

既然您已经彻底理解了“瀑布”式位移的工作原理,让我们通过 Emit() 方法将其付诸实践。每当需要处理并显示新的日志消息时,就会调用此方法。它所做的正是我们讨论的内容:应用“瀑布”式位移,组合所有格式化的文本(包括边框、标题和对齐),最后使用原生的 Comment() 函数将其输出到图表上。

为了避免代码杂乱,我们将逻辑拆分为几个辅助函数,分别负责组装边框、标题和行内容。

该过程由三个简单步骤组成:

  1. 级别过滤:如果消息未达到处理程序配置的日志级别,则将其丢弃。
  2. “瀑布”式位移:将 m_logs[] 数组中的现有日志向前移动一个位置,为新消息腾出零号位。(遵循级联移位逻辑)
  3. 注释合成:使用辅助函数生成:
  • 头部:顶部边框和标题(如果有)。
  • 主体:日志列表,根据配置的方向排序。
  • 尾部:底部边框(如果已配置)。

结果使用 MQL5 自带的 Comment() 显示。为此,我们使用了一些辅助函数:

  • GetSideBorder() → 返回侧边框字符:
    • │ 代表单线边框。
    • ║ 代表双线边框。
    • "" (空) 代表无边框。
  • GetBorderTop() → 返回顶部边框线:
    • 例如:┌───────┐ 或 ╔═══════╗
  • GetBorderMiddle() → 返回标题下方的分隔线:
    • 例如:├───────┤ 或 ╠═══════╣ 
  • GetBorderBottom() → 返回底部边框线:
    • 例如:└──────┘ 或 ╚═══════╝ 
  • BuildHeader() → 组装包含标题(如果配置了)和边框的头部。
  • BuildFooter() → 仅生成边框的尾部。
  • FormatLogLines() → 格式化所有日志行:
    • 应用侧边框(如果有)。
    • 方向配置(LOG_DIRECTION_UP 或 LOG_DIRECTION_DOWN)。

以下是完整代码:

//+------------------------------------------------------------------+
//| Processes a log message and sends it to the specified destination|
//+------------------------------------------------------------------+
void CLogifyHandlerComment::Emit(MqlLogifyModel &data)
  {
   //--- Check if log level is allowed
   if(data.level < this.GetLevel())
     {
      return;
     }

   //--- Shift logs to maintain history
   for(int i = m_config.size-1; i > 0; i--)
     {
      m_logs[i] = m_logs[i-1];
     }
   m_logs[0] = data;

   //--- Build the complete comment
   string comment = BuildHeader();
   comment += FormatLogLines();
   comment += BuildFooter();

   //--- Display on chart
   Comment(comment);
  }
//+------------------------------------------------------------------+
//| Returns the side border character based on frame style          |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::GetSideBorder()
  {
   if(m_config.frame_style == LOG_FRAME_STYLE_SINGLE) return "│";
   if(m_config.frame_style == LOG_FRAME_STYLE_DOUBLE) return "║";
   return "";
  }
//+------------------------------------------------------------------+
//| Returns the top border based on frame style                     |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::GetBorderTop()
  {
   if(m_config.frame_style == LOG_FRAME_STYLE_SINGLE)
      return "┌───────────────────────────────────────────┐\n";
   if(m_config.frame_style == LOG_FRAME_STYLE_DOUBLE)
      return "╔═══════════════════════════════════════════╗\n";
   return "";
  }
//+------------------------------------------------------------------+
//| Returns the middle separator based on frame style               |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::GetBorderMiddle()
  {
   if(m_config.frame_style == LOG_FRAME_STYLE_SINGLE)
      return "├───────────────────────────────────────────┤\n";
   if(m_config.frame_style == LOG_FRAME_STYLE_DOUBLE)
      return "╠═══════════════════════════════════════════╣\n";
   return "";
  }
//+------------------------------------------------------------------+
//| Returns the bottom border based on frame style                  |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::GetBorderBottom()
  {
   if(m_config.frame_style == LOG_FRAME_STYLE_SINGLE)
      return "└───────────────────────────────────────────┘\n";
   if(m_config.frame_style == LOG_FRAME_STYLE_DOUBLE)
      return "╚═══════════════════════════════════════════╝\n";
   return "";
  }
//+------------------------------------------------------------------+
//| Builds the comment header with optional title and frame         |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::BuildHeader()
  {
   string header = "";

   if(m_config.title != "" && m_config.title != NULL)
     {
      if(m_config.frame_style == LOG_FRAME_STYLE_NONE)
        {
         header += " " + m_config.title + "\n";
         header += "─────────────────────────────────────────────\n";
        }
      else
        {
         header += GetBorderTop();
         header += GetSideBorder() + " " + m_config.title + "\n";
         header += GetBorderMiddle();
        }
     }
   else
     {
      if(m_config.frame_style != LOG_FRAME_STYLE_NONE)
        {
         header += GetBorderTop();
        }
     }

   return header;
  }
//+------------------------------------------------------------------+
//| Builds the comment footer based on frame style                  |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::BuildFooter()
  {
   if(m_config.frame_style != LOG_FRAME_STYLE_NONE)
      return GetBorderBottom();
   return "";
  }
//+------------------------------------------------------------------+
//| Formats all log lines according to direction and frame          |
//+------------------------------------------------------------------+
string CLogifyHandlerComment::FormatLogLines()
  {
   string result = "";
   string side = GetSideBorder();

   if(m_config.direction == LOG_DIRECTION_UP)
     {
      for(int i = m_config.size-1; i >= 0; i--)
        {
         string line = m_logs[i].formated;
         if(line != "")
           {
            result += side + " " + line + "\n";
           }
         else
           {
            result += side + "\n";
           }
        }
     }
   else // LOG_DIRECTION_DOWN
     {
      for(int i = 0; i <= m_config.size-1; i++)
        {
         string line = m_logs[i].formated;
         if(line != "")
           {
            result += side + " " + line + "\n";
           }
         else
           {
            result += side + "\n";
           }
        }
     }

   return result;
  }
//+------------------------------------------------------------------+



结束时清除日志

为了完成 CLogifyHandlerComment 类的实现,还缺少一个基本但极必要的细节:当处理程序关闭时,清除图表上绘制的内容。最后,如果 Emit() 函数负责在屏幕上显示消息,那么 Close() 方法的使命恰恰相反:删除所有内容。

您想知道最棒的部分吗?它简单得可笑。与其他可能需要关闭文件、连接或释放内存的处理程序不同,这里我们的工作只是从图表中移除注释。MetaTrader 对此非常直接:只需调用不带任何参数的 Comment() 函数,即 Comment("")。

//+------------------------------------------------------------------+
//| Closes the handler and releases any resources                    |
//+------------------------------------------------------------------+
void CLogifyHandlerComment::Close(void)
  {
   //--- Clear
   Comment("");
  }
//+------------------------------------------------------------------+


实战测试 CLogifyHandlerComment 处理程序

理论不结合实践,终究只是纸上谈兵。所以,让我们运行程序,看看它在 MetaTrader 图表上的表现。为了测试它,我们创建了一个简单的脚本。在这里,我们模拟了一个典型的EA场景,触发不同级别的日志:从调试消息到警报和严重错误。

此外,我们将处理程序配置为直接在图表上显示日志,采用单线边框风格,日志逐行向下滚动(即最新的在底部,旧的被推至顶部)。

//+------------------------------------------------------------------+
//| Import CLogify                                                   |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify logify;
//+------------------------------------------------------------------+
//| Expert initialization                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Handler config
   MqlLogifyHandleCommentConfig m_config;
   m_config.size = 10;                                     // Max log lines
   m_config.frame_style = LOG_FRAME_STYLE_NONE;            // Frame style
   m_config.direction = LOG_DIRECTION_UP;                  // Log direction (up)
   m_config.title = "Expert name";                         // Log panel title
   
   //--- Create and configure handler
   CLogifyHandlerComment *handler_comment = new CLogifyHandlerComment();
   handler_comment.SetConfig(m_config);
   handler_comment.SetLevel(LOG_LEVEL_DEBUG);              // Min log level
   handler_comment.SetFormatter(new CLogifyFormatter("hh:mm:ss",
                                                      "{date_time} [{levelname}]: {msg}"));
   
   //--- Add handler to Logify
   logify.AddHandler(handler_comment);
   
   //--- Test logs
   logify.Debug("Initializing Expert Advisor...", "Init", "");
   Sleep(1500);
   logify.Debug("RSI indicator value calculated: 72.56", "Indicators", "Period: 14");
   Sleep(800);
   logify.Info("Buy order sent successfully", "Order Management", "Symbol: EURUSD, Volume: 0.1");
   Sleep(800);
   logify.Alert("Stop Loss adjusted to breakeven level", "Risk Management", "Order ID: 12345678");
   Sleep(500);
   logify.Error("Failed to send sell order", "Order Management", "Reason: Insufficient balance");
   Sleep(100);
   logify.Fatal("Failed to initialize EA: Invalid settings", "Initialization", "Missing or incorrect parameters");
   
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

为什么要使用Sleep()?Sleep() 并非必须,但在这里用于稍稍拉开日志的时间间隔,模拟事件在不同时间发生的情况,使测试更逼真,图表上的视觉效果也更舒适。如果您觉得文字描述不足以展现其效果,请看下图了解实际样子:

让我们看看其他边框的样子。


至此,我们的 CLogifyHandlerComment 处理程序已经过测试、验证,并达到 100% 可用状态。


结论

至此,本系列关于构建 Logify 文章的已经全部结束。Logify 是一个完整、健壮且完全可定制的 MQL5 日志库。在这段旅程中,我们从日志系统的基础理论出发,探讨了架构搭建、处理程序创建、格式配置,直至最终实现这个可视化处理器——利用 Comment() 函数直接将日志显示在图表上。

我们的目标很简单,但却极其必要:填补MetaTrader 5平台在EA和工具开发方面存在的空白——即缺乏一个完善、灵活且设计精良的日志系统。现在,关注本系列的朋友们已经掌握了一套功能强大的工具集,它能够生成条理清晰的日志,支持按级别筛选,根据需求进行格式化,甚至还能以优雅且实用的方式在图表上实时查看。

本文标志着本系列文章的结束(至少暂时如此)。该库功能完备、成熟,足以满足最常见的各种需求。然而,技术是有生命的。总有改进、调整、优化甚至新点子涌现的空间。如果有这一天(而且极大概率会有),放心,我会在新文章里带上这些更新,继续挖掘Logify的潜力。

目前,该项目已达成其宗旨:为开发者提供一个强大的工具,用于调试、分析和监控他们的机器人及指标。愿 Logify 助您将混乱转化为秩序,把杂乱的消息变成有价值的信息。

下次再见。


文件名
说明
Experts/Logify/LogiftTest.mq5
用于测试库功能的文件,包含一个实际示例
Include/Logify/Formatter/LogifyFormatter.mqh
负责格式化日志记录的类,将占位符替换为具体值
Include/Logify/Handlers/LogifyHandler.mqh
用于管理日志处理程序的基类,包括级别设置和日志发送
Include/Logify/Handlers/LogifyHandlerComment.mqh
将技术日志直接输出到MetaTrader图表注释的处理程序
Include/Logify/Handlers/LogifyHandlerConsole.mqh
将格式化后的日志直接发送到 MetaTrader 终端控制台的日志处理程序
Include/Logify/Handlers/LogifyHandlerDatabase.mqh
将格式化后的日志发送到数据库的日志处理程序(目前仅包含打印输出,但很快我们会将其保存到真正的 sqlite 数据库中)
Include/Logify/Handlers/LogifyHandlerFile.mqh
将格式化后的日志发送到文件的日志处理程序
Include/Logify/Utils/IntervalWatcher.mqh
检查一个时间间隔是否已经过去,允许您在库内部创建定时执行的例程。
Include/Logify/Logify.mqh
日志管理的核心类,集成了级别、模型和格式化功能
Include/Logify/LogifyLevel.mqh
定义 Logify 库日志级别的文件,支持精细控制
Include/Logify/LogifyModel.mqh 用于建模日志记录的结构体,涵盖级别、消息、时间戳和上下文等详细信息


本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18291

附加的文件 |
Logify.zip (21.43 KB)
风险管理(第四部分):完善关键类方法 风险管理(第四部分):完善关键类方法
这是我们关于 MQL5 风险管理系列文章的第四部分,我们将继续探索保护和优化交易策略的高级方法。在前几篇文章中奠定了重要的基础之后,我们现在将专注于完成第三部分中推迟的所有剩余方法,包括检查是否达到特定利润或亏损水平的函数。此外,我们将引入新的关键事件,以实现更准确、更灵活的风险管理。
MQL5中的ARIMA预测指标 MQL5中的ARIMA预测指标
在这篇文章中,我们将在 MQL5 环境中实现一个 ARIMA 预测指标。文章深入探讨了 ARIMA 模型生成预测的机制,并分析了其在外汇市场乃至整个证券市场的适用性。此外,文章还详细阐释了什么是 AR 自回归模型,如何利用自回归模型进行预测,以及自回归机制的具体运作原理。
价格行为分析工具包开发(第二十五部分):双指数移动平均线(EMA)分形突破策略 价格行为分析工具包开发(第二十五部分):双指数移动平均线(EMA)分形突破策略
价格行为分析是识别盈利交易机会的基础方法。然而,人工监测价格走势和形态不仅困难而且极其耗时。为解决这一痛点,我们开发了自动分析价格行为的工具,一旦检测到潜在机会,就会立刻发出信号。本文将介绍一款强大的工具,该工具结合分形突破以及14周期指数移动平均线(EMA 14)和200周期指数移动平均线(EMA 200)来生成可靠的交易信号,帮助交易者更自信地做出明智决策。
MQL5 MVC架构中表格视图与控制器组件:简单控件 MQL5 MVC架构中表格视图与控制器组件:简单控件
本文探讨了如何在MVC(模型 - 视图 - 控制器)架构下实现表格,重点介绍简单控件,它们是构建复杂视图组件的基础。控制器主要用来处理用户与元素、元素与元索之间的交互。这是关于视图组件的第二篇文章,也是关于为MetaTrader 5客户端创建表格系列文章中的第四篇。