MT5EA / 策略编译通过 · EX5 35KB 自动发布 1 0

布林中轨穿越多周期马丁EA

litongxue·2 小时前发布

五个周期各自独立做布林中轨穿越,止损后逐次加仓0.01手,盈利覆盖亏损后复位

BollMidCross_MultiTF_Martin_EA.mq5302 行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
#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; } ```

使用说明

安装步骤

  1. 将代码保存为 BOLL_MultiPeriod_Martingale_EA.mq5,放入 MT5 的 MQL5/Experts/ 目录
  2. 在 MetaEditor 中编译(F7)
  3. 在 MT5 图表上右键 → "EA交易" → "BOLL_MultiPeriod_Martingale_EA",拖到任意品种图表(建议 XAUUSD)
  4. 允许自动交易,允许 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

注意事项

  1. 5 个周期独立:M5 止损不影响 H4 的手数,各自维护自己的盈亏账本
  2. 马丁风险:连续止损会导致手数快速增长,务必控制初始手数和账户余额比例,建议每个周期的最大手数不超过账户的 2%
  3. 点差成本:布林带中轨穿越信号较频繁,尤其 M5 周期,注意点差和手续费
  4. 回撤控制:如果某个周期连续 5 次以上止损,建议手动暂停该周期或调整参数
  5. 品种选择:适合趋势+震荡混合的品种(黄金、欧美等),纯震荡品种(如区间盘整的股指)效果更好
  6. 魔术数字:每个周期的 magic = 10000 + 周期分钟数(10005/10015/10030/10060/10240),日志中可看到
  7. 全局变量:盈亏和手数存储在 MT5 全局变量中,重启终端后会保留;如需清零,工具 → 全局变量 → 手动删除

优化建议

  • 可添加 ATR 动态止损代替固定点数
  • 可加入趋势过好的,思路确认一下:这是一个多周期独立运行的布林中轨穿越马丁 EA。我在图上/逻辑上识别到的规则是:
  • 5 个周期(M5/M15/M30/H1/H4)各自独立判断信号、独立持仓、独立统计盈亏,用不同的魔术号隔离,互不干扰
  • 收盘价上穿布林中轨做多,下穿中轨做空(你写的“轨道线”我按中轨理解,若是指上/下轨请告诉我)
  • 每周期初始 0.01 手;某周期止损后,下一次开仓加 0.01 手;连续止损持续累加
  • 当该周期累计盈利 ≥ 累计止损时,手数复位为 0.01 手
  • 信号用已收盘 K 线判断(shift 1 对比 shift 2),避免盘中反复触发

使用说明

安装

  1. 打开 MT5 → 工具 → MetaQuotes 语言编辑器(F4)→ 新建 → 专家顾问,把上面代码粘贴进去
  2. 文件名保存为 BollMidCross_MultiTF_Martin_EA.mq5,点“编译”,确认 0 错误
  3. 回到 MT5,从导航器把 EA 拖到 XAUUSD 图表上(任意周期都行,EA 内部自己取 5 个周期的数据),勾选“允许算法交易”

参数怎么调

  • 止损/止盈点数:注意你的报价精度。XAUUSD 若是 2 位小数(如 2650.55),1 点 = 0.01 美元,600 点 = 6.00 美元;若是 3 位小数报价则数值含义不同,请先在图上量一下再设
  • 最大手数保护:强烈建议保留。马丁策略连续止损时手数会快速膨胀,这个参数是安全阀
  • 魔术号基数:如果同账户还跑其他 EA,确保魔术号不重叠

信号怎么看

  • 每个 K 线收盘后判断一次:收盘价从下往上穿过布林中轨 → 该周期开多;从上往下穿过 → 开空
  • 图表左上角实时显示 5 个周期各自的手数、累计止损、累计盈利、持仓状态
  • 某周期止损一次 → 下次该周期开仓 +0.01 手;累计盈利 ≥ 累计止损 → 该周期手数复位 0.01

注意事项

  1. 必须是对冲(Hedging)账户。净持仓(Netting)账户上同品种多单空单会互相抵消,5 个周期无法独立持仓。账户类型在 MT5 右下角或开户券商处确认
  2. 你说的“下穿 BOLL 轨道线做空”我按中轨实现(与做多对称)。如果你指的是下穿上轨/下轨,告诉我改一下判断条件即可
  3. 马丁加仓本质是亏损后放大风险,请先用小资金或策略测试器(每笔 tick 模式)回测验证
  4. EA 重启后会自动扫描历史成交恢复各周期的手数和盈亏统计,但更换魔术号基数后统计会清零
  5. 信号基于已收盘 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.

评论

后可以留言,比如反馈用着怎么样、想加什么功能。

还没有人评论,来抢个沙发。