市场模拟(第九部分):套接字(三)(基础篇)
◍ 用套接字把行情推到外部程序
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 会攒下孤儿句柄,这点在高频率重加载回测时尤其明显。
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 本身有断连与丢包风险,实盘前请在策略测试器外开真实账户环境压一遍超时阈值。
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">//+------------------------------------------------------------------+