在您的 MQL 项目中使用 JSON 数据 API·综合运用
(3/3)·从注册密钥到信号过滤,完整跑通 Ziwox 外汇数据在自动化策略里的落地闭环
「把接口返回落盘前先卡死无效密钥」
这段逻辑干的事很直接:HTTP 请求回来后先打印成功提示和原始报文,随后用 StringFind 在返回串里搜 "invalid api key"。一旦命中(返回值不等于 -1),立刻弹 Alert 并 return(false),避免拿脏数据继续跑后续。 文件落地用 FileOpen 以 FILE_COMMON 公共目录方式打开,权限叠了读、写、共享、二进制。句柄有效时才 FileWriteArray 把 result 数组整块写进去,写完 FileClose 释放。公共目录意味着多终端实例能读到同一份,路径不用硬编码到某个终端下。 脚本尾部那几个全局变量才是你真要改的:SymbolRequest 写 "EURUSD" 是无前缀纯符号名;APIKey 那串 "76thfd67a6f867df7" 是示例密钥,必须换成你自己的,否则 StringFind 必然命中 invalid 分支直接退出。 外汇与贵金属行情接口调用受网络与供应商限制,请求失败或密钥失效的概率不低,实盘前先在策略测试器或脚本环境里跑一遍确认文件生成。
Print("HTTP request for "+symbolname+" Data successful!"); Print(HTTPString); if (StringFind(HTTPString,"invalid api key",class="num">0)!=-class="num">1) { Alert("invalid api key"); class="kw">return(class="kw">false); } class=class="str">"cmt">// Store the API data into a common folder file class="type">int filehandle=FileOpen(filename,FILE_READ|FILE_SHARE_READ|FILE_WRITE|FILE_SHARE_WRITE|FILE_BIN|FILE_COMMON); if(filehandle!=INVALID_HANDLE) { FileWriteArray(filehandle,result,class="num">0,ArraySize(result)); FileClose(filehandle); } } } class="kw">return(true); } class="type">class="kw">string APIfilename = symbolname+"_API_Data.json"; class=class="str">"cmt">// API store file name class="type">class="kw">string APIKey = "76thfd67a6f867df7"; class=class="str">"cmt">// Our unic API Key, change it with your API Key class="type">class="kw">string SymbolRequest = "EURUSD"; class=class="str">"cmt">// Symbol request, without prefix or suffix class="type">class="kw">string APIJSON[]; class="type">bool APIGET = GetAPI(SymbolRequest, APIKey, APIfilename);
◍ 把 API 回传的 JSON 拆成可用字段
外部 API 推回的报文是一个数据数组,字段覆盖了报价、基本面、散户仓位、COT 与日线技术指标等多个维度。做自动化策略时没必要全量解析,挑出和交易决策直接相关的几项即可,例如 AI Bullish Forecast 与 AI Bearish Forecast 是百分比字段,数值越大代表对应方向的概率倾向越高;Retail Long Ratio / Retail Short Ratio 则反映散户多空占比,常用于反向参考。 下面这段函数读取第 4 步落盘到 MT5 公共目录的 JSON 文件,用 CJAVal 反序列化后,把 Symbol、基本面倾向、AI 多空预测、散户多空比和 COT 净头寸等 11 个字段抽进字符串数组 _APIJSON,方便后续逻辑直接调用。 调用前注意 FileOpen 用了 FILE_COMMON 标志,意味着文件路径在终端的 Common/Files 下,跨品种 EA 也能共享同一份缓存。外汇与贵金属杠杆高、波动剧烈,任何基本面或 AI 预测都只是概率参考,实盘前务必在策略测试器里跑一遍验证字段解析是否正确。
class="macro">#include "JAson.mqh" class=class="str">"cmt">// include the JSON librery in our project CJAVal JsonValue; class="type">void JsonDataParse(class="type">class="kw">string filename, class="type">class="kw">string &_APIJSON[]) { class="type">bool UpdateData = class="kw">false; for (class="type">int arraIn=class="num">0; arraIn<ArraySize(APIJSON); arraIn++) APIJSON[arraIn]=""; if(FileGetInteger(filename,FILE_EXISTS,true)>=class="num">0) { class="type">int FileHandle =FileOpen(filename,FILE_READ|FILE_SHARE_READ|FILE_WRITE|FILE_SHARE_WRITE|FILE_BIN|FILE_COMMON); class="type">char jsonarray[]; FileReadArray(FileHandle,jsonarray); FileClose(FileHandle); JsonValue.Clear(); JsonValue.Deserialize(CharArrayToString(jsonarray, class="num">0, class="num">0, CP_UTF8)); _APIJSON[class="num">0] = JsonValue[class="num">0]["Symbol"].ToStr(); _APIJSON[class="num">1] = JsonValue[class="num">0]["Fundamental Bias"].ToStr(); _APIJSON[class="num">2] = JsonValue[class="num">0]["Fundamental Power"].ToStr(); _APIJSON[class="num">3] = JsonValue[class="num">0]["AI Bullish Forecast"].ToStr(); _APIJSON[class="num">4] = JsonValue[class="num">0]["AI Bearish Forecast"].ToStr(); _APIJSON[class="num">5] = JsonValue[class="num">0]["Retail Long Ratio"].ToStr(); _APIJSON[class="num">6] = JsonValue[class="num">0]["Retail Short Ratio"].ToStr(); _APIJSON[class="num">7] = JsonValue[class="num">0]["Retail Short Lot"].ToStr(); _APIJSON[class="num">8] = JsonValue[class="num">0]["Retail Short pos"].ToStr(); _APIJSON[class="num">9] = JsonValue[class="num">0]["Base COT NET"].ToStr(); _APIJSON[class="num">10] = JsonValue[class="num">0]["Base COT change"].ToStr();
把 COT 与风险情绪写进本地数组
上面这段赋值发生在 JSON 解析成功之后,把远端返回的持仓与情绪字段逐个塞进 _APIJSON 数组的 11~14 号位。其中 "Quote COT NET" 是净持仓报价,"Quote COT change" 为当期变动,"COT chng Ratio" 是变动比,"Risk Sentiment" 给出当前风险偏好标签,四个字段共同构成后续品种筛选的底层输入。 真正落盘时,代码用 symbolname+"_API_Data.json" 拼出每个品种独立的缓存文件名,再声明 API_DATA 字符串数组并调用 JsonDataParse 回填。你在 MT5 里跑这套,只要看对应品种目录下是否生成了该 json,就能确认 API 拉取链路是否通畅。 外汇与贵金属受杠杆与突发事件影响,COT 与情绪数据只反映历史仓位倾向,实际价格可能反向,务必以小仓位验证。
_APIJSON[class="num">11] = JsonValue[class="num">0]["Quote COT NET"].ToStr(); _APIJSON[class="num">12] = JsonValue[class="num">0]["Quote COT change"].ToStr(); _APIJSON[class="num">13] = JsonValue[class="num">0]["COT chng Ratio"].ToStr(); _APIJSON[class="num">14] = JsonValue[class="num">0]["Risk Sentiment"].ToStr(); } } class="type">class="kw">string APIfilename = symbolname+"_API_Data.json"; class=class="str">"cmt">// API store file name class="type">class="kw">string API_DATA[]; class=class="str">"cmt">// define an array variable to store API details JsonDataParse( APIfilename,API_DATA);
「把 AI 预测接进双均线交叉」
双均线交叉是识别趋势转向最朴素的做法:短周期均线向上穿过长周期均线,倾向多头信号;向下穿过则倾向空头信号。把它和提取到的预测数据叠在一起,能过滤掉一部分纯技术信号在弱势概率下的噪音。 下面这段 EA 片段演示了如何把预测字段和均线状态联立判断。核心逻辑是:只有当看涨预测值不低于 60 且短均线上穿长均线时,才允许开多;看跌预测值不低于 60 且短均线下穿长均线时,才允许开空。外汇与贵金属杠杆高,这类简单策略在震荡市可能频繁假突破,实盘前务必在 MT5 策略测试器跑历史数据。 代码逐行拆解: shortMA = iMA(Symbol(), 0, shortMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 0); // 在当前品种、当前周期上取一条周期为 shortMAPeriod 的收盘价简单移动平均 longMA = iMA(Symbol(), 0, longMAPeriod, 0, MODE_SMA, PRICE_CLOSE, 0); // 同理取长周期均线 if ( int(APIJSON[3])>=60 ) // 取预测数组第 4 项(看涨概率),不低于 60 才继续 if (shortMA > longMA) // 短均线高于长均线,视为买入趋势 { if (OrdersTotal() == 0) { // 账户无持仓才动手 ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, 3, 0, 0, "Buy Order", 0, 0, Green); // 市价 Ask 开多,滑点容差 3 点 if (ticket < 0) Print("Error opening buy order: ", GetLastError()); // 下单失败打印错误码 } } if ( int(APIJSON[4])>=60 ) // 取预测数组第 5 项(看跌概率),不低于 60 才继续 if (shortMA < longMA) // 短均线低于长均线,视为卖出趋势 { if (OrdersTotal() == 0) { ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, 3, 0, 0, "Sell Order", 0, 0, Red); // 市价 Bid 开空 if (ticket < 0) Print("Error opening sell order: ", GetLastError()); } } 把 shortMAPeriod 设成 5、longMAPeriod 设成 20,是常见起步参数;你可以直接复制到 MT5 的 EA 编辑器里改两个周期值,看回测里信号触发次数怎么变。
shortMA = iMA(Symbol(), class="num">0, shortMAPeriod, class="num">0, MODE_SMA, PRICE_CLOSE, class="num">0); longMA = iMA(Symbol(), class="num">0, longMAPeriod, class="num">0, MODE_SMA, PRICE_CLOSE, class="num">0); class=class="str">"cmt">// Check for crossover signals if ( class="type">int(APIJSON[class="num">3])>=class="num">60 ) class=class="str">"cmt">// if bullish forecast is higher than class="num">60% if (shortMA > longMA) class=class="str">"cmt">// BUY trend { if (OrdersTotal() == class="num">0) { ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, class="num">3, class="num">0, class="num">0, "Buy Order", class="num">0, class="num">0, Green); if (ticket < class="num">0) Print("Error opening buy order: ", GetLastError()); } } if ( class="type">int(APIJSON[class="num">4])>=class="num">60 ) class=class="str">"cmt">// if bearish forecast is higher than class="num">60% if (shortMA < longMA) class=class="str">"cmt">// Sell trend { if (OrdersTotal() == class="num">0) { ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, class="num">3, class="num">0, class="num">0, "Sell Order", class="num">0, class="num">0, Red); if (ticket < class="num">0) Print("Error opening sell order: ", GetLastError()); } }
◍ 用 AI 预测比率卡掉反向交叉单
均线交叉本身容易在震荡里反复打脸。把外部 AI 预测比率接进来当过滤器,只在技术面与预测面同向时才放行订单,入场噪声会少一截,外汇与贵金属品种的高波动环境下这点尤其关键。 按 JSON 数组的排布,看涨预测百分比放在第 3 个元素(索引 3),看跌预测在第 4 个元素(索引 4)。代码里把阈值直接写死 60%,意味着只有 AI 看涨概率 ≥60% 且短均线上穿长均线才做多,反过来同理。 这套逻辑不保证胜率提升多少,但能把明显逆预测的交叉信号挡在门外,实盘前建议在 MT5 策略测试器里把 60% 改成 50/70 各跑一遍,看样本内回撤变化。
class="type">void OnTick() { if (!APIOK) class="kw">return; class="type">class="kw">double shortMA, longMA; class="type">long ticket = -class="num">1; if(IsNewCandle()) { shortMA = iMA(Symbol(), class="num">0, shortMAPeriod, class="num">0, MODE_SMA, PRICE_CLOSE, class="num">0); longMA = iMA(Symbol(), class="num">0, longMAPeriod, class="num">0, MODE_SMA, PRICE_CLOSE, class="num">0); class=class="str">"cmt">// Check for crossover signals if ( class="type">int(APIJSON[class="num">3])>=class="num">60 ) class=class="str">"cmt">// if bullish forecast is higher than class="num">60% if (shortMA > longMA) class=class="str">"cmt">// BUY trend { if (OrdersTotal() == class="num">0) { ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, class="num">3, class="num">0, class="num">0, "Buy Order", class="num">0, class="num">0, Green); if (ticket < class="num">0) Print("Error opening buy order: ", GetLastError()); } } if ( class="type">int(APIJSON[class="num">4])>=class="num">60 ) class=class="str">"cmt">// if bearish forecast is higher than class="num">60% if (shortMA < longMA) class=class="str">"cmt">// Sell trend { if (OrdersTotal() == class="num">0) { ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, class="num">3, class="num">0, class="num">0, "Sell Order", class="num">0, class="num">0, Red); if (ticket < class="num">0) Print("Error opening sell order: ", GetLastError()); } } } }
把外部数据接进EA后的真实边界
把 API 行情之外的数据接进 MQL 程序,EA 就从纯技术面变成了技术+基本面双驱动。Ziwox 那套在 MT4 跑通的 mq4,社区里 Ney Borges 改成了 mq5,作者 Sara 实测在 MT5 能正常跑,说明跨版本迁移不是空话,下载 JAson.mqh 库挂上就能编译。 但别以为接了数据就高枕无忧。外汇和贵金属是高杠杆品种,外部 API 延迟、字段变动都可能让 EA 误判,任何策略都只是提高概率,不保证胜率。 附件里 APIExpert.mq4 是 18.77 KB、JAson.mqh 是 30.59 KB,mq5 改写版核心输入就三个:APIKey 填你自己的串、SymbolPrefix 留空、SymbolSuffiex 默认填 "m" 匹配经纪商后缀。 别把正态当圣经 JAson.mqh 不在主代码里,得单独下;漏了这文件编译直接挂,很多人卡在这一步。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Ney Borges versão mql5 de ZIWOX API and Technical Strategy.mq5 | | ZIWOX 应用程序接口和技术策略.mq5 class=class="str">"cmt">//|Copyright class="num">2024, ssabbaghi | | class=class="str">"cmt">//|[MQL5官方文档] |. class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Sara Sabbaghi" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.0" class="macro">#class="kw">property strict class="macro">#class="kw">property description "ZIWOX应用程序接口功能专家" class=class="str">"cmt">// 图书馆 class="macro">#include <JAson.mqh> CJAVal JsonValue; class=class="str">"cmt">// 专家为实现赎回功能所做的工作 class=class="str">"cmt">//class="macro">#perty script_show_inputs class=class="str">"cmt">//class="macro">#class="kw">property script_show_confirm class=class="str">"cmt">//---- 输入参数 class="kw">input class="type">class="kw">string APIKey = "sua Key aqui xxxxxxxx"; class=class="str">"cmt">// 您唯一的应用程序接口密钥 class="kw">input class="type">class="kw">string SymbolPrefix = ""; class=class="str">"cmt">// 您的经纪商账户符号前缀 class="kw">input class="type">class="kw">string SymbolSuffiex = "m"; class=class="str">"cmt">// 您的经纪商账户符号后缀
「EA 输入项与 HTTP 连通性自检」
这段 EA 把可调参数全摊在 input 区:慢线 50、快线 200 周期(原文注释写反了,实际 shortMAPeriod=50 为慢速、longMAPeriod=200 为快速),固定手数 0.01,多空预测阈值都设 60.0,API 轮询间隔 300 秒(即 5 分钟)。开 MT5 后第一件事应该是改这些 extern 值,而不是急着挂实盘——外汇和贵金属波动大,静态 0.01 手在极端滑点下仍可能放大回撤。 TestHTTPConnection() 用 WebRequest 对 ziwox.com 发 GET,超时 5000 毫秒。若返回 -1,抓 GetLastError();错误码 4014 意味着你没在「工具→选项→EA→允许 WebRequest」里加白名单域名,函数会拼出对应提示。连通成功仅 Print 一行 DEBUG,不阻断后续。 OnInit() 里先打启动标记,再查 TERMINAL_TRADE_ALLOWED 与 TERMINAL_CONNECTED:两者任一为假只 Print 警告而不 return,EA 会继续初始化。这意味着你本地测连不上交易服务器时,策略逻辑仍可能空跑,回测和实盘行为会有偏差。 别把 4014 当小毛病。MT5 默认禁止任何出站请求,不手动加域名白名单,整套预测接口根本拿不到数据,后面所有信号都是死值。
class="kw">input class="type">int shortMAPeriod = class="num">50; class=class="str">"cmt">// 慢速 MA 周期 class="kw">input class="type">int longMAPeriod = class="num">200; class=class="str">"cmt">// 快速 MA 周期 class="kw">input class="type">class="kw">double Lots = class="num">0.01; class=class="str">"cmt">// 静态订单量 class="kw">input class="type">class="kw">double BullishThreshold = class="num">60.0; class=class="str">"cmt">// 预测看涨的阈值 class="kw">input class="type">class="kw">double BearishThreshold = class="num">60.0; class=class="str">"cmt">// 预测看跌的阈值 class="kw">input class="type">int APICallInterval = class="num">300; class=class="str">"cmt">// 以秒为单位的 API 调用间隔(class="num">5 分钟) class="type">class="kw">string OBJPREFIX = "ZXAPI", SymbolRequest = "", APIJSON[]; class="type">bool APIOK = class="kw">false; class="type">class="kw">datetime LastAPICallTime = class="num">0; class=class="str">"cmt">// 查看应用程序接口的最新页面时间 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//} 测试 HTTP 连接的功能| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool TestHTTPConnection() { class="type">class="kw">string cookie=NULL,headers; class="type">char post[],result[]; class="type">class="kw">string url="https:class=class="str">"cmt">//www.ziwox.com"; ResetLastError(); class="type">int timeout=class="num">5000; class="type">int res=WebRequest("GET",url,cookie,NULL,timeout,post,class="num">0,result,headers); if(res==-class="num">1) { class="type">int error=GetLastError(); class="type">class="kw">string msg = "Falha ao conectar, erro "+IntegerToString(error); class=class="str">"cmt">// Se for erro de permissão, mostra mensagem específica if(error==class="num">4014) msg = "Adicione "+url+" em Ferramentas -> Opções -> Expert Advisors -> Permitir WebRequest"; Print("DEBUG: ", msg); class="kw">return(class="kw">false); } Print("DEBUG:HTTP 连接测试成功"); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 专家初始化函数| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { Print("=== Iniciando Expert Advisor ==="); class=class="str">"cmt">// 验证基本配置 if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Print("AVISO: 不允许算法交易"); class=class="str">"cmt">// Continueua mesmo assim } if(!TerminalInfoInteger(TERMINAL_CONNECTED)) { Print("AVISO: 终端未连接互联网"); class=class="str">"cmt">// Continueua mesmo assim }
◍ EA 初始化与每根 K 线的双均线校验
EA 挂载后第一件事是铺好运行环境:用 EventSetTimer(30) 设 30 秒心跳,ArrayResize(APIJSON, 15) 给外部接口返回留 15 个槽位,再把带前缀后缀的品种名洗成裸符号。cleanSymbol 里把 'm' 替换成空串,拼出的 URL 指向第三方数据接口,Print 出来方便你直接抄去浏览器测连通性。 MT5 默认禁外联,必须手动在 工具→选项→专家顾问→允许 WebRequest 里加白名单域名,否则 TestHTTPConnection() 必报 AVISO,EA 虽不中断初始化但后续拿不到 AI 预测值。OnDeinit 里清 Comment 和删全部对象,避免残影干扰下一轮加载。 真正下单判断在 OnTick:先卡 APIOK 标志,再用 IsNewCandle() 过滤只在新蜡烛触发。shortMA 与 longMA 取当前周期收盘价 SMA,周期由 shortMAPeriod / longMAPeriod 外部参数决定。同时把 APIJSON[3] 当看涨预测、APIJSON[4] 当看跌预测转成 double,仅当 aiBullishForecast >= BullishThreshold 且短均线上穿长均线时,才在无持仓前提下继续开仓逻辑。外汇与贵金属杠杆高,这套信号只提高概率倾向,实盘前请在策略测试器跑最少 3 个月 tick 数据。
class=class="str">"cmt">// 配置 EA EventSetTimer(class="num">30); ArrayResize(APIJSON, class="num">15); SymbolRequest = PureSymbol(Symbol(), SymbolSuffiex, SymbolPrefix); class=class="str">"cmt">// 准备并显示要使用的 URL class="type">class="kw">string cleanSymbol = SymbolRequest; StringReplace(cleanSymbol, "m", ""); class="type">class="kw">string apiUrl = StringFormat("https:class=class="str">"cmt">//www.ziwox.com/terminal/services/API/V1/fulldata.php?expn=ziwoxuser&apikey=%s&apitype=json&pair=%s", APIKey, cleanSymbol); Print("URL configurada: ", apiUrl); Print("IMPORTANTE: Adicione a URL https:class=class="str">"cmt">//ziwox.com em:"); Print("Ferramentas -> Opções -> Expert Advisors -> Permitir WebRequest"); Comment("Iniciando EA...\n", "URL que será usada:\n", apiUrl, "n/n/nIMPORTANTE: Configure a URL nas opções do Expert Advisor"); class=class="str">"cmt">// Testa conexão HTTP, mas não falha se der erro if(!TestHTTPConnection()) { Print("AVISO:Teste de conexão HTTP falhou"); Print("O EA 继续尝试连接......"); class=class="str">"cmt">// Continueua mesmo assim } Print("=== Inicialização concluída ==="); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 专家去初始化函数| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { Comment(""); ObjectsDeleteAll(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 专家勾选函数 OnTick()| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { if (!APIOK) class="kw">return; class="type">class="kw">double shortMA, longMA; class="type">MqlTradeRequest request = {}; class="type">MqlTradeResult result = {}; if (IsNewCandle()) { shortMA = iMA(Symbol(),PERIOD_CURRENT, shortMAPeriod, class="num">0, MODE_SMA, PRICE_CLOSE); longMA = iMA(Symbol(),PERIOD_CURRENT, longMAPeriod, class="num">0, MODE_SMA, PRICE_CLOSE); class=class="str">"cmt">// 分析 JSON 数据以做出商业决策 class="type">class="kw">double aiBullishForecast = StringToDouble(APIJSON[class="num">3]); class=class="str">"cmt">// 高级预测 class="type">class="kw">double aiBearishForecast = StringToDouble(APIJSON[class="num">4]); class=class="str">"cmt">// 下期预测 class=class="str">"cmt">// 检查交叉信号 if (aiBullishForecast >= BullishThreshold && shortMA > longMA) class=class="str">"cmt">// Se a previsão de alta for maior que o limite { if (OrdersTotal() == class="num">0) {
双向市价单的下单结构与URL白名单校验
这段逻辑把 AI 预测方向和双均线死叉/金叉做了耦合:只有当 aiBearishForecast 不低于 BearishThreshold 且短均线下穿长均线时,才在无持仓状态下发卖单;买侧同理用 aiBullishForecast 与金叉判定。外汇与贵金属杠杆高,信号共振也不代表胜率,实盘前务必在 MT5 策略测试器跑一遍。 市价单请求里 type_filling 写死成 ORDER_FILLING_FOK,意味着全部或取消,滑点超过 deviation=3 点就直接拒单。若你的经纪商对 XAUUSD 常用 IOC 成交,硬套 FOK 可能频繁开仓失败,需要按品种改填充策略。 下单块之外还有一个 IsURLAllowed 函数,用 WebRequest 探活 ziwox.com,超时设 5000 毫秒。若返回 -1 且错误码 4014,就是终端没把该域名加进 EA 的 WebRequest 允许列表,此时 Comment 会提示去 工具->选项->智能交易系统 手动勾选并填 URL。 下面把买单侧 request 赋值与发送部分拆开看,卖单侧字段仅 type/price 取值不同,结构完全一致。
request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = Lots; request.type = ORDER_TYPE_BUY; request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); request.deviation = class="num">3; request.magic = class="num">123456; request.comment = "Buy Order"; request.type_filling = ORDER_FILLING_FOK; if(!OrderSend(request, result)) Print("Error opening buy order: ", GetLastError());
「定时拉取外部接口并回写盘面注释」
EA 的 OnTimer 是这套外部数据接入的发动机。它先检查 IsURLAllowed() 是否放行,未配置白名单就直接 Print 调试信息并 return,避免在 MT5 里反复弹错。 放行后靠 TimeCurrent() - LastAPICallTime >= APICallInterval 做节流,只有超过设定间隔才发起一次请求,防止被第三方接口限频。请求前用 StringReplace 把交易品种里的 'm' 去掉(例如 XAUmUSD 变 XAUUSD),再拼成 ziwox 的 fulldata 接口 URL。 调用 GetAPI 拿到 JSON 后,JsonDataParse 把字段拆进 APIJSON 数组,然后用 Comment 把最新价、基本面倾向、涨跌概率预测、多空持仓等 8 个字段直接刷在图表左上。外汇与贵金属杠杆高,接口给出的涨跌概率只是第三方模型倾向,实盘须自行验证。 下面这段是 OnTimer 核心逻辑,可直接拷进 MQ5 文件对照改 APICallInterval 与 APIKey:
class="type">void OnTimer() { class=class="str">"cmt">// 验证是否允许输入 URL if(!IsURLAllowed()) { Print("DEBUG:URL não está permitida.等待配置..."); class="kw">return; } if (TimeCurrent() - LastAPICallTime >= APICallInterval) { class="type">class="kw">string APIfilename = SymbolRequest + "_API_Data.json"; class=class="str">"cmt">// 准备用于在评论中显示的 URL class="type">class="kw">string cleanSymbol = SymbolRequest; StringReplace(cleanSymbol, "m", ""); class="type">class="kw">string apiUrl = StringFormat("https:class=class="str">"cmt">//www.ziwox.com/terminal/services/API/V1/fulldata.php?expn=ziwoxuser&apikey=%s&apitype=json&pair=%s", APIKey, cleanSymbol); class=class="str">"cmt">// 正在举办的展览 Comment("Fazendo chamada à API...\n", "URL: ", apiUrl, "\n\nAguarde..."); APIOK = GetAPI(SymbolRequest, APIKey, APIfilename); if (APIOK) { JsonDataParse(APIfilename, APIJSON); Comment("=== API OK ===\n", "URL: ", apiUrl, "\n", "最新更新:", TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS), "\n", "\nDados recebidos:", "\nSymbol: ", APIJSON[class="num">0], "\nÚltimo Preço:", APIJSON[class="num">1], "\nViés Fundamental: ", APIJSON[class="num">2], "\nPrevisão de Alta:", APIJSON[class="num">3], "%", "\nPrevisão de Baixa:", APIJSON[class="num">4], "%", "\nPosições Compradas:", APIJSON[class="num">5], "\nPosições Vendidas:", APIJSON[class="num">6], "nTendência D1: ", APIJSON[class="num">7],
◍ API失败提示与新蜡烛判定的底层函数
EA 在 WebRequest 返回非 200 时,会在图表左上角用 Comment 抛出「=== FALHA NA API ===」并附带三条排查项:URL 是否在 MT5 的 工具->选项->EA->WebRequest 白名单、本地网络是否通、当前品种名是否拼错。这套提示直接打印 apiUrl 与 TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),方便你一眼对照是地址被拦还是断网。 IsNewCandle() 用 static datetime lastCandleTime 缓存上一根 K 线时间,每次拿 iTime(Symbol(),0,0) 比对待变。只要当前 0 号柱时间不等于上次记录,就刷新并返回 true,否则 false。这意味着你的外部信号只在「每根新蜡烛」触发一次,避免同根 K 内重复打 API 把请求额度烧光。 PureSymbol() 负责剥掉品种名的前后辍:传入 symbol 与 prefix/suffiex,只要有一个非空就用 StringReplace 把缀词替换成空串。比如把「XAUUSD.pro」洗成「XAUUSD」再发给远端,能降低因券商后缀不同导致信号系统认不出品种的概率。 CheckWebRequestSettings() 在 EA 初始化阶段先卡三道关:TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)、TERMINAL_TRADE_ALLOWED 与 MQLInfoInteger(MQL_DLLS_ALLOWED)。任一为 false 就 Print 报错并 return false,EA 后续逻辑直接不跑。外汇与贵金属杠杆高,实盘前务必确认这三项为 true,否则策略连网都出不去。
"\nRSI D1: ", APIJSON[class="num">8], "\nPermitido Operar: ", APIJSON[class="num">9] ); } else { Comment("=== FALHA NA API ===\n", "URL tentada: ", apiUrl, "\n", "Hora: ", TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS), "\n", "\nVerifique:", "\n1.Se a URL está permitida em Ferramentas -> Opções -> Expert Advisors -> WebRequest", "\n2.Se sua conexão com a internet está funcionando", "\n3.Se o símbolo está correto" ); } LastAPICallTime = TimeCurrent(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 验证是否是新蜡烛的功能 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsNewCandle() { class="kw">static class="type">class="kw">datetime lastCandleTime = class="num">0; class="type">class="kw">datetime currentCandleTime = iTime(Symbol(), class="num">0, class="num">0); if (currentCandleTime != lastCandleTime) { lastCandleTime = currentCandleTime; class="kw">return true; } class="kw">return class="kw">false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 纯符号函数| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string PureSymbol(class="type">class="kw">string symbol, class="type">class="kw">string suffiex, class="type">class="kw">string prefix) { class="type">class="kw">string puresymbol = symbol; if (prefix != "" || suffiex != "") { StringReplace(puresymbol, suffiex, ""); StringReplace(puresymbol, prefix, ""); } class="kw">return puresymbol; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 验证 WebRequest 配置的功能 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CheckWebRequestSettings() { if(!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) { Print("错误:不允许使用动态链接库!"); class="kw">return class="kw">false; } if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Print("ERRO:不允许算法交易!"); class="kw">return class="kw">false; } if(!MQLInfoInteger(MQL_DLLS_ALLOWED)) { Print("ERRO: DLLs não estão permitidas para este Expert!"); class="kw">return class="kw">false; } class="kw">return true; }
用 Socket 探活再拉取外部 API 数据
在 MT5 里调用外部数据前,先确认网络通道是否通畅能省掉大量无效请求。下面这段逻辑用 SocketCreate 建套接字,连到 80 端口做连通性测试,通了才继续,不通直接返回 false 并打印错误码。 CheckServerConnection 函数里 SocketConnect 的第三个参数是超时毫秒数,原文设 1000,也就是 1 秒内没连上就放弃。连接成功后还会用 SocketIsConnected 二次确认,避免半开状态误判。 GetAPI 在拉数据前先调 TestHTTPConnection(内部应调用上面的探活),失败就退出。注意它对品种名做了处理:用 StringReplace 把 'm' 去掉,比如从 'mEURUSD' 变成 'EURUSD' 再拼进 URL 参数,这一步容易漏。 WebRequest 的超时设的是 5000 毫秒,比 Socket 探活宽裕很多,因为真正拉 JSON 耗时更长。返回码 -1 代表请求层出错,此时应当读 GetLastError 排查,而不是假设数据为空。
class="type">bool CheckServerConnection() { class="type">int socket = SocketCreate(); if(socket != INVALID_HANDLE) { class=class="str">"cmt">// 连接 ZIWOX 服务器(HTTP 端口为 class="num">80) if(SocketConnect(socket, "www.ziwox.com", class="num">80, class="num">1000)) { Print("DEBUG:与服务器的连接测试成功"); class=class="str">"cmt">// 验证是否已真正连接 if(SocketIsConnected(socket)) { SocketClose(socket); class="kw">return true; } } else { Print("DEBUG: Falha ao conectar com o servidor: ", GetLastError()); } SocketClose(socket); } else { Print("DEBUG: Falha ao criar socket: ", GetLastError()); } class="kw">return class="kw">false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 获取 API 数据的功能| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool GetAPI(class="type">class="kw">string symbolname, class="type">class="kw">string apikey, class="type">class="kw">string filename) { if(!TestHTTPConnection()) { Print("DEBUG:Falha no teste de conexão HTTP"); class="kw">return class="kw">false; } class="type">class="kw">string headers = "Content-Type: application/json\r\nAccept: application/json\r\n"; class="type">char post[], result[]; class="type">class="kw">string cookie=NULL; class="type">int res; class=class="str">"cmt">// 移除应用程序接口的符号 "m"。 class="type">class="kw">string cleanSymbol = symbolname; StringReplace(cleanSymbol, "m", ""); class="type">class="kw">string URL = "https:class=class="str">"cmt">//www.ziwox.com/terminal/services/API/V1/fulldata.php"; class="type">class="kw">string params = StringFormat("?expn=ziwoxuser&apikey=%s&apitype=json&pair=%s", apikey, cleanSymbol); Print("DEBUG:Preparando requisição..."); Print("DEBUG: URL completa: ", URL + params); URL = URL + params; class="type">class="kw">string body = StringFormat("{\"apikey\":\"%s\",\"pair\":\"%s\"}", apikey, cleanSymbol); StringToCharArray(body, post, class="num">0, WHOLE_ARRAY, CP_UTF8); Print("DEBUG: Body da requisição:", body); class=class="str">"cmt">// Tenta fazer a requisição Print("DEBUG: Iniciando WebRequest..."); res = WebRequest("GET", URL, cookie, NULL, class="num">5000, post, class="num">0, result, headers); Print("DEBUG:Código de resposta WebRequest:", res); if(res == -class="num">1) {
「把 HTTP 响应落盘并回读解析」
EA 向外呼接口拿到原始报文后,先剔除 <pre> 标签噪声,再把 UTF8 字节流直接写进 FILE_COMMON 公共目录的文件,避免终端沙盒路径找不到。下面这段把响应转 char 数组用 FileWriteArray 落盘,成功返回 true,句柄无效则打印失败——在 MT5 里跑之前,先确认你给的 filename 带后缀且公共目录有写权限,否则高频调用会静默丢数据。
调试输出里 Print 了响应前 500 字符和总长度,实盘中外汇与贵金属波动受杠杆放大,这类外部 API 超时或返回非 200 都可能让策略空窗,建议把 res != 200 的分支接上重试计数而不是立即 return false。
回读函数 JsonDataParse 先用 FileGetInteger 查存在性,再 FILE_READ|FILE_BIN 打开,FileReadArray 一次性灌进 char 数组后 Clear 旧 JsonValue。注意它循环清空的是 APIJSON 而非传入的 _APIJSON 引用,数组名不一致是常见编译能过但逻辑漏清的坑,复制代码时务必对齐变量名。
class="type">int error = GetLastError(); Print("DEBUG: GetLastError retornou: ", error); class="kw">return class="kw">false; } if(res != class="num">200) { Print("DEBUG: Resposta HTTP diferente de class="num">200: ", res); class="kw">return class="kw">false; } class="type">class="kw">string response = CharArrayToString(result, class="num">0, WHOLE_ARRAY, CP_UTF8); class=class="str">"cmt">// 删除标签 <pre> da resposta StringReplace(response, "<pre>", ""); StringReplace(response, "</pre>", ""); Print("DEBUG: Resposta recebida(primeiros class="num">500 caracteres): ", StringSubstr(response, class="num">0, class="num">500)); Print("DEBUG: Tamanho total da resposta: ", StringLen(response)); if(StringFind(response, "invalid api key") >= class="num">0) { Print("DEBUG:API Key inválida encontrada na resposta"); Alert("Invalid API key"); class="kw">return class="kw">false; } class=class="str">"cmt">// Tenta salvar a resposta Print("DEBUG: Tentando salvar resposta no arquivo: ", filename); class="type">int filehandle = FileOpen(filename, FILE_WRITE|FILE_BIN|FILE_COMMON); if(filehandle != INVALID_HANDLE) { class=class="str">"cmt">// 将响应转换为特征数组 class="type">char responseArray[]; StringToCharArray(response, responseArray, class="num">0, StringLen(response)); class=class="str">"cmt">// Salva no arquivo FileWriteArray(filehandle, responseArray); FileClose(filehandle); Print("DEBUG: Arquivo salvo com sucesso"); class="kw">return true; } Print("DEBUG: Falha ao salvar arquivo"); class="kw">return class="kw">false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| JSON 文件分析功能| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void JsonDataParse(class="type">class="kw">string filename, class="type">class="kw">string &_APIJSON[]) { for (class="type">int arraIn = class="num">0; arraIn < ArraySize(APIJSON); arraIn++) APIJSON[arraIn] = ""; if (FileGetInteger(filename, FILE_EXISTS, true) >= class="num">0) { class="type">int FileHandle = FileOpen(filename, FILE_READ|FILE_BIN|FILE_COMMON); if(FileHandle != INVALID_HANDLE) { class="type">char jsonarray[]; FileReadArray(FileHandle, jsonarray); FileClose(FileHandle); JsonValue.Clear();
◍ 把 JSON 落进 MT5 变量里
收到 AIGC 接口返回的字节流后,第一步是把它转成可读字符串再反序列化,否则 EA 连字段名都解析不到。 下面这段把 UTF-8 字节数组转字符串并打印,随后用 JsonValue.Deserialize 解析,把 10 个字段逐一塞进 _APIJSON 数组:Symbol、Last Price、Base Fundamental Bias、AI Bullish Forecast、AI Bearish Forecast、Retail Long Ratio、Retail Short Ratio、D1 Trend、D1 RSI、Allow To Trade。 你在 MT5 里跑这段代码,终端会先打印 'JSON Recebido:' 再打印 'Dados processados:',能直观确认哪一步断掉。外汇与贵金属行情受杠杆与突发事件影响,接口给的 AI 预测仅作概率参考,实盘前务必用历史数据回测。
class="type">class="kw">string jsonString = CharArrayToString(jsonarray, class="num">0, WHOLE_ARRAY, CP_UTF8); Print("JSON Recebido: ", jsonString); class=class="str">"cmt">// 调试 if(JsonValue.Deserialize(jsonString)) { _APIJSON[class="num">0] = JsonValue[class="num">0]["Symbol"].ToStr(); _APIJSON[class="num">1] = JsonValue[class="num">0]["Last Price"].ToStr(); _APIJSON[class="num">2] = JsonValue[class="num">0]["Base Fundamental Bias"].ToStr(); _APIJSON[class="num">3] = JsonValue[class="num">0]["AI Bullish Forecast"].ToStr(); _APIJSON[class="num">4] = JsonValue[class="num">0]["AI Bearish Forecast"].ToStr(); _APIJSON[class="num">5] = JsonValue[class="num">0]["Retail Long Ratio"].ToStr(); _APIJSON[class="num">6] = JsonValue[class="num">0]["Retail Short Ratio"].ToStr(); _APIJSON[class="num">7] = JsonValue[class="num">0]["D1 Trend"].ToStr(); _APIJSON[class="num">8] = JsonValue[class="num">0]["D1 RSI"].ToStr(); _APIJSON[class="num">9] = JsonValue[class="num">0]["Allow To Trade"].ToStr(); Print("Dados processados:"); Print("Symbol: ", _APIJSON[class="num">0]); Print("Last Price: ", _APIJSON[class="num">1]);