简单均值回归交易策略(基础篇)
📘

简单均值回归交易策略(基础篇)

第 1/2 篇

均值回归策略的底子

均值回归属于逆势逻辑:交易者押注价格会拉回某个均衡参考位,而不是顺着已走出的单边继续追。这个参考位通常用统计均值或类似的向心趋势值来刻画。 这里先给一个最朴素的版本——不叠加滤波、不谈仓位,只看「价格偏离均值多远才动手」这一件事。MT5 上这类策略的可验证性很强:把代码挂上 EURUSD 的 H1,跑 2023 年全年,你能直接看到回拉发生的次数与失效次数。 外汇与贵金属杠杆高、跳空频繁,均值回归在趋势市里会被反复打脸,任何「拉回」都只是概率倾向,不是必然。

「用均线捕捉偏离后的回拉」

价格运动很少走直线,图表上常见的是上升、下降与横盘交替出现的阶段,这类阶段在量化语境里叫行情制度。对交易者来说,难点不在识别趋势,而在判断制度切换的临界点。 均值回归最朴素的实现就是一条移动平均线。当价格偏离均线过远,统计上存在一个回摆到均线附近的倾向,这种拉力在外汇与贵金属的小时图以上周期里更容易被观测到,但杠杆品种的高波动会放大假信号,风险显著。 打开 MT5 插入一条 20 期 SMA,肉眼就能验证:欧美对多数时间里价格刺穿带宽超过 2 倍标准差后,数根 K 线内回踩均线的概率偏高,可作为手动观测锚点。

◍ 用平均线识别价格偏离的边界

均值回归假设资产价格会随时间的推移向历史平均价聚拢,这一现象在价格、收益和账面数据里都出现过。把它当择时工具,核心是先把证券的交易区间框出来,再用量化算出平均价,现行价低于均值时买方吸引力可能上升,高于均值则回落概率偏大。 市面上股票报告常给 50 日、100 日移动平均线,但光看均线不够,还得自己辨认这段区间的最高最低价。和纯图表技术比,均值回归靠历史数据推具体买卖价,显得更硬一些;不过现在也有人用 RSI 和 ATR 来抓这类系统形态。 外汇和贵金属里也能观测到均值回归,但偏差修复有时拖好几年,对短线账户基本没用,而且这类品种杠杆高、波动猛,试错成本不小。模型也不是万能:破产或永久基本面变化会让价格再也回不到老均值。 最实际的落点是——怎么定义“离均值太远”?一种极简办法只看价格相对移动平均线的位置,偏离到某个倍数就算触发,具体阈值得在 MT5 里拿历史数据回测校准。

用高低价过滤改写均值回归开仓

把行情与 200 周期均线的间距做 50 周期常规化后,原始逻辑是:常规化值从 100 回落、且收盘价低于 5 周期前并位于 200 均线下方时做多,反向则做空。思路直观,但原版只看收盘价,信号容易在震荡里频繁触发。 我改了一版:不盯收盘价,而是比最高价和最低价的位移。做多要求常规化值自 100 回落、均价在买价上方、且 5 周期前 high 低于 1 周期前 low;做空对称处理。这样开单频率明显下降,净值曲线在回测里显得更平稳。 止损参数对结果影响很大。文中示例里,止损 300 点、30 分钟周期图形更稳;止损放到 4000 点时利润倾向更好,但回撤也更深。外汇与贵金属属高风险品种,这套逻辑未经充分优化前直接上实盘可能亏穿。 下面这段就是改完后的信号核心。EA 仍要靠你针对品种和周期做优化,别指望默认参数通吃。

MQL5 / C++
<span class="keyword">if</span>(previousValue==<span class="number">class="num">100</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(Normalizado&lt;<span class="number">class="num">100</span> &amp;&amp; array_ma[<span class="number">class="num">0</span>]&gt;tick.bid&nbsp;&nbsp;&amp;&amp; rates[<span class="number">class="num">5</span>].<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">221, class="num">0);">high</span> &lt; rates[<span class="number">class="num">1</span>].<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">221, class="num">0);">low</span> )
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"Open Order Buy"</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Alert</span>(<span class="class="type">class="kw">string">" Buying"</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Orden=<span class="class="type">class="kw">string">"Buy"</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sl=<span class="functions">NormalizeDouble</span>(tick.ask - ptsl*<span class="predefines">_Point</span>,<span class="predefines">_Digits</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tp=<span class="functions">NormalizeDouble</span>(tick.bid + pttp*<span class="predefines">_Point</span>,<span class="predefines">_Digits</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//trade.Buy(get_lot(tick.bid),_Symbol,tick.bid,sl,tp);</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;trade.PositionOpen(<span class="predefines">_Symbol</span>,<span class="macro">ORDER_TYPE_BUY</span>,get_lot(tick.bid),tick.bid,sl,tp,<span class="class="type">class="kw">string">"Buy"</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span>(Normalizado&lt;<span class="number">class="num">100</span> &amp;&amp; array_ma[<span class="number">class="num">0</span>]&lt;tick.ask&nbsp;&nbsp;&amp;&amp; rates[<span class="number">class="num">5</span>].<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">221, class="num">0);">low</span> &gt; rates[<span class="number">class="num">1</span>].<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">221, class="num">0);">high</span> )
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"Open Order Sell"</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Alert</span>(<span class="class="type">class="kw">string">" Selling"</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Orden=<span class="class="type">class="kw">string">"Sell"</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sl=<span class="functions">NormalizeDouble</span>(tick.bid + ptsl*<span class="predefines">_Point</span>,<span class="predefines">_Digits</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tp=<span class="functions">NormalizeDouble</span>(tick.ask - pttp*<span class="predefines">_Point</span>,<span class="predefines">_Digits</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//trade.Sell(get_lot_s(tick.ask),_Symbol,tick.ask,sl,tp);</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;trade.PositionOpen(<span class="predefines">_Symbol</span>,<span class="macro">ORDER_TYPE_SELL</span>,get_lot(tick.ask),tick.ask,sl,tp,<span class="class="type">class="kw">string">"Sell"</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

常见问题

没有固定点数,常用价格是偏离20周期均线超过1.5倍ATR时视为明显偏离,可结合品种波动率微调。
用近期高低价做边界过滤,能避开趋势强发散发力段,只在价格触碰偏离边界且回头时开仓,假信号更少。
小布可自动标注价格偏离均线边界并提示回拉时机,你只需打开对应品种页看AI给出的偏离诊断即可省去手算。
贵金属受消息跳空多、趋势性强,单纯均线回归在单边市会连续止损,必须加高低价或波动率过滤并控仓。
边界建议用最近N根K线的高低价而非收盘价,这样能抓到刺透边界后的真实回拉机会。