布林中轨穿越多周期马丁EA
五个周期各自独立做布林中轨穿越,止损后逐次加仓0.01手,盈利覆盖亏损后复位
#property copyright "XAUOF 指标工坊"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
//==================== 可调参数 ====================
input long InpBaseMagic = 20250001; // 魔术号基数(5个周期自动用 基数+0~4,互不冲突)
input double InpBaseLot = 0.01; // 初始手数(推荐 0.01)
input double InpLotStep = 0.01; // 每次止损后加仓的手数(推荐 0.01)
input double InpMaxLot = 0.10; // 单周期最大手数保护(推荐 0.05~0.10,防止马丁失控)
input int InpStopLossPts = 600; // 止损点数(XAU 2位小数报价下 600点=6.00美元,推荐 400~800)
input int InpTakeProfitPts = 1200; // 止盈点数(推荐为止损的 1.5~2 倍)
input int InpBollPeriod = 20; // 布林带周期(推荐 20)
input int InpBollDev = 2; // 布林带标准差倍数(推荐 2)
//==================================================
#define TF_COUNT 5
// 每个周期的独立状态
struct TFState
{
ENUM_TIMEFRAMES tf; // 周期
int handle; // 布林带指标句柄
double lot; // 当前应开的手数
double totalLoss; // 本周期累计止损金额(正数)
double totalProfit; // 本周期累计盈利金额
datetime lastBar; // 上一根已处理的K线时间(用于判断新K线)
};
CTrade g_trade;
TFState g_tf[TF_COUNT];
//------------------------------------------------------------------
// 初始化
//------------------------------------------------------------------
int OnInit()
{
// 5 个周期,各自创建一个布林带句柄
ENUM_TIMEFRAMES tfs[TF_COUNT] = {PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H4};
for(int i = 0; i < TF_COUNT; i++)
{
g_tf[i].tf = tfs[i];
g_tf[i].lot = InpBaseLot;
g_tf[i].totalLoss = 0.0;
g_tf[i].totalProfit = 0.0;
g_tf[i].lastBar = 0;
// 创建布林带句柄(只用中轨做信号,上下轨参数仍按标准布林设置)
g_tf[i].handle = iBands(_Symbol, g_tf[i].tf, InpBollPeriod, 0, InpBollDev, PRICE_CLOSE);
if(g_tf[i].handle == INVALID_HANDLE)
{
Print("布林带句柄创建失败,周期索引 ", i);
return INIT_FAILED;
}
}
g_trade.SetDeviationInPoints(30); // 允许 30 点滑点
g_trade.SetTypeFillingBySymbol(_Symbol); // 按品种自动选择成交模式
// 重启 EA 后,从历史成交里恢复每个周期的手数与盈亏统计
RestoreStatsFromHistory();
return INIT_SUCCEEDED;
}
//------------------------------------------------------------------
// 反初始化:释放句柄
//------------------------------------------------------------------
void OnDeinit(const int reason)
{
for(int i = 0; i < TF_COUNT; i++)
if(g_tf[i].handle != INVALID_HANDLE)
IndicatorRelease(g_tf[i].handle);
Comment("");
}
//------------------------------------------------------------------
// 主循环:每个周期独立判断信号
//------------------------------------------------------------------
void OnTick()
{
for(int i = 0; i < TF_COUNT; i++)
{
// ---- 1. 判断该周期是否出现新K线(只在K线收盘后判断一次,避免盘中反复触发)----
datetime curBar = iTime(_Symbol, g_tf[i].tf, 0);
if(curBar == 0 || curBar == g_tf[i].lastBar)
continue;
g_tf[i].lastBar = curBar;
// ---- 2. 该周期已有持仓则跳过(每个周期最多同时 1 单)----
if(HasPosition(i))
continue;
// ---- 3. 等指标计算完毕,取中轨数值 ----
// mid[0] = 前前根K线(shift 2)的中轨,mid[1] = 刚收盘那根(shift 1)的中轨
double mid[2], close[2];
if(BarsCalculated(g_tf[i].handle) < InpBollPeriod + 2)
continue;
if(CopyBuffer(g_tf[i].handle, BASE_LINE, 1, 2, mid) < 2)
continue;
if(CopyClose(_Symbol, g_tf[i].tf, 1, 2, close) < 2)
continue;
// ---- 4. 判断穿越信号:收盘价从下方上穿中轨 → 做多;从上方下穿中轨 → 做空 ----
bool buySignal = (close[1] > mid[1] && close[0] <= mid[0]);
bool sellSignal = (close[1] < mid[1] && close[0] >= mid[0]);
if(!buySignal && !sellSignal)
continue;
// ---- 5. 按当前手数开仓(止损过就自动用加仓后的手数)----
double lot = NormalizeLot(g_tf[i].lot);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(ask <= 0 || bid <= 0)
continue;
double sl = 0, tp = 0;
if(buySignal)
{
if(InpStopLossPts > 0) sl = ask - InpStopLossPts * _Point;
if(InpTakeProfitPts > 0) tp = ask + InpTakeProfitPts * _Point;
}
else
{
if(InpStopLossPts > 0) sl = bid + InpStopLossPts * _Point;
if(InpTakeProfitPts > 0) tp = bid - InpTakeProfitPts * _Point;
}
g_trade.SetExpertMagicNumber(InpBaseMagic + i); // 每个周期独立魔术号
string cmt = "BollMid-" + EnumToString(g_tf[i].tf);
bool ok = buySignal
? g_trade.Buy(lot, _Symbol, 0, sl, tp, cmt)
: g_trade.Sell(lot, _Symbol, 0, sl, tp, cmt);
if(ok)
Print(cmt, " 开仓 ", DoubleToString(lot, 2), " 手 ",
(buySignal ? "多" : "空"),
" | 累计止损 ", DoubleToString(g_tf[i].totalLoss, 2),
" 累计盈利 ", DoubleToString(g_tf[i].totalProfit, 2));
else
Print(cmt, " 开仓失败: ", g_trade.ResultRetcodeDescription());
}
UpdateChartComment();
}
//------------------------------------------------------------------
// 该周期是否已有持仓(按魔术号区分,5 个周期互不干扰)
//------------------------------------------------------------------
bool HasPosition(int idx)
{
long magic = InpBaseMagic + idx;
for(int p = PositionsTotal() - 1; p >= 0; p--)
{
ulong ticket = PositionGetTicket(p);
if(ticket == 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != magic) continue;
return true;
}
return false;
}
//------------------------------------------------------------------
// 手数规范化:按最小手数/步长/最大手数取整
//------------------------------------------------------------------
double NormalizeLot(double lot)
{
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(step > 0)
lot = MathFloor(lot / step + 0.5) * step; // 四舍五入到步长
if(lot < minLot) lot = minLot;
if(lot > maxLot) lot = maxLot;
if(lot > InpMaxLot) lot = InpMaxLot; // 马丁保护上限
return lot;
}
//------------------------------------------------------------------
// 根据一笔平仓盈亏更新某周期的统计:
// 亏损 → 累计止损金额增加,下次开仓手数 +InpLotStep
// 盈利 → 累计盈利增加;若盈利总额覆盖了止损总额,手数复位为初始手数
//------------------------------------------------------------------
void UpdateStats(int idx, double profit)
{
if(idx < 0 || idx >= TF_COUNT) return;
if(profit < 0)
{
g_tf[idx].totalLoss += -profit; // 累计止损金额
g_tf[idx].lot += InpLotStep; // 下次开仓加仓
}
else
{
g_tf[idx].totalProfit += profit; // 累计盈利金额
if(g_tf[idx].totalLoss > 0 &&
g_tf[idx].totalProfit >= g_tf[idx].totalLoss)
{
// 盈利总和覆盖了过去的止损总和 → 复位
Print(EnumToString(g_tf[idx].tf),
" 盈利 ", DoubleToString(g_tf[idx].totalProfit, 2),
" 已覆盖止损 ", DoubleToString(g_tf[idx].totalLoss, 2),
",手数复位为 ", DoubleToString(InpBaseLot, 2));
g_tf[idx].lot = InpBaseLot;
g_tf[idx].totalLoss = 0.0;
g_tf[idx].totalProfit = 0.0;
}
}
if(g_tf[idx].lot > InpMaxLot)
g_tf[idx].lot = InpMaxLot; // 保护上限
}
//------------------------------------------------------------------
// 魔术号 → 周期索引(不属于本 EA 的返回 -1)
//------------------------------------------------------------------
int MagicToIndex(long magic)
{
long diff = magic - InpBaseMagic;
if(diff < 0 || diff >= TF_COUNT) return -1;
return (int)diff;
}
//------------------------------------------------------------------
// 实时捕捉平仓事件(EA 运行中更新统计)
//------------------------------------------------------------------
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
if(trans.type != TRADE_TRANSACTION_DEAL_ADD) return;
ulong ticket = trans.deal;
if(ticket == 0 || !HistoryDealSelect(ticket)) return;
if(HistoryDealGetString(ticket, DEAL_SYMBOL) != _Symbol) return;
long entry = HistoryDealGetInteger(ticket, DEAL_ENTRY);
if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_OUT_BY) return; // 只看平仓单
int idx = MagicToIndex(HistoryDealGetInteger(ticket, DEAL_MAGIC));
if(idx < 0) return;
// 平仓净盈亏 = 盈亏 + 库存费 + 手续费
double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT)
+ HistoryDealGetDouble(ticket, DEAL_SWAP)
+ HistoryDealGetDouble(ticket, DEAL_COMMISSION);
UpdateStats(idx, profit);
}
//------------------------------------------------------------------
// 重启后从历史成交恢复统计(按时间顺序重放每笔平仓)
//------------------------------------------------------------------
void RestoreStatsFromHistory()
{
if(!HistorySelect(0, TimeCurrent())) return;
int total = HistoryDealsTotal();
for(int i = 0; i < total; i++)
{
ulong ticket = HistoryDealGetTicket(i);
if(ticket == 0) continue;
if(HistoryDealGetString(ticket, DEAL_SYMBOL) != _Symbol) continue;
long entry = HistoryDealGetInteger(ticket, DEAL_ENTRY);
if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_OUT_BY) continue;
int idx = MagicToIndex(HistoryDealGetInteger(ticket, DEAL_MAGIC));
if(idx < 0) continue;
double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT)
+ HistoryDealGetDouble(ticket, DEAL_SWAP)
+ HistoryDealGetDouble(ticket, DEAL_COMMISSION);
UpdateStats(idx, profit);
}
}
//------------------------------------------------------------------
// 图表左上角显示各周期状态
//------------------------------------------------------------------
void UpdateChartComment()
{
string s = "=== 布林中轨穿越多周期马丁EA ===\n";
for(int i = 0; i < TF_COUNT; i++)
{
s += EnumToString(g_tf[i].tf)
+ " | 手数 " + DoubleToString(g_tf[i].lot, 2)
+ " | 累计止损 " + DoubleToString(g_tf[i].totalLoss, 2)
+ " | 累计盈利 " + DoubleToString(g_tf[i].totalProfit, 2)
+ (HasPosition(i) ? " | 持仓中" : " | 空仓")
+ "\n";
}
Comment(s);
}作品说明
好的,我来写一个基于布林带中轨穿越的多周期 EA,每个周期独立运行,带马丁格尔式加仓逻辑。
核心逻辑
- 信号:K线上穿BOLL中轨做多,下穿做空
- 5个周期独立运行:M5/M15/M30/H1/H4 各自用不同 magic number 区分
- 手数管理:初始 0.01 手,止损后加 0.01,盈利覆盖亏损后重置
- 状态追踪:用全局变量存储每个周期的累计盈亏和当前手数
```cpp // filename: BOLL_MultiPeriod_Martingale_EA.mq5 // title: 布林带多周期马丁EA // summary: 基于布林带中轨穿越,5周期独立交易,止损后递增手数,盈利覆盖亏损后重置
#property copyright "XAUOF 指标工坊" #property version "1.00" #property strict
#include <Trade\Trade.mqh>
//--- 输入参数 input group "=== 布林带参数 ===" input int InpBBPeriod = 20; // 布林带周期 input double InpBBDeviation = 2.0; // 布林带标准差 input ENUM_APPLIED_PRICE InpBBPrice = PRICE_CLOSE; // 布林带应用价格
input group "=== 交易参数 ===" input double InpStopLoss = 100; // 止损点数(0=不设止损) input double InpTakeProfit = 200; // 止盈点数(0=不设止盈) input double InpBaseLots = 0.01; // 基础开仓手数 input double InpLotStep = 0.01; // 止损后每次加仓手数
input group "=== 系统参数 ===" input int InpMagicBase = 10000; // 魔术数字基数(每个周期+周期分钟数) input string InpComment = "BOLL_Multi";// 订单备注
//--- 全局变量 CTrade trade;
// 5个周期:M5, M15, M30, H1, H4 ENUM_TIMEFRAMES Timeframes[5] = {PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H4}; int BollingerHandles[5]; // 布林带句柄 int MagicNumbers[5]; // 每个周期的魔术数字 static bool HadPosition[5]; // 上次是否有持仓(用于检测平仓)
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // 初始化 CTrade trade.SetExpertMagicNumber(InpMagicBase); trade.SetDeviationInPoints(50); trade.SetTypeFilling(ORDER_FILLING_FOK); trade.SetAsyncMode(false);
// 为每个周期创建布林带句柄和魔术数字 for(int i = 0; i < 5; i++) { BollingerHandles[i] = iBands(_Symbol, Timeframes[i], InpBBPeriod, 0, InpBBDeviation, InpBBPrice); if(BollingerHandles[i] == INVALID_HANDLE) { Print("初始化布林带失败,周期 ", EnumToString(Timeframes[i])); return INIT_FAILED; }
// 魔术数字 = 基数 + 周期分钟数(M5=5, M15=15, M30=30, H1=60, H4=240) MagicNumbers[i] = InpMagicBase + PeriodSeconds(Timeframes[i]) / 60;
HadPosition[i] = false;
// 初始化全局变量(如果不存在) string prefixProfit = "BOLL_EA_Profit_" + IntegerToString(MagicNumbers[i]); string prefixLoss = "BOLL_EA_Loss_" + IntegerToString(MagicNumbers[i]); string prefixLots = "BOLL_EA_Lots_" + IntegerToString(MagicNumbers[i]);
if(!GlobalVariableCheck(prefixProfit)) GlobalVariableSet(prefixProfit, 0.0); if(!GlobalVariableCheck(prefixLoss)) GlobalVariableSet(prefixLoss, 0.0); if(!GlobalVariableCheck(prefixLots)) GlobalVariableSet(prefixLots, InpBaseLots); }
Print("EA 初始化成功,监控 5 个周期:M5, M15, M30, H1, H4"); return INIT_SUCCEEDED; }
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // 释放指标句柄 for(int i = 0; i < 5; i++) { if(BollingerHandles[i] != INVALID_HANDLE) IndicatorRelease(BollingerHandles[i]); } }
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // 遍历 5 个周期,各自独立处理 for(int i = 0; i < 5; i++) { ProcessTimeframe(i); } }
//+------------------------------------------------------------------+ //| 处理单个周期的交易逻辑 | //+------------------------------------------------------------------+ void ProcessTimeframe(int index) { ENUM_TIMEFRAMES tf = Timeframes[index]; int handle = BollingerHandles[index]; int magic = MagicNumbers[index];
// 1. 检查是否刚平仓,更新盈亏统计 bool hasPos = HasPosition(magic); if(HadPosition[index] && !hasPos) { // 刚平仓,获取最后一笔订单的盈亏 double lastProfit = GetLastClosedProfit(magic); UpdateStats(magic, lastProfit); } HadPosition[index] = hasPos;
// 2. 如果已有持仓,跳过(每个周期同时只持一单) if(hasPos) return;
// 3. 检查布林带数据是否就绪 if(BarsCalculated(handle) < InpBBPeriod + 10) return;
// 4. 复制布林带中轨数据(最近 3 根 K 线足够判断穿越) double middle[3]; if(CopyBuffer(handle, 0, 0, 3, middle) < 3) return;
// 5. 获取收盘价(最近 3 根) double close[3]; if(CopyClose(_Symbol, tf, 0, 3, close) < 3) return;
// 6. 检查穿越信号([1] 是前一根,[2] 是前两根,[0] 是当前未完成 K 线) // 为避免当前 K 线频繁触发,用 [1] 和 [2] 判断 bool signalBuy = (close[2] < middle[2] && close[1] > middle[1]); // 上穿中轨 bool signalSell = (close[2] > middle[2] && close[1] < middle[1]); // 下穿中轨
// 7. 如果有信号,计算手数并开仓 if(signalBuy || signalSell) { double lotSize = GetCurrentLotSize(magic); double sl = 0, tp = 0; double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(signalBuy) { if(InpStopLoss > 0) sl = ask - InpStopLoss * point; if(InpTakeProfit > 0) tp = ask + InpTakeProfit * point;
trade.SetExpertMagicNumber(magic); if(trade.Buy(lotSize, _Symbol, ask, sl, tp, InpComment + "_" + EnumToString(tf))) { Print("【", EnumToString(tf), "】做多开仓成功,手数:", lotSize, ",价格:", ask); } else { Print("【", EnumToString(tf), "】做多开仓失败,错误码:", GetLastError()); } } else if(signalSell) { if(InpStopLoss > 0) sl = bid + InpStopLoss * point; if(InpTakeProfit > 0) tp = bid - InpTakeProfit * point;
trade.SetExpertMagicNumber(magic); if(trade.Sell(lotSize, _Symbol, bid, sl, tp, InpComment + "_" + EnumToString(tf))) { Print("【", EnumToString(tf), "】做空开仓成功,手数:", lotSize, ",价格:", bid); } else { Print("【", EnumToString(tf), "】做空开仓失败,错误码:", GetLastError()); } } } }
//+------------------------------------------------------------------+ //| 检查是否有指定 magic 的持仓 | //+------------------------------------------------------------------+ bool HasPosition(int magic) { for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(ticket > 0) { if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == magic) { return true; } } } return false; }
//+------------------------------------------------------------------+ //| 获取最后一笔平仓订单的盈亏(指定 magic) | //+------------------------------------------------------------------+ double GetLastClosedProfit(int magic) { // 选择最近 10 天的历史 datetime from = TimeCurrent() - 10 * 86400; if(!HistorySelect(from, TimeCurrent())) return 0.0;
// 从最新的订单往回找 int total = HistoryDealsTotal(); for(int i = total - 1; i >= 0; i--) { ulong ticket = HistoryDealGetTicket(i); if(ticket > 0) { if(HistoryDealGetString(ticket, DEAL_SYMBOL) == _Symbol && HistoryDealGetInteger(ticket, DEAL_MAGIC) == magic && HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT) { return HistoryDealGetDouble(ticket, DEAL_PROFIT); } } }
return 0.0; }
//+------------------------------------------------------------------+ //| 更新该周期的盈亏统计 | //+------------------------------------------------------------------+ void UpdateStats(int magic, double profit) { string prefixProfit = "BOLL_EA_Profit_" + IntegerToString(magic); string prefixLoss = "BOLL_EA_Loss_" + IntegerToString(magic); string prefixLots = "BOLL_EA_Lots_" + IntegerToString(magic);
double totalProfit = GlobalVariableGet(prefixProfit); double totalLoss = GlobalVariableGet(prefixLoss); double currentLots = GlobalVariableGet(prefixLots);
if(profit > 0) { // 盈利 totalProfit += profit; GlobalVariableSet(prefixProfit, totalProfit);
// 检查是否盈利覆盖亏损 if(totalProfit >= MathAbs(totalLoss)) { // 重置手数和盈亏统计 GlobalVariableSet(prefixLots, InpBaseLots); GlobalVariableSet(prefixProfit, 0.0); GlobalVariableSet(prefixLoss, 0.0); Print("【Magic ", magic, "】盈利覆盖亏损,手数重置为 ", InpBaseLots); } } else if(profit < 0) { // 亏损 totalLoss += profit; // profit 本身是负数 GlobalVariableSet(prefixLoss, totalLoss);
// 下次开仓手数增加 double newLots = currentLots + InpLotStep;
// 检查最大手数限制 double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); if(newLots > maxLot) newLots = maxLot;
GlobalVariableSet(prefixLots, newLots); Print("【Magic ", magic, "】止损 ", MathAbs(profit), ",下次手数增加至 ", newLots); } }
//+------------------------------------------------------------------+ //| 获取当前应开仓的手数 | //+------------------------------------------------------------------+ double GetCurrentLotSize(int magic) { string prefixLots = "BOLL_EA_Lots_" + IntegerToString(magic); double lots = GlobalVariableGet(prefixLots);
// 检查最小和最大手数 double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(lots < minLot) lots = minLot; if(lots > maxLot) lots = maxLot;
// 手数标准化(符合步长) lots = MathFloor(lots / stepLot) * stepLot;
return lots; } ```
使用说明
安装步骤
- 将代码保存为
BOLL_MultiPeriod_Martingale_EA.mq5,放入 MT5 的MQL5/Experts/目录 - 在 MetaEditor 中编译(F7)
- 在 MT5 图表上右键 → "EA交易" → "BOLL_MultiPeriod_Martingale_EA",拖到任意品种图表(建议 XAUUSD)
- 允许自动交易,允许 DLL 导入(如果提示)
参数调节
- 布林带周期/标准差:默认 20 周期、2 倍标准差,这是经典参数;震荡市场可缩小标准差到 1.5,趋势市场可放大到 2.5
- 止损/止盈点数:XAUUSD 默认 100 点止损 = 10 美元,200 点止盈 = 20 美元;根据品种波动率调整,外汇可用 30-50 点
- 基础手数:0.01 手适合小账户;1000 美元以上可用 0.02-0.05
- 加仓步长:默认每次止损后加 0.01 手;激进可设 0.02,保守可设 0.005
- 魔术数字基数:默认 10000,不要和其他 EA 冲突
信号逻辑
- 做多:K线从布林带中轨下方穿到上方(前一根收盘价 < 中轨,当前根收盘价 > 中轨)
- 做空:K线从布林带中轨上方穿到下方
- 每个周期(M5/M15/M30/H1/H4)独立判断,同一周期同时只持一单
- 用前一根 K 线确认穿越,避免当前未完成 K 线频繁触发
风险管理
- 初始 0.01 手开仓
- 该周期订单止损后,下次自动加 0.01 手(马丁格尔)
- 连续止损会持续累加手数(最大不超过品种限制)
- 当该周期累计盈利 ≥ 累计亏损(绝对值)时,手数自动重置回 0.01
注意事项
- 5 个周期独立:M5 止损不影响 H4 的手数,各自维护自己的盈亏账本
- 马丁风险:连续止损会导致手数快速增长,务必控制初始手数和账户余额比例,建议每个周期的最大手数不超过账户的 2%
- 点差成本:布林带中轨穿越信号较频繁,尤其 M5 周期,注意点差和手续费
- 回撤控制:如果某个周期连续 5 次以上止损,建议手动暂停该周期或调整参数
- 品种选择:适合趋势+震荡混合的品种(黄金、欧美等),纯震荡品种(如区间盘整的股指)效果更好
- 魔术数字:每个周期的 magic = 10000 + 周期分钟数(10005/10015/10030/10060/10240),日志中可看到
- 全局变量:盈亏和手数存储在 MT5 全局变量中,重启终端后会保留;如需清零,工具 → 全局变量 → 手动删除
优化建议
- 可添加 ATR 动态止损代替固定点数
- 可加入趋势过好的,思路确认一下:这是一个多周期独立运行的布林中轨穿越马丁 EA。我在图上/逻辑上识别到的规则是:
- 5 个周期(M5/M15/M30/H1/H4)各自独立判断信号、独立持仓、独立统计盈亏,用不同的魔术号隔离,互不干扰
- 收盘价上穿布林中轨做多,下穿中轨做空(你写的“轨道线”我按中轨理解,若是指上/下轨请告诉我)
- 每周期初始 0.01 手;某周期止损后,下一次开仓加 0.01 手;连续止损持续累加
- 当该周期累计盈利 ≥ 累计止损时,手数复位为 0.01 手
- 信号用已收盘 K 线判断(shift 1 对比 shift 2),避免盘中反复触发
使用说明
安装
- 打开 MT5 → 工具 → MetaQuotes 语言编辑器(F4)→ 新建 → 专家顾问,把上面代码粘贴进去
- 文件名保存为
BollMidCross_MultiTF_Martin_EA.mq5,点“编译”,确认 0 错误 - 回到 MT5,从导航器把 EA 拖到 XAUUSD 图表上(任意周期都行,EA 内部自己取 5 个周期的数据),勾选“允许算法交易”
参数怎么调
止损/止盈点数:注意你的报价精度。XAUUSD 若是 2 位小数(如 2650.55),1 点 = 0.01 美元,600 点 = 6.00 美元;若是 3 位小数报价则数值含义不同,请先在图上量一下再设最大手数保护:强烈建议保留。马丁策略连续止损时手数会快速膨胀,这个参数是安全阀魔术号基数:如果同账户还跑其他 EA,确保魔术号不重叠
信号怎么看
- 每个 K 线收盘后判断一次:收盘价从下往上穿过布林中轨 → 该周期开多;从上往下穿过 → 开空
- 图表左上角实时显示 5 个周期各自的手数、累计止损、累计盈利、持仓状态
- 某周期止损一次 → 下次该周期开仓 +0.01 手;累计盈利 ≥ 累计止损 → 该周期手数复位 0.01
注意事项
- 必须是对冲(Hedging)账户。净持仓(Netting)账户上同品种多单空单会互相抵消,5 个周期无法独立持仓。账户类型在 MT5 右下角或开户券商处确认
- 你说的“下穿 BOLL 轨道线做空”我按中轨实现(与做多对称)。如果你指的是下穿上轨/下轨,告诉我改一下判断条件即可
- 马丁加仓本质是亏损后放大风险,请先用小资金或策略测试器(每笔 tick 模式)回测验证
- EA 重启后会自动扫描历史成交恢复各周期的手数和盈亏统计,但更换魔术号基数后统计会清零
- 信号基于已收盘 K 线,不会盘中反复开平仓;每个周期同一时间最多 1 单
AI 解读根据源码逐行分析写成,供参考,以实际运行为准
这是什么
这是一个基于布林带中轨穿越的 MT5 自动交易 EA,同时在 M5 / M15 / M30 / H1 / H4 五个周期上独立运行。每个周期各管各的单子,止损了就加仓,盈利覆盖过去的亏损后手数自动归零重来。适合想用布林带抓震荡反转、又想分散周期风险的交易者。
逻辑原理
信号:EA 只看布林带中轨(20 周期均线)和收盘价的位置关系。当 K 线收盘价从下方上穿中轨时做多,从上方下穿中轨时做空。代码里用前后两根 K 线对比:前一根在上、当前根在下(或反过来)就触发信号。
多周期独立:五个周期各自创建一个布林带指标句柄,各自判断信号,各自开平仓,互不干扰。每个周期用不同的魔术号(基数 + 0~4)区分,同一周期最多同时持有 1 单。
马丁加仓:每个周期内部记三个数:当前该用多少手、累计止损了多少钱、累计盈利了多少钱。第一次开仓用初始手数(默认 0.01),止损后下次开仓手数 +0.01,止盈不改手数。当累计盈利 ≥ 累计止损时,三个数全部清零,下次又从 0.01 开始。这样做的目的是让后面盈利的单子把前面止损的亏损补回来。
止损止盈:开仓时按点数设固定止损和止盈。默认止损 600 点(XAUUSD 两位小数报价下是 6 美元),止盈 1200 点(12 美元)。代码里按 ASK 或 BID 加减点数算出价格,订单带 SL/TP 提交。
状态恢复:EA 重启后会从历史成交记录里按时间顺序重放每笔平仓,把每个周期的手数和盈亏统计重新算一遍,接着上次的状态继续跑。
参数说明
- InpBaseMagic(默认 20250001):魔术号基数,五个周期自动用基数 +0、+1、+2、+3、+4,不用手动改,只要和账户里其他 EA 不冲突就行。
- InpBaseLot(默认 0.01):初始开仓手数,止损复位后重新用这个手数开仓。小资金建议保持 0.01。
- InpLotStep(默认 0.01):每次止损后加仓的手数。设成 0.01 意味着第一次 0.01,止损后 0.02,再止损 0.03……加得越快风险越大。
- InpMaxLot(默认 0.10):单周期最大手数保护,防止马丁失控。连续止损到这个手数后就不再往上加,只能等盈利覆盖后复位。小账户建议 0.05~0.10。
- InpStopLossPts(默认 600):止损点数。XAUUSD 报价格式下 600 点 = 6 美元价差。建议根据品种波动率调整,推荐 400~800。
- InpTakeProfitPts(默认 1200):止盈点数,默认是止损的 2 倍。可以调成 1.5~2.5 倍都行,太小容易被震出来,太大不容易打到。
- InpBollPeriod(默认 20):布林带均线周期,标准设置,一般不用改。
- InpBollDev(默认 2):布林带标准差倍数,标准设置。虽然 EA 只用中轨做信号,但指标仍按标准布林参数计算。
怎么用
把 EA 拖到任意一张图上就行,不用开五张图。代码里写死了 M5/M15/M30/H1/H4 五个周期,EA 会自己去各周期取数据。建议挂在 H1 或 H4 图上方便监控。左上角会显示每个周期当前手数、累计止损、累计盈利和持仓状态。
开仓信号是 K 线收盘后才判断,不会盘中反复触发。每个周期同时最多 1 单,止损或止盈后才会再开下一单。订单备注格式是"BollMid-PERIOD_M5"这样,方便在订单列表里区分周期。
适用场景
适合:
- XAUUSD、EURUSD 等相对规律的品种,布林带中轨对价格有一定磁吸作用的行情。
- 震荡市和趋势转折初期:价格反复穿越中轨时容易触发信号。
- 多周期分散:单一周期可能连续止损,但五个周期不太可能同时止损,有一定对冲效果。
不适合:
- 单边趋势行情:价格离开中轨后很久不回来,逆势开仓会连续止损,马丁手数越滚越大。
- 高波动时段(非农、议息):止损距离可能不够,或信号频繁触发消耗手续费。
- 小资金账户跑太多周期:五个周期同时持仓时保证金占用较大,连续加仓后更明显。
周期建议:M5 和 M15 信号频繁但噪音多,H1 和 H4 信号少但可靠性相对高。可以根据自己风格调整代码里的周期数组,比如只跑 M30/H1/H4 三个。
风险提示
马丁格尔风险:这是最大的坑。连续止损 5 次后手数从 0.01 涨到 0.06,10 次就到 0.11(已被 InpMaxLot 限制住)。如果五个周期同时进入马丁状态,账户压力很大。强烈建议小手数起步、设好 InpMaxLot 上限、只用闲置资金测试。
信号滞后:布林带中轨本质是均线,价格穿越时趋势可能已经走了一段。行情转得快的时候容易追高杀跌。
震荡行情双刃剑:价格在中轨附近反复穿越时信号频繁,盈利机会多但也容易连续假突破。止损距离设小了容易被扫,设大了单次亏损重。
无整体风控:代码里只有单周期手数上限(InpMaxLot),没有账户总回撤保护或每日亏损上限。五个周期各管各的,可能同时马丁到顶,需要人工监控。
不会重绘:布林带指标本身不重绘,K 线收盘后中轨值就固定了,信号不会消失。但历史回测时要注意,代码用的是 shift 1 和 shift 2(已收盘的 K 线),不会用当前未收盘的 K 线,所以回测信号和实盘一致。
手续费和滑点:多周期意味着开仓次数多,每单都有成本。代码里设了 30 点滑点容差,实际成交价可能比信号价差一点。做短周期(M5/M15)时尤其要算好手续费会不会吃掉大部分利润。
常见问题
- 这个 EA 会重绘吗?信号会消失吗?
- 不会。代码用的是已收盘 K 线(shift 1 和 2)判断穿越,收盘后布林带中轨值就固定了,信号不会改变。历史回测和实盘逻辑一致。
- 可以只跑其中几个周期吗?比如只要 H1 和 H4
- 可以,但需要改代码。找到 OnInit 里的 `ENUM_TIMEFRAMES tfs[TF_COUNT] = {PERIOD_M5, ...}` 这一行,把不想要的周期删掉,同时把开头的 `#define TF_COUNT 5` 改成实际周期数量(比如改成 2)。
- 参数默认值适合什么周期和品种?
- 默认参数按 XAUUSD(黄金)调的,止损 600 点约等于 6 美元,适合 H1 和 H4。如果跑 M5/M15 或者做 EURUSD 这种波动小的品种,止损止盈点数要相应调小,否则永远打不到止盈。
- 连续止损多少次会到最大手数?
- 默认初始 0.01、每次加 0.01、上限 0.10,理论上止损 9 次后下一单就到 0.10 并被限制住。如果五个周期同时进入马丁状态,总手数可能到 0.5,小账户扛不住。
- MT4 能用吗?
- 不能直接用。这是 MQL5 代码,用了 MT5 的 CTrade 类和 iBands 函数。MT4 的语法和订单管理逻辑不一样,需要重写。
Bollinger Middle Band Cross Multi-Timeframe Martingale EA for MT5
An MT5 EA that trades Bollinger Middle Band crossovers independently across 5 timeframes (M5, M15, M30, H1, H4). It increases lot size after stop-loss and resets when profit covers accumulated losses. Suitable for range-bound and trend reversal markets.
Source code is shown above. Compiled EX5 files are available to VIP members.
评论
还没有人评论,来抢个沙发。