开发具有 RestAPI 集成的 MQL5 强化学习代理(第 4 部分):在 MQL5 中组织类中的函数·综合运用
📘

开发具有 RestAPI 集成的 MQL5 强化学习代理(第 4 部分):在 MQL5 中组织类中的函数·综合运用

第 3/3 篇

HTTP 响应里的 BOM 与状态码分流

MT5 的 WebRequest 拿到的是 uchar 数组,直接 CharArrayToString 可能在头部吃到 UTF-8 的 BOM(EF BB BF 三字节),导致 JSON 解析失败。下面这段处理函数会在转换前扫描前 3 字节,遇到 BOM 就跳过起始索引。 成功分支只认 HTTP 200–299,其余一律丢给错误处理器;错误里 res == -1 代表请求层本身没发出去,这时用 GetLastError 拿底层错误码,而不是去读响应体。 BOM 检测循环用 MathMin(size, 3) 封顶,避免空响应越界;跳过逻辑是遇到任一 BOM 字节就后移,碰到非 BOM 字节立即 break,所以混合脏数据也只会跳到第一个合法字符。 开 MT5 把下面代码塞进你的 HTTP 封装类,印一段返回体看看开头是否还带乱码,外汇和贵金属行情接口普遍返回 UTF-8,高风险品种下错包概率不低。

MQL5 / C++
  ArrayResize(data, StringToCharArray(payload, data, class="num">0, WHOLE_ARRAY) - class="num">1);
  class="kw">return WebRequest("POST", url, m_headers, m_timeout, data, result, result_headers);
}
class="type">int HttpRequest::ProcessResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[])
{
  if (res >= class="num">200 && res <= class="num">299)
    class="kw">return responseProcessor.ProcessSuccessResponse(out, result);
  class="kw">return responseProcessor.ProcessErrorResponse(res, out, result);
}
class="type">int HttpResponseProcessor::ProcessResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[])
{
  if (res >= class="num">200 && res <= class="num">299)
    class="kw">return ProcessSuccessResponse(out, result);
  class="kw">return ProcessErrorResponse(res, out, result);
}
class="type">int HttpResponseProcessor::ProcessSuccessResponse(class="type">class="kw">string &out, class="type">uchar &result[])
{
  class="type">int size = ArraySize(result);
  class="type">int start_index = DetectAndSkipBOM(result, size);
  out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);
  class="kw">return class="num">0;
}
class="type">int HttpResponseProcessor::ProcessErrorResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[])
{
  ResetLastError();
  if (res == -class="num">1)
    class="kw">return GetLastError();
  else if (res >= class="num">100 && res <= class="num">511)  class=class="str">"cmt">// Errors HTTP
  {
    out = CharArrayToString(result);
    Print(out);
    class="kw">return res;
  }
  class="kw">return res;
}
class="type">int HttpResponseProcessor::DetectAndSkipBOM(class="type">uchar &result[], class="type">int size)
{
  class="type">int start_index = class="num">0;
  for (class="type">int i = class="num">0; i < MathMin(size, class="num">3); i++)
  {
    if (result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
      start_index = i + class="num">1;
    else
      class="kw">break;
  }
  class="kw">return start_index;
}

「把 HTTP 收发拆成两套接口更省心」

在 MT5 里做外部行情或信号接口,直接堆 WebRequest 容易把请求逻辑和报文解析搅成一团。下面这套抽象把「发」和「收」彻底分开:IHttpRequest 只管怎么把请求打出去,IHttpResponseProcessor 只管把回来的字节流变成能用的字符串。 IHttpRequest 里 Request 是统一入口,method 传 "GET"/"POST",url 和 payload 分开,query_param 单独留参,方便后面拼签名或时间戳。ValidateMethod 用来拦掉非法动词,PerformGetRequest / PerformPostRequest 则把底层 char 数组和 uchar 结果数组暴露出来,便于你接自己的代理或重试逻辑。 IHttpResponseProcessor 的四个虚函数才是重点:ProcessResponse 按 res 码分流成功或错误;DetectAndSkipBOM 处理 UTF-8 头那 3 个字节(0xEF 0xBB 0xBF),不跳掉的话 JSON 解析大概率首字符报错。 HttpResponseProcessorBase 只是把上面四个方法标成 override=0 的纯虚壳,逼你在具体品种(比如 XAUUSD 的 REST 行情源)里写自己的实现。外汇和贵金属接口普遍限频且延迟敏感,这类抽象能让你把重试和 BOM 处理集中改一处,不用每根 K 线都重写。

MQL5 / C++
class=class="str">"cmt">//| Interface for HttpRequest                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
interface IHttpRequest
  {
class="kw">public:
   class="kw">virtual class="type">int      Request(class="type">class="kw">string method, class="type">class="kw">string &out, const class="type">class="kw">string url, const class="type">class="kw">string payload = "", const class="type">class="kw">string query_param = "") = class="num">0;
   class="kw">virtual class="type">int      ValidateMethod(class="type">class="kw">string method) = class="num">0;
   class="kw">virtual class="type">int      PerformGetRequest(class="type">char &data[], class="type">uchar &result[], class="type">class="kw">string &result_headers, const class="type">class="kw">string url, const class="type">class="kw">string query_param) = class="num">0;
   class="kw">virtual class="type">int      PerformPostRequest(class="type">char &data[], class="type">uchar &result[], class="type">class="kw">string &result_headers, const class="type">class="kw">string url, const class="type">class="kw">string payload) = class="num">0;
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Interface for HttpResponseProcessor                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
interface IHttpResponseProcessor
  {
class="kw">public:
   class="kw">virtual class="type">int      ProcessResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[]) = class="num">0;
   class="kw">virtual class="type">int      ProcessSuccessResponse(class="type">class="kw">string &out, class="type">uchar &result[]) = class="num">0;
   class="kw">virtual class="type">int      ProcessErrorResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[]) = class="num">0;
   class="kw">virtual class="type">int      DetectAndSkipBOM(class="type">uchar &result[], class="type">int size) = class="num">0;
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Abstract base class for HttpResponseProcessor                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class HttpResponseProcessorBase : class="kw">public IHttpResponseProcessor
  {
class="kw">public:
                     HttpResponseProcessorBase() {};
   class="kw">virtual class="type">int      ProcessResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[]) class="kw">override = class="num">0;
   class="kw">virtual class="type">int      ProcessSuccessResponse(class="type">class="kw">string &out, class="type">uchar &result[]) class="kw">override = class="num">0;
   class="kw">virtual class="type">int      ProcessErrorResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[]) class="kw">override = class="num">0;
   class="kw">virtual class="type">int      DetectAndSkipBOM(class="type">uchar &result[], class="type">int size) class="kw">override = class="num">0;
  };

◍ HttpRequestBase 如何统一收口 REST 调用

在 MT5 里做 AIGC 行情分析,EA 往往要自己发 HTTP 请求拉外部模型或数据接口。把公共逻辑抽到一个抽象基类里,可以避免每个具体请求类都重写超时、表头与响应处理。 下面这段基类默认超时设成 5000 毫秒,表头串允许构造时传入;若没指定响应处理器,会自动 new 一个 HttpResponseProcessor。注意构造函数里判断的是 responseProcessor 指针是否为 NULL,但声明时并未初始化该成员,实际编码要补上初始化避免野指针。 Request 方法是统一入口:先 ValidateMethod 拦掉非法动词,GET 走 PerformGetRequest,POST 走 PerformPostRequest,两者都是纯虚函数留给子类实现。返回码 ≥0 才进 ProcessResponse,否则直接填错误串。 开 MT5 把这段贴进类模块,先补全 responseProcessor 初始化,再写个子类实现两个 Perform 虚函数,就能用同一套超时与表头策略测通你的第一个外部接口。外汇与贵金属接口调用受网络与平台限制,失败概率不低,需自行压测。

MQL5 / C++
class HttpRequestBase : class="kw">public IHttpRequest
  {
class="kw">protected:
   class="type">class="kw">string            m_headers;
   class="type">int               m_timeout;
   IHttpResponseProcessor *responseProcessor;
class="kw">public:
                     HttpRequestBase(class="type">class="kw">string headers = "", class="type">int timeout = class="num">5000) : m_headers(headers), m_timeout(timeout)
     {
      if(responseProcessor == NULL)
        {
         responseProcessor = new HttpResponseProcessor();
        }
     }
   class="kw">virtual class="type">int       Request(class="type">class="kw">string method, class="type">class="kw">string &out, const class="type">class="kw">string url, const class="type">class="kw">string payload = "", const class="type">class="kw">string query_param = "") class="kw">override;
   class="kw">virtual class="type">int       ValidateMethod(class="type">class="kw">string method) class="kw">override;
   class="kw">virtual class="type">int       PerformGetRequest(class="type">char &data[], class="type">uchar &result[], class="type">class="kw">string &result_headers, const class="type">class="kw">string url, const class="type">class="kw">string query_param) class="kw">override = class="num">0;
   class="kw">virtual class="type">int       PerformPostRequest(class="type">char &data[], class="type">uchar &result[], class="type">class="kw">string &result_headers, const class="type">class="kw">string url, const class="type">class="kw">string payload) class="kw">override = class="num">0;
   class="kw">virtual class="type">int       ProcessResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[]) = class="num">0;
   };

class="type">int HttpRequestBase::Request(class="type">class="kw">string method, class="type">class="kw">string &out, const class="type">class="kw">string url, const class="type">class="kw">string payload, const class="type">class="kw">string query_param) class="kw">override
  {
   if(!ValidateMethod(method))
     {
      out = "Método HTTP inválido.";
      class="kw">return -class="num">1;
     }
   class="type">char data[];
   class="type">uchar result[];
   class="type">class="kw">string result_headers;
   class="type">int res = -class="num">1;
   if(method == "GET")
      res = PerformGetRequest(data, result, result_headers, url, query_param);
   else
      if(method == "POST")
         res = PerformPostRequest(data, result, result_headers, url, payload);
   if(res >= class="num">0)
      class="kw">return ProcessResponse(res, out, result);
   else
     {
      out = "Error when making HTTP request.";

给 MT5 的 WebRequest 套一层类型壳

把裸调 WebRequest 的散代码收进类里,能少踩不少坑。下面这段实现把 GET / POST 的共性收在 HttpRequestBase,具体收发交给 HttpRequest 派生类,验证层只放行 GET 与 POST 两种 method。

ValidateMethod 直接返回布尔式 (method=="GET"method=="POST"),非这两种动词的请求在基类阶段就被挡掉,省得后面 WebRequest 抛错误码。

PerformGetRequest 对 query_param 做了长度判断:StringLen(query_param) > 0 时才拼 "?" + query_param 进 URL,否则走无参 GET。注意带参版本传的是 data 与 StringLen(query_param),无参版本传的是 m_headers 与 m_timeout,两套 WebRequest 重载签名别写混。 POST 这边若 m_headers 为空,自动补 "Content-Type: application/json\r\n";再用 ArrayResize(data, StringToCharArray(payload, data, 0, WHOLE_ARRAY) - 1) 把 JSON 字符串塞进 char 数组并去掉结尾 \0。外汇 / 贵金属 EA 接外部信号源时,这套封装能降低请求层出错概率,但网络失败仍可能发生,需自行处理重试。

MQL5 / C++
class="type">int HttpRequestBase::ValidateMethod(class="type">class="kw">string method)
  {
   class="kw">return (method == "GET" || method == "POST");
  }

class HttpRequest : class="kw">public HttpRequestBase
  {
class="kw">public:
                       HttpRequest(class="type">class="kw">string headers = "", class="type">int timeout = class="num">5000) : HttpRequestBase(headers, timeout) {}
   class="kw">virtual class="type">int       PerformGetRequest(class="type">char &data[], class="type">uchar &result[], class="type">class="kw">string &result_headers, const class="type">class="kw">string url, const class="type">class="kw">string query_param) class="kw">override;
   class="kw">virtual class="type">int       PerformPostRequest(class="type">char &data[], class="type">uchar &result[], class="type">class="kw">string &result_headers, const class="type">class="kw">string url, const class="type">class="kw">string payload) class="kw">override;
   class="kw">virtual class="type">int       ProcessResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[]) class="kw">override;
  };

class="type">int HttpRequest::PerformGetRequest(class="type">char &data[], class="type">uchar &result[], class="type">class="kw">string &result_headers, const class="type">class="kw">string url, const class="type">class="kw">string query_param)
  {
   if(StringLen(query_param) > class="num">0)
      class="kw">return WebRequest("GET", url + "?" + query_param, NULL, NULL, m_timeout, data, StringLen(query_param), result, result_headers);
   class="kw">return WebRequest("GET", url, m_headers, m_timeout, data, result, result_headers);
  }

class="type">int HttpRequest::PerformPostRequest(class="type">char &data[], class="type">uchar &result[], class="type">class="kw">string &result_headers, const class="type">class="kw">string url, const class="type">class="kw">string payload)
  {
   if(m_headers == "")
     m_headers = "Content-Type: application/json\r\n";
   ArrayResize(data, StringToCharArray(payload, data, class="num">0, WHOLE_ARRAY) - class="num">1);

「响应码分流与 UTF-8 去 BOM 的细节」

HTTP 交互里最容易被忽略的是状态码边界。上面这段实现把 200~299 统一视作成功,直接进成功分支;其余一律丢给错误处理,逻辑干净,但你得知道 MT5 的 WebRequest 返回码就是标准 HTTP 状态码,外网接口若返回 301 重定向不会自动跟随。 成功响应里有个坑:服务端返回的字节流常带 BOM(字节顺序标记)。DetectAndSkipBOM 先算出偏移量,再用 CharArrayToString 从偏移位按 CP_UTF8 转成字符串,WHOLE_ARRAY 表示吃到尾。不跳 BOM,你解析 JSON 时首字符可能是 \uFEFF,EA 或 XAUUSD 行情接口踩过这坑的人不少。 开 MT5 自建个 HttpResponseProcessor 继承类,把 ProcessSuccessResponse 打印出 start_index,连真实接口跑一次,确认跳过的是 3 字节还是 0 字节,比看文档实在。外汇与贵金属接口调用涉网络与杠杆,属高风险操作,返回异常时别硬解包。

MQL5 / C++
class="type">int HttpRequest::ProcessResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[])
  {
   if(res >= class="num">200 && res <= class="num">299)
      class="kw">return responseProcessor.ProcessSuccessResponse(out, result);
   class="kw">return responseProcessor.ProcessErrorResponse(res, out, result);
  }

class HttpResponseProcessor : class="kw">public HttpResponseProcessorBase
  {
class="kw">public:
   class="kw">virtual class="type">int      ProcessResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[]) class="kw">override;
   class="kw">virtual class="type">int      ProcessSuccessResponse(class="type">class="kw">string &out, class="type">uchar &result[]) class="kw">override;
   class="kw">virtual class="type">int      ProcessErrorResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[]) class="kw">override;
   class="kw">virtual class="type">int      DetectAndSkipBOM(class="type">uchar &result[], class="type">int size) class="kw">override;
   };

class="type">int      HttpResponseProcessor::ProcessResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[])
  {
   if(res >= class="num">200 && res <= class="num">299)
      class="kw">return ProcessSuccessResponse(out, result);
   class="kw">return ProcessErrorResponse(res, out, result);
  }

class="type">int      HttpResponseProcessor::ProcessSuccessResponse(class="type">class="kw">string &out, class="type">uchar &result[]) class="kw">override
  {
   class="type">int size = ArraySize(result);
   class="type">int start_index = DetectAndSkipBOM(result, size);
   out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);
   class="kw">return class="num">0;
  }

◍ HTTP响应里的报错与BOM头处理

在 MT5 里做 REST 调用,返回码和报文内容经常不是一回事。下面这段重载把 res 小于 0、100~511 区间、以及其他情况分清楚:res 为 -1 时直接取系统错误码;落在 HTTP 状态码范围(100~511,共 412 个码)就把 uchar 数组转成字符串打印出来,方便看服务端返回的报错体。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int      HttpResponseProcessor::ProcessErrorResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[]) class="kw">override
  {
   ResetLastError();
   if(res == -class="num">1)
     class="kw">return GetLastError();
   else
     if(res >= class="num">100 && res <= class="num">511)  class=class="str">"cmt">// Errors HTTP
       {
        out = CharArrayToString(result);
        Print(out);
        class="kw">return res;
       }
   class="kw">return res;
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int      HttpResponseProcessor::DetectAndSkipBOM(class="type">uchar &result[], class="type">int size) class="kw">override
  {
   class="type">int start_index = class="num">0;
   for(class="type">int i = class="num">0; i < MathMin(size, class="num">3); i++)
     {
      if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
        start_index = i + class="num">1;
      else
        class="kw">break;
     }
   class="kw">return start_index;
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
ProcessErrorResponse 逐行看:先 ResetLastError 清掉旧错误;res 等于 -1 返回 GetLastError 拿到底层网络错误;res 在 100~511 之间说明是 HTTP 层响应,把 result 转字符串赋给 out 并 Print,返回原 res;其余情况原样返回 res。 DetectAndSkipBOM 处理的是 UTF-8 BOM 头:只检查前 3 字节(MathMin(size,3)),遇到 0xef、0xbb、0xbf 任一就后移 start_index,连续命中才累加,断掉就停。返回值是跳过 BOM 后的起始下标,后续解析 JSON 前先吃这个偏移,能避免首字符乱码。外汇与贵金属接口调用属高风险操作,返回解析异常可能引发行情误判,建议先在模拟账户验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int      HttpResponseProcessor::ProcessErrorResponse(class="type">int res, class="type">class="kw">string &out, class="type">uchar &result[]) class="kw">override
  {
   ResetLastError();
   if(res == -class="num">1)
     class="kw">return GetLastError();
   else
     if(res >= class="num">100 && res <= class="num">511)  class=class="str">"cmt">// Errors HTTP
       {
        out = CharArrayToString(result);
        Print(out);
        class="kw">return res;
       }
   class="kw">return res;
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int      HttpResponseProcessor::DetectAndSkipBOM(class="type">uchar &result[], class="type">int size) class="kw">override
  {
   class="type">int start_index = class="num">0;
   for(class="type">int i = class="num">0; i < MathMin(size, class="num">3); i++)
     {
      if(result[i] == 0xef || result[i] == 0xbb || result[i] == 0xbf)
        start_index = i + class="num">1;
      else
        class="kw">break;
     }
   class="kw">return start_index;
  };
class=class="str">"cmt">//+------------------------------------------------------------------+

把 HTTP 请求拆成类来跑

在 MT5 里做外部数据对接,最怕把请求、解析、错误处理全糊在一个脚本里。把 HttpResponseProcessor 和 HttpRequest 各自封成类,重复发请求时直接 new 一个对象就能复用,比复制整段网络代码安全得多。 下面这段示例用模拟响应验证了三件事:成功 JSON 的解析、404 错误的归口处理、以及 UTF-8 BOM 头跳过。BOM 检测测试里,数组前三个字节是 0xEF 0xBB 0xBF,紧跟 'a' 'b' 'c',DetectAndSkipBOM 返回起始索引为 3,说明 BOM 被正确跳过。 OnInit 里实际发了一个 GET 到 jsonplaceholder 的 /posts/1,超时设 5000 毫秒,Print 出的 Response Code 和 Output 可以直接在专家日志里核对。外汇与贵金属行情接口若走 HTTP,同样高风险,返回结构异常时类封装能让排查范围缩到单个方法。 类模块化不是花活:相关方法和属性捆在一起,哪块出错改哪块,不用在全文件里捞逻辑。测试函数随脚本启动自动跑,你改完 ProcessSuccessResponse 的逻辑,重开 MT5 就能看到 Success Test 的输出变化。

MQL5 / C++
class="type">void TestProcessSuccessResponse()
{
  HttpResponseProcessor processor;
  class="type">class="kw">string output;
  class="type">uchar result[];
  class=class="str">"cmt">// Simulate a successful response in JSON format
  class="type">class="kw">string mockResponse = "{\"status\": \"success\", \"data\": \"Sample data\"}";
  StringToCharArray(mockResponse, result);
  class=class="str">"cmt">// Process the simulated response
  processor.ProcessSuccessResponse(output, result);
  class=class="str">"cmt">// Check the output
  Print("Success Test: ", output);
}
class="type">void TestProcessErrorResponse()
{
  HttpResponseProcessor processor;
  class="type">class="kw">string output;
  class="type">uchar result[];
  class=class="str">"cmt">// Simulate an error response(class="num">404 Not Found)
  class="type">class="kw">string mockResponse = "class="num">404 Not Found";
  StringToCharArray(mockResponse, result);
  class=class="str">"cmt">// Process an error response
  processor.ProcessErrorResponse(class="num">404, output, result);
  class=class="str">"cmt">// Check the output
  Print("Error Test: ", output);
}
class="type">void TestDetectAndSkipBOM()
{
  HttpResponseProcessor processor;
  class="type">uchar result[class="num">6] = {0xEF, 0xBB, 0xBF, &class="macro">#x27;a&class="macro">#x27;, &class="macro">#x27;b&class="macro">#x27;, &class="macro">#x27;c&class="macro">#x27;}; class=class="str">"cmt">// &class="macro">#x27;abc&class="macro">#x27; with BOM UTF-class="num">8
  class=class="str">"cmt">// Detect and skip the BOM(Byte Order Mark)
  class="type">int startIndex = processor.DetectAndSkipBOM(result, ArraySize(result));
  class=class="str">"cmt">// Check the initial index after BOM
  Print("Start index after BOM: ", startIndex); class=class="str">"cmt">// Expected: class="num">3
}
class="type">int OnInit()
{
  RunTests(); class=class="str">"cmt">// Run the tests
  HttpRequest httpRequest("", class="num">5000); class=class="str">"cmt">// Create an instance of the HttpRequest class
  class="type">class="kw">string output; class=class="str">"cmt">// Variable to store the output
  class=class="str">"cmt">// Perform the GET request
  class="type">int responseCode = httpRequest.Request("GET", output, "https:class=class="str">"cmt">//jsonplaceholder.typicode.com/posts/class="num">1");
  class=class="str">"cmt">// Show the result
  Print("Response Code: ", responseCode);
  Print("Output: ", output);
}

「给 HTTP 响应处理器写三组自测」

把外部 API 的返回塞进 EA 之前,先在本机跑通响应解析逻辑,能省掉实盘里一半的莫名断线。下面三组测试函数覆盖了成功报文、错误码、BOM 头三种典型情况,直接抄进脚本用 OnStart 调用即可。 成功路径用 JSON 模拟串 {"status": "success", "data": "Sample data"},经 StringToCharArray 转 uchar 数组后丢给 ProcessSuccessResponse,Print 出来的 output 应当原样带回业务字段。 错误路径构造了 404 Not Found 明文,连同错误码 404 一起喂给 ProcessErrorResponse;若输出能区分出非 2xx 的异常分支,说明你的容错没写反。 UTF-8 的 BOM 头常被忽略:测试里手搓了 6 字节数组 {0xEF,0xBB,0xBF,'a','b','c'},DetectAndSkipBOM 返回的 startIndex 应为 3,也就是跳过前三个 BOM 字节从 'a' 起读。外汇与贵金属行情接口返回带 BOM 时,不跳头会解析错位,属于高风险隐性 bug。

MQL5 / C++
class="macro">#class="kw">property copyright "Copyright class="num">2023, Lejjo Digital"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include "Requests.mqh"

class="type">void TestProcessSuccessResponse()
  {
   HttpResponseProcessor processor;
   class="type">class="kw">string output;
   class="type">uchar result[];
class=class="str">"cmt">// Simulate a success report(example with JSON)
   class="type">class="kw">string mockResponse = "{\"status\": \"success\", \"data\": \"Sample data\"}";
   StringToCharArray(mockResponse, result);
class=class="str">"cmt">// Call ProcessSuccessResponse
   processor.ProcessSuccessResponse(output, result);
class=class="str">"cmt">// Check that the output is as expected
   Print("Success Test: ", output);
  }

class="type">void TestProcessErrorResponse()
  {
   HttpResponseProcessor processor;
   class="type">class="kw">string output;
   class="type">uchar result[];
class=class="str">"cmt">// Simulate an error response(example with error class="num">404)
   class="type">class="kw">string mockResponse = "class="num">404 Not Found";
   StringToCharArray(mockResponse, result);
class=class="str">"cmt">// Call ProcessErrorResponse with a simulated error code
   processor.ProcessErrorResponse(class="num">404, output, result);
class=class="str">"cmt">// Verify that the output is as expected
   Print("Error Test: ", output);
  }

class="type">void TestDetectAndSkipBOM()
  {
   HttpResponseProcessor processor;
   class="type">uchar result[class="num">6] = {0xEF, 0xBB, 0xBF, &class="macro">#x27;a&class="macro">#x27;, &class="macro">#x27;b&class="macro">#x27;, &class="macro">#x27;c&class="macro">#x27;}; class=class="str">"cmt">// &class="macro">#x27;abc&class="macro">#x27; with BOM UTF-class="num">8
class=class="str">"cmt">// Call DetectAndSkipBOM
   class="type">int startIndex = processor.DetectAndSkipBOM(result, ArraySize(result));
class=class="str">"cmt">// Check if the start index is correct
   Print("Índice de início após BOM: ", startIndex); class=class="str">"cmt">// Expected: class="num">3
  }

◍ 把 HTTP 响应解析塞进 EA 初始化跑通

想在 MT5 里直接验证前面写的响应解析逻辑,最省事的办法是在 OnInit 里先跑一组单元测试,再发一次真实请求看回传。下面这段就是把测试函数和运行入口接起来的完整骨架。 RunTests() 里依次调用了成功响应、错误响应、BOM 头跳过三个用例,能在 EA 加载时就把解析器的边界情况覆盖掉。若其中任一断言失败,日志会直接暴露,不用等行情来了才发现有脏数据。 OnInit 中先 RunTests(),随后创建 HttpRequest 实例,超时设为 5000 毫秒,对 jsonplaceholder 的 /posts/1 发 GET。Print 出的 responseCode 与 output 就是你可以贴到小布里做二次分析的原始素材。 外汇与贵金属行情接口常带 BOM 或异常状态码,这类解析器上线前建议本地先跑通再接实盘,杠杆品种高风险,解析错一层就可能下错单。

MQL5 / C++
class="type">void RunTests()
  {
   TestProcessSuccessResponse();
   TestProcessErrorResponse();
   TestDetectAndSkipBOM();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
class=class="str">"cmt">// Run HttpResponseProcessor tests
   RunTests();
class=class="str">"cmt">// Create the HttpRequest class instance
   HttpRequest httpRequest("", class="num">5000);
class=class="str">"cmt">// Variables to store the output
   class="type">class="kw">string output;
class=class="str">"cmt">// Perform the GET request
   class="type">int responseCode = httpRequest.Request("GET", output, "https:class=class="str">"cmt">//jsonplaceholder.typicode.com/posts/class="num">1");
class=class="str">"cmt">// Show the result
   Print("Response Code: ", responseCode);
   Print("Output: ", output);
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

别急着下结论

把过程式脚本重构成基于类的架构,在 MT5 里不是赶时髦,而是应对策略膨胀的现实选择。一个 EA 从几十行长到上千行之后,函数间共享全局变量引发的隐性 bug 概率会明显上升,而封装成类能把职责边界锁死。 模块化和封装最直接的好处是排错路径缩短:某个信号计算异常,你只需进对应类的成员函数看状态,不必在几十个独立函数里翻全局数组。代码重用则体现在跨项目搬运——同一套风控类,在黄金 EA 和欧美 EA 里改几个参数就能跑。 不过 OOP 不是银弹。MQL5 终归跑在终端的单线程事件循环里,类拆得太碎反而增加调用栈开销,回测时可能拖慢 tick 处理。新手先拿一个小指标练手封装,比直接重构整个交易系统更稳。 外汇和贵金属杠杆高、跳空频繁,任何架构优化都只解决工程问题,不解决市场方向判断。开 MT5 建个测试类,把原有指标计算塞进去,跑一轮历史数据对比输出差异,比读十篇软文都实在。

常见问题

多半是服务端带了 UTF-8 的 BOM 头,在解析前把前三个字节(EF BB BF)剥掉即可正常读取。
按状态码分流:401 倾向是鉴权失效先刷新 token,500 倾向服务端异常可做退避重试,不要混为一谈。
可以,小布能替你监控接口响应码并标注异常,同时自动识别去 BOM,你只看结论不用扒原始报文。
更省心。收发分离后复用度高、出错点清晰,改超时或加签名只动一处,调试也更快。
能把字符串参数和裸调用收口成带类型的接口,减少手误传参,后续接强化学习代理也更好维护。