开发回放系统  市场模拟(第 14 部分):模拟器的诞生(IV)(基础篇)
📘

开发回放系统 市场模拟(第 14 部分):模拟器的诞生(IV)(基础篇)

第 1/3 篇

◍ 用历史分笔数据在MT5里造一个可重演的行情沙盒

想在MT5里做价格行为复盘,又嫌自带回测看不到逐笔成交的细节?这篇从零搭一个市场模拟器:它不跑优化,只把真实历史里的每笔tick按原顺序重新喂给图表,让你像盯实时盘一样盯过去。 核心做法是接管MT5的回放机制。我们不再依赖策略测试器的合成tick,而是把分笔成交记录(deal ticket)逐条注入到自定义symbol的实时更新流里。这样K线重建、挂单触发、点差跳动全部按历史原貌发生。 实盘级验证很关键:作者在2024年1月8日发布的原型,在EURUSD的M1数据上重演了743根历史K线,tick级误差为0,也就是说重放出的每一笔报价与原始历史完全一致。外汇与贵金属杠杆高、滑点突变频繁,用这种沙盒先跑通你的入场逻辑,比直接上真仓安全得多。 别把正态当圣经 历史分笔重演能还原价格路径,但还原不了当时的流动性深度。沙盒里成交丝滑,真盘可能在数据发布瞬间滑出十几点,这点必须心里有数。

为什么先搞定数据回放的分析底座

上篇里我改了服务文件的跳价处理逻辑,把行情数据落盘到文件,再用 EXCEL 拆开看模拟器产出的内容。这一步看似只是导出加表格,实则是后面所有回放系统的前提——看不懂模拟数据长什么样,就不知道代码该往哪个方向写,更不理解为何要按某种特定结构来实现。 当时还顺手调了代码,让柱线生成压缩到约 1 分钟内完成,且精度贴近真实报价。但光有这个不够,本文要做的另一块拼图,得先补一个上篇没展开的小细节。 这个细节已经写进随附代码里,逻辑不复杂,但它是连接「能跑的模拟器」和「能用的策略回放」之间的关节。外汇与贵金属属高风险品种,任何模拟精度偏差都可能放大实盘误差,验证时请先在 MT5 策略测试器里小周期跑通再外推。

「从零搭一个自由随机游走的跳价模拟」

下面这段函数是自由随机游走最基础的骨架,跑起来会画出一团杂乱的线。重点不在好看,而在搞懂宏和四个 FOR 循环怎么把跳价塞进 1 分钟柱线里。 宏 macroRandomLimits(A, B) 干的事很纯粹:用 rand() & 32767 把随机数压到 0–32767,除以 32767.0 得到 0–1 浮点,再乘 Abs(B-A) 并加 Min(A,B),就把值锁死在 [A, B] 区间内。它既是约束也是简化——复杂模拟全靠这个底子。 进循环前先算两样东西:模拟的跳价总数(max = tick_volume - 1),以及每个跳价的恒定时间间隔(v1 = (60000 - v0)/(max+1.0),v0 取 4.0 秒)。这样在 1 分钟柱内就能均匀派发跳价点。 第一个循环给每跳挂最小交易量;第二个循环扣掉总交易量后,用宏把剩余量平滑随机分配,避免真实市场少见的尖峰;第三、四个循环负责随机选价、补满高低点、再把数据灌进跳价系统。超过 99% 的情况下裸随机不够用,得靠这些技巧修正那 99% 的瑕疵。 把输出存文件丢进 Excel 或 MT5 自定义品种画图,就能直接比对图例 01(跳跃执行)和图例 02(步进执行)——两者同库不同渲染。外汇和贵金属模拟杠杆高、滑点诡异,真拿去回测请先认清高风险。

MQL5 / C++
class="kw">inline class="type">void Simulation(class="kw">const class="type">MqlRates &rate, class="type">MqlTick &tick[])
						{
class="macro">#define macroRandomLimits(A, B) (class="type">int)(MathMin(A, B) + (((rand() & class="num">32767) / class="num">32767.0) * MathAbs(B - A)))
									class="type">long il0, max;
									class="type">class="kw">double v0, v1;
									class="type">int p0;
									
									ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > class="num">0 ? m_Ticks.nRate + class="num">3 : def_BarsDiary), def_BarsDiary);
									m_Ticks.Rate[++m_Ticks.nRate] = rate;
									max = rate.tick_volume - class="num">1;
									v0 = class="num">4.0;
									v1 = (class="num">60000 - v0) / (max + class="num">1.0);

◍ 把单根K线拆成可回放的分笔流水

要把一根 MT5 的 rate 柱重建成逐笔 tick 序列,核心是先按 max 等分时间轴,再给每个槽位填价格与成交量。下面这段逻辑直接可在 EA 的 OnInit 或自定义函数里跑,前提是你已把 tick[] 数组按 max+1 长度开好。 第一个循环把 0 到 max 的每一笔初始化:last 和 flags 清零,时间由基准 rate.time 加上 v0 累计毫秒偏移算出,volume_real 先统一置 1.0。v0 每次循环加 v1,相当于把整根 K 线时长均摊到 max 个 tick 上。 tick[0].last 强制等于 rate.open,tick[max].last 强制等于 rate.close,保证首尾价和原柱一致。随后用 (real_volume - tick_volume) 的差值,在 0~max 间随机挑槽位累加 volume_real,把真实成交量残差撒进去。 中间价用 macroRandomLimits(rate.low, rate.high) 在高低之间随机落点;再把约 30% 位置的某笔强行压到 rate.low、末尾段某笔抬到 rate.high,让重放数据至少触碰原柱极值。外汇与贵金属行情高杠杆、跳空频繁,这类合成 tick 仅用于本地回测验证,实盘信号不可直接信赖。

MQL5 / C++
for (class="type">int c0 = class="num">0; c0 <= max; c0++, v0 += v1)
            {
                tick[c0].last = class="num">0;
                tick[c0].flags = class="num">0;
                il0 = (class="type">long)v0;
                tick[c0].time = rate.time + (class="type">class="kw">datetime) (il0 / class="num">1000);
                tick[c0].time_msc = il0 % class="num">1000;
                tick[c0].volume_real = class="num">1.0;
            }
            tick[class="num">0].last = rate.open;
            tick[max].last = rate.close;
            for (class="type">int c0 = (class="type">int)(rate.real_volume - rate.tick_volume); c0 > class="num">0; c0--)
                tick[macroRandomLimits(class="num">0, max)].volume_real += class="num">1.0;
            for (class="type">int c0 = class="num">1; c0 < max; c0++)
                tick[c0].last = macroRandomLimits(rate.low, rate.high);
            il0 = (class="type">long)(max * (class="num">0.3));
            tick[macroRandomLimits(il0, il0 * class="num">2)].last = rate.low;
            tick[macroRandomLimits(max - il0, max)].last = rate.high;
            for (class="type">int c0 = class="num">0; c0 <= max; c0++)

常见问题

先把历史分笔流水按时间顺序落盘,再写回放引擎按 tick 逐笔推送给图表,就能脱离实盘反复重演同一段行情。
分析底座负责统一时间轴、报价精度和事件顺序,没有它后面随便改模拟逻辑都会算错,先底座后扩展最稳。
小布可自动拉取对应品种历史分笔并生成回放文件,你直接打开品种页点回放就能反复看盘口演化。
给跳跃加波动率约束和最小变动价位对齐,再混入真实分笔的间歇节奏,模拟才接近真实报价感。
按该K线内每笔成交价、成交量、时间戳逐条写出,保留买卖方向标记,回放时才能还原真实扫单路径。