MQL5用GSM调制解调器远程控EA
无网也能短信监控与平仓的实战开发
为什么需要GSM调制解调器做EA远程监控
通常我们监视账户用手机MT终端、推送通知或ICQ,这些都依赖互联网。但在野外、机房断网或移动互联网不可用时,交易终端就成孤岛。本文描述的EA通过GSM调制解调器(或带调制解调器功能的旧手机)用短信保持联系:服务器掉线、账户盈亏、开平仓均可短信通知,甚至能收短信命令平仓。这给无法稳定联网的场景提供了兜底通道。
测试设备选用华为E1550广普USB上网卡,也用2003年西门子M55手机验证兼容性。只要系统识别为虚拟COM口且支持AT命令,就能用。注意旧手机可能不支持USSD查询命令。
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等。
- InitComPort/DeinitComPort:开/关COM
- InitModem:AT初始化 modem
- ModemTimerProc:1秒轮询事件
- SendSMS(da,text,flash):仅发拉丁字符,最长158
- IncomingCall/IncomingSMS/SMSMemoryFull:回调事件
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(); }
用USSD查余额与短信余量
手机欠费会断网停EA,故余额监控关键。用USSD请求(如*100#)获响应,靠后缀前缀解析。例:响应'7.13 UAH,失效于2014.05.22',设inp_ussd_bal_suffix=' UAH ',inp_ussd_exp_prefix='失效于'。SMS余量同理,运营商常给已用数,用每日上限减得可用。
参数含查询周期、请求串、前后缀、每日限制。老旧手机不支持USSD AT命令则跳过。文末作者因运营商推圣诞广告致日期解析失败显示n/a,说明解析依赖运营商格式稳定。
双向短信:上报状态与接收平仓指令
来电若来自管理员号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外参数全获,证明老设备可行。整体方案价值:无网环境仍短信可控,命令用英文免输法切换。相较纯互联网监控,它牺牲实时性换通达性,适合作为灾备通道而非主通道。
- 优势:断网可用、硬件便宜、命令闭环
- 劣势:短信延迟、仅拉丁发、旧设备无USSD
- 对比推送:不依赖IP网络但需手机信号
- 适用:机房托管机、移动场景兜底