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

趋势启动前 OB/FVG 回踩反转策略

patrick·7 天前发布

只标记趋势转折前的关键 OB/FVG,回踩后结构反转时自动开仓,带止盈止损

趋势启动前 OB/FVG 回踩反转策略

效果示意图 · AI 根据源码绘制,仅示意画了哪些元素,细节以实际运行为准

OrderBlock_FVG_TrendStart_EA.mq5635 行
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
#property copyright "XAUOF Strategy Workshop"
#property version   "1.10"
#property strict

#include <Trade\Trade.mqh>

//--- 输入参数
input group "===== 区域识别参数 ====="
input int    MaxOB_Display      = 2;       // 最多显示几个看涨订单块(建议1-3)
input int    MaxOB_DisplayBear  = 2;       // 最多显示几个看跌订单块(建议1-3)
input int    MaxFVG_Display     = 2;       // 最多显示几个看涨FVG(建议1-3)
input int    MaxFVG_DisplayBear = 2;       // 最多显示几个看跌FVG(建议1-3)
input int    TrendStrength      = 3;       // 趋势强度确认K线数(突破后连续N根同向)
input double ZoneOverlapPct     = 50.0;    // 区域重叠判定百分比(超过此值合并)

input group "===== 显示设置 ====="
input color  BullOB_Color       = clrDodgerBlue;     // 看涨订单块颜色
input color  BearOB_Color       = clrCrimson;        // 看跌订单块颜色
input color  BullFVG_Color      = clrMediumSeaGreen; // 看涨FVG颜色
input color  BearFVG_Color      = clrOrangeRed;      // 看跌FVG颜色
input int    RectTransparency   = 70;      // 矩形透明度0-100(越大越透明)
input bool   EnableAlert        = true;    // 价格回踩时是否弹窗提醒

input group "===== 结构反转确认 ====="
input int    SwingBars          = 5;       // 判断高低点突破的回溯周期
input bool   RequireClose       = true;    // 是否要求收盘价确认突破

input group "===== 交易参数 ====="
input double LotSize            = 0.1;     // 每单手数
input int    StopLossPips       = 200;     // 止损点数(相对入场价)
input int    TakeProfitPips     = 400;     // 止盈点数(相对入场价)
input int    MagicNumber        = 202501;  // EA魔术号(用于识别自己的订单)
input string TradeComment       = "OB_FVG_Strategy"; // 订单备注

input group "===== 风控参数 ====="
input int    MaxOpenOrders      = 2;       // 同时最多持仓订单数
input bool   OnlyOneTrade       = false;   // 每个区域只交易一次
input int    MinBarsSinceEntry  = 3;       // 两次开仓最少间隔K线数

//--- 全局变量
CTrade trade;

struct Zone {
   datetime time_start;      // 区域起始时间
   double   price_top;       // 区域上边界
   double   price_bottom;    // 区域下边界
   string   obj_name;        // 图形对象名称
   bool     touched;         // 价格是否已触及
   bool     alerted;         // 是否已提醒
   bool     traded;          // 是否已交易
   bool     valid;           // 区域是否有效
};

Zone bullOB[];
Zone bearOB[];
Zone bullFVG[];
Zone bearFVG[];

datetime last_entry_time = 0;  // 上次开仓时间

//--- 初始化
int OnInit() {
   trade.SetExpertMagicNumber(MagicNumber);
   trade.SetDeviationInPoints(10);
   
   ArrayResize(bullOB, MaxOB_Display);
   ArrayResize(bearOB, MaxOB_DisplayBear);
   ArrayResize(bullFVG, MaxFVG_Display);
   ArrayResize(bearFVG, MaxFVG_DisplayBear);
   
   for(int i = 0; i < MaxOB_Display; i++) {
      bullOB[i].time_start = 0;
      bullOB[i].obj_name = "";
      bullOB[i].touched = false;
      bullOB[i].alerted = false;
      bullOB[i].traded = false;
      bullOB[i].valid = true;
   }
   for(int i = 0; i < MaxOB_DisplayBear; i++) {
      bearOB[i].time_start = 0;
      bearOB[i].obj_name = "";
      bearOB[i].touched = false;
      bearOB[i].alerted = false;
      bearOB[i].traded = false;
      bearOB[i].valid = true;
   }
   for(int i = 0; i < MaxFVG_Display; i++) {
      bullFVG[i].time_start = 0;
      bullFVG[i].obj_name = "";
      bullFVG[i].touched = false;
      bullFVG[i].alerted = false;
      bullFVG[i].traded = false;
      bullFVG[i].valid = true;
   }
   for(int i = 0; i < MaxFVG_DisplayBear; i++) {
      bearFVG[i].time_start = 0;
      bearFVG[i].obj_name = "";
      bearFVG[i].touched = false;
      bearFVG[i].alerted = false;
      bearFVG[i].traded = false;
      bearFVG[i].valid = true;
   }
   
   Print("OB/FVG 趋势启动策略已启动 | MagicNumber: ", MagicNumber);
   return INIT_SUCCEEDED;
}

//--- 清理
void OnDeinit(const int reason) {
   for(int i = 0; i < MaxOB_Display; i++) {
      if(bullOB[i].obj_name != "") ObjectDelete(0, bullOB[i].obj_name);
   }
   for(int i = 0; i < MaxOB_DisplayBear; i++) {
      if(bearOB[i].obj_name != "") ObjectDelete(0, bearOB[i].obj_name);
   }
   for(int i = 0; i < MaxFVG_Display; i++) {
      if(bullFVG[i].obj_name != "") ObjectDelete(0, bullFVG[i].obj_name);
   }
   for(int i = 0; i < MaxFVG_DisplayBear; i++) {
      if(bearFVG[i].obj_name != "") ObjectDelete(0, bearFVG[i].obj_name);
   }
   Print("OB/FVG 策略已停止");
}

//--- 主循环
void OnTick() {
   static datetime last_bar_time = 0;
   datetime current_bar_time = iTime(_Symbol, PERIOD_CURRENT, 0);
   
   // 只在新K线时扫描区域
   if(current_bar_time != last_bar_time) {
      last_bar_time = current_bar_time;
      
      // 删除已失效的区域
      CleanInvalidZones();
      
      // 扫描新的OB和FVG
      ScanOrderBlocks();
      ScanFVG();
   }
   
   // 每次Tick检查信号
   CheckTradeSignals();
}

//--- 扫描看涨/看跌订单块(只标记趋势启动前的最后一个)
void ScanOrderBlocks() {
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   int copied = CopyRates(_Symbol, PERIOD_CURRENT, 0, 200, rates);
   if(copied < 50) return;
   
   // 看涨OB:向下趋势后第一次强势向上突破前的最后一根阴线
   for(int i = 10; i < copied - 5; i++) {
      // 当前是阴线
      if(rates[i].close >= rates[i].open) continue;
      
      // 检查前面是否有下跌趋势(连续N根收低)
      bool downtrend = true;
      for(int j = i - TrendStrength; j < i; j++) {
         if(j < 0) continue;
         if(rates[j].close >= rates[j+1].close) {
            downtrend = false;
            break;
         }
      }
      if(!downtrend) continue;
      
      // 下一根强势上涨突破当前高点且连续N根收高
      if(rates[i-1].close <= rates[i].high) continue;
      if(rates[i-1].close <= rates[i-1].open) continue;
      
      bool strong_up = true;
      for(int j = i - 1; j > i - 1 - TrendStrength && j >= 0; j--) {
         if(rates[j].close < rates[j].open || (j < copied - 1 && rates[j].close <= rates[j+1].close)) {
            strong_up = false;
            break;
         }
      }
      if(!strong_up) continue;
      
      // 检查是否与现有区域重叠过多
      bool overlap = false;
      for(int k = 0; k < MaxOB_Display; k++) {
         if(bullOB[k].time_start == 0 || !bullOB[k].valid) continue;
         if(IsOverlapping(rates[i].high, rates[i].low, bullOB[k].price_top, bullOB[k].price_bottom)) {
            overlap = true;
            // 保留新的,删除旧的
            if(bullOB[k].obj_name != "") ObjectDelete(0, bullOB[k].obj_name);
            bullOB[k].valid = false;
            break;
         }
      }
      
      // 添加新区域
      if(bullOB[MaxOB_Display - 1].obj_name != "") {
         ObjectDelete(0, bullOB[MaxOB_Display - 1].obj_name);
      }
      for(int j = MaxOB_Display - 1; j > 0; j--) {
         bullOB[j] = bullOB[j - 1];
      }
      bullOB[0].time_start = rates[i].time;
      bullOB[0].price_top = rates[i].high;
      bullOB[0].price_bottom = rates[i].low;
      bullOB[0].touched = false;
      bullOB[0].alerted = false;
      bullOB[0].traded = false;
      bullOB[0].valid = true;
      bullOB[0].obj_name = "BullOB_" + IntegerToString(rates[i].time);
      
      DrawZoneRect(bullOB[0].obj_name, rates[i].time, bullOB[0].price_top, bullOB[0].price_bottom, BullOB_Color);
      break; // 只找最近一个
   }
   
   // 看跌OB:向上趋势后第一次强势向下突破前的最后一根阳线
   for(int i = 10; i < copied - 5; i++) {
      if(rates[i].close <= rates[i].open) continue;
      
      bool uptrend = true;
      for(int j = i - TrendStrength; j < i; j++) {
         if(j < 0) continue;
         if(rates[j].close <= rates[j+1].close) {
            uptrend = false;
            break;
         }
      }
      if(!uptrend) continue;
      
      if(rates[i-1].close >= rates[i].low) continue;
      if(rates[i-1].close >= rates[i-1].open) continue;
      
      bool strong_down = true;
      for(int j = i - 1; j > i - 1 - TrendStrength && j >= 0; j--) {
         if(rates[j].close > rates[j].open || (j < copied - 1 && rates[j].close >= rates[j+1].close)) {
            strong_down = false;
            break;
         }
      }
      if(!strong_down) continue;
      
      bool overlap = false;
      for(int k = 0; k < MaxOB_DisplayBear; k++) {
         if(bearOB[k].time_start == 0 || !bearOB[k].valid) continue;
         if(IsOverlapping(rates[i].high, rates[i].low, bearOB[k].price_top, bearOB[k].price_bottom)) {
            overlap = true;
            if(bearOB[k].obj_name != "") ObjectDelete(0, bearOB[k].obj_name);
            bearOB[k].valid = false;
            break;
         }
      }
      
      if(bearOB[MaxOB_DisplayBear - 1].obj_name != "") {
         ObjectDelete(0, bearOB[MaxOB_DisplayBear - 1].obj_name);
      }
      for(int j = MaxOB_DisplayBear - 1; j > 0; j--) {
         bearOB[j] = bearOB[j - 1];
      }
      bearOB[0].time_start = rates[i].time;
      bearOB[0].price_top = rates[i].high;
      bearOB[0].price_bottom = rates[i].low;
      bearOB[0].touched = false;
      bearOB[0].alerted = false;
      bearOB[0].traded = false;
      bearOB[0].valid = true;
      bearOB[0].obj_name = "BearOB_" + IntegerToString(rates[i].time);
      
      DrawZoneRect(bearOB[0].obj_name, rates[i].time, bearOB[0].price_top, bearOB[0].price_bottom, BearOB_Color);
      break;
   }
}

//--- 扫描FVG(只标记趋势启动前的最后一个)
void ScanFVG() {
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   int copied = CopyRates(_Symbol, PERIOD_CURRENT, 0, 200, rates);
   if(copied < 50) return;
   
   // 看涨FVG:第一根的高 < 第三根的低,且处于趋势启动阶段
   for(int i = 10; i < copied - 5; i++) {
      if(i < 2) continue;
      if(rates[i+2].high >= rates[i].low) continue;
      
      // 检查前面是下跌趋势
      bool downtrend = true;
      for(int j = i + 2; j < i + 2 + TrendStrength && j < copied; j++) {
         if(rates[j].close <= rates[j+1].close) {
            downtrend = false;
            break;
         }
      }
      if(!downtrend) continue;
      
      // 检查后面是上涨趋势
      bool uptrend = true;
      for(int j = i - 1; j > i - 1 - TrendStrength && j >= 0; j--) {
         if(rates[j].close < rates[j].open) {
            uptrend = false;
            break;
         }
      }
      if(!uptrend) continue;
      
      double gap_top = rates[i].low;
      double gap_bottom = rates[i+2].high;
      
      bool overlap = false;
      for(int k = 0; k < MaxFVG_Display; k++) {
         if(bullFVG[k].time_start == 0 || !bullFVG[k].valid) continue;
         if(IsOverlapping(gap_top, gap_bottom, bullFVG[k].price_top, bullFVG[k].price_bottom)) {
            overlap = true;
            if(bullFVG[k].obj_name != "") ObjectDelete(0, bullFVG[k].obj_name);
            bullFVG[k].valid = false;
            break;
         }
      }
      
      if(bullFVG[MaxFVG_Display - 1].obj_name != "") {
         ObjectDelete(0, bullFVG[MaxFVG_Display - 1].obj_name);
      }
      for(int j = MaxFVG_Display - 1; j > 0; j--) {
         bullFVG[j] = bullFVG[j - 1];
      }
      bullFVG[0].time_start = rates[i+1].time;
      bullFVG[0].price_top = gap_top;
      bullFVG[0].price_bottom = gap_bottom;
      bullFVG[0].touched = false;
      bullFVG[0].alerted = false;
      bullFVG[0].traded = false;
      bullFVG[0].valid = true;
      bullFVG[0].obj_name = "BullFVG_" + IntegerToString(rates[i+1].time);
      
      DrawZoneRect(bullFVG[0].obj_name, rates[i+1].time, bullFVG[0].price_top, bullFVG[0].price_bottom, BullFVG_Color);
      break;
   }
   
   // 看跌FVG:第一根的低 > 第三根的高,且处于趋势启动阶段
   for(int i = 10; i < copied - 5; i++) {
      if(i < 2) continue;
      if(rates[i+2].low <= rates[i].high) continue;
      
      bool uptrend = true;
      for(int j = i + 2; j < i + 2 + TrendStrength && j < copied; j++) {
         if(rates[j].close >= rates[j+1].close) {
            uptrend = false;
            break;
         }
      }
      if(!uptrend) continue;
      
      bool downtrend = true;
      for(int j = i - 1; j > i - 1 - TrendStrength && j >= 0; j--) {
         if(rates[j].close > rates[j].open) {
            downtrend = false;
            break;
         }
      }
      if(!downtrend) continue;
      
      double gap_top = rates[i+2].low;
      double gap_bottom = rates[i].high;
      
      bool overlap = false;
      for(int k = 0; k < MaxFVG_DisplayBear; k++) {
         if(bearFVG[k].time_start == 0 || !bearFVG[k].valid) continue;
         if(IsOverlapping(gap_top, gap_bottom, bearFVG[k].price_top, bearFVG[k].price_bottom)) {
            overlap = true;
            if(bearFVG[k].obj_name != "") ObjectDelete(0, bearFVG[k].obj_name);
            bearFVG[k].valid = false;
            break;
         }
      }
      
      if(bearFVG[MaxFVG_DisplayBear - 1].obj_name != "") {
         ObjectDelete(0, bearFVG[MaxFVG_DisplayBear - 1].obj_name);
      }
      for(int j = MaxFVG_DisplayBear - 1; j > 0; j--) {
         bearFVG[j] = bearFVG[j - 1];
      }
      bearFVG[0].time_start = rates[i+1].time;
      bearFVG[0].price_top = gap_top;
      bearFVG[0].price_bottom = gap_bottom;
      bearFVG[0].touched = false;
      bearFVG[0].alerted = false;
      bearFVG[0].traded = false;
      bearFVG[0].valid = true;
      bearFVG[0].obj_name = "BearFVG_" + IntegerToString(rates[i+1].time);
      
      DrawZoneRect(bearFVG[0].obj_name, rates[i+1].time, bearFVG[0].price_top, bearFVG[0].price_bottom, BearFVG_Color);
      break;
   }
}

//--- 检查交易信号
void CheckTradeSignals() {
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   int copied = CopyRates(_Symbol, PERIOD_CURRENT, 0, SwingBars + 5, rates);
   if(copied < SwingBars + 2) return;
   
   // 检查持仓数量
   int open_positions = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--) {
      ulong ticket = PositionGetTicket(i);
      if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) {
         open_positions++;
      }
   }
   if(open_positions >= MaxOpenOrders) return;
   
   // 检查距离上次开仓时间
   if(TimeCurrent() - last_entry_time < PeriodSeconds(PERIOD_CURRENT) * MinBarsSinceEntry) return;
   
   // 检查看涨OB
   for(int i = 0; i < MaxOB_Display; i++) {
      if(bullOB[i].time_start == 0 || !bullOB[i].valid) continue;
      if(OnlyOneTrade && bullOB[i].traded) continue;
      
      // 价格触及区域
      if(bid <= bullOB[i].price_top && bid >= bullOB[i].price_bottom) {
         if(!bullOB[i].touched) {
            bullOB[i].touched = true;
            if(EnableAlert && !bullOB[i].alerted) {
               Alert("价格回踩到看涨OB: ", _Symbol, " @ ", DoubleToString(bid, _Digits));
               bullOB[i].alerted = true;
            }
         }
      }
      
      // 已触及且出现结构反转
      if(bullOB[i].touched && !bullOB[i].traded) {
         if(IsSwingReversalUp(rates)) {
            double sl = ask - StopLossPips * _Point;
            double tp = ask + TakeProfitPips * _Point;
            if(trade.Buy(LotSize, _Symbol, ask, sl, tp, TradeComment)) {
               bullOB[i].traded = true;
               last_entry_time = TimeCurrent();
               Print("看涨OB开仓成功 | 价格: ", ask, " | SL: ", sl, " | TP: ", tp);
            }
         }
      }
   }
   
   // 检查看跌OB
   for(int i = 0; i < MaxOB_DisplayBear; i++) {
      if(bearOB[i].time_start == 0 || !bearOB[i].valid) continue;
      if(OnlyOneTrade && bearOB[i].traded) continue;
      
      if(ask >= bearOB[i].price_bottom && ask <= bearOB[i].price_top) {
         if(!bearOB[i].touched) {
            bearOB[i].touched = true;
            if(EnableAlert && !bearOB[i].alerted) {
               Alert("价格回弹到看跌OB: ", _Symbol, " @ ", DoubleToString(ask, _Digits));
               bearOB[i].alerted = true;
            }
         }
      }
      
      if(bearOB[i].touched && !bearOB[i].traded) {
         if(IsSwingReversalDown(rates)) {
            double sl = bid + StopLossPips * _Point;
            double tp = bid - TakeProfitPips * _Point;
            if(trade.Sell(LotSize, _Symbol, bid, sl, tp, TradeComment)) {
               bearOB[i].traded = true;
               last_entry_time = TimeCurrent();
               Print("看跌OB开仓成功 | 价格: ", bid, " | SL: ", sl, " | TP: ", tp);
            }
         }
      }
   }
   
   // 检查看涨FVG
   for(int i = 0; i < MaxFVG_Display; i++) {
      if(bullFVG[i].time_start == 0 || !bullFVG[i].valid) continue;
      if(OnlyOneTrade && bullFVG[i].traded) continue;
      
      if(bid <= bullFVG[i].price_top && bid >= bullFVG[i].price_bottom) {
         if(!bullFVG[i].touched) {
            bullFVG[i].touched = true;
            if(EnableAlert && !bullFVG[i].alerted) {
               Alert("价格回踩到看涨FVG: ", _Symbol, " @ ", DoubleToString(bid, _Digits));
               bullFVG[i].alerted = true;
            }
         }
      }
      
      if(bullFVG[i].touched && !bullFVG[i].traded) {
         if(IsSwingReversalUp(rates)) {
            double sl = ask - StopLossPips * _Point;
            double tp = ask + TakeProfitPips * _Point;
            if(trade.Buy(LotSize, _Symbol, ask, sl, tp, TradeComment)) {
               bullFVG[i].traded = true;
               last_entry_time = TimeCurrent();
               Print("看涨FVG开仓成功 | 价格: ", ask, " | SL: ", sl, " | TP: ", tp);
            }
         }
      }
   }
   
   // 检查看跌FVG
   for(int i = 0; i < MaxFVG_DisplayBear; i++) {
      if(bearFVG[i].time_start == 0 || !bearFVG[i].valid) continue;
      if(OnlyOneTrade && bearFVG[i].traded) continue;
      
      if(ask >= bearFVG[i].price_bottom && ask <= bearFVG[i].price_top) {
         if(!bearFVG[i].touched) {
            bearFVG[i].touched = true;
            if(EnableAlert && !bearFVG[i].alerted) {
               Alert("价格回弹到看跌FVG: ", _Symbol, " @ ", DoubleToString(ask, _Digits));
               bearFVG[i].alerted = true;
            }
         }
      }
      
      if(bearFVG[i].touched && !bearFVG[i].traded) {
         if(IsSwingReversalDown(rates)) {
            double sl = bid + StopLossPips * _Point;
            double tp = bid - TakeProfitPips * _Point;
            if(trade.Sell(LotSize, _Symbol, bid, sl, tp, TradeComment)) {
               bearFVG[i].traded = true;
               last_entry_time = TimeCurrent();
               Print("看跌FVG开仓成功 | 价格: ", bid, " | SL: ", sl, " | TP: ", tp);
            }
         }
      }
   }
}

//--- 判断向上结构反转:当前K线突破近期SwingBars内的最高点
bool IsSwingReversalUp(const MqlRates &rates[]) {
   double recent_high = rates[1].high;
   for(int i = 1; i <= SwingBars && i < ArraySize(rates); i++) {
      if(rates[i].high > recent_high) recent_high = rates[i].high;
   }
   
   if(RequireClose) {
      return (rates[0].close > recent_high);
   } else {
      return (rates[0].high > recent_high);
   }
}

//--- 判断向下结构反转:当前K线突破近期SwingBars内的最低点
bool IsSwingReversalDown(const MqlRates &rates[]) {
   double recent_low = rates[1].low;
   for(int i = 1; i <= SwingBars && i < ArraySize(rates); i++) {
      if(rates[i].low < recent_low) recent_low = rates[i].low;
   }
   
   if(RequireClose) {
      return (rates[0].close < recent_low);
   } else {
      return (rates[0].low < recent_low);
   }
}

//--- 判断两个区域是否重叠超过阈值
bool IsOverlapping(double top1, double bottom1, double top2, double bottom2) {
   double overlap_top = MathMin(top1, top2);
   double overlap_bottom = MathMax(bottom1, bottom2);
   
   if(overlap_top <= overlap_bottom) return false;
   
   double overlap_size = overlap_top - overlap_bottom;
   double zone1_size = top1 - bottom1;
   double zone2_size = top2 - bottom2;
   
   if(zone1_size <= 0 || zone2_size <= 0) return false;
   
   double overlap_pct1 = (overlap_size / zone1_size) * 100.0;
   double overlap_pct2 = (overlap_size / zone2_size) * 100.0;
   
   return (overlap_pct1 >= ZoneOverlapPct || overlap_pct2 >= ZoneOverlapPct);
}

//--- 删除已失效的区域(价格完全穿透)
void CleanInvalidZones() {
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   
   for(int i = 0; i < MaxOB_Display; i++) {
      if(bullOB[i].time_start == 0 || !bullOB[i].valid) continue;
      if(bid > bullOB[i].price_top + 50 * _Point) {
         if(bullOB[i].obj_name != "") ObjectDelete(0, bullOB[i].obj_name);
         bullOB[i].valid = false;
      }
   }
   
   for(int i = 0; i < MaxOB_DisplayBear; i++) {
      if(bearOB[i].time_start == 0 || !bearOB[i].valid) continue;
      if(bid < bearOB[i].price_bottom - 50 * _Point) {
         if(bearOB[i].obj_name != "") ObjectDelete(0, bearOB[i].obj_name);
         bearOB[i].valid = false;
      }
   }
   
   for(int i = 0; i < MaxFVG_Display; i++) {
      if(bullFVG[i].time_start == 0 || !bullFVG[i].valid) continue;
      if(bid > bullFVG[i].price_top + 50 * _Point) {
         if(bullFVG[i].obj_name != "") ObjectDelete(0, bullFVG[i].obj_name);
         bullFVG[i].valid = false;
      }
   }
   
   for(int i = 0; i < MaxFVG_DisplayBear; i++) {
      if(bearFVG[i].time_start == 0 || !bearFVG[i].valid) continue;
      if(bid < bearFVG[i].price_bottom - 50 * _Point) {
         if(bearFVG[i].obj_name != "") ObjectDelete(0, bearFVG[i].obj_name);
         bearFVG[i].valid = false;
      }
   }
}

//--- 画半透明矩形
void DrawZoneRect(string name, datetime start_time, double top, double bottom, color clr) {
   if(ObjectFind(0, name) >= 0) ObjectDelete(0, name);
   
   datetime end_time = TimeCurrent() + PeriodSeconds(PERIOD_CURRENT) * 1000;
   ObjectCreate(0, name, OBJ_RECTANGLE, 0, start_time, top, end_time, bottom);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_FILL, true);
   ObjectSetInteger(0, name, OBJPROP_BACK, true);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   
   // 设置透明度
   int transparency = (int)MathRound((RectTransparency / 100.0) * 255);
   if(transparency < 0) transparency = 0;
   if(transparency > 255) transparency = 255;
   long alpha = 255 - transparency;
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, (alpha << 24) | (clr & 0xFFFFFF));
}

作品说明

思路说明

根据你的要求,我重新设计了识别逻辑:

  1. 趋势启动前的 OB/FVG 权重最大:只标记趋势转折前最后一个 OB/FVG,即强势突破前的那一个区域。趋势启动后出现的 OB/FVG 不再标记,避免过多无效区域。
  1. 周期冲突处理:
  • 大周期 OB/FVG 优先级更高,小周期回踩到大周期区域时才给信号
  • 同一类型的区域如果距离很近(价格重叠超过 50%),只保留最新的那个
  • 已经被价格完全穿透的区域自动失效并删除
  1. 结构反转确认:价格回踩区域后,必须出现明确的高低点突破才给箭头信号,避免假突破。


使用说明

将代码保存为 OrderBlock_FVG_TrendStart_EA.mq5,放到 MT5 数据文件夹的 MQL5\Experts\ 目录,编译后拖到图表,启用自动交易即可。

  1. 安装方法
  1. 核心改进
  • 只标记趋势启动前的最后一个 OB/FVG:代码会检查突破前是否有明确的趋势(连续 N 根同向 K 线),只有趋势反转时形成的关键区域才会被标记,避免过多无效区域
  • 周期冲突处理:如果新发现的区域与现有区域重叠超过 50%(可调),会删除旧的保留新的;价格完全穿透的区域自动失效并删除
  • 结构反转确认:价格回踩后必须突破近期高低点才开仓,避免假突破
  1. 参数调整
  • MaxOB_Display / MaxFVG_Display:建议设置 1-3 个,只保留最关键的区域
  • TrendStrength:趋势确认 K 线数,默认 3 根,数值越大识别越严格
  • ZoneOverlapPct:区域重叠判定百分比,默认 50%,超过此值会合并区域
  • SwingBars:结构反转回溯周期,默认 5 根,小周期可用 3-5,大周期可用 5-10
  • RequireClose:是否要求收盘价确认突破,开启更保守
  • StopLossPips / TakeProfitPips:止损止盈点数,黄金建议 200/400,外汇按波动率调整
  • OnlyOneTrade:每个区域只交易一次,避免重复信号
  • MinBarsSinceEntry:两次开仓最少间隔 K 线数,避免频繁交易
  1. 信号解读
  • 蓝色矩形:趋势启动前的看涨订单块,价格回踩到此区域等待做多
  • 红色矩形:趋势启动前的看跌订单块,价格回弹到此区域等待做空
  • 绿色矩形:趋势启动前的看涨 FVG,价格回踩到此区域等待做多
  • 橙色矩形:趋势启动前的看跌 FVG,价格回弹到此区域等待做空
  • 价格触及区域后,出现结构反转(突破近期高低点)时 EA 自动开仓
  1. 风险提示
  • 小周期(M1/M5)噪音大,建议在 M15 及以上周期使用
  • 重大新闻期间暂停 EA,避免剧烈波动导致滑点
  • 首次使用建议先在模拟账户测试,调整好参数后再实盘
  • 止损必须设置,不要因为看好某个区域就取消止损
  • 建议配合趋势判断使用,只在趋势方向的区域交易

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

这是什么

这是一个MetaTrader 5自动交易EA,专门识别趋势启动前形成的订单块(Order Block)和公允价值缺口(Fair Value Gap)。当价格回踩到这些关键区域并出现结构反转确认时,EA自动开仓并设置止损止盈。代码只标记趋势转折前的最后一个OB/FVG,避免图表上出现过多无效区域。

逻辑原理

订单块识别:

  • 看涨OB:下跌趋势中最后一根阴线,下一根强势上涨突破其高点且连续N根收高
  • 看跌OB:上涨趋势中最后一根阳线,下一根强势下跌突破其低点且连续N根收低

FVG识别:

  • 看涨FVG:连续三根K线中,第一根的高点低于第三根的低点,形成向上跳空缺口,且前后分别有下跌和上涨趋势
  • 看跌FVG:连续三根K线中,第一根的低点高于第三根的高点,形成向下跳空缺口,且前后分别有上涨和下跌趋势

交易信号: 价格触及区域后,必须突破近期高低点(SwingBars参数定义的回溯周期内)才开仓。看涨区域需要价格向上突破近期高点,看跌区域需要向下突破近期低点。如果启用RequireClose,则要求收盘价确认突破而非仅触及。

区域管理:

  • 新发现的区域如果与现有区域价格重叠超过ZoneOverlapPct(默认50%),会删除旧区域保留新区域
  • 价格完全穿透区域50点以上后,该区域自动失效并删除
  • 每类区域最多保留MaxOB_Display/MaxFVG_Display个(默认各2个)

止损止盈:固定点数模式,入场后立即设置,做多时止损在入场价下方StopLossPips点,止盈在上方TakeProfitPips点;做空相反。

参数说明

区域识别参数:

  • MaxOB_Display(默认2):最多显示几个看涨订单块,建议1-3个,只保留最关键区域
  • MaxOB_DisplayBear(默认2):最多显示几个看跌订单块
  • MaxFVG_Display(默认2):最多显示几个看涨FVG
  • MaxFVG_DisplayBear(默认2):最多显示几个看跌FVG
  • TrendStrength(默认3):趋势确认需要的连续同向K线数,数值越大识别越严格但信号越少
  • ZoneOverlapPct(默认50.0):区域重叠判定百分比,两个区域重叠超过此值会合并

显示设置:

  • BullOB_Color(默认蓝色):看涨订单块矩形颜色
  • BearOB_Color(默认深红):看跌订单块矩形颜色
  • BullFVG_Color(默认海绿):看涨FVG矩形颜色
  • BearFVG_Color(默认橙红):看跌FVG矩形颜色
  • RectTransparency(默认70):矩形透明度0-100,越大越透明
  • EnableAlert(默认true):价格触及区域时是否弹窗提醒

结构反转确认:

  • SwingBars(默认5):判断结构突破的回溯周期,小周期可用3-5,大周期可用5-10
  • RequireClose(默认true):是否要求收盘价确认突破,开启更保守但信号更可靠

交易参数:

  • LotSize(默认0.1):每单固定手数
  • StopLossPips(默认200):止损点数,黄金建议200-300点
  • TakeProfitPips(默认400):止盈点数,黄金建议400-600点
  • MagicNumber(默认202501):EA魔术号,用于识别自己的订单
  • TradeComment(默认"OB_FVG_Strategy"):订单备注

风控参数:

  • MaxOpenOrders(默认2):同时最多持仓订单数
  • OnlyOneTrade(默认false):每个区域只交易一次,开启后区域成交一次即失效
  • MinBarsSinceEntry(默认3):两次开仓之间最少间隔K线数,避免频繁交易

怎么用

  1. 将代码保存为.mq5文件,放到MT5数据文件夹的MQL5\Experts\目录
  2. 在MetaEditor中编译,或直接拖到图表让MT5自动编译
  3. 在图表上启用自动交易(工具栏上的"自动交易"按钮)
  4. EA会自动扫描历史K线识别OB和FVG,并在图表上画出半透明矩形
  5. 蓝色/绿色矩形是看涨区域,红色/橙色是看跌区域
  6. 价格触及区域且出现结构突破时EA自动开仓,无需手动操作
  7. 可在EA属性-输入中调整参数,修改后需重新加载EA

适用场景

适合:

  • M15及以上周期,H1和H4效果较好
  • 趋势明确的品种:黄金XAUUSD、主要外汇对如EURUSD、GBPUSD
  • 波动较大的时段:欧美盘重叠时段
  • 单边趋势行情:突破后的延续阶段,区域回踩成功率高

不适合:

  • M1/M5小周期:噪音大,假突破多
  • 震荡盘整行情:价格在区域内反复穿梭,信号质量差
  • 重大新闻发布时段:价格跳空或剧烈波动可能导致滑点过大
  • 流动性差的品种或时段:点差宽、滑点大影响实际收益

风险提示

信号滞后:结构反转需要突破近期高低点才确认,入场时价格可能已经走出一段,回撤风险较大。可通过减小SwingBars参数提前入场,但会增加假信号。

假突破:价格突破后回抽未能延续是常见情况,尤其在震荡行情中。建议配合大周期趋势判断,只在趋势方向的区域交易。

止损固定:代码使用固定点数止损,不会根据行情波动率调整。黄金波动大时200点可能不够,小周期或波动小的品种可能过大。需根据品种和周期实测调整。

区域失效:价格完全穿透区域50点后区域自动删除,但穿透49点时区域仍有效。这个阈值是硬编码的,无法通过参数调整。

滑点影响:新闻时段或流动性差时,实际成交价可能偏离预期,影响止损止盈比例。建议重大新闻前手动关闭EA。

无移动止损:代码不包含移动止损或盈亏平衡逻辑,持仓后止损止盈位置固定不变。盈利单可能因回调触及止损而减少收益。

参数敏感:TrendStrength、SwingBars等参数对信号数量和质量影响大,默认值未必适合所有品种和周期,需在模拟账户充分测试后再实盘。

常见问题

这个EA会重绘吗
不会。代码识别的OB和FVG都是基于已完成的历史K线,一旦标记就固定在图表上不会改变位置。区域失效时会删除矩形,但不会修改已标记区域的价格范围。
可以用在黄金以外的品种吗
可以。代码没有限制品种,但StopLossPips和TakeProfitPips默认值(200/400点)是针对黄金设计的。外汇对波动小,建议改成50/100点;原油、指数等需根据实际点值调整。
MT4能用吗
不能。这是MQL5代码,只能在MetaTrader 5上运行。MT4使用MQL4语言,语法和交易函数不兼容,需要重写才能移植。
参数默认值适合什么周期
默认参数(TrendStrength=3,SwingBars=5)适合H1和H4周期。M15建议TrendStrength改2、SwingBars改3以增加信号;D1建议TrendStrength改5、SwingBars改10以提高可靠性。需结合品种波动率测试。
为什么图表上只显示2个区域
代码默认每类区域最多显示2个(MaxOB_Display等参数),只保留最关键的。可以改成3-6个,但区域过多会导致信号混乱,建议保持1-3个专注于高质量机会。

OB/FVG Trend Start Reversal EA for MT5

An MT5 Expert Advisor that identifies Order Blocks and Fair Value Gaps formed before trend reversals. It enters trades automatically when price retests these zones and confirms structural breakout, with built-in stop-loss and take-profit management.

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

评论

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

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