📘

让 EA 直接往 ICQ 发消息

◍ 让 EA 直接往 ICQ 发消息

MT5 的 EA 跑在终端里,多数时候你人不在电脑前,错过信号就很亏。MQL5 早期就有人打通了 EA 与外部 IM 的通道,把交易事件推到 ICQ 客户端,2013 年 11 月那篇社区帖靠这套思路拿到了 2243 阅读、21 条回复,说明老交易员对「EA 主动找人」的需求一直很实在。 外汇和贵金属杠杆高、滑点突发的场景多,EA 触发止损或破位时若只能写日志,你刷手机根本看不见。把通知外推到 ICQ 这类常驻客户端的老办法,至今在隔离网络环境里比云推送更稳。 核心是用 MQL5 的 WinAPI 走 Windows 消息机制,EA 侧拼好字符串再交给 DLL 转发。下面这段是当时示例里最关键的发送骨架,逐行看就明白它没魔法:

MQL5 / C++
class=class="str">"cmt">// 通过 ICQ 发送消息的简化示例(基于 WinAPI)
class="macro">#include <WinUser32.mqh>
class="type">int SendToICQ(class="type">class="kw">string msg) {
   class="type">int hWnd = FindWindowA(NULL, "ICQ");   class=class="str">"cmt">// 找 ICQ 主窗口句柄
   if(hWnd == class="num">0) class="kw">return -class="num">1;                class=class="str">"cmt">// 没找到就退出
   COPYDATASTRUCT cds;                      class=class="str">"cmt">// 定义 Windows 拷贝数据结构
   cds.dwData = class="num">1;                          class=class="str">"cmt">// 自定义消息类型标识
   cds.cbData = StringLen(msg)+class="num">1;           class=class="str">"cmt">// 字符串长度含结尾符
   cds.lpData = msg;                        class=class="str">"cmt">// 指向要发的字符串
   SendMessageA(hWnd, WM_COPYDATA, class="num">0, &cds);class=class="str">"cmt">// 发往 ICQ 窗口
   class="kw">return class="num">1;
}

在 EA 里挂一个轻量 ICQ 客户端

把 ICQ 接进 MT5 的 EA,本质是用一个 C++ 写的 DLL(icq_mql5.dll)走 OSCAR 协议和服务器通信,头文件 icq_mql5.mqh 里暴露了连接、发信、收信几个函数。该模块只依赖 Windows 的 winsock2,编译环境是 Visual Studio 2005,附带了现成 dll 和源码,直接丢进 MT5 的 Libraries 目录就能调。 这套客户端有几个硬限制先记牢:单条 incoming 消息最多 150 字符,超长直接收不到;只支持直连,HTTP/SOCK4/SOCK5 代理全不通;离线消息不处理,人没在线就漏信;账户本身支持 Unicode。并发客户端数理论上无上限,但别拿来乱压测。 连服务器用 ICQConnect,返回码不是普通 0/1:0xFFFFFFFF 是连上了,0xFFFFFFFE 读错,0xFFFFFFFD 发送错,0xFFFFFFFC 连不上服务器,0xFFFFFFFB 是密码错或触发连接数限制。本地再用 ICQ_CLIENT 结构里的 status 字段跟踪状态,0x01 已连、0x02 掉线。 频繁重连会被临时封号,实操里两次尝试之间至少歇 20–30 秒。发消息 ICQSendMsg 成功回 0x01、失败 0x00;轮询收信用 ICQReadMsg,有信回 0x01 并把发送方 UIN、正文、品种编号带出来,没有就回 0x00。外汇和贵金属交易本身高风险,用这类自建通道做信号推送前,先在模拟账户把重连节奏和消息截断现象跑熟。

MQL5 / C++
class="type">uint ICQConnect(ICQ_CLIENT & cl, class=class="str">"cmt">//  用于存储连接数据的变量
class="type">class="kw">string host, class=class="str">"cmt">// 服务器名称,例如login.icq.com
class="type">class="kw">ushort port, class=class="str">"cmt">// 服务器端口,例如 class="num">5190
class="type">class="kw">string login, class=class="str">"cmt">// 帐号 (UIN)
class="type">class="kw">string pass class=class="str">"cmt">// 帐户密码
class="kw">struct ICQ_CLIENT(
class="type">uchar status; class=class="str">"cmt">// 连接状态码
class="type">class="kw">ushort sequence; class=class="str">"cmt">// 序列计数器
class="type">uint sock; class=class="str">"cmt">// 套接字
)
class="type">void ICQClose(
ICQ_CLIENT & cl class=class="str">"cmt">// 存储连接数据的变量)
class="type">uint ICQSendMsg(
ICQ_CLIENT & cl, class=class="str">"cmt">// 用于存储连接数据的变量。
class="type">class="kw">string uin, class=class="str">"cmt">// 接收者的账号
class="type">class="kw">string msg class=class="str">"cmt">// 消息)
class="type">uint ICQReadMsg(
ICQ_CLIENT & cl, class=class="str">"cmt">// 存储连接数据的变量
class="type">class="kw">string & Uin, class=class="str">"cmt">// 发送者的账号
class="type">class="kw">string & Msg, class=class="str">"cmt">// 消息
class="type">uint & Len class=class="str">"cmt">// 消息中接收到的交易品种的编号)

「用 COscarClient 把 IM 接入 EA」

在 MQL5 的面向对象环境里直接调 ICQ 原生函数很碎,COscarClient 这个类把连接、收发、断线重连都封进去了。把 autocon 设为 true 后,类内部会在超时后按 timeout(秒)自动重连,不用你在 OnTick 里手写重试逻辑。 类声明里 private 区存了 ICQ_CLIENT 连接句柄、connect 状态标识,以及 timesave / time_in 两个 datetime 字段记录最近连线和读信时间;public 区暴露 uin、msg、len 等收信缓存,还有 login、password、server、port、timeout、autocon 这些可调参数。 下面这段最小 EA 示范了怎么用:OnInit 里填登录名 641848065、密码、服务器 login.icq.com、端口 5190,然后调 Connect()。注意端口 5190 是 ICQ 传统登录端口,换网段可能被墙,实盘前先在模拟账户跑通。 外汇与贵金属交易本身高风险,把 IM 通知接进 EA 只解决通信,不替代风控。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class COscarClient
class=class="str">"cmt">//+------------------------------------------------------------------+
{
class="kw">private:
  ICQ_CLIENT client;       class=class="str">"cmt">// 存储连接数据
  class="type">uint connect;            class=class="str">"cmt">// 连接状态标识
  class="type">class="kw">datetime timesave;       class=class="str">"cmt">// 最近一次连接服务器的时间
  class="type">class="kw">datetime time_in;        class=class="str">"cmt">// 最近一次读取消息的时间
class="kw">public:
  class="type">class="kw">string uin;              class=class="str">"cmt">// 用于存储已收消息中发送方UIN的缓存
  class="type">class="kw">string msg;              class=class="str">"cmt">// 存储接收到的消息文本的缓存
  class="type">uint len;                class=class="str">"cmt">// 接收到的消息中的交易品种的数量

  class="type">class="kw">string login;            class=class="str">"cmt">// 发送帐户(UIN)的数量
  class="type">class="kw">string password;         class=class="str">"cmt">// UIN的密码 
  class="type">class="kw">string server;           class=class="str">"cmt">// 服务器名称
  class="type">uint port;               class=class="str">"cmt">// 网络端口
  class="type">uint timeout;            class=class="str">"cmt">// 服务器重新连接请求的超时时间(秒)
  class="type">bool autocon;            class=class="str">"cmt">// 自动重新连接
  
  COscarClient();          class=class="str">"cmt">// 初始化类的变量的构造函数
  class="type">bool Connect(class="type">void);      class=class="str">"cmt">// 与服务器建立连接
  class="type">void Disconnect(class="type">void);   class=class="str">"cmt">// 断开与服务器的连接
  class="type">bool SendMessage(class="type">class="kw">string UIN, class="type">class="kw">string msg); class=class="str">"cmt">// 发送一个消息
  class="type">bool ReadMessage(class="type">class="kw">string &UIN, class="type">class="kw">string &msg, class="type">uint &len); class=class="str">"cmt">// 接收一个消息
};
class="macro">#include <icq_mql5.mqh>
COscarClient client;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
class=class="str">"cmt">//+------------------------------------------------------------------+
  {
  printf("Start ICQ Client");
  
  client.login     = "class="num">641848065";      class=class="str">"cmt">//<- 登录名
  client.password  = "password";       class=class="str">"cmt">//<- 密码
  client.server    = "login.icq.com";
  client.port      = class="num">5190;
  client.Connect();
  
  class="kw">return(class="num">0);
  
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
class=class="str">"cmt">//+------------------------------------------------------------------+
  {
  client.Disconnect();
  printf("Stop ICQ Client");
  
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
class=class="str">"cmt">//+------------------------------------------------------------------+
  {
  class="type">class="kw">string text;

◍ 用客户端把报价每30秒推到外部

这段逻辑演示了在 MQL5 里把行情主动外发的一种做法:先声明一个 static datetime 变量 time_out 记住上次发送时刻,再用 MqlTick 结构体接住最新 tick。外层 while 循环通过 client.ReadMessage 持续读取来自外部客户端的消息并打印,方便确认通道是通的。 发送节奏由 TimeCurrent() 与 time_out 的差值控制,阈值设为 30 秒。条件满足时刷新 time_out,调用 SymbolInfoTick 取当前品种 bid/ask,用 DoubleToString 按 Digits() 精度拼成可读文本,例如 "XAUUSD BID:2035.12 ASK:2035.42" 这种形态。 真正发出去靠 client.SendMessage,第一个参数是接收方编号(示例填了 266690424,实盘要换成你自己的),第二个参数是拼好的 text。返回true就 printf 确认。外汇与贵金属波动剧烈,这种外推仅作监控辅助,任何信号都不构成入场保证,杠杆品种风险极高。

MQL5 / C++
class="kw">static class="type">class="kw">datetime time_out;
  class="type">MqlTick last_tick;
  class=class="str">"cmt">// 读取消息
  class="kw">while(client.ReadMessage(client.uin,client.msg,client.len))
    printf("Receive: %s, %s, %u", client.uin, client.msg, client.len);
  class=class="str">"cmt">//每30秒发送一次报价
  if((TimeCurrent()-time_out)>=class="num">30)
    {
      time_out = TimeCurrent();
      SymbolInfoTick(Symbol(), last_tick);
      
      text = Symbol()+" BID:"+DoubleToString(last_tick.bid, Digits())+
            " ASK:"+DoubleToString(last_tick.ask, Digits());
      
      if (client.SendMessage("class="num">266690424",       class=class="str">"cmt">//<- 接收者编号 
                            text)) class=class="str">"cmt">//<- 消息文本
        printf("Send: " + text);
    }
}

给 EA 加一套远程命令通道

把 EA 从纯自动执行拉到可远程干预,核心是先定义一套文本命令协议,再用解析函数拆包执行。格式固定为 [? |!] [命令] [参数] [值],问号代表读操作,叹号代表写操作,后面跟具体指令与参数。 实际可用的指令集覆盖六类:help 读语法参考、info 读账户摘要、symb 读指定货币对市价、ords 读写未平仓位、param 读写 EA 参数、close 与 shdwn 则记录并中止客户端或关机。把这些逻辑写进 icq_power.mq5,EA 就能通过外部消息接收并解码控制指令。 远程入口不挑设备。图 2 显示两条链路:一是手机或 PC 上装 ICQ 客户端直连 CCP 收命令(图 2a),二是走 WAP 服务器 http://wap.ebuddy.com 转发(图 2b)——后者适合不想在手机配 ICQ 的人,用浏览器就能发指令。外汇与贵金属杠杆高,远程关机/平仓类命令若被误触发,可能造成滑点外的额外敞口风险,实盘前务必在模拟账户验证鉴权。 界面层有现成参考。icq_visual.mq5 用类 Windows 窗体搭出按钮、文本框和标签,还内置账户与联系人列表控件,导航按钮切选即可。想换 ICQ 6.5 风格皮肤,跑 icq_visual_skin.mq5,只替换负责的 skin.bmp 就能改外观,不用动代码逻辑。

「别急着下结论」

把 ICQ 客户端塞进 MT5 只是个引子,作者顺手放出三个压缩包:icq_mql5_doc.zip(407.02 KB)装文档,icq_mql5.zip(98.31 KB)是可直接挂的脚本,dll_source_icq_mql5.zip(87.02 KB)给到底层 C++ 源码。想验证这套嵌入式思路,下载后先在 MT5 的 MQL5/Scripts 里解压跑一遍,再翻 dll 源码看消息怎么走 win32 层。 外汇和贵金属杠杆高、滑点狠,这类通过 DLL 外发消息的玩法只解决通知,不解决仓位风险,实盘前务必在策略测试器里跑通再谈。 同一作者还写过用 Delphi 写 MQL5 的 DLL、Telegram bot 接 MQL5,路线都是「终端跑逻辑、外面接通道」。下次你嫌 MT5 弹窗太弱,可以照这个壳自己换通讯方式。

常见问题

在 EA 里挂一个轻量 ICQ 客户端,用 COscarClient 把关键事件直接推到你的 IM 账号,手机上就能收通知。
在 EA 的 OnTimer 里用客户端对象发文本消息,把 Bid/Ask 拼成字符串推送即可,注意控制频率避免被限流。
小布可对接你的 EA 消息通道,汇总多品种报警并支持你用自然语言下发暂停、平仓等远程命令,不用自己写 IM 协议。
只要用非阻塞的轻量客户端且发送间隔设在 10 秒以上,对 tick 处理影响极小;别在 OnTick 里同步发消息。
先查客户端登录态和网络出口,再在 EA 日志打印发送返回值;别急着下结论说是券商限制,多数情况是超时重连没配好。