开发回放系统  市场模拟(第 06 部分):首次改进(I)·综合运用
📘

开发回放系统 市场模拟(第 06 部分):首次改进(I)·综合运用

第 3/3 篇

逐笔tick落库与数组边界的实操坑

这段逻辑处理的是历史 tick 文件的逐笔读入:若当前 tick 的时间戳与上一笔重合,就直接把真实成交量累加到旧记录上,而不是新开一条;否则写入新槽位,并按 volume_real 是否大于 0 来决定 nTicks 计数器是否 +1。 m_Ticks.Info[old].volume_real += tick.volume_real; else { m_Ticks.Info[m_Ticks.nTicks] = tick; m_Ticks.nTicks += (tick.volume_real > 0.0 ? 1 : 0); old = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : old); } 循环里用 FileIsEnding(file) 和 _StopFlag 做双保险,文件没读完且脚本没被终止才继续;若超量则 Print 报错并返回 false,tick 文件找不到也同样返回 false。 宏定义里能看到两套 def_MaxSizeArray:注释写的 128 Mbytes 对应 134217727,而下一条直接改成 16777216(16 Mbytes)。在 MT5 实盘环境跑回放时,若你的品种 tick 密度高,数组上限设 16M 位置可能在几小时就触顶,倾向先用小样本验证再放开。外汇与贵金属价格行为分析高风险,参数须自行压力测试。

MQL5 / C++
m_Ticks.Info[old].volume_real += tick.volume_real;
else
  {
  m_Ticks.Info[m_Ticks.nTicks] = tick;
  m_Ticks.nTicks += (tick.volume_real > class="num">0.0 ? class="num">1 : class="num">0);
  old = (m_Ticks.nTicks > class="num">0 ? m_Ticks.nTicks - class="num">1 : old);
  }
if((!FileIsEnding(file))&&(!_StopFlag))
  {
  Print("Too much data in the tick file.\nCannot class="kw">continue....");
  class="kw">return false;
  }
else
  {
  Print("Tick file ", szFileNameCSV,".csv not found...");
  class="kw">return false;
  }
class="kw">return (!_StopFlag);
class="macro">#undef macroRemoveSec
class="macro">#define def_MaxSizeArray      class="num">134217727 class=class="str">"cmt">// class="num">128 Mbytes of positions
class="macro">#define def_MaxSizeArray      class="num">16777216 class=class="str">"cmt">// class="num">16 Mbytes of positions

「回放 tick 文件的读取与释放」

做历史逐笔回放时,第一步是把落盘的 CSV tick 数据读进内存。下面这段逻辑先尝试打开 Market Replay\Ticks\ 目录下的文件,若句柄不等于 INVALID_HANDLE 才继续,否则后续全是空转。 打开成功后先给 m_Ticks.Info 预留 def_MaxSizeArray 大小,并记录 old = m_Ticks.nTicks 作为旧计数。接着循环 7 次用 FileReadString 拼出 szInfo,与 def_Header_Ticks 比对——不一致就 Print 提示“不是成交 tick 文件”并 return false,这能拦掉表头错乱的脏数据。 校验通过后打印“Loading replay ticks. Please wait...”,进入 while 循环:条件是未到文件尾、nTicks 小于 INT_MAX-2、且 _StopFlag 为假。循环体内每次把数组扩 1 行,读两列拼成一行 tick 信息(其余解析代码略)。注意 INT_MAX-2 这个上限,真跑满量 tick 时可以避免整数溢出导致数组越界。 回放结束要干净拆卸。CloseReplay 里依次 ArrayFree 释放 tick 数组、ChartClose 关回放图表、SymbolSelect(...,false) 取消品种关注、CustomSymbolDelete 删自定义品种、GlobalVariableDel 清全局变量。漏掉 CustomSymbolDelete 的话,MT5 市场报价里会留下僵尸符号,下次加载可能报重名。

MQL5 / C++
if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
{
      ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
      old = m_Ticks.nTicks;
      for (class="type">int c0 = class="num">0; c0 < class="num">7; c0++) szInfo += FileReadString(file);
      if (szInfo != def_Header_Ticks)
      {
            Print("File ", szFileNameCSV, ".csv is not a file with traded ticks.");
            class="kw">return false;
      }
      Print("Loading replay ticks. Please wait...");
      class="kw">while ((!FileIsEnding(file)) && (m_Ticks.nTicks < (INT_MAX - class="num">2)) && (!_StopFlag))
      {
            ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + class="num">1), def_MaxSizeArray);
            szInfo = FileReadString(file) + " " + FileReadString(file);
class=class="str">"cmt">// ... The rest of the code...
class="type">void CloseReplay(class="type">void)
{
      ArrayFree(m_Ticks.Info);
      ChartClose(m_IdReplay);
      SymbolSelect(def_SymbolReplay, false);
      CustomSymbolDelete(def_SymbolReplay);
      GlobalVariableDel(def_GlobalVariableReplay);
}

◍ 把工具请下神坛

当前版本的回放系统已经允许把历史起点提前到 8 月 6 日再退回 8 月 5 日第一次跳价,这在此前版本里做不到。视频里用的 Market_Replay.zip 约 13 MB,里面含可直接在 MT5 加载的源码与配置样例,建议先跑通再改参数。 系统现在还有一个已知错误,作者说下一篇会修,但配置文件的结构从这一阶段起就会持续演进。外汇和贵金属回放测试杠杆高、滑点模拟和实盘有偏差,结论只作概率参考。 与其等完美版本,不如现在就用附件里的文件建一次自己的会话配置。工具只是让你重复看市场的一种手段,真值钱的是你从重复里归纳出的价格行为规律。

常见问题

多在写入前没判当前索引是否达到数组上限,先用 ArraySize 取长度并动态 ArrayResize,越界前就扩容,别等报错。
若没显式释放文件句柄和缓存数组,下次加载可能占用旧内存导致读脏数据;结束回放时务必 FileClose 并清零数组。
可以,小布能按你给的品种和周期自动加载回放、监控数组边界并提示越界风险,你把重复劳动交给它就行。
先比对两端tick时间戳粒度与小数点位数,再查回放时是否漏了非交易时段的脏tick,逐段打印首尾值定位断点。
把它接进每日开盘前例行检查,只验证一个品种的一个时段,跑通就关,别追求全量,习惯比完整重要。