从头开始开发智能交易系统(第 17 部分):访问 web 上的数据(III)·综合运用
📘

从头开始开发智能交易系统(第 17 部分):访问 web 上的数据(III)·综合运用

第 3/3 篇

◍ 用全局变量从外部服务捞指数快照

这段逻辑依赖 MT5 的全局变量在 EA 与数据服务进程之间传数据。先通过 GlobalVariableGet 把通道里的掩码信息和数值拉进本地结构体,再用 CharArrayToString 转成可读字符串做状态判断。 如果返回的字符串等于预设的失败常量,比如 def_MSG_FailedConnection,就直接 Print 提示连接失败;否则继续比对服务器错误、掩码错误、服务停止等状态,都不命中才把拿到的值打印出来。 SetFind 里用静态变量 b 防止重复写入:只有当 GlobalVariableCheck 确认通道存在且 b 不等于当前 user01 时才进分支。以 DOW_JONES 为例,代码把 "INDU:IND" 写进 loc1.Info,并给 loc2.Position 赋了 172783 与 173474 两个偏移量,SP500 则对应 "SPX:IND" 和 175487 起的位置。 开 MT5 把这套全局变量名和 Position 数值抄进自己的接收端,能直接验证外部服务回传的字段排布是否对得上。外汇与贵金属行情受宏观数据冲击大,这类跨进程取数方案仅解决传输,不消除品种本身的高风险。

MQL5 / C++
GlobalVariableGet(def_GlobalMaskInfo, loc1.value);
loc2.value = GlobalVariableGet(def_GlobalValueInChannel);
sz0 = CharArrayToString(loc2.Info, class="num">0, class="kw">sizeof(uDataServer));
if (sz0 == def_MSG_FailedConnection) Print("Failed in connection."); else
if (sz0 == def_MSG_FailedReturn) Print("Error in Server Web."); else
if (sz0 == def_MSG_FailedMask) Print("Bad Mask or position."); else
if (sz0 == def_MSG_FinishServer) Print("Service Stop."); else
Print(CharArrayToString(loc1.Info, class="num">0, class="kw">sizeof(uDataServer)), "  ", loc2.value);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">inline class="type">void SetFind(class="type">void)
{
   class="kw">static class="type">int b = -class="num">1;
   uDataServer loc1, loc2;
   
   if ((GlobalVariableCheck(def_GlobalValueInChannel)) && (b != user01))
   {
      b = user01;
      class="kw">switch (user01)
      {
         case DOW_JONES  :
            StringToCharArray("INDU:IND", loc1.Info, class="num">0, class="kw">sizeof(uDataServer));
            loc2.Position[class="num">0] = class="num">172783;
            loc2.Position[class="num">1] = class="num">173474;
            class="kw">break;
         case SP500      :
            StringToCharArray("SPX:IND", loc1.Info, class="num">0, class="kw">sizeof(uDataServer));
            loc2.Position[class="num">0] = class="num">175487;

「用全局变量打通服务与指标的数据通道」

这段 MT5 服务程序的核心,是把外部抓取的数据通过全局变量(Global Variable)桥接到图表侧的指标。服务以 #property service 声明,版本 1.03,启动时进入 while(!IsStopped()) 的常驻循环,持续刷新 def_GlobalValueInChannel 等三个键值。 若全局变量尚未存在,代码用 GlobalVariableTemp 建临时变量,避免首次运行报错;随后 GlobalVariableGet 把掩码与仓位信息读入 loc1、loc2 两个 uDataServer 结构。 真正拉数据的动作在 GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>"),第二个参数 100 代表尝试抓取的行数上限,返回值写回 def_GlobalValueInChannel。外汇与贵金属交易者借这类通道做跨源比对时,需意识到外部站点延迟或结构变动可能导致断流,属高风险的非常规数据源。 循环里还有一处细节:loc2.Position[1] = 176159 是在特定分支中写死的一个仓位标记位,break 跳出后由 GlobalVariableSet 落盘。你在 MT5 里跑这段代码,可直接在全局变量窗口看到 def_GlobalMaskInfo 与 def_GlobalPositionInfos 的实时数值变化。

MQL5 / C++
loc2.Position[class="num">1] = class="num">176159;
class="kw">break;
}
GlobalVariableSet(def_GlobalMaskInfo, loc1.value);
GlobalVariableSet(def_GlobalPositionInfos, loc2.value);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property service
class="macro">#class="kw">property copyright "Daniel Jose"
class="macro">#class="kw">property version   "class="num">1.03"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Inner Channel.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
{
   uDataServer loc1, loc2;
   
   class="kw">while (!IsStopped())
   {
      if (!GlobalVariableCheck(def_GlobalValueInChannel))
      {
         GlobalVariableTemp(def_GlobalValueInChannel);
         GlobalVariableTemp(def_GlobalMaskInfo);
         GlobalVariableTemp(def_GlobalPositionInfos);
      }
      GlobalVariableGet(def_GlobalMaskInfo, loc1.value);
      GlobalVariableGet(def_GlobalPositionInfos, loc2.value);
      if (!_StopFlag)
      {
         GlobalVariableSet(def_GlobalValueInChannel, GetDataURL(
            "https:class=class="str">"cmt">//tradingeconomics.com/stocks",
            class="num">100,
            "<!doctype html>",

从服务器回读数据的底层拼装

这段片段展示的是 EA 与本地服务进程交换行情快照时的报文拼法:把服务端结构体 loc1 的 Info 字段用 CharArrayToString 按 uDataServer 长度摊成字符串,再塞进 loc2.Position 的前两个槽位,结尾补 0x0D 回车符,整体作为单条记录推过去。 循环里每发一帧就 Sleep(1000),也就是 1 秒节流一次,避免把终端线程跑满;外汇与贵金属行情在高频回读下容易因 broker 端限流丢包,这种节奏偏保守但更稳。 收尾用 GlobalVariableSet 写了一个全局变量 def_GlobalValueInChannel,值来自 Codification(def_MSG_FinishServer),相当于给监听方发「本批完事」的信号。 下方 GetDataURL 是配套的拉取函数:用 WebRequest 发 GET,若返回 -1 直接回 Codification(def_MSG_FailedConnection)。timeout 由调用方传,post 数组置空,说明只取不推。

MQL5 / C++
class="num">2,
CharArrayToString(loc1.Info, class="num">0, class="kw">sizeof(uDataServer)),
loc2.Position[class="num">0],
loc2.Position[class="num">1],
0x0D
);
Sleep(class="num">1000);
GlobalVariableSet(def_GlobalValueInChannel, Codification(def_MSG_FinishServer));

class="type">class="kw">double GetDataURL(class="kw">const class="type">class="kw">string url, class="kw">const class="type">int timeout, class="kw">const class="type">class="kw">string szTest, class="type">int iTest, class="kw">const class="type">class="kw">string szFind, class="type">int iPos, class="type">int iInfo, class="type">char cLimit)
{
   class="type">class="kw">string      headers, szInfo = "";
   class="type">char        post[], charResultPage[];
   class="type">int         counter;
   if (WebRequest("GET", url, NULL, NULL, timeout, post, class="num">0, charResultPage, headers) == -class="num">1) class="kw">return Codification(def_MSG_FailedConnection);

◍ 从网页字节流里抠出那串报价数字

上面这段是 EA 与服务端交互后做数据校验并提取数值的核心收尾逻辑。前两重 for 循环拿预设的 szTest 和 szFind 去逐字符比对返回页面缓冲 charResultPage,任意一位不匹配就走 Codification(def_MSG_FailedReturn) 或 def_MSG_FailedMask 报错退出;循环里都嵌了 !_StopFlag 判断,策略中途被用户点停止时能立刻跳出,不卡线程。 第三处判断若 _StopFlag 已置位,直接返回 def_MSG_FinishServer,相当于优雅终止。接着两个 for 用来跳过 0x20(空格)并一直读到分界符 cLimit,把中间内容拼进 szInfo,最后 StringToDouble 转成 double 返回——这就是从原始字符里析出浮点报价的动作。 下面的 Codification 是个 inline 辅助:把传入字符串塞进 uDataServer 共用体(StringToCharArray 写到 Info 成员),再取 value 字段。共用体布局决定同样的字节被解释成 double,所以报错常量字符串本身也携带着对应的数字编码。 在 MT5 里跑这套,重点看 charResultPage 的编码页是否和服务端一致;若比对总失败,先打印 szTest、szFind 的十六进制,别盲目调超时。外汇与贵金属行情抓取受网络与报价源影响,解析失败概率不低,需留好容错。

MQL5 / C++
for (class="type">int c0 = class="num">0, c1 = StringLen(szTest); (c0 < c1) && (!_StopFlag); c0++) if (szTest[c0] != charResultPage[iTest + c0]) class="kw">return Codification(def_MSG_FailedReturn);
for (class="type">int c0 = class="num">0, c1 = StringLen(szFind); (c0 < c1) && (!_StopFlag); c0++) if (szFind[c0] != charResultPage[iPos + c0]) class="kw">return Codification(def_MSG_FailedMask);
if (_StopFlag) class="kw">return Codification(def_MSG_FinishServer);
for (counter = class="num">0; charResultPage[counter + iInfo] == 0x20; counter++);
for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);

class="kw">return StringToDouble(szInfo);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">inline class="type">class="kw">double Codification(class="kw">const class="type">class="kw">string arg)
{
    uDataServer loc;
    StringToCharArray(arg, loc.Info, class="num">0, class="kw">sizeof(uDataServer));
    
    class="kw">return loc.value;
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「把 web 数据接进 MT5 的路还长」

在 MT5 里折腾 web 数据、套接字和 SQL,前期门槛确实不低。原文作者用一系列文章演示了从套接字通信到本地数据库落地的完整链路,光「市场模拟」套接字部分就写了十一节,SQL 入门又拆成四节,可见这套体系不是看一遍就能跑通的。 对编程底子薄的交易者,建议先 clone 文末的 Servi0o_-_EA.zip(10.71 KB)在策略测试器里跑一遍,再反向读代码。有读者反馈部分系统程序无法通过文件编译,说明环境差异和权限限制仍是实盘前必须自己排的坑。 外汇和贵金属波动剧烈、杠杆风险高,这类外部数据接入方案只解决「信号从哪来」,不解决「信号准不准」。能稳定取数之后,才谈得上拿去喂你自己的价格行为策略。

常见问题

可以用终端全局变量做中转:外部服务把数值写入GlobalVariableSet,EA或指标用GlobalVariableGet直接读取,避免频繁落盘。
先定位数据起始标记,按字段偏移截取ASCII段,再用StringToInteger或StringToDouble转换;建议加长度校验防截断。
小布盯盘的AIGC已内置外部数据诊断,打开对应品种页即可看到web快照是否接通,不用自己拼装底层字节流。
全局变量随终端进程存活,重启即清空;长期通道需服务侧重推或本地缓存,别拿它当持久数据库用。
延迟多来自服务轮询周期和网络RTT,把抓取间隔从10秒压到3秒、并用异步请求可明显收窄,但外汇贵金属高风险仍须人工核对。