价格行为分析工具包开发(第九部分):外部数据流·进阶篇
🔌

价格行为分析工具包开发(第九部分):外部数据流·进阶篇

(2/3)·只靠 MQL5 内部计算做信号,常卡在统计深度不够;接上外部数据流才打开新口子

实战向进阶 第 2/3 篇
很多交易者把 EA 算出来的信号当唯一真相,却没意识到 MQL5 原生数组处理在滚动统计、缺失值修正上很吃力。直接拿裸价数组跑策略,遇到跳空和异常 tick 容易误判。把外部库当补层而不是替代层,桥接成本比想象低。

用收盘价与均价交叉生成多空信号

这段逻辑把上一根收盘价和一段时间的平均价做对比,直接输出 BUY 或 SELL 信号。若 last_close 大于 average_price,判定为 BUY;反之则 SELL,并附带一句解释说明具体数值关系。 代码里通过 jsonify 把 signal、average_price、average_volume 和解释文本打包成 JSON 返回,方便前端或小布盯盘模块直接解析调用。异常时返回 500 和错误字符串,避免脚本静默崩溃。 本地跑起来要在命令行切到脚本目录执行 python filename.py,服务监听在 189.7.6.8:5877。外汇与贵金属波动剧烈、杠杆高风险大,该信号仅作概率倾向参考,实盘前务必在 MT5 历史数据上验证胜率与回撤。

MQL5 / C++
last_close = df[&class="macro">#x27;prev_close&class="macro">#x27;].iloc[-class="num">1]
if last_close > average_price:
    signal = "BUY"
    signal_explanation = f"The last close price({last_close}) is higher than the average price({average_price})."
else:
    signal = "SELL"
    signal_explanation = f"The last close price({last_close}) is lower than the average price({average_price})."
# Print the signal and explanation
print(f"Generated Signal: {signal}")
print(f"Signal Explanation: {signal_explanation}")
# Return the signal as JSON
class="kw">return jsonify({
    "signal": signal,
    "average_price": average_price,
    "average_volume": average_volume,
    "signal_explanation": signal_explanation
})
except Exception as e:
    class="kw">return jsonify({"error": str(e)}), class="num">500
if __name__ == &class="macro">#x27;__main__&class="macro">#x27;:
    app.run(host=&class="macro">#x27;class="num">189.7.class="num">6.8&class="macro">#x27;, port=class="num">5877)
cd C:\Users\pathway to your python script folder
python filename.py
Running on http:class=class="str">"cmt">//class="num">189.7.class="num">6.8:class="num">5877

「EA主循环怎么把行情喂给Python」

MT5 的 EA 生命周期里,OnInit 只在加载时跑一次,往日志打一句就绪就返回 INIT_SUCCEEDED;OnDeinit 在卸载或平台关闭时触发,目前只做打印,不释放额外句柄。真正的活儿全在 OnTick——每个新 tick 进来都会执行一遍,但先用 TimeCurrent() 减 lastSignalTime 判断是否过了 signalInterval,没到就直接 return,避免高频空转。 数据收集写死在 for 循环里:i 从 1 到 10,用 iTime / iHigh / iLow / iOpen / iClose / iVolume 抓前 10 根 D1 柱的日期、高低开收和成交量,拼成带表头的 CSV 字符串。想扩到 20 天就把循环上界 10 改成 20,本地文件会按 <symbol>_analytics.csv 落盘,FileOpen 拿到句柄后 FileWriteString 写完即关。 CSV 备好后走 WebRequest 发 HTTP POST 给 Python 服务端,头里 Content-Type 设成 application/json。返回俩东西:responseCode(200 算通)和 result 字符数组。成功就把字符数组经 CharArrayToString 转成字符串,失败打错误。响应里用 StringFind + StringSubstr 抠出交易信号、平均价、平均量、信号说明;只有当前信号不等于 lastSignal 才更新变量并 Comment() 贴到图表——外汇和贵金属波动快,这种「变才显示」的逻辑能少干扰盘面,但信号本身只是概率倾向,实操请先在模拟盘验证。

MQL5 / C++
class="type">int OnInit()
{
   Print("Expert initialized. Ready to send data to Python.");
   class="kw">return(INIT_SUCCEEDED);
}
class="type">void OnDeinit(const class="type">int reason)
{
   Print("Expert deinitialized.");
}
class="type">void OnTick()
{
   class=class="str">"cmt">// Check if enough time has passed since the last signal update
   if(TimeCurrent() - lastSignalTime < signalInterval)
   {
      class="kw">return;  class=class="str">"cmt">// Skip if it&class="macro">#x27;s too soon to update
   }
   class=class="str">"cmt">// Collect data and prepare CSV for Python
   class="type">class="kw">string csvData = "date,prev_high,prev_low,prev_open,prev_close,prev_volume\n";
   class=class="str">"cmt">// Get the previous trend data for the last `trendDays`
   for(class="type">int i = class="num">1; i <= class="num">10; i++)  class=class="str">"cmt">// You can adjust the trendDays here
   {
      class="type">class="kw">datetime prevDate = iTime(Symbol(), PERIOD_D1, i);
      class="type">class="kw">double prevHigh = iHigh(Symbol(), PERIOD_D1, i);
      class="type">class="kw">double prevLow = iLow(Symbol(), PERIOD_D1, i);
      class="type">class="kw">double prevOpen = iOpen(Symbol(), PERIOD_D1, i);
      class="type">class="kw">double prevClose = iClose(Symbol(), PERIOD_D1, i);
      class="type">long prevVolume = iVolume(Symbol(), PERIOD_D1, i);
      csvData += StringFormat("%s,%.5f,%.5f,%.5f,%.5f,%ld\n",
                                 TimeToString(prevDate, TIME_DATE | TIME_MINUTES),
                                 prevHigh, prevLow, prevOpen, prevClose, prevVolume);
   }
   class=class="str">"cmt">// Save data to CSV file
   class="type">class="kw">string fileName = StringFormat("%s_analytics.csv", Symbol());
   class="type">int fileHandle = FileOpen(fileName, FILE_WRITE | FILE_CSV | FILE_ANSI);
   if(fileHandle != INVALID_HANDLE)
   {
      FileWriteString(fileHandle, csvData);
      FileClose(fileHandle);

◍ 从 HTTP 回包里抠出信号字段

EA 把 CSV 推给 Python 服务后,真正的落地动作是解析 WebRequest 回来的 JSON。下面这段逻辑只认 responseCode == 200,其他状态码一律走错误打印,避免把脏数据当信号用。 响应体先由 CharArrayToString 转成字符串,再用 StringFind 定位 "signal":、"average_price":、"average_volume":、"signal_explanation": 四个锚点。注意 signal 的截取长度是 signalEnd - signalStart - 12,多减的 12 是留给冒号、引号与逗号占位,实际跑的时候若 Python 返回格式变了,这里会直接截歪。 avgPrice 和 avgVolume 的偏移量分别是 +16 和 +18,对应键名长度加引号;explanation 则从 +23 开始截到下一个引号。外汇与贵金属行情受杠杆影响,信号仅作概率参考,实盘前务必在 MT5 策略测试器里用历史数据验证字段解析是否稳定。 只有当 signal != lastSignal 时才更新图表,等于用一次字符串比对挡掉了同信号的重复重绘,省的是主图对象的频繁刷新开销。

MQL5 / C++
class="type">class="kw">string headers = "Content-Type: application/json\r\n";
class="type">char result[];
class="type">class="kw">string resultHeaders;
class="type">int responseCode = WebRequest(
   "POST",                 class=class="str">"cmt">// HTTP method
   pythonUrl,              class=class="str">"cmt">// URL
   headers,                class=class="str">"cmt">// Custom headers
   timeout,                class=class="str">"cmt">// Timeout in milliseconds
   data,                   class=class="str">"cmt">// Data to send
   result,                 class=class="str">"cmt">// Response content
   resultHeaders           class=class="str">"cmt">// Response headers
);
if(responseCode == class="num">200)
{
   class="type">class="kw">string response = CharArrayToString(result);
   Print("Received response: ", response);
}
else
{
   Print("Error: HTTP request failed with code ", responseCode);
}
if(responseCode == class="num">200)
{
   class="type">class="kw">string signal = "";
   class="type">class="kw">string avgPrice = "";
   class="type">class="kw">string avgVolume = "";
   class="type">class="kw">string explanation = "";
   class=class="str">"cmt">// Extract signal, avgPrice, avgVolume, and explanation from the response
   class="type">int signalStart = StringFind(response, "\"signal\":");
   class="type">int signalEnd = StringFind(response, "\"average_price\":");
   class="type">int explanationStart = StringFind(response, "\"signal_explanation\":");
   class="type">int avgPriceStart = StringFind(response, "\"average_price\":");
   class="type">int avgVolumeStart = StringFind(response, "\"average_volume\":");
   if(signalStart != -class="num">1 && signalEnd != -class="num">1)
   {
      signal = StringSubstr(response, signalStart + class="num">10, signalEnd - signalStart - class="num">12);
   }
   if(explanationStart != -class="num">1)
   {
      explanation = StringSubstr(response, explanationStart + class="num">23, StringFind(response, "\"", explanationStart + class="num">23) - (explanationStart + class="num">23));
   }
   if(avgPriceStart != -class="num">1)
   {
      avgPrice = StringSubstr(response, avgPriceStart + class="num">16, StringFind(response, "\"", avgPriceStart + class="num">16) - (avgPriceStart + class="num">16));
   }
   if(avgVolumeStart != -class="num">1)
   {
      avgVolume = StringSubstr(response, avgVolumeStart + class="num">18, StringFind(response, "\"", avgVolumeStart + class="num">18) - (avgVolumeStart + class="num">18));
   }
   class=class="str">"cmt">// Update the chart if the signal has changed
   if(signal != lastSignal)
   {
      lastSignal = signal;

把信号与均值回写进图表和日志

这段收尾逻辑干的事很直接:只要信号发生变化,就把当前时间写进 lastSignalTime,拼一段含信号方向、平均价、平均量以及解释文字的摘要,同时用 Print 丢进专家日志、用 Comment 钉在图表左上角。 注意两个分支几乎重复——第一个在更大花括号内(可能是某次接收回调里)更新并输出,第二个在 signal != lastSignal 判定下再次更新输出。实战里这属于冗余,若你复制去用,建议只保留后者,避免同一根 K 线被打印两次摘要。 末尾的 CharArrayToString 是把 char 数组逐个格式化成字符再拼成 string,用来把底层收到的字节流还原成可读文本。外汇与贵金属波动剧烈,这类信号展示仅作辅助参考,实际下单仍可能因滑点产生偏差,属高风险操作。

MQL5 / C++
lastSignalTime = TimeCurrent(); class=class="str">"cmt">// Update last signal time
class="type">class="kw">string receivedSummary = "Signal: " + signal + "\n" +
                              "Avg Price: " + avgPrice + "\n" +
                              "Avg Volume: " + avgVolume + "\n" +
                              "Explanation: " + explanation;
Print("Received metrics and signal: ", receivedSummary);
Comment(receivedSummary); class=class="str">"cmt">// Display it on the chart
}
if(signal != lastSignal)
{
   lastSignal = signal;
   lastSignalTime = TimeCurrent(); class=class="str">"cmt">// Update last signal time
   class="type">class="kw">string receivedSummary = "Signal: " + signal + "\n" +
                               "Avg Price: " + avgPrice + "\n" +
                               "Avg Volume: " + avgVolume + "\n" +
                               "Explanation: " + explanation;
   Print("Received metrics and signal: ", receivedSummary);
   Comment(receivedSummary); class=class="str">"cmt">// Display it on the chart
}
class="type">class="kw">string CharArrayToString(class="type">char &arr[])
{
   class="type">class="kw">string result = "";
   for(class="type">int i = class="num">0; i < ArraySize(arr); i++)
   {
      result += StringFormat("%c", arr[i]);
   }
   class="kw">return(result);
}
让小布替你盯桥接状态
MQL5 与 Python 服务间的 POST 延迟、响应解析失败这类脏活,小布盯盘的 AIGC 已内置巡检,打开对应品种页就能看到通道健康度,你只管看信号解释。

常见问题

Python 端若按列名而非位置解析影响较小;若按位置取数,最高价最低价错位会直接污染信号。建议 EA 侧固定表头并做服务端校验。
取决于 Python 服务部署位置与 HTTP 往返。同机本地服务可能控制在几十毫秒,跨云则倾向用于 1H 以上周期,概率上更稳。
小布盯盘内置的 AIGC 看板可呈现外部分析层回传的解释与信号变化,不必自己搭服务器也能观察桥接结果。
算法一致时数值接近;差异多来自 Pandas 可轻松做加权、去极值,而 MQL5 要手写循环。外汇贵金属波动大,去异常值后信号可能更平滑。