William Blau 的 MQL5 指标与交易系统。第一部分:指标(基础篇)
📘

William Blau 的 MQL5 指标与交易系统。第一部分:指标(基础篇)

第 1/3 篇

Blau 工具链在 MT5 的落地前提

William Blau 那套基于动量与反转的交易逻辑,在 MT5 上并非直接可用,而是需要先把他提出的指标改写成 MQL5 程序。没有这层数学层级的代码化,单纯靠肉眼读价格行为,很难把他的经验判断复现成可验证信号。 MT5 社区里较早的系统化整理出现在 2014 年 1 月,一篇关于 Blau 指标与交易系统的文章当时获得 7376 次查看、7 条讨论。这个数据点说明:即便小众,动量类指标的代码化需求在外汇与贵金属交易者中一直有基本盘。 外汇与贵金属杠杆高、滑点随机,直接套用任何指标都存在试错成本。开 MT5 后先确认你的终端版本支持 iCustom 调用,再考虑把 Blau 的原始公式逐行搬进脚本,比盲目加载第三方 ex5 更可控。

「Blau 四阶分析框架怎么落到 MT5 里」

William Blau 在《动量、方向和背离》里把技术分析拆成四个阶段,MQL5 实现全部以源码形式塞进 Blau_Indicators_MQL5_en.zip 归档,直接挂 MT5 就能跑。 第一阶只用 q 根价格柱算原始指标并画线,它本身不表趋势也不抓反转,只是毛坯。 真正关键在平滑:对同一指标连做三次 EMA,周期分别是 r、s、u。三次下来指标延迟被压到极小,能重现细微价格波动,噪声也被吃掉,趋势和反转点才看得清。 再把平滑结果做标准化,数值就能读成超买/超卖。最后用周期 ul 的 EMA 再平滑一次,配上柱线、信号线与超买超卖水平,摆动指标成型,用于区分状态与拐点。外汇与贵金属波动剧烈,这套仅作概率参考,实盘须控仓。

◍ Blau 指标族与 EMA 平滑的坑

William Blau 把 EMA 当成统一平滑工具,覆盖动量、随机动量、市场趋势平均偏差、MACD、烛形动量、复合高低动量六大类,光文件就列了二十多个 mq5,比如 Blau_Mtm、Blau_TSI、Blau_Ergodic 等。每组默认输出三样东西:平滑指数、标准化平滑指数、以及基于标准化值的摆动指标,读者在 MT5 里加载后能看到分层显示。 EMA 的递推式是 EMA(k,n)=price(k)*2/(n+1)+EMA(k-1,n)*(1-2/(n+1)),最新价权重随 n 缩小而放大;n=1 时按 Blau 原书意思就是不做平滑。

标准库 MovingAverages.mqh 里的 ExponentialMAOnBuffer() 有个硬伤:当 period<=1 直接 return(0),导致 n=1 完全跑不起来。代码里能看到 `if(period<=1rates_total-begin<period) return(0);` 这种判断。
改完的 ExponentialMAOnBufferWB() 把条件换成 `if(period<1rates_total-begin<period) return(0);`,放进了 WilliamBlau.mqh,这样 n=1 就能如实表达“无平滑”。同文件还给了 PriceName() 回价格类型字符串、CalculatePriceBuffer() 按所选 applied_price 填 Price[] 数组,支持日线收盘价之外的任意价格类型与时间框架。

外汇和贵金属波动剧烈,这类指标仅描述动量结构,信号失效概率不低,实盘前先用策略 tester 跑一遍周期参数。

MQL5 / C++
EMA(k,n) = EMA(k-<span class="number">class="num">1</span>,n) + <span class="number">class="num">2</span>/(n+<span class="number">class="num">1</span>) * (price(k) - EMA(k-<span class="number">class="num">1</span>,n))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= price(k) * <span class="number">class="num">2</span>/(n+<span class="number">class="num">1</span>) + EMA(k-<span class="number">class="num">1</span>,n) * (class="num">1 - <span class="number">class="num">2</span>/(n+<span class="number">class="num">1</span>))
<span class="keyword">if</span>(period<b><span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153); class="type">class="kw">color:rgb(class="num">226, class="num">8, class="num">0);">&lt;=</span></b><span class="number">class="num">1</span> || rates_total-begin&lt;period) <span class="keyword">class="kw">return</span>(<span class="number">class="num">0</span>);
<span class="keyword">if</span>(period<b><span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153); class="type">class="kw">color:rgb(class="num">226, class="num">8, class="num">0);">&lt;</span></b><span class="number">class="num">1</span> || rates_total-begin&lt;period) <span class="keyword">class="kw">return</span>(<span class="number">class="num">0</span>);
<span class="keyword">class="type">class="kw">string</span> PriceName(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> applied_price <span class="comment">class=class="str">"cmt">// 价格类型</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)
<span class="keyword">class="type">int</span> CalculatePriceBuffer(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> applied_price,&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 价格类型</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> rates_total,&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 柱形数量</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> prev_calculated, <span class="comment">class=class="str">"cmt">// 最近一次调用计算的柱形</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> &amp;Open[],&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Open[]</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> &amp;High[],&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// High[]</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> &amp;Low[],&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Low[]</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">double</span> &amp;Close[],&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Close[]</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> &amp;Price[]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 计算价格数组</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)

Blau 动量:从一阶导数到三重平滑

William Blau 把动量看成价格的一阶导数:单周期动量就是当前收盘减前一收盘的相对变化,而不是内置 iMomentum 那种绝对价差。他进一步用 q 周期动量(默认 q=2)拉开时间跨度,公式为 price - price[q-1],本质仍是收盘价序列的差分。 裸动量只显示价格波动的速度和方向,抓不到趋势也不标反转点。Blau 的做法是叠 EMA:第一次平滑(默认 r=20)近似重现价格曲线主波动,周期越大越贴近;但噪声还在。第二次、第三次平滑(默认 s=5、u=3)专门吃掉噪声,代价是曲线多出一点平移延迟。 有意思的是,小周期平滑的动量常和价格走向背离——比如价格还在涨、动量已拐头,这种背离往往预告走向变化。不过要把它当信号,得结合超买超卖区看才靠谱,后面 TSI 会接着讲。 附件 Blau_Mtm.mq5 直接给了可跑的规范:输入 q/r/s/u 四个周期加 AppliedPrice,u 设 1 就是双平滑,全设 1 就是裸动量。价格数组最小尺寸 = (q-1 + r + s + u - 3 + 1),开 MT5 把默认 (2,20,5,3) 输进去就能看到三平滑动量线。

MQL5 / C++
mtm(price) = price - price[class="num">1]
mtm(price,q) = price - price[q-class="num">1]
Mtm(price,q,r,s,u) = EMA(EMA(EMA( mtm(price,q) ,r),s),u)

「把动量压进 [-1,1] 看超买超卖」

标准化这一步,是把平滑动量做单一标度映射,落进 [-1, +1] 区间。这样不同品种、不同周期的动量终于能放同一把尺子下比。 想要百分比视角,就把标准化后的值再乘 100,区间顺势拉到 [-100, 100]。图 1.4 展示的就是这条标准化平滑动量曲线,越靠两端越极端。 当标准化平滑动量卡在超买或超卖区时,价格与指标之间出现的背离,作为转向信号的可信度倾向更高。外汇与贵金属波动剧烈,这种状态只代表概率倾斜,不代表反转必现。

◍ TSI 的三次平滑与代码骨架

真实强弱指数(TSI)本质是把 q 周期动量做三次 EMA 平滑,再除以同向平滑后的动量绝对值,最后乘 100 映射到 [-100,+100]。它不像裸动量那样跳,三次平滑把噪声压下去,超买超卖区间才有可读性——默认 ±25 只是经验阈值,外汇和贵金属波动大,穿出阈值后反转概率高但绝不意味着一定回头。 MT5 里指标文件叫 Blau_TSI.mq5,四个周期参数 q=2、r=20、s=5、u=3 是出厂设定。价格数组最小尺寸等于 (q-1 + r + s + u-3 +1),也就是 25 根 K 线起步,少于这个数指标不画,回测时别拿太短样本硬跑。 下面这段就是 TSI 核心公式的 MQL5 写法,分母为零时直接返回 0,避免除零崩指标: [CODE]

  • * EMA(EMA(EMA( mtm(price,q) ,r),s),u) 100 * Mtm(price,q,r,s,u)

TSI(price,q,r,s,u) = –––––––––––––––––––––––––------–––––––––– = ––––––––––––––––------–––––––––––––––

EMA(EMA(EMA(mtm(price,q),r),s),u) EMA(EMA(EMA(mtm(price,q),r),s),u)
if EMA(EMA(EMA(mtm(price,q),r),s),u)=0, then TSI(price,q,r,s,u)=0

[/CODE] 逐行拆:第一行分子是动量三次平滑后乘 100;第二行等号两侧都是同一公式的书写变体,右侧用封装好的 Mtm 函数;第三行分母是动量绝对值的同结构三次平滑,作为归一基准;第四行兜底,分母平滑结果若为 0 则 TSI 强制为 0。开 MT5 把这段代码塞进自定义指标,调 r 从 20 改到 13,能看到信号敏感度和假突破次数的明显此消彼长。

MQL5 / C++
class="num">100 * EMA(EMA(EMA( mtm(price,q) ,r),s),u)        class="num">100 * Mtm(price,q,r,s,u)
TSI(price,q,r,s,u) = –––––––––––––––––––––––––------–––––––––– = ––––––––––––––––------–––––––––––––––
          EMA(EMA(EMA( |mtm(price,q)| ,r),s),u)        EMA(EMA(EMA( |mtm(price,q)| ,r),s),u)
if EMA(EMA(EMA(|mtm(price,q)|,r),s),u)=class="num">0, then TSI(price,q,r,s,u)=class="num">0

常见问题

先确认图表周期和数据质量,再核对指标输入参数与默认周期是否匹配,避免用错时间框架导致信号偏移。
按动量、平滑、归一、复合四个层级依次加载对应指标,每层输出作为下一层输入,不要跳阶直接看最终线。
小布可自动比对当前品种的多周期 Blau 数值与历史背离样本,标出可疑信号,省去你手动翻图。
常见坑是 EMA 周期设得过长或重复平滑,建议按原书参数先跑通,再微调单次平滑系数。
容易错在第二次平滑误用了价格而非第一次平滑结果,应按骨架把上阶输出显式传入下阶函数。