MQL5 中创建订单号指标·进阶篇
📘

MQL5 中创建订单号指标·进阶篇

第 2/2 篇

◍ 买卖价落盘与缓冲区滚动对齐

这段逻辑处理的是历史 tick 已读和实时追加两种路径。若文件里已有数据,就按空格切出 Bid 与 Ask 位置,用 StringSubstr 截段后 StringToDouble 写进对应 Buffer;若是首次运行,则 FileSeek 跳到文件尾,把 TimeCurrent、Bid、Ask 拼成一行 FileWrite 落盘,同时把最新价推入数组。 当 ticks_stored 大于等于 rates_total 时,代码做了一半窗口滑动:只保留后半段 tick_stored/2 个报价,前半段直接丢弃,计数器同步减半。这意味着指标常驻内存的 tick 量约为图表柱数的一倍,EURUSD 在 M1 下若 rates_total=500,则缓冲上限约 1000 个报价。 最后用 PlotIndexSetInteger 的 PLOT_SHIFT 把两条线往左推 rates_total-ticks_stored 根,保证买卖价线贴合最新 K 线;并把 last_price_bid / last_price_ask 写进 rates_total-1 位置,让指标窗口始终显示实时报价。外汇与贵金属点差跳动频繁,这种自存文件方式在跳空时可能丢失部分 tick,验证前先开 MT5 用脚本跑一分钟观察文件行数。

MQL5 / C++
   AskPosition=StringFind(file_buffer," ",BidPosition)+class="num">1;
   class=class="str">"cmt">// 如果要绘制卖价线,将其加到BidBuffer[]数组中
   if(BidLineEnable)
BidBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,BidPosition,AskPosition-BidPosition-class="num">1));
   class=class="str">"cmt">// 如果要绘制买价线,将其加到AskBuffer[]数组中
   if(AskLineEnable)
AskBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,AskPosition));
   class=class="str">"cmt">// 报价计数器值加一
   ticks_stored++;
   }
  }
 }
 class=class="str">"cmt">// 如果数据已经被读取过
else
 {
  class=class="str">"cmt">// 将文件指针移动到文件末端
  FileSeek(file_handle,class="num">0,SEEK_END);
  class=class="str">"cmt">// 形成一个要写入文件的字符串
  StringConcatenate(file_buffer,TimeCurrent()," ",DoubleToString(last_price_bid,_Digits)," ",DoubleToString(last_price_ask,_Digits));
  class=class="str">"cmt">// 将一个字符串写入文件
  FileWrite(file_handle,file_buffer);
  class=class="str">"cmt">// 如果要绘制卖价线,将最新的卖价加到BidBuffer[]数组中
  if(BidLineEnable) BidBuffer[ticks_stored]=last_price_bid;
  class=class="str">"cmt">// 如果要绘制买价线,将最新的买价加到AskBuffer[]数组中
  if(AskLineEnable) AskBuffer[ticks_stored]=last_price_ask;
  class=class="str">"cmt">// 报价计数器值加一
  ticks_stored++;
 }
class=class="str">"cmt">// 关闭文件
FileClose(file_handle);
class=class="str">"cmt">// 如果报价数量大于等于图表中的柱形图
if(ticks_stored>=rates_total)
 {
  class=class="str">"cmt">// 移除前半部分tick_stored/2个报价,转移剩余报价
  for(i=ticks_stored/class="num">2;i<ticks_stored;i++)
   {
    class=class="str">"cmt">// 如果要绘制卖价线,将BidBuffer[]数组后tick_stored/2个元素移到前面来
    if(BidLineEnable) BidBuffer[i-ticks_stored/class="num">2]=BidBuffer[i];
    class=class="str">"cmt">// 如果要绘制买价线,将AskBuffer[]数组后tick_stored/2个元素移到前面来
    if(AskLineEnable) AskBuffer[i-ticks_stored/class="num">2]=AskBuffer[i];
   }
  class=class="str">"cmt">// 改变计数器的值
  ticks_stored-=ticks_stored/class="num">2;
 }
class=class="str">"cmt">// 移动卖价线使其和价格图表对齐
PlotIndexSetInteger(class="num">0,PLOT_SHIFT,rates_total-ticks_stored);
class=class="str">"cmt">// 移动买价线使其和价格图表对齐
PlotIndexSetInteger(class="num">1,PLOT_SHIFT,rates_total-ticks_stored);
class=class="str">"cmt">// 如果要绘制卖价线,将值存入BidBuffer []数组的最后一个元素中
class=class="str">"cmt">// 指标窗口中显示最新的卖价  
if(BidLineEnable) BidBuffer[rates_total-class="num">1]=last_price_bid;
class=class="str">"cmt">// 如果要绘制买价线,将值存入AskBuffer []数组的最后一个元素中  
class=class="str">"cmt">// 在指标窗口中显示最新的买价
if(AskLineEnable) AskBuffer[rates_total-class="num">1]=last_price_ask;

class=class="str">"cmt">// 从OnCalculate()返回一个非零值  
class="kw">return(rates_total);
}

「把成交笔数塞进一根蜡烛里」

常规 MT5 蜡烛按固定时间切片,但「订单号烛形」换了个切法:每根蜡烛由预定义的 N 笔报价(tick)堆出来,而不是由分钟或小时框出来。这样在流动性突变的贵金属或外汇盘面,等量成交结构可能比等时结构更早就暴露出价格推进的节奏断层。 指标在独立子窗口绘制,只挂一种图形——彩色蜡烛。它需要 5 个缓冲区:开高低收各占一个,外加一个颜色索引缓存;中间计算的报价流另用 TicksBuffer[] 承接,不绘出。默认配色给了 Gray、Red、Green 三档,用来区分不同状态蜡烛。 用户能改的输入就三个:ticks_in_candle(每根蜡烛的 tick 数,样例写死 16)、applied_price(取 Bid 还是 Ask,枚举 0/1)、path_prefix(历史 tick 数据文件的前缀与路径)。所有传入报价都会被写进文件,数据格式和前文订单号指标一致,所以两套工具可以共用同一批落盘数据。 OnInit 里先把 0~4 号缓冲区绑成指标数据/颜色索引,5 号绑成计算用,再把开高低收和颜色数组全部 ArraySetAsSeries(true)——最新一根索引为 0,符合 MT5 时间序列习惯。 实盘验证建议:把 ticks_in_candle 从 16 调到 50 看 XAUUSD 的 M1 窗口,等量蜡烛对亚盘窄幅、美盘放量两种时段的形态差异会非常直观;外汇和贵金属杠杆高,等量切片只是视角切换,不预示方向。

MQL5 / C++
class=class="str">"cmt">// 指标将在独立的窗口中绘制
class="macro">#class="kw">property indicator_separate_window
class=class="str">"cmt">// 只用到一个绘图形状,彩色蜡烛线
class="macro">#class="kw">property indicator_plots class="num">1
class=class="str">"cmt">// 我们需要4个缓存存储开盘、最高、最低和收盘价格,以及1个颜色索引缓存
class="macro">#class="kw">property indicator_buffers class="num">5
class=class="str">"cmt">// 确定绘图类型:彩色蜡烛线
class="macro">#class="kw">property indicator_type1 DRAW_COLOR_CANDLES
class=class="str">"cmt">// 确定蜡烛线的颜色
class="macro">#class="kw">property indicator_color1 Gray,Red,Green
/ / 声明枚举值
enum price_types(
   Bid,
   Ask
  )
class=class="str">"cmt">// 输入参数ticks_in_candle确定
class=class="str">"cmt">// 一根蜡烛线的tick数量
class="kw">input class="type">int ticks_in_candle=class="num">16; class=class="str">"cmt">//蜡烛线的tick数量
class=class="str">"cmt">// price_types类型的输入参数applied_price,表示 
class=class="str">"cmt">// 用于计算指标的数据:买价或卖价。
class="kw">input price_types applied_price=class="num">0; class=class="str">"cmt">// 价格
class=class="str">"cmt">// path_prefix输入参数确定了路径和文件名的前缀
class="kw">input class="type">class="kw">string path_prefix=""; class=class="str">"cmt">// 文件名前缀
class=class="str">"cmt">// ticks_stored变量保存报价的数量
class="type">int ticks_stored;
class=class="str">"cmt">// TTicksBuffer []数组用来存储价格
class=class="str">"cmt">// OpenBuffer [], HighBuffer [], LowBuffer [] 以及 CloseBuffer [] 数组
class=class="str">"cmt">// 用来存储蜡烛线的开盘、最高、最低和收盘价
class=class="str">"cmt">// ColorIndexBuffer []数组用来存储彩色蜡烛线的索引
class="type">class="kw">double TicksBuffer[],OpenBuffer[],HighBuffer[],LowBuffer[],CloseBuffer[],ColorIndexBuffer[];
class="type">void OnInit()
  {
  class=class="str">"cmt">// OpenBuffer[]数组是一个指标缓存
  SetIndexBuffer(class="num">0,OpenBuffer,INDICATOR_DATA);
  class=class="str">"cmt">// HighBuffer[]数组是一个指标缓存
  SetIndexBuffer(class="num">1,HighBuffer,INDICATOR_DATA);
  class=class="str">"cmt">// LowBuffer[]数组是一个指标缓存
  SetIndexBuffer(class="num">2,LowBuffer,INDICATOR_DATA);
  class=class="str">"cmt">// CloseBuffer[]数组是一个指标缓存
  SetIndexBuffer(class="num">3,CloseBuffer,INDICATOR_DATA);
  class=class="str">"cmt">// ColorIndexBuffer[]数组是颜色索引的缓存
  SetIndexBuffer(class="num">4,ColorIndexBuffer,INDICATOR_COLOR_INDEX);
  class=class="str">"cmt">// TicksBuffer[]数组用来存储中间计算值
  SetIndexBuffer(class="num">5,TicksBuffer,INDICATOR_CALCULATIONS);
  class=class="str">"cmt">// 设置OpenBuffer[]数组为时间序列
  ArraySetAsSeries(OpenBuffer,true);
  class=class="str">"cmt">// 设置HighBuffer[]数组为时间序列
  ArraySetAsSeries(HighBuffer,true);
  class=class="str">"cmt">// 设置LowBuffer[]数组为时间序列
  ArraySetAsSeries(LowBuffer,true);

把外部报价塞进自定义指标的缓冲区

在 MT5 写自定义指标时,若想用外部 txt 里的逐笔报价还原 K 线,第一步是把缓冲数组按时间序列排好。CloseBuffer 和 ColorIndexBuffer 都调用 ArraySetAsSeries(...,true),这样索引 0 永远对应最新一根棒,避免画图错位。 四个价格图形(开高低收分别对应 PlotIndex 0~3)都要设 PLOT_EMPTY_VALUE 为 0,否则空值会被画成 0 轴附近的杂线,干扰价格行为判读。外汇与贵金属波动剧烈,这类外部数据回放仅用于复盘,实盘高风险。

OnCalculate 里先抓 SymbolInfoDouble 的 BID/ASK 作为 last_price_bid / last_price_ask,再用 StringConcatenate 拼出『路径前缀+品种名.txt』。FileOpen 用 FILE_READFILE_WRITEFILE_ANSIFILE_SHARE_READ,意味着指标运行期间文件可被其他程序并行读,适合小布盯盘类 AIGC 工具边写边读。

首根计算(prev_calculated==0)时,先量首行长度 line_string_len,若 FileSize 超过 line_string_len*rates_total/2,就 FileSeek 到末尾倒数 rates_total/2 行,只加载最近半屏报价;否则从头读。这个阈值直接决定历史回放的内存占用,调小 rates_total/2 能更快冷启动。

MQL5 / C++
  class=class="str">"cmt">// 设置CloseBuffer[]数组为时间序列
  ArraySetAsSeries(CloseBuffer,true);
  class=class="str">"cmt">// 设置ColorIndexBuffer []数组为时间序列
  ArraySetAsSeries(ColorIndexBuffer,true);
  class=class="str">"cmt">// 开盘价(第0个图形)的空值不应被绘制
  PlotIndexSetDouble(class="num">0,PLOT_EMPTY_VALUE,class="num">0);
  class=class="str">"cmt">// 最高价(第1个图形)的空值不应被绘制
  PlotIndexSetDouble(class="num">1,PLOT_EMPTY_VALUE,class="num">0);
  class=class="str">"cmt">// 最低价(第2个图形)的空值不应被绘制
  PlotIndexSetDouble(class="num">2,PLOT_EMPTY_VALUE,class="num">0);
  class=class="str">"cmt">// 收盘价(第3个图形)的空值不应被绘制
  PlotIndexSetDouble(class="num">3,PLOT_EMPTY_VALUE,class="num">0);
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                const class="type">class="kw">datetime &time[],
                const class="type">class="kw">double &open[],
                const class="type">class="kw">double &high[],
                const class="type">class="kw">double &low[],
                const class="type">class="kw">double &close[],
                const class="type">long &tick_volume[],
                const class="type">long &volume[],
                const class="type">int &spread[])
  {
class=class="str">"cmt">//file_handle变量定义文件的句柄
class=class="str">"cmt">// BidPosition和AskPosition是字符串中头寸的卖出和买价;
class=class="str">"cmt">// line_string_len是从文件中读取的字符串的长度,
class=class="str">"cmt">// CandleNumber确定开盘、最高、最低和收盘价的蜡烛线的编号
class=class="str">"cmt">// i - 循环计数器;
class="type">int file_handle,BidPosition,AskPosition,line_string_len,CandleNumber,i;
class=class="str">"cmt">// last_price_bid变量是最近接收到的卖价
class="type">class="kw">double last_price_bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);
class=class="str">"cmt">// last_price_ask变量是最近接收到的买价
class="type">class="kw">double last_price_ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
class=class="str">"cmt">// filename是文件名,the file_buffer是一个字符串, 
class=class="str">"cmt">// 用来读取和写入字符串数据的缓存
class="type">class="kw">string filename,file_buffer;
class=class="str">"cmt">// 设置TicksBuffer[]数组的大小
ArrayResize(TicksBuffer,ArraySize(CloseBuffer));
class=class="str">"cmt">// 由path_prefix变量和交易品种
class=class="str">"cmt">// 以及“.Txt”构成的文件名
StringConcatenate(filename,path_prefix,Symbol(),".txt");
class=class="str">"cmt">// 打开一个文件供读写,编码类型ANSI,读共享模式
file_handle=FileOpen(filename,FILE_READ|FILE_WRITE|FILE_ANSI|FILE_SHARE_READ);
if(prev_calculated==class="num">0)
  {
   class=class="str">"cmt">// 读取文件的首行并确定字符串的长度
   line_string_len=StringLen(FileReadString(file_handle))+class="num">2;
   class=class="str">"cmt">// 如果文件很大(包含了大于rates_total/2个报价)
   if(FileSize(file_handle)>(class="type">class="kw">ulong)line_string_len*rates_total/class="num">2)
     {
      class=class="str">"cmt">// 设置文件指针,读取最近的rates_total/2个报价
      FileSeek(file_handle,-line_string_len*rates_total/class="num">2,SEEK_END);
      class=class="str">"cmt">// 将文件指针移动到下一行的开头
      FileReadString(file_handle);
     }
   class=class="str">"cmt">// 如果文件大小很小
   else
     {
      class=class="str">"cmt">// 将文件指针移动到文件的开头

◍ 从tick文件回放重建蜡烛线缓冲

这段逻辑处理的是历史 tick 文件的二次读取与实时追加两条路径。若文件已存在且之前读过,先把指针拨回头部 FileSeek(file_handle,0,SEEK_SET),重置 ticks_stored=0,然后逐行 FileReadString 直到 FileIsEnding 返回 false;只处理长度大于 6 的行,避免表头或空行污染缓冲。 行内解析靠空格定位:第一次 StringFind 找空格,第二次从该位置+1 再找空格得到卖价起点 BidPosition,第三次从 BidPosition 找空格得到买价起点 AskPosition。applied_price==0 取卖价子串转 double 存入 TicksBuffer,applied_price==1 则直接取 AskPosition 之后的买价。每读入一个有效报价 ticks_stored 自增。 若数据未被读取过,则 FileSeek 到 SEEK_END,用 TimeCurrent、last_price_bid、last_price_ask 拼成字符串 FileWrite 追加,并把最新价按 applied_price 写入 TicksBuffer 末尾。注意这里 DoubleToString 用了 _Digits 精度,黄金 XAUUSD 通常 _Digits=3 或 2,写文件前确认品种小数位,否则回读时 StringToDouble 精度不对。 文件关闭后有个关键裁剪:当 ticks_stored>=rates_total,把前 ticks_stored/2 个旧报价丢弃,剩余前半移回数组头部,计数器减半。这意味着你的可视 tick 窗口始终约等于当前图表柱数,回测大周期时内存不会无限涨。 最后用 MathFloor((ticks_stored-1)/ticks_in_candle) 推算每根自定义蜡烛包含多少 tick,CandleNumber 从 -1 起,循环里若报价仍属当前蜡烛就更新 CloseBuffer 与 HighBuffer。HighBuffer 仅在 TicksBuffer[i]>HighBuffer[CandleNumber] 时刷新,符合价格行为里「影子才代表流动性试探」的直觉。外汇与贵金属杠杆高,自建 tick 蜡烛仅用于复盘验证,实盘信号须结合点差与滑点评估。

MQL5 / C++
FileSeek(file_handle,class="num">0,SEEK_SET);
 }
 class=class="str">"cmt">// 重置报价计数器
ticks_stored=class="num">0;
 class=class="str">"cmt">//一直读到文件结尾
 class="kw">while(FileIsEnding(file_handle)==false)
  {
   class=class="str">"cmt">// 从文件中读取一个字符串
   file_buffer=FileReadString(file_handle);
   class=class="str">"cmt">// 如果字符串长度大于6,处理该字符串
   if(StringLen(file_buffer)>class="num">6)
     {
      class=class="str">"cmt">// 在行中找到卖价的开始位置
      BidPosition=StringFind(file_buffer," ",StringFind(file_buffer," ")+class="num">1)+class="num">1;
      class=class="str">"cmt">//在行中找到买价的开始位置
      AskPosition=StringFind(file_buffer," ",BidPosition)+class="num">1;
      class=class="str">"cmt">// 如果使用了卖价,将其加到TicksBuffer[]数组中
      if(applied_price==class="num">0)
TicksBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,BidPosition,AskPosition-BidPosition-class="num">1));
      class=class="str">"cmt">// 如果使用了买价,将其加到TicksBuffer[]数组中
      if(applied_price==class="num">1)
 TicksBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,AskPosition));
      class=class="str">"cmt">// 报价计数器值加一
      ticks_stored++;
     }
   }
 }
class=class="str">"cmt">// 如果数据已经被读取过
else
  {
   class=class="str">"cmt">// 将文件指针移动到文件末端
   FileSeek(file_handle,class="num">0,SEEK_END);
   class=class="str">"cmt">// 形成一个要写入文件的字符串
   StringConcatenate(file_buffer,TimeCurrent()," ",DoubleToString(last_price_bid,_Digits)," ",DoubleToString(last_price_ask,_Digits));
   class=class="str">"cmt">// 将一个字符串写入文件
   FileWrite(file_handle,file_buffer);
   class=class="str">"cmt">// 如果使用了卖价,将最新的卖价加到TicksBuffer[]数组中
   if(applied_price==class="num">0) TicksBuffer[ticks_stored]=last_price_bid;
   class=class="str">"cmt">// 如果使用了买价,将最新的买价加到TicksBuffer[]数组中
   if(applied_price==class="num">1) TicksBuffer[ticks_stored]=last_price_ask;
   class=class="str">"cmt">// 报价计数器值加一
   ticks_stored++;
  }
class=class="str">"cmt">// 关闭文件
FileClose(file_handle);
class=class="str">"cmt">// 如果报价数量大于等于图表中的柱形图的数量
if(ticks_stored>=rates_total)
  {
   class=class="str">"cmt">// 移除前半部分tick_stored/2个报价,转移剩余报价
   for(i=ticks_stored/class="num">2;i<ticks_stored;i++)
     {
      class=class="str">"cmt">// 将数据移到TicksBuffer[]数组开头的tick_stored/2个元素中
      TicksBuffer[i-ticks_stored/class="num">2]=TicksBuffer[i];
     }
   class=class="str">"cmt">// 改变报价计数器
   ticks_stored-=ticks_stored/class="num">2;
  }
  class=class="str">"cmt">// 为CandleNumber分配一个无效的蜡烛线数字
   CandleNumber=-class="num">1;
   class=class="str">"cmt">// 查找所有能构成蜡烛线的价格数据
   for(i=class="num">0;i<ticks_stored;i++)
     {
      class=class="str">"cmt">//如果这个蜡烛线已经形成
      if(CandleNumber==(class="type">int)(MathFloor((ticks_stored-class="num">1)/ticks_in_candle)-MathFloor(i/ticks_in_candle)))
       {
        class=class="str">"cmt">// 当前报价仍就是当前蜡烛线的收盘价
        CloseBuffer[CandleNumber]=TicksBuffer[i];
        class=class="str">"cmt">// 如果当前价格大于当前蜡烛线的最高价,
class=class="str">"cmt">// 那么它将是蜡烛线的新高价
        if(TicksBuffer[i]>HighBuffer[CandleNumber]) HighBuffer[CandleNumber]=TicksBuffer[i];

「用逐笔报价重建蜡烛线的着色逻辑」

把逐笔报价(tick)聚合成自定义蜡烛线时,每一根的形态和颜色都依赖缓冲区实时刷新。下面这段逻辑直接决定你在副图里看到的是绿、红还是灰。 若蜡烛线已在之前被计算过,新报价只需更新边界:当TicksBuffer[i]低于LowBuffer[CandleNumber]时,最低价被刷新;收盘高于开盘则ColorIndexBuffer记为2(绿),低于则记1(红),相等记0(灰)。 若蜡烛线尚未建立,先用MathFloor算出CandleNumber,然后把当前报价同时写入开高低收四个缓冲区,颜色先置0(灰)等待后续tick确认。这样每根蜡烛在形成过程中从灰起步,方向明朗后才染色。 外汇与贵金属点差跳变频繁,这种按tick重绘的蜡烛在重要数据发布时可能出现瞬间灰→红→绿,属正常高波动现象,复盘时需注意滑点风险。

MQL5 / C++
   class=class="str">"cmt">// 如果当前价格小于当前蜡烛线的最低价,那么它将是蜡烛线的新低价
   if(TicksBuffer[i]<LowBuffer[CandleNumber]) LowBuffer[CandleNumber]=TicksBuffer[i];
   class=class="str">"cmt">// 如果蜡烛线看涨,它的颜色索引将为2(绿色)
   if(CloseBuffer[CandleNumber]>OpenBuffer[CandleNumber]) ColorIndexBuffer[CandleNumber]=class="num">2;
   class=class="str">"cmt">// 如果蜡烛线看跌,它的颜色索引将为1(红色)
   if(CloseBuffer[CandleNumber]<OpenBuffer[CandleNumber]) ColorIndexBuffer[CandleNumber]=class="num">1;
   class=class="str">"cmt">// 如果开盘和收盘价一样,它的颜色索引将为0(灰色)
   if(CloseBuffer[CandleNumber]==OpenBuffer[CandleNumber]) ColorIndexBuffer[CandleNumber]=class="num">0;
   }
   class=class="str">"cmt">// 如果这个蜡烛线还没被计算过
   else
   {
   class=class="str">"cmt">// 让我们来确定蜡烛线的索引
   CandleNumber=(class="type">int)(MathFloor((ticks_stored-class="num">1)/ticks_in_candle)-MathFloor(i/ticks_in_candle));
   class=class="str">"cmt">// 当前报价是蜡烛线的开盘价
   OpenBuffer[CandleNumber]=TicksBuffer[i];
   class=class="str">"cmt">// 当前报价是蜡烛线的最高价
   HighBuffer[CandleNumber]=TicksBuffer[i];
   class=class="str">"cmt">// 当前报价是蜡烛线的最低价
   LowBuffer[CandleNumber]=TicksBuffer[i];
   class=class="str">"cmt">// 当前报价是蜡烛线的收盘价
   CloseBuffer[CandleNumber]=TicksBuffer[i];
   class=class="str">"cmt">// 此蜡烛线的颜色索引将为0(灰色)
   ColorIndexBuffer[CandleNumber]=class="num">0;
   }
   }
 class=class="str">"cmt">// 从OnCalculate()返回一个非零值 
 class="kw">return(rates_total);
}

把工具请下神坛

这两套指标——订单号图表与订单号烛形图——说到底只是把 MT5 里被忽略的 tick 流重新摆回肉眼可见的位置。tickindicator.mq5 占 12.68 KB,tickcolorcandles.mq5 占 16.96 KB,下载解压后直接丢进 MQL5/Indicators 就能编译挂盘。 有读者在 EURUSD M1 上原样编译后遇到底部红蓝柱不显示、只有左上角白字买卖价,左移图表才出图;后来换用作者更新的修正版指标,问题消失。这说明老版本对当前蜡烛可视区的缓冲刷新有 bug,新版本才修了。 外汇与贵金属 tick 数据量大、跳空频繁,这类工具只帮你看见微观结构,不替你判断方向。真要验证,自己开 MT5 挂上修正版,把图表左右拖几下,看底部窗口什么时候补出柱体,比看任何说明都直观。

常见问题

用两组缓冲区分别接 bid 与 ask,写入时按相同索引推进,避免一根蜡烛里买卖价错位。回测时优先用真实 tick 重建,肉眼核对前 20 根蜡烛的价差。
开一个计数缓冲区,在每笔成交事件里对该根蜡烛索引累加,最后用不同柱宽或标签呈现。注意贵金属与外汇点差跳变时计数会失真,需加过滤。
可以。小布能读取你加载的指标配置,比对缓冲区滚动与外部报价时间戳,标出错位蜡烛并给出修正参数建议,省去手动翻代码。
按时间切片重放逐笔报价,以最后成交价更新该根蜡烛的 close 与 high/low,再单独跑一遍着色逻辑校验影线。外汇高风险,回放不等于实盘。
先确认你的着色条件是按 tick 更新还是按收盘价更新;多数偏差来自把盘口跳动当成交。建议抽 5 根异常蜡烛逐 tick 打印比对。