价格行为分析工具包开发(第九部分):外部数据流·进阶篇
(2/3)·只靠 MQL5 内部计算做信号,常卡在统计深度不够;接上外部数据流才打开新口子
用收盘价与均价交叉生成多空信号
这段逻辑把上一根收盘价和一段时间的平均价做对比,直接输出 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 历史数据上验证胜率与回撤。
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() 贴到图表——外汇和贵金属波动快,这种「变才显示」的逻辑能少干扰盘面,但信号本身只是概率倾向,实操请先在模拟盘验证。
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 时才更新图表,等于用一次字符串比对挡掉了同信号的重复重绘,省的是主图对象的频繁刷新开销。
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,用来把底层收到的字节流还原成可读文本。外汇与贵金属波动剧烈,这类信号展示仅作辅助参考,实际下单仍可能因滑点产生偏差,属高风险操作。
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); }