MQL5用GSM调制解调器远程控EA
📟

MQL5用GSM调制解调器远程控EA

无网也能短信监控与平仓的实战开发

MQL5 编程 难度 · 进阶 2026-06-26 6 分钟阅读
#MQL5#GSM#EA开发#远程监控

为什么需要GSM调制解调器做EA远程监控

通常我们监视账户用手机MT终端、推送通知或ICQ,这些都依赖互联网。但在野外、机房断网或移动互联网不可用时,交易终端就成孤岛。本文描述的EA通过GSM调制解调器(或带调制解调器功能的旧手机)用短信保持联系:服务器掉线、账户盈亏、开平仓均可短信通知,甚至能收短信命令平仓。这给无法稳定联网的场景提供了兜底通道。

测试设备选用华为E1550广普USB上网卡,也用2003年西门子M55手机验证兼容性。只要系统识别为虚拟COM口且支持AT命令,就能用。注意旧手机可能不支持USSD查询命令。

✦ 创新延伸
从EA访问COM口不仅能连调制解调器,还可接LED屏、移动文本屏显示净值或报价,拓展可视化边界。

COM端口通信基础与TrComPort.dll

调制解调器连电脑装驱动后露出虚拟COM口(如COM3)。所有数据交换经此口。我们用自由分发的TrComPort.dll及其源码,它封装了配置、状态查询、收发。MQL5通过#import调用,需把DLL放Libraries目录,头文件放Include。

关键细节:PortID从0编号,COM3标识符是2。打开的端口若不正常关闭,往往重启系统才释放,所以去初始化必须调Close。大多数设备设8数据位、无校验、1停止位,EA仅需暴露COM索引和波特率两个输入参数。

#import "TrComPort.dll"
   int TrComPortOpen(int portnum);
   int TrComPortClose(int portid);
   int TrComPortSetConfig(int portid, TrComPortParameters& parameters);
   int TrComPortGetConfig(int portid, TrComPortParameters& parameters);
   int TrComPortWriteArray(int portid, uchar& buffer[], uint length, int timeout);
   int TrComPortReadArray(int portid, uchar& buffer[], uint length, int timeout);
   int TrComPortGetQueue(int portid, uint& input_queue, uint& output_queue);
#import
struct TrComPortParameters
{
   uint   DesiredParams;
   int    BaudRate;
   int    DefaultTimeout;
   uchar  ByteSize;
   uchar  StopBits;
   uchar  CheckParity;
   uchar  Parity;
   uchar  RtsControl;
   uchar  DtrControl;
};
bool InitComPort()
  {
   rx_cnt=0; tx_cnt=0; tx_err=0;
   PortID=TrComPortOpen(inp_com_port_index);
   if(PortID!=inp_com_port_index) { Print("Error opening COM"); return(false); }
   com_par.DesiredParams=tcpmpBaudRate|tcpmpDefaultTimeout|tcpmpByteSize|tcpmpStopBits|tcpmpCheckParity|tcpmpParity|tcpmpEnableRtsControl|tcpmpEnableDtrControl;
   if(TrComPortGetConfig(PortID,com_par)==-1) return(false);
   com_par.ByteSize=8; com_par.Parity=0; com_par.StopBits=0; com_par.DefaultTimeout=100; com_par.BaudRate=inp_com_baudrate;
   if(TrComPortSetConfig(PortID,com_par)==-1) return(false);
   return(true);
  }

收发模型:先GetQueue看接收缓冲字节数,再用ReadArray读入uchar数组转字符串;写用WriteArray发AT命令。超时可传-1用默认。错误返回负代码,详情见DLL源码文档。

uchar rx_buf[1024];
TrComPortGetQueue(PortID, rxn, txn);
if(rxn>0){
   TrComPortReadArray(PortID, rx_buf, rxn, -1);
   rx_str = CharArrayToString(rx_buf,0,rxn,CP_ACP);
   if(StringFind(rx_str,"Hello world!",0)!=-1){
      string tx_str = "Have a nice day!";
      StringToCharArray(tx_str, tx_buf, 0, StringLen(tx_str), CP_ACP);
      TrComPortWriteArray(PortID, tx_buf, StringLen(tx_str), -1);
   }
}

AT命令集与调制解调器封装函数

调制解调器靠AT命令控制,以AT前缀开头、回车(0x0d)结尾。我们取最小必要集保兼容:ATE1开回显、AT+CGMI取厂商、AT+CREG查注册、AT+CSQ信号、AT+CUSD发USSD、AT+CMGS发短信等。文中modem.mqh提供InitModem、ModemTimerProc、ReadSMSbyIndex、DelSMSbyIndex、rssi_to_str、SendSMS等。

MODEM_STR状态结构由处理程序填写,只读供其他函数用:含厂商、SIM状态、网络注册、信号rssi、余额bal、短信内存等。SMS结构含索引、发送者、中心时间标签(含时区)、文本。时间标签每15分钟一区,如8为GMT+2。

struct MODEM_STR
{
   bool init_ok; string manufacturer; string device; int sim_stat; int net_reg;
   int status; string op; int rssi; string sms_sca; int bat_stat; int bat_charge;
   double bal; string exp_date; int sms_free; int sms_free_cnt;
   int sms_mem_size; int sms_mem_used; string incoming;
};

搭建EA框架与参数可视化表格

复制TrComPort.dll到Libraries,ComPort.mqh/modem.mqh/sms.mqh到Include。向导建EA,OnInit调InitComPort和InitModem并EventSetTimer(1);OnDeinit关定时器并DeinitComPort。运行需允许DLL导入且选对COM口。

为直观,用OBJ_LABEL等宽字体绘表格于左上角,显示端口、调制解调器、余额、来电等。RefreshTab()每秒更新。注意对象名str0~str24与背景矩形创建删除配对,避免残留。

#include <modem.mqh>
int OnInit(){
   if(!InitComPort()) return(INIT_FAILED);
   DrawTab(); InitModem(); EventSetTimer(1); RefreshTab();
   return(INIT_SUCCEEDED);
}
void OnDeinit(const int r){ EventKillTimer(); DeinitComPort(); DelTab(); }
void OnTimer(){ ModemTimerProc(); }
⚠ 坑点
EA重跑若报端口打不开,多半上次没走OnDeinit或崩了,需重启系统释放COM句柄。

用USSD查余额与短信余量

手机欠费会断网停EA,故余额监控关键。用USSD请求(如*100#)获响应,靠后缀前缀解析。例:响应'7.13 UAH,失效于2014.05.22',设inp_ussd_bal_suffix=' UAH ',inp_ussd_exp_prefix='失效于'。SMS余量同理,运营商常给已用数,用每日上限减得可用。

参数含查询周期、请求串、前后缀、每日限制。老旧手机不支持USSD AT命令则跳过。文末作者因运营商推圣诞广告致日期解析失败显示n/a,说明解析依赖运营商格式稳定。

⚠ 危险
乱发USSD可能订付费服务!务必先人工测准请求格式再填EA参数。

双向短信:上报状态与接收平仓指令

来电若来自管理员号inp_admin_number(国际格式+号开头),EA回短信报账户登录、利润、净值、持仓数、手机余额。更进一步,IncomingSMS里若发来'CLOSE AUDCAD'或'CLOSE ALL',且时间在inp_sms_max_old秒内、号码匹配,则ClosePositions平仓。

命令忽略延迟防运营商滞后送达时行情已变。处理完可DelSMSbyIndex立即删,防内存满。SMSMemoryFull回调可批量删。注意发送仅拉丁字符,接收支持西里尔。

void IncomingCall(string number){
   if(number==inp_admin_number){
      SendSMS(inp_admin_number, "Profit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2), false);
   }
   ObjectSetString(0,"str16",OBJPROP_TEXT,"Incoming: "+number);
}
uint ClosePositions(string sstr){
   bool all = StringFind(sstr,"ALL",0)>=0;
   for(int i=0;i<PositionsTotal();i++){
      string sym=PositionGetSymbol(i);
      if(all||sstr==sym) if(PositionSelect(sym)){
         long t; double v; PositionGetInteger(POSITION_TYPE,t); PositionGetDouble(POSITION_VOLUME,v);
         OrderClose(sym,(ENUM_POSITION_TYPE)t,v);
      }
   }
   return(0);
}

服务器连接监视与交易回报

用TerminalInfoInteger(TERMINAL_CONNECTED)每10秒查,inp_conn_hyst=6即60秒迟滞滤抖。掉线/恢复时短信报时间点与时长差。dTimeToString把秒转dd hh:mm:ss。conn_mon_init在OnInit设初值仿已连,避免启动即误报。

开平仓监视用OnTradeTransaction捕DEAL_ADD入缓冲,OnTimer生成短信含品种、量、价、出入场与利润。短时需缓冲防漏。全部功能编译后实测AUDCAD 0.14手买平均收短信。

input int inp_conn_hyst=6;
if(++s10>=10){ s10=0;
 if(TerminalInfoInteger(TERMINAL_CONNECTED)){ if(cm.conn_cnt<inp_conn_hyst && ++cm.conn_cnt>=inp_conn_hyst && !cm.connected){ cm.connected=true; cm.new_state=true; } }
 else { /*对称掉线逻辑*/ }
}

旧手机实测与方案总结

西门子M55连上后除USSD外参数全获,证明老设备可行。整体方案价值:无网环境仍短信可控,命令用英文免输法切换。相较纯互联网监控,它牺牲实时性换通达性,适合作为灾备通道而非主通道。

常见问题

将TrComPort.dll放Libraries,头文件放Include,用#import声明函数;InitComPort打开端口设8N1,ModemTimerProc里TrComPortWriteArray发AT+xxx加回车,ReadArray收响应。
设inp_admin_number为+国际号,inp_sms_max_old防过期;IncomingSMS里判断sender匹配且文本含CLOSE ALL或品种,调ClosePositions用OrderClose平仓,处理后DelSMSbyIndex删信。
多因上次未正常DeinitComPort释放句柄,重启系统;确认设备管理器COM号与inp_com_port_index一致(标识符0起,COM3填2),且EA允许DLL导入。
西门子M55等老型号不支持AT+CUSD处理USSD,无法获余额短信;可仅用其收发送短信与来电拒绝,或换支持USSD的 modem。
用inp_conn_hyst计数,每10秒查TERMINAL_CONNECTED,连续6次(60秒)才确认掉线/恢复,避免短时重连误报,并用conn_mon_init初值防启动误发。