价格行为分析工具包开发(第12部分):外部资金流(3)趋势图谱(TrendMap)(基础篇)
📘

价格行为分析工具包开发(第12部分):外部资金流(3)趋势图谱(TrendMap)(基础篇)

第 1/3 篇

◍ 用 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 本地不拦截就可能漏信号或重复发单。

MQL5 / C++
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 响应实时出现。

MQL5 / C++
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 侧应据此做空信号兜底而非重复发请求。

MQL5 / C++
@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 = {

常见问题

VWAP 反映加权均价区,斐波那契给出概率回撤位,叠加后能过滤掉无资金确认的反抽,只在重合区看方向。
当价格回踩斐波那契关键位且未破 VWAP,倾向视作资金承接;下破 VWAP 且脱离斐波那契区,概率上偏弱。
可以,小布盯盘的 AIGC 已内置这类外部信号诊断,打开对应品种页就能直接看到重合区与箭头提示。
容易漏掉时间戳对齐,导致 Python 端收到的价位和 K 线不对应,画出的箭头会偏移真实信号位。
回看箭头出现前后三根 K 线的成交量分布,若无量配合且脱离 VWAP,大概率为噪音,外汇贵金属高风险需自辨。