创建 MQL5-Telegram 集成 EA 交易(第 4 部分):模块化代码函数以增强可重用性·进阶篇
📘

创建 MQL5-Telegram 集成 EA 交易(第 4 部分):模块化代码函数以增强可重用性·进阶篇

第 2/3 篇

◍ EA 初始化时把账户状态推到 Telegram

在 MT5 里用 WebRequest 直连 Telegram Bot API,可以把 EA 初始化那一刻的账户权益与空闲保证金主动推送到手机。下面这段逻辑先拼出请求 URL,再以 POST 方式发出去,timeout 设为 10000 毫秒(10 秒)是比较稳妥的等待值,太短容易在行情跳动时漏发。 返回码 200 代表推送成功,终端会打印 TELEGRAM MESSAGE SENT SUCCESSFULLY;若是 -1 且 GetLastError() 返回 4014,说明目标域名没在终端「允许 WebRequest 的 URL 列表」里勾选,这是最常见的配置坑。 账户侧取数直接用 AccountInfoDouble(ACCOUNT_EQUITY) 和 ACCOUNT_MARGIN_FREE,拼进带 emoji 的字符串后必须过一遍 UrlEncode,否则换行符和特殊符号会让 Bot API 报 400。外汇与贵金属杠杆高,权益和空闲保证金瞬间波动大,这类推送只作监控辅助,不构成任何方向判断。 别把 4014 当成网络坏了 先开 MT5 的「工具→选项→EA 交易」,把你的 TG_API_URL 根域名加进允许列表,再跑回测,否则代码逻辑再对也发不出去。

MQL5 / C++
  class="type">char data[];  class=class="str">"cmt">// Array to hold data to be sent in the web request(empty in this case)
  class="type">char res[];  class=class="str">"cmt">// Array to hold the response data from the web request
  class="type">class="kw">string resHeaders;  class=class="str">"cmt">// String to hold the response headers from the web request
  class="type">class="kw">string message = custom_message;

  const class="type">class="kw">string url = api_url + "/bot" + bot_token + "/sendmessage?chat_id=" + chat_id +
      "&text=" + message;

  class=class="str">"cmt">// Send the web request to the Telegram API
  class="type">int send_res = WebRequest("POST", url, "", timeout, data, res, resHeaders);

  class=class="str">"cmt">// Check the response status of the web request
  if (send_res == class="num">200) {
      class=class="str">"cmt">// If the response status is class="num">200 (OK), print a success message
      Print("TELEGRAM MESSAGE SENT SUCCESSFULLY");
      class="kw">return (SUCCEEDED_CODE);
  }
  else if (send_res == -class="num">1) {
      class=class="str">"cmt">// If the response status is -class="num">1 (error), check the specific error code
      if (GetLastError() == class="num">4014) {
          class=class="str">"cmt">// If the error code is class="num">4014, it means the Telegram API URL is not allowed in the terminal
          Print("PLEASE ADD THE ", api_url, " TO THE TERMINAL");
      }
      class=class="str">"cmt">// Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM MESSAGE");
      class="kw">return (FAILED_CODE);
  }
  else if (send_res != class="num">200) {
      class=class="str">"cmt">// If the response status is not class="num">200 or -class="num">1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      class="kw">return (FAILED_CODE);
  }

  class="kw">return (SUCCEEDED_CODE);
}
  class=class="str">"cmt">////--- Account Status Update:
  class="type">class="kw">double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
  class="type">class="kw">double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
  class="type">class="kw">string complex_msg = "\xF680 EA INITIALIZED ON CHART " + _Symbol + "\xF680"
          +"\n\xF4CA Account Status \xF4CA"
          +"\nEquity: $"
          +DoubleToString(accountEquity,class="num">2)
          +"\nFree Margin: $"
          +DoubleToString(accountFreeMargin,class="num">2);

  class="type">class="kw">string encloded_msg = UrlEncode(complex_msg);
  complex_msg = encloded_msg;
  sendEncodedMessage(complex_msg,TG_API_URL,botTkn,chatID,class="num">10000);

「把图表截图推到 Telegram 的模块化写法」

要让 MT5 把图表画面自动发到 Telegram,核心是先拆出两个独立函数:一个负责抓图存盘,一个负责读文件并发 POST 请求。这样主逻辑只需两行调用,后期接交易信号也方便。 sendScreenShot 用整数返回码标记成败:文件打不开直接回 FAILED_CODE,WebRequest 回 200 才回 SUCCEEDED_CODE,其他情况把错误码 4014(终端未放行 API 域名)单独提示。下面这段是发图函数的骨架与关键返回逻辑。 抓图侧也不能马虎。getScreenshot_of_Current_Chart 会先删同名旧文件避免覆盖,调 ChartRedraw 刷新后再用 ChartScreenShot 存盘,并用 while 循环最多等 30 秒确认文件落地;超时回 FAILED_CODE,成功回 SUCCEEDED_CODE。实测用户约 10 秒内能在 Telegram 收到图。 caption 参数留空传 NULL 时发过去的图没说明文字;想带上下文,就把品种、周期、时间拼成字符串传进去。编译后新开自定义图表的截图上就会带一行标题,排查哪张图来自哪段行情一目了然。

MQL5 / C++
class="type">int sendScreenShot(class="type">class="kw">string screenshot_name,const class="type">class="kw">string telegram_url,
                    const class="type">class="kw">string bot_token,const class="type">class="kw">string chat_id,
                    class="type">class="kw">string caption=""){
class=class="str">"cmt">//...
}
   class="type">int screenshot_Handle = INVALID_HANDLE;
   screenshot_Handle = FileOpen(screenshot_name,FILE_READ|FILE_BIN);
   if(screenshot_Handle == INVALID_HANDLE){
      Print("INVALID SCREENSHOT HANDLE. REVERTING NOW!");
      class="kw">return(FAILED_CODE);
   }
   class=class="str">"cmt">// Send the web request to the Telegram API
   class="type">int send_res = WebRequest("POST",URL,HEADERS,class="num">10000, DATA, res, resHeaders);
   class=class="str">"cmt">// Check the response status of the web request
   if (send_res == class="num">200) {
      class=class="str">"cmt">// If the response status is class="num">200 (OK), print a success message
      Print("TELEGRAM SCREENSHOT FILE SENT SUCCESSFULLY");
      class="kw">return (SUCCEEDED_CODE);
   }
   else if (send_res == -class="num">1) {
      class=class="str">"cmt">// If the response status is -class="num">1 (error), check the specific error code
      if (GetLastError() == class="num">4014) {
         class=class="str">"cmt">// If the error code is class="num">4014, it means the Telegram API URL is not allowed in the terminal
         Print("PLEASE ADD THE ", telegram_url, " TO THE TERMINAL");
      }
      class=class="str">"cmt">// Print a general error message if the request fails
      Print("UNABLE TO SEND THE TELEGRAM SCREENSHOT FILE");
      class="kw">return (FAILED_CODE);
   }

把图表截图读入并转成 Base64 待发

MT5 里要把图表截图推到外部接口,第一步是先以二进制方式把存在终端里的截图文件打开。FileOpen 拿到的句柄若是 INVALID_HANDLE,说明文件没落盘或路径错,直接返回失败码,这种异常在 EA 初次跑时命中率不低。 句柄有效后,FileSize 给出的字节数就是后续数组的基准。下面这段代码把截图读进 uchar 数组,并打印出实际读取长度,方便你在 Experts 日志里确认文件没读空: 读取成功后必须做 Base64 编码,因为 HTTP 表单无法直接吞二进制。CryptEncode 用 CRYPT_BASE64 把原始像素流变成可嵌字符串,编码后长度通常会比原文件大三分之一左右,日志里打印的 Transformed BASE-64 data 就是这个数值。 发图前还要截一段做边界校验。这里固定拷贝 base64 数组的前 1024 字节进临时数组,后面拼 multipart 边界时会用到,你可以先打开 MT5 日志看这 1024 字节是否随图表变化而变。

MQL5 / C++
else if (send_res != class="num">200) {
      class=class="str">"cmt">// If the response status is not class="num">200 or -class="num">1, print the unexpected response code and error code
      Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
      class="kw">return (FAILED_CODE);
   }
   class="kw">return (SUCCEEDED_CODE);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|    FUNCTION TO SEND CHART SCREENSHOT FILES                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int sendScreenShot(class="type">class="kw">string screenshot_name,const class="type">class="kw">string telegram_url,
                   const class="type">class="kw">string bot_token,const class="type">class="kw">string chat_id,
                   class="type">class="kw">string caption=""){

   class="type">int screenshot_Handle = INVALID_HANDLE;
   screenshot_Handle = FileOpen(screenshot_name,FILE_READ|FILE_BIN);
   if(screenshot_Handle == INVALID_HANDLE){
      Print("INVALID SCREENSHOT HANDLE. REVERTING NOW!");
      class="kw">return(FAILED_CODE);
   }

   else if (screenshot_Handle != INVALID_HANDLE){
      Print("SCREENSHOT WAS SAVED & OPENED SUCCESSFULLY FOR READING.");
      Print("HANDLE ID = ",screenshot_Handle,". IT IS NOW READY FOR ENCODING.");
   }

   class="type">int screenshot_Handle_Size = (class="type">int)FileSize(screenshot_Handle);
   if (screenshot_Handle_Size > class="num">0){
      Print("CHART SCREENSHOT FILE SIZE = ",screenshot_Handle_Size);
   }
   class="type">uchar photoArr_Data[];
   ArrayResize(photoArr_Data,screenshot_Handle_Size);
   FileReadArray(screenshot_Handle,photoArr_Data,class="num">0,screenshot_Handle_Size);
   if (ArraySize(photoArr_Data) > class="num">0){
      Print("READ SCREENSHOT FILE DATA SIZE = ",ArraySize(photoArr_Data));
   }
   FileClose(screenshot_Handle);

   class=class="str">"cmt">//ArrayPrint(photoArr_Data);

   class=class="str">"cmt">//--- create boundary: (data -> base64 -> class="num">1024 bytes -> md5)
   class=class="str">"cmt">//Encodes the photo data into base64 format
   class=class="str">"cmt">//This is part of preparing the data for transmission over HTTP.
   class="type">uchar base64[];
   class="type">uchar key[];
   CryptEncode(CRYPT_BASE64,photoArr_Data,key,base64);
   if (ArraySize(base64) > class="num">0){
      Print("Transformed BASE-class="num">64 data = ",ArraySize(base64));
      class=class="str">"cmt">//Print("The whole data is as below:");
      class=class="str">"cmt">//ArrayPrint(base64);
   }

   class=class="str">"cmt">//Copy the first class="num">1024 bytes of the base64-encoded data into a temporary array
   class="type">uchar temporaryArr[class="num">1024]= {class="num">0};
   class=class="str">"cmt">//Print("FILLED TEMPORARY ARRAY WITH ZERO(class="num">0) IS AS BELOW:");
   class=class="str">"cmt">//ArrayPrint(temporaryArr);
   ArrayCopy(temporaryArr,base64,class="num">0,class="num">0,class="num">1024);
   class=class="str">"cmt">//Print("FIRST class="num">1024 BYTES OF THE ENCODED DATA IS AS FOLLOWS:");
   class=class="str">"cmt">//ArrayPrint(temporaryArr);

◍ 用 MD5 拼出 multipart 边界并塞入 chat_id

给 Telegram 发图走的是 multipart/form-data,边界字符串不能随便写。这段逻辑先对临时数组做 MD5,再用前 16 位十六进制当 boundary,既满足 HTTP 规范也对得上服务端解析长度要求。 CryptEncode 算出的 md5 数组长度固定为 16 字节(CRYPT_HASH_MD5 输出 128 位),循环里用 StringFormat("%02X",md5[i]) 转成大写十六进制,拼完再 StringSubstr 截前 16 字符。打印出来你能直接看到边界长什么样,复制到 Postman 里对照也行。 边界拼好后往 DATA 里逐段 ArrayAdd:先 \r\n,再 "--"+Hash+"\r\n",接着 Content-Disposition 头与 chat_id 值。注意每加完一个 form-data 段都要补 \r\n,否则服务端可能把字段吞掉。最后用 CharArrayToString(DATA,0,WHOLE_ARRAY,CP_UTF8) 转成可读串,Print 出来核对比直接发请求更安全。 外汇与贵金属行情波动剧烈、滑点频发,这类自动推送脚本只建议用于告警辅助,别拿它当下单依据。

MQL5 / C++
  class=class="str">"cmt">//Create an MD5 hash of the temporary array
  class=class="str">"cmt">//This hash will be used as part of the boundary in the multipart/form-data
  class="type">uchar md5[];
  CryptEncode(CRYPT_HASH_MD5,temporaryArr,key,md5);
  if (ArraySize(md5) > class="num">0){
      Print("SIZE OF MD5 HASH OF TEMPORARY ARRAY = ",ArraySize(md5));
      Print("MD5 HASH boundary in multipart/form-data is as follows:");
      ArrayPrint(md5);
  }
  class=class="str">"cmt">//Format MD5 hash as a hexadecimal class="type">class="kw">string &
  class=class="str">"cmt">//truncate it to class="num">16 characters to create the boundary.
  class="type">class="kw">string HexaDecimal_Hash=NULL;class=class="str">"cmt">//Used to store the hexadecimal representation of MD5 hash
  class="type">int total=ArraySize(md5);
  for(class="type">int i=class="num">0; i<total; i++){
      HexaDecimal_Hash+=StringFormat("%02X",md5[i]);
  }
  Print("Formatted MD5 Hash String is: \n",HexaDecimal_Hash);
  HexaDecimal_Hash=StringSubstr(HexaDecimal_Hash,class="num">0,class="num">16);class=class="str">"cmt">//truncate HexaDecimal_Hash class="type">class="kw">string to its first class="num">16 characters
  class=class="str">"cmt">//done to comply with a specific length requirement for the boundary
  class=class="str">"cmt">//in the multipart/form-data of the HTTP request.
  Print("Final Truncated(class="num">16 characters) MD5 Hash String is: \n",HexaDecimal_Hash);

  class=class="str">"cmt">//--- WebRequest
  class="type">char DATA[];
  class="type">class="kw">string URL = NULL;
  URL = telegram_url+"/bot"+bot_token+"/sendPhoto";
  class=class="str">"cmt">//--- add chart_id
  class=class="str">"cmt">//Append a carriage class="kw">return and newline character sequence to the DATA array.
  class=class="str">"cmt">//In the context of HTTP, \r\n is used to denote the end of a line
  class=class="str">"cmt">//and is often required to separate different parts of an HTTP request.
  ArrayAdd(DATA,"\r\n");
  class=class="str">"cmt">//Append a boundary marker to the DATA array.
  class=class="str">"cmt">//Typically, the boundary marker is composed of two hyphens(--)
  class=class="str">"cmt">//followed by a unique hash class="type">class="kw">string and then a newline sequence.
  class=class="str">"cmt">//In multipart/form-data requests, boundaries are used to separate
  class=class="str">"cmt">//different pieces of data.
  ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n");
  class=class="str">"cmt">//Add a Content-Disposition header for a form-data part named chat_id.
  class=class="str">"cmt">//The Content-Disposition header is used to indicate that the following data
  class=class="str">"cmt">//is a form field with the name chat_id.
  ArrayAdd(DATA,"Content-Disposition: form-data; name=\"chat_id\"\r\n");
  class=class="str">"cmt">//Again, append a newline sequence to the DATA array to end the header section
  class=class="str">"cmt">//before the value of the chat_id is added.
  ArrayAdd(DATA,"\r\n");
  class=class="str">"cmt">//Append the actual chat ID value to the DATA array.
  ArrayAdd(DATA,chat_id);
  class=class="str">"cmt">//Finally, Append another newline sequence to the DATA array to signify
  class=class="str">"cmt">//the end of the chat_id form-data part.
  ArrayAdd(DATA,"\r\n");
  class=class="str">"cmt">// EXAMPLE OF USING CONVERSIONS
  class=class="str">"cmt">//class="type">uchar array[] = { class="num">72, class="num">101, class="num">108, class="num">108, class="num">111, class="num">0 }; // "Hello" in ASCII
  class=class="str">"cmt">//class="type">class="kw">string output = CharArrayToString(array,class="num">0,WHOLE_ARRAY,CP_ACP);
  class=class="str">"cmt">//Print("EXAMPLE OUTPUT OF CONVERSION = ",output); // Hello

  Print("CHAT ID DATA:");
  ArrayPrint(DATA);
  class="type">class="kw">string chatID_Data = CharArrayToString(DATA,class="num">0,WHOLE_ARRAY,CP_UTF8);
  Print("SIMPLE CHAT ID DATA IS AS FOLLOWS:",chatID_Data);
  class=class="str">"cmt">//--- Caption
  class="type">class="kw">string CAPTION_STRING = NULL;

「把截图塞进 multipart 请求发到 TG」

这段逻辑干的事很直接:先把 caption 文本按 multipart/form-data 格式拼进 DATA 数组,边界符用的是 HexaDecimal_Hash。若 caption 长度大于 0,就追加一段 name="caption" 的表单字段;随后无条件追加 name="photo" 且 filename="Upload_ScreenShot.jpg" 的二进制段,结尾补上 "--"+Hash+"--" 表示表单结束。 HEADERS 只设了一行 Content-Type,明确 boundary 与 HexaDecimal_Hash 一致,否则服务端解析会直接失败。WebRequest 用 POST、超时 10000 毫秒(10 秒),把 DATA 作为请求体发出,响应写进 res 与 resHeaders。 返回码判断有三个分支:200 视为发送成功并返回 SUCCEEDED_CODE;-1 且 GetLastError()==4014 说明终端没把 TG 域名加进允许列表,此时提示去添加;其余非 200 一律打印 UNEXPECTED RESPONSE 及错误码并返回 FAILED_CODE。外汇与贵金属账户跑这类 EA 存在 API 泄露 bot token 的高风险,建议用只读权限的 bot 且限定 chat_id。

MQL5 / C++
  CAPTION_STRING = caption;
  if(StringLen(CAPTION_STRING) > class="num">0){
    ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n");
    ArrayAdd(DATA,"Content-Disposition: form-data; name=\"caption\"\r\n");
    ArrayAdd(DATA,"\r\n");
    ArrayAdd(DATA,CAPTION_STRING);
    ArrayAdd(DATA,"\r\n");
  }
  class=class="str">"cmt">//---

  ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n");
  ArrayAdd(DATA,"Content-Disposition: form-data; name=\"photo\"; filename=\"Upload_ScreenShot.jpg\"\r\n");
  ArrayAdd(DATA,"\r\n");
  ArrayAdd(DATA,photoArr_Data);
  ArrayAdd(DATA,"\r\n");
  ArrayAdd(DATA,"--"+HexaDecimal_Hash+"--\r\n");

  Print("FINAL FULL PHOTO DATA BEING SENT:");
  ArrayPrint(DATA);
  class="type">class="kw">string final_Simple_Data = CharArrayToString(DATA,class="num">0,WHOLE_ARRAY,CP_ACP);
  Print("FINAL FULL SIMPLE PHOTO DATA BEING SENT:",final_Simple_Data);
  class="type">class="kw">string HEADERS = NULL;
  HEADERS = "Content-Type: multipart/form-data; boundary="+HexaDecimal_Hash+"\r\n";

  Print("SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY.");

  class=class="str">"cmt">//class="type">char data[];  // Array to hold data to be sent in the web request(empty in this case)
  class="type">char res[];  class=class="str">"cmt">// Array to hold the response data from the web request
  class="type">class="kw">string resHeaders;  class=class="str">"cmt">// String to hold the response headers from the web request
  class=class="str">"cmt">//class="type">class="kw">string msg = "EA INITIALIZED ON CHART " + _Symbol;  // Message to send, including the chart symbol

  class=class="str">"cmt">//const class="type">class="kw">string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID +
  class=class="str">"cmt">//   "&text=" + msg;

  class=class="str">"cmt">// Send the web request to the Telegram API
  class="type">int send_res = WebRequest("POST",URL,HEADERS,class="num">10000, DATA, res, resHeaders);
  class=class="str">"cmt">// Check the response status of the web request
  if (send_res == class="num">200) {
    class=class="str">"cmt">// If the response status is class="num">200 (OK), print a success message
    Print("TELEGRAM SCREENSHOT FILE SENT SUCCESSFULLY");
    class="kw">return (SUCCEEDED_CODE);
  }
  else if (send_res == -class="num">1) {
    class=class="str">"cmt">// If the response status is -class="num">1 (error), check the specific error code
    if (GetLastError() == class="num">4014) {
      class=class="str">"cmt">// If the error code is class="num">4014, it means the Telegram API URL is not allowed in the terminal
      Print("PLEASE ADD THE ", telegram_url, " TO THE TERMINAL");
    }
    class=class="str">"cmt">// Print a general error message if the request fails
    Print("UNABLE TO SEND THE TELEGRAM SCREENSHOT FILE");
    class="kw">return (FAILED_CODE);
  }
  else if (send_res != class="num">200) {
    class=class="str">"cmt">// If the response status is not class="num">200 or -class="num">1, print the unexpected response code and error code
    Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError());
    class="kw">return (FAILED_CODE);
  }
  class="kw">return (SUCCEEDED_CODE);
}

把图表截屏喂给视觉模型的两种取法

做价格行为复盘时,让小布这类 AIGC 工具读 MT5 图表,第一步是把当前画面或指定品种周期存成图片。下面两段函数分别解决“截当前图”和“开新图再截”的需求,可直接抄进 EA 或脚本。 当前图截图函数先判断文件是否已存在,存在就删掉并重绘,避免旧图干扰。随后以 1366×768、右对齐方式截屏,并用 60 次、每次 500 毫秒的循环等待文件落盘,最长约 30 秒;若超时未生成则返回失败码,否则返回成功码。 新图截图函数多一步 ChartOpen:传入 symbol_name 与 period_name 开一个独立图表,并等 SERIES_SYNCHRONIZED 为真确认行情同步。之后关掉网格和周期分隔线,把阴阳线强制染成红蓝、背景设为浅鲑鱼色,这样截出来的图对比度高,视觉模型辨识形态(如吞没、pin bar)的误读概率倾向更低。外汇与贵金属波动剧烈、杠杆风险高,截图仅作分析辅助,不构成方向建议。

MQL5 / C++
class="type">int getScreenshot_of_Current_Chart(class="type">class="kw">string screenshot_name){

  class=class="str">"cmt">//--- First class="kw">delete an instance of the screenshot file if it already exists
  if(FileIsExist(screenshot_name)){
     FileDelete(screenshot_name);
     Print("Chart Screenshot was found and deleted.");
     ChartRedraw(class="num">0);
  }

  ChartScreenShot(class="num">0,screenshot_name,class="num">1366,class="num">768,ALIGN_RIGHT);

  class=class="str">"cmt">// Wait for class="num">30 secs to save screenshot if not yet saved
  class="type">int wait_loops = class="num">60;
  while(!FileIsExist(screenshot_name) && --wait_loops > class="num">0){
     Sleep(class="num">500);
  }

  if(!FileIsExist(screenshot_name)){
     Print("THE SPECIFIED SCREENSHOT DOES NOT EXIST(WAS NOT SAVED). REVERTING NOW!");
     class="kw">return (FAILED_CODE);
  }
  else if(FileIsExist(screenshot_name)){
     Print("THE CHART SCREENSHOT WAS SAVED SUCCESSFULLY TO THE DATA-BASE.");
     class="kw">return (SUCCEEDED_CODE);
  }
  class="kw">return (SUCCEEDED_CODE);
}

class="type">int getScreenshot_of_New_Chart(class="type">class="kw">string screenshot_name,class="type">class="kw">string symbol_name,
                                ENUM_TIMEFRAMES period_name){

  class=class="str">"cmt">//--- First class="kw">delete an instance of the screenshot file if it already exists
  if(FileIsExist(screenshot_name)){
     FileDelete(screenshot_name);
     Print("Chart Screenshot was found and deleted.");
     ChartRedraw(class="num">0);
  }

  class="type">long chart_id=ChartOpen(symbol_name,period_name);
  ChartSetInteger(chart_id,CHART_BRING_TO_TOP,true);
  class=class="str">"cmt">// update chart
  class="type">int wait=class="num">60;
  while(--wait>class="num">0){class=class="str">"cmt">//decrease the value of wait by class="num">1 before loop condition check
     if(SeriesInfoInteger(symbol_name,period_name,SERIES_SYNCHRONIZED)){
        class="kw">break; class=class="str">"cmt">// if prices up to date, terminate the loop and proceed
     }
  }
  ChartRedraw(chart_id);
  ChartSetInteger(chart_id,CHART_SHOW_GRID,false);
  ChartSetInteger(chart_id,CHART_SHOW_PERIOD_SEP,false);
  ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BEAR,clrRed);
  ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BULL,clrBlue);
  ChartSetInteger(chart_id,CHART_COLOR_BACKGROUND,clrLightSalmon);

常见问题

在 OnInit 里调用模块化的账户状态推送函数,拼好文本消息后经 Telegram Bot API 发出,无需手动操作。
Base64 把二进制图转成文本,方便塞进 multipart 表单请求体,TG 接口才能正确解析图片文件。
小布内置 AIGC 看盘,可自动截取异动图表并推送到你指定的通道,省去自己写 EA 对接的重复劳动。
用 MD5 散列出随机边界字符串避免冲突,chat_id 作为表单字段塞进 multipart 体内和图片一起提交。
一种直接读图表窗口位图,一种存临时文件再读;存文件再转 Base64 更稳,不易丢帧。