在 MetaTrader 5 中的连续期货合约·综合运用
📈

在 MetaTrader 5 中的连续期货合约·综合运用

(3/3)·从日期覆盖到偏移算法,手把手在 MT5 里把多张期货拼成可交易图表

案例拆解新手友好 第 3/3 篇
很多人以为经纪商没给连续合约就只能干等,其实本地历史数据就能自己接。乌克兰股指期货的实战案例说明,重叠交易日和衔接缝隙都能靠指标解决。先把思路理顺,再写代码会省掉大量返工。

◍ 把历史报价灌进缓冲区的循环写法

自定义指标要在图表上重绘 K 线,核心是把 CopyRates 拿到的批次数据按偏移写进各自的缓冲数组。下面这段循环就是干这个活的:从 shift_array 起步,一直写到 shift_array+copied 之前,把 open/high/low/close 和颜色逐一落位。 [CODE] for(int j=shift_array;j<shift_array+copied;j++) { //--- write prices in buffers OpenBuffer[j]=rates[j-shift_array].open; HighBuffer[j]=rates[j-shift_array].high; LowBuffer[j]=rates[j-shift_array].low; CloseBuffer[j]=rates[j-shift_array].close; ColorCandlesColors[j]=Color; } shift_array=shift_array+copied; indicator_rendered=true; ChartRedraw(); } else { Print("Unable to get the symbol history data",simbUP); indicator_rendered=false; return(false); } //--- Simple addition end return(true); } //+------------------------------------------------------------------+ [/CODE] 逐行拆解:第 1 行 for(int j=shift_array;j<shift_array+copied;j++) 用 j 遍历本次拷贝的柱线区间,避免每次从 0 重画;rates[j-shift_array] 是因为 rates 数组从 0 开始存,而缓冲区 j 带全局偏移,相减才对得上。写完后 shift_array=shift_array+copied 把游标推到下次起点,置 indicator_rendered=trueChartRedraw() 强制重绘。 若 CopyRates 返回失败走 else 分支,会 Print 报错并 return(false),此时指标不渲染。外汇与贵金属行情在高波动时段可能取不到完整历史,这种分支在 MT5 里要实测确认不会静默空图。 开 MT5 新建指标把这段嵌进 OnCalculate,把 copied 打到注释里观察每帧增量,能直接验证缓冲写入是否有空洞。

MQL5 / C++
for(class="type">int j=shift_array;j<shift_array+copied;j++)
  {
  class=class="str">"cmt">//--- write prices in buffers
  OpenBuffer[j]=rates[j-shift_array].open;
  HighBuffer[j]=rates[j-shift_array].high;
  LowBuffer[j]=rates[j-shift_array].low;
  CloseBuffer[j]=rates[j-shift_array].close;
  ColorCandlesColors[j]=Color;
  }
  shift_array=shift_array+copied;
  indicator_rendered=true;
  ChartRedraw();
   }
 else
  {
  Print("Unable to get the symbol history data",simbUP);
  indicator_rendered=false;
  class="kw">return(false);
  }
class=class="str">"cmt">//---  Simple addition end
  class="kw">return(true);
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「偏移拼接在到期前10天动手」

做连续合约拼接时,简单追加是从旧品种结束那一刻直接接上;偏移追加则提前 10 天切入。逻辑落在 AdditionWithShift() 里:取 SYMBOL_EXPIRATION_TIME 后减掉 86400*10 秒,也就是 10 个自然日,作为拼接起点。 代码差异极小,核心就两行——把到期时间往前挪 10 天再比对。原文没贴全函数,只给了上下骨架和这两处偏移计算,完整实现在指标源文件里。 别硬把 SimpleAddition 和 AdditionWithShift 合并成一个函数。期货换算法、或拿去跑测试时,分开留着更稳,改一处不会炸掉另一条路径。 外汇与贵金属叠加此类拼接逻辑时波动叠加跳空风险偏高,回测前先在 MT5 用 EURUSD 连续报价验证偏移天数是否贴合你的经纪商到期规则。

MQL5 / C++
class=class="str">"cmt">//--- Simple addition end
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Addition With Shift                                              |
class=class="str">"cmt">//| Addition with Shift. Add in the indicator array only             |
class=class="str">"cmt">//| sibmUP symbol                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool AdditionWithShift(class="type">class="kw">string simbUP,class="type">class="kw">string simbDOWN,ENUM_TIMEFRAMES period,class="type">int Color)
  {
class=class="str">"cmt">//--- 
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
   .
   .
   .
   expiration_time_UP=class="type">int(SymbolInfoInteger(simbUP,SYMBOL_EXPIRATION_TIME))-class="num">86400*class="num">10;
   .
   .
   .
   expiration_time_DOWN=class="type">int(SymbolInfoInteger(simbDOWN,SYMBOL_EXPIRATION_TIME))-class="num">86400*class="num">10;
   .
   .
   .

把期货品种全量历史搬进缓存再画图

在 MT5 里做跨品种指标时,先确认历史到位比直接算缓冲区更稳妥。CheckLoadHistory() 的思路是:把指定品种从上市起点到到期日的全部 K 线,一次性塞进 tmp_rates 这个临时数组,成功了才把 good_history 置真,后续绘制逻辑才敢跑。 期货品类寿命短,全量 CopyRates 通常不会撑爆内存——这是它能粗暴预加载的前提。对外汇或贵金属品种则相反,十年以上日线若也照此全拉,缓存占用可能陡增,实盘前建议在参数里限制时间窗。 下面这段是函数的核心骨架,逐行拆一下关键点: MqlRates tmp_rates[] 声明汇率缓存,OHLC 会落在这里;start_time 与 expiration_time 分别用 SymbolInfoInteger 抓品种起止交易时间;CopyRates 以起止时间为界整段拷贝,返回值大于 0 即认为成功并写 good_history=true,否则写 false。注意函数末尾无论成败都 return(true),调用方不能只靠返回值判断,必须读 good_history 标志。 外汇与贵金属杠杆高、跳空频繁,历史缺失会导致指标重绘异常,上线前务必在策略测试器里用真实品种跑一遍该函数的返回与标志状态。

MQL5 / C++
class=class="str">"cmt">//--- Addition With Shift end
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Request to receive all history from a trade server                |
class=class="str">"cmt">//| Request to recieve all history from a trade server                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CheckLoadHistory(class="type">class="kw">string symbol,ENUM_TIMEFRAMES period)
  {
  class="type">MqlRates tmp_rates[];       class=class="str">"cmt">// the Open, High, Low and Close prices will be copied in the rates[]array 
  class="type">class="kw">datetime start_time;        class=class="str">"cmt">// start time of the instrument trades
  class="type">class="kw">datetime expiration_time;   class=class="str">"cmt">// expiration time of the instrument trade
  start_time=class="type">int(SymbolInfoInteger(symbol,SYMBOL_START_TIME));
  expiration_time=class="type">int(SymbolInfoInteger(symbol,SYMBOL_EXPIRATION_TIME));
  if(CopyRates(symbol,period,start_time,expiration_time,tmp_rates)>class="num">0)
    {
      good_history=true;
    }
  else
    {
      good_history=false;
    }
class=class="str">"cmt">//--- 
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 用 OnTimer 驱动拼接指标的运行节奏

前面已经备好了两种拼接逻辑和历史数据加载函数,现在改 OnTimer() 才是让指标真正跑起来的关键。编译后挂到图表时,建议挑一个未平仓的品种,周期设为 H1,这样不会和实盘跳动互相干扰。 代码里第一道闸是 indicator_rendered 标志:一旦渲染完成直接 return,避免定时器反复重绘吃资源。good_history 为 true 才进入拼接分支,否则就循环调用 CheckLoadHistory 把各品种 D1 数据补全——这套守卫逻辑保证了数据不齐时不瞎画。 拼接分支用 switch(gluing_type) 分流:simple_addition 与 addition_with_shift 各自跑循环,对 numder_futures_gluing 个相邻品种调用对应函数。每次用 MathRand() 取随机数再对 PlotIndexGetInteger(0,PLOT_COLOR_INDEXES)-1 取模,得到随机颜色索引 t,让相邻拼接段在图上可视区分。 把两种拼接方法的 SYNT 指标模板都加载到图表后,并排看差异最直接。实测中 D1 周期下相邻期货合约拼接的跳空处理,带 shift 的版本在换月点偏移更平滑,普通加法在价差扩大时容易画出陡阶。外汇与贵金属品种叠加此类指标仍属高风险,信号仅作结构参考。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Timer function                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
  {
   if(indicator_rendered==true) class="kw">return;
   if(good_history==true)
     {
      class="type">int t=class="num">0;       class=class="str">"cmt">// class="type">color || class="type">color
      class="type">int number;
      class="kw">switch(gluing_type)
        {
         case simple_addition:
           for(class="type">int n=class="num">0;n<numder_futures_gluing;n++)
             {
              class=class="str">"cmt">//--- get the random number
              number=MathRand();
              class=class="str">"cmt">//--- get the class="type">color index as the modulo
              t=number%(PlotIndexGetInteger(class="num">0,PLOT_COLOR_INDEXES)-class="num">1);
              SimpleAddition(SymbolName(n,true),SymbolName(n+class="num">1,true),PERIOD_D1,t);
             }
           class="kw">break;
         case addition_with_shift:
           for(class="type">int n=class="num">0;n<numder_futures_gluing;n++)
             {
              class=class="str">"cmt">//--- get random number
              number=MathRand();
              class=class="str">"cmt">//--- get the class="type">color index as the modulo
              t=number%(PlotIndexGetInteger(class="num">0,PLOT_COLOR_INDEXES)-class="num">1);
              AdditionWithShift(SymbolName(n,true),SymbolName(n+class="num">1,true),PERIOD_D1,t);
             }
           class="kw">break;
        }
     }
   else
     {
      for(class="type">int n=class="num">0;n<numder_futures_gluing;n++)
        {
         CheckLoadHistory(SymbolName(n,true),PERIOD_D1);
        }
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「把均线类指标挂进 SYNT 的实操路径」

在 MT5 的「浏览器」窗口里展开「自定义指标」,再进「例子」列表,挑出目标指标拖进 SYNT 容器,于「参数」选卡的下拉框选「以前的指标数据」,就能完成拼接。这套做法的核心限制是:并非所有指标都能无错挂接,目前验证过可稳定在 SYNT 内启动的只有 AMA、BB、自定义移动平均、DEMA、FrAMA、TEMA 这几类均线系。 外汇与贵金属品种叠加这类拼接指标时,跨周期与跨品种重算会带来滑点放大,属于高风险用法,实盘前务必在策略测试器跑一遍。SYNT 曾演示过同时拼接三个不同品种的自定义均线,用于观察期货联动,但换到 XAUUSD 上要警惕点差突变导致的均线错位。 OnCalculate 的首调逻辑决定了拼接指标如何接收上游数据,下面这段是标准签名,参数含义直接决定你能否正确拿到前级输出。

MQL5 / C++
class="type">int OnCalculate(class="kw">const class="type">int rates_total,      class=class="str">"cmt">// size of the array price[]
                    class="kw">const class="type">int prev_calculated,  class=class="str">"cmt">// calculated bars during the previous call
                    class="kw">const class="type">int begin,            class=class="str">"cmt">// tangible data starting point
                    class="kw">const class="type">class="kw">double& price[]       class=class="str">"cmt">// calculation array
   );

连续期货粘合在回测里的实际边界

日线图上翻旧合约的连续期货价格,比盯单个交割月份直观得多。指标数量虽受限制,但跟踪连续期货的价格行为本身就能省掉手工拼接的麻烦。 需要明确一点:这类粘合本质是自定义指标,没法直接在策略测试器里像普通品种那样一键回测。想做多年区间整体优化,只能按每种期货设定日期区间分别处理,或借助外部粘合工具生成连续符号后再挂 EA。 外汇和贵金属杠杆高、跳空频繁,连续期货的拼接方式(简单加法或偏移加法)只反映概率上的走势近似,不保证和历史真实成交一致。真要验证,开 MT5 把 synt.mq5 丢进指标目录,切日线看旧品种粘合效果最实在。

把拼接监控交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到合约重叠与过渡周期提示,你只需专注参数微调。

常见问题

因为不同月份合约的实际交易时段有交叉,例如旧合约未到期新合约已开盘,MT5 里会同时出现两段可交易数据,需要人工或指标决定如何接续。
简单追加保留真实价格但图表有间隙,偏移追加用过渡周期换平滑度却引入偏差,短线对缝隙敏感时倾向偏移,但需明白价格已非原值。
可以,小布在品种页标记重叠与胶合区,省去手动翻市场报价核对降序排列的步骤,不过最终拼接逻辑仍由你设定的指标控制。
它按计时器触发历史预加载与重绘,避免每次 tick 都重算,保证多合约数据接续时客户端负载可控。
参数下限是 2,少于 2 就没有接续意义,实务中根据重叠长度选 2 到若干张,过多会拉长预加载时间。