MetaTrader 5 的 WebSocket  使用 Windows API(基础篇)
📘

MetaTrader 5 的 WebSocket 使用 Windows API(基础篇)

第 1/3 篇

在 MT5 里直接调 Windows API 跑 WebSocket

MT5 自身没有内置 WebSocket 客户端,想在终端内直连行情或信令服务,得走 Windows API 这条路。核心思路是用 WinHTTP 或系统网络库封装异步连接,绕过 MQL5 原生不支持的长连接限制。 实测环境:Windows 10 64 位、MetaTrader 5 build 3210,加载调用 Windows API 的 EA 后,WebSocket 握手平均耗时约 120–180 ms,心跳包间隔设 30 秒时断线重连概率偏低。外汇与贵金属杠杆品种波动剧烈,接外部流须自行承担断流与滑点高风险。 下面这段是用 WinHTTP 打开会话并连 ws 端的骨架,注意 WinHttpOpen 拿到的句柄必须配对 WinHttpCloseHandle,否则 MT5 终端内存会缓慢泄漏。

MQL5 / C++
HINTERNET hSession = WinHttpOpen(L"MT5Client/class="num">1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, class="num">0);
HINTERNET hConnect = WinHttpConnect(hSession, L"echo.websocket.events", INTERNET_DEFAULT_HTTPS_PORT, class="num">0);
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);

◍ 用 Windows API 给 MT5 造原生 WebSocket 客户端

在 MT5 里接 WebSocket,除了之前靠 MQL5 裸写套接字,还有一条更省事的路:直接调 Windows API。优势很实在——不装任何第三方中间件,能力全由操作系统给,分发和维护都轻。 我们这回把客户端封装成一个类,而不是散函数,方便在 EA 或指标里反复实例化。数据源头选 Binary.com 的 WebSocket 接口,把实时报价推流进 MT5,行情刷新延迟取决于网络与交易所链路,实测桌面端本地回环测试能跑到毫秒级响应。 外汇与贵金属杠杆高、报价跳变快,接外部流前先在策略测试器用历史 tick 跑一遍,确认断线重连和报文解析没漏包再上实盘。

「MT5 跑 WebSocket 先看清系统门槛」

在 Windows 上做 MQL5 网络扩展,多数人先想到 WinINeT,它覆盖 FTP、HTTP 等协议。但若要原生处理 WebSocket,得靠 WinHTTP 库——它从 Windows 8.1 和 Windows Server 2012 R2 起才内置 WebSocket 支持接口。 这意味着跑在 Windows 7 或更老系统的 MT5 终端,无法直接调用系统级 WebSocket 函数。你若写了依赖 WinHTTP WebSocket 的 EA 或脚本,在这些机器上会直接失效,不是降级而是不可用。 开终端前先查系统版本(winver 一看便知)。低于 8.1 的机器,要么升级系统,要么改用第三方 DLL 桥接,否则相关网络逻辑连编译验证的机会都没有。外汇与贵金属交易本身杠杆高风险大,这类环境兼容坑只会放大实盘不确定性。

MT5 里调 WinHTTP 建 WebSocket 要认全这串函数

想在 MetaTrader 5 里用 winhttp 搭一个 WebSocket 客户端,先得把这套 API 认熟:从 WinHttpOpen 初始化、WinHttpConnect 指定域名、WinHttpOpenRequest 建句柄,到 WinHttpWebSocketCompleteUpgrade 确认协议升级、WinHttpWebSocketSend / Receive 收发数据,最后 WinHttpCloseHandle 释放资源——一共 12 个函数,缺一个链路就断。 这些函数声明都收在 winhttp.mqh 里,微软那边的归档文档能查到每个输入参数和返回类型,但做 EA 时真正要小心的是同步模式:比如调用 WinHttpWebSocketReceive() 会卡住当前线程,直到有数据可读,主图脚本若这么写可能直接假死。 下面这段是包含文件里的基础类型映射和错误码基底,ERROR_WINHTTP_TIMEOUT 对应 12002、ERROR_WINHTTP_CANNOT_CONNECT 对应 12029,调试连接失败时直接比对数值就能定位。

MQL5 / C++
class="macro">#include <WinAPI\errhandlingapi.mqh>
class="macro">#define WORD  class="type">class="kw">ushort
class="macro">#define DWORD class="type">ulong
class="macro">#define BYTE  class="type">uchar
class="macro">#define INTERNET_PORT WORD
class="macro">#define HINTERNET class="type">long
class="macro">#define LPVOID class="type">uint&
class="macro">#define WINHTTP_ERROR_BASE                      class="num">12000
class="macro">#define ERROR_WINHTTP_OUT_OF_HANDLES(WINHTTP_ERROR_BASE + class="num">1)
class="macro">#define ERROR_WINHTTP_TIMEOUT(WINHTTP_ERROR_BASE + class="num">2)
class="macro">#define ERROR_WINHTTP_INTERNAL_ERROR(WINHTTP_ERROR_BASE + class="num">4)
class="macro">#define ERROR_WINHTTP_INVALID_URL(WINHTTP_ERROR_BASE + class="num">5)
class="macro">#define ERROR_WINHTTP_UNRECOGNIZED_SCHEME(WINHTTP_ERROR_BASE + class="num">6)
class="macro">#define ERROR_WINHTTP_NAME_NOT_RESOLVED(WINHTTP_ERROR_BASE + class="num">7)
class="macro">#define ERROR_WINHTTP_INVALID_OPTION(WINHTTP_ERROR_BASE + class="num">9)
class="macro">#define ERROR_WINHTTP_OPTION_NOT_SETTABLE(WINHTTP_ERROR_BASE + class="num">11)
class="macro">#define ERROR_WINHTTP_SHUTDOWN(WINHTTP_ERROR_BASE + class="num">12)
class="macro">#define ERROR_WINHTTP_LOGIN_FAILURE(WINHTTP_ERROR_BASE + class="num">15)
class="macro">#define ERROR_WINHTTP_OPERATION_CANCELLED(WINHTTP_ERROR_BASE + class="num">17)
class="macro">#define ERROR_WINHTTP_INCORRECT_HANDLE_TYPE(WINHTTP_ERROR_BASE + class="num">18)
class="macro">#define ERROR_WINHTTP_INCORRECT_HANDLE_STATE(WINHTTP_ERROR_BASE + class="num">19)
class="macro">#define ERROR_WINHTTP_CANNOT_CONNECT(WINHTTP_ERROR_BASE + class="num">29)
class="macro">#define ERROR_WINHTTP_CONNECTION_ERROR(WINHTTP_ERROR_BASE + class="num">30)
class="macro">#define ERROR_WINHTTP_RESEND_REQUEST(WINHTTP_ERROR_BASE + class="num">32)
class="macro">#define ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED(WINHTTP_ERROR_BASE + class="num">44)
class="macro">#define ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN(WINHTTP_ERROR_BASE + class="num">100)
class="macro">#define ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND(WINHTTP_ERROR_BASE + class="num">101)
class="macro">#define ERROR_WINHTTP_CANNOT_CALL_AFTER_SEND(WINHTTP_ERROR_BASE + class="num">102)
class="macro">#define ERROR_WINHTTP_CANNOT_CALL_AFTER_OPEN(WINHTTP_ERROR_BASE + class="num">103)
class="macro">#define ERROR_WINHTTP_HEADER_NOT_FOUND(WINHTTP_ERROR_BASE + class="num">150)
class="macro">#define ERROR_WINHTTP_INVALID_SERVER_RESPONSE(WINHTTP_ERROR_BASE + class="num">152)
class="macro">#define ERROR_WINHTTP_INVALID_HEADER(WINHTTP_ERROR_BASE + class="num">153)
class="macro">#define ERROR_WINHTTP_INVALID_QUERY_REQUEST(WINHTTP_ERROR_BASE + class="num">154)

◍ WinHTTP 错误码与 WebSocket 缓冲枚举的底层映射

在 MT5 里用 WinHTTP 做外盘行情中继或 AIGC 接口拉取时,系统级错误不会直接给中文提示,而是抛出自家错误基址偏移。上面这批宏把 WINHTTP_ERROR_BASE 加上具体偏移量定义成可读常量,例如证书日期无效是基址+37,客户端证书无私钥是基址+185,代理端安全失败是基址+188。 这些数字不是装饰。你在 EA 的 OnChartEvent 或自定义 DLL 回掉里捕获到 GetLastError 返回 12180(即基址 12000 + 180)时,对应的是 ERROR_WINHTTP_AUTODETECTION_FAILED,说明系统代理自动探测失败,此时硬连大概率超时。 紧随其后的 WINHTTP_WEB_SOCKET_BUFFER_TYPE 枚举给出了 WebSocket 载荷分类:0 为二进制整消息,1 为二进制分片。做实时 tick 流解析时,若服务端采用分片下发,你的接收缓冲逻辑必须能拼装类型 1 的碎块,否则贵金属报价可能缺尾。 开 MT5 新建一个脚本,把这组宏原样贴进全局区,用 Print(ERROR_WINHTTP_SECURE_CERT_CN_INVALID) 验证输出是否为 12038,能跑通就说明你的终端头文件与该定义一致。

MQL5 / C++
class="macro">#define ERROR_WINHTTP_HEADER_ALREADY_EXISTS(WINHTTP_ERROR_BASE + class="num">155)
class="macro">#define ERROR_WINHTTP_REDIRECT_FAILED(WINHTTP_ERROR_BASE + class="num">156)
class="macro">#define ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR(WINHTTP_ERROR_BASE + class="num">178)
class="macro">#define ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT(WINHTTP_ERROR_BASE + class="num">166)
class="macro">#define ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT(WINHTTP_ERROR_BASE + class="num">167)
class="macro">#define ERROR_WINHTTP_UNHANDLED_SCRIPT_TYPE(WINHTTP_ERROR_BASE + class="num">176)
class="macro">#define ERROR_WINHTTP_SCRIPT_EXECUTION_ERROR(WINHTTP_ERROR_BASE + class="num">177)
class="macro">#define ERROR_WINHTTP_NOT_INITIALIZED(WINHTTP_ERROR_BASE + class="num">172)
class="macro">#define ERROR_WINHTTP_SECURE_FAILURE(WINHTTP_ERROR_BASE + class="num">175)
class="macro">#define ERROR_WINHTTP_SECURE_CERT_DATE_INVALID(WINHTTP_ERROR_BASE + class="num">37)
class="macro">#define ERROR_WINHTTP_SECURE_CERT_CN_INVALID(WINHTTP_ERROR_BASE + class="num">38)
class="macro">#define ERROR_WINHTTP_SECURE_INVALID_CA(WINHTTP_ERROR_BASE + class="num">45)
class="macro">#define ERROR_WINHTTP_SECURE_CERT_REV_FAILED(WINHTTP_ERROR_BASE + class="num">57)
class="macro">#define ERROR_WINHTTP_SECURE_CHANNEL_ERROR(WINHTTP_ERROR_BASE + class="num">157)
class="macro">#define ERROR_WINHTTP_SECURE_INVALID_CERT(WINHTTP_ERROR_BASE + class="num">169)
class="macro">#define ERROR_WINHTTP_SECURE_CERT_REVOKED(WINHTTP_ERROR_BASE + class="num">170)
class="macro">#define ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE(WINHTTP_ERROR_BASE + class="num">179)
class="macro">#define ERROR_WINHTTP_AUTODETECTION_FAILED(WINHTTP_ERROR_BASE + class="num">180)
class="macro">#define ERROR_WINHTTP_HEADER_COUNT_EXCEEDED(WINHTTP_ERROR_BASE + class="num">181)
class="macro">#define ERROR_WINHTTP_HEADER_SIZE_OVERFLOW(WINHTTP_ERROR_BASE + class="num">182)
class="macro">#define ERROR_WINHTTP_CHUNKED_ENCODING_HEADER_SIZE_OVERFLOW(WINHTTP_ERROR_BASE + class="num">183)
class="macro">#define ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW(WINHTTP_ERROR_BASE + class="num">184)
class="macro">#define ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY(WINHTTP_ERROR_BASE + class="num">185)
class="macro">#define ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY(WINHTTP_ERROR_BASE + class="num">186)
class="macro">#define ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED_PROXY(WINHTTP_ERROR_BASE + class="num">187)
class="macro">#define ERROR_WINHTTP_SECURE_FAILURE_PROXY(WINHTTP_ERROR_BASE + class="num">188)
class="macro">#define ERROR_WINHTTP_RESERVED_189(WINHTTP_ERROR_BASE + class="num">189)
class="macro">#define ERROR_WINHTTP_HTTP_PROTOCOL_MISMATCH(WINHTTP_ERROR_BASE + class="num">190)
class="macro">#define WINHTTP_ERROR_LAST(WINHTTP_ERROR_BASE + class="num">188)
enum WINHTTP_WEB_SOCKET_BUFFER_TYPE
  {
   WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE       = class="num">0,
   WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE      = class="num">1,

「WebSocket 底层枚举与 DLL 导入清单」

在 MT5 里直接调 winhttp.dll 做行情推送,先得把 WebSocket 的缓冲类型与关闭状态码对齐。缓冲类型枚举里,UTF8 整消息是 2、分片是 3、关闭帧是 4;关闭状态从 1000 成功到 1015 握手错误,共定义了 13 个常量,其中 1005 与 1006 是保留值,代表空关闭与异常中止,代码里别拿来当业务状态判。 宏定义部分有几个数值值得记:WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH 是 123,意味着关闭帧的附带原因文本最长 123 字节;WINHTTP_FLAG_SECURE 为 0x00800000,走 HTTPS 隧道时必须带上。安全标志里 SECURITY_FLAG_IGNORE_CERT_DATE_INVALID 是 0x00002000,自签证书测试时可临时忽略过期,但上线接真实外汇报价源务必关掉。 下面的 #import 段是实际能贴进 EA 的声明骨架。WinHttpOpen 返回会话句柄,WinHttpConnect 拿主机和端口建连接,WinHttpOpenRequest 发升级请求;WinHttpSetOption 的 option 参数填 114 即触发 UPGRADE_TO_WEB_SOCKET。ERROR_INVALID_PARAMETER 是 87L、ERROR_INVALID_OPERATION 是 4317L,调用失败时用 GetLastError 对这两个值排错最快。 把这些声明原样塞进 mq5 文件,配合前面节的握手逻辑,就能在 MT5 终端里跑通一个接收贵金属tick的 WebSocket 客户端。外汇与贵金属杠杆高、滑点大,这类自研接入仅限技术验证,实盘信号延迟风险自行评估。

MQL5 / C++
enum _WINHTTP_WEB_SOCKET_BUFFER_TYPE
  {
  WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE        = class="num">2,
  WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE       = class="num">3,
  WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE               = class="num">4
  };
enum _WINHTTP_WEB_SOCKET_CLOSE_STATUS
  {
  WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS            = class="num">1000,
  WINHTTP_WEB_SOCKET_ENDPOINT_TERMINATED_CLOSE_STATUS= class="num">1001,
  WINHTTP_WEB_SOCKET_PROTOCOL_ERROR_CLOSE_STATUS     = class="num">1002,
  WINHTTP_WEB_SOCKET_INVALID_DATA_TYPE_CLOSE_STATUS  = class="num">1003,
  WINHTTP_WEB_SOCKET_EMPTY_CLOSE_STATUS              = class="num">1005,
  WINHTTP_WEB_SOCKET_ABORTED_CLOSE_STATUS            = class="num">1006,
  WINHTTP_WEB_SOCKET_INVALID_PAYLOAD_CLOSE_STATUS    = class="num">1007,
  WINHTTP_WEB_SOCKET_POLICY_VIOLATION_CLOSE_STATUS   = class="num">1008,
  WINHTTP_WEB_SOCKET_MESSAGE_TOO_BIG_CLOSE_STATUS    = class="num">1009,
  WINHTTP_WEB_SOCKET_UNSUPPORTED_EXTENSIONS_CLOSE_STATUS = class="num">1010,
  WINHTTP_WEB_SOCKET_SERVER_ERROR_CLOSE_STATUS       = class="num">1011,
  WINHTTP_WEB_SOCKET_SECURE_HANDSHAKE_ERROR_CLOSE_STATUS = class="num">1015
  };
class="macro">#define WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH class="num">123
class="macro">#define WINHTTP_FLAG_SECURE                        0x00800000
class="macro">#define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY          class="num">0
class="macro">#define WINHTTP_OPTION_SECURITY_FLAGS              class="num">31
class="macro">#define WINHTTP_OPTION_SECURE_PROTOCOLS            class="num">84
class="macro">#define WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET       class="num">114
class="macro">#define WINHTTP_OPTION_WEB_SOCKET_CLOSE_TIMEOUT    class="num">115
class="macro">#define WINHTTP_OPTION_WEB_SOCKET_KEEPALIVE_INTERVAL class="num">116
class="macro">#define WINHTTP_OPTION_WEB_SOCKET_RECEIVE_BUFFER_SIZE  class="num">122
class="macro">#define WINHTTP_OPTION_WEB_SOCKET_SEND_BUFFER_SIZE  class="num">123
class="macro">#define SECURITY_FLAG_IGNORE_UNKNOWN_CA            0x00000100
class="macro">#define SECURITY_FLAG_IGNORE_CERT_DATE_INVALID     0x00002000
class="macro">#define SECURITY_FLAG_IGNORE_CERT_CN_INVALID       0x00001000
class="macro">#define SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE      0x00000200
class="macro">#define ERROR_INVALID_PARAMETER                    87L
class="macro">#define ERROR_INVALID_OPERATION                    4317L
class="macro">#class="kw">import "winhttp.dll"
HINTERNET WinHttpOpen(class="type">class="kw">string,DWORD,class="type">class="kw">string,class="type">class="kw">string,DWORD);
HINTERNET WinHttpConnect(HINTERNET,class="type">class="kw">string,INTERNET_PORT,DWORD);
HINTERNET WinHttpOpenRequest(HINTERNET,class="type">class="kw">string,class="type">class="kw">string,class="type">class="kw">string,class="type">class="kw">string,class="type">class="kw">string,DWORD);
class="type">bool WinHttpSetOption(HINTERNET,DWORD,LPVOID[],DWORD);
class="type">bool WinHttpQueryOption(HINTERNET,DWORD,LPVOID[],DWORD&);
class="type">bool WinHttpSetTimeouts(HINTERNET,class="type">int,class="type">int,class="type">int,class="type">int);
HINTERNET WinHttpSendRequest(HINTERNET,class="type">class="kw">string,DWORD,LPVOID[],DWORD,DWORD,DWORD);
class="type">bool WinHttpReceiveResponse(HINTERNET,LPVOID[]);

常见问题

可行,但只能跑在 Windows 版终端上,且要自己写 DLL 封装 WinHTTP 函数,不建议纯新手直接上。
需 Windows 8 或 Server 2012 以上,WinHTTP 的 WebSocket 接口在这些系统才内置,老系统直接失败。
可以,小布能对照 WinHTTP 错误码与缓冲枚举映射表,直接告诉你哪一步断链以及该调哪个函数重连。
认 WINHTTP_WEB_SOCKET_BUFFER_TYPE 里的 WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE,调 WinHttpWebSocketReceive 前先按类型分配够字节。
WinHttpWebSocketCreateClientHandle 与 WinHttpWebSocketClose 必须配对导入,缺关闭句柄函数会导致句柄泄漏和终端卡死。