市场模拟(第九部分):套接字(三)(基础篇)
📘

市场模拟(第九部分):套接字(三)(基础篇)

第 1/2 篇

◍ 用套接字把行情推到外部程序

MT5 的 MQL5 从 build 1880 起原生支持 Winsock 套接字,意味着 EA 或脚本能直接通过 TCP/UDP 把报价、指标值往外发,不必依赖文件桥或 DLL 中转。对做价格行为复盘的人,这等于把tick级数据实时喂给 Python 或自建数据库。 实际跑通要注意:套接字函数在 MQL5 里是异步的,SocketConnect 返回的是句柄而非连接结果,得在 OnTimer 或独立线程里轮询 SocketIsConnected 状态。MetaQuotes 官方示例里单客户端稳定吞吐约 200–300 报文/秒,超过这个量级建议做本地缓冲批发的设计。 外汇与贵金属保证金交易自带高杠杆与滑点风险,套接字转发只是传输手段,不代表任何策略胜率提升,实盘前务必在模拟账户验证延迟与丢包。

「指标里塞 socket 会卡死,聊天窗只能借壳」

MQL5 本身不支持纯语言层起服务器,但 socket 通信不依赖具体语言或系统,所以在 MT5 客户端里照样能跑通。上一篇文章已经验证:用指标挂一个迷你聊天窗,按钮和文本面板都画在独立子窗口,不会打断主图资产的计算流程。 不过有个硬限制——指标代码里不能直接调 socket 过程。MT5 平台内部机制决定了,这类调用一旦进指标执行流,就可能冻结或拖垮指标自身的运算性能,这一点在写 EA 或脚本时要反过来利用。 所以上一版可执行文件并不算能用的成品:聊天室还缺底层支撑。本篇先在 MQL5 侧把能做的部分补完,剩下不能满足的需求再靠外部程序补位,具体后面展开。 外汇与贵金属交易本就高波动高风险,这类自定义工具仅用于界面交互验证,别拿它替你做信号决策。

套接字连接类的私有变量与构造陷阱

做 MT5 上的迷你聊天或任何套接字通信 EA,第一步是把连接逻辑封成一个类。这个类把 m_Socket、m_buff、m_info、m_NewLine 全丢在 private 段(第 07–10 行),类外碰不到,逼你走成员函数。 构造函数(第 13 行)上来就在初始化列表里把 m_Socket 设成 INVALID_HANDLE、m_info 清空、m_NewLine 置 true(第 14–16 行)。这种写法不是炫技,是防自己忘了在函数体里初始化——实际开发中漏初始化主变量是高频低级 bug,初始化列表等于上了道保险。 第 18 行调 SocketCreate(),失败就 Print 报错;成功则第 19 行 SocketConnect(),连上了直接 return,因为连接已建立不用再跑后面的失败分支。两个动作都失败才走到第 20–21 行通知主程序。你在 EA 里只要测 m_Socket == INVALID_HANDLE 就能知道连没连上。 析构函数(第 24 行)只有一行:第 26 行 SocketClose() 释放句柄。MT5 里不主动关套接字,策略测试器反复加载 EA 会攒下孤儿句柄,这点在高频率重加载回测时尤其明显。

MQL5 / C++
class="num">01. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">02. class="macro">#class="kw">property copyright "Daniel Jose"
class="num">03. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">04. class C_Connection
class="num">05. {
class="num">06.    class="kw">private  :
class="num">07.      class="type">int      m_Socket;
class="num">08.      class="type">char     m_buff[];
class="num">09.      class="type">class="kw">string   m_info;
class="num">10.      class="type">bool     m_NewLine;
class="num">11.    class="kw">public  :
class="num">12. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">13.      C_Connection(class="type">class="kw">string addr, class="type">class="kw">ushort port, class="type">class="kw">ushort timeout = class="num">1000)
class="num">14.      :m_Socket(INVALID_HANDLE),
class="num">15.       m_info(""),
class="num">16.       m_NewLine(true)
class="num">17.      {
class="num">18.        if ((m_Socket = SocketCreate()) == INVALID_HANDLE) Print("Unable to create socket. Error: ", GetLastError());
class="num">19.        else if (SocketConnect(m_Socket, addr, port, timeout)) class="kw">return;
class="num">20.        else Print("Connection with the address [", addr,"] in port: ",port, " failed. Error code: ", GetLastError());

◍ 连接类的读写与析构收尾

这套 socket 封装的末尾,先把异常出口和回收逻辑钉死:第21行在连接失败分支里调 SetUserError(1) 主动抛自定义错误码,第24–27行的析构函数 ~C_Connection() 只做一件事——SocketClose(m_Socket) 释放句柄,避免 MT5 退出时残留半开连接。 写操作 ConnectionWrite 先把字符串转 char 数组并减1去掉结尾 \0,第31行 len 若小于0直接走失败打印。第33–36行三层 if 嵌套判断:句柄有效、长度非负、SocketSend 实际发出字节数等于 len 才返回 true,否则 Print("Connection Write: FAILED...") 并返回 false。 读逻辑 ConnectionRead 默认 timeout=500 毫秒,第46–50行遇到 m_NewLine 置位就清空 m_info 并复位标志。第51行用 SocketIsReadable 拿可读字节数喂给 SocketRead,第54行把读到的字节拼回字符串,第55–56行倒序扫缓冲区找 \n 或 \r 来置 m_NewLine。 返回时只在整行收满(m_NewLine 为真)才把 m_info 交出去,否则返回空串——这意味着上层调用者若没拿到内容,可能只是半包没收完,别急着断连。外汇与贵金属行情转发走 socket 本身有断连与丢包风险,实盘前请在策略测试器外开真实账户环境压一遍超时阈值。

MQL5 / C++
class="num">21.      SetUserError(class="num">1);
class="num">22.    }
class="num">23. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">24.      ~C_Connection()
class="num">25.      {
class="num">26.         SocketClose(m_Socket);
class="num">27.      }
class="num">28. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">29.      class="type">bool ConnectionWrite(class="type">class="kw">string szMsg)
class="num">30.      {
class="num">31.         class="type">int len = StringToCharArray(szMsg, m_buff) - class="num">1;
class="num">32.
class="num">33.         if (m_Socket != INVALID_HANDLE)
class="num">34.            if (len >= class="num">0)
class="num">35.               if (SocketSend(m_Socket, m_buff, len) == len)
class="num">36.                  class="kw">return true;
class="num">37.         Print("Connection Write: FAILED...");
class="num">38.
class="num">39.         class="kw">return class="kw">false;
class="num">40.      }
class="num">41. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">42.      class="kw">const class="type">class="kw">string ConnectionRead(class="type">class="kw">ushort timeout = class="num">500)
class="num">43.      {
class="num">44.         class="type">int ret;
class="num">45.
class="num">46.         if (m_NewLine)
class="num">47.         {
class="num">48.            m_info = "";
class="num">49.            m_NewLine = class="kw">false;
class="num">50.         }
class="num">51.         ret = SocketRead(m_Socket, m_buff, SocketIsReadable(m_Socket), timeout);
class="num">52.         if (ret > class="num">0)
class="num">53.         {
class="num">54.            m_info += CharArrayToString(m_buff, class="num">0, ret);
class="num">55.            for (ret--; (ret >= class="num">0) && (!m_NewLine); ret--)
class="num">56.               m_NewLine = (m_buff[ret] == &class="macro">#x27;\n&class="macro">#x27;) || (m_buff[ret] == &class="macro">#x27;\r&class="macro">#x27;);
class="num">57.         }
class="num">58.         
class="num">59.         class="kw">return (m_NewLine ? m_info : "");
class="num">60.      }
class="num">61. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">62. };
class="num">63. class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

指标主线程被阻塞会导致刷新停滞,应把 socket 收发放到独立线程或外部程序,指标只做轻量读取。
可利用聊天窗消息机制作为中转通道把数据抛给外部程序,但仅适合低频小包,别拿它当主链路。
小布可以盯着你的行情推送状态,断连或超时直接在品种页标红提醒,你不用自己写轮询。
套接字句柄和缓冲区若构造时没置空,读写可能命中野指针导致闪退,构造里必须显式清零。
系统句柄会泄漏,跑久了外部程序或终端变卡,析构里要关句柄并清缓冲。