开发回放系统 市场模拟(第 21 部分):外汇(II)·综合运用
◍ 回放品种的参数注入分支
这段 switch 结构负责把配置文件里的字段映射到自定义回放品种 def_SymbolReplay 的属性上。case 0 只记路径不碰品种属性,case 1~3 分别写 tick 最小变动价位、每跳价值和成交量步长,全部用 StringToDouble 把 CSV 文本转成浮点再落库。 case 4 处理模型加载模式 m_ModelLoading,先用 StringInit 转整型,再用一行条件表达式把越界值夹回 1。注意原代码写的是 (m_ModelLoading < 1) && (m_ModelLoading > 4),逻辑上永远为假,实际任何不在 1~4 的值都不会被修正,这是移植时容易踩的坑。 SetSymbolReplay 入口先拼宏 macroFileName,再尝试用 FileOpen 以 CSV+ANSI 只读方式打开 Market Replay 目录下的配置。若句柄等于 INVALID_HANDLE,会打印失败并退出服务,说明路径或文件名错一个字符就直接中断,没有兜底。 开 MT5 把这段粘进 EA 初始化,故意改错配置名看 Print 输出,能确认你的回放品种到底卡在哪一行。外汇与贵金属回放含高杠杆风险,参数错配可能让历史重演失真。
case class="num">0: m_szPath = szRet[class="num">1]; class="kw">return true; case class="num">1: CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[class="num">1])); class="kw">return true; case class="num">2: CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[class="num">1])); class="kw">return true; case class="num">3: CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[class="num">1])); class="kw">return true; case class="num">4: m_ModelLoading = StringInit(szRet[class="num">1]); m_ModelLoading = ((m_ModelLoading < class="num">1) && (m_ModelLoading > class="num">4) ? class="num">1 : m_ModelLoading); class="kw">return true; } Print("Variable >>", szRet[class="num">0], "<< undefined."); }else Print("Set-up configuration >>", szInfo, "<< invalid."); class="kw">return false; } class="type">bool SetSymbolReplay(const class="type">class="kw">string szFileConfig) { class="macro">#define macroFileName((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo) class="type">int file; class="type">char cError, cStage; class="type">class="kw">string szInfo; class="type">bool bBarsPrev; if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE) { Print("Failed to open configuration file [", szFileConfig, "]. Closing the service...");
「回放引擎的初始化与文件解析循环」
这段逻辑出现在 tick 回放类模块的加载函数里,负责把历史转录文件读进内存并切分阶段。先打印等待提示,把 m_Ticks.Rate 按 def_BarsDiary 预扩容,再把 nRate 置 -1、首元素时间归零,相当于清空上一轮回放残留。 cError 与 cStage 都清 0,bBarsPrev 设 false,私有指针 pTicksToReplay / pTicksToBars / pBarsToTicks / pBarsToPrev 全部 NULL,Line 从 1 计起。这样后续 while 循环才能在文件未尾、未点停止、无错误三个条件下持续读取。 循环体里用 FileReadString 取行、GetDefinition 判别是 DEFINE 还是 INFO。DEFINE 走 WhatDefine 解析;INFO 非空时按 cStage 分流:0 阶段收到 INFO 直接 cError=2 报错,1 阶段挂 pBarsToPrev 数组,2 阶段挂 pTicksToReplay,3 阶段挂 pTicksToBars。外汇与贵金属 tick 回放属高风险验证,参数错配可能导致回测失真。 开 MT5 把这段贴进 EA 的加载段,改 def_BarsDiary 看内存占用变化,能直接确认你的转录文件是否被按阶段正确捕获。
class="kw">return false; } Print("Loading ticks for replay. Please wait...."); ArrayResize(m_Ticks.Rate, def_BarsDiary); m_Ticks.nRate = -class="num">1; m_Ticks.Rate[class="num">0].time = class="num">0; cError = cStage = class="num">0; bBarsPrev = false; m_GlPrivate.Line = class="num">1; m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL; class="kw">while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == class="num">0)) { class="kw">switch (GetDefinition(FileReadString(file), szInfo)) { case Transcription_DEFINE: cError = (WhatDefine(szInfo, cStage) ? class="num">0 : class="num">1); break; case Transcription_INFO: if (szInfo != "") class="kw">switch (cStage) { case class="num">0: cError = class="num">2; break; case class="num">1: if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new C_Array(); (*m_GlPrivate.pBarsToPrev).Add(macroFileName, m_GlPrivate.Line); break; case class="num">2: if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new C_Array(); (*m_GlPrivate.pTicksToReplay).Add(macroFileName, m_GlPrivate.Line); break; case class="num">3: if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new C_Array();
回放脚本的指令解析与报错分支
这段逻辑处在历史数据回放器的配置加载尾部,负责把宏文件里读到的指令分派到不同数组,并依据错误码决定后续建栏方式。case 4 与 case 5 分别把文件名塞进 BarsToTicks 映射和走 Configs 解析,若 Configs 失败则把 cError 置为 7,意味着该行的配置项直接被丢弃。 加载收尾时连续调用 CMD_Array 四次,根据 m_ModelLoading 的值(≤2 走 TickReplay/BarToTick,否则反向)以及最低位比特切换 eTickToBar 与 eBarPrev 的启用标记,任一返回真都会把 bBarsPrev 翻成 true。 最后的 switch(cError) 是硬约束:cError 为 0 时若 m_Ticks.nTicks ≤ 0 直接打印 No ticks to use 并置 -1 退出;若 nTicks 正常但 bBarsPrev 仍假则补 FirstBarNULL()。其余 1、2 及 default 分别打印无法识别指令、行内容异常、文件访问错误并带出 m_GlPrivate.Line 行号,方便在 MT5 终端里定位宏文件哪一行写崩了。外汇与贵金属历史回放涉及杠杆与滑点,实盘前务必用策略测试器小周期验证该加载路径。
(*m_GlPrivate.pTicksToBars).Add(macroFileName, m_GlPrivate.Line); break; case class="num">4: if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new C_Array(); (*m_GlPrivate.pBarsToTicks).Add(macroFileName, m_GlPrivate.Line); break; case class="num">5: if (!Configs(szInfo)) cError = class="num">7; break; } break; }; m_GlPrivate.Line += (cError > class="num">0 ? class="num">0 : class="num">1); } FileClose(file); CMD_Array(cError, (m_ModelLoading <= class="num">2 ? eTickReplay : eBarToTick)); CMD_Array(cError, (m_ModelLoading <= class="num">2 ? eBarToTick : eTickReplay)); bBarsPrev = (CMD_Array(cError, ((m_ModelLoading & class="num">1) == class="num">1 ? eTickToBar : eBarPrev)) ? true : bBarsPrev); bBarsPrev = (CMD_Array(cError, ((m_ModelLoading & class="num">1) == class="num">1 ? eBarPrev : eTickToBar)) ? true : bBarsPrev); class="kw">switch(cError) { case class="num">0: if (m_Ticks.nTicks <= class="num">0) { Print("No ticks to use. Closing the service..."); cError = -class="num">1; }else if (!bBarsPrev) FirstBarNULL(); break; case class="num">1 : Print("Command in line ", m_GlPrivate.Line, " could not be recognized by the system..."); break; case class="num">2 : Print("The contents of the line is unexpected for the system: ", m_GlPrivate.Line); break; class="kw">default : Print("Access error occurred in the files indicated in line: ", m_GlPrivate.Line); }
◍ 退出逻辑里的停止旗标判定
这段收尾代码把错误码和全局停止旗标做了二值收敛:当 cError 等于 0 时,返回「非 _StopFlag」状态,否则直接返回 false。 宏 macroFileName 在作用域末尾用 #undef 撤掉,避免污染后续编译单元。 在 MT5 里跑 EA 时,若你在策略测试器手动点停止,_StopFlag 会被置 true,此时即便无报错也会令函数返回 false,循环自然断掉。 验证方式:把这段塞进 OnTick 尾部的退出分支,打印返回值,观察点停止后第一帧是否翻 false。
class="kw">return (cError == class="num">0 ? !_StopFlag : false); class="macro">#undef macroFileName }
「配置文件先搁一边,外汇回放接着调」
这一阶段的配置文件工作算告一段落,用户目前能指定早期阶段所需的全部参数,暂时不用再回头折腾配置结构。无论配置文件怎么搭,按现有逻辑都会按预期加载,后续开发可以放心往上叠功能。 下一篇会把回放/模拟系统往外汇和贵金属市场推,股票那套已经更成熟,所以重点补外汇侧的等价实现。外汇和贵金属杠杆高、波动猛,模拟回放只能帮你验证逻辑,真上实盘仍可能因滑点和流动性吃亏。 附带工程包 Market_Replay_ev221.zip 约 14.4 MB,开 MT5 直接丢进对应目录就能跑起当前版本,先熟悉配置字段再等下一篇的回放改造。