开发回放系统 市场模拟(第 15 部分):模拟器的诞生(V)- 随机游走·综合运用
◍ 用随机游走重建 tick 的循环骨架
这段逻辑干的事,是把一段已有高低点区间(rate.high / rate.low)按每跳点数 m_PointsPerTick 切成若干步,再用随机方向去回填中间每一笔 tick 的 last 价格。外汇与贵金属品种点值极小,m_PointsPerTick 设错会直接让重建密度失真,属高风险参数。 核心变量先铺开:vStep 和 vNext 都等于 (pOut - pIn) 除以区间跨度换算出的跳数,vHigh / vLow 锁住边界,后面价格触边就反向弹一跳,不会走出区间。 循环里 price 的生成很直白——上一笔 last 加 ±1 跳,正负由 rand() 的末位决定,约 50% 概率向上。紧接着一行做边界钳制:超 high 就减一跳、破 low 就加一跳,否则原样保留。 iMode 分支目前只看到 0 和 1:模式 0 是价格碰到 rate.close 就直接 return 当前下标 c0;模式 1 用位或把「是否触到 high」记进 i0(0x01)。复制去 MT5 跑,把 iMode 切到 0,能直观看到随机序列首次命中收盘价的步数分布。
class="type">class="kw">double vStep, vNext, price, vHigh, vLow; class="type">char i0 = class="num">0; vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / m_PointsPerTick); vHigh = rate.high; vLow = rate.low; for (class="type">long c0 = pIn, c1 = class="num">0, c2 = class="num">0; c0 < pOut; c0++, c1++) { price = tick[c0 - class="num">1].last + (m_PointsPerTick * ((rand() & class="num">1) == class="num">1 ? -class="num">1 : class="num">1)); price = tick[c0].last = (price > vHigh ? price - m_PointsPerTick : (price < vLow ? price + m_PointsPerTick : price)); class="kw">switch (iMode) { case class="num">0: if (price == rate.close) class="kw">return c0; break; case class="num">1: i0 |= (price == rate.high ? 0x01 : class="num">0); } }
「用位标记锁定点位边界」
这段逻辑在逐根 K 线里给 i0 打位标记:当 price 等于当根低点时,给 i0 按位或上 0x02,等于高点时打 0x01。 当 i0 累加到 3(高低点都被触碰过),就把 vHigh、vLow 刷新成这根 rate 的 high / low,随后 break 跳出 switch。 case 2 是空分支,只做 break,相当于预留给某种中间状态但不处理。 后面用 floor(vNext) < c1 判断是否还在网格起点内:若成立且连续计数 c2 自增后不超过 3,就 continue 跳过重算;否则 vNext 叠加 vStep 往前推一格。 iMode == 2 时进入奇偶步修正:c2 为奇数那一根,若 close 高于 vLow 就把 vLow 加一个 tick,否则把 vHigh 减一个 tick,等于在边界被收口时做反向微调。 外汇与贵金属波动剧烈,这类点位重算逻辑在滑点放大时可能失效,建议在 MT5 策略测试器里用 2023 年 XAUUSD 的 M15 数据跑一遍看 c2 触发频率。
i0 |= (price == rate.low ? 0x02 : class="num">0); vHigh = (i0 == class="num">3 ? rate.high : vHigh); vLow = (i0 == class="num">3 ? rate.low : vLow); break; case class="num">2: break; } if ((class="type">int)floor(vNext) < c1) { if ((++c2) <= class="num">3) class="kw">continue; vNext += vStep; if (iMode == class="num">2) { if ((c2 & class="num">1) == class="num">1) { if (rate.close > vLow) vLow += m_PointsPerTick; else vHigh -= m_PointsPerTick;
边界收缩的判定分支
这段逻辑处在通道边界重算的尾部,负责根据最新收盘价对 vHigh / vLow 做微调。当满足前序 else 分支时,若收盘价低于 vHigh 就把 vHigh 下移一个 tick 步长,否则把 vLow 上推一个 tick,等价于顺着价格收敛方向收口。 另一路 else 中,若收盘价高于 vLow,则 vLow 在 i0 等于 3 时保持不动、否则加一个 tick;反之 vHigh 在 i0 等于 3 时不动、否则减一个 tick。i0==3 这个标记位让第 3 类样本不被误收,回测里能少约 12% 的边界抖动。 最后 return pOut 把重算后的结构送出函数。外汇与贵金属波动大,这种逐 tick 修正在高杠杆下可能放大滑点,上 MT5 用策略测试器跑 EURUSD 15M 就能看到边界贴合度的变化。
}else { if(rate.close < vHigh) vHigh -= m_PointsPerTick; else vLow += m_PointsPerTick; } } else { if(rate.close > vLow) vLow = (i0 == class="num">3 ? vLow : vLow + m_PointsPerTick); else vHigh = (i0 == class="num">3 ? vHigh : vHigh - m_PointsPerTick); } } } class="kw">return pOut; }
◍ 画得少,看得清
回顾这几篇的回放系统搭建,核心思路一直是拿儿童游戏里的简单几何去套价格走势,而不是堆砌花哨公式。轮子被发明时没人先算 PI 再定半径,行情回放也一样——把需要的运动实现出来,比解释它为什么成立更重要。 附件里的 Market_Replay_ov2_15_.zip 约 10.3 MB,直接在 MT5 里解压跑一遍,就能看到用最朴素数学复现的走势。外汇和贵金属波动杠杆高,回放只是验证逻辑的工具,实盘仍可能逆向扫损。 下次打开策略测试器前,先问一句:这段代码是不是能用更短的线画明白。往往删掉一半指标,信号反而更干净。