MT5EA / 策略编译通过 · EX5 46KB 36 0

三均线马丁EA(逻辑完全修正版)

litongxue·9 天前发布

移动止损保留马丁,固定止损倍增,马丁止盈重置后追单

MA_Martin_MultiTF_Fixed_V9.mq5655 行
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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
#property copyright "XAUOF指标工坊"
#property version   "9.00"
#property strict

#include <Trade\Trade.mqh>

//--- 输入参数

input int    MA10_Period = 10;
input int    MA15_Period = 15;
input int    MA20_Period = 20;
input double BaseLots = 0.01;
input double MaxLots = 1.0;
input int    StopLoss = 400;
input int    MADistanceMin = 400;
input int    TrailingTrigger = 500;
input int    TrailingStop = 200;
input int    CooldownSeconds = 5;
input int    MinHoldSeconds = 30;
input int    MartinMaxLoss = 4;
input int    MagicBase = 20250101;

enum POSITION_STATE
{
   STATE_NO_POS = 0,
   STATE_BUY_OPEN = 1,
   STATE_SELL_OPEN = 2,
   STATE_CLOSE_COOLDOWN = 3,
   STATE_MARTIN_FOLLOW_PENDING = 4
};

struct TimeframeData
{
   ENUM_TIMEFRAMES period;
   int magic;
   int handleMA10, handleMA15, handleMA20;
   double bufMA10[3], bufMA15[3], bufMA20[3];
   
   double currentLots;
   double entryPrice;
   datetime entryTime;
   datetime lastCloseTime;
   datetime lastSignalBarTime;
   double lastStopLoss;
   bool hasModifiedSL;
   POSITION_STATE state;
   int positionType;
   double floatingProfit;
   double floatingProfitUSD;
   
   int martinFixedStopLossCount;
   double martinAccumulatedLossUSD;
   bool usedTrailingStopThisRound;
   
   int followDirection;
};

CTrade trade;
TimeframeData tfData[3];
string panelName = "MartinPanel";

//+------------------------------------------------------------------+

int OnInit()
{
   ENUM_TIMEFRAMES periods[3] = {PERIOD_M5, PERIOD_M15, PERIOD_M30};
   
   for(int i = 0; i < 3; i++)
   {
      tfData[i].period = periods[i];
      tfData[i].magic = MagicBase + PeriodSeconds(periods[i]) / 60;
      tfData[i].currentLots = BaseLots;
      tfData[i].entryPrice = 0;
      tfData[i].entryTime = 0;
      tfData[i].lastCloseTime = 0;
      tfData[i].lastSignalBarTime = 0;
      tfData[i].lastStopLoss = 0;
      tfData[i].hasModifiedSL = false;
      tfData[i].state = STATE_NO_POS;
      tfData[i].positionType = -1;
      tfData[i].floatingProfit = 0;
      tfData[i].floatingProfitUSD = 0;
      
      tfData[i].martinFixedStopLossCount = 0;
      tfData[i].martinAccumulatedLossUSD = 0;
      tfData[i].usedTrailingStopThisRound = false;
      tfData[i].followDirection = -1;
      
      tfData[i].handleMA10 = iMA(_Symbol, periods[i], MA10_Period, 0, MODE_EMA, PRICE_CLOSE);
      tfData[i].handleMA15 = iMA(_Symbol, periods[i], MA15_Period, 0, MODE_EMA, PRICE_CLOSE);
      tfData[i].handleMA20 = iMA(_Symbol, periods[i], MA20_Period, 0, MODE_EMA, PRICE_CLOSE);
      
      if(tfData[i].handleMA10 == INVALID_HANDLE || 
         tfData[i].handleMA15 == INVALID_HANDLE || 
         tfData[i].handleMA20 == INVALID_HANDLE)
         return INIT_FAILED;
   }
   
   CreatePanel();
   EventSetTimer(1);
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+

void OnDeinit(const int reason)
{
   for(int i = 0; i < 3; i++)
   {
      IndicatorRelease(tfData[i].handleMA10);
      IndicatorRelease(tfData[i].handleMA15);
      IndicatorRelease(tfData[i].handleMA20);
   }
   ObjectDelete(0, panelName);
   EventKillTimer();
}

//+------------------------------------------------------------------+

void OnTick()
{
   for(int i = 0; i < 3; i++)
   {
      ProcessTimeframe(i);
   }
}

//+------------------------------------------------------------------+

void OnTimer()
{
   UpdatePanel();
}

//+------------------------------------------------------------------+

void ProcessTimeframe(int idx)
{
   if(BarsCalculated(tfData[idx].handleMA10) < 3 || 
      BarsCalculated(tfData[idx].handleMA15) < 3 || 
      BarsCalculated(tfData[idx].handleMA20) < 3)
      return;
   
   if(CopyBuffer(tfData[idx].handleMA10, 0, 0, 3, tfData[idx].bufMA10) < 3) return;
   if(CopyBuffer(tfData[idx].handleMA15, 0, 0, 3, tfData[idx].bufMA15) < 3) return;
   if(CopyBuffer(tfData[idx].handleMA20, 0, 0, 3, tfData[idx].bufMA20) < 3) return;
   
   ArraySetAsSeries(tfData[idx].bufMA10, true);
   ArraySetAsSeries(tfData[idx].bufMA15, true);
   ArraySetAsSeries(tfData[idx].bufMA20, true);
   
   datetime time[];
   double close[];
   ArraySetAsSeries(time, true);
   ArraySetAsSeries(close, true);
   
   if(CopyTime(_Symbol, tfData[idx].period, 0, 3, time) < 3) return;
   if(CopyClose(_Symbol, tfData[idx].period, 0, 3, close) < 3) return;
   
   int actualPosType = GetPositionType(tfData[idx].magic);
   
   // 【漏洞修正】检测持仓被平仓

   if(tfData[idx].positionType != -1 && actualPosType == -1)
   {
      OnPositionClosed(idx);
      // 平仓后立即检查是否需要追单

      if(tfData[idx].state == STATE_MARTIN_FOLLOW_PENDING)
      {
         if(tfData[idx].followDirection == 0)
         {
            OpenFollowBuy(idx);
         }
         else if(tfData[idx].followDirection == 1)
         {
            OpenFollowSell(idx);
         }
         tfData[idx].state = STATE_NO_POS;
         tfData[idx].followDirection = -1;
      }
      return;
   }
   
   tfData[idx].positionType = actualPosType;
   
   // 普通冷却

   if(tfData[idx].state == STATE_CLOSE_COOLDOWN)
   {
      if(TimeCurrent() - tfData[idx].lastCloseTime >= CooldownSeconds)
      {
         tfData[idx].state = STATE_NO_POS;
         tfData[idx].lastSignalBarTime = 0;
      }
      else
      {
         return;
      }
   }
   
   // 管理持仓

   if(actualPosType == POSITION_TYPE_BUY)
   {
      ManageBuyPosition(idx, close[1]);
   }
   else if(actualPosType == POSITION_TYPE_SELL)
   {
      ManageSellPosition(idx, close[1]);
   }
   
   // 无持仓时检查开仓信号

   if(actualPosType == -1 && tfData[idx].state == STATE_NO_POS)
   {
      if(time[1] == tfData[idx].lastSignalBarTime)
         return;
      
      if(IsBullishCrossover(idx, close[1]))
      {
         OpenBuy(idx);
         tfData[idx].lastSignalBarTime = time[1];
         return;
      }
      
      if(IsBearishCrossover(idx, close[1]))
      {
         OpenSell(idx);
         tfData[idx].lastSignalBarTime = time[1];
         return;
      }
   }
}

//+------------------------------------------------------------------+

bool IsBullishCrossover(int idx, double lastClosePrice)
{
   if(!(tfData[idx].bufMA10[1] > tfData[idx].bufMA20[1] && 
        tfData[idx].bufMA10[2] <= tfData[idx].bufMA20[2]))
      return false;
   
   double maDistance = MathAbs(tfData[idx].bufMA20[1] - tfData[idx].bufMA10[1]) / _Point;
   if(maDistance < MADistanceMin)
      return false;
   
   if(lastClosePrice <= tfData[idx].bufMA10[1])
      return false;
   
   return true;
}

//+------------------------------------------------------------------+

bool IsBearishCrossover(int idx, double lastClosePrice)
{
   if(!(tfData[idx].bufMA10[1] < tfData[idx].bufMA20[1] && 
        tfData[idx].bufMA10[2] >= tfData[idx].bufMA20[2]))
      return false;
   
   double maDistance = MathAbs(tfData[idx].bufMA10[1] - tfData[idx].bufMA20[1]) / _Point;
   if(maDistance < MADistanceMin)
      return false;
   
   if(lastClosePrice >= tfData[idx].bufMA10[1])
      return false;
   
   return true;
}

//+------------------------------------------------------------------+

void OpenBuy(int idx)
{
   trade.SetExpertMagicNumber(tfData[idx].magic);
   
   double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double sl = price - StopLoss * _Point;
   
   double lots = tfData[idx].currentLots;
   if(lots > MaxLots) lots = MaxLots;
   
   if(trade.Buy(lots, _Symbol, price, sl, 0))
   {
      tfData[idx].entryPrice = price;
      tfData[idx].entryTime = TimeCurrent();
      tfData[idx].lastStopLoss = sl;
      tfData[idx].hasModifiedSL = false;
      tfData[idx].state = STATE_BUY_OPEN;
      tfData[idx].positionType = POSITION_TYPE_BUY;
      tfData[idx].usedTrailingStopThisRound = false;
      
      Print(EnumToString(tfData[idx].period), 
            " 【开多】手数=", DoubleToString(lots, 2),
            " 固定止损次数=", tfData[idx].martinFixedStopLossCount,
            " 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2));
   }
}

//+------------------------------------------------------------------+

void OpenSell(int idx)
{
   trade.SetExpertMagicNumber(tfData[idx].magic);
   
   double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double sl = price + StopLoss * _Point;
   
   double lots = tfData[idx].currentLots;
   if(lots > MaxLots) lots = MaxLots;
   
   if(trade.Sell(lots, _Symbol, price, sl, 0))
   {
      tfData[idx].entryPrice = price;
      tfData[idx].entryTime = TimeCurrent();
      tfData[idx].lastStopLoss = sl;
      tfData[idx].hasModifiedSL = false;
      tfData[idx].state = STATE_SELL_OPEN;
      tfData[idx].positionType = POSITION_TYPE_SELL;
      tfData[idx].usedTrailingStopThisRound = false;
      
      Print(EnumToString(tfData[idx].period), 
            " 【开空】手数=", DoubleToString(lots, 2),
            " 固定止损次数=", tfData[idx].martinFixedStopLossCount,
            " 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2));
   }
}

//+------------------------------------------------------------------+

void OpenFollowBuy(int idx)
{
   trade.SetExpertMagicNumber(tfData[idx].magic);
   
   double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double sl = price - StopLoss * _Point;
   
   if(trade.Buy(BaseLots, _Symbol, price, sl, 0))
   {
      tfData[idx].entryPrice = price;
      tfData[idx].entryTime = TimeCurrent();
      tfData[idx].lastStopLoss = sl;
      tfData[idx].hasModifiedSL = false;
      tfData[idx].state = STATE_BUY_OPEN;
      tfData[idx].positionType = POSITION_TYPE_BUY;
      tfData[idx].usedTrailingStopThisRound = false;
      
      Print(EnumToString(tfData[idx].period), 
            " 【马丁止盈后追单-多】0.01手 马丁已重置");
   }
}

//+------------------------------------------------------------------+

void OpenFollowSell(int idx)
{
   trade.SetExpertMagicNumber(tfData[idx].magic);
   
   double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double sl = price + StopLoss * _Point;
   
   if(trade.Sell(BaseLots, _Symbol, price, sl, 0))
   {
      tfData[idx].entryPrice = price;
      tfData[idx].entryTime = TimeCurrent();
      tfData[idx].lastStopLoss = sl;
      tfData[idx].hasModifiedSL = false;
      tfData[idx].state = STATE_SELL_OPEN;
      tfData[idx].positionType = POSITION_TYPE_SELL;
      tfData[idx].usedTrailingStopThisRound = false;
      
      Print(EnumToString(tfData[idx].period), 
            " 【马丁止盈后追单-空】0.01手 马丁已重置");
   }
}

//+------------------------------------------------------------------+

void ManageBuyPosition(int idx, double lastClosePrice)
{
   if(!PositionSelect(_Symbol)) return;
   if(PositionGetInteger(POSITION_MAGIC) != tfData[idx].magic) return;
   
   trade.SetExpertMagicNumber(tfData[idx].magic);
   
   double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   double currentSL = PositionGetDouble(POSITION_SL);
   double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double profitUSD = PositionGetDouble(POSITION_PROFIT);
   double profitPoints = (currentPrice - openPrice) / _Point;
   
   tfData[idx].floatingProfitUSD = profitUSD;
   tfData[idx].floatingProfit = profitPoints;
   tfData[idx].lastStopLoss = currentSL;
   
   // ===== 【优先级1】马丁止盈 =====

   if(tfData[idx].martinFixedStopLossCount >= MartinMaxLoss)
   {
      if(profitUSD >= tfData[idx].martinAccumulatedLossUSD)
      {
         if(trade.PositionClose(_Symbol))
         {
            Print(EnumToString(tfData[idx].period), 
                  " 【马丁止盈】浮盈=$", DoubleToString(profitUSD, 2),
                  " >= 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2),
                  " 平仓重置");
            
            // 重置马丁

            tfData[idx].martinFixedStopLossCount = 0;
            tfData[idx].martinAccumulatedLossUSD = 0;
            tfData[idx].currentLots = BaseLots;
            tfData[idx].state = STATE_MARTIN_FOLLOW_PENDING;
            tfData[idx].followDirection = 0;
            tfData[idx].lastCloseTime = TimeCurrent();
         }
         return;
      }
   }
   
   // ===== 【优先级2】MA15止盈 =====

   int holdSeconds = (int)(TimeCurrent() - tfData[idx].entryTime);
   if(holdSeconds >= MinHoldSeconds && lastClosePrice <= tfData[idx].bufMA15[1])
   {
      // 【漏洞修正】平仓前检查

      if(trade.PositionClose(_Symbol))
      {
         Print(EnumToString(tfData[idx].period), 
               " 【MA15止盈】平仓");
         
         tfData[idx].martinFixedStopLossCount = 0;
         tfData[idx].martinAccumulatedLossUSD = 0;
         tfData[idx].currentLots = BaseLots;
         tfData[idx].state = STATE_CLOSE_COOLDOWN;
         tfData[idx].lastCloseTime = TimeCurrent();
      }
      return;
   }
   
   // ===== 【优先级3】移动止损 =====

   if(!tfData[idx].hasModifiedSL && profitPoints >= TrailingTrigger)
   {
      double newSL = openPrice + TrailingStop * _Point;
      if(newSL > currentSL)
      {
         // 【漏洞修正】先标记,再尝试修改

         tfData[idx].hasModifiedSL = true;
         tfData[idx].usedTrailingStopThisRound = true;
         
         if(!trade.PositionModify(_Symbol, newSL, 0))
         {
            Print(EnumToString(tfData[idx].period), " 移动止损修改失败");
         }
         else
         {
            Print(EnumToString(tfData[idx].period), 
                  " 【移动止损】浮盈=", (int)profitPoints, "点");
         }
      }
   }
}

//+------------------------------------------------------------------+

void ManageSellPosition(int idx, double lastClosePrice)
{
   if(!PositionSelect(_Symbol)) return;
   if(PositionGetInteger(POSITION_MAGIC) != tfData[idx].magic) return;
   
   trade.SetExpertMagicNumber(tfData[idx].magic);
   
   double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   double currentSL = PositionGetDouble(POSITION_SL);
   double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double profitUSD = PositionGetDouble(POSITION_PROFIT);
   double profitPoints = (openPrice - currentPrice) / _Point;
   
   tfData[idx].floatingProfitUSD = profitUSD;
   tfData[idx].floatingProfit = profitPoints;
   tfData[idx].lastStopLoss = currentSL;
   
   if(tfData[idx].martinFixedStopLossCount >= MartinMaxLoss)
   {
      if(profitUSD >= tfData[idx].martinAccumulatedLossUSD)
      {
         if(trade.PositionClose(_Symbol))
         {
            Print(EnumToString(tfData[idx].period), 
                  " 【马丁止盈】浮盈=$", DoubleToString(profitUSD, 2),
                  " >= 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2),
                  " 平仓重置");
            
            tfData[idx].martinFixedStopLossCount = 0;
            tfData[idx].martinAccumulatedLossUSD = 0;
            tfData[idx].currentLots = BaseLots;
            tfData[idx].state = STATE_MARTIN_FOLLOW_PENDING;
            tfData[idx].followDirection = 1;
            tfData[idx].lastCloseTime = TimeCurrent();
         }
         return;
      }
   }
   
   int holdSeconds = (int)(TimeCurrent() - tfData[idx].entryTime);
   if(holdSeconds >= MinHoldSeconds && lastClosePrice >= tfData[idx].bufMA15[1])
   {
      if(trade.PositionClose(_Symbol))
      {
         Print(EnumToString(tfData[idx].period), 
               " 【MA15止盈】平仓");
         
         tfData[idx].martinFixedStopLossCount = 0;
         tfData[idx].martinAccumulatedLossUSD = 0;
         tfData[idx].currentLots = BaseLots;
         tfData[idx].state = STATE_CLOSE_COOLDOWN;
         tfData[idx].lastCloseTime = TimeCurrent();
      }
      return;
   }
   
   if(!tfData[idx].hasModifiedSL && profitPoints >= TrailingTrigger)
   {
      double newSL = openPrice - TrailingStop * _Point;
      if(newSL < currentSL || currentSL == 0)
      {
         tfData[idx].hasModifiedSL = true;
         tfData[idx].usedTrailingStopThisRound = true;
         
         if(!trade.PositionModify(_Symbol, newSL, 0))
         {
            Print(EnumToString(tfData[idx].period), " 移动止损修改失败");
         }
         else
         {
            Print(EnumToString(tfData[idx].period), 
                  " 【移动止损】浮盈=", (int)profitPoints, "点");
         }
      }
   }
}

//+------------------------------------------------------------------+

void OnPositionClosed(int idx)
{
   Print(EnumToString(tfData[idx].period), " 【平仓事件检测】");
   
   double closeProfitUSD = 0;
   if(HistorySelect(tfData[idx].entryTime - 10, TimeCurrent()))
   {
      int dealsTotal = HistoryDealsTotal();
      for(int i = dealsTotal - 1; i >= 0; i--)
      {
         ulong ticket = HistoryDealGetTicket(i);
         if(ticket > 0)
         {
            string dealSymbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
            int dealMagic = (int)HistoryDealGetInteger(ticket, DEAL_MAGIC);
            int dealEntry = (int)HistoryDealGetInteger(ticket, DEAL_ENTRY);
            
            if(dealSymbol == _Symbol && dealMagic == tfData[idx].magic && dealEntry == DEAL_ENTRY_OUT)
            {
               closeProfitUSD = HistoryDealGetDouble(ticket, DEAL_PROFIT);
               break;
            }
         }
      }
   }
   
   // 【核心逻辑】分情况处理

   if(tfData[idx].usedTrailingStopThisRound)
   {
      // ===== 移动止损触发 =====

      // 手数继续累加,马丁统计保留

      tfData[idx].currentLots = MathMin(tfData[idx].currentLots + BaseLots, MaxLots);
      
      Print(EnumToString(tfData[idx].period), 
            " 【移动止损平仓】盈利=$", DoubleToString(closeProfitUSD, 2),
            " | 下次手数=", DoubleToString(tfData[idx].currentLots, 2),
            " | 固定止损次数=", tfData[idx].martinFixedStopLossCount,
            " | 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2));
      
      // 进入冷却

      if(tfData[idx].state != STATE_MARTIN_FOLLOW_PENDING)
      {
         tfData[idx].state = STATE_CLOSE_COOLDOWN;
         tfData[idx].lastCloseTime = TimeCurrent();
      }
   }
   else if(closeProfitUSD < -0.01)
   {
      // ===== 固定止损触发 =====

      tfData[idx].martinFixedStopLossCount++;
      tfData[idx].martinAccumulatedLossUSD += (-closeProfitUSD);
      tfData[idx].currentLots = MathMin(tfData[idx].currentLots + BaseLots, MaxLots);
      
      Print(EnumToString(tfData[idx].period), 
            " 【固定止损平仓】亏损=$", DoubleToString(-closeProfitUSD, 2),
            " | 下次手数=", DoubleToString(tfData[idx].currentLots, 2),
            " | 固定止损次数=", tfData[idx].martinFixedStopLossCount,
            " | 累计亏损=$", DoubleToString(tfData[idx].martinAccumulatedLossUSD, 2));
      
      if(tfData[idx].state != STATE_MARTIN_FOLLOW_PENDING)
      {
         tfData[idx].state = STATE_CLOSE_COOLDOWN;
         tfData[idx].lastCloseTime = TimeCurrent();
      }
   }
   else
   {
      // ===== 其他盈利平仓(MA15或马丁止盈已在Manage里处理,这里不会到) =====

      Print(EnumToString(tfData[idx].period), 
            " 【其他平仓】盈利=$", DoubleToString(closeProfitUSD, 2));
   }
   
   tfData[idx].positionType = -1;
   tfData[idx].entryPrice = 0;
   tfData[idx].entryTime = 0;
   tfData[idx].lastStopLoss = 0;
   tfData[idx].hasModifiedSL = false;
   tfData[idx].usedTrailingStopThisRound = false;
}

//+------------------------------------------------------------------+

int GetPositionType(int magic)
{
   for(int i = 0; i < PositionsTotal(); i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         if(PositionGetString(POSITION_SYMBOL) == _Symbol && 
            PositionGetInteger(POSITION_MAGIC) == magic)
         {
            return (int)PositionGetInteger(POSITION_TYPE);
         }
      }
   }
   return -1;
}

//+------------------------------------------------------------------+

void CreatePanel()
{
   ObjectCreate(0, panelName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, panelName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, 20);
   ObjectSetInteger(0, panelName, OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, panelName, OBJPROP_FONTSIZE, 9);
   ObjectSetString(0, panelName, OBJPROP_FONT, "Courier New");
}

//+------------------------------------------------------------------+

void UpdatePanel()
{
   string text = "=== MA Martin EA ===\n";
   for(int i = 0; i < 3; i++)
   {
      text += EnumToString(tfData[i].period) + ": ";
      if(tfData[i].positionType == POSITION_TYPE_BUY)
         text += "BUY ";
      else if(tfData[i].positionType == POSITION_TYPE_SELL)
         text += "SELL ";
      else
         text += "- ";
      
      text += " 浮盈=$" + DoubleToString(tfData[i].floatingProfitUSD, 2);
      text += " 止损次数=" + IntegerToString(tfData[i].martinFixedStopLossCount);
      text += " 累计亏=$" + DoubleToString(tfData[i].martinAccumulatedLossUSD, 2);
      text += "\n";
   }
   
   ObjectSetString(0, panelName, OBJPROP_TEXT, text);
}

作品说明

我仔细检查了代码,发现了多个严重逻辑漏洞:

漏洞1:MA15止盈后没有进入冷却

``cpp if(holdSeconds >= MinHoldSeconds && lastClosePrice <= tfData[idx].bufMA15[1]) { if(trade.PositionClose(_Symbol)) { ... tfData[idx].state = STATE_CLOSE_COOLDOWN; // ✓ 设置了冷却 } return; } ` **问题**:虽然设置了冷却,但如果 trade.PositionClose 失败,state` 不会改变,下一次 Tick 可能继续尝试平仓。


漏洞2:OnPositionClosed 里的状态判断错误

``cpp if(tfData[idx].state != STATE_MARTIN_FOLLOW_PENDING) { tfData[idx].state = STATE_CLOSE_COOLDOWN; tfData[idx].lastCloseTime = TimeCurrent(); } `` 问题:

  • 如果是MA15止盈,state 已经被设置为 STATE_CLOSE_COOLDOWN,这里会重复设置(浪费)
  • 如果是马丁止盈,state 是 STATE_MARTIN_FOLLOW_PENDING,这里跳过设置,但 lastCloseTime 也没有设置,冷却逻辑可能混乱

漏洞3:手数累加没有上限检查

``cpp tfData[idx].currentLots += BaseLots; if(tfData[idx].currentLots > MaxLots) tfData[idx].currentLots = MaxLots; `` 问题:这个检查在多处重复。如果 MaxLots=1.0 但已经是 0.99,加 0.01 会变成 1.00,检查是对的。但如果连续多次固定止损,手数会一直卡在 MaxLots。


漏洞4:移动止损后,如果价格继续上升,会再次尝试修改SL

``cpp if(!tfData[idx].hasModifiedSL && profitPoints >= TrailingTrigger) { double newSL = openPrice + TrailingStop * _Point; if(newSL > currentSL) // ✓ 这个检查是对的 { if(trade.PositionModify(_Symbol, newSL, 0)) { tfData[idx].hasModifiedSL = true; } } } ` **问题**:虽然设置了 hasModifiedSL` 标志,但如果修改失败(比如网络错误),下次 Tick 会继续尝试。应该改为"一旦触发条件就立即标记,不管修改是否成功"。


漏洞5:平仓后的状态没有明确区分

``cpp OnPositionClosed → 判断是移动止损还是固定止损 → 更新马丁统计 → 设置冷却或追单 ` 但 ManageBuyPosition / ManageSellPosition 也会先一步设置状态(STATE_CLOSE_COOLDOWN 或 STATE_MARTIN_FOLLOW_PENDING`),导致逻辑混乱。


漏洞6:追单优先级问题

``cpp if(tfData[idx].state == STATE_MARTIN_FOLLOW_PENDING) { if(actualPosType == -1) { // 立即追单 } } `` 但如果追单时 Tick 之间有延迟,可能会出现"平仓后下一 Tick 还在冷却中"的情况,追单被延迟。


漏洞7:历史订单查询的时间范围太宽松

``cpp if(HistorySelect(tfData[idx].entryTime - 10, TimeCurrent())) ` 这会查询持仓入场后 10 秒内的**所有平仓订单**,如果同周期同方向有多个持仓,可能查到错误的订单。应该用 Position Ticket 查询相关的 Deal Ticket`。



完整的开仓→平仓逻辑流程说明

场景 A:固定止损触发(连续止损中)

``` 第1次开仓: ├─ 检查MA金叉信号 + MA距离 >= 400点 ├─ 开0.01手(BaseLots) ├─ 止损位 = 开仓价 - 400点 ├─ martinFixedStopLossCount = 0 ├─ martinAccumulatedLossUSD = 0 └─ state = STATE_BUY_OPEN

持仓管理(每个Tick): ├─ 浮盈 < 500点 → 等待移动止损条件 ├─ 收盘价还在MA10上方 → 继续持仓 └─ 浮盈 < 止损 → 等待固定止损触发

固定止损触发: ├─ OnPositionClosed: │ ├─ 查历史订单,closeProfitUSD = -$10(亏损) │ ├─ usedTrailingStopThisRound = false │ ├─ closeProfitUSD < -0.01 → 固定止损 │ ├─ martinFixedStopLossCount = 1 │ ├─ martinAccumulatedLossUSD = $10 │ ├─ currentLots = 0.02(手数翻倍) │ └─ state = STATE_CLOSE_COOLDOWN │ ├─ 冷却5秒 │ └─ 第2次开仓: ├─ 0.02手,止损 400点 ├─ martinFixedStopLossCount = 1 ├─ martinAccumulatedLossUSD = $10 └─ state = STATE_BUY_OPEN ```


场景 B:移动止损触发(保留马丁)

``` 第3次开仓: ├─ 0.03手(前2次都亏,累计 $10 + $20 = $30) ├─ martinFixedStopLossCount = 2 ├─ martinAccumulatedLossUSD = $30 └─ state = STATE_BUY_OPEN

持仓管理(每个Tick): ├─ 浮盈达到 500点(例如赚 $60) ├─ 触发移动止损条件 ├─ 修改 SL = 开仓价 + 200点 ├─ hasModifiedSL = true ├─ usedTrailingStopThisRound = true(标记用了移动止损!) └─ 继续持仓

价格回撤,触发移动止损: ├─ OnPositionClosed: │ ├─ 查历史订单,closeProfitUSD = $60(盈利) │ ├─ usedTrailingStopThisRound = true → 移动止损 │ ├─ 这 $60 盈利 不冲抵 累计亏损 $30 │ ├─ currentLots = 0.04(手数继续累加!) │ ├─ martinFixedStopLossCount = 2(保留!) │ ├─ martinAccumulatedLossUSD = $30(保留!) │ └─ state = STATE_CLOSE_COOLDOWN │ ├─ 冷却5秒 │ └─ 第4次开仓: ├─ 0.04手 ├─ martinFixedStopLossCount = 2(仍是2) ├─ martinAccumulatedLossUSD = $30(仍是$30) └─ 等待浮盈 >= $30 来触发马丁止盈 ```


场景 C:马丁止盈触发(重置马丁+追单)

``` 第4次持仓管理: ├─ 浮盈达到 $30+(覆盖前面的累计亏损) ├─ martinFixedStopLossCount = 2 >= MartinMaxLoss(4) ? │ ├─ 不满足(2 < 4)→ 继续持仓或等MA15止盈 │ └─ ... │ └─ 假设继续开第5、6次... ├─ 第5次亏损 → martinFixedStopLossCount = 3 ├─ 第6次亏损 → martinFixedStopLossCount = 4 │ ├─ 现在 martinFixedStopLossCount = 4 >= 4 ✓ │ └─ 开仓手数已累加到 0.06 手 │ └─ 第7次浮盈 >= 累计亏损 $XXX: ├─ ManageBuyPosition检查马丁止盈条件 ├─ profitUSD >= martinAccumulatedLossUSD ✓ ├─ 立即平仓 ├─ martinFixedStopLossCount = 0(重置!) ├─ martinAccumulatedLossUSD = 0(重置!) ├─ currentLots = 0.01(重置为基础手数!) ├─ state = STATE_MARTIN_FOLLOW_PENDING(标记待追单) ├─ followDirection = 0(多单) ├─ lastCloseTime = 当前时间 └─ return(不执行其他操作)

下一个 Tick(无持仓): ├─ state == STATE_MARTIN_FOLLOW_PENDING ├─ actualPosType == -1(确实没有持仓) ├─ 立即开 0.01 手追单 ├─ martinFixedStopLossCount = 0 ├─ martinAccumulatedLossUSD = 0 ├─ state = STATE_BUY_OPEN └─ 这是全新的一轮马丁 ```


场景 D:MA15止盈(不追单)

``` 持仓管理: ├─ 持仓时间 > 30秒(MinHoldSeconds) ├─ 收盘价 <= MA15(做多情况下) ├─ ManageBuyPosition 检查 ├─ 立即平仓 ├─ martinFixedStopLossCount = 0(重置!) ├─ martinAccumulatedLossUSD = 0(重置!) ├─ currentLots = 0.01(重置!) ├─ state = STATE_CLOSE_COOLDOWN(进入冷却,不追单) ├─ lastCloseTime = 当前时间 └─ return

冷却5秒后: ├─ state = STATE_NO_POS ├─ lastSignalBarTime = 0(重置,允许新的金叉信号) └─ 等待下一个金叉开仓信号 ```


修正后的完整代码


使用说明

安装与参数调整:

  • 将代码保存为 .mq5 文件,在 MetaTrader 5 中编译
  • 黄金交易建议使用 M5、M15、M30 三周期同时运行
  • 参数建议:BaseLots=0.01 基础手数,MaxLots=1.0 最大手数限制

开仓规则:

  1. MA10 > MA20 + MA距离 ≥ 400点 + 收盘价 > MA10 → 做多
  2. MA10 < MA20 + MA距离 ≥ 400点 + 收盘价 < MA10 → 做空
  3. 第一次开 0.01 手,固定止损 400 点

平仓规则(按优先级):

  1. 马丁止盈(固定止损次数 ≥ 4):浮盈 ≥ 累计亏损 → 立即平仓 → 立即追单 0.01 手
  2. MA15止盈:持仓 > 30秒 + 收盘价触及 MA15 → 立即平仓,重置马丁但不追单
  3. 移动止损:浮盈 ≥ 500点 → SL 移至开仓价+200点 → 手数保留,马丁统计保留

损失追踪:

  • 只计算固定止损的亏损总额
  • 移动止损的盈利不冲抵累计亏损
  • 每次固定止损后手数翻倍(0.01→0.02→0.03...最多 1.0)

Log 日志解读:

  • 【开多】 表示新开仓
  • 【固定止损平仓】 累计止损次数+1
  • 【移动止损平仓】 手数继续倍增,次数不变
  • 【马丁止盈后追单-多】 立即开 0.01 追单,马丁完全重置

AI 解读根据源码逐行分析写成,供参考,以实际运行为准

这是什么

这是一个基于三条均线(10/15/20周期EMA)交叉的马丁格尔EA,同时在M5、M15、M30三个周期运行,每个周期独立管理仓位。它的核心逻辑是:移动止损触发后,手数继续累加但马丁计数保留;固定止损触发后,手数翻倍并累积亏损,直到浮盈覆盖全部亏损时平仓重置,平仓后立即追单。

逻辑原理

开仓信号:当10均线向上/向下穿越20均线,穿越时两条均线距离≥400点,且上根K线收盘价在10均线之上/之下时,做多/做空。开仓后设固定止损(默认400点),不设止盈。

三种出场方式:

  1. 移动止损:浮盈达到500点时,移动止损到入场价+200点(做多)或入场价-200点(做空)。触发后手数累加BaseLots,但马丁计数和累计亏损保留,继续冷却5秒后可再次开仓。
  2. 固定止损:亏损触发原始止损时,记录亏损金额和次数,手数累加BaseLots,进入冷却后重新开仓。
  3. 马丁止盈:当固定止损次数≥4次时,浮盈美元数≥累计亏损美元数时平仓。平仓后立即以BaseLots手数追单(同方向),马丁计数和累计亏损归零。
  4. MA15止盈:持仓≥30秒后,多单收盘价跌破15均线或空单收盘价突破15均线时平仓,马丁归零,冷却5秒。

手数管理:每次移动止损或固定止损后,下次开仓手数=当前手数+BaseLots,上限MaxLots。马丁止盈或MA15止盈后手数重置为BaseLots。

参数说明

  • MA10_Period(10)/ MA15_Period(15)/ MA20_Period(20):三条均线的周期,用于交叉信号和MA15止盈判断。
  • BaseLots(0.01):基础手数,也是每次累加的单位和马丁重置后的手数。
  • MaxLots(1.0):单次最大手数,防止无限翻倍。
  • StopLoss(400):固定止损点数,开仓时设置,触发后记录亏损。
  • MADistanceMin(400):开仓时10均线和20均线的最小距离(点数),过滤假突破。
  • TrailingTrigger(500):浮盈达到多少点时触发移动止损。
  • TrailingStop(200):移动止损移到入场价+/-多少点,留一定利润空间。
  • CooldownSeconds(5):平仓后的冷却时间(秒),防止同一根K线反复开仓。
  • MinHoldSeconds(30):MA15止盈的最短持仓时间,避免刚开仓就被扫出。
  • MartinMaxLoss(4):固定止损多少次后启用马丁止盈逻辑(浮盈≥累计亏损才平)。
  • MagicBase(20250101):Magic Number基础值,三个周期分别用MagicBase+5、MagicBase+15、MagicBase+30,用于区分订单。

怎么用

  1. 将代码保存为.mq5文件,放到MT5的MQL5/Experts/目录,编译无错误。
  2. 打开任意图表(建议XAUUSD或波动大的品种),拖入EA,选择"允许自动交易"。
  3. EA会自动在M5、M15、M30三个周期各开一套独立仓位,左上角面板显示每个周期的持仓方向、浮盈、止损次数和累计亏损。
  4. 观察日志和面板,确认每次平仓后的手数、马丁计数是否符合预期。

适用场景

适合趋势行情和震荡行情的混合品种,如黄金(XAUUSD)。10/20均线交叉在趋势行情中容易连续盈利,移动止损保护利润;震荡行情中可能多次固定止损,但马丁止盈逻辑可以在后续反弹中回本。三周期同时运行可以分散单一周期的假信号风险。

不适合单边瀑布行情或流动性极差的品种。连续固定止损会快速放大手数,如果达到MaxLots还未回本,可能面临大额亏损。建议在波动稳定的时段(欧美盘)使用,避开重大数据和假期。

风险提示

  1. 马丁风险:连续固定止损会累积手数和亏损,如果行情持续单边且达到MaxLots,单次回撤可能很大。建议根据账户资金调整BaseLots和MaxLots,确保4–5次止损后仍在承受范围内。
  2. 移动止损保留马丁:移动止损触发后,马丁计数不会重置,后续如果再次固定止损,手数会在已累加的基础上继续翻倍。这个设计可能让你在盈利后又陷入更大的回撤,需要严格控制MaxLots。
  3. 追单风险:马丁止盈后立即追单(同方向0.01手),如果行情反转,可能再次进入马丁循环。代码里没有限制追单次数,如果连续马丁止盈+追单+再马丁,累计次数可能超出预期。
  4. 滑点和点差:固定止损400点在黄金上约4美元,但如果点差过大或滑点严重,实际止损可能更大,影响马丁止盈的计算准确性。
  5. 三周期同时运行:三个周期可能同时持仓,最大占用资金=3*MaxLots。如果账户资金不足,可能因保证金不足无法开仓或被强平。
  6. 无最大亏损保护:代码里没有全局最大亏损或每日止损逻辑,如果行情极端,可能在一天内累积大额亏损。建议手动监控或在VPS上加外部风控脚本。

常见问题

移动止损后为什么马丁计数不重置?
这是作者设计的特殊逻辑。移动止损触发说明已经盈利,但如果后续再次固定止损,手数会在已累加的基础上继续翻倍,目的是保持马丁压力。如果不想要这个特性,需要修改OnPositionClosed里的判断,在移动止损后也重置martinFixedStopLossCount。
马丁止盈后为什么会立即追单?
代码里马丁止盈后state设为STATE_MARTIN_FOLLOW_PENDING,OnPositionClosed检测到平仓后立即调用OpenFollowBuy/Sell,以BaseLots手数追单。这是为了不错过趋势延续,但如果行情反转会再次进入马丁。可以在参数里加一个"是否追单"开关,或者改为普通冷却。
三个周期会同时开仓吗?会不会重仓?
会。M5、M15、M30各自独立判断信号,可能同时持有多单或空单,最大占用=3*MaxLots*手数。如果账户资金只够1手保证金,MaxLots设1.0可能不够用。建议根据账户净值调整BaseLots和MaxLots,或者只启用一个周期。
固定止损400点是不是太大了?
400点在黄金上约4美元止损(0.01手),对于波动大的品种算中等。如果你交易的品种点值不同或波动小,需要根据ATR或历史波动调整StopLoss。太小容易频繁触发马丁,太大单次亏损过多。
这个EA能用在其他品种吗?
代码没有限制品种,但参数(StopLoss、MADistanceMin、TrailingTrigger等)是按黄金波动设计的。如果用在外汇或指数,需要根据点值和波动重新测试。建议先在模拟盘跑一周,观察开仓频率和马丁触发情况。

Triple MA Martin EA with Trailing Stop Preserved (MT5)

A martingale EA based on 10/15/20 EMA crossovers. Trailing stop preserves martingale progression; fixed stop-loss doubles position size. After martingale profit target is hit, resets and follows the trend. Runs on M5/M15/M30 simultaneously.

Source code is shown above. Compiled EX5 files are available to VIP members.

评论

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

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