English Русский Deutsch 日本語
preview
从 MQL5 向 Discord 发送消息,创建 Discord-MetaTrader 5 机器人

从 MQL5 向 Discord 发送消息,创建 Discord-MetaTrader 5 机器人

MetaTrader 5积分 |
71 0
Omega J Msigwa
Omega J Msigwa

目录


概述

我们不再生活在互联网和原始数字时代的早期阶段;如今,几乎所有东西都可以与互联网上的任何东西连接;这只取决于一个人是否愿意做所需的工作。

API(应用程序编程接口)是一组规则和协议,允许不同的软件应用程序相互通信,它的存在使多个软件或应用程序更容易连接到另一个,因此它使我们今天看到的连接最紧密的互联网。

要使用任何提供的 API,您必须遵守规则和协议,而 API 提供商必须授予您安全的方式(如果有的话)来访问它。

在 MetaTrader 5 和外部应用程序之间进行通信并不是一件新鲜事;以前在几个提供可靠 API 的应用程序上进行过此操作,MQL5 开发人员使用这些应用程序使用 Web 请求发送和接收信息。MQL5 交易者进行此类通信最常用的应用程序是 Telegram

在本文中,我们将讨论如何使用 MQL5 编程语言将消息和交易信息(信号)从 MetaTrader 5 发送到 Discord。


创建 Discord Webhook

对于那些还不熟悉 Discord 的人来说:

Discord 是一个免费的通信平台,允许用户通过文本、语音和视频聊天,并共享屏幕。它在与朋友和社区联系方面很受欢迎,特别是在在线游戏中,但也用于各种其他团体,如读书俱乐部或缝纫圈。

与 Telegram 类似,这是一个允许用户相互交流的通信平台。这两个平台都提供了 API,允许在平台之外进行通信,但在集成和自动化方面,Discord 远远领先于 Telegram。

该平台允许用户创建灵活的社区并管理不同类型的应用程序(也称为机器人)。

您可以通过多种方式创建机器人,并自动执行与 Discord 的通信和信息共享过程,但最简单的方法是在社区的某个频道中使用 Webhook 。以下是创建方法。

 

图 01.

从您的社区,转到服务器设置

 

图 02.

服务器设置转到“集成”>“Webhooks”。

 

图 03.

点击“新建 Webhook”按钮创建 Webhook。将创建一个名为 Captain Hook 的 Webhook 。您可以随意将此名称修改为您想要的任何名称。

 

图 04.

修改名称后,您可以选择要将此 webhook 应用于的频道。在这个服务器上,我有一个名为“交易信号”的频道,我想把这个 Webhook 应用到这个频道上。参见图 01。

最后,我们需要复制 Webhook URL 。这是我们的 API 网关,我们可以将其MQL5 中的 Web 请求一起使用

 

图 05.



从 MetaTrader 5 向 Discord 发送第一条消息

我们首先需要做的就是将 添加到 MetaTrader 5 的允许 URL 列表中,否则,接下来我们要讨论的所有内容都将无法正常工作。

在 MetaTrader 5 中,转到 “工具”>“选项”>“EA 交易”> “确保已选中‘允许对列出的 URL 发送 Web 请求’” 然后继续在下面的表单中添加 URL。

使用 Webhook URL,我们可以在给定 JSON 格式数据的情况下向这个 API 端点发送 POST 请求。

文件名: Discord EA.mq5

#include <jason.mqh> //https://www.mql5.com/en/code/13663

string discord_webhook_url = "https://discord.com/api/webhooks/1384809399767269527/105Kp27yKnQDpKD01VdEb01GS5P-KH5o5rYKuJb_xD_D8O23GPkGLXGn9pHBB1aOt4wR";
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
     
   CJAVal js(NULL, jtUNDEF); //Creating a json object
   
   string raw_json = "{\"content\": \"Hello world from MetaTrader5!\"}";
   
   bool b = js.Deserialize(raw_json); //Parse JSON string into a structured object
   
   string json;
   js.Serialize(json); //Convert the object to a valid JSON string
   
//--- Sending a Post webrequest

   char data[]; 
   ArrayResize(data, StringToCharArray(json, data, 0, StringLen(json))); //--- serialize to string
   
//--- send data

   char res_data[]; //For storing the body of the response
   string res_headers=NULL; //For storing response headers (if needed)
   
   string headers="Content-Type: application/json; charset=utf-8";
   uint timeout=10000; //10 seconds timeout for the HTTP request
   
   int ret = WebRequest("POST", 
                         discord_webhook_url, 
                         headers, 
                         timeout, 
                         data, 
                         res_data, 
                         res_headers); //Send a post request
   
   if (ret==-1)
     {
       printf("func=%s line=%d, Failed to send a webrequest. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError()));
       return false;
     }
   
//--- Check if the post request was successful or not

   if (ret==204)
     {
       if (MQLInfoInteger(MQL_DEBUG))
         Print("Message sent to discord successfully");
     }
   else
     {
       printf("Failed to send message to discord. Json response Error = %s",CharArrayToString(res_data));
     }
  
//---
   return(INIT_SUCCEEDED);
  }

您要发送到 Discord 的所有信息和消息必须始终采用 JSON 格式。

JSON 对象中 content 键下的所有内容都代表发送到 Discord 的消息正文

运行上述代码会向 Discord 发送一条简单的消息。

非常好!然而,处理 JSON 数据和其他所有内容只是为了向 Discord 发送一条简单的消息,这是一个令人厌烦的过程,让我们把所有内容都放在一个类中,以便更容易发送消息,而不必每次都担心技术问题。


在 MQL5 中创建 Discord 类

为了确保我们有一个标准的功能类,让我们在其中添加日志记录,以跟踪 API 端点和整个发送请求过程产生的错误。

文件名:Discord.mqh。

#include <errordescription.mqh>
#include <logging.mqh>
#include <jason.mqh>

class CDiscord
  {
protected:
   string m_webhook_url;
   string m_headers;
   uint m_timeout;
   CLogging logging;
   
   bool PostRequest(const string json);
   string GetFormattedJson(const string raw_json);
   
public:
                     CDiscord(const string webhook_url, const string headers="Content-Type: application/json; charset=utf-8", const uint timeout=10000);
                    ~CDiscord(void);
                     
                     bool SendMessage(const string message);
                     bool SendEmbeds(const string title, const string description_, color clr, const string footer_text, const datetime time);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CDiscord::CDiscord(const string webhook_url, string headers="Content-Type: application/json; charset=utf-8", uint timeout=10000):
 m_webhook_url(webhook_url),
 m_headers(headers),
 m_timeout(timeout)
 {
//--- Initialize the logger

   logging.Config("Discord server");
   if (!logging.init())
      return;
      
   logging.info("Initialized",MQLInfoString(MQL_PROGRAM_NAME),__LINE__);
 }

我们对发送 POST 请求和将文本转换为 JSON 格式的重复性过程进行了封装。

string CDiscord::GetFormattedJson(const string raw_json)
 {
   CJAVal js(NULL, jtUNDEF);
   bool b = js.Deserialize(raw_json);
   
   string json;
   js.Serialize(json); 
   
   return json;
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CDiscord::PostRequest(const string json)
 {
   char res[];
    
//--- serialize to string

   char data[]; 
   ArrayResize(data, StringToCharArray(json, data, 0, StringLen(json)));
   
//--- send data

   char res_data[];
   string res_headers=NULL;

   int ret = WebRequest("POST", m_webhook_url, m_headers, m_timeout, data, res_data, res_headers); //Send a post request
   
   if (ret==-1)
     {
       printf("func=%s line=%d, Failed to send a webrequest. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError()));
       return false;
     }
   
//--- Check if the post request was successful or not

   if (ret==204)
     {
       if (MQLInfoInteger(MQL_DEBUG))
         Print("Message sent to discord successfully");
         logging.info("Message sent to discord successfully",MQLInfoString(MQL_PROGRAM_NAME), __LINE__);
     }
   else
     {
       printf("Failed to send message to discord. Json response Error = %s",CharArrayToString(res_data));
       logging.error(CharArrayToString(res_data), MQLInfoString(MQL_PROGRAM_NAME), __LINE__);
     }

  return true;
 }

下面是一个向 Discord 发送消息的简单函数。

bool CDiscord::SendMessage(const string message)
 {
   string raw_json = StringFormat("{\"content\": \"%s\"}",message);   
   string json = GetFormattedJson(raw_json); //Deserialize & Serialize the message in JSON format
   
   return PostRequest(json);
 }

使用方法如下。

文件名: Discord EA.mq5

#include <Discord.mqh>
CDiscord *discord;

string discord_webhook_url = "https://discord.com/api/webhooks/1384809399767269527/105Kp27yKnQDpKD01VdEb01GS5P-KH5o5rYKuJb_xD_D8O23GPkGLXGn9pHBB1aOt4wR";
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
     
    discord = new CDiscord(discord_webhook_url);  
    discord.SendMessage("Hello from MetaTrader5!");
  }

这个简单而强大的函数可以用来发送不同类型的消息,因为 Discord 使用的是 Markdown 格式的文本

Markdown 消息示例

    discord.SendMessage(
                         "# h1 header\n"
                         "## h2 header\n"
                         "### h3 header\n\n"
                         "**bold text** normal text\n"
                         "[MQL5](https://www.mql5.com)\n"
                         "![image](https://i.imgur.com/4M34hi2.png)"
                       );

输出。

这甚至不是你用 Discord 提供的 Markdown 文本编辑器能做的最酷的事情。让我们与 Discord 分享一些代码行。

    discord.SendMessage("```MQL5\n" //we put the type of language for markdown highlighters after three consecutive backticks
                        "if (CheckPointer(discord)!=POINTER_INVALID)\n"
                        "  delete discord;\n"
                        "```");
                        
    discord.SendMessage("```Python\n"
                        "while(True):\n"
                        "  break\n"
                        "```");

输出。

这是为了向您展示 Markdown 文本编辑器的强大功能。

在消息中添加表情符号

表情符号在短信中非常有用;它们增添了优雅,提高了可读性,更不用说注入了一点幽默。

与其他使用由特定数字代码制作的表情符号的通信平台不同,Discord 使用独特的语法直接在原始文本消息中识别和呈现表情符号。

你只需要将表情符号的名称放在两个冒号之间 —— 一个冒号放在表情符号名称的开头,另一个冒号放在表情符号名称的结尾。

当这些表情符号的代码插入到短信中时,它们将在 Discord 中呈现为表情符号。

discord.SendMessage(":rotating_light: Trade Alert!");

信息。

有成千上万的表情符号,很难把它们全部记录下来。这是速查表 —— https://github.com/ikatyang/emoji-cheat-sheet/blob/master/README.md 。 我创建了一个简单的库文件,其中包含一些表情符号的代码和语法,请随时根据自己的喜好添加更多表情符号的代码

文件名:discord emojis.mqh

#define DISCORD_EMOJI_ROCKET                   ":rocket:"               
#define DISCORD_EMOJI_CHART_UP                 ":chart_with_upwards_trend:"   
#define DISCORD_EMOJI_CHART_DOWN               ":chart_with_downwards_trend:" 
#define DISCORD_EMOJI_BAR_CHART                ":bar_chart:"            
#define DISCORD_EMOJI_BOMB                     ":bomb:"                 
#define DISCORD_EMOJI_THUMBS_UP                ":thumbsup:"             
#define DISCORD_EMOJI_THUMBS_DOWN              ":thumbsdown:"           
#define DISCORD_EMOJI_WARNING                  ":warning:"              
#define DISCORD_EMOJI_JOY                      ":joy:"                  
#define DISCORD_EMOJI_SOB                      ":sob:"                  
#define DISCORD_EMOJI_SMILE                    ":smile:"                
#define DISCORD_EMOJI_FIRE                     ":fire:"                 
#define DISCORD_EMOJI_STAR                     ":star:"                 
#define DISCORD_EMOJI_BLUSH                    ":blush:"                
#define DISCORD_EMOJI_THINKING                 ":thinking:"             
#define DISCORD_EMOJI_ROTATING_LIGHT           ":rotating_light:"
#define DISCORD_EMOJI_X                        ":x:"
#define DISCORD_EMOJI_WHITE_CHECK_MARK         ":white_check_mark:"
#define DISCORD_EMOJI_BALLOT_BOX_WITH_CHECK    ":ballot_box_with_check:"

#define DISCORD_EMOJI_HASH                     ":hash:"               
#define DISCORD_EMOJI_ASTERISK                 ":asterisk:"           

#define DISCORD_EMOJI_ZERO                     ":zero:"               
#define DISCORD_EMOJI_ONE                      ":one:"                
#define DISCORD_EMOJI_TWO                      ":two:"                
#define DISCORD_EMOJI_THREE                    ":three:"              
#define DISCORD_EMOJI_FOUR                     ":four:"               
#define DISCORD_EMOJI_FIVE                     ":five:"               
#define DISCORD_EMOJI_SIX                      ":six:"                
#define DISCORD_EMOJI_SEVEN                    ":seven:"              
#define DISCORD_EMOJI_EIGHT                    ":eight:"              
#define DISCORD_EMOJI_NINE                     ":nine:"               
#define DISCORD_EMOJI_TEN                      ":keycap_ten:"       
  
#define DISCORD_EMOJI_RED_CIRCLE               ":red_circle:"               
#define DISCORD_EMOJI_GREEN_CIRCLE             ":green_circle:"       

发送消息。

discord.SendMessage(DISCORD_EMOJI_ROTATING_LIGHT " Trade Alert! " DISCORD_EMOJI_JOY);

消息结果。

提及用户和角色

这个简单的消息可以处理提及和角色,这对有效的消息传递至关重要。

  • @everyone — 此通知会推送至频道中的所有用户,即使他们处于离线状态。
  • @here — 这会通知聊天中所有非空闲用户。所有在线用户。
  • <@user_id> — 此操作会通知特定用户或多个用户。
  • <@&role_id> 此操作会通知所有分配到特定角色的用户。例如,向所有被指定为手动交易者的用户发送交易信号。

使用示例。

discord.SendMessage("@everyone This is an information about a trading account");
discord.SendMessage("@here This is a quick trading signals for all of you that are active");

消息结果。


Discord 机器人的身份

webhook 的好处在于它不局限于默认外观(名称和头像);你可以在发送消息请求时随时更改这些值。

此功能非常方便,因为您可以使用多个交易机器人向一两个社区发送某种信息,所有这些机器人共享一个 webhook,能够使用不同的身份,有助于区分发件人和收到的消息。

每次调用(启动)discord 类以使身份全局化时,我们都可以将此身份作为必要条件。

class CDiscord
  {
protected:

//...
//...
   
public:
                     string m_name;
                     string m_avatar_url;
                     
                     CDiscord(const string webhook_url, 
                              const string name, 
                              const string avatar_url, 
                              const string headers="Content-Type: application/json; charset=utf-8", 
                              const uint timeout=10000);
 }

我们将这些变量设为公有声明,因为用户可能希望在过程中(在类初始化之后)修改或访问它们。

这使得跟踪分配给此 Discord 信使的每个用户名的日志变得更加容易。

CDiscord::CDiscord(const string webhook_url, 
                   const string name, 
                   const string avatar_url, 
                   const string headers="Content-Type: application/json; charset=utf-8", 
                   const uint timeout=10000):
                   
 m_webhook_url(webhook_url),
 m_headers(headers),
 m_timeout(timeout),
 m_name(name),
 m_avatar_url(avatar_url)
 {
//--- Initialize the logger

   logging.Config(m_name);
   if (!logging.init())
      return;
      
   logging.info("Initialized",MQLInfoString(MQL_PROGRAM_NAME),__LINE__);
 }

现在,我们必须在找到 JSON 字符串的任何地方附加此身份(姓名和头像)信息。

bool CDiscord::SendMessage(const string message)
 {
   string raw_json = StringFormat("{"
                                    "\"username\": \"%s\","
                                    "\"avatar_url\": \"%s\","
                                    "\"content\": \"%s\""
                                  "}",
                                  m_name, m_avatar_url, message);   
                                  
   string json = GetFormattedJson(raw_json); //Deserialize & Serialize the message in JSON format
   
   return PostRequest(json);
 }

在 SendJSON() 函数内部,我们需要将此标识设置为可选,因为用户可能首先有不同的想法,这就是为什么他们选择使用一个接受原始 JSON 字符串的函数,该字符串使他们能够控制要发送的信息。

bool CDiscord::SendJSON(const string raw_json, bool use_id=true)
 {
   CJAVal js;                                 
   
   js.Deserialize(raw_json);
   
   if (use_id) //if a decides to use the ids assigned to the class constructor, we append that information to the json object
     {
       js["username"] = m_name;
       js["avatar_url"] = m_avatar_url;
     }
     
   string json;
   js.Serialize(json);
   
   return PostRequest(json);
 }

让我们为我们的机器人设置一个不同的身份。

#include <Discord.mqh>
CDiscord *discord;

input string discord_webhook_url = "https://discord.com/api/webhooks/1384809399767269527/105Kp27yKnQDpKD01VdEb01GS5P-KH5o5rYKuJb_xD_D8O23GPkGLXGn9pHBB1aOt4wR";
input string avatar_url_ = "https://imgur.com/m7sVf51.jpeg";
input string bots_name = "Signals Bot";
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
     
    discord = new CDiscord(discord_webhook_url,
                           bots_name,
                           avatar_url_);  
    
    discord.SendMessage("Same webhook but, different Identity :smile: cheers!");
            
//---
   return(INIT_SUCCEEDED);
  }

结果。


处理嵌入式文件和图像文件

尽管在创建 Discord 类时已将图像附加到上一节中讨论的消息中,但仍有一种正确的方法可以将文件和嵌入项附加到消息中。

我们可以通过独立的 JSON 请求来实现这一点。

discord.SendJSON(
    "{"
        "\"content\": \"Here's the latest chart update:\","
        "\"embeds\": ["
            "{"
                "\"title\": \"Chart Update\","
                "\"image\": {"
                    "\"url\": \"https://imgur.com/SBsomI7.png\""
                "}"
            "}"
        "]"
    "}"
);

结果。

遗憾的是,尽管 Discord 能够接收文件,但您无法使用 Web Request 直接从 MQL5 发送图像。  目前,您所能做的最好的事情就是从在线托管文件的某个链接中共享图像。

在之前的例子中,我必须使用 imgur.com


将 MetaTrader 5 的交易通知发送到 Discord

交易者经常利用 MetaTrader 5 将信息发送到外部平台的功能,发送有关其交易活动的更新信息。让我们使用这个机器人(EA 交易)来完成这项任务。

首先是交易开仓通知。

01:发送交易开仓通知

OnTradeTransaction 函数非常适合这项任务。

以下是用于检查最近头寸和交易状态的条件检查器。

#define IS_TRANSACTION_POSITION_OPENED         (trans.type == TRADE_TRANSACTION_DEAL_ADD && HistoryDealSelect(trans.deal) && (ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY) == DEAL_ENTRY_IN)
#define IS_TRANSACTION_POSITION_CLOSED         (trans.type == TRADE_TRANSACTION_DEAL_ADD && HistoryDealSelect(trans.deal) && (ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY) == DEAL_ENTRY_OUT && ((ENUM_DEAL_REASON)HistoryDealGetInteger(trans.deal, DEAL_REASON) != DEAL_REASON_SL && (ENUM_DEAL_REASON)HistoryDealGetInteger(trans.deal, DEAL_REASON) != DEAL_REASON_TP))
#define IS_TRANSACTION_POSITION_MODIFIED       (trans.type == TRADE_TRANSACTION_POSITION)
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
     ulong deal_ticket = trans.deal;
     ulong position_ticket = trans.position;

//---
     
     m_deal.Ticket(deal_ticket); //select a deal by it's ticket
     
     if (IS_TRANSACTION_POSITION_OPENED)
       {
         if (deal_ticket==0)
           return;
          
         if (!m_position.SelectByTicket(position_ticket)) //select a position by ticket
            return;
              
         if (!m_symbol.Name(m_deal.Symbol())) //select a symbol from a position
           {
             printf("line=%d Failed to select symbol %s. Error = %s",__LINE__,m_deal.Symbol(),ErrorDescription(GetLastError()));
             return;
           }
         
         string message = 
            DISCORD_EMOJI_ROTATING_LIGHT " **TRADE OPENED ALERT** \n\n"
            "- **Symbol:**"+m_position.Symbol()+"\n"
            "- **Trade Type:** "+ (m_position.PositionType()==POSITION_TYPE_BUY ? DISCORD_EMOJI_GREEN_CIRCLE: DISCORD_EMOJI_RED_CIRCLE) +" "+m_position.TypeDescription()+"\n"
            "- **Entry Price:** `"+DoubleToString(m_deal.Price(), m_symbol.Digits())+"`\n" 
            "- **Stop Loss:** `"+DoubleToString(m_position.StopLoss(), m_symbol.Digits())+"`\n"
            "- **Take Profit:** `"+DoubleToString(m_position.TakeProfit(), m_symbol.Digits())+"`\n"
            "- **Time UTC:** `"+TimeToString(TimeGMT())+"`\n"
            "- **Position Ticket:** `"+(string)position_ticket+"`\n"
            "> "DISCORD_EMOJI_WARNING" *Risk what you can afford to lose.*";
               
         discord.SendMessage(message);
       }

  //...
  //... Other lines of code
  //...     
 }

请注意:

在格式化市场价值(如入场价格、止损价和止盈价等)时,我用一个反引号(`)将每个值包裹起来。这会在 Discord 中创建一个内联代码块,这有助于在视觉上分隔数字,使用户更容易复制。

Discord 使用:

  • 单个反引号 (`) 可以格式化行内代码 —— 非常适合价格等简短值。
  • 三个反引号(```)格式化多行代码块 —— 用于较大的代码块或消息。

我使用 MetaTrader 5 的一键交易功能手动开了两个反向交易,结果如下。

02:发送交易修改通知

if (IS_TRANSACTION_POSITION_MODIFIED)
  {
    if (!m_position.SelectByTicket(position_ticket))
      {
        printf("Failed to modify a position. Erorr = %s",ErrorDescription(GetLastError()));
        return;
      }
              
    if (!m_symbol.Name(m_deal.Symbol()))
      {
        printf("line=%d Failed to select symbol %s. Error = %s",__LINE__,m_deal.Symbol(),ErrorDescription(GetLastError()));
        return;
      }
         
    string message = 
       DISCORD_EMOJI_BAR_CHART " **TRADE MODIFIED ALERT** \n\n"
       "- **Symbol:** `"+m_position.Symbol()+"`\n"
       "- **Trade Type:** "+ (m_position.PositionType()==POSITION_TYPE_BUY ? DISCORD_EMOJI_GREEN_CIRCLE: DISCORD_EMOJI_RED_CIRCLE) +" "+m_position.TypeDescription()+"\n"
       "- **Entry Price:** `"+DoubleToString(m_position.PriceOpen(), m_symbol.Digits())+"`\n" 
       "- **New Stop Loss:** `"+DoubleToString(m_position.StopLoss(), m_symbol.Digits())+"`\n"
       "- **New Take Profit:** `"+DoubleToString(m_position.TakeProfit(), m_symbol.Digits())+"`\n"
       "- **Time UTC:** `"+TimeToString(TimeGMT())+"`\n"
       "- **Position Ticket:** `"+(string)position_ticket+"`\n"
       "> "DISCORD_EMOJI_WARNING" *Risk what you can afford to lose.*";
            
    discord.SendMessage(message);      
  }

此消息中的所有内容与发送交易通知的消息相同,只是更改了止损和止盈字段的名称。

03:发送平仓交易通知

if (IS_TRANSACTION_POSITION_CLOSED)
   {         
    if (!m_symbol.Name(m_deal.Symbol()))
      {
        printf("line=%d Failed to select symbol %s. Error = %s",__LINE__,m_deal.Symbol(),ErrorDescription(GetLastError()));
        return;
      }
         
    m_symbol.RefreshRates(); //Get recent ask and bid prices
         
    long reason_integer;
    m_deal.InfoInteger(DEAL_REASON, reason_integer);
         
    string reason_text = GetDealReasonText(reason_integer);
            
    string message = 
          DISCORD_EMOJI_X " **TRADE CLOSED ALERT**\n\n"
          "- **Symbol:** `" + m_deal.Symbol() + "`\n"
          "- **Trade Type:** " + (m_position.PositionType() == POSITION_TYPE_BUY ? DISCORD_EMOJI_GREEN_CIRCLE : DISCORD_EMOJI_RED_CIRCLE) + " " + m_position.TypeDescription() + "\n"
          "- **Entry Price:** `" + DoubleToString(m_deal.Price(), m_symbol.Digits()) + "`\n"
          "- **Exit Price:** `" + (m_position.PositionType() == POSITION_TYPE_BUY ? (DoubleToString(m_symbol.Bid(), m_symbol.Digits())) : (DoubleToString(m_symbol.Ask(), m_symbol.Digits()))) + "`\n"
          "- **Profit:** " + (m_deal.Profit() >= 0 ? DISCORD_EMOJI_THUMBS_UP : DISCORD_EMOJI_THUMBS_DOWN) + " `" + DoubleToString(m_deal.Profit(), 2) + "`\n"
          "- **Close Reason:** `" + reason_text+ "`\n"
          "- **Commission:** `" + DoubleToString(m_position.Commission(), 2) + "`\n"
          "- **Swap:** `" + DoubleToString(m_position.Swap(), 2)+ "`\n"
          "- **Time (UTC):** `" + TimeToString(TimeGMT()) + "`\n"
          "- **Deal Ticket:** `" + string(deal_ticket) + "`\n"
          "> "DISCORD_EMOJI_WARNING" *Risk what you can afford to lose.*";
               
    discord.SendMessage(message);
  }

在收盘交易信息中,当交易以盈利收盘时,我们会放上一个点赞的表情符号,当交易亏损收盘时,会放一个点踩的表情符号。与开设和修改仓位不同,我们当时有一个仓位编号。已平仓的头寸不再是头寸,而是一笔交易,因此我们使用交易单号来表示它。

这笔交易是手动完成的,因此其原因变为客户端(桌面终端) ,这是根据 ENUM_DEAL_REASON 得出的。

string GetDealReasonText(long reason)
{
   switch((int)reason)
   {
      case DEAL_REASON_CLIENT:            return "Client (Desktop Terminal)";
      case DEAL_REASON_MOBILE:            return "Mobile App";
      case DEAL_REASON_WEB:               return "Web Platform";
      case DEAL_REASON_EXPERT:            return "Expert Advisor";
      case DEAL_REASON_SL:                return "Stop Loss Hit";
      case DEAL_REASON_TP:                return "Take Profit Hit";
      case DEAL_REASON_SO:                return "Stop Out (Margin)";
      case DEAL_REASON_ROLLOVER:          return "Rollover Execution";
      case DEAL_REASON_VMARGIN:           return "Variation Margin Charged";
      case DEAL_REASON_SPLIT:             return "Stock Split Adjustment";
      case DEAL_REASON_CORPORATE_ACTION:  return "Corporate Action";
      default:                            return "Unknown Reason";
   }
}


底线

Discord webhook 是 MetaTrader 5 和 Discord 集成的良好起点,它能够实现这两个强大平台之间的通信。但是,正如我之前所说,所有 API 都有规则和限制 —— Discord Webhook API 也不例外,因为 Webhook 请求有速率限制。

  • 使用单个 Webhook,您每 2 秒最多只能发送 5 条消息。
  • 单个 Webhook 每分钟每个频道最多允许发送 30 条消息。

如果超过此限制,Discord 将返回 HTTP 429(请求过多)。为了解决这个问题,您可以在 MQL5 代码中添加延迟或队列。

此外,在整篇文章中,我都在谈论 Discord 机器人,但 Webhook 与 Discord 机器人非常不同。MQL5 和 Discord 之间的整个工作流程就是我所说的机器人。

机器人本质上是在 Discord 中运行的程序,提供从基本命令到复杂自动化的广泛功能。而另一方面,Webhook 只是简单的 URL,允许外部应用程序向特定的 Discord 频道发送自动消息。

下面表格列出了 Discord Webhook 和 Discord 机器人之间的区别。


Webhooks 机器人
功能
  • 只能向指定频道发送消息
  • 它们只能发送消息,不能查看消息。
  • 每条消息最多可发送 10 个嵌入内容。

  • 它们更加灵活,可以执行类似于普通用户的更复杂的操作。
  • 机器人可以查看和发送消息。
  • 每条消息只允许嵌入一个链接。
定制
  • 每个服务器可创建 10 个 webhook,并可自定义每个 webhook 的头像和名称。
  • 能够为嵌入内容之外的任何文本添加超链接

  • 公共机器人通常具有预设的头像和名称,最终用户无法修改。
  • 普通邮件中无法添加超链接,必须使用嵌入文本。
负载和安全
  • 只是一个发送数据的端点,不需要实际的托管。
  • 没有验证发送到 webhook 的数据是否来自可信来源。
  • 没有验证发送到 webhook 的数据是否来自可信来源。如果 webhook URL 泄露,只会造成一些非永久性问题(例如,垃圾邮件)。
  • 如有需要,可以轻松更改 webhook URL。

  • 机器人必须托管在一个需要始终保持在线的安全环境中,这会消耗更多的资源。
  • 机器人通过令牌进行身份验证;如果被泄露的令牌具有服务器所有者授予的权限,则由于其功能,可能会造成严重损害。
  • 但是,如果需要,您可以重置机器人令牌。

Discord 机器人相当复杂,对于 MQL5 程序员来说不太实用,因为我们通常只需要一个功能来提醒其他交易者交易的进展情况。更不用说,要创建一个 Discord 机器人,你需要几个 Python 模块

但是,如果你愿意,你可以开发这个功能齐全的 Discord 机器人,它可以与 MetaTrader 5 配合使用。

由于我们有一个 MetaTrader 5-Python 软件包,您可以在 Python 环境中实现这两个平台之间的完全集成。

致以最诚挚的问候。


附件表

文件名 描述与用途
Include\discord emojis.mqh 包含与 Discord 兼容的表情符号代码和语法。
Include\discord.mqh 它有 CDiscord 类,用于以 JSON 格式向 Discord 平台发送消息。
Include\errordescription.mqh 包含 MetaTrader 5 以 MQL5 语言生成的所有错误代码的描述。
Include\jason.mqh 一个用于 JSON 协议序列化和反序列化的库。
Include\logging.mqh  用于记录 CDiscord 类(Webhook 发送器)产生的所有信息和错误的库。 
Experts\Discord EA.mq5  一个用于测试 Discord webhook 并向指定的 Discord webhook URL 发送交易警报的 EA 交易。 

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

附加的文件 |
Attachments.zip (19.26 KB)
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
成功餐饮经营者算法(SRA) 成功餐饮经营者算法(SRA)
成功餐饮经营者算法(SRA)是一种受餐饮业管理原则启发的创新优化方法。与传统方法不同,SRA不会直接淘汰劣质解,而是通过融合优质解的元素对其进行改进。该算法在优化问题中展现出极具竞争力的表现,并为平衡探索与利用提供了全新视角。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
从新手到专家:使用 MQL5 制作动画新闻标题(二) 从新手到专家:使用 MQL5 制作动画新闻标题(二)
今天,我们又向前迈进了一步,整合了一个外部新闻 API 作为我们的 News Headline EA 的头条新闻来源。在这个阶段,我们将探索各种新闻来源 —— 包括成熟的和新兴的 —— 并学习如何有效地访问它们的 API。我们还将介绍如何将检索到的数据解析成适合在我们的 EA 交易中显示的格式。加入讨论,我们将探索直接在图表上访问新闻标题和经济日历的好处,所有这些都在一个紧凑、不干扰用户的界面中。