MT5脚本 / 工具 三创编译通过 · EX5 23KB 自动发布 2 0

Smart Money 供需区 + 买卖信号指标

chenkun5629·1 小时前发布

自动识别供需区矩形,回踩支撑/触及阻力时显示买卖箭头及止盈止损线

相对上一版改了:把右边的代码,在这基础上增加买卖提示点,以及止盈止损点;刚才这份代码用 MetaEditor 编译通过了,但按 MT5/MT4 真实画图规则会画错。用户把它装到自己的终端上,看到的箭头、均线会和说明里说的完全不一样(典型症状:金叉指标变成每根 K 线都插箭头、线和 K 线错位、新 K 线不更新)。 问题: - 循环上界是 i < rates_total,循环体里却读了 buf[i+1] / close[i+1]。i 走到最后一根时 i+1 越界,终端会报 array out of range 并把指标停掉。读 i+1 必须写成 i < rates_total - 1。 【必须按这个骨架写 On

SmartMoneyZonesWithSignals.mq5566
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
// 基于 XAUOF 社区作品 #15《一键平仓 / 一键保本工具(二创)》(作者 123456789)二次创作
// 本次改动:
// 1. 新增买入/卖出信号箭头:价格触及需求区且反弹显示买入,触及供给区且回落显示卖出
// 2. 新增止盈止损标注:自动绘制虚线和文字标签显示建议的 SL/TP 位置
// 3. 新增信号确认参数:可设置反弹/回落的最小幅度,避免假信号
// 4. 修复索引方向:移除 ArraySetAsSeries,使用正确的 MT5 正向索引规则
//+------------------------------------------------------------------+
#property copyright "XAUOF 指标工坊 - 基于社区作品 #15 二创"
#property version   "2.01"
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2

//--- 买入信号箭头
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrLime
#property indicator_width1  3
#property indicator_label1  "买入信号"

//--- 卖出信号箭头
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrRed
#property indicator_width2  3
#property indicator_label2  "卖出信号"

//+------------------------------------------------------------------+
//| 输入参数                                                           |
//+------------------------------------------------------------------+
input int    InpSwingLength = 5;              // 摆动识别周期(左右各多少根K线)
input int    InpZoneThickness = 30;           // 供需区厚度(单位:点数)
input int    InpZoneExtendBars = 50;          // 区域向右延伸多少根K线
input color  InpDemandColor = clrDodgerBlue;  // 需求区颜色(支撑)
input color  InpSupplyColor = clrTomato;      // 供给区颜色(阻力)
input int    InpZoneTransparency = 85;        // 区域透明度(0-100,越大越透明)
input bool   InpShowLabels = true;            // 是否显示供需区标注
input int    InpMaxZones = 10;                // 最多显示多少个区域(0=不限制)
input bool   InpDeleteOldZones = true;        // 价格触及后是否删除旧区域

//--- 信号相关参数
input int    InpSignalMinPips = 5;            // 信号确认最小点数(反弹/回落幅度)
input bool   InpShowSignals = true;           // 是否显示买卖信号箭头
input int    InpArrowOffset = 10;             // 信号箭头距离K线的点数
input bool   InpShowSLTP = true;              // 是否显示止盈止损线
input double InpRiskRewardRatio = 2.0;        // 止盈止损比例(盈亏比,例如 2.0 表示止盈是止损的2倍)
input color  InpSLColor = clrOrangeRed;       // 止损线颜色
input color  InpTPColor = clrSpringGreen;     // 止盈线颜色

//--- 全局变量
struct ZoneInfo
{
   string   name;          // 对象名称
   double   price_top;     // 区域上边界
   double   price_bottom;  // 区域下边界
   datetime time_start;    // 开始时间
   bool     is_demand;     // true=需求区,false=供给区
   bool     touched;       // 是否已被触及
};

ZoneInfo g_zones[];        // 存储所有区域
int      g_zone_count = 0; // 当前区域数量
string   g_prefix = "SMZ_"; // 对象名称前缀
string   g_signal_prefix = "SIG_"; // 信号对象前缀

//--- 指标缓冲区
double BuySignalBuffer[];   // 买入信号箭头
double SellSignalBuffer[];  // 卖出信号箭头

//+------------------------------------------------------------------+
//| 初始化函数                                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- 检查参数
   if(InpSwingLength < 1)
   {
      Print("错误:摆动周期必须 >= 1");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   if(InpRiskRewardRatio <= 0)
   {
      Print("错误:盈亏比必须大于 0");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   //--- 绑定指标缓冲区
   SetIndexBuffer(0, BuySignalBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, SellSignalBuffer, INDICATOR_DATA);
   
   //--- 设置箭头样式
   PlotIndexSetInteger(0, PLOT_ARROW, 233);  // 上箭头
   PlotIndexSetInteger(1, PLOT_ARROW, 234);  // 下箭头
   
   //--- 设置空值为 EMPTY_VALUE(必须,否则 0 会被当作有效价格画出来)
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   
   //--- 初始化缓冲区为空值
   ArrayInitialize(BuySignalBuffer, EMPTY_VALUE);
   ArrayInitialize(SellSignalBuffer, EMPTY_VALUE);
   
   //--- 清理旧对象
   DeleteAllObjects();
   
   //--- 初始化数组
   ArrayResize(g_zones, 0);
   g_zone_count = 0;
   
   Print("Smart Money 供需区 + 信号指标已加载");
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| 反初始化函数                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   //--- 清理所有绘制的对象
   DeleteAllObjects();
}

//+------------------------------------------------------------------+
//| 主计算函数                                                         |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   //--- 需要至少有足够的K线
   if(rates_total < InpSwingLength * 2 + 10)
      return 0;
   
   //--- 首次运行时初始化缓冲区和扫描历史供需区
   if(prev_calculated == 0)
   {
      ArrayInitialize(BuySignalBuffer, EMPTY_VALUE);
      ArrayInitialize(SellSignalBuffer, EMPTY_VALUE);
      
      //--- 扫描历史K线识别供需区(从老到新,注意循环上界要留出 InpSwingLength 避免越界)
      for(int i = InpSwingLength + 1; i < rates_total - InpSwingLength - 1; i++)
      {
         //--- 检查摆动高点(供给区)
         bool is_swing_high = true;
         for(int j = 1; j <= InpSwingLength; j++)
         {
            if(high[i] <= high[i - j] || high[i] <= high[i + j])
            {
               is_swing_high = false;
               break;
            }
         }
         
         if(is_swing_high)
         {
            double zone_top = high[i];
            double zone_bottom = zone_top - InpZoneThickness * _Point;
            if(!ZoneExists(zone_top, zone_bottom, time[i]))
            {
               CreateZone(time[i], zone_top, zone_bottom, false);
            }
         }
         
         //--- 检查摆动低点(需求区)
         bool is_swing_low = true;
         for(int j = 1; j <= InpSwingLength; j++)
         {
            if(low[i] >= low[i - j] || low[i] >= low[i + j])
            {
               is_swing_low = false;
               break;
            }
         }
         
         if(is_swing_low)
         {
            double zone_bottom = low[i];
            double zone_top = zone_bottom + InpZoneThickness * _Point;
            if(!ZoneExists(zone_top, zone_bottom, time[i]))
            {
               CreateZone(time[i], zone_top, zone_bottom, true);
            }
         }
      }
   }
   
   //--- 计算信号扫描起点(标准骨架:从 prev_calculated-1 开始,首次运行从 1 开始)
   int start = (prev_calculated > 1) ? prev_calculated - 1 : 1;
   
   //--- 逐根K线检测信号
   for(int i = start; i < rates_total && !IsStopped(); i++)
   {
      //--- 默认无信号(每根K线先初始化为 EMPTY_VALUE)
      BuySignalBuffer[i] = EMPTY_VALUE;
      SellSignalBuffer[i] = EMPTY_VALUE;
      
      //--- 至少需要前一根K线来判断反弹/回落(i < 1 时跳过)
      if(i < 1) continue;
      
      //--- 检查是否有买入信号(价格回踩需求区且反弹)
      if(InpShowSignals)
      {
         ZoneInfo nearest_demand;
         if(FindNearestZone(close[i], true, nearest_demand))
         {
            //--- 判断当前K线是否在需求区内或刚离开
            bool in_zone = (low[i] <= nearest_demand.price_top && low[i] >= nearest_demand.price_bottom);
            bool just_left = (low[i-1] <= nearest_demand.price_top && close[i] > nearest_demand.price_top);
            
            if(in_zone || just_left)
            {
               //--- 检查是否是反弹K线(阳线且幅度足够)
               bool is_bullish = close[i] > open[i];
               double bounce_pips = (close[i] - low[i]) / _Point;
               
               if(is_bullish && bounce_pips >= InpSignalMinPips)
               {
                  //--- 只在这根K线显示买入箭头
                  BuySignalBuffer[i] = low[i] - InpArrowOffset * _Point;
                  
                  //--- 绘制止盈止损线
                  if(InpShowSLTP)
                  {
                     double sl_price = nearest_demand.price_bottom - 2 * _Point; // 止损设在需求区下方
                     double sl_pips = (close[i] - sl_price) / _Point;
                     double tp_price = close[i] + sl_pips * InpRiskRewardRatio * _Point; // 按盈亏比计算止盈
                     
                     DrawSLTPLines(time[i], close[i], sl_price, tp_price, true);
                  }
               }
            }
         }
         
         //--- 检查是否有卖出信号(价格触及供给区且回落)
         ZoneInfo nearest_supply;
         if(FindNearestZone(close[i], false, nearest_supply))
         {
            bool in_zone = (high[i] >= nearest_supply.price_bottom && high[i] <= nearest_supply.price_top);
            bool just_left = (high[i-1] >= nearest_supply.price_bottom && close[i] < nearest_supply.price_bottom);
            
            if(in_zone || just_left)
            {
               //--- 检查是否是回落K线(阴线且幅度足够)
               bool is_bearish = close[i] < open[i];
               double drop_pips = (high[i] - close[i]) / _Point;
               
               if(is_bearish && drop_pips >= InpSignalMinPips)
               {
                  //--- 只在这根K线显示卖出箭头
                  SellSignalBuffer[i] = high[i] + InpArrowOffset * _Point;
                  
                  //--- 绘制止盈止损线
                  if(InpShowSLTP)
                  {
                     double sl_price = nearest_supply.price_top + 2 * _Point;
                     double sl_pips = (sl_price - close[i]) / _Point;
                     double tp_price = close[i] - sl_pips * InpRiskRewardRatio * _Point;
                     
                     DrawSLTPLines(time[i], close[i], sl_price, tp_price, false);
                  }
               }
            }
         }
      }
   }
   
   //--- 检查价格是否触及区域(可选:触及后删除)
   if(InpDeleteOldZones && rates_total > 0)
   {
      CheckZonesTouched(close[rates_total - 1]);
   }
   
   return rates_total;
}

//+------------------------------------------------------------------+
//| 创建供需区矩形                                                     |
//+------------------------------------------------------------------+
void CreateZone(datetime start_time, double price_top, double price_bottom, bool is_demand)
{
   //--- 检查最大区域数量限制
   if(InpMaxZones > 0 && g_zone_count >= InpMaxZones)
   {
      DeleteOldestZone();
   }
   
   //--- 生成唯一对象名
   string obj_name = g_prefix + (is_demand ? "D_" : "S_") + TimeToString(start_time, TIME_DATE | TIME_SECONDS);
   
   //--- 计算结束时间(向右延伸)
   datetime end_time = start_time + InpZoneExtendBars * PeriodSeconds();
   
   //--- 创建矩形对象
   if(ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0, start_time, price_top, end_time, price_bottom))
   {
      color zone_color = is_demand ? InpDemandColor : InpSupplyColor;
      
      ObjectSetInteger(0, obj_name, OBJPROP_COLOR, zone_color);
      ObjectSetInteger(0, obj_name, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, obj_name, OBJPROP_FILL, true);
      ObjectSetInteger(0, obj_name, OBJPROP_BACK, true);
      ObjectSetInteger(0, obj_name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, obj_name, OBJPROP_HIDDEN, true);
      
      //--- 设置透明度
      int alpha = (int)MathRound(255 * (100 - InpZoneTransparency) / 100.0);
      if(alpha < 0) alpha = 0;
      if(alpha > 255) alpha = 255;
      long color_with_alpha = (long)zone_color | ((long)alpha << 24);
      ObjectSetInteger(0, obj_name, OBJPROP_BGCOLOR, color_with_alpha);
      
      //--- 保存到数组
      int idx = g_zone_count;
      ArrayResize(g_zones, g_zone_count + 1);
      g_zones[idx].name = obj_name;
      g_zones[idx].price_top = price_top;
      g_zones[idx].price_bottom = price_bottom;
      g_zones[idx].time_start = start_time;
      g_zones[idx].is_demand = is_demand;
      g_zones[idx].touched = false;
      g_zone_count++;
      
      //--- 添加文字标注
      if(InpShowLabels)
      {
         string label_name = obj_name + "_LBL";
         double label_price = is_demand ? price_bottom : price_top;
         string label_text = is_demand ? "Demand" : "Supply";
         
         if(ObjectCreate(0, label_name, OBJ_TEXT, 0, start_time, label_price))
         {
            ObjectSetString(0, label_name, OBJPROP_TEXT, label_text);
            ObjectSetInteger(0, label_name, OBJPROP_COLOR, zone_color);
            ObjectSetInteger(0, label_name, OBJPROP_FONTSIZE, 8);
            ObjectSetInteger(0, label_name, OBJPROP_ANCHOR, is_demand ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER);
            ObjectSetInteger(0, label_name, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, label_name, OBJPROP_HIDDEN, true);
            ObjectSetInteger(0, label_name, OBJPROP_BACK, false);
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 查找最近的供需区                                                   |
//+------------------------------------------------------------------+
bool FindNearestZone(double current_price, bool is_demand, ZoneInfo &zone_out)
{
   double nearest_distance = DBL_MAX;
   bool found = false;
   
   for(int i = 0; i < g_zone_count; i++)
   {
      //--- 只查找对应类型的区域
      if(g_zones[i].is_demand != is_demand)
         continue;
      
      //--- 跳过已触及的区域
      if(g_zones[i].touched && InpDeleteOldZones)
         continue;
      
      //--- 计算距离
      double distance;
      if(is_demand)
      {
         //--- 需求区:计算价格到区域上边界的距离(价格应该在上方)
         distance = current_price - g_zones[i].price_top;
      }
      else
      {
         //--- 供给区:计算区域下边界到价格的距离(价格应该在下方)
         distance = g_zones[i].price_bottom - current_price;
      }
      
      //--- 只考虑合理方向的区域,并找最近的(允许价格稍微进入区域内)
      if(distance > -InpZoneThickness * _Point && distance < nearest_distance)
      {
         nearest_distance = distance;
         zone_out = g_zones[i];
         found = true;
      }
   }
   
   return found;
}

//+------------------------------------------------------------------+
//| 绘制止盈止损线                                                     |
//+------------------------------------------------------------------+
void DrawSLTPLines(datetime signal_time, double entry_price, double sl_price, double tp_price, bool is_buy)
{
   string base_name = g_signal_prefix + TimeToString(signal_time, TIME_DATE | TIME_SECONDS);
   
   //--- 止损线
   string sl_name = base_name + "_SL";
   if(ObjectCreate(0, sl_name, OBJ_TREND, 0, signal_time, sl_price, signal_time + 30 * PeriodSeconds(), sl_price))
   {
      ObjectSetInteger(0, sl_name, OBJPROP_COLOR, InpSLColor);
      ObjectSetInteger(0, sl_name, OBJPROP_STYLE, STYLE_DASH);
      ObjectSetInteger(0, sl_name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, sl_name, OBJPROP_RAY_RIGHT, true);
      ObjectSetInteger(0, sl_name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, sl_name, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, sl_name, OBJPROP_BACK, true);
   }
   
   //--- 止盈线
   string tp_name = base_name + "_TP";
   if(ObjectCreate(0, tp_name, OBJ_TREND, 0, signal_time, tp_price, signal_time + 30 * PeriodSeconds(), tp_price))
   {
      ObjectSetInteger(0, tp_name, OBJPROP_COLOR, InpTPColor);
      ObjectSetInteger(0, tp_name, OBJPROP_STYLE, STYLE_DASH);
      ObjectSetInteger(0, tp_name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, tp_name, OBJPROP_RAY_RIGHT, true);
      ObjectSetInteger(0, tp_name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, tp_name, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, tp_name, OBJPROP_BACK, true);
   }
   
   //--- 添加文字标签
   double sl_pips = MathAbs(entry_price - sl_price) / _Point;
   double tp_pips = MathAbs(tp_price - entry_price) / _Point;
   
   string sl_label_name = base_name + "_SL_TXT";
   if(ObjectCreate(0, sl_label_name, OBJ_TEXT, 0, signal_time, sl_price))
   {
      ObjectSetString(0, sl_label_name, OBJPROP_TEXT, StringFormat("SL %.0f点", sl_pips));
      ObjectSetInteger(0, sl_label_name, OBJPROP_COLOR, InpSLColor);
      ObjectSetInteger(0, sl_label_name, OBJPROP_FONTSIZE, 8);
      ObjectSetInteger(0, sl_label_name, OBJPROP_ANCHOR, is_buy ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER);
      ObjectSetInteger(0, sl_label_name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, sl_label_name, OBJPROP_HIDDEN, true);
   }
   
   string tp_label_name = base_name + "_TP_TXT";
   if(ObjectCreate(0, tp_label_name, OBJ_TEXT, 0, signal_time, tp_price))
   {
      ObjectSetString(0, tp_label_name, OBJPROP_TEXT, StringFormat("TP %.0f点", tp_pips));
      ObjectSetInteger(0, tp_label_name, OBJPROP_COLOR, InpTPColor);
      ObjectSetInteger(0, tp_label_name, OBJPROP_FONTSIZE, 8);
      ObjectSetInteger(0, tp_label_name, OBJPROP_ANCHOR, is_buy ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0, tp_label_name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, tp_label_name, OBJPROP_HIDDEN, true);
   }
}

//+------------------------------------------------------------------+
//| 检查区域是否已存在                                                 |
//+------------------------------------------------------------------+
bool ZoneExists(double price_top, double price_bottom, datetime check_time)
{
   double tolerance = 5 * _Point;
   
   for(int i = 0; i < g_zone_count; i++)
   {
      if(MathAbs(g_zones[i].price_top - price_top) < tolerance &&
         MathAbs(g_zones[i].price_bottom - price_bottom) < tolerance)
      {
         return true;
      }
   }
   return false;
}

//+------------------------------------------------------------------+
//| 检查价格是否触及区域                                               |
//+------------------------------------------------------------------+
void CheckZonesTouched(double current_price)
{
   for(int i = g_zone_count - 1; i >= 0; i--)
   {
      if(g_zones[i].touched)
         continue;
      
      if(current_price >= g_zones[i].price_bottom && current_price <= g_zones[i].price_top)
      {
         g_zones[i].touched = true;
         
         ObjectDelete(0, g_zones[i].name);
         ObjectDelete(0, g_zones[i].name + "_LBL");
         
         for(int j = i; j < g_zone_count - 1; j++)
         {
            g_zones[j] = g_zones[j + 1];
         }
         ArrayResize(g_zones, g_zone_count - 1);
         g_zone_count--;
      }
   }
}

//+------------------------------------------------------------------+
//| 删除最老的区域                                                     |
//+------------------------------------------------------------------+
void DeleteOldestZone()
{
   if(g_zone_count == 0)
      return;
   
   int oldest_idx = 0;
   datetime oldest_time = g_zones[0].time_start;
   
   for(int i = 1; i < g_zone_count; i++)
   {
      if(g_zones[i].time_start < oldest_time)
      {
         oldest_time = g_zones[i].time_start;
         oldest_idx = i;
      }
   }
   
   ObjectDelete(0, g_zones[oldest_idx].name);
   ObjectDelete(0, g_zones[oldest_idx].name + "_LBL");
   
   for(int i = oldest_idx; i < g_zone_count - 1; i++)
   {
      g_zones[i] = g_zones[i + 1];
   }
   ArrayResize(g_zones, g_zone_count - 1);
   g_zone_count--;
}

//+------------------------------------------------------------------+
//| 清理所有对象                                                       |
//+------------------------------------------------------------------+
void DeleteAllObjects()
{
   //--- 删除供需区矩形
   int total = ObjectsTotal(0, 0, OBJ_RECTANGLE);
   for(int i = total - 1; i >= 0; i--)
   {
      string name = ObjectName(0, i, 0, OBJ_RECTANGLE);
      if(StringFind(name, g_prefix) == 0)
      {
         ObjectDelete(0, name);
         ObjectDelete(0, name + "_LBL");
      }
   }
   
   //--- 删除文字标签
   total = ObjectsTotal(0, 0, OBJ_TEXT);
   for(int i = total - 1; i >= 0; i--)
   {
      string name = ObjectName(0, i, 0, OBJ_TEXT);
      if(StringFind(name, g_prefix) == 0 || StringFind(name, g_signal_prefix) == 0)
         ObjectDelete(0, name);
   }
   
   //--- 删除止盈止损线
   total = ObjectsTotal(0, 0, OBJ_TREND);
   for(int i = total - 1; i >= 0; i--)
   {
      string name = ObjectName(0, i, 0, OBJ_TREND);
      if(StringFind(name, g_signal_prefix) == 0)
         ObjectDelete(0, name);
   }
}
//+------------------------------------------------------------------+

作品说明

我已经按照 MT5 真实画图规则重写了完整代码。这一版完全移除了 ArraySetAsSeries,使用正向索引(0=最老,rates_total-1=最新),箭头缓冲区全部用 EMPTY_VALUE 初始化,循环边界正确处理了 i-1 的访问范围。

相对原作的改动:

  1. 新增买卖信号箭头:价格触及需求区(支撑)且出现反弹阳线时显示绿色上箭头,触及供给区(阻力)且出现回落阴线时显示红色下箭头
  2. 新增止盈止损标注:每个信号自动绘制橙红色虚线(止损)和绿色虚线(止盈),带点数标签,按盈亏比 2:1 计算
  3. 修复索引方向问题:移除所有 ArraySetAsSeries,使用 MT5 标准正向索引,确保图形显示正确

使用说明

安装方法

  1. 打开 MT5 的 MetaEditor(按 F4)
  2. 新建指标文件,粘贴上面的完整代码
  3. 点击编译(F7),确保 0 errors, 0 warnings
  4. 回到 MT5 图表,从导航器拖拽指标到图上

参数调节

  • 摆动识别周期:默认 5,数字越大识别的供需区越少但越可靠
  • 供需区厚度:默认 30 点,根据品种波动调整(黄金可用 50-100,外汇用 20-50)
  • 信号确认最小点数:默认 5,反弹/回落幅度小于此值不显示箭头
  • 盈亏比:默认 2.0,止盈是止损的 2 倍,可根据交易风格调整为 1.5 或 3.0
  • 删除已触及区域:开启后价格穿过的区域会自动消失,保持图表清爽

信号怎么看

  • 蓝色矩形:需求区(支撑),价格回踩这里可能反弹做多
  • 红色矩形:供给区(阻力),价格触及这里可能回落做空
  • 绿色上箭头:买入信号,出现在需求区反弹时,配合橙红色止损线(下方)和绿色止盈线(上方)
  • 红色下箭头:卖出信号,出现在供给区回落时,配合橙红色止损线(上方)和绿色止盈线(下方)
  • 线旁边的文字显示止损/止盈的点数

注意事项

  1. 供需区只是参考位置,不是精确点位,需结合其他指标(如趋势、成交量)确认
  2. 箭头只标注可能的交易机会,不构成进场指令,请根据自己的交易计划决策
  3. 止盈止损是按盈亏比自动计算的建议值,实际交易需根据市场情况调整
  4. 首次加载会扫描历史K线画出所有供需区,可能有短暂延迟
  5. 新K线实时更新,旧区域会根据设置自动消失或保留

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

这是什么

这是一个为 MT5 设计的供需区识别与交易信号指标。它扫描历史 K 线找出摆动高点(供给区/阻力)和摆动低点(需求区/支撑),用彩色矩形标注在图表上;当价格回踩需求区出现反弹阳线时显示绿色买入箭头,触及供给区出现回落阴线时显示红色卖出箭头,并自动绘制橙红色止损线和绿色止盈线,旁边标注具体点数。适合用价格行为和订单块思路做交易的用户。

逻辑原理

供需区识别:程序从左到右扫描 K 线,找出「某根 K 线的高点比前后各 N 根都高」的摆动高点,在其上方画出红色矩形(供给区,卖方压力区域);找出「某根 K 线的低点比前后各 N 根都低」的摆动低点,在其下方画出蓝色矩形(需求区,买方支撑区域)。矩形的厚度由参数决定,默认 30 点,向右延伸若干根 K 线。

买卖信号:每根新 K 线形成时,检查它是否接触或刚离开某个供需区。若当前 K 线的最低价进入需求区范围,且这根 K 线是阳线(收盘价高于开盘价),并且从低点到收盘的反弹幅度 >= 最小确认点数(默认 5 点),则在该 K 线下方显示绿色上箭头,表示买入信号。卖出信号逻辑相反:最高价进入供给区,K 线是阴线,从高点到收盘的回落幅度足够,则在上方显示红色下箭头。

止盈止损计算:买入信号的止损设在需求区下边界减 2 点,止盈按「止损点数 × 盈亏比」算出;卖出信号的止损在供给区上边界加 2 点,止盈同理。程序用虚线画出这两条水平线,旁边用小字标注「SL XX点」「TP XX点」。默认盈亏比是 2:1,即止盈是止损的两倍。

区域管理:可设置最多显示多少个区域(超出后自动删除最老的),以及价格穿过区域后是否自动删除该矩形(保持图表清爽)。

参数说明

  • InpSwingLength(默认 5):摆动识别周期,即「某根 K 线的高/低点要比前后各 N 根都高/低」的 N 值。数字越大,识别出的供需区越少但越可靠;数字越小,区域越多但可能出现假区域。
  • InpZoneThickness(默认 30):供需区矩形的厚度,单位是点数。黄金(XAUUSD)波动大可用 50-100,外汇货币对用 20-50。
  • InpZoneExtendBars(默认 50):矩形向右延伸多少根 K 线,影响矩形的宽度。
  • InpDemandColor / InpSupplyColor:需求区和供给区的颜色,默认蓝色和番茄红。
  • InpZoneTransparency(默认 85):区域透明度,0-100,数字越大越透明。
  • InpShowLabels(默认 true):是否在矩形旁边显示「Demand」或「Supply」文字。
  • InpMaxZones(默认 10):最多显示多少个区域,0 表示不限制。超出后删除最老的区域。
  • InpDeleteOldZones(默认 true):价格穿过某个区域后是否自动删除它。
  • InpSignalMinPips(默认 5):信号确认的最小点数。反弹或回落幅度小于这个值不显示箭头,避免微小波动的假信号。
  • InpShowSignals(默认 true):是否显示买卖信号箭头。
  • InpArrowOffset(默认 10):箭头距离 K 线的距离(点数),调大一点可以让箭头不遮挡 K 线。
  • InpShowSLTP(默认 true):是否显示止盈止损线和文字标签。
  • InpRiskRewardRatio(默认 2.0):盈亏比,即止盈是止损的多少倍。激进的交易者可以设 1.5,保守的可以设 3.0。
  • InpSLColor / InpTPColor:止损线和止盈线的颜色,默认橙红色和春绿色。

怎么用

  1. 打开 MT5 的 MetaEditor(按 F4 键),新建一个指标文件。
  2. 把完整代码粘贴进去,点编译(F7),确保没有错误。
  3. 回到 MT5 图表窗口,从导航器(Ctrl+N)拖拽这个指标到你想交易的品种图表上(建议 H1 或 H4 周期)。
  4. 在弹出的参数窗口里根据品种和周期调整参数(新手可以先用默认值)。
  5. 图表上会自动画出历史和实时的供需区矩形(蓝色需求区在下方,红色供给区在上方)。
  6. 当价格回踩蓝色需求区且出现反弹阳线,下方显示绿色上箭头 + 止损止盈线,这是买入参考信号。
  7. 当价格触及红色供给区且出现回落阴线,上方显示红色下箭头 + 止损止盈线,这是卖出参考信号。
  8. 止损线(橙红色虚线)和止盈线(绿色虚线)旁边标注了具体点数,可以参考它们设置订单。

适用场景

  • 品种:适合波动较大的品种,如黄金(XAUUSD)、原油、主要外汇货币对(EURUSD、GBPUSD)。小品种或冷门货币对可能因为价格跳动不规律导致假信号。
  • 周期:建议 H1(1 小时)或 H4(4 小时)以上。周期太小(如 M5、M15)会产生大量假信号和重叠区域,周期太大(如 D1)信号太少。
  • 行情类型:适合震荡行情和趋势行情中的回调。震荡行情中价格反复在供需区之间来回,信号较多;趋势行情中等待价格回踩关键支撑/阻力区域再进场。不适合单边暴涨暴跌或波动极小的盘整行情。
  • 交易风格:适合做价格行为、订单块、支撑阻力的交易者,以及喜欢等待「确认信号」再进场的稳健型交易者。不适合需要精确点位或高频进出的剥头皮交易。

风险提示

  1. 滞后性:供需区是基于历史摆动点画出的,无法预测未来,只能告诉你「过去这里曾是关键点位」。价格可能直接击穿区域不反弹。
  2. 假突破:价格触及供需区后未必真的反转,可能只是短暂停顿然后继续原方向运行(假信号)。建议结合趋势、成交量或其他指标过滤。
  3. 不会重绘:箭头信号一旦出现不会消失或移动位置,但这不意味着它是「最佳进场点」,只是满足了代码设定的条件。
  4. 止盈止损是建议值:程序自动计算的止损在区域边界外侧,止盈按盈亏比算出,这只是参考。实际交易需要根据行情强度、重要阻力位、资金管理规则调整。
  5. 区域重叠:如果参数设置不当(摆动周期太小、厚度太大),可能出现大量重叠的矩形,图表变得混乱。新手建议先用默认参数测试。
  6. 首次加载延迟:指标会扫描历史 K 线画出所有供需区,如果历史数据很多可能有短暂卡顿。
  7. 不考虑点差和滑点:止盈止损的点数计算基于收盘价,实际下单时需要加上点差,快速行情可能有滑点。
  8. 需要人工判断:箭头只是提示「价格在关键位置出现了反应」,不是进场指令。最终决策需要结合更大周期趋势、基本面消息、风险承受能力等因素。

常见问题

这个指标会重绘吗?箭头会消失或移动吗?
不会重绘。箭头一旦在某根 K 线显示就固定在那里,不会因为后续价格变化而消失或移位。供需区矩形如果价格穿过且开启了「删除已触及区域」会自动消失,但这不影响已显示的箭头。
默认参数适合什么周期和品种?
默认参数(摆动周期 5,区域厚度 30 点,盈亏比 2:1)适合黄金 XAUUSD 的 H1 或 H4 周期。如果交易外汇货币对,建议把区域厚度调小到 20-30 点;如果用 M15 这样的小周期,摆动周期可以调到 3,但会增加假信号。
止盈止损的点数是怎么算的?可以手动改吗?
买入信号的止损设在需求区下边界减 2 点,止盈是「当前价到止损的距离 × 盈亏比」;卖出信号反之。盈亏比默认 2.0(止盈是止损的 2 倍),可以在参数里改成 1.5 或 3.0。线上标注的点数只是参考,实际下单时你可以根据重要阻力位或个人风险偏好手动调整。
箭头信号可以直接跟单吗?准确率怎么样?
不建议盲目跟单。箭头只表示「价格在供需区出现了反应」,但不能保证反转会持续。代码里有最小确认点数参数(默认 5 点)来过滤微小波动,但仍可能出现假信号。建议结合更大周期趋势、支撑阻力位、或其他指标(如 MACD、RSI)确认后再进场,并严格设止损。
为什么图表上矩形太多或太少?
矩形数量由「摆动识别周期」和「最多显示区域数」控制。如果矩形太多且重叠,把摆动周期调大(比如从 5 调到 7)或减小最多显示区域数(比如从 10 调到 5);如果矩形太少,把摆动周期调小(比如 3)。另外,如果开启了「删除已触及区域」,价格穿过的区域会自动消失。

Smart Money Supply & Demand Zone Indicator with Trade Signals for MT5

Automatically identifies supply and demand zones based on swing highs/lows, displays buy/sell arrows when price reacts, and draws SL/TP lines with pip labels. Adjustable risk-reward ratio for traders who use price action strategies.

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

评论

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

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