在您的 MQL 项目中使用 JSON 数据 API·综合运用
🔌

在您的 MQL 项目中使用 JSON 数据 API·综合运用

(3/3)·从注册密钥到信号过滤,完整跑通 Ziwox 外汇数据在自动化策略里的落地闭环

进阶 第 3/3 篇
很多交易者拿到了 API 密钥却只在脚本里打印一下就闲置,EA 依旧只看 K 线。把外部情绪、基本面偏差接进策略过滤层,才有机会跳出纯技术信号的同质化竞争。

「把接口返回落盘前先卡死无效密钥」

这段逻辑干的事很直接:HTTP 请求回来后先打印成功提示和原始报文,随后用 StringFind 在返回串里搜 "invalid api key"。一旦命中(返回值不等于 -1),立刻弹 Alert 并 return(false),避免拿脏数据继续跑后续。 文件落地用 FileOpen 以 FILE_COMMON 公共目录方式打开,权限叠了读、写、共享、二进制。句柄有效时才 FileWriteArray 把 result 数组整块写进去,写完 FileClose 释放。公共目录意味着多终端实例能读到同一份,路径不用硬编码到某个终端下。 脚本尾部那几个全局变量才是你真要改的:SymbolRequest 写 "EURUSD" 是无前缀纯符号名;APIKey 那串 "76thfd67a6f867df7" 是示例密钥,必须换成你自己的,否则 StringFind 必然命中 invalid 分支直接退出。 外汇与贵金属行情接口调用受网络与供应商限制,请求失败或密钥失效的概率不低,实盘前先在策略测试器或脚本环境里跑一遍确认文件生成。

MQL5 / C++
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 预测都只是概率参考,实盘前务必在策略测试器里跑一遍验证字段解析是否正确。

MQL5 / C++
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 与情绪数据只反映历史仓位倾向,实际价格可能反向,务必以小仓位验证。

MQL5 / C++
  _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 编辑器里改两个周期值,看回测里信号触发次数怎么变。

MQL5 / C++
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 各跑一遍,看样本内回撤变化。

MQL5 / C++
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 不在主代码里,得单独下;漏了这文件编译直接挂,很多人卡在这一步。

MQL5 / C++
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 默认禁止任何出站请求,不手动加域名白名单,整套预测接口根本拿不到数据,后面所有信号都是死值。

MQL5 / C++
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 数据。

MQL5 / C++
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 取值不同,结构完全一致。

MQL5 / C++
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:

MQL5 / C++
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,否则策略连网都出不去。

MQL5 / C++
        "\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 排查,而不是假设数据为空。

MQL5 / C++
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 引用,数组名不一致是常见编译能过但逻辑漏清的坑,复制代码时务必对齐变量名。

MQL5 / C++
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 预测仅作概率参考,实盘前务必用历史数据回测。

MQL5 / C++
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]);
让小布替你盯多源异动
这些跨源数据的异常波动诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到,你只需决定要不要据此调仓。

常见问题

散户统计偏短线零售持仓分布,COT 为机构期货持仓周报,前者高频后者低频,策略中建议分层使用。
目前小布盯盘内置主流多源情报面板,若你用 Ziwox 类 API,可在品种页叠加查看,不必自己写解析前端。
过滤本身不发单,只在符合条件时放行,滑点取决于经纪商执行;外汇贵金属高波动时段仍可能扩大点差。
通常映射为 -1 到 1 的连续量,写入全局变量供 EA 读取,避免每次 tick 都请求远程接口。