MetaTrader 5 的 WebSocket 使用 Windows API(基础篇)
在 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 终端内存会缓慢泄漏。
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,调试连接失败时直接比对数值就能定位。
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,能跑通就说明你的终端头文件与该定义一致。
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 客户端。外汇与贵金属杠杆高、滑点大,这类自研接入仅限技术验证,实盘信号延迟风险自行评估。
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[]);