价格行为分析工具包开发(第12部分):外部资金流(3)趋势图谱(TrendMap)(基础篇)
◍ 用 TrendMap 把外部资金流画成趋势图谱
在 MT5 里做价格行为分析,外部资金流不能只看成一堆数字,得有空间感。TrendMap 的思路是把不同周期的资金净流向映射成一张可叠加的图谱,让趋势的强弱在图上直接显形。 原文给出的样本在 2025-10-23 发布后,单篇阅读量达到 1,180 次,说明这类把资金流可视化的工具在实盘圈有真实需求。 开 MT5 加载这类指标后,优先看日线与四小时两层图谱的错位:当长周期图谱翻绿而短周期仍红,价格倾向进入回调而非反转。外汇与贵金属杠杆高,图谱只作概率参考,仓位仍要自担风险。
「VWAP叠加斐波那契的由来」
2020年新冠冲击下,波动率骤升,不少交易者翻出斐波那契时间区间,试图从历史走势里挖出潜在转折点。政府刺激陆续落地后,经济缓慢重启,这类工具更多是被当成不确定环境中的参考框架之一,而非确定性路标。 上一辑「价格行为分析工具包开发」里,我们做过纯VWAP策略:价在VWAP上方偏多、下方偏空。极端行情里这种单线逻辑容易吃反向鞭子,尤其临近反转时信号会发飘。 这一篇把VWAP和斐波那契回调位绑在一起用。斐波那契位先圈出可能的支撑阻力区,再让VWAP确认方向,策略稳健性理论上会好一些。下面先交待基础概念,再列MQL5 EA与Python脚本的主功能,后续节展开细节与预期结果。 目录先放这:概述、主函数、其他函数、成果、结论。外汇与贵金属杠杆高,任何信号都只是概率倾向,实盘前请在MT5用历史数据自测。
斐波那契叠加VWAP的信号逻辑
斐波那契回调比率源自13世纪莱昂纳多·斐波那契提出的数列,技术分析中常用它标出价格可能反转的潜在支撑与阻力。单看回调位容易被假突破骗线,把VWAP(成交量加权均价)叠进来就不一样:VWAP吃进了成交量和价格,能反映真实流动性与情绪,相当于用成交量给关键反转区做背书,虚假信号的概率会降一截。 趋势图谱系统把这套逻辑拆成了 MQL5 EA + Python 分析服务器。EA 只负责算数和画图,不下单;它按指定根数 K 线找波动高低点,算出 0%–100% 的斐波那契水平(其中 50% 为中间价),再算 VWAP,然后通过 HTTP 把 JSON 发给本地 Python 端。 信号规则很直白:VWAP 低于 50% 回调位(中间价)出买入,高于则出卖出。原文图例里 Fib 3 即 50% 位,VWAP 压在它上方,系统因此给了卖出信号,图上也圈出了价格同时碰斐波那契位和 VWAP 的位置。 Python 侧跑的是 Flask,监听 127.0.0.1:5110,收到 EA 数据后判 VWAP 与中价上下关系返回信号,并用 Matplotlib 在子线程画图存盘。EA 端用定时器周期刷新,收到新信号就在图表画斐波那契线、VWAP 线和箭头。外汇与贵金属波动大、杠杆高,这套信号仅作辅助参考,实盘前请在 MT5 策略测试器用历史数据验证误报率。
◍ EA主循环怎么把斐波那契和VWAP喂给Python
MQL5 这边的主逻辑不是自己算信号,而是先把市场状态算清楚再丢给外部。每次跑信号前,UpdateIndicators() 会重算波动高低点和 VWAP,保证用的是最新 K 线数据;外汇和贵金属波动快,不刷新指标就容易在过时区间上画箭头。 刷新完指标后,BuildJSONPayload() 把品种、周期、波动高低点、VWAP、7 个斐波那契位(循环到 i<7)以及近 InpNumBars 根收盘价打包成 JSON。注意 fibLevels 写死了 7 个元素,若前面算出来不足 7 个就会越界报错,接手代码先核对数组长度。 SendDataToPython() 负责发 HTTP 请求,headers 写死 Content-Type: application/json,postData 数组在片段里只声明未填充,实际要把 payload 转 char 再送。Python 端 Flask 路由收 POST,起线程画图,再用 VWAP 和波动中点的关系回信号:VWAP 低于中点且价靠斐波那契支撑,偏买入;高于中点且价碰阻力,偏卖出。 图表上箭头用 ObjectCreate 动态画,绿向上红向下。这套机制的高风险点在外部服务延迟或断连,MT5 本地不拦截就可能漏信号或重复发单。
class="type">void UpdateIndicators() { CalculateFibonacciLevels(InpTimeFrame, InpNumBars, g_SwingHigh, g_SwingLow, g_FibLevels); g_VWAP = CalculateVWAP(InpTimeFrame, InpNumBars); Print("Updated Indicators: SwingHigh=" + DoubleToString(g_SwingHigh, Digits()) + ", SwingLow=" + DoubleToString(g_SwingLow, Digits()) + ", VWAP=" + DoubleToString(g_VWAP, Digits())); } class="type">class="kw">string BuildJSONPayload() { class="type">class="kw">string jsonPayload = "{"; jsonPayload += "\"symbol\":\"" + Symbol() + "\","; jsonPayload += "\"timeframe\":\"" + EnumToString(InpTimeFrame) + "\","; jsonPayload += "\"swingHigh\":" + DoubleToString(g_SwingHigh, Digits()) + ","; jsonPayload += "\"swingLow\":" + DoubleToString(g_SwingLow, Digits()) + ","; jsonPayload += "\"vwap\":" + DoubleToString(g_VWAP, Digits()) + ","; jsonPayload += "\"fibLevels\":["; for(class="type">int i = class="num">0; i < class="num">7; i++) { jsonPayload += DoubleToString(g_FibLevels[i], class="num">3); if(i < class="num">6) jsonPayload += ","; } jsonPayload += "],"; jsonPayload += "\"priceData\":["; for(class="type">int i = class="num">0; i < InpNumBars; i++) { jsonPayload += DoubleToString(iClose(Symbol(), InpTimeFrame, i), Digits()); if(i < InpNumBars - class="num">1) jsonPayload += ","; } jsonPayload += "]}"; class="kw">return jsonPayload; } class="type">class="kw">string SendDataToPython(class="type">class="kw">string payload) { class="type">class="kw">string headers = "Content-Type: application/json\r\n"; class="type">char postData[];
「把外部信号画成 MT5 箭头」
EA 向 Python 服务发 POST 拿到信号字符串后,要在图表上落点可视化,核心是把 buy/sell 文本映射成箭头对象。下面这段 MQL5 负责发请求并回退处理: StringToCharArray(payload, postData); char result[]; string resultHeaders; int resCode = WebRequest("POST", InpPythonURL, headers, InpHTTPTimeout, postData, result, resultHeaders); if(resCode == 200) { string response = CharArrayToString(result); Print("HTTP Response: " + response); string signal = ParseSignalFromJSON(response); return signal; } else { Print("Error: WebRequest failed with code " + IntegerToString(resCode)); return ""; } 逐行看:先把 payload 转成 char 数组 postData;声明 result 存响应体、resultHeaders 存头;WebRequest 用 POST 打 InpPythonURL,超时受 InpHTTPTimeout 控制。返回 200 才把 result 转字符串、解析 JSON 取信号,否则打印错误码并返回空。外汇与贵金属波动剧烈,信号失效或网络超时都可能让箭头不出现,属正常风险。 DrawSignalArrow 接着把信号画出来:buy 映射箭头码 233 且绿色,sell 映射 234 且红色,其它值直接打印无效并 return。箭头名用 SignalArrow_+TimeCurrent() 避免重名,挂在当前 K 线收盘价位置,线宽设 2。 void DrawSignalArrow(string signal) { int arrowCode = 0; color arrowColor = clrWhite; string lowerSignal = MyStringToLower(signal); if(lowerSignal == "buy") { arrowCode = 233; // Upward arrow arrowColor = clrLime; } else if(lowerSignal == "sell") { arrowCode = 234; // Downward arrow arrowColor = clrRed; } else { Print("Invalid signal: " + signal); return; } string arrowName = "SignalArrow_" + IntegerToString(TimeCurrent()); ObjectCreate(0, arrowName, OBJ_ARROW, 0, TimeCurrent(), iClose(Symbol(), PERIOD_CURRENT, 0)); ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, arrowCode); ObjectSetInteger(0, arrowName, OBJPROP_COLOR, arrowColor); ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, 2); } Python 侧用 matplotlib 画 VWAP 与斐波那契叠加:图尺寸 7.5x5、dpi 100,价格用蓝点线,Fib 水平线线宽 1.5,VWAP 黄实线宽 2。跑通后你可在 MT5 按 F5 编译 EA,观察信号箭头是否随 POST 响应实时出现。
StringToCharArray(payload, postData); class="type">char result[]; class="type">class="kw">string resultHeaders; class="type">int resCode = WebRequest("POST", InpPythonURL, headers, InpHTTPTimeout, postData, result, resultHeaders); if(resCode == class="num">200) { class="type">class="kw">string response = CharArrayToString(result); Print("HTTP Response: " + response); class="type">class="kw">string signal = ParseSignalFromJSON(response); class="kw">return signal; } else { Print("Error: WebRequest failed with code " + IntegerToString(resCode)); class="kw">return ""; } } class="type">void DrawSignalArrow(class="type">class="kw">string signal) { class="type">int arrowCode = class="num">0; class="type">class="kw">color arrowColor = clrWhite; class="type">class="kw">string lowerSignal = MyStringToLower(signal); if(lowerSignal == "buy") { arrowCode = class="num">233; class=class="str">"cmt">// Upward arrow arrowColor = clrLime; } else if(lowerSignal == "sell") { arrowCode = class="num">234; class=class="str">"cmt">// Downward arrow arrowColor = clrRed; } else { Print("Invalid signal: " + signal); class="kw">return; } class="type">class="kw">string arrowName = "SignalArrow_" + IntegerToString(TimeCurrent()); ObjectCreate(class="num">0, arrowName, OBJ_ARROW, class="num">0, TimeCurrent(), iClose(Symbol(), PERIOD_CURRENT, class="num">0)); ObjectSetInteger(class="num">0, arrowName, OBJPROP_ARROWCODE, arrowCode); ObjectSetInteger(class="num">0, arrowName, OBJPROP_COLOR, arrowColor); ObjectSetInteger(class="num">0, arrowName, OBJPROP_WIDTH, class="num">2); }
把 VWAP 与斐波那契判据接成信号接口
这段 Python 服务代码承接前文的绘图函数,用 Flask 的 /getSignal 路由接收 MT5 侧传来的 JSON,解析出 symbol、swingHigh、swingLow、vwap 与 fibLevels 等字段;若客户端未给 fibLevels,则默认落为 [0.236, 0.382, 0.5, 0.618, 1.618] 五档。 信号判定只用 mid_price = (swingHigh+swingLow)/2 作中轴:当 vwap 低于中轴且最新价落在 swingLow 向上 38.2% 回调位下方时,返回 Buy;当 vwap 高于中轴且最新价越过 61.8% 位时,返回 Sell;其余情况给 None。外汇与贵金属波动受杠杆与消息面影响,该信号仅作概率倾向参考,实盘前须在 MT5 用历史数据回测验证。 绘图不在主线程阻塞,而是 threading.Thread 拉起 plot_vwap_fib_vs_price,存图文件名带 symbol 与毫秒级时间戳,便于同品种多频次跑图不覆盖。出错时接口统一回 {signal:None, error:...},MT5 侧应据此做空信号兜底而非重复发请求。
@app.route(&class="macro">#x27;/getSignal&class="macro">#x27;, methods=[&class="macro">#x27;POST&class="macro">#x27;]) def get_signal(): try: raw_data = request.data.decode(&class="macro">#x27;utf-class="num">8&class="macro">#x27;).strip() logging.debug("Raw data received: " + raw_data) decoder = json.JSONDecoder() data, idx = decoder.raw_decode(raw_data) if idx != len(raw_data): logging.error(f"Extra data found after valid JSON: index {idx} of {len(raw_data)}") logging.info("Data received from MQL5: " + json.dumps(data, indent=class="num">2)) except Exception as e: logging.error("Error parsing JSON: " + str(e)) class="kw">return jsonify({"signal": "None", "error": str(e)}) try: symbol = data.get(&class="macro">#x27;symbol&class="macro">#x27;, &class="macro">#x27;Unknown&class="macro">#x27;) swingHigh = class="type">class="kw">float(data.get(&class="macro">#x27;swingHigh&class="macro">#x27;, class="num">0)) swingLow = class="type">class="kw">float(data.get(&class="macro">#x27;swingLow&class="macro">#x27;, class="num">0)) vwap = class="type">class="kw">float(data.get(&class="macro">#x27;vwap&class="macro">#x27;, class="num">0)) fibLevels = data.get(&class="macro">#x27;fibLevels&class="macro">#x27;, [class="num">0.236, class="num">0.382, class="num">0.5, class="num">0.618, class="num">1.618]) price_data = pd.Series(data.get(&class="macro">#x27;priceData&class="macro">#x27;, [])) threading.Thread(target=plot_vwap_fib_vs_price, args=(symbol, vwap, swingHigh, swingLow, fibLevels, price_data)).start() mid_price = np.mean([swingHigh, swingLow]) if vwap < mid_price and price_data.iloc[-class="num">1] < swingLow + (swingHigh - swingLow) * class="num">0.382: signal = "Buy" elif vwap > mid_price and price_data.iloc[-class="num">1] > swingLow + (swingHigh - swingLow) * class="num">0.618: signal = "Sell" else: signal = "None" explanation = f"Signal: {signal} based on VWAP and Fibonacci analysis." except Exception as e: logging.error("Error processing data: " + str(e)) signal = "None" explanation = "Error processing the signal." response = {