无需 DLL 的原生 MT4/MT5 推特(Twitter)客户端(基础篇)
◍ 不依赖 DLL 的 MT5 原生推特推送
在 MT5 里发推特不必挂 DLL,直接用平台内置的 WebRequest 就能把信号推到 Twitter API,规避了第三方库被杀软拦截、加载失败的常见坑。 原生方案的核心是把 OAuth 签名和 POST 请求拼好,交给 MQL5 的 WebRequest 发出去;账户侧需在 Twitter Developer 后台拿到 API Key、API Secret、Access Token 和 Token Secret 四组凭证。 实测在 2020 年 9 月的一篇公开示例里,该客户端累计被查看 4818 次、被收藏 8 次,说明纯 MQL5 实现具备可复现性,你打开 MT5 终端的「选项—EA交易—允许 WebRequest」勾上对应域名即可开始验证。 涉及外汇与贵金属自动化推送仍属高风险操作,密钥泄露可能导致账户被控,建议先用沙盒 Token 跑通再上实盘。
「先搞定 Twitter API 的四把钥匙」
要把社媒信号接进 MT5 做情绪分析,第一道坎不是策略,而是 Twitter 开发者平台的令牌体系。它免费开放,但申请流程里那一堆相似名称的 token 足以让多数人卡半天。 实际要用 API 拉数据,核心就四个:customer_token、customer_token_secret、access_token、access_token_secret。前一对相当于给“你的应用”做数字签名,后一对是把“你这个用户”交给 Twitter 做身份甄别,术语叫用户上下文。 另有 bearer_token 可做匿名式 app 上下文访问,但部分接口强制要求用户上下文,缺了 access_token 就调不通,这点在其 API 参考里写得很死。若你熟公钥私钥,这套映射一眼就能看穿。 有其他语言经验的,直接翻官方函数库实现比硬读文档快——很多鉴权细节文档不写,代码里才露原形。
拿到 Twitter 的四组令牌才能过 OAuth 签名
做 MT5 接 Twitter 信号之前,先备齐四组令牌:customer_token、customer_token_secret、access_token、access_token_secret。获取流程 YouTube 上有不少实操录屏,照着走一遍就能拿到。 OAuth 是 Twitter API 用的 Web 鉴权标准,本质是对请求内容做数字签名。哪怕请求里一个字符被改,签名就会失效,服务端直接拒收。 签名生成和授权请求的过程是特制的,步骤顺序不能跳。Twitter 官方文档的归档质量很高,卡住了直接查原文档比搜博客更省时间。 下节会讲 Twitter 要求的特定编码方式,它是架在 HTTP 传输之上的,先把这四组令牌存进 EA 的.ini或全局变量里等着用。
◍ 签名前参数怎么编码和排顺序
做 OAuth 类接口的数字签名,第一步不是算哈希,而是先把 POST/GET 参数规整成确定字符串。规则很死:字母数字和 ‘-’ ‘.’ ‘_’ ‘~’ 原样保留,其余一律百分号编码成 %XX,且十六进制必须用大写 A~F,比如空格会写成 %20 而不是 %20 的小写形态。 参数还要先按字典序排好再拼串。OAuth 规范允许同键参数按值继续排,但 Twitter 这类服务会直接拒绝重复 key,所以实战里一般保证 key 唯一即可,不必处理复选框那种同名字段。 下面这段 MQL5 是最小可用实现:hex() 把字符码点转两位大写十六进制;URLEncode() 逐字符判断并拼接;arrayEncode() 把二维数组键值对编码成 key=val&… 形式;sortParam() 用冒泡做字典序排列(代码截到内层循环开头)。 注意示例里的排序是不完整的——它没考虑多个相同 key 的情况。如果你的请求体来自 HTML 表单的单选/复选,需要自己改比较逻辑,否则签出来的串服务端可能拒收。
class="type">class="kw">string hex(class="type">int i) { class="kw">static class="type">class="kw">string h="0123456789ABCDEF"; class="type">class="kw">string ret=""; class="type">int a = i % class="num">16; class="type">int b = (i-a)/class="num">16; if(b>class="num">15) StringConcatenate(ret,ret,hex(b),StringSubstr(h,a,class="num">1)); else StringConcatenate(ret,ret,StringSubstr(h,b,class="num">1),StringSubstr(h,a,class="num">1)); class="kw">return (ret); } class="type">class="kw">string URLEncode(class="type">class="kw">string toCode) { class="type">int max=StringLen(toCode); class="type">class="kw">string RetStr=""; for(class="type">int i=class="num">0; i<max; i++) { class="type">class="kw">string c = StringSubstr(toCode,i,class="num">1); class="type">class="kw">ushort asc = StringGetCharacter(c, class="num">0); if((asc >= &class="macro">#x27;class="num">0&class="macro">#x27; && asc <= &class="macro">#x27;class="num">9&class="macro">#x27;) || (asc >= &class="macro">#x27;a&class="macro">#x27; && asc <= &class="macro">#x27;z&class="macro">#x27;) || (asc >= &class="macro">#x27;A&class="macro">#x27; && asc <= &class="macro">#x27;Z&class="macro">#x27;) || (asc == &class="macro">#x27;-&class="macro">#x27;) || (asc == &class="macro">#x27;.&class="macro">#x27;) || (asc == &class="macro">#x27;_&class="macro">#x27;) || (asc == &class="macro">#x27;~&class="macro">#x27;)) StringAdd(RetStr,c); else { StringConcatenate(RetStr,RetStr,"%",hex(asc)); } } class="kw">return (RetStr); } class="type">class="kw">string arrayEncode(class="type">class="kw">string &array[][class="num">2]) { class="type">class="kw">string ret=""; class="type">class="kw">string key,val; class="type">int l=ArrayRange(array,class="num">0); for(class="type">int i=class="num">0; i<l; i++) { key = URLEncode(array[i,class="num">0]); val = URLEncode(array[i,class="num">1]); StringConcatenate(ret,ret,key,"=",val); if(i+class="num">1<l) StringConcatenate(ret,ret,"&"); } class="kw">return (ret); } class="type">void sortParam(class="type">class="kw">string&arr[][class="num">2]) { class="type">class="kw">string k1, k2; class="type">class="kw">string v1, v2; class="type">int n = ArrayRange(arr,class="num">0); class=class="str">"cmt">// bubble sort class="type">int i, j; for(i = class="num">0; i < n-class="num">1; i++) { class=class="str">"cmt">// Last i elements are already in place for(j = class="num">0; j < n-i-class="num">1; j++) {
「参数排序与追加的数组实操」
这段逻辑解决的是二维 string 数组的键排序与动态追加。sortParam 里用相邻比较,若前一个键 k1 大于后一个键 k2 就交换整行(键和值一起换),本质是冒泡思路,最坏要跑 n*(n-1)/2 次比较。 addParam 先用 ArrayRange 取当前行数 x,再 ArrayResize 扩一行;返回大于 -1 才写 key/val。注意 ArrayResize 失败会返回 -1,不判断就写数组会越界报错。 调用处先声明 params[][2],连续 addParam 三次(oauth_callback=oob、oauth_consumer_key=consumer_key 等),最后 sortParam(params) 让 OAuth 类签名参数按字典序排好,方便后续做哈希基串。 在 MT5 里建个脚本把这串粘进去,改 consumer_key 常量,跑完打印 params 就能看到排序结果,验证冒泡是否按 ASCII 升序。
class="type">int x = j+class="num">1; k1 = arr[j][class="num">0]; k2 = arr[x][class="num">0]; if(k1 > k2) { class=class="str">"cmt">// swap values v1 = arr[j][class="num">1]; v2 = arr[x][class="num">1]; arr[j][class="num">1] = v2; arr[x][class="num">1] = v1; class=class="str">"cmt">// swap keys arr[j][class="num">0] = k2; arr[x][class="num">0] = k1; } } } } class="type">void addParam(class="type">class="kw">string key,class="type">class="kw">string val,class="type">class="kw">string&array[][class="num">2]) { class="type">int x=ArrayRange(array,class="num">0); if(ArrayResize(array,x+class="num">1)>-class="num">1) { array[x][class="num">0]=key; array[x][class="num">1]=val; } } class="type">class="kw">string params[][class="num">2]; addParam("oauth_callback", "oob", params); addParam("oauth_consumer_key", consumer_key, params); sortParam(params);