用于 Renko 制图的指标·进阶篇
(2/3)· 从收盘价比较到箱体尺寸设定,手把手拆解 Renko 砖块生成的代码骨架
「砖形图与ZigZag的绘制参数入口」
在 MT5 自定义指标里,砖形(Renko 类方块)和 ZigZag 的视觉呈现完全由一组 input 变量控制。把 shadow_print 设为 true,会在主图叠加影线阴影;filter_number 默认 0,代表反转所需砖块数走内置逻辑,调成 3 或 5 会明显改变砖形翻转密度。 ZigZag 部分有独立开关:zig_zag 控制主图是否画折线,zig_zag_shadow 决定高低点是否落在真实价格极值上。线宽 zig_zag_width 给 2,上行色 clrBlue、下行色 clrRed,肉眼区分趋势段够用。 square_draw 为 true 时主图才渲染砖块,square_fill 控制实心填充,square_width 与 frame_width 都默认 2 像素。frame_draw 管砖框,frame_color_up 用 clrBlue——这些参数直接决定你盯盘时图形是清爽还是拥挤。 外汇与贵金属波动剧烈,这类可视化仅辅助判势,实际反转概率随品种与周期漂移,请先在策略测试器用历史数据验证再上实盘。
input class="type">bool shadow_print=true; class=class="str">"cmt">// Show shadows input class="type">int filter_number=class="num">0; class=class="str">"cmt">// Bricks number needed to reversal input class="type">bool zig_zag=true; class=class="str">"cmt">// Whether ZigZag should be drawn on the main chart input class="type">bool zig_zag_shadow=true; class=class="str">"cmt">// Draw ZigZag at highs and lows of the price input class="type">int zig_zag_width=class="num">2; class=class="str">"cmt">// ZigZag line width input class="type">color zig_zag_color_up=clrBlue; class=class="str">"cmt">// ZigZag up line class="type">color input class="type">color zig_zag_color_down=clrRed; class=class="str">"cmt">// ZigZag down line class="type">color input class="type">bool square_draw=true; class=class="str">"cmt">// Whether bricks should be drawn on the main chart input class="type">color square_color_up=clrBlue; class=class="str">"cmt">// Up brick class="type">color on the main chart input class="type">color square_color_down=clrRed; class=class="str">"cmt">// Down brick class="type">color on the main chart input class="type">bool square_fill=true; class=class="str">"cmt">// Brick filling on the main chart input class="type">int square_width=class="num">2; class=class="str">"cmt">// Brick line width on the main chart input class="type">bool frame_draw=true; class=class="str">"cmt">// Whether to draw frames of the bricks input class="type">int frame_width=class="num">2; class=class="str">"cmt">// Brick frame line width input class="type">color frame_color_up=clrBlue; class=class="str">"cmt">// Up brick frames class="type">color
◍ 砖型图的缓冲与变量怎么铺
在 MT5 里手搓 Renko(砖型)指标,第一步是把各类数组先声明清楚。原文用 input color 控制下跌砖边框为 clrRed,其余上涨/下跌砖色由 RENKO_color 缓冲区分,这种分离让你在肉眼复盘时一眼能分出多空倾向。 核心数据走五个 INDICATOR_DATA 缓冲:RENKO_open / high / low / close / color,全部用 ArraySetAsSeries(...,true) 改成时间序列排列,最新砖在索引 0。辅助计算缓冲(Price、Date、Price_high、Price_low 等)标为 INDICATOR_CALCULATIONS,不参与绘图,只供 OnCalculate 内部搬运裸 K 数据。 全局变量里 obj 记图形对象数、a 计砖块数、bars 存柱数,date_start / date_stop / date_change 配合处理跨周期时间跳变。外汇与贵金属波动剧烈,砖型参数设错可能让历史回看完全失真,上机前先开策略测试器用 2023 年 EURUSD M5 跑一遍验证缓冲映射。 下面这段是 OnInit 里缓冲绑定的最小可运行片段,注意索引号与缓冲类型必须一一对应,漏掉 INDICATOR_COLOR_INDEX 会导致砖色不渲染。
input class="type">color frame_color_down=clrRed; class=class="str">"cmt">// Down brick frames class="type">color class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double RENKO_open[]; class="type">class="kw">double RENKO_high[]; class="type">class="kw">double RENKO_low[]; class="type">class="kw">double RENKO_close[]; class="type">class="kw">double RENKO_color[]; class="type">class="kw">double Price[]; class=class="str">"cmt">// copy price data to the buffer class="type">class="kw">double Date[]; class=class="str">"cmt">// copy data to the buffer class="type">class="kw">double Price_high[]; class=class="str">"cmt">// copy high prices to the buffer class="type">class="kw">double Price_low[]; class=class="str">"cmt">// copy low prices to the buffer class=class="str">"cmt">//--- calculation buffer arrays class="type">class="kw">double up_price[]; class=class="str">"cmt">// up brick price class="type">class="kw">double down_price[]; class=class="str">"cmt">// down brick price class="type">char type_box[]; class=class="str">"cmt">// brick type(up, down) class="type">class="kw">datetime time_box[]; class=class="str">"cmt">// brick copy time class="type">class="kw">double shadow_up[]; class=class="str">"cmt">// up high price class="type">class="kw">double shadow_down[]; class=class="str">"cmt">// down low price class="type">int number_id[]; class=class="str">"cmt">// Index of Price_high and Price_low arrays class=class="str">"cmt">//--- calculation global variables class="type">int obj=class="num">0; class=class="str">"cmt">//variable for storing number of graphics objects class="type">int a=class="num">0; class=class="str">"cmt">// variable to count bricks class="type">int bars; class=class="str">"cmt">// number of bars class="type">class="kw">datetime date_stop; class=class="str">"cmt">// current data class="type">class="kw">datetime date_start; class=class="str">"cmt">// start date variable, for calculations class="type">bool date_change; class=class="str">"cmt">// variable for storing details about time changes class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping SetIndexBuffer(class="num">0,RENKO_open,INDICATOR_DATA); ArraySetAsSeries(RENKO_open,true); SetIndexBuffer(class="num">1,RENKO_high,INDICATOR_DATA); ArraySetAsSeries(RENKO_high,true); SetIndexBuffer(class="num">2,RENKO_low,INDICATOR_DATA); ArraySetAsSeries(RENKO_low,true); SetIndexBuffer(class="num">3,RENKO_close,INDICATOR_DATA); ArraySetAsSeries(RENKO_close,true); SetIndexBuffer(class="num">4,RENKO_color,INDICATOR_COLOR_INDEX); ArraySetAsSeries(RENKO_color,true); class=class="str">"cmt">//--- SetIndexBuffer(class="num">5,Price,INDICATOR_CALCULATIONS); class=class="str">"cmt">// initialize price buffer
缓冲绑定与起算日的自适应收缩
指标初始化阶段先把 6/7/8 号缓冲区分别绑给 Date、Price_high、Price_low,类型都设成 INDICATOR_CALCULATIONS,意味着这三组数组只参与内部运算、不直接往图表上画。PlotIndexSetInteger(0,PLOT_SHOW_DATA,false) 进一步关掉主图数值面板,避免 ABCR 这类辅助指标污染报价窗。 短名用 "ABCR "+IntegerToString(magic_numb) 动态拼接,多实例加载时靠 magic_numb 区分;精度直接吃 _Digits,省去手动对齐小数位的麻烦。 真正容易踩坑的是 func_calc_date_start:它用 Bars() 反推实际可得 K 线数,若初始日期拉得太早、超出 Price[] 数组容量,就按 864000 秒(即 10 天)为步长往前挪 input_data_start,直到 Bars_Size 不大于 Array_Size 才停。外汇与贵金属杠杆高、跳空频繁,历史样本不足时这套收缩逻辑能避免数组越界崩指标,但也可能悄悄砍掉你预设的回测起点。 func_copy_price 的 price_type 用 0~3 映射 Close/Open/High/Low,内部靠静态数组 price_interim 暂存副本、bars_to_copy 记条数,调用前先确认 result_copy 返回非负再处理,否则复制失败会被静默吞掉。
SetIndexBuffer(class="num">6,Date,INDICATOR_CALCULATIONS); class=class="str">"cmt">// initialize data buffer SetIndexBuffer(class="num">7,Price_high,INDICATOR_CALCULATIONS); class=class="str">"cmt">// initialize high price SetIndexBuffer(class="num">8,Price_low,INDICATOR_CALCULATIONS); class=class="str">"cmt">// initialize low price class=class="str">"cmt">//--- set data which will not be drawn PlotIndexSetDouble(class="num">0,PLOT_EMPTY_VALUE,class="num">0); class=class="str">"cmt">//--- set the indicator appearance IndicatorSetString(INDICATOR_SHORTNAME,"ABCR "+IntegerToString(magic_numb)); class=class="str">"cmt">// indicator name class=class="str">"cmt">//--- display accuracy IndicatorSetInteger(INDICATOR_DIGITS,_Digits); class=class="str">"cmt">//--- prohibit display of the results of the indicator current values PlotIndexSetInteger(class="num">0,PLOT_SHOW_DATA,class="kw">false); class=class="str">"cmt">//--- assign start date variable value date_start=first_date_start; class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Func Calculate Date Start | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">datetime func_calc_date_start(class="type">class="kw">datetime input_data_start,class=class="str">"cmt">// initially start date set class="type">class="kw">datetime data_stop) class=class="str">"cmt">// calculation end date(current date) class=class="str">"cmt">//--- { class="type">int Array_Size=ArraySize(Price); class="type">int Bars_Size=Bars(_Symbol,time_frame,input_data_start,data_stop); for(;Bars_Size>Array_Size;input_data_start+=class="num">864000) class=class="str">"cmt">// class="num">864000 = class="num">10 days { Bars_Size=Bars(_Symbol,time_frame,input_data_start,data_stop); } class="kw">return(input_data_start); class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Func Copy Price | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool func_copy_price(class="type">class="kw">double &result_array[], ENUM_TIMEFRAMES period,class=class="str">"cmt">// Timeframe class="type">class="kw">datetime data_start, class="type">class="kw">datetime data_stop, class="type">char price_type) class=class="str">"cmt">// class="num">0-Close, class="num">1-Open, class="num">2-High, class="num">3-Low { class=class="str">"cmt">//--- class="type">int x=class="kw">false; class=class="str">"cmt">// Variable for answering class="type">int result_copy=-class="num">1; class=class="str">"cmt">// copied data number class=class="str">"cmt">//--- class="kw">static class="type">class="kw">double price_interim[]; class=class="str">"cmt">// Temporal dynamic array for storing copied data class="kw">static class="type">int bars_to_copy; class=class="str">"cmt">// number of bars to copy
「增量拷贝时四种价格的计数分轨」
在 MT5 自定义指标里做历史数据增量加载,最容易被忽略的是 Close / Open / High / Low 四条价格序列的已拷贝根数必须分开记账。上面用 static int 声明了 bars_copied_0 到 bars_copied_3 外加一个通用 bars_copied,分别对应四种价格类型的起始日以来已复制柱数,避免互相串扰。 一旦检测到起始日期变更(date_change==true),代码通过 ZeroMemory 把 price_interim、bars_to_copy 以及全部 bars_copied_* 清零。这一步很关键:若不清零,旧计数会叠加到新时间窗口,导致 CopyClose 等函数少拉或多拉数据,回测时可能悄无声息地错位。 实际拷贝前先用 Bars(_Symbol,period,data_start,data_stop) 取当前时间范围内总柱数,再减去对应 price_type 下已拷贝的柱数得到本次需补的 bars_to_copy。注意当 bars_copied 非零(非首次)时,代码令 bars_copied-- 且 bars_to_copy++,相当于把上一根重叠柱让出来重拷,保证序列连续不丢边界。 最后按 price_type 走 switch 调 CopyClose / CopyOpen / CopyHigh / CopyLow,目标数组用 ArrayResize 按 bars_to_copy 动态扩容。外汇与贵金属行情跳空频繁,这种分轨计数逻辑能降低重连后数据断裂的概率,但这类品种杠杆高、滑点风险大,任何数据层优化都不构成方向判断依据。
class="kw">static class="type">int bars_copied_0; class=class="str">"cmt">// number of copied bars from Close start date class="kw">static class="type">int bars_copied_1; class=class="str">"cmt">// number of copied bars from Open start date class="kw">static class="type">int bars_copied_2; class=class="str">"cmt">// number of copied bars from High start date class="kw">static class="type">int bars_copied_3; class=class="str">"cmt">// number of copied bars from Low start date class="kw">static class="type">int bars_copied; class=class="str">"cmt">// number of copied bars from the common variable start date class=class="str">"cmt">//--- variables reset due to changes in a start date if(date_change==true) { ZeroMemory(price_interim); ZeroMemory(bars_to_copy); ZeroMemory(bars_copied_0); ZeroMemory(bars_copied_1); ZeroMemory(bars_copied_2); ZeroMemory(bars_copied_3); ZeroMemory(bars_copied); } class=class="str">"cmt">//--- get an information about the current bars number on the timeframe bars_to_copy=Bars(_Symbol,period,data_start,data_stop); class=class="str">"cmt">//--- assign a copied function value to a common variable class="kw">switch(price_type) { case class="num">0: class=class="str">"cmt">//--- Close bars_copied=bars_copied_0; class="kw">break; case class="num">1: class=class="str">"cmt">//--- Open bars_copied=bars_copied_1; class="kw">break; case class="num">2: class=class="str">"cmt">//--- High bars_copied=bars_copied_2; class="kw">break; case class="num">3: class=class="str">"cmt">//--- Low bars_copied=bars_copied_3; class="kw">break; } class=class="str">"cmt">//--- calculate number of bars required to be copied bars_to_copy-=bars_copied; class=class="str">"cmt">//--- if it is not the first time the data has been copied if(bars_copied!=class="num">0) { bars_copied--; bars_to_copy++; } class=class="str">"cmt">//--- change the size of the recieving array ArrayResize(price_interim,bars_to_copy); class=class="str">"cmt">//--- copy data to the recieving array class="kw">switch(price_type) { case class="num">0: class=class="str">"cmt">//--- Close { result_copy=CopyClose(_Symbol,period,class="num">0,bars_to_copy,price_interim); } class="kw">break; case class="num">1: class=class="str">"cmt">//--- Open { result_copy=CopyOpen(_Symbol,period,class="num">0,bars_to_copy,price_interim); } class="kw">break; case class="num">2: class=class="str">"cmt">//--- High {
◍ 用中间数组接管多类型报价拷贝
这段逻辑把 High、Low 等不同价格类型的批量拷贝收口到同一个流程里:先按 price_type 分支调用 CopyHigh / CopyLow 等,把数据塞进临时数组 price_interim,返回实际拷贝根数 result_copy。 若 result_copy 不等于 -1,说明本次拷贝成功,立刻用 ArrayCopy 把临时数组搬进主结果数组 result_array,偏移量由已处理根数 bars_copied 决定,再把 x 置为 true、累加 bars_copied。外汇与贵金属行情跳空频繁,拷贝失败返回 -1 的概率不低,必须靠这个判断拦掉脏数据。 随后又用一次 switch 把最终的 bars_copied 按类型写回 bars_copied_0~3,方便上层知道 Close/Open/High/Low 各自落了多少根。下面附的 func_copy_date 则换成按 datetime 区间抽时间轴,静态变量 time_interim、bars_to_copy、bars_copied 在起始时间变动时才重置,避免重复分配。 开 MT5 把这段直接塞进 EA 的 OnTick 前跑一遍,改 period 为 PERIOD_M15 并观察 bars_copied_2 的累加值,能验证 High 序列是否连续无断点。
result_copy=CopyHigh(_Symbol,period,class="num">0,bars_to_copy,price_interim); class="kw">break; case class="num">3: class=class="str">"cmt">//--- Low { result_copy=CopyLow(_Symbol,period,class="num">0,bars_to_copy,price_interim); } class="kw">break; } class=class="str">"cmt">//--- check the result of data copying if(result_copy!=-class="num">1) class=class="str">"cmt">// if copying to the intermediate array is successful { ArrayCopy(result_array,price_interim,bars_copied,class="num">0,WHOLE_ARRAY); class=class="str">"cmt">// copy the data from the temporary array to the main one x=true; class=class="str">"cmt">// assign the positive answer to the function bars_copied+=result_copy; class=class="str">"cmt">// increase the value of the processed data } class=class="str">"cmt">//--- class="kw">return the information about the processed data with one of the copied variables class="kw">switch(price_type) { case class="num">0: class=class="str">"cmt">//--- Close bars_copied_0=bars_copied; class="kw">break; case class="num">1: class=class="str">"cmt">//--- Open bars_copied_1=bars_copied; class="kw">break; case class="num">2: class=class="str">"cmt">//--- High bars_copied_2=bars_copied; class="kw">break; case class="num">3: class=class="str">"cmt">//--- Low bars_copied_3=bars_copied; class="kw">break; } class=class="str">"cmt">//--- class="kw">return(x); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Func Copy Date | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool func_copy_date(class="type">class="kw">double &result_array[], ENUM_TIMEFRAMES period,class=class="str">"cmt">// timeframe class="type">class="kw">datetime data_start, class="type">class="kw">datetime data_stop) { class=class="str">"cmt">//--- class="type">int x=class="kw">false; class=class="str">"cmt">// variable for answer class="type">int result_copy=-class="num">1; class=class="str">"cmt">// number of copied data class="kw">static class="type">class="kw">datetime time_interim[]; class=class="str">"cmt">// temporaty dynamic array for storing the copied data class="kw">static class="type">int bars_to_copy; class=class="str">"cmt">// bars number required to be copied class="kw">static class="type">int bars_copied; class=class="str">"cmt">// copied bars with start date class=class="str">"cmt">//--- variables reset due to the start date change
增量拷贝与时间砖绘制的收尾实现
在日期切换标记置真时,先把三个缓冲数组清零,避免上一交易周期残留的时间戳污染新周期的分段统计。这一步在外汇与贵金属这类跳空频繁的品种上尤其关键,MT5 默认不会替你清空跨日残留,手动 ZeroMemory 能降低后续 Bars() 计数偏差的概率。 增量拷贝的核心是先算尚需复制的 bar 数:用 Bars() 取当前区间总 bar 数,再减去已拷贝数。若非首次拷贝,则已拷贝数减一、待拷贝数加一,用来补回上一轮末尾可能重复的那一根,保证 Renko 砖时间序列连续不断层。 func_calc_dorstep 给出两种砖高算法:type_doorstep 为 0 时直接取点数;为 1 时按百分比换算成点数,公式是 price/_Point*doorstep/100。黄金若设 0.1% 砖高、现价 2300,算出来约 230 点,回测时可直接改这个参数看噪讯比变化。 func_draw_renko 接收价格与日期引用数组、反转过滤砖数、阴影开关与砖高类型,把前面的缓冲结果落成图形。外汇与贵金属杠杆高、滑点风险大,任何砖型参数都先在策略测试器用历史数据验证,再上实盘。
if(date_change==true) { ZeroMemory(time_interim); ZeroMemory(bars_to_copy); ZeroMemory(bars_copied); } class=class="str">"cmt">//--- bars_to_copy=Bars(_Symbol,period,data_start,data_stop); class=class="str">"cmt">// Find out the current number of bars on the time interval bars_to_copy-=bars_copied; class=class="str">"cmt">// Calculate the number of bars to be copied class=class="str">"cmt">//--- if(bars_copied!=class="num">0) class=class="str">"cmt">// If it is not the first time the data has been copied { bars_copied--; bars_to_copy++; } class=class="str">"cmt">//--- ArrayResize(time_interim,bars_to_copy); class=class="str">"cmt">// Change the size of the receiving array result_copy=CopyTime(_Symbol,period,class="num">0,bars_to_copy,time_interim); class=class="str">"cmt">//--- if(result_copy!=-class="num">1) class=class="str">"cmt">// If copying to the intermediate array is successful { ArrayCopy(result_array,time_interim,bars_copied,class="num">0,WHOLE_ARRAY); class=class="str">"cmt">// Copy the data from the temporary array to the main one x=true; class=class="str">"cmt">// assign the positive answer to the function bars_copied+=result_copy; class=class="str">"cmt">// Increase the value of the processed data } class=class="str">"cmt">//--- class="kw">return(x); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Func Calculate Doorstep | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int func_calc_dorstep(class="type">class="kw">double price, class=class="str">"cmt">// price class="type">char type_doorstep,class=class="str">"cmt">// step type class="type">class="kw">double doorstep) class=class="str">"cmt">// step { class="type">class="kw">double x=class="num">0; class=class="str">"cmt">// variable for answer if(type_doorstep==class="num">0) class=class="str">"cmt">// If the calculation is to be performed in points { x=doorstep; } if(type_doorstep==class="num">1) class=class="str">"cmt">// If the calculation is to be performed in percentage { x=price/_Point*doorstep/class="num">100; } class="kw">return((class="type">int)x); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Func Draw Renko | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void func_draw_renko(class="type">class="kw">double &price[], class=class="str">"cmt">// prices array class="type">class="kw">double &date[], class=class="str">"cmt">// date array class="type">int number_filter, class=class="str">"cmt">// bricks number for reversal class="type">bool draw_shadow, class=class="str">"cmt">// draw shadow class="type">char type_doorstep,class=class="str">"cmt">// step type