在 MetaTrader 5 中的连续期货合约·进阶篇
(2/3)· 面对期货日期重叠与图表断裂,手动接图总出错?这套指标化拼接值得细看
把拼接类型做成下拉框
想在 MT5 指标参数里用下拉列表选拼接方式,核心是先声明一个全局枚举,再把它挂到 input 参数上。原文给出的 ENUM_GLUING_TYPE 只放了 simple_addition 和 addition_with_shift 两项,编译后(F7)加挂指标,选项里就会直接出现下拉控件,不用手敲字符串。 顺带把指标描述写进 property,首次加挂或改属性时,「一般」栏会显示这几行字:用于多个期货拼接、画在最近期货窗口、取自选窗口前 N 个品种。描述纯展示,不影响计算,但能省掉使用者猜用途的成本。 这个 SYNT 指标用 DRAW_COLOR_CANDLES 画彩色蜡烛,占 4 个价格缓冲加 1 个颜色索引缓冲,共 5 个 buffer;默认 numder_futures_gluing=2,意味着至少得有两个品种才拼得起来。它只在加载或历史追加时重算,不跟每个 tick,日线趋势分析够用,也省 CPU。 别把枚举当摆设 下拉枚举值一旦改了顺序或改名,旧图表里存的预设会错位;上线前先在一张测试图上手动切一遍两种拼接模式确认缓冲绑定没乱。
class="macro">#class="kw">property indicator_separate_window class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Splicing types enumeration | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_GLUING_TYPE { simple_addition, class=class="str">"cmt">// simple addition addition_with_shift class=class="str">"cmt">// addition with shift }; class=class="str">"cmt">//--- class="kw">input parameters class="kw">input ENUM_GLUING_TYPE gluing_type=simple_addition; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| version "class="num">1.00": The timer history swapping | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property description "Indicator for several futures splicing." class="macro">#class="kw">property description "Is drawn in the latest futures window" class="macro">#class="kw">property description "Uses N first symbols for drawing" class="macro">#class="kw">property description "which were taken from the \"Market review\"." class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property description "taken from the \"Market review\"." class=class="str">"cmt">//--- indicator settings class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_buffers class="num">5 class="macro">#class="kw">property indicator_plots class="num">1 class=class="str">"cmt">//--- plot Bars class="macro">#class="kw">property indicator_label1 "SYNT" class="macro">#class="kw">property indicator_type1 DRAW_COLOR_CANDLES class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| enumeration of splicing methods | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- class="kw">input parameters class="kw">input class="type">int numder_futures_gluing=class="num">2; class="kw">input ENUM_GLUING_TYPE gluing_type=simple_addition; class="macro">#class="kw">property indicator_style1 STYLE_SOLID
◍ 把多根 K 线拼进一个缓冲区的初始化写法
想在 MT5 里做一个把若干根 K 线「缝合」成一根自定义蜡烛的指标,第一步是把 6 个缓冲区绑好:开高低收各占一个 INDICATOR_DATA,颜色索引占一个 INDICATOR_COLOR_INDEX,还有一个 INDICATOR_CALCULATIONS 专门放历史加载标记。 代码里 numder_futures_gluing 默认是 2,意味着指标倾向把后续 2 根未来K线并到当前合成烛里;gluing_type 默认 simple_addition,即简单相加式拼接。外汇与贵金属波动剧烈,这种拼接仅用于形态观察,不预示方向。 所有价格缓冲区都必须 ArraySetAsSeries(...,true),否则索引会按普通数组从 0 递增,导致新 K 线出现在数组头而不是尾部,画图直接错位。IndicatorSetInteger(INDICATOR_DIGITS,0) 把显示精度锁成整数,合成烛价格差较大时不会刷出一堆小数噪音。 别把缓冲绑错类型 绑成 INDICATOR_DATA 的计算缓冲不会被画图调用,而该给 COLOR_INDEX 的若错绑成 DATA,MT5 会初始化报错 INIT_FAILED。开 MT5 新建指标时照上面 6 行 SetIndexBuffer 抄一遍最稳。
class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double OpenBuffer[]; class="type">class="kw">double HighBuffer[]; class="type">class="kw">double LowBuffer[]; class="type">class="kw">double CloseBuffer[]; class="type">class="kw">double ColorCandlesColors[]; class="type">class="kw">double LoadHistory[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| enumeration of splicing methods | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- indicator buffers mapping class=class="str">"cmt">//--- indicator buffers mapping SetIndexBuffer(class="num">0,OpenBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">1,HighBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">2,LowBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">3,CloseBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">4,ColorCandlesColors,INDICATOR_COLOR_INDEX); SetIndexBuffer(class="num">5,LoadHistory,INDICATOR_CALCULATIONS); class=class="str">"cmt">//--- set buffer indexing as timeseries class=class="str">"cmt">//--- set buffer indexing as in timeseries ArraySetAsSeries(OpenBuffer,true); ArraySetAsSeries(HighBuffer,true); ArraySetAsSeries(LowBuffer,true); ArraySetAsSeries(CloseBuffer,true); ArraySetAsSeries(ColorCandlesColors,true); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); class="kw">input class="type">int numder_futures_gluing=class="num">2; class="kw">input ENUM_GLUING_TYPE gluing_type=simple_addition; class=class="str">"cmt">//--- symbol name class="type">class="kw">string s_symbol; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input ENUM_GLUING_TYPE gluing_type=simple_addition; class=class="str">"cmt">//--- symbol name class="type">class="kw">string s_symbol; class="type">int shift_array=class="num">0; class="type">bool good_history=false; class=class="str">"cmt">//history is not prepared class="type">bool indicator_rendered=false; class=class="str">"cmt">// indicator is not drawn class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ ArraySetAsSeries(CloseBuffer,true); ArraySetAsSeries(ColorCandlesColors,true); class=class="str">"cmt">//--- set accuracy IndicatorSetInteger(INDICATOR_DIGITS,class="num">0); class=class="str">"cmt">//--- set drawing line empty value
「给合成指标配色与初始化定时器」
在 MT5 自定义指标里,PlotIndexSetInteger 用 PLOT_COLOR_INDEXES 设成 9,意味着这条绘图缓冲允许挂 9 种颜色索引,分别对应 clrBlue 到 clrDimGray 等九档。DataWindow 里显示的标签由 PlotIndexSetString 的 PLOT_LABEL 决定,把品种名拼上 Open/High/Low/Close 四段,交易者一眼能分清哪根线对应哪个价位。 初始化收尾前用 EventSetTimer(3) 起一个 3 秒周期定时器,再调 MathSrand(GetTickCount()) 用系统节拍数播随机种子——这两行决定了后续如果用到随机扰动,每次加载指标都不会复现同一序列。外汇与贵金属波动剧烈,这类定时器驱动的重绘逻辑可能带来视觉跳动,实盘前务必在策略测试器里跑一遍。 OnCalculate 开头有个硬校验:numder_futures_gluing 小于等于 1 就直接 Comment 提示“至少选 N 个品种”并 return(0)。这说明该合成指标本质是把多个品种黏合显示,只挂一个符号毫无意义,最少得选 2 个以上才能进主计算。
PlotIndexSetDouble(class="num">0,PLOT_EMPTY_VALUE,class="num">0.0); class=class="str">"cmt">//--- set labels for the line || displayed name in the DataWindow PlotIndexSetString(class="num">0,PLOT_LABEL,s_symbol+" Open;"+s_symbol+" High;"+s_symbol+" Low;"+s_symbol+" Close"); IndicatorSetString(INDICATOR_SHORTNAME,"SYNT"); class=class="str">"cmt">//--- set number of colors in class="type">color buffer || number of colors in the buffer PlotIndexSetInteger(class="num">0,PLOT_COLOR_INDEXES,class="num">9); class=class="str">"cmt">//--- set line class="type">color || set the line class="type">color PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,class="num">0,clrBlue); PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,class="num">1,clrOrange); PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,class="num">2,clrRed); PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,class="num">3,clrGreen); PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,class="num">4,clrPink); PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,class="num">5,clrIndigo); PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,class="num">6,clrPaleVioletRed); PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,class="num">7,clrDarkViolet); PlotIndexSetInteger(class="num">0,PLOT_LINE_COLOR,class="num">8,clrDimGray); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); EventSetTimer(class="num">3); class=class="str">"cmt">//--- random number generator initializor MathSrand(GetTickCount()); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); if(numder_futures_gluing<=class="num">1) { class="type">class="kw">string comm=StringFormat("For the indicator choose not less than %d symbols",numder_futures_gluing); Comment(comm); class="kw">return(class="num">0); } class="kw">return(rates_total);
渲染标记下的提前返回陷阱
在自定义指标里用 indicator_rendered 标记控制流程时,最常见的写法是在函数头部直接判断:若已渲染就 return(rates_total)。 [CODE] if(indicator_rendered==true) return(rates_total); //--- return value of prev_calculated for next call return(rates_total); if(indicator_rendered==true) return(rates_total); //--- if calculations were started the first time //--- or if we need to calculate the indicator for two or more bars (changes in history) //--- remember, that the "0" bar - is the left-most
| if(prev_calculated==0 | rates_total>prev_calculated+1) |
|---|
{ } //--- return value of prev_calculated for next call return(rates_total); [/CODE] 逐行看:第1行若 indicator_rendered 为真,直接把 rates_total 交回去,后续计算全跳过;第3行是无条件返回,若它写在判断之前,上面那个 if 根本没机会执行。第4行又重复了一次渲染判断返回,属于冗余。第8行开始处理首次计算或历史重算(prev_calculated==0 或新K线数多于上回+1),但代码块体是空的,实际什么都没算。 空块加无条件 return 的组合,会让 MT5 每次调用都拿到 rates_total 却从不填充缓冲区,图表可能只画出一个 flat 线。外汇与贵金属行情跳空频繁,prev_calculated 与 rates_total 差值偶尔会大于1,这种空处理会漏掉重算。 开 MT5 把这段贴进 OnInit 之后的 OnCalculate,故意让 indicator_rendered 初值为 false,观察 Data Window 里指标缓冲区是否全为 EMPTY_VALUE,就能复现这个静默失效。
if(indicator_rendered==true) class="kw">return(rates_total); class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="kw">return(rates_total); if(indicator_rendered==true) class="kw">return(rates_total); class=class="str">"cmt">//--- if calculations were started the first time class=class="str">"cmt">//--- or if we need to calculate the indicator for two or more bars(changes in history) class=class="str">"cmt">//--- remember, that the "class="num">0" bar - is the left-most if(prev_calculated==class="num">0 || rates_total>prev_calculated+class="num">1) { } class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="kw">return(rates_total);
◍ 缓存区初始化要放进 OnCalculate 而非 OnInit
SYNT 指标的四个缓存区(开高低收)绑定在动态数组上,首次加载时必须强制清零,否则图表上可能画出错位或断裂的线条。很多新手习惯在 OnInit 里写初始化,但 OnInit 只在指标被手动「更新」或加载时触发一次,而 OnCalculate 在每根价格跳动、历史回补时都会进来,覆盖更全。 从事件触发机制看,OnCalculate 在任何报价到达时都会跑,OnInit 不会。若只在 OnInit 初始化,遇到切换周期或重连后缓存区未重建,指标拼接会失败。实测若用 EMPTY_VALUE 去初始化缓存区,SYNT 上根本无法把多个子指标拼到同一面板。 下面这段是在 OnCalculate 开头做的标准处理:当 prev_calculated 为 0(首次计算)或 rates_total 比已算数多 1 根以上(补历史)时,把四个缓冲区全部置 0。注意注释里写的「0 号 bar 是最左侧」,这和 MT5 内部索引方向相反,写错容易把数据填到右边去。
class=class="str">"cmt">//--- remember, that the "class="num">0" bar - is the left-most if(prev_calculated==class="num">0 || rates_total>prev_calculated+class="num">1) { class=class="str">"cmt">//--- arrays initialization ArrayInitialize(OpenBuffer,class="num">0); ArrayInitialize(HighBuffer,class="num">0); ArrayInitialize(LowBuffer,class="num">0); ArrayInitialize(CloseBuffer,class="num">0); } class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="kw">return(rates_total);
「拼接两个近月合约的到期窗口」
把上下两个期货拼成连续序列,核心是先拿到各自的到期时间。原文以 UX-9.13、UX-12.13、UX-3.14 三个循环为例:UX-9.13 从 2013.03.15 到 2013.09.16,UX-12.13 从 2013.06.17 到 2013.12.16,UX-3.14 从 2013.09.16 到 2014.03.17;在 2013.12.25 这个真实日历日,UX-3.14 仍处活跃状态。 SimpleAddition 函数接收上边合约 simbUP、下边合约 simbDOWN、周期 period 和绘制颜色 Color。若下边合约为空串,则退回用上边合约的 SYMBOL_START_TIME 当截断起点,否则取它的到期时间。 代码里先用 SymbolInfoInteger 抓到期时间,若上边合约还没到期就强制用 TimeLocal 截断到当下,避免去拉未来的空数据。随后用 CopyRates 从 expiration_time_DOWN 到 expiration_time_UP 把 OHLC 拷进 rates[],copied>0 才继续后续拼接。 外汇与贵金属期货叠加这类连续化处理有跳空与流动性断裂风险,拼接结果只反映历史窗口,不预示后续价差走向。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Timer function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTimer() { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Simple addition | class=class="str">"cmt">//| Simple addition. Add in the indicator array | class=class="str">"cmt">//| sibmUP symbol | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool SimpleAddition(class="type">class="kw">string simbUP,class="type">class="kw">string simbDOWN,ENUM_TIMEFRAMES period,class="type">int Color) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Simple addition | class=class="str">"cmt">//| Simple addition. Add in the indicator array | class=class="str">"cmt">//| sibmUP symbol | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool SimpleAddition(class="type">class="kw">string simbUP,class="type">class="kw">string simbDOWN,ENUM_TIMEFRAMES period,class="type">int Color)class=class="str">"cmt">////// { class="type">class="kw">datetime expiration_time_UP; class=class="str">"cmt">// simbUP symbol expiration date class="type">class="kw">datetime expiration_time_DOWN; class=class="str">"cmt">// simbDOWN expiration date expiration_time_UP=class="type">int(SymbolInfoInteger(simbUP,SYMBOL_EXPIRATION_TIME)); if(expiration_time_UP>TimeLocal()) { expiration_time_UP=TimeLocal(); } if(simbDOWN!="") { expiration_time_DOWN=class="type">int(SymbolInfoInteger(simbDOWN,SYMBOL_EXPIRATION_TIME)); } else { expiration_time_DOWN=class="type">int(SymbolInfoInteger(simbUP,SYMBOL_START_TIME)); } class=class="str">"cmt">//--- Open, High, Low and Close prices will be copied in the rates[] array class="type">MqlRates rates[]; ArraySetAsSeries(rates,true); class="type">int copied=class="num">0; class=class="str">"cmt">//--- copied number copied=CopyRates(simbUP,period,expiration_time_DOWN,expiration_time_UP,rates); if(copied>class="num">0) {