# 布林带均值回归EA - 完整MQL4源码
本文提供一个基于布林带均值回归原理的完整自动交易EA。与趋势跟踪策略不同,本EA进行反向交易:当价格触及上轨时做空,触及下轨时做多,预期价格会回归到中轨附近。
策略逻辑
EA使用可配置周期和标准差的布林带。当价格触及或跌破下轨时开多单,当价格触及或突破上轨时开空单。ATR波动率过滤确保只在市场有足够波动时入场,避免在低波动横盘市场中产生假信号。
完整MQL4代码
```mql4
//+------------------------------------------------------------------+
//| BB_MeanReversionEA.mq4 |
//| 自主编译 |
//| |
//+------------------------------------------------------------------+
#property copyright "AI助手"
#property link ""
#property version "1.00"
#property strict
//--- 输入参数
input double LotSize = 0.1; // 固定手数(RiskPercent=0时使用)
input double RiskPercent = 1.0; // 风险百分比(基于可用保证金)
input int BandsPeriod = 20; // 布林带周期
input double BandsDeviation = 2.0; // 布林带标准差倍数
input int ATRPeriod = 14; // ATR周期(波动率过滤)
input double MinATR = 10.0; // 最小ATR值(点数,0=禁用)
input int StopLoss = 60; // 止损点数
input int TakeProfit = 120; // 止盈点数
input bool UseDynamicTP = true; // 使用ATR动态止盈
input double TPMultiplier = 2.5; // 动态止盈ATR倍数
input int BreakEvenPips = 30; // 保本触发点数(0=禁用)
input int MaxSpread = 30; // 最大允许点差
input int Slippage = 10; // 最大滑点
input int MagicNumber = 202412; // EA魔术号
input bool CloseOpposite = true; // 反向信号时平仓反向单
//--- 全局变量
double upperBand = 0, lowerBand = 0, middleBand = 0;
double atrValue = 0;
int pointMultiplier = 10; // 5位报价平台乘数
//+------------------------------------------------------------------+
//| EA初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
// 检测4位还是5位报价平台
if(Digits == 3 || Digits == 5)
pointMultiplier = 10;
else if(Digits == 2 || Digits == 4)
pointMultiplier = 1;
if(BandsPeriod < 2)
{
Print("错误: 布林带周期至少为2");
return(INIT_PARAMETERS_INCORRECT);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA反初始化函数 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
//| EA报价处理函数 |
//+------------------------------------------------------------------+
void OnTick()
{
// 点差检查
if(!IsSpreadOK())
return;
// 计算指标值
CalculateIndicators();
// ATR波动率过滤
if(!IsVolatilityOK())
return;
// 管理现有持仓(保本)
ManageBreakeven();
// 信号检测
if(IsBuySignal())
{
if(CloseOpposite) CloseSellPositions();
if(CountBuyPositions() == 0)
OpenOrder(OP_BUY);
}
else if(IsSellSignal())
{
if(CloseOpposite) CloseBuyPositions();
if(CountSellPositions() == 0)
OpenOrder(OP_SELL);
}
}
//+------------------------------------------------------------------+
//| 计算布林带和ATR值 |
//+------------------------------------------------------------------+
void CalculateIndicators()
{
// 布林带: MODE_UPPER=1, MODE_LOWER=2, MODE_MAIN=0
upperBand = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_UPPER, 0);
lowerBand = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_LOWER, 0);
middleBand = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_MAIN, 0);
atrValue = iATR(Symbol(), 0, ATRPeriod, 0);
}
//+------------------------------------------------------------------+
//| 检查买入信号 - 价格触及下轨 |
//+------------------------------------------------------------------+
bool IsBuySignal()
{
if(lowerBand == 0 || lowerBand == EMPTY_VALUE)
return false;
// 价格触及或跌破下轨
return (Bid <= lowerBand);
}
//+------------------------------------------------------------------+
//| 检查卖出信号 - 价格触及上轨 |
//+------------------------------------------------------------------+
bool IsSellSignal()
{
if(upperBand == 0 || upperBand == EMPTY_VALUE)
return false;
// 价格触及或突破上轨
return (Ask >= upperBand);
}
//+------------------------------------------------------------------+
//| 使用ATR检查波动率是否足够 |
//+------------------------------------------------------------------+
bool IsVolatilityOK()
{
if(MinATR <= 0) return true;
if(atrValue == 0 || atrValue == EMPTY_VALUE) return false;
double minATRPoints = MinATR * Point * pointMultiplier;
return (atrValue >= minATRPoints);
}
//+------------------------------------------------------------------+
//| 基于风险百分比计算手数 |
//+------------------------------------------------------------------+
double CalculateLotSize()
{
if(RiskPercent <= 0)
return LotSize;
double stopLossPoints = StopLoss * pointMultiplier;
if(stopLossPoints <= 0) return LotSize;
double riskAmount = AccountFreeMargin() * RiskPercent / 100.0;
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
if(tickValue == 0 || lotStep == 0) return LotSize;
double calculatedLot = riskAmount / (stopLossPoints * tickValue);
// 按允许步长取整
calculatedLot = MathFloor(calculatedLot / lotStep) * lotStep;
// 最小/最大手数检查
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
if(calculatedLot < minLot) calculatedLot = minLot;
if(calculatedLot > maxLot) calculatedLot = maxLot;
return NormalizeDouble(calculatedLot, 2);
}
//+------------------------------------------------------------------+
//| 开仓函数 |
//+------------------------------------------------------------------+
void OpenOrder(int cmd)
{
double price = (cmd == OP_BUY) ? Ask : Bid;
double sl = 0, tp = 0;
double lot = CalculateLotSize();
// 止损计算
if(StopLoss > 0)
{
if(cmd == OP_BUY)
sl = price - StopLoss * Point * pointMultiplier;
else
sl = price + StopLoss * Point * pointMultiplier;
}
// 止盈计算 - 基于ATR动态调整
if(UseDynamicTP && atrValue > 0)
{
double dynamicTP = atrValue / Point / pointMultiplier * TPMultiplier;
if(cmd == OP_BUY)
tp = price + dynamicTP * Point * pointMultiplier;
else
tp = price - dynamicTP * Point * pointMultiplier;
}
else if(TakeProfit > 0)
{
if(cmd == OP_BUY)
tp = price + TakeProfit * Point * pointMultiplier;
else
tp = price - TakeProfit * Point * pointMultiplier;
}
int ticket = OrderSend(Symbol(), cmd, lot, price, Slippage, sl, tp, "BB MeanRev", MagicNumber, 0, clrNONE);
if(ticket < 0)
Print("开仓失败. 错误码: ", GetLastError());
else
Print("开仓成功. 订单号: ", ticket, " 手数: ", lot);
}
//+------------------------------------------------------------------+
//| 管理现有持仓的保本止损 |
//+------------------------------------------------------------------+
void ManageBreakeven()
{
if(BreakEvenPips <= 0) return;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
double breakevenPoints = BreakEvenPips * Point * pointMultiplier;
if(OrderType() == OP_BUY)
{
double profitPips = (Bid - OrderOpenPrice()) / Point / pointMultiplier;
if(profitPips >= BreakEvenPips && OrderStopLoss() < OrderOpenPrice())
{
OrderModify(OrderTicket(), OrderOpenPrice(), OrderOpenPrice(), OrderTakeProfit(), 0, clrNONE);
Print("保本止损已触发 BUY #", OrderTicket());
}
}
else if(OrderType() == OP_SELL)
{
double profitPips = (OrderOpenPrice() - Ask) / Point / pointMultiplier;
if(profitPips >= BreakEvenPips && (OrderStopLoss() > OrderOpenPrice() || OrderStopLoss() == 0))
{
OrderModify(OrderTicket(), OrderOpenPrice(), OrderOpenPrice(), OrderTakeProfit(), 0, clrNONE);
Print("保本止损已触发 SELL #", OrderTicket());
}
}
}
}
}
}
//+------------------------------------------------------------------+
//| 检查点差是否在限制范围内 |
//+------------------------------------------------------------------+
bool IsSpreadOK()
{
if(MaxSpread <= 0) return true;
int currentSpread = (int)((Ask - Bid) / Point / pointMultiplier);
return (currentSpread <= MaxSpread);
}
//+------------------------------------------------------------------+
//| 统计买单数量 |
//+------------------------------------------------------------------+
int CountBuyPositions()
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == OP_BUY)
count++;
}
}
return count;
}
//+------------------------------------------------------------------+
//| 统计卖单数量 |
//+------------------------------------------------------------------+
int CountSellPositions()
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == OP_SELL)
count++;
}
}
return count;
}
//+------------------------------------------------------------------+
//| 平仓所有买单 |
//+------------------------------------------------------------------+
void CloseBuyPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == OP_BUY)
{
OrderClose(OrderTicket(), OrderLots(), Bid, Slippage, clrNONE);
}
}
}
}
//+------------------------------------------------------------------+
//| 平仓所有卖单 |
//+------------------------------------------------------------------+
void CloseSellPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == OP_SELL)
{
OrderClose(OrderTicket(), OrderLots(), Ask, Slippage, clrNONE);
}
}
}
}
//+------------------------------------------------------------------+
```
参数详解
| 参数 | 说明 | 推荐值 |
|------|------|--------|
| 固定手数 | 固定交易手数(RiskPercent=0时使用) | 0.01-0.1 |
| 风险百分比 | 每笔风险占可用保证金的百分比 | 1.0-2.0 |
| 布林带周期 | 布林带计算周期 | 20 |
| 布林带倍数 | 标准差倍数 | 2.0 |
| ATR周期 | 波动率过滤周期 | 14 |
| 最小ATR | 最小ATR阈值(点数,0=禁用) | 8-15 |
| 止损点数 | 止损距离 | 40-80 |
| 止盈点数 | 固定止盈点数(动态禁用时生效) | 80-160 |
| 动态止盈 | 启用基于ATR的动态止盈 | true |
| 止盈倍数 | 动态止盈的ATR倍数 | 2.0-3.0 |
| 保本点数 | 移动止损到开仓价的触发点数 | 25-40 |
| 最大点差 | 允许的最大点差 | 20-40 |
| 滑点 | 最大滑点容忍度 | 10 |
| 魔术号 | EA唯一标识 | 任意 |
| 反向平仓 | 新信号时平掉反向持仓 | true |
安装步骤
1. 复制代码到MT4的MetaEditor(按F4)
2. 点击编译(F7)- 确保无错误
3. 将EA附加到图表(建议EURUSD、GBPUSD、USDJPY)
4. 在输入参数选项卡中调整参数
5. 启用自动交易(Alt+T)
编译与修改技巧
主要修改方向:
最佳市场环境:
本策略在震荡/盘整市场中表现最佳。在强趋势行情中应避免使用,因为均值回归逻辑会失效。
参考来源
本文EA源码为自主编译,策略基于布林带均值回归原理并加入ATR波动率确认。
*如需更专业的优化版EA(含多周期分析、高级资金管理、完整回测报告),请查看我们的付费EA合集。订阅后可每周获取更新和独家交易工具。*