保证 MQL5 代码的安全:密码保护,钥匙生成器,时间限制,远程许可证及先进的 EA 许可证密钥加密技术·进阶篇
(2/3)·从单一密码到远程许可证,EA 防盗版链条还差这几环
「用账户号给EA上锁」
MT5 里每个经纪商终端的账户登录号都是唯一的,直接拿它做白名单就能限制 EA 只在指定账户跑。核心就两个调用:AccountInfoString(ACCOUNT_COMPANY) 取经纪商名,AccountInfoInteger(ACCOUNT_LOGIN) 取账户号,再和代码里写死的允许值比对。 这套做法实现成本低、防误装效果好,但硬伤也明显——每加一个授权账户号,就得重新编译发版,不适合大规模分发。 下面这段示例把允许经纪商写死为 MetaQuotes Software Corp.,账户号数组里放了 5 个:979890、436290、646490、225690、279260。OnInit 里比对通过就置 password_status=1,否则打印不允许运行。 别把经纪商名当万能钥匙 只校验 ACCOUNT_COMPANY 可能被同名的白标经纪商绕过,实盘部署前建议把登录号数组和经纪商名双重锁死,并自己在 MT5 用两个不同账户号测一遍通断。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| AccountProtectedEA.mq5 | class=class="str">"cmt">//| Copyright class="num">2012, Investeo.pl | class=class="str">"cmt">//| http://www.investeo.pl | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2012, Investeo.pl" class="macro">#class="kw">property link "http:class=class="str">"cmt">//www.investeo.pl" class="macro">#class="kw">property version "class="num">1.00" const class="type">class="kw">string allowed_broker = "MetaQuotes Software Corp."; const class="type">long allowed_accounts[] = { class="num">979890, class="num">436290, class="num">646490, class="num">225690, class="num">279260 }; class="type">int password_status = -class="num">1; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- class="type">class="kw">string broker = AccountInfoString(ACCOUNT_COMPANY); class="type">long account = AccountInfoInteger(ACCOUNT_LOGIN); printf("The name of the broker = %s", broker); printf("Account number = %d", account); if (broker == allowed_broker) for (class="type">int i=class="num">0; i<ArraySize(allowed_accounts); i++) if (account == allowed_accounts[i]) { password_status = class="num">1; Print("EA account verified"); class="kw">break; } if (password_status == -class="num">1) Print("EA is not allowed to run on this account."); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- if (password_status == class="num">1)
授权校验的极简写法
在 EA 或指标里做基础权限控制,可以直接用字符串比对确认密码是否匹配。 下面这段 MQL5 片段展示了校验通过后的空分支处理:密码正确时仅留一个注释,不做额外动作,逻辑上把鉴权失败的交由外层拦截。
{
class=class="str">"cmt">// password correct
}
{
class=class="str">"cmt">// password correct
}◍ 给 EA 和指标加时限锁
临时授权的场景在 MT5 里很常见:试用版 EA、按月或按年售卖的指标,都得靠时限保护来掐断过期后的使用权限。最直白的思路就是比对服务器时间,时间没到就放行功能,过了点就部分或完全禁用——这套逻辑对 EA 和指标都通用。 它的硬伤也明显:每发给一个客户,就得单独编译一份带特定到期日的 exe 或 ex5。没法像联网验证那样一套程序管所有人,分发成本随用户数线性上涨。 下面这段 MT5 代码演示了最朴素的时限锁。核心只有一个常量 allowed_until,以及 OnInit 和 OnTick 里对 TimeCurrent() 的比对。 //+------------------------------------------------------------------+
| // | TimeLimitProtectedEA.mq5 |
|---|---|
| // | Copyright 2012, Investeo.pl |
| // | http://www.investeo.pl |
//+------------------------------------------------------------------+ #property copyright "Copyright 2012, Investeo.pl" #property link "http://www.investeo.pl" #property version "1.00" datetime allowed_until = D'2012.02.11 00:00'; int password_status = -1; //+------------------------------------------------------------------+
| // | Expert initialization function |
|---|
//+------------------------------------------------------------------+ int OnInit() { //--- printf("This EA is valid until %s", TimeToString(allowed_until, TIME_DATE|TIME_MINUTES)); datetime now = TimeCurrent(); if (now < allowed_until) Print("EA time limit verified, EA init time : " + TimeToString(now, TIME_DATE|TIME_MINUTES)); //--- return(0); } //+------------------------------------------------------------------+
| // | Expert deinitialization function |
|---|
//+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+
| // | Expert tick function |
|---|
//+------------------------------------------------------------------+
void OnTick()
{
//---
if (TimeCurrent() < allowed_until)
{
}
else Print("EA expired.");
}
逐行拆解:
#property version "1.00" 定义程序版本号,纯元数据。
datetime allowed_until = D'2012.02.11 00:00'; 设定到期时刻,D 前缀是 MQL5 的日期字面量语法,这里写死为 2012-02-11 零点。
int password_status = -1; 预留的状态变量,本例未实际使用。
OnInit() 里 TimeCurrent() 取经纪商服务器当前时间;若早于 allowed_until 就打印验证通过,否则静默(未在初始化阻断)。
OnTick() 每笔报价触发,仍用 TimeCurrent() < allowed_until 判断;过期后只 Print("EA expired."),没有强制停止交易——真要锁功能得在里面加 ExpertRemove() 或跳过下单逻辑。
你在 MT5 里把 allowed_until 改成本月月底,编译发给朋友试跑,就能直观看到过期那天终端里开始刷 "EA expired."。外汇和贵金属杠杆高,这类时限 EA 仅作授权技术验证,实盘前务必自测风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TimeLimitProtectedEA.mq5 | class=class="str">"cmt">//| Copyright class="num">2012, Investeo.pl | class=class="str">"cmt">//| http://www.investeo.pl | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2012, Investeo.pl" class="macro">#class="kw">property link "http:class=class="str">"cmt">//www.investeo.pl" class="macro">#class="kw">property version "class="num">1.00" class="type">class="kw">datetime allowed_until = D&class="macro">#x27;class="num">2012.02.class="num">11 class="num">00:class="num">00&class="macro">#x27;; class="type">int password_status = -class="num">1; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- printf("This EA is valid until %s", TimeToString(allowed_until, TIME_DATE|TIME_MINUTES)); class="type">class="kw">datetime now = TimeCurrent(); if (now < allowed_until) Print("EA time limit verified, EA init time : " + TimeToString(now, TIME_DATE|TIME_MINUTES)); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- if (TimeCurrent() < allowed_until) { } else Print("EA expired."); }
「用 RPC 把 EA 授权捏在远端手里」
想按用户逐个开关 EA 权限、或拖长试用期限,不必把逻辑写死在 MT5 端。走一次 MQL5-RPC 调用,把账户名丢给远端服务,它回你一个值:按试用跑、还是直接禁用。 下面这段 Python 起的 XML-RPC 服务,预置了两个 MetaTrader 5 许可,绑定默认 MetaQuotes 演示服务器(access.metatrader5.com:443)上账号 1024221 与 1024223 的 RemoteProtectedEA。生产环境大概率会接 Postgresql 或别的库,但示例足够看清远程许可的运转方式。 EA 侧只消在初始化时打一个远程调用到 isValid(),拿到 true 才放行。你也能把许可做成限时型:给人 10 天试用,不满意远端撤激活,满意就续任意时长。外汇与贵金属杠杆高,这类远程控制若网络抖动可能误杀实盘,上之前先在模拟盘验链路。 代码逐行拆解: from SimpleXMLRPCServer import SimpleXMLRPCServer — 引入 XML-RPC 服务基类。 from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler — 引入请求处理器。 class RequestHandler(SimpleXMLRPCRequestHandler): rpc_path = ('/RPC2',) — 限定 RPC 访问路径为 /RPC2。 class RemoteLicenseExample(SimpleXMLRPCServer): — 自定义许可服务类。 def __init__(self): SimpleXMLRPCServer.__init__(self, ('192.168.2.103', 9099), requestHandler=RequestHandler, logRequests=False) — 在 192.168.2.103:9099 起服务,关请求日志。 self.register_introspection_functions() — 注册内省函数。 self.register_function(self.xmlrpc_isValid, 'isValid') — 把校验方法暴露为 isValid。 self.licenses = {} — 初始化许可字典。 def addLicense(self, ea_name, broker_name, account_number): — 添加一条 EA+券商+账号的许可。 if ea_name in self.licenses: 已存在则追加,否则新建列表。 def listLicenses(self): print self.licenses — 打印当前许可。 def xmlrpc_isValid(self, ea_name, broker_name, account_number): — 远端校验入口。 isValidLicense = False — 默认无效。 try: account_number = int(account_number) except ValueError: return isValidLicense — 账号转整型失败直接拒。 if ea_name in self.licenses: 遍历比对券商名与账号,全中才置 True。 if __name__ == '__main__': 预置两个账号许可并永久服务。
from SimpleXMLRPCServer class="kw">import SimpleXMLRPCServer from SimpleXMLRPCServer class="kw">import SimpleXMLRPCRequestHandler class RequestHandler( SimpleXMLRPCRequestHandler ): rpc_path = ( &class="macro">#x27;/RPC2&class="macro">#x27;, ) class RemoteLicenseExample(SimpleXMLRPCServer): def __init__(self): SimpleXMLRPCServer.__init__( self, ("class="num">192.168.class="num">2.103", class="num">9099), requestHandler=RequestHandler, logRequests = False) self.register_introspection_functions() self.register_function( self.xmlrpc_isValid, "isValid" ) self.licenses = {} def addLicense(self, ea_name, broker_name, account_number): if ea_name in self.licenses: self.licenses[ea_name].append({ &class="macro">#x27;broker_name&class="macro">#x27;: broker_name, &class="macro">#x27;account_number&class="macro">#x27; : account_number }) else: self.licenses[ea_name] = [ { &class="macro">#x27;broker_name&class="macro">#x27;: broker_name, &class="macro">#x27;account_number&class="macro">#x27; : account_number } ] def listLicenses(self): print self.licenses def xmlrpc_isValid(self, ea_name, broker_name, account_number): isValidLicense = False ea_name = str(ea_name) broker_name = str(broker_name) print "Request for license", ea_name, broker_name, account_number try: account_number = class="type">int(account_number) except ValueError as error: class="kw">return isValidLicense if ea_name in self.licenses: for license in self.licenses[ea_name]: if license[&class="macro">#x27;broker_name&class="macro">#x27;] == broker_name and license[&class="macro">#x27;account_number&class="macro">#x27;] == account_number: isValidLicense = True class="kw">break print "License valid:", isValidLicense class="kw">return isValidLicense if __name__ == &class="macro">#x27;__main__&class="macro">#x27;: server = RemoteLicenseExample() server.addLicense("RemoteProtectedEA", "MetaQuotes Software Corp.", class="num">1024221) server.addLicense("RemoteProtectedEA", "MetaQuotes Software Corp.", class="num">1024223) server.listLicenses() server.serve_forever()
EA 远程授权校验的初始化写法
把 EA 绑死到具体账户和券商,靠本地写死密码不现实,远程 license 校验是更稳的做法。下面这段初始化逻辑在 OnInit 里直连内网 RPC 服务(示例地址 192.168.2.103:9099),拿到 broker 名和登录号再去问服务端这个 EA 是否合法。 核心动作就三步:先取 AccountInfoString(ACCOUNT_COMPANY) 和 AccountInfoInteger(ACCOUNT_LOGIN),再把 "RemoteProtectedEA"、broker、account 塞进 XML-RPC 的 isValid 查询,最后从返回的 CArrayBool 里读第一位赋值给 license_status。若服务端连不上,直接打印 "License server not connected." 并保留默认 false。 实盘前你该把 192.168.2.103:9099 换成自己的验证机,且注意外汇/贵金属杠杆高,授权失效时 EA 应停止下单而非裸奔——这段代码只管标记 license_status,下单守卫得你自己补。
class=class="str">"cmt">//| RemoteProtectedEA.mq5 | class=class="str">"cmt">//| Copyright class="num">2012, Investeo.pl | class=class="str">"cmt">//| http://www.investeo.pl | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2012, Investeo.pl" class="macro">#class="kw">property link "http:class=class="str">"cmt">//www.investeo.pl" class="macro">#class="kw">property version "class="num">1.00" class="macro">#include <MQL5-RPC.mqh> class="macro">#include <Arrays\ArrayObj.mqh> class="macro">#include <Arrays\ArrayInt.mqh> class="macro">#include <Arrays\ArrayString.mqh> class="macro">#include <Arrays\ArrayBool.mqh> class="type">bool license_status=false; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- class=class="str">"cmt">/* License proxy server */ CXMLRPCServerProxy s("class="num">192.168.class="num">2.103:class="num">9099"); if(s.isConnected()==true) { CXMLRPCResult *result; class=class="str">"cmt">/* Get account data */ class="type">class="kw">string broker= AccountInfoString(ACCOUNT_COMPANY); class="type">long account = AccountInfoInteger(ACCOUNT_LOGIN); printf("The name of the broker = %s",broker); printf("Account number = %d",account); class=class="str">"cmt">/* Get remote license status */ CArrayObj* params= new CArrayObj; CArrayString* ea = new CArrayString; CArrayString* br = new CArrayString; CArrayInt *ac=new CArrayInt; ea.Add("RemoteProtectedEA"); br.Add(broker); ac.Add((class="type">int)account); params.Add(ea); params.Add(br); params.Add(ac); CXMLRPCQuery query("isValid",params); result=s.execute(query); CArrayObj *resultArray=result.getResults(); if(resultArray!=NULL && resultArray.At(class="num">0).Type()==TYPE_BOOL) { CArrayBool *stats=resultArray.At(class="num">0); license_status=stats.At(class="num">0); } else license_status=false; if(license_status==true) printf("License valid."); else printf("License invalid."); class="kw">delete params; class="kw">delete result; } else Print("License server not connected."); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 把授权校验接进行情 tick
EA 的生命周期里,OnDeinit 负责退出清理,但上面这段模板里它是空壳,只留了 //--- 注释位,没做任何资源释放或指标句柄删除。真要落地,至少该在退出时把创建过的 iMA、iRSI 句柄用 IndicatorRelease 清掉,否则反复加载卸载可能漏句柄。 OnTick 才是行情驱动核心。示例里先判断 license_status==true 才进主逻辑,等于把「授权有效」作为一切交易的门禁。你在 MT5 里跑第三方 EA 时,可以故意把 license_status 初始值改成 false,观察图表是否彻底不交易——这能快速验证作者有没有把校验做成摆设。 外汇与贵金属杠杆高,这类带授权开关的 EA 若校验被绕过,风险不只亏本金,还可能触发经纪商风控。建议每次拿到新 EA 先单 tick 单步跟一遍 OnTick 入口。
class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- if(license_status==true) { class=class="str">"cmt">// license valid } } class=class="str">"cmt">//+------------------------------------------------------------------+