价格行为分析工具包开发(第六部分):均值回归信号捕捉器(基础篇)
「均值回归信号捕捉器的设计起点」
在 MT5 里做均值回归,核心是先定义「偏离」——价格离基准均线多远才算异常。多数交易者拍脑袋设 2 倍标准差,但不同品种波动结构差很大,贵金属和直盘货币对不能套同一套阈值。 原文给出的基准取 20 周期 SMA,偏离度用 (Close - SMA20) / SMA20 表达,回看样本里欧元兑美元在 2024 年 Q2 有 11 次该比值突破 +1.8% 后 5 根 K 线内回踩均线,概率约 73%。 真正能跑起来的信号不是「碰线就反」,而是偏离同时伴随成交量萎缩,否则容易在趋势中段被洗。开 MT5 把下面这段挂到 EURUSD 的 M15,先把偏离分布打印出来再谈参数。
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头部参数与句柄声明,直接决定你复刻时改哪几个数:
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 的文本刷新节奏是否和你的盯盘频率对得上。
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">//+------------------------------------------------------------------+