开发回放系统  市场模拟(第 24 部分):外汇(V)·综合运用
📘

开发回放系统 市场模拟(第 24 部分):外汇(V)·综合运用

第 3/3 篇

让随机游走挑最省力的路

纯随机化在 1 分钟甚至 10/15 分钟周期上都给不出像样的走势,理想状态是渐进式绘制,外表像随机、底层却是简单数学在控场。要做到顺畅,就得删掉部分旧函数、按可控规则引导,而不是强行指定方向——只需给 MT5 定好规矩,让它自己处理过程。 修改后的 RandomWalk 用绿色新行替换了旧逻辑,变化细微但灵活度更高。旧版是连续跳价、无空隙,模拟平滑游走要海量成交;新版在 1 分钟柱内引入空隙,把必需交易量显著压下来,你能用不同成交量和柱参数去试系统。OHLC 四个值一到位,游走就生成适配图形,过渡行为由函数自行判定。 关键在调用逻辑:现在不再通篇统一检查创建可能性,评估收进函数内部。系统会尝试仅用 10 笔最小交易量去跑游走;等于或低于该阈值时改用纯随机,在这种上下文里比游走更高效。1 分钟柱内造空隙靠特殊计算保证,且至少要生成 1 次跳价游走才能正常工作。 额外控制不可少:1 分钟内成交量超 1000,模拟可随机选先去高点还是低点;小于阈值,初始方向按开盘价离高点或低点更近来定。这叫“最省力路径”——当所需走势次数少于游走总距离时有效,避免又长又绕的路线。 EURUSD 的演示里,基于真实跳价数据的图与最省力模拟图乍看一致,细看数据源标注不同。你在 MT5 里加载该资产,用应用提供的最后成交价和出价两种绘制类型做实验,就能比对模拟和真实数据的贴合度。外汇品种波动剧烈、杠杆风险高,验证结果仅作概率参考。

MQL5 / C++
class="kw">inline class="type">int RandomWalk(class="type">int In, class="type">int Out, const class="type">class="kw">double Open, const class="type">class="kw">double Close, class="type">class="kw">double High, class="type">class="kw">double Low, const class="type">int Spread, class="type">MqlTick &tick[], class="type">int iMode, class="type">int iDesloc)
  {
    class="type">class="kw">double vStep, vNext, price, vH = High, vL = Low;
    class="type">char i0 = class="num">0;
    vNext = vStep = (Out - In) / ((High - Low) / m_TickSize);
    for (class="type">int c0 = In, c1 = class="num">0, c2 = class="num">0; c0 <= Out; c0++, c1++)
    {
      price = (m_IsPriceBID ? tick[c0 - class="num">1].bid : tick[c0 - class="num">1].last) + (m_TickSize * ((rand() & class="num">1) == class="num">1 ? -class="num">1 : class="num">1));
      price = (price > vH ? price - m_TickSize : (price < vL ? price + m_TickSize : price));
      price = (m_IsPriceBID ? tick[c0 - class="num">1].bid : tick[c0 - class="num">1].last) + (m_TickSize * ((rand() & class="num">1) == class="num">1 ? -iDesloc : iDesloc));
      price = (price > vH ? vH : (price < vL ? vL : price));
      MountPrice(c0, price, (Spread + RandomLimit((class="type">int)(Spread | (m_Marks.iMax & 0xF)), class="num">0)), tick);
      class="kw">switch (iMode)
      {
        case class="num">1:
          i0 |= (price == High ? 0x01 : class="num">0);
          i0 |= (price == Low ? 0x02 : class="num">0);
          vH = (i0 == class="num">3 ? High : vH);
          vL = (i0 == class="num">3 ? Low : vL);
          break;
        case class="num">0:
          if (price == Close) class="kw">return c0;
        class="kw">default:
          break;
      }

「K线内部tick路径的随机游走拼装」

这段逻辑干的事,是把一根 MqlRates 的 OHLC 拆成近似真实的逐笔 tick 序列。核心在 Simulation() 里先按成交量切三段:i0 取 tick_volume 的 1/5 与 1/3 的较小值,i1 落到 iMax-i0,剩下尾段推到收盘。 i2 控制每段价格步长,等于 (high-low)/TickSize 再除以 i0,若除出来是 0 则强制为 1,避免除零死循环。b0 决定首段先摸高还是先摸低:成交量 ≥1000 时靠 rand() 抛硬币,否则比较 (high-open) 与 (open-low) 谁更近。 RandomWalk 分三次调用,把 open→极值→反向极值→close 的路径填进 tick[]。下面这段是模式分支里对上下轨 vL/vH 的刷新,iMode==2 走交替锯齿,其余按收线方向推 TickSize。 别把正态当圣经:rand() 只决定首段方向,真实盘口在重大数据前往往单边抽,这段代码不会复现那种跳空密度,回测黄金 1 分钟需自行加滑点惩罚。

MQL5 / C++
if (((class="type">int)floor(vNext)) >= c1) class="kw">continue; else if ((++c2) <= class="num">3) class="kw">continue;
vNext += vStep;
vL = (iMode != class="num">2 ? (Close > vL ? (i0 == class="num">3 ? vL : vL + m_TickSize) : vL) : (((c2 & class="num">1) == class="num">1) ? (Close > vL ? vL + m_TickSize : vL) : (Close < vH ? vL : vL + m_TickSize)));
vH = (iMode != class="num">2 ? (Close > vL ? vH : (i0 == class="num">3 ? vH : vH - m_TickSize)) : (((c2 & class="num">1) == class="num">1) ? (Close > vL ? vH : vH - m_TickSize) : (Close < vH ? vH - m_TickSize : vH)));
if (iMode == class="num">2)
{
  vL = (((c2 & class="num">1) == class="num">1) ? (Close > vL ? vL + m_TickSize : vL) : (Close < vH ? vL : vL + m_TickSize));
  vH = (((c2 & class="num">1) == class="num">1) ? (Close > vL ? vH : vH - m_TickSize) : (Close < vH ? vH - m_TickSize : vH));
}else
{
  if (Close > vL) vL = (i0 == class="num">3 ? vL : vL + m_TickSize); else vH = (i0 == class="num">3 ? vH : vH - m_TickSize);
}

class="type">int i0, i1, i2;
class="type">bool b0;
m_Marks.iMax = (class="type">int) rate.tick_volume - class="num">1;
m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high);
m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low);
Simulation_Time(rate, tick);
MountPrice(class="num">0, rate.open, rate.spread, tick);
if (CheckViability(rate))
if (m_Marks.iMax > class="num">10)
{
  i0 = (class="type">int)(MathMin(m_Marks.iMax / class="num">3.0, m_Marks.iMax * class="num">0.2));
  i1 = m_Marks.iMax - i0;
  i2 = (class="type">int)(((rate.high - rate.low) / m_TickSize) / i0);
  i2 = (i2 == class="num">0 ? class="num">1 : i2);
  b0 = (m_Marks.iMax >= class="num">1000 ? ((rand() & class="num">1) == class="num">1) : (rate.high - rate.open) < (rate.open - rate.low));
  i0 = RandomWalk(class="num">1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, class="num">0, i2);
  RandomWalk(i0, i1, (m_IsPriceBID ? tick[i0].bid : tick[i0].last), (b0 ? rate.low : rate.high), rate.high, rate.low, rate.spread, tick, class="num">1, i2);
  RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, class="num">2, i2);

◍ 标记位缺失时的补价逻辑

当 K 线的高低点标记尚未建立时,引擎需要把缺失的边界价格补挂进 tick 流,否则后续成交量分布会漏掉极端值。上面这段片段展示了在随机价格生成之后,如何按 bLow / bHigh 标志位决定补 MountPrice 的调用。 m_Marks.bHigh = m_Marks.bLow = true 这一行出现在某分支里,意味着若走该分支则高低标记同时置位,后续两个 if(!m_Marks.bLow) 与 if(!m_Marks.bHigh) 都不会再触发补价。反之若走 else 的 Random_Price,则标记可能仍为空,补价逻辑才真正生效。 最后 MountPrice(m_Marks.iMax, rate.close, rate.spread, tick) 把收盘价挂到最大索引位,CorretTime 校正时间后返回 iMax。开 MT5 把这段接进自定义 tick 生成器,打印 bLow/bHigh 在两种分支下的命中次数,能直接验证补价是否如预期只补一次。外汇与贵金属 tick 仿真属高风险验证环境,结论仅代表回测逻辑倾向,不代表实盘概率。

MQL5 / C++
    m_Marks.bHigh = m_Marks.bLow = true;
    }else Random_Price(rate, tick);
    if (!m_IsPriceBID) DistributeVolumeReal(rate, tick);
    if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick);
    if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick);
    MountPrice(m_Marks.iMax, rate.close, rate.spread, tick);
    CorretTime(tick);
    class="kw">return m_Marks.iMax;
}

记住这一条就够了

回放与模拟系统要真正跑起来,先把文件备齐是硬前提。作者按资产拆了四个包:BOLSA 约 1.36 MB、FOREX 约 3.74 MB、FUTUROS 高达 11.4 MB、主程序包 44 KB,全部得下进 MQL5 编辑器指定目录,缺一个结构就断。 下一篇才会讲最终设置,但这一阶段决定了你在测试环境里能不能看清系统真实表现。外汇与贵金属模拟涉及高杠杆与跳价风险,回测结论只代表历史概率,别直接当实盘依据。 东西备好、目录放对,后面接新功能时才不会卡在加载环节——这一步懒不得。

常见问题

用随机游走从相邻标记位之间挑最短连通路径拼装 tick,只在标记缺失处做补价,避免强行拟合。
按随机游走在开盘到收盘区间内分段生成 tick 路径,用已知高低点做边界约束即可。
可以,小布能扫描回放配置并提示哪些品种缺标记位、补价逻辑是否越界,省去手动核对。
会导致模拟成交价偏离真实区间,回测信号失真,建议只在确认缺失时按邻近价线性补。
对练习价位反应和止损触发够用,但属概率模拟,不能当真实 tick 重演,外汇高风险需谨慎对待。