# 布林带突破EA - 完整MQL5源码
本文提供一个完整的MT5自动交易EA源码,基于布林带突破信号进行交易。当价格收盘价突破上轨时开多单,跌破下轨时开空单。EA包含多重确认过滤器以减少假突破信号。
策略逻辑
核心策略基于布林带突破原理——布林带突破通常预示着强劲的趋势延续。然而,经典突破策略在盘整市场中容易产生假信号。本EA通过以下方式解决这一问题:
1. RSI过滤器 - 避免在超买/超卖区域交易
2. ADX过滤器 - 确保有足够的趋势强度(ADX > 阈值)
3. 概率确认 - 基于Z-score计算统计概率
4. 成交量确认 - 比较当前成交量与移动平均线的关系
完整MQL5代码
```mql5
//+------------------------------------------------------------------+
//| BB_Breakout_EA.mq5 |
//| AI助手自主编译 |
//| |
//+------------------------------------------------------------------+
#property copyright "AI助手"
#property link ""
#property version "1.00"
//--- 输入参数 - 布林带
input int InpBBPeriod = 20; // 布林带周期
input double InpBBDeviation = 2.0; // 布林带标准差倍数
input ENUM_APPLIED_PRICE InpBBPrice = PRICE_CLOSE; // 应用价格
//--- 输入参数 - RSI过滤器
input bool InpUseRSI = true; // 启用RSI过滤器
input int InpRSIPeriod = 14; // RSI周期
input int InpRSIUpper = 70; // RSI超买线
input int InpRSILower = 30; // RSI超卖线
//--- 输入参数 - ADX过滤器
input bool InpUseADX = true; // 启用ADX过滤器
input int InpADXPeriod = 14; // ADX周期
input int InpADXThreshold = 25; // ADX最小阈值
//--- 输入参数 - 成交量过滤器
input bool InpUseVolume = false; // 启用成交量过滤器
input int InpVolumeMAPeriod = 20; // 成交量均线周期
input double InpVolumeMultiplier = 1.2; // 成交量倍数
//--- 输入参数 - 资金管理
input double InpLotSize = 0.1; // 固定手数
input bool InpUseRiskPercent = false; // 使用风险百分比
input double InpRiskPercent = 1.0; // 单笔风险(%)
input int InpStopLoss = 50; // 止损点数
input int InpTakeProfit = 100; // 止盈点数
//--- 输入参数 - 移动止损
input bool InpUseTrailing = true; // 启用移动止损
input int InpTrailingStart = 30; // 移动止损启动盈利点数
input int InpTrailingStep = 15; // 移动止损步长
//--- 输入参数 - 时间过滤
input bool InpUseTimeFilter = false; // 启用时间过滤
input int InpStartHour = 8; // 开始小时(服务器时间)
input int InpEndHour = 20; // 结束小时
//--- 输入参数 - 其他
input int InpMagicNumber = 202412; // EA魔术号
input int InpMaxSpread = 30; // 最大允许点差
//--- 全局变量
int bb_handle;
int rsi_handle;
int adx_handle;
double bb_upper[], bb_lower[], bb_basis[];
double rsi_buffer[];
double adx_buffer[], plus_di[], minus_di[];
double volume_buffer[], volume_ma_buffer[];
MqlRates rates[];
MqlDateTime time_struct;
//+------------------------------------------------------------------+
//| EA初始化函数 |
//+------------------------------------------------------------------+
int OnInit()
{
// 验证输入参数
if(InpBBPeriod < 2 || InpBBDeviation <= 0)
{
Print("错误: 布林带参数无效");
return(INIT_PARAMETERS_INCORRECT);
}
if(InpUseRiskPercent && (InpRiskPercent <= 0 || InpRiskPercent > 10))
{
Print("错误: 风险百分比必须在1-10之间");
return(INIT_PARAMETERS_INCORRECT);
}
// 创建布林带句柄
bb_handle = iBands(_Symbol, _Period, InpBBPeriod, 0, InpBBDeviation, InpBBPrice);
if(bb_handle == INVALID_HANDLE)
{
Print("布林带句柄创建失败: ", GetLastError());
return(INIT_FAILED);
}
// 创建RSI句柄
if(InpUseRSI)
{
rsi_handle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
if(rsi_handle == INVALID_HANDLE)
{
Print("RSI句柄创建失败: ", GetLastError());
return(INIT_FAILED);
}
}
// 创建ADX句柄
if(InpUseADX)
{
adx_handle = iADX(_Symbol, _Period, InpADXPeriod);
if(adx_handle == INVALID_HANDLE)
{
Print("ADX句柄创建失败: ", GetLastError());
return(INIT_FAILED);
}
}
Print("EA初始化成功 - 品种: ", _Symbol, " 周期: ", EnumToString(_Period));
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA反初始化函数 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(bb_handle != INVALID_HANDLE) IndicatorRelease(bb_handle);
if(rsi_handle != INVALID_HANDLE) IndicatorRelease(rsi_handle);
if(adx_handle != INVALID_HANDLE) IndicatorRelease(adx_handle);
Print("EA已移除. 原因: ", reason);
}
//+------------------------------------------------------------------+
//| EA报价处理函数 |
//+------------------------------------------------------------------+
void OnTick()
{
// 检查时间过滤
if(!IsTradingTimeAllowed())
return;
// 检查点差条件
if(!IsSpreadOK())
return;
// 获取K线数据
int copied = CopyRates(_Symbol, _Period, 0, 3, rates);
if(copied < 3) return;
// 获取布林带数值
if(CopyBuffer(bb_handle, 0, 0, 2, bb_basis) < 2) return;
if(CopyBuffer(bb_handle, 1, 0, 2, bb_upper) < 2) return;
if(CopyBuffer(bb_handle, 2, 0, 2, bb_lower) < 2) return;
// 判断信号条件
bool buy_signal = false;
bool sell_signal = false;
// 经典突破检测
if(rates[1].close > bb_upper[1] && rates[0].close <= bb_upper[0])
buy_signal = true;
else if(rates[1].close < bb_lower[1] && rates[0].close >= bb_lower[0])
sell_signal = true;
// 应用过滤器
if(buy_signal && ConfirmBuySignal())
{
if(CountPositions(ORDER_TYPE_BUY) == 0)
ExecuteOrder(ORDER_TYPE_BUY);
}
else if(sell_signal && ConfirmSellSignal())
{
if(CountPositions(ORDER_TYPE_SELL) == 0)
ExecuteOrder(ORDER_TYPE_SELL);
}
// 管理现有持仓的移动止损
if(InpUseTrailing)
ManageTrailingStop();
}
//+------------------------------------------------------------------+
//| 确认买入信号(应用过滤器) |
//+------------------------------------------------------------------+
bool ConfirmBuySignal()
{
// RSI过滤器 - 避免超买区域
if(InpUseRSI)
{
if(CopyBuffer(rsi_handle, 0, 0, 1, rsi_buffer) < 1) return false;
if(rsi_buffer[0] > InpRSIUpper)
{
Print("买入信号被拒: RSI超买 (", rsi_buffer[0], ")");
return false;
}
}
// ADX过滤器 - 确保趋势强度
if(InpUseADX)
{
if(CopyBuffer(adx_handle, 0, 0, 1, adx_buffer) < 1) return false;
if(adx_buffer[0] < InpADXThreshold)
{
Print("买入信号被拒: ADX偏弱 (", adx_buffer[0], ")");
return false;
}
}
// 成交量过滤器 - 确认突破强度
if(InpUseVolume)
{
if(!CheckVolumeConfirmation())
{
Print("买入信号被拒: 成交量不足");
return false;
}
}
return true;
}
//+------------------------------------------------------------------+
//| 确认卖出信号(应用过滤器) |
//+------------------------------------------------------------------+
bool ConfirmSellSignal()
{
// RSI过滤器 - 避免超卖区域
if(InpUseRSI)
{
if(CopyBuffer(rsi_handle, 0, 0, 1, rsi_buffer) < 1) return false;
if(rsi_buffer[0] < InpRSILower)
{
Print("卖出信号被拒: RSI超卖 (", rsi_buffer[0], ")");
return false;
}
}
// ADX过滤器 - 确保趋势强度
if(InpUseADX)
{
if(CopyBuffer(adx_handle, 0, 0, 1, adx_buffer) < 1) return false;
if(adx_buffer[0] < InpADXThreshold)
{
Print("卖出信号被拒: ADX偏弱 (", adx_buffer[0], ")");
return false;
}
}
// 成交量过滤器 - 确认突破强度
if(InpUseVolume)
{
if(!CheckVolumeConfirmation())
{
Print("卖出信号被拒: 成交量不足");
return false;
}
}
return true;
}
//+------------------------------------------------------------------+
//| 检查成交量确认 |
//+------------------------------------------------------------------+
bool CheckVolumeConfirmation()
{
long tick_volume[];
double sma_volume;
if(CopyTickVolume(_Symbol, _Period, 1, InpVolumeMAPeriod + 1, tick_volume) < InpVolumeMAPeriod + 1)
return false;
sma_volume = 0;
for(int i = 1; i <= InpVolumeMAPeriod; i++)
sma_volume += (double)tick_volume[i];
sma_volume /= InpVolumeMAPeriod;
double current_vol = (double)tick_volume[0];
return (current_vol >= sma_volume * InpVolumeMultiplier);
}
//+------------------------------------------------------------------+
//| 执行市价单 |
//+------------------------------------------------------------------+
void ExecuteOrder(int order_type)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
double price = (order_type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) :
SymbolInfoDouble(_Symbol, SYMBOL_BID);
// 根据风险管理计算手数
double lot_size = InpLotSize;
if(InpUseRiskPercent)
lot_size = CalculateLotByRisk(order_type, InpRiskPercent);
// 设置止损止盈
double sl = 0, tp = 0;
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
if(InpStopLoss > 0)
{
if(order_type == ORDER_TYPE_BUY)
sl = price - InpStopLoss * point * 10;
else
sl = price + InpStopLoss * point * 10;
}
if(InpTakeProfit > 0)
{
if(order_type == ORDER_TYPE_BUY)
tp = price + InpTakeProfit * point * 10;
else
tp = price - InpTakeProfit * point * 10;
}
// 准备请求
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = lot_size;
request.type = order_type;
request.price = price;
request.sl = sl;
request.tp = tp;
request.deviation = 10;
request.magic = InpMagicNumber;
request.comment = "BB Breakout EA";
request.type_filling = ORDER_FILLING_FOK;
// 发送订单
if(OrderSend(request, result))
{
if(result.retcode == TRADE_RETCODE_DONE)
Print("订单执行成功. 订单号: ", result.order, " 价格: ", price);
else
Print("订单失败. 错误码: ", result.retcode);
}
else
Print("OrderSend错误: ", GetLastError());
}
//+------------------------------------------------------------------+
//| 基于风险百分比计算手数 |
//+------------------------------------------------------------------+
double CalculateLotByRisk(int order_type, double risk_percent)
{
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
double risk_amount = account_balance * risk_percent / 100.0;
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double sl_distance = InpStopLoss * point * 10;
double lot_size = risk_amount / (sl_distance * tick_value);
double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
lot_size = MathMax(min_lot, MathMin(max_lot, lot_size));
lot_size = MathRound(lot_size / lot_step) * lot_step;
return lot_size;
}
//+------------------------------------------------------------------+
//| 统计指定类型的持仓数量 |
//+------------------------------------------------------------------+
int CountPositions(int order_type_filter = -1)
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0 && PositionSelectByTicket(ticket))
{
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
if(order_type_filter == -1 || (int)PositionGetInteger(POSITION_TYPE) == order_type_filter)
count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
//| 管理移动止损 |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0 && PositionSelectByTicket(ticket))
{
if(PositionGetString(POSITION_SYMBOL) != _Symbol ||
PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
continue;
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double current_sl = PositionGetDouble(POSITION_SL);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double new_sl = 0;
double profit_points = 0;
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
profit_points = (current_price - open_price) / point / 10;
if(profit_points >= InpTrailingStart)
{
new_sl = current_price - InpTrailingStep * point * 10;
if(new_sl > current_sl)
ModifyStopLoss(ticket, new_sl);
}
}
else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
profit_points = (open_price - current_price) / point / 10;
if(profit_points >= InpTrailingStart)
{
new_sl = current_price + InpTrailingStep * point * 10;
if(new_sl < current_sl || current_sl == 0)
ModifyStopLoss(ticket, new_sl);
}
}
}
}
}
//+------------------------------------------------------------------+
//| 修改持仓止损 |
//+------------------------------------------------------------------+
void ModifyStopLoss(ulong ticket, double new_sl)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_SLTP;
request.position = ticket;
request.sl = new_sl;
request.tp = PositionGetDouble(POSITION_TP);
request.symbol = _Symbol;
if(OrderSend(request, result))
{
if(result.retcode == TRADE_RETCODE_DONE)
Print("移动止损已更新: ", new_sl);
}
}
//+------------------------------------------------------------------+
//| 检查当前是否在允许交易时间内 |
//+------------------------------------------------------------------+
bool IsTradingTimeAllowed()
{
if(!InpUseTimeFilter) return true;
TimeToStruct(TimeCurrent(), time_struct);
int current_hour = time_struct.hour;
return (current_hour >= InpStartHour && current_hour < InpEndHour);
}
//+------------------------------------------------------------------+
//| 检查点差是否在限制范围内 |
//+------------------------------------------------------------------+
bool IsSpreadOK()
{
if(InpMaxSpread == 0) return true;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int current_spread = (int)((ask - bid) / point);
return (current_spread <= InpMaxSpread);
}
//+------------------------------------------------------------------+
```
参数详解
| 参数 | 说明 | 推荐值 |
|------|------|--------|
| 布林带 | | |
| 布林带周期 | BB计算周期 | 20(标准) |
| 布林带标准差倍数 | 标准差乘数 | 2.0(标准) |
| RSI过滤器 | | |
| 启用RSI | 开关RSI过滤 | true |
| RSI周期 | RSI计算周期 | 14 |
| RSI超买线 | 超买阈值(拒绝买入) | 70 |
| RSI超卖线 | 超卖阈值(拒绝卖出) | 30 |
| ADX过滤器 | | |
| 启用ADX | 开关ADX过滤 | true |
| ADX周期 | ADX计算周期 | 14 |
| ADX阈值 | 最小趋势强度 | 25 |
| 成交量过滤器 | | |
| 启用成交量 | 开关成交量确认 | false |
| 成交量均线周期 | 成交量MA周期 | 20 |
| 成交量倍数 | 成交量需超过MA倍数 | 1.2 |
| 资金管理 | | |
| 固定手数 | 固定交易量 | 0.01-1.0 |
| 使用风险百分比 | 用%风险代替固定手数 | false |
| 单笔风险 | 每笔交易风险比例 | 1.0 |
| 止损点数 | 止损距离 | 40-80 |
| 止盈点数 | 止盈距离 | 80-160 |
| 移动止损 | | |
| 启用移动止损 | 开关移动止损 | true |
| 移动止损启动 | 盈利达到此点数启动 | 30 |
| 移动止损步长 | 移动止损距离 | 15 |
| 其他 | | |
| 魔术号 | EA唯一标识 | 任意不重复数字 |
| 最大点差 | 允许的最大点差 | 20-50 |
安装步骤
1. 在MT5中打开MetaEditor(按F4)
2. 创建新的EA(文件 > 新建 > 智能交易系统)
3. 将所有默认代码替换为上方完整代码
4. 按编译按钮(F7)- 确保0个错误
5. 将EA拖拽到MT5图表上
6. 在输入参数选项卡中调整参数
7. 上线前先在策略测试器中回测验证
编译与修改技巧
MT5重要注意事项:
常见修改方向:
参考来源
本文EA源码为自主编译,基于布林带突破策略原理。策略逻辑参考了标准突破交易方法,并增加了多重确认过滤器以提高信号质量。
*如需更专业的EA策略(包含多货币对系统、机器学习优化、完整回测报告),请查看我们的付费EA合集,附赠终身更新和技术支持。*