开发回放系统 市场模拟(第 06 部分):首次改进(I)·综合运用
逐笔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 位置可能在几小时就触顶,倾向先用小样本验证再放开。外汇与贵金属价格行为分析高风险,参数须自行压力测试。
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 市场报价里会留下僵尸符号,下次加载可能报重名。
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 加载的源码与配置样例,建议先跑通再改参数。 系统现在还有一个已知错误,作者说下一篇会修,但配置文件的结构从这一阶段起就会持续演进。外汇和贵金属回放测试杠杆高、滑点模拟和实盘有偏差,结论只作概率参考。 与其等完美版本,不如现在就用附件里的文件建一次自己的会话配置。工具只是让你重复看市场的一种手段,真值钱的是你从重复里归纳出的价格行为规律。