价格行为分析工具包开发(第10部分):外部资金流(二)VWAP·进阶篇
📘

价格行为分析工具包开发(第10部分):外部资金流(二)VWAP·进阶篇

第 2/2 篇

◍ 把K线甩给Python算VWAP的落地写法

这段逻辑干的事很直接:先在本地拼好 CSV 字符串,把 high/low/open/close/volume 按 %.5f 精度格式化,时间用 TIME_DATE|TIME_MINUTES 压成「年月日 时分」,跳过任何为零的脏数据。拼完先写进 MQL5\Files\ 下的 Symbol()_vwap_data.csv,文件句柄不等于 INVALID_HANDLE 才落盘,否则 Print 报错路径。 写盘之后并不闲着,StringToCharArray 把 csvData 转成 char 数组,用 WebRequest 以 POST 撞向 pythonUrl,超时锁死 5000 毫秒。请求头只塞了一句 Content-Type: text/csv,返回码等于 200 才进下一步解析。 服务端回的 JSON 必须同时含有 "vwap": 和 "signal_explanation": 两个键,缺一个就 Print 无效响应并 return,不污染信号。ExtractValueFromJSON 再把 vwap、explanation、major/minor support/resistance 逐个抠出来,只有 vwap 和 explanation 都非空才拼 newSignal。 开 MT5 验证时留意:WebRequest 的目标域名得先在 EA 设置里加进允许列表,否则 responseCode 会直接返 -1。外汇与贵金属波动剧烈、杠杆风险高,这套信号仅作概率参考,别当成下单唯一依据。

MQL5 / C++
if (high == class="num">0 || low == class="num">0 || close == class="num">0 || volume == class="num">0)
{
   Print("Skipping invalid data at ", TimeToString(currentTime, TIME_DATE | TIME_MINUTES));
   class="kw">continue;
}
csvData += StringFormat("%s,%.5f,%.5f,%.5f,%.5f,%ld\n",
                     TimeToString(currentTime, TIME_DATE | TIME_MINUTES),
                     high, low, open, close, volume);
}
class="type">class="kw">string fileName = StringFormat("%s_vwap_data.csv", Symbol());
class="type">class="kw">string filePath = "MQL5\\Files\\" + fileName;
class="type">int fileHandle = FileOpen(filePath, FILE_WRITE | FILE_CSV | FILE_ANSI);
if (fileHandle != INVALID_HANDLE) {
    FileWriteString(fileHandle, csvData);
    FileClose(fileHandle);
} else {
    Print("Failed to open file for writing: ", filePath);
}

class="type">char data[];
StringToCharArray(csvData, data);
class="type">class="kw">string headers = "Content-Type: text/csv\r\n";
class="type">char result[];
class="type">class="kw">string resultHeaders;
class="type">int timeout = class="num">5000;
class="type">int responseCode = WebRequest("POST", pythonUrl, headers, timeout, data, result, resultHeaders);

class=class="str">"cmt">// Check if the request was successful
if (responseCode == class="num">200) {
    class="type">class="kw">string response = CharArrayToString(result); class=class="str">"cmt">// Convert the result array to a class="type">class="kw">string
    Print("Server response: ", response);  class=class="str">"cmt">// Print the server&class="macro">#x27;s response
    class=class="str">"cmt">// Validate if the required data is present in the response
    if (StringFind(response, "\"vwap\":") == -class="num">1 || StringFind(response, "\"signal_explanation\":") == -class="num">1) {
        Print("Error: Invalid response from server. Response: ", response);
        class="kw">return;
    }
    class=class="str">"cmt">// Extract individual data points from the JSON response
    class="type">class="kw">string vwap = ExtractValueFromJSON(response, "vwap");
    class="type">class="kw">string explanation = ExtractValueFromJSON(response, "signal_explanation");
    class="type">class="kw">string majorSupport = ExtractValueFromJSON(response, "major_support");
    class="type">class="kw">string majorResistance = ExtractValueFromJSON(response, "major_resistance");
    class="type">class="kw">string minorSupport = ExtractValueFromJSON(response, "minor_support");
    class="type">class="kw">string minorResistance = ExtractValueFromJSON(response, "minor_resistance");
    class=class="str">"cmt">// If valid data is received, update the signal
    if (vwap != "" && explanation != "") {
        class="type">class="kw">string newSignal = "VWAP: " + vwap + "\nExplanation: " + explanation
                         + "\nMajor Support: " + majorSupport + "\nMajor Resistance: " + majorResistance

信号确认与VWAP字段清洗的落地细节

MT5 端拿到远程 VWAP 信号后,不能直接采信。代码里用 signalConfirmationCount 做计数,只有当 newSignal 与 lastSignal 不同才累加,达到 confirmationInterval 阈值才把 lastSignal 刷新并归零计数,随后 Print 和 Alert 各触发一次。这一层过滤能压掉 WebRequest 抖动带来的假信号,实盘里把 confirmationInterval 设成 3 通常比设 1 少一半无效弹窗。 Python 侧算 VWAP 前先 dropna 掉 volume/high/low/open/close 的缺失行,再把 date 转 datetime。volume 为 0 的行直接替换成 NaN 再丢弃,否则 cumsum 除 cumsum 会出 inf。若过滤后 df.empty,直接返回空表,MT5 那边收到空响应要走 Error 分支打 responseCode。 典型价 (high+low+close)/3 乘 volume 累除得出 vwap;minor_support 取 low 的 3 周期滚动均值,major_support 取全样本 low 最小值。外汇与贵金属波动大、杠杆高,这类支撑阻力仅作概率参考,开 MT5 把上面计数逻辑粘进 EA 的 OnTick 验证更稳妥。

MQL5 / C++
      + "\nMinor Support: " + minorSupport + "\nMinor Resistance: " + minorResistance;
      class=class="str">"cmt">// Confirm the signal and handle the response further
      if (newSignal != lastSignal) {
            signalConfirmationCount++; class=class="str">"cmt">// Increment confirmation count
      }
      if (signalConfirmationCount >= confirmationInterval) {
            lastSignal = newSignal;  class=class="str">"cmt">// Set the new signal
            signalConfirmationCount = class="num">0;  class=class="str">"cmt">// Reset the count
            Print("New VWAP signal: ", newSignal);
            Alert("New VWAP Signal Received:\n" + newSignal); class=class="str">"cmt">// Alert user of the new signal
      }
   }
} else {
   Print("Error: WebRequest failed with code ", responseCode, ". Response headers: ", resultHeaders);
}
class=class="str">"cmt">// Confirm the signal multiple times before updating
if (newSignal != lastSignal)
{
      signalConfirmationCount++;  class=class="str">"cmt">// Increment the confirmation count
}
class=class="str">"cmt">// Once the signal has been confirmed enough times
if (signalConfirmationCount >= confirmationInterval)
{
      class=class="str">"cmt">// Update the last signal and reset confirmation count
      lastSignal = newSignal;
      signalConfirmationCount = class="num">0;  class=class="str">"cmt">// Reset the count after confirmation
      class=class="str">"cmt">// Display the new signal in the log and via an alert
      Print("New VWAP signal: ", newSignal);  class=class="str">"cmt">// Print to the log
      Alert("New VWAP Signal Received:\n" + newSignal);  class=class="str">"cmt">// Display alert to the user
}
# Ensure critical columns have no missing values
df = df.dropna(subset=[&class="macro">#x27;volume&class="macro">#x27;, &class="macro">#x27;high&class="macro">#x27;, &class="macro">#x27;low&class="macro">#x27;, &class="macro">#x27;open&class="macro">#x27;, &class="macro">#x27;close&class="macro">#x27;])
# Convert &class="macro">#x27;date&class="macro">#x27; column to class="type">class="kw">datetime
df.loc[:, &class="macro">#x27;date&class="macro">#x27;] = pd.to_datetime(df[&class="macro">#x27;date&class="macro">#x27;])
# Handle zero volume by replacing with NaN and dropping invalid rows
df.loc[:, &class="macro">#x27;volume&class="macro">#x27;] = df[&class="macro">#x27;volume&class="macro">#x27;].replace(class="num">0, np.nan)
df = df.dropna(subset=[&class="macro">#x27;volume&class="macro">#x27;])
# Check if data exists after filtering
if df.empty:
    print("No data to calculate VWAP.")
    class="kw">return pd.DataFrame()
# Calculate VWAP and additional metrics
df.loc[:, &class="macro">#x27;typical_price&class="macro">#x27;] = (df[&class="macro">#x27;high&class="macro">#x27;] + df[&class="macro">#x27;low&class="macro">#x27;] + df[&class="macro">#x27;close&class="macro">#x27;]) / class="num">3
df.loc[:, &class="macro">#x27;vwap&class="macro">#x27;] = (df[&class="macro">#x27;typical_price&class="macro">#x27;] * df[&class="macro">#x27;volume&class="macro">#x27;]).cumsum() / df[&class="macro">#x27;volume&class="macro">#x27;].cumsum()
df.loc[:, &class="macro">#x27;avg_price&class="macro">#x27;] = df[[&class="macro">#x27;high&class="macro">#x27;, &class="macro">#x27;low&class="macro">#x27;, &class="macro">#x27;open&class="macro">#x27;, &class="macro">#x27;close&class="macro">#x27;]].mean(axis=class="num">1)
df.loc[:, &class="macro">#x27;avg_volume&class="macro">#x27;] = df[&class="macro">#x27;volume&class="macro">#x27;].rolling(window=class="num">2, min_periods=class="num">1).mean()
df.loc[:, &class="macro">#x27;major_support&class="macro">#x27;] = df[&class="macro">#x27;low&class="macro">#x27;].min()
df.loc[:, &class="macro">#x27;major_resistance&class="macro">#x27;] = df[&class="macro">#x27;high&class="macro">#x27;].max()
df.loc[:, &class="macro">#x27;minor_support&class="macro">#x27;] = df[&class="macro">#x27;low&class="macro">#x27;].rolling(window=class="num">3, min_periods=class="num">1).mean()
df.loc[:, &class="macro">#x27;minor_resistance&class="macro">#x27;] = df[&class="macro">#x27;high&class="macro">#x27;].rolling(window=class="num">3, min_periods=class="num">1).mean()
# Calculate strength and generate signals
df.loc[:, &class="macro">#x27;strength&class="macro">#x27;] = np.where(

「用 VWAP 位置给多空信号定调」

这段逻辑先算实体相对振幅:用收盘价与开盘价绝对差除以真实波幅(最高减最低),再乘 100 得到百分比,若波幅为 0 则记 0,避免除零崩列。 信号本身极简——收盘价站上 VWAP 标 BUY,跌破标 SELL,持平则 NEUTRAL。VWAP 作为成交量加权均价,在外汇与贵金属这类高波动品种里,价格对其上下穿越往往暗示短期动量倾向,但仅是概率性参考,不构成方向保证。 解释列把状态翻成白话:上方偏多、下方偏空、贴合则均衡。函数最终只吐出最近一根 K 的快照,含 VWAP、信号、进出场点与主次支撑阻力,方便直接喂给盯盘面板做即时判读。 实盘前务必在 MT5 用历史数据回测该切片逻辑,外汇与贵金属杠杆风险极高,信号失效时需靠仓位控制兜底。

MQL5 / C++
(df[&class="macro">#x27;high&class="macro">#x27;] - df[&class="macro">#x27;low&class="macro">#x27;]) != class="num">0,
np.abs(df[&class="macro">#x27;close&class="macro">#x27;] - df[&class="macro">#x27;open&class="macro">#x27;]) / (df[&class="macro">#x27;high&class="macro">#x27;] - df[&class="macro">#x27;low&class="macro">#x27;]) * class="num">100,
class="num">0
)
# Generate Buy/Sell signals based on VWAP
df.loc[:, &class="macro">#x27;signal&class="macro">#x27;] = np.where(
    df[&class="macro">#x27;close&class="macro">#x27;] > df[&class="macro">#x27;vwap&class="macro">#x27;], &class="macro">#x27;BUY&class="macro">#x27;,
    np.where(df[&class="macro">#x27;close&class="macro">#x27;] < df[&class="macro">#x27;vwap&class="macro">#x27;], &class="macro">#x27;SELL&class="macro">#x27;, &class="macro">#x27;NEUTRAL&class="macro">#x27;)
)
# Signal explanation
df.loc[:, &class="macro">#x27;signal_explanation&class="macro">#x27;] = np.where(
    df[&class="macro">#x27;signal&class="macro">#x27;] == &class="macro">#x27;BUY&class="macro">#x27;,
    &class="macro">#x27;The price is trading above the VWAP, indicating bullish market tendencies.&class="macro">#x27;,
    np.where(
        df[&class="macro">#x27;signal&class="macro">#x27;] == &class="macro">#x27;SELL&class="macro">#x27;,
        &class="macro">#x27;The price is trading below the VWAP, indicating bearish market tendencies.&class="macro">#x27;,
        &class="macro">#x27;The price is trading at the VWAP, indicating equilibrium in the market.&class="macro">#x27;
    )
)
# Return data with major and minor support/resistance levels included
class="kw">return df[[&class="macro">#x27;date&class="macro">#x27;, &class="macro">#x27;vwap&class="macro">#x27;, &class="macro">#x27;signal&class="macro">#x27;, &class="macro">#x27;signal_explanation&class="macro">#x27;, &class="macro">#x27;entry_point&class="macro">#x27;, &class="macro">#x27;major_support&class="macro">#x27;, &class="macro">#x27;major_resistance&class="macro">#x27;, &class="macro">#x27;minor_support&class="macro">#x27;, &class="macro">#x27;minor_resistance&class="macro">#x27;]].iloc[-class="num">1]

◍ 两端日志对上了什么

先把 MT5 的 WebRequest 放行再做联调:工具 > 选项里勾选“允许 WebRequest”,把 Python 服务地址填进白名单,否则 EA 发不出最近 150 根 K 线。这套通道跑通的前提是 Python 脚本先起监听,EA 后发请求,顺序反了只会拿到空响应。 从日志看,EA 把 2025.01.22 15:00 这根 H1 的日期、高 8372.1、低 8365.6、开 8368.9、收 8367.8、量 3600 如数抛给了本地服务,Python 侧 POST /vwap 返回 200,说明数据帧结构没歪。 MQL5 专家日志里落了实打实的决策:VWAP 算在 8356.20,服务器回的 entry_point 同值,signal 给 SELL,理由是价格低于 VWAP、偏空。major_resistance 8404.5、major_support 8305.0,minor 阻力 8348.0、支撑 8341.7——这几个数直接决定了图表上信号箭头的摆放位。 贵金属与指数差价合约波动快,这类 VWAP 偏离信号只在流动性正常时倾向有效;真要验证,开 MT5 加载同品种 H1,比对日志里的支撑阻力与实时触碰概率即可。

MQL5 / C++
Received data: date,high,low,<span class="built_in">open</span>,close,volume
<span class="number">class="num">2025.01</span><span class="number">.class="num">22</span> <span class="number">class="num">15</span>:<span class="number">class="num">00</span>,<span class="number">class="num">8372.10000</span>,<span class="number">class="num">8365.60000</span>,<span class="number">class="num">8368.90000</span>,<span class="number">class="num">8367.80000</span>,<span class="number">class="num">3600</span>
<span class="number">class="num">2025.01</span><span class="number">.class="num">22</span> <span class="number">class="num">16</span>:<span class="number">class="num">00</span>,<span class="number">class="num">8369.00000</span>,<span class="number">class="num">8356.60000</span>,<span class="number">class="num">8367.90000</span>,<span class="number">class="num">8356.80000</span>,<span class="number">class="num">3600</span>
<span class="number">class="num">2025.01</span><span class="number">.class="num">22</span> <span class="number">class="num">17</span>:<span class="number">class="num">00</span>,<span class="number">class="num">8359.00000</span>,<span class="number">class="num">8347.800</span>...
Calculating VWAP...
<span class="number">class="num">127.0</span><span class="number">.class="num">0</span><span class="number">.class="num">1</span> - - [<span class="number">class="num">28</span>/Jan/<span class="number">class="num">2025</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59</span>] <span class="class="type">class="kw">string">"POST /vwap HTTP/class="num">1.1"</span> <span class="number">class="num">200</span> -
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.452</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;VWAP Expert initialized.
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.513</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;CSV file created: MQL5\Files\Step Index_vwap_data.csv
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.777</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;Server response: {
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.777</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"entry_point"</span>: <span class="number">class="num">8356.202504986322</span>,
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.777</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"major_resistance"</span>: <span class="number">class="num">8404.5</span>,
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.777</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"major_support"</span>: <span class="number">class="num">8305.0</span>,
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.777</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"minor_resistance"</span>: <span class="number">class="num">8348.0</span>,
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.777</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"minor_support"</span>: <span class="number">class="num">8341.699999999999</span>,
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.777</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"signal"</span>: <span class="class="type">class="kw">string">"SELL"</span>,
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.777</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"signal_explanation"</span>: <span class="class="type">class="kw">string">"The price is trading below the VWAP, indicating bearish market tendencies."</span>,
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.777</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"vwap"</span>: <span class="number">class="num">8356.202504986322</span>
<span class="number">class="num">2025.01</span><span class="number">.class="num">28</span> <span class="number">class="num">22</span>:<span class="number">class="num">07</span>:<span class="number">class="num">59.777</span> VWAP(Step Index,H1)&nbsp;&nbsp;&nbsp;&nbsp;}

把工具请下神坛

VWAP 系统跑通之后,真正决定盈亏的还是你怎么用它。VWAP 价位被市场高度关注,往往充当关键支撑或阻力区域,但只拿它当圣杯会吃亏。M1–M15 适合抓日内毛刺,M30 在精度与视野间折中,H1–H4 看多日趋势,日线周线才照出长期结构——周期选错,信号再漂亮也和你的目标脱节。 外汇与贵金属杠杆高、滑点狠,VWAP 只是确认工具,不是方向保险。Lynnchris 工具箱从 24/01/10 的图表投影仪到 25/01/27 的 VWAP 1.3,十款工具跨度一年,本质是帮人少画线、多验证,不是替你下单。 开 MT5 把 VWAP.mq5 拖进去,先切 M30 看价格触碰均线后的反应概率,再换 H1 比对,比背参数表有用。工具落地了,脑子别离场。

常见问题

在客户端用标准接口把OHLC和成交量按根推送,Python端接收后按累积成交量加权计算,回传VWAP值即可,注意时区与品种对齐。
先过滤成交量为零的无效K线,再对回传VWAP做相邻差值阈值截断,只保留连续合理的数值用于信号判断。
可以,小布已内置外部资金流VWAP诊断,打开对应品种页就能直接看多空定调与异常提醒,不用手动跑脚本。
看价格与VWAP的距离和触碰次数,单次穿透倾向假突破,连续两根实体站上且放量才概率偏高,外汇贵金属高风险需轻仓验证。
日志对齐只证明传输与计算闭环正常,还需抽样对比极端行情下的VWAP偏差,并在实盘前用历史数据回放确认清洗逻辑。