开发回放系统  市场模拟(第 19 部分):必要的调整·综合运用
📘

开发回放系统 市场模拟(第 19 部分):必要的调整·综合运用

第 3/3 篇

「自定义品种里真正要清零的字段」

做回放/模拟前,最容易被忽略的一步是把自定义品种的若干关键值先归零。我们只关心三样东西:跳价大小(SYMBOL_TRADE_TICK_SIZE)、跳价值(SYMBOL_TRADE_TICK_VALUE)和交易量步数(SYMBOL_VOLUME_STEP)。其它配置项大多与目的无关,不必动。 把这三个值初始化成 0,好处是能让自定义品种和实际交易品种在缺失数据时的状态一致。更实际的是,零值方便你后续写测试脚本去抓「配置没填」的 bug——MetaTrader 5 在这方面有个坑:我试过直接调最小交易量参数,终端直接忽略,根本不生效,所以改用步数来替代最小交易量反而更稳。 初始化完必须做校验。下面代码用了一个宏 macroError,逐个检查那三个值是不是还是 0;只要有一个没被配置文件填上,就打印葡萄牙语报错并中断,否则回放服务后续没法跑。注意:外汇与贵金属杠杆品种波动剧烈,模拟配置错误可能在真仓映射时放大风险,务必在模拟账户先验证。 校验逻辑本身很简单:SymbolInfoDouble 取出来等于 0 就说明没初始化。真正要补的是在配置解析里加两个字段——一个跳价值(触发一次函数生成对应数值),一个步数值(触发内部函数调整),改任何品种的回放配置文件都能加,不影响其它流程。

MQL5 / C++
C_Replay(class="kw">const class="type">class="kw">string szFileConfig)
    {
        m_ReplayCount = class="num">0;
        m_dtPrevLoading = class="num">0;
        m_Ticks.nTicks = class="num">0;
        Print("************** Market Replay Service **************");
        srand(GetTickCount());
        GlobalVariableDel(def_GlobalVariableReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        CustomRatesDelete(def_SymbolReplay, class="num">0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, class="num">0, LONG_MAX);
        SymbolSelect(def_SymbolReplay, true);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, class="num">0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, class="num">0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, class="num">0);
        CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? class="num">0 : -class="num">1);
    }
class="type">bool ViewReplay(ENUM_TIMEFRAMES arg1)
   {
class="macro">#define macroError(A) { Print(A); class="kw">return false; }
   u_Interprocess info;

   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == class="num">0)
       macroError("Configuração do ativo não esta completa, falta declarar o tamanho do ticket.");
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == class="num">0)
       macroError("Configuração do ativo não esta completa, falta declarar o valor do ticket.");
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == class="num">0)
       macroError("Configuração do ativo não esta completa, falta declarar o volume mínimo.");
   if (m_IdReplay == -class="num">1) class="kw">return false;
   if ((m_IdReplay = ChartFirst()) > class="num">0) do
   {
       if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
       {
           ChartClose(m_IdReplay);
           ChartRedraw();

◍ 回放图表初始化与参数解析的底层写法

这段 MQL5 片段负责在 Market Replay 场景下把回放图表拉起来,并等待指标放行。核心循环用 ChartNext 遍历图表句柄,直到找到目标回放图;ChartOpen 以指定交易品种和周期开图后,立刻套用 Market Replay.tpl 模板并强制重绘。 全局变量在这里充当跨模块信令:先删掉旧的 def_GlobalVariableIdGraphics,再建临时变量并写入浮点句柄值;随后用 while 轮询 GlobalVariableCheck(def_GlobalVariableReplay),每次 Sleep(750) 毫秒,直到指标置位或用户停止标志 _StopFlag 触发才退出。750ms 的轮询间隔意味着信令响应延迟大概率在 1 秒以内。 Configs 函数把外部传入的键值串拆成 PATH / POINTSPERTICK / VALUEPERPOINTS / VOLUMEMINIMAL 四类配置。StringSplit 以 '=' 分割且要求恰好两段,再对键名做 trim 后匹配;命中后用 CustomSymbolSetDouble 把 tick size、tick value 直接写进自定义回放品种的合约属性。 别在实盘品种上复用这套全局变量名。外汇与贵金属回放属高杠杆模拟,参数写错可能让小布生成的复盘信号偏离真实点值。

MQL5 / C++
  }
  class="kw">while ((m_IdReplay = ChartNext(m_IdReplay)) > class="num">0);
  Print("Aguardando permissão do indicador [Market Replay] para iniciar replay ...");
  info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
  ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
  ChartRedraw(m_IdReplay);
  GlobalVariableDel(def_GlobalVariableIdGraphics);
  GlobalVariableTemp(def_GlobalVariableIdGraphics);
  GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
  class="kw">while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(class="num">750);

  class="kw">return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
class="macro">#undef macroError
  }
class="kw">inline class="type">bool Configs(class="kw">const class="type">class="kw">string szInfo)
  {
    class="kw">const class="type">class="kw">string szList[] = {
      "PATH",
      "POINTSPERTICK",
      "VALUEPERPOINTS",
      "VOLUMEMINIMAL"
      };
    class="type">class="kw">string  szRet[];
    class="type">char    cWho;

    if (StringSplit(szInfo, &class="macro">#x27;=&class="macro">#x27;, szRet) == class="num">2)
    {
      StringTrimRight(szRet[class="num">0]);
      StringTrimLeft(szRet[class="num">1]);
      for (cWho = class="num">0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[class="num">0]) class="kw">break;
      class="kw">switch (cWho)
      {
        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]));

回放品种的量价参数回填

在自定义回放品种的配置解析里,case 3 负责把文本配置中的交易量步长写进模拟品种。它调用 CustomSymbolSetDouble,以 SYMBOL_VOLUME_STEP 为属性标识,把 szRet[1] 经 StringToDouble 转成的双精度值设进去,随后 return true 表示该项生效。 如果 szRet[0] 命中不了任何已定义的 case,会走到 Print 输出“Variable >>…<< undefined.”,提示该配置变量未实现;而当整段 szInfo 连基础格式校验都没过,则打印“Definition of configuratoin >>…<< invalid.”并返回 false,中断本次配置写入。 实盘接这段逻辑时,建议先在脚本里用 Print 把 szRet[0] 和 szRet[1] 打出来核对:EURUSD 回放常设 volume_step 为 0.01,若填成 0.1 会导致后续下单量校验失败。外汇与贵金属模拟回放属高风险验证环境,参数错配只影响回测真实性,不会直接造成实盘亏损,但结论仅具概率意义。

MQL5 / C++
         class="kw">return true;
         case class="num">3:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[class="num">1]));
               class="kw">return true;            
         }
         Print("Variable >>", szRet[class="num">0], "<< undefined.");
      }else
         Print("Definition of configuratoin >>", szInfo, "<< invalid.");
      class="kw">return false;
   }

「外汇回放还得等下一篇」

这套市场重放系统眼下能跑自定义品种,但外汇数据回放和模拟先别碰。实测里只要切到外汇品种,无论是回放还是模拟模式,都会直接触发数组超界错误,底层还有没处理完的市场机制。 作者明确说配置参数还没做过充分验证,改配置文件时手要稳,错了可能让订单逻辑直接崩。想看进度可以用压缩包里附的自定义品种先跑着,ZIP 体积约 12.9 MB。 外汇这块的坑正在修,下一篇才正式开讲。贵金属和外币对本身高波动高杠杆,等系统能跑外汇回测了,也建议先用小周期自定义数据验证逻辑,再上实盘品种。

常见问题

至少要把当日开仓量、未平仓兴趣、昨收等日内累计字段清零,否则回放图表会沿用真实品种的残留数据导致量价错乱。
用键值对逐字段读取并做类型校验,缺失项给默认兜底,避免直接强转字符串引发初始化中断。
可以,小布能读取你的回放配置并自动生成字段回填脚本草稿,你只需核对品种映射关系即可减少手工出错。
多半是tick时间戳没对齐或点值参数沿用原品种,回放前需单独重算合约规格并校准时间轴。
不行,外汇有swap和跨日会话差异,作者明确留到下一篇,直接套用会因隔夜利息字段缺失而失真。