MT5指标编译通过 · EX5 44KB 自动发布 1 0

HT 指标 EA - 增强型趋势跟踪

老吴·2 小时前发布

支持分阶段止损、ATR 动态跟踪、反向信号平仓,适合大趋势行情

HT 指标 EA - 增强型趋势跟踪

MT5 真机运行截图 · 服务器把编译好的 EX5 挂到黄金 H1 图表上截的,你装上去看到的就是这个样子

挂到 MT5 上运行时终端报了错

编译通过了,但真挂到图表上时终端记录了下面的错误。指标一报错就会被终端停掉,图上可能只剩半截线甚至一片空白—— 这就是「装上去和预期不一样」的原因。把报错连同代码丢给 AI 工作台,让它改好重新发布,截图会自动更新。

cannot load custom indicator 'HT' (XAUUSD.n) [4802]
HT_EA_Advanced.mq5591 行
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
#property copyright "厕所2号"
#property version   "3.00"

#include <Trade\Trade.mqh>

//========== 指标设置 ==========
input string    Section1 = "========== 指标设置 ==========";
input string    IndicatorName = "HT";
input int       InpAmplitude = 2;
input int       InpAtrPeriod = 100;
input ENUM_TIMEFRAMES SignalTimeframe = PERIOD_M15;

//========== 交易设置 ==========
input string    Section2 = "========== 交易设置 ==========";
input double    InitialLot = 0.01;
input double    MaxLot = 0.3;

//========== 摆动止损设置 ==========
input string    Section3 = "========== 摆动止损设置 ==========";
input int       SwingLookback = 10;
input int       StopBuffer = 100;

//========== 分阶段止损设置(新增)==========
input string    Section4 = "========== 分阶段止损设置 ==========";
input bool      EnablePhaseTrailing = true;          // 启用分阶段跟踪止损
input int       Phase1ProfitTrigger = 300;           // 第一阶段:盈利达此值(点)后进入第二阶段
input int       Phase2ProfitTrigger = 800;           // 第二阶段:盈利达此值(点)后进入第三阶段
input int       Phase1SLBuffer = 200;                // 第一阶段平仓:设置保本止损,缓冲此距离
input int       Phase2ATRMultiplier = 2;             // 第二阶段跟踪:用 2 倍 ATR 作为止损距离
input int       Phase3ATRMultiplier = 1;             // 第三阶段跟踪:用 1 倍 ATR 作为止损距离(更激进)
input int       MinATRDistance = 100;                // ATR 跟踪的最小距离(点)
input bool      CloseOnReverseSignal = true;         // 反向信号时立即平仓

//========== 风控设置 ==========
input string    Section5 = "========== 风控设置 ==========";
input int       MagicNumber = 888888;
input string    TradeComment = "HT_EA_V3";

//========== 全局变量 ==========
CTrade trade;
int indicatorHandle = INVALID_HANDLE;
int atrHandle = INVALID_HANDLE;
double upArrow[], downArrow[];
double currentLotBuy = 0.01;
double currentLotSell = 0.01;
datetime lastBarTime = 0;

// 持仓阶段跟踪
struct PositionPhase {
   int phase;              // 1=初期摆动, 2=中期保本, 3=深度跟踪
   double entryPrice;
   double highestPrice;    // 做多最高点
   double lowestPrice;     // 做空最低点
   int profitAtPhase2;     // 进入第二阶段的盈利
};

PositionPhase positionPhaseInfo;

//+------------------------------------------------------------------+
//| EA 初始化
//+------------------------------------------------------------------+
int OnInit()
{
   if(SwingLookback < 1)
   {
      Print("❌ 摆动回溯K线数必须大于0");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   trade.SetExpertMagicNumber(MagicNumber);
   trade.SetDeviationInPoints(50);
   trade.SetTypeFilling(ORDER_FILLING_FOK);
   trade.SetAsyncMode(false);
   
   // 加载 HT 指标
   indicatorHandle = iCustom(_Symbol, SignalTimeframe, IndicatorName, 
                             InpAmplitude, InpAtrPeriod, 
                             true, true, false, false, false, false);
   
   if(indicatorHandle == INVALID_HANDLE)
   {
      Print("❌ 无法加载指标: ", IndicatorName);
      return INIT_FAILED;
   }
   
   // 加载 ATR 指标
   atrHandle = iATR(_Symbol, SignalTimeframe, InpAtrPeriod);
   if(atrHandle == INVALID_HANDLE)
   {
      Print("❌ 无法加载 ATR 指标");
      return INIT_FAILED;
   }
   
   currentLotBuy = InitialLot;
   currentLotSell = InitialLot;
   
   Print("✅ EA 初始化成功 (v3.0)");
   Print("   分阶段止损: ", EnablePhaseTrailing ? "启用" : "禁用");
   Print("   第一阶段触发: ", Phase1ProfitTrigger, " 点");
   Print("   第二阶段触发: ", Phase2ProfitTrigger, " 点");
   Print("   反向信号平仓: ", CloseOnReverseSignal ? "是" : "否");
   
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| EA 反初始化
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(indicatorHandle != INVALID_HANDLE)
      IndicatorRelease(indicatorHandle);
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
   
   Print("EA 已停止");
}

//+------------------------------------------------------------------+
//| 每个 Tick 执行
//+------------------------------------------------------------------+
void OnTick()
{
   datetime currentBarTime = iTime(_Symbol, SignalTimeframe, 0);
   bool isNewBar = (currentBarTime != lastBarTime);
   
   if(isNewBar)
   {
      lastBarTime = currentBarTime;
      CheckSignalsAndTrade();
   }
   
   // 每个 Tick 检查分阶段止损和跟踪
   if(EnablePhaseTrailing)
      CheckPhaseTrailing();
}

//+------------------------------------------------------------------+
//| 检查信号并开单
//+------------------------------------------------------------------+
void CheckSignalsAndTrade()
{
   int calculated = BarsCalculated(indicatorHandle);
   if(calculated < 2)
      return;
   
   if(CopyBuffer(indicatorHandle, 6, 0, 3, upArrow) < 3)
      return;
   if(CopyBuffer(indicatorHandle, 7, 0, 3, downArrow) < 3)
      return;
   
   int signalBar = 1;
   
   //====== 做多信号 ======
   if(upArrow[signalBar] != EMPTY_VALUE && upArrow[signalBar] > 0)
   {
      if(HasPosition(POSITION_TYPE_BUY))
         return;
      
      double swingLow = GetSwingLow(SwingLookback);
      if(swingLow <= 0)
         return;
      
      double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      double sl = swingLow - StopBuffer * _Point;
      
      if(sl >= price)
         sl = price - StopBuffer * _Point;
      
      double lotToUse = NormalizeLot(currentLotBuy);
      
      if(trade.Buy(lotToUse, _Symbol, price, sl, 0, TradeComment))
      {
         Print("🔵 做多开单: ", price, " | 止损: ", sl);
         
         // 初始化持仓阶段信息
         positionPhaseInfo.phase = 1;
         positionPhaseInfo.entryPrice = price;
         positionPhaseInfo.highestPrice = price;
         positionPhaseInfo.profitAtPhase2 = 0;
         
         currentLotBuy = InitialLot;
      }
   }
   
   //====== 做空信号 ======
   if(downArrow[signalBar] != EMPTY_VALUE && downArrow[signalBar] > 0)
   {
      if(HasPosition(POSITION_TYPE_SELL))
         return;
      
      double swingHigh = GetSwingHigh(SwingLookback);
      if(swingHigh <= 0)
         return;
      
      double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double sl = swingHigh + StopBuffer * _Point;
      
      if(sl <= price)
         sl = price + StopBuffer * _Point;
      
      double lotToUse = NormalizeLot(currentLotSell);
      
      if(trade.Sell(lotToUse, _Symbol, price, sl, 0, TradeComment))
      {
         Print("🔴 做空开单: ", price, " | 止损: ", sl);
         
         positionPhaseInfo.phase = 1;
         positionPhaseInfo.entryPrice = price;
         positionPhaseInfo.lowestPrice = price;
         positionPhaseInfo.profitAtPhase2 = 0;
         
         currentLotSell = InitialLot;
      }
   }
   
   //====== 反向信号平仓 ======
   if(CloseOnReverseSignal)
   {
      if(upArrow[signalBar] != EMPTY_VALUE && upArrow[signalBar] > 0 && HasPosition(POSITION_TYPE_SELL))
      {
         CloseAllPositionByType(POSITION_TYPE_SELL);
         Print("⚠️ 反向做多信号,平仓所有做空");
      }
      
      if(downArrow[signalBar] != EMPTY_VALUE && downArrow[signalBar] > 0 && HasPosition(POSITION_TYPE_BUY))
      {
         CloseAllPositionByType(POSITION_TYPE_BUY);
         Print("⚠️ 反向做空信号,平仓所有做多");
      }
   }
}

//+------------------------------------------------------------------+
//| 检查分阶段止损和跟踪(核心优化逻辑)
//+------------------------------------------------------------------+
void CheckPhaseTrailing()
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      
      double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
      double currentSL = PositionGetDouble(POSITION_SL);
      double currentTP = PositionGetDouble(POSITION_TP);
      ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      double currentPrice = (posType == POSITION_TYPE_BUY) ? bid : ask;
      
      if(posType == POSITION_TYPE_BUY)
         ProcessBuyPhaseTrailing(ticket, openPrice, currentSL, currentTP, currentPrice, bid);
      else
         ProcessSellPhaseTrailing(ticket, openPrice, currentSL, currentTP, currentPrice, ask);
   }
}

//+------------------------------------------------------------------+
//| 处理做多的分阶段止损
//+------------------------------------------------------------------+
void ProcessBuyPhaseTrailing(ulong ticket, double openPrice, double currentSL, 
                             double currentTP, double currentPrice, double bid)
{
   double profitPoints = (currentPrice - openPrice) / _Point;
   
   // 更新最高价
   if(currentPrice > positionPhaseInfo.highestPrice)
      positionPhaseInfo.highestPrice = currentPrice;
   
   double newSL = currentSL;
   
   //===== 第一阶段:只用摆动止损 =====
   if(positionPhaseInfo.phase == 1 && profitPoints >= Phase1ProfitTrigger)
   {
      positionPhaseInfo.phase = 2;
      positionPhaseInfo.profitAtPhase2 = (int)profitPoints;
      
      // 激活保本:移至入场价 + Phase1SLBuffer
      newSL = openPrice + Phase1SLBuffer * _Point;
      
      Print("📈 [做多] 进入第二阶段 | 票号: ", ticket);
      Print("   触发条件: 盈利 ", (int)profitPoints, " 点 >= ", Phase1ProfitTrigger, " 点");
      Print("   新止损: ", newSL, " (保本缓冲 ", Phase1SLBuffer, " 点)");
   }
   
   //===== 第二阶段:ATR 2x 跟踪 =====
   if(positionPhaseInfo.phase == 2)
   {
      if(profitPoints >= Phase2ProfitTrigger)
      {
         positionPhaseInfo.phase = 3;
         Print("📈 [做多] 进入第三阶段 | 票号: ", ticket);
         Print("   触发条件: 盈利 ", (int)profitPoints, " 点 >= ", Phase2ProfitTrigger, " 点");
      }
      else
      {
         // 用 2 倍 ATR 作为动态止损
         double atrValue = GetATRValue();
         double atrDistance = MathMax(atrValue * Phase2ATRMultiplier / _Point, MinATRDistance);
         newSL = positionPhaseInfo.highestPrice - atrDistance * _Point;
         
         // 只在新止损高于当前止损时修改
         if(newSL > currentSL + _Point)
         {
            if(trade.PositionModify(ticket, newSL, currentTP))
            {
               Print("📊 [做多] 第二阶段止损更新 | 票号: ", ticket);
               Print("   ATR: ", DoubleToString(atrValue, 2), " | 2x距离: ", 
                     DoubleToString(atrDistance, 0), " 点 | 新止损: ", newSL);
            }
         }
      }
   }
   
   //===== 第三阶段:ATR 1x 激进跟踪 =====
   if(positionPhaseInfo.phase == 3)
   {
      double atrValue = GetATRValue();
      double atrDistance = MathMax(atrValue * Phase3ATRMultiplier / _Point, MinATRDistance);
      newSL = positionPhaseInfo.highestPrice - atrDistance * _Point;
      
      if(newSL > currentSL + _Point)
      {
         if(trade.PositionModify(ticket, newSL, currentTP))
         {
            Print("📊 [做多] 第三阶段止损更新 | 票号: ", ticket);
            Print("   ATR: ", DoubleToString(atrValue, 2), " | 1x距离: ", 
                  DoubleToString(atrDistance, 0), " 点 | 新止损: ", newSL);
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 处理做空的分阶段止损
//+------------------------------------------------------------------+
void ProcessSellPhaseTrailing(ulong ticket, double openPrice, double currentSL, 
                              double currentTP, double currentPrice, double ask)
{
   double profitPoints = (openPrice - currentPrice) / _Point;
   
   // 更新最低价
   if(currentPrice < positionPhaseInfo.lowestPrice)
      positionPhaseInfo.lowestPrice = currentPrice;
   
   double newSL = currentSL;
   
   //===== 第一阶段:只用摆动止损 =====
   if(positionPhaseInfo.phase == 1 && profitPoints >= Phase1ProfitTrigger)
   {
      positionPhaseInfo.phase = 2;
      positionPhaseInfo.profitAtPhase2 = (int)profitPoints;
      
      // 激活保本:移至入场价 - Phase1SLBuffer
      newSL = openPrice - Phase1SLBuffer * _Point;
      
      Print("📈 [做空] 进入第二阶段 | 票号: ", ticket);
      Print("   触发条件: 盈利 ", (int)profitPoints, " 点 >= ", Phase1ProfitTrigger, " 点");
      Print("   新止损: ", newSL, " (保本缓冲 ", Phase1SLBuffer, " 点)");
   }
   
   //===== 第二阶段:ATR 2x 跟踪 =====
   if(positionPhaseInfo.phase == 2)
   {
      if(profitPoints >= Phase2ProfitTrigger)
      {
         positionPhaseInfo.phase = 3;
         Print("📈 [做空] 进入第三阶段 | 票号: ", ticket);
         Print("   触发条件: 盈利 ", (int)profitPoints, " 点 >= ", Phase2ProfitTrigger, " 点");
      }
      else
      {
         // 用 2 倍 ATR 作为动态止损
         double atrValue = GetATRValue();
         double atrDistance = MathMax(atrValue * Phase2ATRMultiplier / _Point, MinATRDistance);
         newSL = positionPhaseInfo.lowestPrice + atrDistance * _Point;
         
         // 只在新止损低于当前止损时修改
         if(newSL < currentSL - _Point)
         {
            if(trade.PositionModify(ticket, newSL, currentTP))
            {
               Print("📊 [做空] 第二阶段止损更新 | 票号: ", ticket);
               Print("   ATR: ", DoubleToString(atrValue, 2), " | 2x距离: ", 
                     DoubleToString(atrDistance, 0), " 点 | 新止损: ", newSL);
            }
         }
      }
   }
   
   //===== 第三阶段:ATR 1x 激进跟踪 =====
   if(positionPhaseInfo.phase == 3)
   {
      double atrValue = GetATRValue();
      double atrDistance = MathMax(atrValue * Phase3ATRMultiplier / _Point, MinATRDistance);
      newSL = positionPhaseInfo.lowestPrice + atrDistance * _Point;
      
      if(newSL < currentSL - _Point)
      {
         if(trade.PositionModify(ticket, newSL, currentTP))
         {
            Print("📊 [做空] 第三阶段止损更新 | 票号: ", ticket);
            Print("   ATR: ", DoubleToString(atrValue, 2), " | 1x距离: ", 
                  DoubleToString(atrDistance, 0), " 点 | 新止损: ", newSL);
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 获取 ATR 值
//+------------------------------------------------------------------+
double GetATRValue()
{
   double atrBuffer[];
   if(BarsCalculated(atrHandle) < 1)
      return 100 * _Point;  // 默认返回 100 点
   
   if(CopyBuffer(atrHandle, 0, 0, 1, atrBuffer) <= 0)
      return 100 * _Point;
   
   return atrBuffer[0];
}

//+------------------------------------------------------------------+
//| 获取摆动低点
//+------------------------------------------------------------------+
double GetSwingLow(int lookback)
{
   double lowArray[];
   ArraySetAsSeries(lowArray, true);
   
   if(CopyLow(_Symbol, SignalTimeframe, 0, lookback + 1, lowArray) <= 0)
      return 0;
   
   double swingLow = lowArray[1];
   for(int i = 1; i <= lookback && i < ArraySize(lowArray); i++)
   {
      if(lowArray[i] < swingLow)
         swingLow = lowArray[i];
   }
   
   return swingLow;
}

//+------------------------------------------------------------------+
//| 获取摆动高点
//+------------------------------------------------------------------+
double GetSwingHigh(int lookback)
{
   double highArray[];
   ArraySetAsSeries(highArray, true);
   
   if(CopyHigh(_Symbol, SignalTimeframe, 0, lookback + 1, highArray) <= 0)
      return 0;
   
   double swingHigh = highArray[1];
   for(int i = 1; i <= lookback && i < ArraySize(highArray); i++)
   {
      if(highArray[i] > swingHigh)
         swingHigh = highArray[i];
   }
   
   return swingHigh;
}

//+------------------------------------------------------------------+
//| 检查是否有指定方向持仓
//+------------------------------------------------------------------+
bool HasPosition(ENUM_POSITION_TYPE type)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      
      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type)
         return true;
   }
   return false;
}

//+------------------------------------------------------------------+
//| 平仓指定方向所有持仓
//+------------------------------------------------------------------+
void CloseAllPositionByType(ENUM_POSITION_TYPE type)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      
      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type)
         trade.PositionClose(ticket);
   }
}

//+------------------------------------------------------------------+
//| 交易事件回调(检测亏损后翻倍)
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
{
   if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
      return;
   
   if(!HistoryDealSelect(trans.deal))
      return;
   
   if(HistoryDealGetInteger(trans.deal, DEAL_MAGIC) != MagicNumber)
      return;
   if(HistoryDealGetString(trans.deal, DEAL_SYMBOL) != _Symbol)
      return;
   
   ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
   if(entry != DEAL_ENTRY_OUT)
      return;
   
   double profit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT);
   double swap = HistoryDealGetDouble(trans.deal, DEAL_SWAP);
   double commission = HistoryDealGetDouble(trans.deal, DEAL_COMMISSION);
   double totalProfit = profit + swap + commission;
   
   ENUM_DEAL_TYPE dealType = (ENUM_DEAL_TYPE)HistoryDealGetInteger(trans.deal, DEAL_TYPE);
   
   if(totalProfit < 0)
   {
      if(dealType == DEAL_TYPE_BUY)
      {
         currentLotBuy = currentLotBuy * 2;
         if(currentLotBuy > MaxLot)
            currentLotBuy = MaxLot;
         Print("📉 做多止损 | 亏损: ", DoubleToString(totalProfit, 2), 
               " | 下次手数: ", currentLotBuy);
      }
      else if(dealType == DEAL_TYPE_SELL)
      {
         currentLotSell = currentLotSell * 2;
         if(currentLotSell > MaxLot)
            currentLotSell = MaxLot;
         Print("📉 做空止损 | 亏损: ", DoubleToString(totalProfit, 2), 
               " | 下次手数: ", currentLotSell);
      }
   }
   else if(totalProfit > 0)
   {
      if(dealType == DEAL_TYPE_BUY)
      {
         currentLotBuy = InitialLot;
         Print("📈 做多盈利 | 盈利: ", DoubleToString(totalProfit, 2), 
               " | 手数重置: ", currentLotBuy);
      }
      else if(dealType == DEAL_TYPE_SELL)
      {
         currentLotSell = InitialLot;
         Print("📈 做空盈利 | 盈利: ", DoubleToString(totalProfit, 2), 
               " | 手数重置: ", currentLotSell);
      }
   }
}

//+------------------------------------------------------------------+
//| 规范化手数
//+------------------------------------------------------------------+
double NormalizeLot(double lot)
{
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   
   if(lot < minLot)
      lot = minLot;
   if(lot > maxLot)
      lot = maxLot;
   
   lot = MathFloor(lot / stepLot) * stepLot;
   
   return lot;
}
MT5 运行截图 · 完整视图带价格轴和时间轴的整张图表(约 150 根 H1 K 线),上面的封面是它最近 80 根 K 线的放大视图
HT 指标 EA - 增强型趋势跟踪 在 MT5 上的真实截图

作品说明

我先分析一下这个 EA 的核心逻辑和痛点。

分析与优化方案

现状痛点识别:

  1. 保本触发太激进 — 当价格触及「摆动高点 - BreakevenTriggerOffset」时就立即激活保本,在大趋势行情中这个条件往往很快满足,导致止损被锁定在接近入场价的位置。
  2. 移动止损步长固定 — 每盈利固定点数才移动一次,在大行情中跟不上价格变化的速度,容易被回调止损。
  3. 缺乏趋势强度判断 — 没有区分「盘整回调」和「大趋势继续」,一律用同样的摆动逻辑处理。

优化方案:

  1. 分阶段止损策略:
  • 第一阶段(初期):只用摆动止损,不激活保本,让利润充分积累
  • 第二阶段(中期盈利):盈利达到较深的目标(如 500 点)时才激活保本
  • 第三阶段(大幅盈利):改用更激进的跟踪止损,每个新高 / 新低主动追踪
  1. 加入 ATR 动态跟踪 — 用 ATR 衡量波动率,而不是固定点数,大行情时自动跟踪更快
  1. 反向信号平仓 — HT 指标产生反向信号时强制平仓(可选),防止大行情反转时亏损扩大
  1. 保留摆动止损作为最后防线 — 不完全依赖移动止损

我给你写个升级版本,加入「深度盈利触发」和「ATR 动态跟踪」:

使用说明

  1. 分阶段逻辑:盈利 300 点→保本,盈利 800 点→进入 ATR 激进跟踪。参数可根据品种调整。
  1. Phase1ProfitTrigger / Phase2ProfitTrigger:控制从摆动止损、到保本、再到 ATR 跟踪的两个阶段,比之前一成不变的策略更符合大趋势节奏。
  1. Phase2ATRMultiplier / Phase3ATRMultiplier:第二、三阶段用 ATR 的倍数动态设置止损距离,大行情波动大时自动放宽,防止被回调止损。
  1. CloseOnReverseSignal:反向信号时强制平仓,保护大幅利润。
  1. MinATRDistance:ATR 过小时的最小止损距离下限,避免止损设置过紧。
  1. 旧参数(SwingLookback、StopBuffer)保留,作为第一阶段的保底止损。
  1. 关键改进:延迟保本激活 → 让利润充分积累 → ATR 动态跟踪 → 自适应波动 → 反向信号平仓 → 保护大单。
  1. 建议在回测中调整 Phase1/Phase2 的触发点和 ATR 倍数,找到平衡点。

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

这是什么

这是一个为 MT5 平台开发的自动交易 EA(Expert Advisor),必须配合名为 "HT" 的自定义指标使用。它的核心特点是「分三阶段管理止损」:初期用摆动高低点守住底线,盈利达标后切换到保本模式,大幅盈利时改用 ATR 动态跟踪,让利润跑得更远。同时支持反向信号强制平仓,避免趋势反转时吃回利润。

逻辑原理

开仓信号来自外部 HT 指标的箭头:出现向上箭头(upArrow)时做多,向下箭头(downArrow)时做空。止损设在最近 N 根 K 线的摆动高点或低点外侧,用 StopBuffer 参数留出缓冲距离。

分阶段止损是核心亮点:

  • 第一阶段(初期):止损保持在摆动高低点,不轻易移动,给价格足够的波动空间
  • 第二阶段(中期盈利):浮盈达到 Phase1ProfitTrigger(默认 300 点)后,止损移至入场价上方 Phase1SLBuffer 距离(保本 + 缓冲),开始用 ATR 的 2 倍作为跟踪距离
  • 第三阶段(深度盈利):浮盈达到 Phase2ProfitTrigger(默认 800 点)后,改用 ATR 的 1 倍激进跟踪,止损跟得更紧,锁定大部分利润

每个阶段的止损距离根据当前 ATR 值动态计算,波动大的时候自动放宽,波动小的时候收紧,避免被正常回调扫出场。

反向信号平仓:如果持有做多单时 HT 指标出现做空箭头(或反之),EA 会立即平掉所有持仓,不等止损触发。

资金管理用了马丁格尔策略:亏损后下一单手数翻倍(最高到 MaxLot),盈利后重置为 InitialLot。这是一把双刃剑,能快速回本,但连续亏损会急速放大风险。

参数说明

指标设置

  • IndicatorName("HT"):外部指标的文件名,默认是 "HT",必须确保该指标已安装在 Indicators 文件夹
  • InpAmplitude(2):传给 HT 指标的振幅参数,具体含义取决于 HT 指标的设计
  • InpAtrPeriod(100):ATR 计算周期,用于动态止损距离,周期越长越平滑
  • SignalTimeframe(M15):读取 HT 信号的时间周期,默认 15 分钟

交易设置

  • InitialLot(0.01):初始手数,建议根据账户余额调整
  • MaxLot(0.3):马丁格尔翻倍的上限,防止单笔风险过大

摆动止损设置

  • SwingLookback(10):回溯多少根 K 线找摆动高低点,数值越大止损越宽
  • StopBuffer(100):止损在摆动点外留的缓冲距离(点),防止被瞬间波动扫到

分阶段止损设置

  • EnablePhaseTrailing(true):是否启用分阶段跟踪,关闭后只用摆动止损
  • Phase1ProfitTrigger(300):盈利多少点后进入第二阶段(保本),适合波动品种可调大
  • Phase2ProfitTrigger(800):盈利多少点后进入第三阶段(激进跟踪),大趋势中才能达到
  • Phase1SLBuffer(200):第二阶段保本时,止损在入场价上方留多少点缓冲
  • Phase2ATRMultiplier(2):第二阶段用 ATR 的 2 倍作为止损距离,相对保守
  • Phase3ATRMultiplier(1):第三阶段用 ATR 的 1 倍,更激进,快速锁定利润
  • MinATRDistance(100):ATR 计算出的距离如果太小,强制用这个最小值,防止止损过紧
  • CloseOnReverseSignal(true):反向信号时立即平仓,关闭后允许持仓等待止损

风控设置

  • MagicNumber(888888):EA 的唯一标识,多个 EA 同时运行时用来区分
  • TradeComment("HT_EA_V3"):订单备注

怎么用

  1. 安装 HT 指标:先确保名为 "HT" 的自定义指标已放在 MT5 的 MQL5/Indicators 文件夹,重启 MT5 后在图表上能手动加载它
  2. 编译 EA:把这个 EA 源码放到 MQL5/Experts 文件夹,在 MetaEditor 里编译成 .ex5 文件
  3. 挂载到图表:打开你想交易的品种(如 XAUUSD)和周期(建议 M15 或以上),把 EA 拖到图表上
  4. 调整参数:根据品种波动特性调整 Phase1/Phase2 触发点、ATR 倍数、InitialLot 和 MaxLot
  5. 启用自动交易:点击 MT5 工具栏的 "AutoTrading" 按钮,EA 会在 HT 指标出现箭头时自动开单
  6. 观察日志:终端窗口的 "Expert" 选项卡会打印每次进入新阶段、修改止损的详细信息

EA 不会在图表上画任何东西,所有信号和止损逻辑都通过订单和日志体现。

适用场景

适合的行情:单边大趋势,价格持续朝一个方向走几百上千点,中间回调幅度不超过 ATR 的 2 倍。典型场景是重大消息后的单边行情、趋势突破后的延续阶段。

适合的品种:波动较大、点差相对小的品种,如 XAUUSD(黄金)、主要货币对(EURUSD、GBPUSD)、原油。点差过大的小品种会被分阶段止损频繁扫出。

适合的周期:M15 及以上,H1 和 H4 更理想。M5 以下噪音太多,摆动止损和 ATR 跟踪都容易误判。

不适合的情况:

  • 震荡行情:HT 指标会频繁产生箭头,每次开单都被摆动止损打掉,马丁格尔翻倍会急速亏损
  • ATR 极小的时段(如亚洲盘早盘):MinATRDistance 会强制止损收得过紧
  • 连续假突破:反向信号平仓会导致来回止损

风险提示

依赖外部指标:EA 完全依赖 HT 指标的箭头信号,如果 HT 指标本身有重绘问题(即箭头出现后又消失),EA 的回测结果会严重失真,实盘可能完全不同。使用前必须先确认 HT 指标不重绘。

马丁格尔翻倍:亏损后手数翻倍是高风险策略,连续 5 次亏损后手数会变成初始的 32 倍。如果 InitialLot = 0.01、MaxLot = 0.3,只需 5 次亏损就触顶。建议只在大概率趋势延续时使用,或者把 MaxLot 设得保守一些。

滑点和点差:分阶段修改止损时,如果在快速行情中滑点较大,实际成交价可能和预期有偏差。Phase1SLBuffer 和 MinATRDistance 建议设得比品种平均点差大 3 倍以上。

反向信号误判:如果 HT 指标在趋势中途产生短暂反向箭头(假信号),CloseOnReverseSignal 会让你过早离场,错过后续大段利润。可以根据 HT 指标的稳定性决定是否关闭这个功能。

ATR 滞后性:ATR 是历史波动率的平均值,在行情突然加速时会滞后,可能导致第三阶段止损跟得太慢。InpAtrPeriod 设得越大越平滑但越滞后,建议在回测中找平衡点。

单一持仓假设:代码中 PositionPhase 结构体是全局变量,隐含假设同一时间只有一个方向的持仓。如果手动或其他 EA 同时开了多单,阶段跟踪会混乱。

常见问题

这个 EA 必须配合 HT 指标吗?我能用其他指标吗?
必须配合。EA 的开仓信号完全来自 HT 指标的 upArrow 和 downArrow 缓冲区(第 6、7 号缓冲区)。如果你想用其他指标,需要修改源码中 `CopyBuffer(indicatorHandle, 6, ...)` 这部分逻辑,改成你的指标的缓冲区索引和信号判断条件。
分阶段止损的 Phase1 和 Phase2 触发点怎么调?
Phase1ProfitTrigger 决定多快进入保本,默认 300 点适合黄金 M15。如果品种波动小(如 EURUSD)可以改成 30-50 点;Phase2ProfitTrigger 决定多深的利润才激进跟踪,默认 800 点只有大趋势才能达到。建议先在策略测试器里回测历史数据,看多少点后行情通常会延续,把触发点设在「趋势确认」的位置。
马丁格尔翻倍策略会不会爆仓?
有可能。连续亏损时手数翻倍,5 次亏损后是初始的 32 倍(0.01 → 0.32),如果账户余额不够或达到 MaxLot 上限,再亏一次可能触发强平。这个策略适合趋势延续概率高的场景,震荡行情中非常危险。保守做法是把 MaxLot 设为 InitialLot 的 4-8 倍,或者直接关闭翻倍(把源码里 `currentLotBuy * 2` 改成 `InitialLot`)。
适合在 M5 周期用吗?
不建议。M5 噪音大,摆动止损会频繁被触发,ATR 值也不稳定。代码里 SignalTimeframe 默认是 M15,这是作者测试过的最小周期。如果一定要用 M5,建议把 SwingLookback 调大到 20 以上、StopBuffer 加到 150-200 点,并在模拟盘测试至少一周。
反向信号平仓会不会太敏感?
取决于 HT 指标的稳定性。如果 HT 在趋势中途经常闪现反向箭头,CloseOnReverseSignal = true 会让你频繁离场。可以先在图表上手动加载 HT 指标,观察几天它的箭头是否可靠,如果假信号多就把这个参数改成 false,单纯依靠分阶段止损出场。

HT Phase Trailing EA with ATR Dynamic Stop Loss for MT5

An automated EA that trades HT indicator signals with 3-phase stop-loss strategy (swing → breakeven → aggressive ATR trailing). Features reverse-signal closing and martingale money management for trending markets.

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

评论

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

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