在MQL中操作套接字,或者如何成为信号提供者(基础篇)
📘

在MQL中操作套接字,或者如何成为信号提供者(基础篇)

第 1/3 篇

「用 MQL5 套接字把自己变成信号源」

MT5 客户端原生支持通过 Socket 接口对外通信,这意味着你写的 EA 不只能下单,还能把账户状态、信号标记实时推到外部服务,变相成为信号提供者。

  • 年 9 月 9 日官方示例帖发布后,该主题在站内累计获得约 7045 次浏览与 94 条讨论,说明用 MQL 直接玩套接字的需求一直有真实交易者埋单。

想在 MT5 里验证这条路,先确认终端设置中「允许 WebRequest」与「允许 DLL 导入」已开启,否则 Socket 函数会直接返回无效句柄。外汇与贵金属杠杆高、滑点无常,实盘前务必在模拟环境把断线重连逻辑跑满。

套接字是 MT5 联网的底层命脉

早上你点开 MetaTrader 5,终端第一时间就建了套接字连上交易服务器;你开浏览器、收邮件、校时,背后也都是套接字在反复建连和断连。从 1982 年 BSD 套接字问世到现在,它一直是网络的神经末梢,MT5 的实时报价和分布式计算全靠这套机制。 外汇和贵金属交易靠网络连接行情与订单,套接字异常会直接断流,这类依赖外部网络的环境本身高风险,参数和超时设置要留余量。 想动手验证很简单:开 MT5 按 F4 进 MetaEditor,新建 EA 后在 OnInit 里写 SocketCreate 调一次,编译挂到图表,看日志是否返回句柄。本文后续会把维基百科和 MSDN 里的标准结构逐一翻成 MQL5 可调用的代码。

◍ 把 WinAPI 套接字函数搬进 MQL5

MQL5 在内存模型上和 C 系 WinAPI 是近亲,所以把 TCP/UDP 所需的那几个 Winsock 函数原样移植成 .mqh 并不绕。TCP 客户端只要走完 WSAStartup 初始化、socket 建套接字、ioctlsocket 设非阻塞、connect 连服务器、recv/send 收发、closesocket 加 WSACleanup 收尾这六步;服务器端多一个 bind 和 listen,再用 accept 接连接。UDP 更短,没有握手环节,客户端用 sendto/recvfrom 直接跑,服务端仅多 bind 一个端口。 移植的核心技巧是用字节数组冒充结构指针。例如 WSAStartup 在 MQL 里声明成 int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData[]),其中 LPWSADATA 被定义为 char&,实际传进去的是一块大小等于 WSAData 结构的字节数组。调用前 ArrayResize(wsaData, sizeof(WSAData)) 把数组撑到结构体积,函数写回的数据再靠类型转换抠出来。 MAKEWORD(2,2) 在代码里直接展开成 ((WORD)(((BYTE)(2)&0xff))|((WORD)((BYTE)(2)&0xff))<<8),对应请求 Winsock 2.2 版——这是 MT5 里跑网络通讯的最低可用基线。socket 函数常数也提前钉死:AF_INET=2、SOCK_STREAM=1、IPPROTO_TCP=6,少查一次 MSDN 就少一处笔误。 别把结构当玄学 任何结构本质就是定长内存块,MQL 里用同尺寸 char 数组承接,WinAPI 侧照常读写。connect 要的 sockaddr 也是同样套路:从 MSDN 抄结构,指针全换成数组地址,sin_addr 的 union 在 MQL 里落为 8 字节整数表达。 下面这段是移植完可直接存成 WinAPI_Sockets.mqh 的骨架,开 MT5 建个脚本 include 它就能验证编译通过:

MQL5 / C++
WINAPI:
class="type">int WSAAPI WSAStartup(_In_ WORD wVersionRequested,  _Out_ LPWSADATA lpWSAData);
class="macro">#define MAKEWORD(a, b)       ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << class="num">8))
class="macro">#define BYTE          class="type">uchar
class="macro">#define WORD          class="type">class="kw">ushort
class="macro">#define DWORD         class="type">int
class="macro">#define DWORD_PTR     class="type">class="kw">ulong
class="kw">struct WSAData
{
  WORD wVersion;
  WORD  wHighVersion;
  class="type">char szDescription[WSADESCRIPTION_LEN+class="num">1];
  class="type">char szSystemStatus[WSASYS_STATUS_LEN+class="num">1];
  class="type">class="kw">ushort iMaxSockets;
  class="type">class="kw">ushort iMaxUdpDg;
  class="type">char  lpVendorInfo[];
}
class="macro">#define LPWSADATA          class="type">char&
MQL:
class="type">int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData[]);
class="type">char wsaData[]; class=class="str">"cmt">// 未来结构的字节数组
ArrayResize(wsaData, class="kw">sizeof(WSAData)); class=class="str">"cmt">// 把它的大小设置为结构的大小 
WSAStartup(MAKEWORD(class="num">2,class="num">2), wsaData); class=class="str">"cmt">// 调用函数
WINAPI:
SOCKET WSAAPI socket(_In_ class="type">int af, _In_ class="type">int type, _In_ class="type">int protocol);
class="macro">#define SOCKET           class="type">uint
class="macro">#define INVALID_SOCKET(SOCKET)(~class="num">0)
class="macro">#define SOCKET_ERROR(-class="num">1)
class="macro">#define NO_ERROR      class="num">0
class="macro">#define AF_INET          class="num">2 class=class="str">"cmt">// 互联网家族: UDP, TCP, 等等.
class="macro">#define SOCK_STREAM      class="num">1
class="macro">#define IPPROTO_TCP      class="num">6
MQL:
class="type">int ioctlsocket(SOCKET s, class="type">int cmd, class="type">int &argp);
WINAPI:

「MT5 套接字函数与底层宏定义对照」

在 MT5 里做行情外联或自建数据中转,绕不开 WinAPI 的 socket 接口封装。MQL5 把 connect、send、recv、sendto、recvfrom、closesocket、WSACleanup 都重写成可直接调用的形式,参数里用 LPSOCKADDR(即 char& 别名)替代原生指针结构,降低脚本层踩内存的概率。 原生 sockaddr 只占 16 字节:sa_family 用 ushort 标记地址家族,sa_data 是 14 字节原始地址。实际跑 TCP 时多用 sockaddr_in,其中 sin_zero 固定留 8 字节补齐,sin_port 为 ushort,sin_addr 内 s_addr 是 ulong 型 IPv4 地址——这套布局跟 POSIX 标准一致,开 MT5 用 sizeof(sockaddr_in) 验证会得到 16 字节。 下面的宏是硬骨头:AF_INET 值是 2,SOCK_STREAM 是 1,IPPROTO_TCP 是 6,SOMAXCONN 限制 128 个挂起连接。INVALID_SOCKET 被定义成 uint 的全 1(~0),SOCKET_ERROR 为 -1,写重连逻辑时要用这两个常量判断返回值,别自己写 -1 魔法数。 MAKEWORD(a,b) 把两个字节拼成 ushort,低位 a 高位 b 左移 8 位,WSAStartup 传版本号就靠它。WSADESCRIPTION_LEN 256 与 WSASYS_STATUS_LEN 128 决定你缓冲区数组该开多大,开小了 recv 回来会被截断。

MQL5 / C++
class="type">int connect(_In_ SOCKET s, _In_ class="kw">const class="kw">struct sockaddr *name, _In_ class="type">int namelen);
class="kw">struct sockaddr
{
    class="type">class="kw">ushort sa_family; class=class="str">"cmt">// 地址家族.
    class="type">char sa_data[class="num">14]; class=class="str">"cmt">// 一共14个字节的直接地址.
};
class="macro">#define LPSOCKADDR      class="type">char&
class="kw">struct sockaddr_in
{
    class="type">short   sin_family;
    class="type">class="kw">ushort  sin_port;
    class="kw">struct  in_addr sin_addr;
    class="type">char    sin_zero[class="num">8];
};
class="kw">struct in_addr
{
    class="type">class="kw">ulong s_addr;
};
MQL:
class="type">int connect(SOCKET s, LPSOCKADDR name[], class="type">int namelen);
WINAPI:
class="type">int send(_In_ SOCKET s, _In_ class="kw">const class="type">char* buf, _In_ class="type">int len, _In_ class="type">int flags);
class="type">int recv(_In_  SOCKET s, _Out_    class="type">char* buf, _In_ class="type">int len, _In_ class="type">int flags);
MQL:
class="type">int send(SOCKET s, class="type">char& buf[], class="type">int len, class="type">int flags);
class="type">int recv(SOCKET s, class="type">char& buf[], class="type">int len, class="type">int flags);
WINAPI:
class="type">int recvfrom(_In_  SOCKET s, _Out_ class="type">char* buf, _In_ class="type">int len, _In_ class="type">int flags, _Out_ class="kw">struct sockaddr *from,
_Inout_opt_ class="type">int *fromlen);
class="type">int sendto(_In_ SOCKET s, _In_ class="kw">const class="type">char* buf, _In_ class="type">int len, _In_ class="type">int flags,  _In_ class="kw">const class="kw">struct sockaddr *to,
_In_         class="type">int tolen);
MQL:
class="type">int recvfrom(SOCKET s,class="type">char &buf[],class="type">int len,class="type">int flags,LPSOCKADDR from[],class="type">int &fromlen);
class="type">int sendto(SOCKET s,class="kw">const class="type">char &buf[],class="type">int len,class="type">int flags,LPSOCKADDR to[],class="type">int tolen);
MQL:
class="type">int closesocket(SOCKET s);
class="type">int WSACleanup();
class="macro">#define BYTE               class="type">uchar
class="macro">#define WORD               class="type">class="kw">ushort
class="macro">#define DWORD              class="type">int
class="macro">#define DWORD_PTR          class="type">class="kw">ulong
class="macro">#define SOCKET             class="type">uint
class="macro">#define MAKEWORD(a, b)     ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << class="num">8))
class="macro">#define WSADESCRIPTION_LEN   class="num">256
class="macro">#define WSASYS_STATUS_LEN    class="num">128
class="macro">#define INVALID_SOCKET(SOCKET)(~class="num">0)
class="macro">#define SOCKET_ERROR(-class="num">1)
class="macro">#define NO_ERROR        class="num">0
class="macro">#define SOMAXCONN       class="num">128
class="macro">#define AF_INET         class="num">2 class=class="str">"cmt">// 互联网家族: UDP, TCP, 等等.
class="macro">#define SOCK_STREAM     class="num">1
class="macro">#define IPPROTO_TCP     class="num">6
class="macro">#define SD_RECEIVE      0x00

在 MT5 里直接撸原生 socket 结构

想在 MQL5 里做低延迟行情中转或私有信号通道,绕不开 Windows 的 Ws2_32.dll。上面这组宏和结构体就是把 Berkeley socket 的底层契约搬进 MT5 编译环境,不依赖任何第三方库。 #define 里 IOCPARM_MASK 是 0x7f,意味着 ioctl 类的参数长度必须压在 128 字节以内;_IOW('f',126,int) 生成的 FIONBIO 命令码用来切换阻塞与非阻塞模式,做高频轮询时这行直接决定线程会不会卡死在 recv 上。 WSAData 和 sockaddr_in 两个 struct 必须按字段宽度对齐:sockaddr_in 里 sin_zero 固定 8 字节填充,sa_data 在通用 sockaddr 里是 14 字节,ref_sockaddr 用 2+14 的 char 数组做等价映射,错一个字节 socket 函数就会返回 10014(WSAEFAULT)。 import 段列出了 WSAStartup 到 listen 的 15 个原生函数声明。开 MT5 新建一个 EA,把这段原样贴进 include 头文件,编译后调一次 WSAStartup(MAKEWORD(2,2), wsa) 即可验证环境是否通——外汇与贵金属杠杆交易本身高风险,这类底层网络代码若写错还会引发终端崩溃,务必先在模拟账户跑。

MQL5 / C++
class="macro">#define SD_SEND        0x01
class="macro">#define SD_BOTH        0x02
class="macro">#define IOCPARM_MASK   0x7f            class=class="str">"cmt">/* 参数必须 < class="num">128 个字节 */
class="macro">#define IOC_IN         0x80000000      class=class="str">"cmt">/* 复制输入参数 */
class="macro">#define _IOW(x,y,t)    (IOC_IN|(((class="type">int)class="kw">sizeof(t)&IOCPARM_MASK)<<class="num">16)|((x)<<class="num">8)|(y))
class="macro">#define FIONBIO        _IOW(&class="macro">#x27;f&class="macro">#x27;, class="num">126, class="type">int) class=class="str">"cmt">/* 设置/清除 非阻塞 i/o */
class=class="str">"cmt">//------------------------------------------------------------------    class="kw">struct WSAData
class="kw">struct WSAData
  {
   WORD                wVersion;
   WORD                wHighVersion;
   class="type">char                szDescription[WSADESCRIPTION_LEN+class="num">1];
   class="type">char                szSystemStatus[WSASYS_STATUS_LEN+class="num">1];
   class="type">class="kw">ushort              iMaxSockets;
   class="type">class="kw">ushort              iMaxUdpDg;
   class="type">char                lpVendorInfo[];
  };
class="macro">#define LPWSADATA                class="type">char&
class=class="str">"cmt">//------------------------------------------------------------------    class="kw">struct sockaddr_in
class="kw">struct sockaddr_in
  {
   class="type">class="kw">ushort              sin_family;
   class="type">class="kw">ushort              sin_port;
   class="type">class="kw">ulong               sin_addr; class=class="str">"cmt">//class="kw">struct in_addr { class="type">class="kw">ulong s_addr; };
   class="type">char                sin_zero[class="num">8];
  };
class=class="str">"cmt">//------------------------------------------------------------------    class="kw">struct sockaddr
class="kw">struct sockaddr
  {
   class="type">class="kw">ushort              sa_family; class=class="str">"cmt">// 地址家族.
   class="type">char                sa_data[class="num">14]; class=class="str">"cmt">// 14个字节大小的直接地址.
  };
class="macro">#define LPSOCKADDR      class="type">char&
class="kw">struct ref_sockaddr { class="type">char ref[class="num">2+class="num">14]; };
class=class="str">"cmt">//------------------------------------------------------------------    class="kw">import Ws2_32.dll
class="macro">#class="kw">import "Ws2_32.dll"
class="type">int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData[]);
class="type">int WSACleanup();
class="type">int WSAGetLastError();
class="type">class="kw">ushort htons(class="type">class="kw">ushort hostshort);
class="type">class="kw">ulong inet_addr(class="type">char& cp[]);
class="type">class="kw">string inet_ntop(class="type">int Family,class="type">class="kw">ulong &pAddr,class="type">char &pStringBuf[],class="type">uint StringBufSize);
class="type">class="kw">ushort ntohs(class="type">class="kw">ushort netshort);
SOCKET socket(class="type">int af,class="type">int type,class="type">int protocol);
class="type">int ioctlsocket(SOCKET s,class="type">int cmd,class="type">int &argp);
class="type">int shutdown(SOCKET s,class="type">int how);
class="type">int closesocket(SOCKET s);
class=class="str">"cmt">// 服务器端函数
class="type">int bind(SOCKET s,LPSOCKADDR name[],class="type">int namelen);
class="type">int listen(SOCKET s,class="type">int backlog);

◍ 把 socket 接口接回 EA 里跑通

上面这组声明把底层 socket 函数重新暴露给 MQL5 的 EA 或脚本,省去自己写 DLL 的麻烦。accept 用于服务端套接字等待客户端连入,addr 与 addrlen 以引用方式回传对端地址,connect 则由客户端发起三次握手,name 与 namelen 描述目标地址结构。 send 和 recv 是实际收发数据的两个调用,buf 用 char 数组引用传递,len 指明本次字节数,flags 一般填 0。在 MT5 里直接 #import 进这些符号后,你可以在 OnTimer 里轮询 recv,把行情外的外部信号接进策略。 外汇与贵金属杠杆高、滑点突变频繁,用 socket 引外部数据时要先在小周期回测确认延迟不击穿止损逻辑。开 MT5 新建脚本,把这几行贴进 #import 段,编译无报错即说明本机 socket 库可用。

MQL5 / C++
SOCKET accept(SOCKET s,LPSOCKADDR addr[],class="type">int &addrlen);
class=class="str">"cmt">// 客户端函数
class="type">int connect(SOCKET s,LPSOCKADDR name[],class="type">int namelen);
class="type">int send(SOCKET s,class="type">char &buf[],class="type">int len,class="type">int flags);
class="type">int recv(SOCKET s,class="type">char &buf[],class="type">int len,class="type">int flags);
class="macro">#class="kw">import

常见问题

可以用套接字在本地或服务器间直连推送信号数据,自己写个转发服务即可,不依赖平台信号市场。
MQL 本身无原生 socket,需通过导入 WinAPI 网络函数并定义对应结构体和宏,才能在 EA 里使用。
可以,小布能盯你信号服务的端口存活和推送延迟,异常时直接弹提醒,你不用自己写心跳检测。
地址族或端口填错会连接失败或发往错误端,建议先打印十六进制结构再比对,别盲调。
正常,回测环境禁外部网络,套接字只能在实盘或模拟盘实时环境跑通,测试时请用 demo 实连。