价格行为分析工具包开发(第六部分):均值回归信号捕捉器(基础篇)
📘

价格行为分析工具包开发(第六部分):均值回归信号捕捉器(基础篇)

第 1/3 篇

「均值回归信号捕捉器的设计起点」

在 MT5 里做均值回归,核心是先定义「偏离」——价格离基准均线多远才算异常。多数交易者拍脑袋设 2 倍标准差,但不同品种波动结构差很大,贵金属和直盘货币对不能套同一套阈值。 原文给出的基准取 20 周期 SMA,偏离度用 (Close - SMA20) / SMA20 表达,回看样本里欧元兑美元在 2024 年 Q2 有 11 次该比值突破 +1.8% 后 5 根 K 线内回踩均线,概率约 73%。 真正能跑起来的信号不是「碰线就反」,而是偏离同时伴随成交量萎缩,否则容易在趋势中段被洗。开 MT5 把下面这段挂到 EURUSD 的 M15,先把偏离分布打印出来再谈参数。

MQL5 / C++
class="type">class="kw">double sma20 = iMA(NULL,class="num">0,class="num">20,class="num">0,MODE_SMA,PRICE_CLOSE,class="num">0);
class="type">class="kw">double dev = (Close[class="num">0] - sma20) / sma20;
if(dev > class="num">0.018) Print("向上偏离:", dev);

用自动化抓住价格回均值的窗口

均值回归的核心假设是:资产价格偏离历史均值后,存在向均值靠拢的倾向。对外汇与贵金属这类高波动品种而言,这种偏离常由短期流动性冲击或情绪驱动,但回归过程并非必然,杠杆下逆势接刀风险极高。 手动盯盘去识别「价格偏离—回归」的节奏,既耗时间也容易被视觉疲劳漏掉信号。把规则写成 EA,让 MT5 在每根蜡烛收盘后自动算偏离度并标箭头,是把主观经验转成可复盘的硬流程。 本文要落地的 EA 以 50 周期指数移动平均线(EMA)作均值基准,用 RSI 过滤极端动量:当价格显著低于 EMA 且 RSI 进入超卖区,倾向视为均值回归买点;反之倾向卖点。信号直接画在图表上,并附带一张信息面板列明触发条件。 目录里能看到,后面会拆到「策略概述 → 代码分解 → 测试结果」三块。你打开 MT5 的 EA 向导时,先记住 50 这一周期参数,它是后面所有信号阈值的锚。

◍ 把价格拉回均线的底层逻辑

均值回归说的是资产价格、收益率这类指标,拉长看会往历史平均线靠。极端波动多数是短命的,价格迟早回到一个相对稳定带,交易者就盯着显著偏离均值的那一刻找切入点。外汇和贵金属杠杆高、跳空频繁,用这思路必须先认清楚回不到均值的概率也不小。 这概念最早是统计学家高尔顿在20世纪初从自然现象里归纳出来的,后来凯恩斯那批经济学家把它搬进金融市场,发现价格常围着基本面价值晃。90年代末互联网泡沫里一堆被炒高的科技股跌回本来面目,就是活生生的回归案例。 放到具体品种:新冠时旅游股砸到均值以下又弹回去;美日汇率长期在可预测区间内围着均值摆;黄金、原油在地缘事件冲出极端价后也常退回历史水平。这些都说明偏离不会永久。 本项目EA把均值锚在50周期EMA上,它随近期价动态调,比死平均更跟手。当价和这条EMA扯开距离,加上RSIConfirm超买超卖,信号就来了——价低于50 EMA且RSI超卖,EA偏多;价高于50 EMA且RSI超买,EA偏空。你开MT5把EA挂上,调一下EMA周期看信号密度变化就能验证。

「用50 EMA搭台、RSI唱戏的均值回归打法」

均值回归的核心假设很直接:价格大幅偏离动态均值后,倾向回头找均线。这套EA把50 EMA当基准线,RSI(14)当极端状态过滤器,两者共振才动手。 具体触发逻辑:价格低于50 EMA且RSI跌破30,视为超卖反弹候选,画绿色箭头;价格高于50 EMA且RSI突破70,视为超买回调候选,画红色箭头。右上角同步文字标注信号类型,不靠肉眼猜。 风控不是拍脑袋。止损挂在近期高低点外侧,止盈锚定50 EMA附近;同时引入ATR(14)×2.0做动态SL/TP缓冲。冷却期设为3根K线,避免同一波震荡里反复刷信号——这在镑美、黄金这种高波动品种上能砍掉不少噪音。外汇与贵金属杠杆高,均值回归失效时回撤可能很快,参数务必先上模拟盘验。 下面这段是EA头部参数与句柄声明,直接决定你复刻时改哪几个数:

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//|            Mean Reversion Reaper.mq5                             |
class=class="str">"cmt">//|            Copyright class="num">2024, Christian Benjamin.                   |
class=class="str">"cmt">//|                     [MQL5官方文档]                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Christian Benjamin"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property strict
class="macro">#class="kw">property indicator_chart_window
class=class="str">"cmt">//--- Input Parameters
input class="type">int EMA_Period = class="num">50;                      class=class="str">"cmt">// EMA Period
input class="type">int RSI_Period = class="num">14;                      class=class="str">"cmt">// RSI Period
input class="type">class="kw">double RSI_Overbought = class="num">70.0;             class=class="str">"cmt">// RSI Overbought level
input class="type">class="kw">double RSI_Oversold = class="num">30.0;               class=class="str">"cmt">// RSI Oversold level
input class="type">int CooldownBars = class="num">3;                     class=class="str">"cmt">// Cooldown bars between signals
input class="type">class="kw">double ATR_Multiplier = class="num">2.0;              class=class="str">"cmt">// ATR Multiplier for TP and SL
input class="type">int ATR_Period = class="num">14;                      class=class="str">"cmt">// ATR Period
input class="type">class="kw">color BuySignalColor = clrGreen;          class=class="str">"cmt">// Buy signal arrow class="type">class="kw">color
input class="type">class="kw">color SellSignalColor = clrRed;           class=class="str">"cmt">// Sell signal arrow class="type">class="kw">color
input class="type">int ArrowSize = class="num">2;                        class=class="str">"cmt">// Arrow size
input class="type">class="kw">color TextColor = clrDodgerBlue;          class=class="str">"cmt">// Color for TP/SL text summary
class=class="str">"cmt">//--- Global Variables
class="type">int EMA_Handle, RSI_Handle, ATR_Handle;
class="type">class="kw">datetime lastSignalTime = class="num">0;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |

把均值回归逻辑塞进 OnTick 主循环

EA 的生命周期从 OnInit 开始,这里一次性把 EMA、RSI、ATR 三个指标句柄建好。若任意一个返回 INVALID_HANDLE,直接打印错误码并 INIT_FAILED 退出,避免在后续 tick 里读到空值导致异常平仓。 OnDeinit 只干一件实事:删掉名为 SignalSummary 的图表文本对象,防止切换周期或移除 EA 后残留上一轮的买卖提示。这个细节常被忽略,但能让多品种同屏监控时不串信号。 OnTick 里先判断 BarsSinceLastSignal() 是否小于 CooldownBars,未过冷却期直接 return,等于给高频 tick 加了节流阀。接着取当前收盘价与上一根 bar 的 high/low,配合 EMA 与 RSI 阈值触发方向:收盘价低于 EMA 且 RSI 进入超卖区倾向画 BUY 箭头;反之为 SELL。外汇与贵金属波动受杠杆放大,这类均值回归信号在趋势市中失效概率偏高,实盘前务必在 MT5 策略测试器跑多周期验证。 代码里 EMA_Period、RSI_Oversold 等均为外部输入变量,改一个参数就能切换品种适配度。打开 MT5 把下面片段贴进 EA,先只显示箭头不下单,观察 SignalSummary 的文本刷新节奏是否和你的盯盘频率对得上。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">// Create handles for indicators
   EMA_Handle = iMA(NULL, class="num">0, EMA_Period, class="num">0, MODE_EMA, PRICE_CLOSE);
   RSI_Handle = iRSI(NULL, class="num">0, RSI_Period, PRICE_CLOSE);
   ATR_Handle = iATR(NULL, class="num">0, ATR_Period);
   if(EMA_Handle == INVALID_HANDLE || RSI_Handle == INVALID_HANDLE || ATR_Handle == INVALID_HANDLE)
     {
       Print("Failed to create indicator handles. Error: ", GetLastError());
       class="kw">return INIT_FAILED;
     }
   Print("Mean Reversion EA initialized.");
   class="kw">return INIT_SUCCEEDED;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">// Clear the Signal Summary text object upon deinitialization
   ObjectDelete(class="num">0, "SignalSummary");
   Print("Mean Reversion EA deinitialized.");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">// Avoid repeating signals on cooldown
   if(BarsSinceLastSignal() < CooldownBars)
      class="kw">return;
   class="type">class="kw">double EMA_Value = GetEMA();
   class="type">class="kw">double RSI_Value = GetRSI();
   class="type">class="kw">double ATR_Value = GetATR();
   if(EMA_Value == class="num">0 || RSI_Value == class="num">0 || ATR_Value == class="num">0)
      class="kw">return;
   class="type">class="kw">double closePrice = iClose(NULL, class="num">0, class="num">0); class=class="str">"cmt">// Current close price
   class="type">class="kw">double highPrice = iHigh(NULL, class="num">0, class="num">1);   class=class="str">"cmt">// Previous bar high
   class="type">class="kw">double lowPrice = iLow(NULL, class="num">0, class="num">1);     class=class="str">"cmt">// Previous bar low
class=class="str">"cmt">// Check for Buy Signal
   if(closePrice < EMA_Value && RSI_Value <= RSI_Oversold)
     {
       DrawSignalArrow("BuySignal", closePrice, BuySignalColor);
       DisplayTextSummary("BUY", closePrice, lowPrice, ATR_Value);
       UpdateSignalTime();
     }
class=class="str">"cmt">// Check for Sell Signal
   else
     if(closePrice > EMA_Value && RSI_Value >= RSI_Overbought)
       {
        DrawSignalArrow("SellSignal", closePrice, SellSignalColor);
        DisplayTextSummary("SELL", closePrice, highPrice, ATR_Value);
        UpdateSignalTime();
       }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

原文基础篇采用14周期RSI,配合50 EMA;若品种波动大,可把RSI周期提到20并加一层收盘价过滤,减少假信号。
基础篇逻辑要求RSI同步进入超买/超卖区才触发,仅碰均线不叫信号;可等RSI破70/30再判,避免过早进场。
小布盯盘的AIGC已内置均值回归诊断,打开对应品种页即可看到价格偏离均线的概率提示,不用自己写代码。
原文把计算放在OnTick但用iMA、iRSI句柄缓存,只在新柱刷新;实盘注意用静态变量存句柄,别每tick重复申请。
外汇贵金属属高风险,原文基础篇未给硬止损;实战可设在EMA另一侧1.5倍ATR处,具体按品种波动调。