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

自定义秒周期K线生成器(防闪烁版)

zhaowenwei·2 小时前发布

生成任意秒级周期K线(15s/30s/45s等),优化更新逻辑,避免图表闪烁

自定义秒周期K线生成器(防闪烁版)

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

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

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

❌ 错误:品种 XAUUSD 不存在或无法选择
CustomPeriod_NoFlicker.mq5579 行
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
#property copyright "XAUOF指标工坊"
#property version   "2.00"
#property strict

//--- 输入参数
input string   InpSymbol       = "XAUUSD";     // 原始品种名称(必须存在于MT5中)
input int      InpPeriodSec    = 30;           // 自定义周期(秒),建议:15/30/45等能整除60的值
input int      InpBarsCount    = 10000;        // 加载的历史K线数量(建议500-2000)
input int      InpUpdateInterval = 1000;       // 当前K线更新间隔(毫秒),建议1000-3000,越大越流畅

//--- 全局变量
string   g_customSymbol = "";    // 自定义品种全名
datetime g_lastBarTime = 0;      // 上一根K线的时间
MqlRates g_currentBar;           // 当前正在构建的K线
bool     g_initialized = false;  // 是否已初始化
int      g_barCount = 0;         // 已生成的K线数量
int      g_tickCounter = 0;      // Tick计数器
double   g_lastValidPrice = 0;   // 上一个有效价格(用于异常检测)
bool     g_isFirstRun = false;   // 标记:是否首次运行(用于决定是否自动打开图表)
ulong    g_lastUpdateTime = 0;   // 上次更新当前K线的时间(毫秒)

//+------------------------------------------------------------------+
//| 专家初始化函数                                                      |
//+------------------------------------------------------------------+
int OnInit()
{
   // 1. 检查原始品种是否存在
   if(!SymbolSelect(InpSymbol, true))
   {
      Print("❌ 错误:品种 ", InpSymbol, " 不存在或无法选择");
      return INIT_FAILED;
   }
   
   // 强制订阅实时报价
   if(!SymbolSelect(InpSymbol, true))
   {
      Print("❌ 错误:无法订阅品种 ", InpSymbol, " 的实时报价");
      return INIT_FAILED;
   }
   
   // 等待报价准备就绪
   for(int i = 0; i < 10; i++)
   {
      MqlTick testTick;
      if(SymbolInfoTick(InpSymbol, testTick))
      {
         if(testTick.bid > 0)
            break;
      }
      Print("等待 ", InpSymbol, " 报价准备就绪...");
      Sleep(500);
   }
   
   // 验证当前报价
   MqlTick testTick;
   if(!SymbolInfoTick(InpSymbol, testTick))
   {
      Print("❌ 错误:无法获取品种 ", InpSymbol, " 的报价");
      return INIT_FAILED;
   }
   
   if(testTick.bid <= 0)
   {
      Print("❌ 错误:品种 ", InpSymbol, " 没有有效的bid报价");
      return INIT_FAILED;
   }
   
   g_lastValidPrice = testTick.bid;
   
   Print("✓ 品种 ", InpSymbol, " 当前价格:bid=", DoubleToString(testTick.bid, 2), 
         " ask=", DoubleToString(testTick.ask, 2));
   
   // 2. 检查周期参数合法性
   if(InpPeriodSec <= 0 || InpPeriodSec >= 60)
   {
      Print("❌ 错误:秒周期必须在 1-59 之间,当前值:", InpPeriodSec);
      return INIT_FAILED;
   }
   
   // 3. 构建自定义品种名称(格式:原品种_XXs)
   g_customSymbol = InpSymbol + "_" + IntegerToString(InpPeriodSec) + "s";
   
   // 4. 检查自定义品种是否已存在
   bool isCustom = false;
   bool symbolExists = SymbolExist(g_customSymbol, isCustom);
   
   if(symbolExists && isCustom)
   {
      // 品种已存在,说明不是首次运行
      g_isFirstRun = false;
      Print("✓ 检测到已存在的自定义品种:", g_customSymbol);
      Print("正在清空旧数据并重建...");
      
      // 清空历史数据
      datetime clearFrom = D'1970.01.01 00:00:00';
      datetime clearTo = TimeCurrent() + 86400;
      
      if(!CustomRatesDelete(g_customSymbol, clearFrom, clearTo))
      {
         int err = GetLastError();
         Print("⚠️ 清空旧数据失败,错误码:", err);
      }
      else
      {
         Print("✓ 已清空旧数据");
      }
      Sleep(100);
      
      // 重新生成历史K线
      if(!BuildHistoricalBars())
      {
         Print("⚠️ 警告:历史数据加载失败,将只处理实时tick");
         InitializeCurrentBar();
      }
      
      // 不自动打开图表,只在后台更新数据
      Print("✓ EA将在后台持续更新数据");
      Print("💡 如需查看图表:在市场报价中右键点击 ", g_customSymbol, " → 图表窗口");
   }
   else
   {
      // 品种不存在,首次运行
      g_isFirstRun = true;
      Print("✓ 首次运行,正在创建自定义品种...");
      
      // 创建新品种
      if(!CreateCustomSymbol())
         return INIT_FAILED;
      
      // 生成历史K线
      if(!BuildHistoricalBars())
      {
         Print("⚠️ 警告:历史数据加载失败,将只处理实时tick");
         InitializeCurrentBar();
      }
      
      // 首次运行:自动打开图表
      long chartID = ChartOpen(g_customSymbol, PERIOD_M1);
      if(chartID > 0)
      {
         Print("✅ 自动打开图表:", g_customSymbol, ",图表ID:", chartID);
         Sleep(300);
         ChartSetInteger(chartID, CHART_AUTOSCROLL, true);
         ChartSetInteger(chartID, CHART_BRING_TO_TOP, true);
         ChartRedraw(chartID);
      }
      else
      {
         Print("⚠️ 图表打开失败,请手动在市场报价中找到 ", g_customSymbol, " 并打开图表");
      }
   }
   
   // 5. 启动定时器(每10秒触发一次,用于心跳日志)
   EventSetTimer(10);
   
   g_initialized = true;
   Print("✅ 初始化完成:", g_customSymbol, "(", InpPeriodSec, "秒周期),已生成 ", g_barCount, " 根K线");
   Print("⚠️ 重要提示:EA将持续在后台更新数据,无论图表是否打开");
   Print("💡 更新间隔:", InpUpdateInterval, "毫秒(参数可调,越大越流畅,但实时性略降低)");
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| 专家反初始化函数                                                    |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
   
   // 退出前保存最后一根K线
   if(g_initialized && g_lastBarTime > 0)
   {
      MqlRates rates[1];
      rates[0] = g_currentBar;
      CustomRatesUpdate(g_customSymbol, rates, 1);
      Print("✓ 已保存最后一根K线");
   }
   
   Print("EA已停止,自定义品种 ", g_customSymbol, " 的数据已保存");
   Print("💡 提示:如需继续更新数据,请重新启动EA");
}

//+------------------------------------------------------------------+
//| 定时器事件(心跳日志)                                               |
//+------------------------------------------------------------------+
void OnTimer()
{
   if(!g_initialized) return;
   
   // 检查原始品种是否还在接收数据
   MqlTick tick;
   if(!SymbolInfoTick(InpSymbol, tick))
   {
      Print("⚠️ 警告:无法获取 ", InpSymbol, " 的报价,尝试重新订阅...");
      SymbolSelect(InpSymbol, true);
      return;
   }
   
   // 每10秒输出一次心跳日志
   static int heartbeat = 0;
   heartbeat++;
   
   if(heartbeat % 6 == 0)  // 每60秒输出一次
   {
      Print("💓 EA运行中... 已处理 ", g_tickCounter, " 个tick,当前K线数:", g_barCount, 
            ",最新K线时间:", TimeToString(g_lastBarTime, TIME_DATE|TIME_SECONDS),
            ",当前价格:", DoubleToString(tick.bid, 2));
   }
}

//+------------------------------------------------------------------+
//| 实时tick处理(核心优化:减少更新频率)                                  |
//+------------------------------------------------------------------+
void OnTick()
{
   if(!g_initialized) return;
   
   g_tickCounter++;
   
   // 获取原始品种的最新tick
   MqlTick tick;
   if(!SymbolInfoTick(InpSymbol, tick))
      return;
   
   // 使用bid价格(与标准图表一致)
   double price = tick.bid;
   
   // 严格的价格验证
   if(price <= 0.000001)
      return;
   
   // 异常价格检测:如果价格变化超过50%,很可能是错误数据
   if(g_lastValidPrice > 0)
   {
      double changePercent = MathAbs(price - g_lastValidPrice) / g_lastValidPrice * 100.0;
      if(changePercent > 50.0)
      {
         Print("⚠️ 检测到异常价格变化:", DoubleToString(changePercent, 2), 
               "%,从 ", DoubleToString(g_lastValidPrice, 2), 
               " 到 ", DoubleToString(price, 2), ",已忽略此tick");
         return;
      }
   }
   
   g_lastValidPrice = price;
   
   // 【关键优化1】每个tick都更新实时报价(轻量级操作,用于显示Ask/Bid线)
   UpdateCustomSymbolTick(tick);
   
   // 计算当前tick应该归属的K线时间
   datetime barTime = AlignTime(tick.time, InpPeriodSec);
   
   // 【关键优化2】如果是新K线,立即完成上一根K线
   if(barTime != g_lastBarTime && g_lastBarTime > 0)
   {
      // 写入上一根完成的K线(这是必须的,不能省略)
      MqlRates rates[1];
      rates[0] = g_currentBar;
      
      int result = CustomRatesUpdate(g_customSymbol, rates, 1);
      if(result == -1)
      {
         int err = GetLastError();
         Print("❌ K线更新失败,错误码:", err);
      }
      else
      {
         g_barCount++;
         Print("✓ 新K线 #", g_barCount, ": ", TimeToString(g_currentBar.time, TIME_DATE|TIME_SECONDS), 
               " O:", DoubleToString(g_currentBar.open, 2),
               " H:", DoubleToString(g_currentBar.high, 2),
               " L:", DoubleToString(g_currentBar.low, 2),
               " C:", DoubleToString(g_currentBar.close, 2));
      }
      
      // 初始化新K线
      g_currentBar.time = barTime;
      g_currentBar.open = price;
      g_currentBar.high = price;
      g_currentBar.low = price;
      g_currentBar.close = price;
      g_currentBar.tick_volume = 1;
      g_currentBar.spread = (int)((tick.ask - tick.bid) / SymbolInfoDouble(InpSymbol, SYMBOL_POINT));
      g_currentBar.real_volume = 0;
      
      g_lastBarTime = barTime;
      g_lastUpdateTime = 0;  // 重置更新时间,新K线立即写入一次
   }
   else if(g_lastBarTime == 0)
   {
      // 第一次运行,初始化第一根K线
      g_currentBar.time = barTime;
      g_currentBar.open = price;
      g_currentBar.high = price;
      g_currentBar.low = price;
      g_currentBar.close = price;
      g_currentBar.tick_volume = 1;
      g_currentBar.spread = (int)((tick.ask - tick.bid) / SymbolInfoDouble(InpSymbol, SYMBOL_POINT));
      g_currentBar.real_volume = 0;
      
      g_lastBarTime = barTime;
      g_lastUpdateTime = 0;
   }
   else
   {
      // 【关键优化3】更新当前K线数据(内存中)
      if(price > g_currentBar.high) g_currentBar.high = price;
      if(price < g_currentBar.low) g_currentBar.low = price;
      g_currentBar.close = price;
      g_currentBar.tick_volume++;
      g_currentBar.spread = (int)((tick.ask - tick.bid) / SymbolInfoDouble(InpSymbol, SYMBOL_POINT));
      
      // 【关键优化4】节流:只在达到更新间隔时才写入当前K线
      ulong currentTime = GetTickCount64();
      if(g_lastUpdateTime == 0 || (currentTime - g_lastUpdateTime) >= (ulong)InpUpdateInterval)
      {
         MqlRates rates[1];
         rates[0] = g_currentBar;
         CustomRatesUpdate(g_customSymbol, rates, 1);
         g_lastUpdateTime = currentTime;
      }
      // 如果未到更新间隔,跳过写入,只更新内存中的数据
   }
}

//+------------------------------------------------------------------+
//| 更新自定义品种的实时报价(用于显示Ask/Bid线)                           |
//+------------------------------------------------------------------+
void UpdateCustomSymbolTick(const MqlTick &sourceTick)
{
   MqlTick customTick;
   customTick.time = sourceTick.time;
   customTick.bid = sourceTick.bid;
   customTick.ask = sourceTick.ask > 0 ? sourceTick.ask : sourceTick.bid;
   customTick.last = sourceTick.last > 0 ? sourceTick.last : sourceTick.bid;
   customTick.volume = sourceTick.volume;
   customTick.time_msc = sourceTick.time_msc;
   customTick.flags = sourceTick.flags;
   customTick.volume_real = sourceTick.volume_real;
   
   // 更新自定义品种的tick(轻量级操作,不会触发K线重绘)
   MqlTick tickArray[1];
   tickArray[0] = customTick;
   
   CustomTicksAdd(g_customSymbol, tickArray, 1);
}

//+------------------------------------------------------------------+
//| 初始化当前K线                                                      |
//+------------------------------------------------------------------+
void InitializeCurrentBar()
{
   MqlTick tick;
   if(!SymbolInfoTick(InpSymbol, tick))
      return;
   
   double price = tick.bid;
   if(price <= 0)
      return;
   
   datetime barTime = AlignTime(tick.time, InpPeriodSec);
   
   g_currentBar.time = barTime;
   g_currentBar.open = price;
   g_currentBar.high = price;
   g_currentBar.low = price;
   g_currentBar.close = price;
   g_currentBar.tick_volume = 1;
   g_currentBar.spread = (int)((tick.ask - tick.bid) / SymbolInfoDouble(InpSymbol, SYMBOL_POINT));
   g_currentBar.real_volume = 0;
   
   g_lastBarTime = barTime;
   g_lastValidPrice = price;
   
   // 写入初始K线
   MqlRates rates[1];
   rates[0] = g_currentBar;
   CustomRatesUpdate(g_customSymbol, rates, 1);
   g_barCount = 1;
   
   Print("✓ 已初始化起始K线:", TimeToString(barTime, TIME_DATE|TIME_SECONDS), " 价格:", DoubleToString(price, 2));
}

//+------------------------------------------------------------------+
//| 创建自定义品种                                                      |
//+------------------------------------------------------------------+
bool CreateCustomSymbol()
{
   // 创建新的自定义品种
   if(!CustomSymbolCreate(g_customSymbol, "", InpSymbol))
   {
      int err = GetLastError();
      Print("❌ 创建自定义品种失败,错误码:", err);
      return false;
   }
   
   Print("✓ 创建自定义品种:", g_customSymbol);
   
   // 复制原始品种的属性
   long digits = SymbolInfoInteger(InpSymbol, SYMBOL_DIGITS);
   CustomSymbolSetInteger(g_customSymbol, SYMBOL_DIGITS, digits);
   CustomSymbolSetInteger(g_customSymbol, SYMBOL_TRADE_CALC_MODE, SymbolInfoInteger(InpSymbol, SYMBOL_TRADE_CALC_MODE));
   CustomSymbolSetDouble(g_customSymbol, SYMBOL_POINT, SymbolInfoDouble(InpSymbol, SYMBOL_POINT));
   CustomSymbolSetDouble(g_customSymbol, SYMBOL_TRADE_TICK_SIZE, SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_TICK_SIZE));
   CustomSymbolSetDouble(g_customSymbol, SYMBOL_TRADE_TICK_VALUE, SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_TICK_VALUE));
   CustomSymbolSetDouble(g_customSymbol, SYMBOL_TRADE_CONTRACT_SIZE, SymbolInfoDouble(InpSymbol, SYMBOL_TRADE_CONTRACT_SIZE));
   CustomSymbolSetInteger(g_customSymbol, SYMBOL_SPREAD, SymbolInfoInteger(InpSymbol, SYMBOL_SPREAD));
   CustomSymbolSetString(g_customSymbol, SYMBOL_CURRENCY_BASE, SymbolInfoString(InpSymbol, SYMBOL_CURRENCY_BASE));
   CustomSymbolSetString(g_customSymbol, SYMBOL_CURRENCY_PROFIT, SymbolInfoString(InpSymbol, SYMBOL_CURRENCY_PROFIT));
   CustomSymbolSetString(g_customSymbol, SYMBOL_DESCRIPTION, SymbolInfoString(InpSymbol, SYMBOL_DESCRIPTION) + " (" + IntegerToString(InpPeriodSec) + "s)");
   
   Print("✓ 品种属性设置完成,小数位:", digits);
   return true;
}

//+------------------------------------------------------------------+
//| 构建历史K线数据                                                     |
//+------------------------------------------------------------------+
bool BuildHistoricalBars()
{
   Print("========== 开始构建历史K线 ==========");
   Print("目标K线数:", InpBarsCount);
   
   // 计算需要获取的tick时间范围
   datetime toTime = TimeCurrent();
   datetime fromTime = toTime - (InpBarsCount * InpPeriodSec) - 7200;
   
   Print("时间范围:", TimeToString(fromTime, TIME_DATE|TIME_SECONDS), " 至 ", TimeToString(toTime, TIME_DATE|TIME_SECONDS));
   
   // 获取历史tick数据
   MqlTick ticks[];
   int tickCount = CopyTicksRange(InpSymbol, ticks, COPY_TICKS_ALL, (long)fromTime * 1000, (long)toTime * 1000);
   
   if(tickCount <= 0)
   {
      Print("❌ 无法获取历史tick数据,错误码:", GetLastError());
      Print("提示:某些券商不提供历史tick,将只处理实时数据");
      return false;
   }
   
   Print("✓ 获取到 ", tickCount, " 个历史tick");
   
   // 聚合成K线
   MqlRates bars[];
   ArrayResize(bars, 0);
   ArraySetAsSeries(bars, false);
   
   datetime currentBarTime = 0;
   MqlRates tempBar;
   int validTickCount = 0;
   double lastPrice = 0;
   
   for(int i = 0; i < tickCount; i++)
   {
      // 使用bid价格(与标准图表一致)
      double price = ticks[i].bid;
      
      // 跳过无效价格
      if(price <= 0.000001) continue;
      
      // 异常价格检测
      if(lastPrice > 0)
      {
         double changePercent = MathAbs(price - lastPrice) / lastPrice * 100.0;
         if(changePercent > 50.0)
         {
            continue;
         }
      }
      
      lastPrice = price;
      validTickCount++;
      
      datetime barTime = AlignTime(ticks[i].time, InpPeriodSec);
      
      // 新K线
      if(barTime != currentBarTime)
      {
         // 保存上一根K线
         if(currentBarTime > 0)
         {
            int size = ArraySize(bars);
            ArrayResize(bars, size + 1);
            bars[size] = tempBar;
         }
         
         // 初始化新K线
         tempBar.time = barTime;
         tempBar.open = price;
         tempBar.high = price;
         tempBar.low = price;
         tempBar.close = price;
         tempBar.tick_volume = 1;
         tempBar.spread = (int)((ticks[i].ask - ticks[i].bid) / SymbolInfoDouble(InpSymbol, SYMBOL_POINT));
         tempBar.real_volume = 0;
         
         currentBarTime = barTime;
      }
      else
      {
         // 更新当前K线
         if(price > tempBar.high) tempBar.high = price;
         if(price < tempBar.low) tempBar.low = price;
         tempBar.close = price;
         tempBar.tick_volume++;
      }
   }
   
   // 保存最后一根K线
   if(currentBarTime > 0)
   {
      int size = ArraySize(bars);
      ArrayResize(bars, size + 1);
      bars[size] = tempBar;
   }
   
   int totalBars = ArraySize(bars);
   
   if(totalBars == 0)
   {
      Print("❌ 未能生成任何K线数据(有效tick数:", validTickCount, ")");
      return false;
   }
   
   Print("✓ 聚合完成,共生成 ", totalBars, " 根K线(有效tick数:", validTickCount, ")");
   
   // 截取到指定数量(保留最新的)
   int startIdx = 0;
   if(totalBars > InpBarsCount)
   {
      startIdx = totalBars - InpBarsCount;
      Print("✓ 截取最新 ", InpBarsCount, " 根K线(从索引 ", startIdx, " 开始)");
   }
   
   MqlRates finalBars[];
   int finalCount = totalBars - startIdx;
   ArrayResize(finalBars, finalCount);
   ArrayCopy(finalBars, bars, 0, startIdx, finalCount);
   
   // 使用 CustomRatesReplace 整体写入
   int result = CustomRatesReplace(g_customSymbol, finalBars[0].time, finalBars[finalCount - 1].time + InpPeriodSec, finalBars, finalCount);
   
   if(result == -1)
   {
      int err = GetLastError();
      Print("❌ 历史K线写入失败,错误码:", err);
      return false;
   }
   
   Print("✅ 成功写入 ", finalCount, " 根历史K线到自定义品种");
   Print("历史K线时间范围:", TimeToString(finalBars[0].time, TIME_DATE|TIME_SECONDS), 
         " 至 ", TimeToString(finalBars[finalCount - 1].time, TIME_DATE|TIME_SECONDS));
   
   // 记录最后一根K线
   g_lastBarTime = finalBars[finalCount - 1].time;
   g_currentBar = finalBars[finalCount - 1];
   g_barCount = finalCount;
   g_lastValidPrice = finalBars[finalCount - 1].close;
   
   return true;
}

//+------------------------------------------------------------------+
//| 时间对齐函数                                                       |
//+------------------------------------------------------------------+
datetime AlignTime(datetime tickTime, int periodSec)
{
   MqlDateTime dt;
   TimeToStruct(tickTime, dt);
   
   int secInMinute = dt.sec;
   int barNumber = secInMinute / periodSec;
   int alignedSec = barNumber * periodSec;
   
   dt.sec = alignedSec;
   
   return StructToTime(dt);
}
//+------------------------------------------------------------------+
MT5 运行截图 · 完整视图带价格轴和时间轴的整张图表(约 150 根 H1 K 线),上面的封面是它最近 80 根 K 线的放大视图
自定义秒周期K线生成器(防闪烁版) 在 MT5 上的真实截图

作品说明

图表闪烁是因为每个tick都在调用 `CustomRatesUpdate()` 强制刷新K线,导致图表上的其他指标被迫频繁重新计算。解决方法是:只在K线完成时写入历史数据,当前K线内通过节流机制减少更新频率。

以下是优化后的完整代码:

使用说明

核心优化点:

  1. 节流机制:在同一根K线内,只有达到更新间隔(默认1秒)才写入一次数据,大幅减少图表刷新频率
  2. 轻量级tick更新:CustomTicksAdd() 只更新报价线,不触发K线重绘
  3. K线完成时立即写入:新K线开始时,上一根K线立即完整写入,保证数据完整性

参数调节:

  • InpUpdateInterval:当前K线更新间隔(毫秒)
  • 默认 1000(1秒更新一次当前K线)
  • 如果仍然闪烁,可改为 2000 或 3000
  • 值越大越流畅,但实时性略降低

安装步骤:

  1. 删除原来的EA,重新编译这个优化版
  2. 拖到原始品种图表上(例如XAUUSD的任意周期图)
  3. 参数保持默认即可,如果图表上指标较多可把更新间隔改为2000或3000
  4. 确认后会自动打开自定义品种图表

测试方法: 在自定义品种图表上添加MA、RSI等指标,观察是否还有闪烁。如果还有轻微闪烁,继续增大更新间隔到3000-5000毫秒。

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

这是什么

这是一个MT5专用的EA(智能交易系统),不是用来交易的,而是用来生成MT5原生不支持的秒级K线周期。你可以用它创建15秒、30秒、45秒等任意秒周期的图表,就像普通的M1、M5图表一样使用。代码核心解决的问题是图表闪烁——通过节流机制减少K线更新频率,避免图表上的其他指标被迫频繁重新计算。

逻辑原理

EA从原始品种(比如XAUUSD)获取实时tick报价,按你设定的秒周期(参数InpPeriodSec)对齐时间,把tick聚合成OHLC四价。每个tick都会更新bid/ask报价线(轻量级操作),但当前K线的OHLC数据只在达到更新间隔(参数InpUpdateInterval,默认1000毫秒)时才写入一次,大幅减少CustomRatesUpdate()调用次数。当新K线开始时,上一根K线立即完整写入,保证数据完整性。

初始化时会从历史tick聚合出指定数量的K线(参数InpBarsCount),写入自定义品种(名称格式:原品种名_XXs,比如XAUUSD_30s)。之后EA持续在后台处理实时tick,你可以在自定义品种图表上加任何指标。

代码还内置了异常价格过滤:如果价格变化超过50%,判定为错误数据并跳过。

参数说明

  • InpSymbol(默认"XAUUSD"):原始品种名称,必须是你MT5里能看到行情的品种。
  • InpPeriodSec(默认30):自定义周期的秒数,建议填能整除60的值(15/30/45),范围1–59秒。
  • InpBarsCount(默认10000):加载的历史K线数量。如果券商不提供历史tick,这个参数无效,EA只处理实时数据。建议500–2000,太大可能加载慢。
  • InpUpdateInterval(默认1000):当前K线的更新间隔,单位毫秒。默认1秒更新一次当前K线。如果图表上指标很多仍然闪烁,把这个值改成2000或3000。值越大越流畅,但实时性略降低。

怎么用

  1. 把代码保存为.mq5文件,在MT5的MetaEditor里编译。
  2. 打开原始品种的任意周期图表(比如XAUUSD的M1图),把EA拖上去。
  3. 参数按需调整,一般保持默认即可。如果图表上已经有很多指标,建议把InpUpdateInterval改成2000或3000。
  4. 确认后,首次运行会自动打开自定义品种图表(比如XAUUSD_30s);非首次运行只在后台更新数据,需要你手动在市场报价里右键点击自定义品种→图表窗口。
  5. 在自定义品种图表上可以像普通图表一样添加均线、RSI等指标。
  6. EA停止后数据会保留,下次重启EA会清空旧数据重新生成。

适用场景

适合需要秒级精度观察的场景:剥头皮交易、高频进出场、观察开盘瞬间价格波动。可用于任何有tick数据的品种(外汇、黄金、指数、加密货币等),不限于XAUUSD。

秒周期比分钟周期更细腻,适合波动快的品种和时段(比如非农数据发布、开盘前5分钟)。如果你交易的是日内超短线,30秒或45秒周期能比M1更早看到趋势变化。

不适合波动慢的品种或长周期交易——秒K线噪音大,日线级别的交易者用不上。

风险提示

  1. 历史数据依赖券商:代码会尝试加载历史tick聚合成K线,但很多券商不提供或只提供几天的tick历史。如果加载失败,EA只处理实时数据,图表上看不到历史K线。
  2. 不是独立指标:这个EA只生成图表,不提供交易信号。你需要自己在生成的图表上加指标或手动分析。
  3. 更新间隔权衡:InpUpdateInterval参数越大越流畅,但当前K线的High/Low更新会有延迟。比如设成3000毫秒,最多延迟3秒才能看到最新高低点。
  4. EA停止后数据不更新:自定义品种的数据完全依赖EA运行。关闭EA或重启MT5后,自定义品种图表会停在最后更新的时间,需要重新启动EA。
  5. 不能回测:自定义品种不能用于策略测试器,只能用于实时观察。

常见问题

这个EA会闪烁吗?和普通的秒周期生成器有什么区别?
代码专门优化了更新逻辑,通过节流机制(参数InpUpdateInterval)减少K线刷新频率,比直接每个tick都调用CustomRatesUpdate的方案流畅很多。如果图表上指标较多仍有轻微闪烁,可以把更新间隔改成2000或3000毫秒。
历史K线加载失败怎么办?
很多券商不提供历史tick数据,这种情况下EA会输出警告并只处理实时数据。自定义品种图表上看不到历史K线,但不影响实时使用。如果需要历史数据,可以让EA运行几天积累。
可以用在XAUUSD以外的品种吗?
可以。参数InpSymbol可以改成任何你MT5里有行情的品种,比如EURUSD、US30、BTCUSD等。代码会自动复制原品种的点值、合约大小等属性。
秒周期填多少合适?
建议填能整除60的值(15/30/45),这样每分钟的K线数量是整数,方便对照M1图。代码限制范围是1–59秒,但太小的值(比如5秒)会生成大量K线,加载和显示都慢。
EA停止后自定义品种的图表还能用吗?
图表可以继续打开,但数据不会更新。自定义品种的K线完全依赖EA运行时写入,关闭EA后就是静态数据。重新启动EA会清空旧数据重新生成。

MT5 Custom Second-Period Chart Generator (Anti-Flicker)

An MT5 EA that creates custom symbol charts with sub-minute timeframes (15s/30s/45s etc.). Uses throttling to reduce update frequency and prevent chart flicker. Loads historical tick data and aggregates into second-based OHLC bars.

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

评论

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

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