用于 Renko 制图的指标·综合运用
(3/3)·从砖块生成算法到实盘 EA 挂载,前 two 篇没讲的工程细节都在这一篇收口
砖形图 bricks 循环里的向上突破判定
在自定义砖形(RENKO)指标里,砖块数量不是预先写死的,而是靠逐根 K 线价格去反推。上面这段核心循环从 bars 根历史数据里跑一遍,用 func_calc_dorstep 算出当前台阶 doorstep_now,再拿 price[z] 和上一砖的上沿 up_price_calc 做差,除以 _Point 得到跨过的点数 point_go。 当 (price[z]-up_price_calc)/_Point >= doorstep_now 时,说明价格向上越过了一个砖高。若此前砖型是上升(type_box_calc==1)或未知(==0),就进入第一个 for 循环,从 point_go 往下每次减 doorstep_now,每减一轮就 a++ 补一块向上砖,同时把 down_price_calc 设为旧上沿、up_price_calc 推高一个台阶。 若之前是下跌砖(type_box_calc==-1),则多一道 number_filter 拦截:只有 point_go/doorstep_now 大于等于该过滤值才允许翻砖,避免毛刺行情里频繁反向。外汇与贵金属波动大,这个过滤参数设太小,MT5 上容易刷出一堆噪音砖,实盘前建议先用历史数据跑一遍看砖密度。
class="type">class="kw">double doorstep) class=class="str">"cmt">// step { class=class="str">"cmt">//--- arrays reset class=class="str">"cmt">//--- drawing buffer arrays ZeroMemory(RENKO_close); ZeroMemory(RENKO_color); ZeroMemory(RENKO_high); ZeroMemory(RENKO_low); ZeroMemory(RENKO_open); class=class="str">"cmt">//--- additional variables class="type">int doorstep_now; class=class="str">"cmt">// current step class="type">int point_go; class=class="str">"cmt">// passed points class=class="str">"cmt">//--- additional variables for bricks number calculating a=class="num">0; class="type">class="kw">double up_price_calc=price[class="num">0]; class="type">class="kw">double down_price_calc=price[class="num">0]; class="type">char type_box_calc=class="num">0; for(class="type">int z=class="num">0; z<bars; z++) class=class="str">"cmt">//---> bricks calculating loop { class=class="str">"cmt">//--- calculate step according to the current price doorstep_now=func_calc_dorstep(price[z],type_doorstep,doorstep); class=class="str">"cmt">//--- if price rises if((price[z]-up_price_calc)/_Point>=doorstep_now) { class=class="str">"cmt">//--- calculate points passed point_go=class="type">int((price[z]-up_price_calc)/_Point); class=class="str">"cmt">//--- prices was rising or unknown price behavour if(type_box_calc==class="num">1 || type_box_calc==class="num">0) { for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now) { class=class="str">"cmt">//--- add the next brick a++; class=class="str">"cmt">//--- add value of the next brick low price down_price_calc=up_price_calc; class=class="str">"cmt">//--- add value of the next brick up price up_price_calc=down_price_calc+(doorstep_now*_Point); class=class="str">"cmt">//--- set the brick type(up) type_box_calc=class="num">1; } } class=class="str">"cmt">//--- price went down if(type_box_calc==-class="num">1) { if((point_go/doorstep_now)>=number_filter) { for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now) { class=class="str">"cmt">//--- add the next brick a++; class=class="str">"cmt">//--- set the next brick down price down_price_calc=up_price_calc; class=class="str">"cmt">//--- set the next brick up price up_price_calc=down_price_calc+(doorstep_now*_Point);
◍ 砖形图下行方向的绘制逻辑
在砖形图(Renko)的自定义计算里,下行方向的判定依靠价格相对当前砖的下沿位移。当 (down_price_calc - price[z]) / _Point 超过当前砖步长 doorstep_now 时,说明价格向下走出了至少一块砖的距离,这时才进入新增砖块的处理。 如果上一块砖类型 type_box_calc 是 -1(下砖)或 0(未知方向),直接按 doorstep_now 为步长循环累加:每满一个步长就 a++ 生成新砖,并把下一块的 up_price_calc 设为当前 down_price_calc,down_price_calc 再减一个步长的点数,砖类型固定为 -1。 若前一块是上砖(type_box_calc==1),则多了层过滤:只有 point_go / doorstep_now 大于等于 number_filter 时才允许反向画下砖,避免毛刺行情里频繁翻转。这个 number_filter 是实战中压制假信号的关键参数,设在 2~3 之间比较常见。 循环结束后,用 Bars(_Symbol, PERIOD_CURRENT) 取当前图表 K 线数 b,并对 up_price、down_price 两个数组做 ArrayResize,保证存储砖价的坐标空间与显示条数对齐。外汇与贵金属杠杆高,砖形图仅过滤噪声,反转仍可能连续打损,需配合仓位控制。
class=class="str">"cmt">//--- if the price moves downwards if((down_price_calc-price[z])/_Point>=doorstep_now) { class=class="str">"cmt">//--- calculate the points passed point_go=class="type">int((down_price_calc-price[z])/_Point); class=class="str">"cmt">//--- if the price went downwards or the direction is unknown if(type_box_calc==-class="num">1 || type_box_calc==class="num">0) { for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now) { class=class="str">"cmt">//--- add the next brick a++; class=class="str">"cmt">//--- set the next brick low price value up_price_calc=down_price_calc; class=class="str">"cmt">//--- set the next brick up price value down_price_calc=up_price_calc-(doorstep_now*_Point); class=class="str">"cmt">//--- set the britck type(up) type_box_calc=-class="num">1; } } class=class="str">"cmt">//--- the price moved upwards if(type_box_calc==class="num">1) { if((point_go/doorstep_now)>=number_filter) { for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now) { class=class="str">"cmt">//--- add the next brick a++; class=class="str">"cmt">//--- set the next brick down price value up_price_calc=down_price_calc; class=class="str">"cmt">//--- set the next brick up price value down_price_calc=up_price_calc-(doorstep_now*_Point); class=class="str">"cmt">//--- set the brick type(up) type_box_calc=-class="num">1; } } } } } class=class="str">"cmt">//---< bricks calculate loop class=class="str">"cmt">//--- calculate the number of display bars class="type">int b=Bars(_Symbol,PERIOD_CURRENT); class=class="str">"cmt">//--- resize arrays ArrayResize(up_price,b); ArrayResize(down_price,b);
「砖形数组的初始化与重排逻辑」
在 MT5 自定义指标里画砖形(Renko 类)图形,第一步是把存储砖块属性的动态数组按可视砖数 b 重新开尺寸。type_box、time_box、shadow_up、shadow_down、number_id 这几个数组都靠 ArrayResize 对齐,否则后面写越界会直接报错退出了。 重新分配完内存,紧接着用 ZeroMemory 把 up_price、down_price 以及上述五个数组全部清成 0。这一步不是可有可无:若上次计算残留了旧砖价,新一根 K 线进来会套用错误基准,图形可能从中间裂开。 初始化时只给 0 号元素赋值——up_price[0] 和 down_price[0] 都等于传入的 price[0],type_box[0] 设为 0 表示未知方向砖。随后用 l=a-b 算差值,turn_cycle=l/(b-1) 与 turn_rest=MathMod(l,b-1)+2 决定图表放满后从第几块开始循环覆盖,属于「砖数多于图表容量」时的滚动算法。 主循环前有一句 Alert:当 a>b 会弹「More bricks than can be placed on the chart, the step is small」。这实际在提醒你步长设太小了,砖块生成速度远超可视量,外汇与贵金属市场跳空频繁,这种参数组合在高波动时段可能让历史砖被快速冲掉,调参时建议先开 MT5 用 EURUSD 的 M5 跑一遍看覆盖节奏。
ArrayResize(type_box,b); ArrayResize(time_box,b); ArrayResize(shadow_up,b); ArrayResize(shadow_down,b); ArrayResize(number_id,b); class=class="str">"cmt">//--- resize calculation buffers array ZeroMemory(up_price); ZeroMemory(down_price); ZeroMemory(type_box); ZeroMemory(time_box); ZeroMemory(shadow_up); ZeroMemory(shadow_down); ZeroMemory(number_id); class=class="str">"cmt">//--- fill arrays with the initial values up_price[class="num">0]=price[class="num">0]; down_price[class="num">0]=price[class="num">0]; type_box[class="num">0]=class="num">0; class=class="str">"cmt">//--- calculate odd bricks number class="type">int l=a-b; class="type">int turn_cycle=l/(b-class="num">1); class="type">int turn_rest=(class="type">int)MathMod(l,(b-class="num">1))+class="num">2; class="type">int turn_var=class="num">0; class=class="str">"cmt">//--- message of partially displayed bricks if(a>b)Alert("More bricks than can be placed on the chart, the step is small"); a=class="num">0; class=class="str">"cmt">//--- reset bricks claculating variable for(class="type">int z=class="num">0; z<bars; z++) class=class="str">"cmt">//---> Main loop { class=class="str">"cmt">//--- calculate the step according to the price doorstep_now=func_calc_dorstep(price[z],type_doorstep,doorstep); class=class="str">"cmt">//---if the price moves upwards if((price[z]-up_price[a])/_Point>=doorstep_now) { class=class="str">"cmt">//--- calculate the points passed point_go=class="type">int((price[z]-up_price[a])/_Point); class=class="str">"cmt">//--- price moved upwards or its behavour is unknown if(type_box[a]==class="num">1 || type_box[a]==class="num">0) { for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now) { a++; class=class="str">"cmt">//--- add the next brick if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a)) { up_price[class="num">0]=up_price[a-class="num">1]; a=class="num">1; class=class="str">"cmt">// bricks calculator reset turn_var++; class=class="str">"cmt">// calculator of loops reset } class=class="str">"cmt">//--- the next brick low price value down_price[a]=up_price[a-class="num">1]; class=class="str">"cmt">//--- set the brick up price up_price[a]=down_price[a]+(doorstep_now*_Point); class=class="str">"cmt">//--- set the up shadow value if(shadow_print==true) shadow_up[a]=price[z]; class=class="str">"cmt">//to the upper price level
下行砖块的递推与循环复位
当上一块砖类型被标记为 -1(下行)且累计波动 point_go 达到过滤阈值 number_filter 倍门阶时,引擎按 doorstep_now 步长向下切砖。 循环里每切一块 a 自增,新的 down_price[a] 直接承接前一块的 up_price[a-1],而 up_price[a] 再叠 doorstep_now*_Point,保证砖体高度严格等于当前门阶。 若满足 (a==b 且 turn_var<turn_cycle) 或 (turn_var==turn_cycle 且 turn_rest==a),计数器 a 被重置为 1、turn_var 加一,意味着完成一个转向周期后重新计数,避免在长单边行情里数组越界。 影子处理上,shadow_print 为 true 时 shadow_up[a] 取实际价 price[z],否则退回砖体上线 up_price[a];shadow_down[a] 恒等于砖体下线。外汇与贵金属波动剧烈,门阶与 number_filter 设错会让砖形严重失真,建议在 MT5 用 EURUSD 的 M5 先跑一遍观察重置逻辑。
else shadow_up[a]=up_price[a]; class=class="str">"cmt">// to the up price level class=class="str">"cmt">//--- set the low price value(to the brick price level) shadow_down[a]=down_price[a]; class=class="str">"cmt">//--- value of the brick closing time time_box[a]=(class="type">class="kw">datetime)Date[z]; class=class="str">"cmt">//--- set the brick type(up) type_box[a]=class="num">1; class=class="str">"cmt">//--- set the index number_id[a]=z; } } class=class="str">"cmt">//--- the price moved downwards if(type_box[a]==-class="num">1) { if((point_go/doorstep_now)>=number_filter) { for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now) { a++; class=class="str">"cmt">//--- add the next brick if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a)) { up_price[class="num">0]=up_price[a-class="num">1]; a=class="num">1; class=class="str">"cmt">// bricks counter reset turn_var++; class=class="str">"cmt">// loops reset cycle } class=class="str">"cmt">//--- set the next brick low price value down_price[a]=up_price[a-class="num">1]; class=class="str">"cmt">//--- set the next brick up price up_price[a]=down_price[a]+(doorstep_now*_Point); class=class="str">"cmt">//--- set the up shadow value if(shadow_print==true) shadow_up[a]=price[z]; class=class="str">"cmt">// at the up price level else shadow_up[a]=up_price[a]; class=class="str">"cmt">// the brick up price level class=class="str">"cmt">//--- set of the down price value(the brick price level) shadow_down[a]=down_price[a]; class=class="str">"cmt">//--- set the close time time_box[a]=(class="type">class="kw">datetime)Date[z]; class=class="str">"cmt">//--- set the up brick
◍ 砖形图向下突破的逐砖递推逻辑
这段逻辑处理的是价格相对前一根砖向下脱离阈值时的砖块生成。先算出行情越过当前砖下沿多少点:point_go = (down_price[a]-price[z]) / _Point,若结果大于等于 doorstep_now(当前砖宽),才进入向下补砖循环。 循环里每跨过一个 doorstep_now 就 a++ 新增一块砖,并把 up_price[a] 设为上一块的下沿,down_price[a] 再减一个砖宽。这里 type_box[a]==-1 或 0 表示原方向非向上,才允许向下叠砖;若 type_box[a]==1 则走另一分支,用 number_filter 过滤微小反向波动。 重置条件藏在 a==b 且 turn_var<turn_cycle,或 turn_var==turn_cycle 且 turn_rest==a 时:把 down_price[0] 接上末尾下沿、a 归 1、turn_var 自增,相当于完成一次序列折叠。外汇与贵金属波动跳点频繁,砖宽 _Point 与 doorstep_now 设错会让重绘砖数爆炸,建议先在 EURUSD 的 M1 上把 doorstep_now 设为 10 点跑一遍。 shadow_print 开关决定影线取真实最低价还是砖体下沿,关掉时 shadow_down[a]=down_price[a],开着则记录 price[z] 最后一笔低价,回测拐点时有用。
type_box[a]=class="num">1; class=class="str">"cmt">//--- set index number_id[a]=z; } } } class=class="str">"cmt">//--- if price moves upwards if((down_price[a]-price[z])/_Point>=doorstep_now) { class=class="str">"cmt">//--- calculate the points passed point_go=class="type">int((down_price[a]-price[z])/_Point); class=class="str">"cmt">//--- price moved downwards or the direction is unknown if(type_box[a]==-class="num">1 || type_box[a]==class="num">0) { for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now) { a++; class=class="str">"cmt">//--- add the next brick if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a)) { down_price[class="num">0]=down_price[a-class="num">1]; a=class="num">1; class=class="str">"cmt">// set the bricks counter to zero turn_var++; class=class="str">"cmt">// reset loop counter } class=class="str">"cmt">//--- set the next brick down price up_price[a]=down_price[a-class="num">1]; class=class="str">"cmt">//--- set the next brick up price down_price[a]=up_price[a]-(doorstep_now*_Point); class=class="str">"cmt">//--- set the down shadow value if(shadow_print==true) shadow_down[a]=price[z]; class=class="str">"cmt">//--- the last lowest price level else shadow_down[a]=down_price[a]; class=class="str">"cmt">//--- low price level class=class="str">"cmt">//--- set the up price value shadow_up[a]=up_price[a]; class=class="str">"cmt">//--- set the brick close time time_box[a]=set the down shadow value]; class=class="str">"cmt">//--- set the brick type(down) type_box[a]=-class="num">1; class=class="str">"cmt">//--- set index number_id[a]=z; } } class=class="str">"cmt">//--- price moved upwards if(type_box[a]==class="num">1) { if((point_go/doorstep_now)>=number_filter)
「砖形图下行砖的循环堆砌逻辑」
这段循环负责在价格反向穿越后,按步长 doorstep_now 逐格向下补画砖块。外层 for 从 point_go 递减到 doorstep_now,每走一步 a 自增,代表新叠一块砖。 当满足条件 (a==b 且 turn_var<turn_cycle) 或 (turn_var==turn_cycle 且 turn_rest==a) 时,把 down_price[0] 回退到前一块下沿,a 重置为 1,turn_var 加一,相当于完成了一次反向砖列的轮回计数。 每块下行砖的上下沿这样算:up_price[a] 取前一块下沿,down_price[a] 再减 doorstep_now*_Point;阴影线若开启 shadow_print 则锚在最低价 price[z],否则贴着下沿。砖的类型写死 type_box[a]=-1,时间截 Date[z],编号 number_id[a]=z。 填图缓冲段把砖序倒排进 RENKO 系列数组:type 为 1 给色值 0,否则 1;open/close/high/low 分别映射 down_price、up_price、shadow_up、shadow_down。跑通后开 MT5 把 doorstep_now 从 10 点调到 30 点,能直接看到砖块稀疏度变化,外汇与贵金属品种须留意滑点导致砖列断裂的高风险。
{
for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now)
{
a++; class=class="str">"cmt">//--- add the next brick
if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a))
{
down_price[class="num">0]=down_price[a-class="num">1];
a=class="num">1; class=class="str">"cmt">// reset bricks counter
turn_var++; class=class="str">"cmt">// reset loop counter
}
up_price[a]=down_price[a-class="num">1]; class=class="str">"cmt">//--- set the next brick down price
down_price[a]=up_price[a]-(doorstep_now*_Point); class=class="str">"cmt">//--- set the up price value
class=class="str">"cmt">//--- set the down shadow value
if(shadow_print==true) shadow_down[a]=price[z]; class=class="str">"cmt">// at the lowest price level
else shadow_down[a]=down_price[a]; class=class="str">"cmt">// at the down price level
class=class="str">"cmt">//--- set the up price level
shadow_up[a]=up_price[a];
class=class="str">"cmt">//--- set the brick close time
time_box[a]=(class="type">class="kw">datetime)Date[z];
class=class="str">"cmt">//--- set the brick type(down)
type_box[a]=-class="num">1;
class=class="str">"cmt">//--- index set
number_id[a]=z;
}
}
}
} class=class="str">"cmt">//---< Main loop
class=class="str">"cmt">//--- fill the draw buffer
class="type">int y=a;
for(class="type">int z=class="num">0; z<a; z++)
{
if(type_box[y]==class="num">1)RENKO_color[z]=class="num">0;
else RENKO_color[z]=class="num">1;
RENKO_open[z]=down_price[y];
RENKO_close[z]=up_price[y];
RENKO_high[z]=shadow_up[y];
RENKO_low[z]=shadow_down[y];
y--;
}
}用代码在MT5上画趋势线与矩形区
在MT5脚本里批量标注关键位,比起手动拖图形更不容易漏掉拐点。下面两段函数分别负责创建一条限定端点的趋势线,以及一个矩形区域,参数都从外部传入,方便复用。 趋势线函数先以 OBJ_TREND 类型建对象,绑定 time1/price1 与 time2/price2 两个锚点;随后用 ObjectSetInteger 依次设定颜色、实线样式、线宽(width 参数可控 1~5 像素)、前景显示,并关闭左右射线,使线段严格落在两点之间而非无限延伸。 矩形函数改用 OBJ_RECTANGLE 类型,同样以两对角时间点与价格点框出区域;fill 参数决定是否半透明填充,配合 color_square 可快速区分不同震荡区间。外汇与贵金属波动剧烈,这类图形仅作结构参考,实际突破方向需结合量价确认,存在较高风险。 把下面代码直接塞进 ea 或脚本,传入具体价位与时间,就能在图表上复现你的价格行为分析框架。
class="type">class="kw">double price1, class="type">class="kw">double price2, class="type">class="kw">datetime time1, class="type">class="kw">datetime time2, class="type">int width, class="type">color color_line) { ObjectCreate(class="num">0,name,OBJ_TREND,class="num">0,time1,price1,time2,price2); class=class="str">"cmt">//--- set the line class="type">color ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,color_line); class=class="str">"cmt">//--- set the line display style ObjectSetInteger(class="num">0,name,OBJPROP_STYLE,STYLE_SOLID); class=class="str">"cmt">//--- set the width of the line ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,width); class=class="str">"cmt">//--- display in the foreground(class="kw">false) or in the(true) background ObjectSetInteger(class="num">0,name,OBJPROP_BACK,class="kw">false); class=class="str">"cmt">//--- enable(true) or disable(class="kw">false) the mode of the left line display ObjectSetInteger(class="num">0,name,OBJPROP_RAY_LEFT,class="kw">false); class=class="str">"cmt">//--- enable(true) or disable(class="kw">false) the right line display ObjectSetInteger(class="num">0,name,OBJPROP_RAY_RIGHT,class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Func Create Square or Rectangle | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void func_create_square_or_rectangle(class="type">class="kw">string name, class="type">class="kw">double price1, class="type">class="kw">double price2, class="type">class="kw">datetime time1, class="type">class="kw">datetime time2, class="type">int width, class="type">color color_square, class="type">bool fill) { class=class="str">"cmt">//--- create rectangle according to the setpoints ObjectCreate(class="num">0,name,OBJ_RECTANGLE,class="num">0,time1,price1,time2,price2); class=class="str">"cmt">//--- set the rectangle class="type">color ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,color_square); class=class="str">"cmt">//--- set style of rectangle class="type">color
◍ 用矩形块重绘 Renko 主图
在自定义 Renko 图表里,每个砖块本质是 MT5 的矩形对象。下面这段绘制函数按序号遍历已有砖块数组 a,把上涨块和下跌块分别用不同配色画成实心或空心矩形,方便直接在主图叠加裸 K 替代视图。 循环从 z=2 起步而非 0,是因为 time_box[z-1] 需要前一个时间锚点;若从 0 开始会越界读到无效下标。砖块命名用 magic_numb 加类型后缀,同一 EA 多实例不会互相覆盖对象名。 type_box[z]==1 判为上涨,-1 为下跌。square 与 frame 两个布尔开关独立控制是否画填充块体和是否画边框:只开 frame 时能得到空心线框,适合叠加在原本蜡烛图上做区块标记。OBJPROP_BACK 设 false 保证矩形浮在前景,不会被原生 K 线挡住。 实盘加载这类对象绘制前,先确认 a 数组长度与 time_box/up_price 已正确填充,否则 MT5 可能刷出几十个错位矩形拖慢终端。外汇与贵金属品种波动跳空频繁,Renko 砖参数过小会生成过量对象,存在图表卡顿风险。
ObjectSetInteger(class="num">0,name,OBJPROP_STYLE,STYLE_SOLID); class=class="str">"cmt">//--- set lines width ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,width); class=class="str">"cmt">//--- activate(true) or disactivate(class="kw">false) mode of rectangle colouring ObjectSetInteger(class="num">0,name,OBJPROP_FILL,fill); class=class="str">"cmt">//--- display in the foreground(class="kw">false) or in the background(true) ObjectSetInteger(class="num">0,name,OBJPROP_BACK,class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Func Draw Renko Main Chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void func_draw_renko_main_chart(class="type">color color_square_up, class="type">color color_square_down, class="type">color color_frame_up, class="type">color color_frame_down, class="type">int width_square, class="type">int width_frame, class="type">bool square, class="type">bool fill, class="type">bool frame) { class="type">class="kw">string name_square; class="type">class="kw">string name_frame; for(class="type">int z=class="num">2; z<=a; z++) { name_square=IntegerToString(magic_numb)+"_Square_"+IntegerToString(z); name_frame=IntegerToString(magic_numb)+"_Frame_"+IntegerToString(z); if(type_box[z]==class="num">1) { if(square==true)func_create_square_or_rectangle(name_square,up_price[z],down_price[z],time_box[z-class="num">1],time_box[z],width_square,color_square_up,fill); if(frame==true)func_create_square_or_rectangle(name_frame,up_price[z],down_price[z],time_box[z-class="num">1],time_box[z],width_frame,color_frame_up,class="kw">false); } if(type_box[z]==-class="num">1) { if(square==true)func_create_square_or_rectangle(name_square,up_price[z],down_price[z],time_box[z-class="num">1],time_box[z],width_square,color_square_down,fill); if(frame==true)func_create_square_or_rectangle(name_frame,up_price[z],down_price[z],time_box[z-class="num">1],time_box[z],width_frame,color_frame_down,class="kw">false); } } }
「用拐点数组画出之字转向连线」
把识别出的波峰波谷连成可见的之字线,核心在 func_draw_zig_zag 这个函数。它接收四个参数:price_shadow 决定是否用含影线的极端价、line_width 控制线宽、line_color_up / line_color_down 分别给上涨段和下跌段上色。 函数先用 type_box[1] 判定第一个拐点是下探还是上冲,把 price_1 和 date_1 锚定在最近一个已确认拐点。随后从 z=2 开始遍历到 a,只要 type_box[z] 不等于前一项,就认为出现新转折,n 自增并拼出形如 'magic_numb_Line_n' 的唯一对象名,避免重复创建。 当 price_shadow 为真,代码会拿 number_id[z-1] 定位前一根 K 线,并向两侧比对 Price_low / Price_high,把 id 微调到真正的极值棒。这一步让连线端点落在影线尖而不是收盘价,EURUSD 这类影线频繁的品种上,转向线贴合度会明显更准。 下跌段(type_box[z]==1)调用 func_create_trend_line 时用 line_color_down,上涨段逻辑对称,用 line_color_up。每次画完把 price_2 / date_2 回灌给 price_1 / date_1,链条就顺着推下去。开 MT5 把这段直接塞进 EA,调 line_width=2 先看日线拐点是否如预期连出。
class="type">void func_draw_zig_zag(class="type">bool price_shadow, class="type">int line_width, class="type">color line_color_up, class="type">color line_color_down) { class="type">class="kw">double price_1=class="num">0; class="type">class="kw">double price_2=class="num">0; class="type">class="kw">datetime date_1=class="num">0; class="type">class="kw">datetime date_2=class="num">0; if(type_box[class="num">1]==class="num">1)price_1=down_price[class="num">1]; if(type_box[class="num">1]==-class="num">1)price_1=up_price[class="num">1]; date_1=time_box[class="num">1]; class="type">int id=class="num">0; class=class="str">"cmt">// Low & High array storing variable class="type">int n=class="num">0; class=class="str">"cmt">// variable for name forming class="type">class="kw">string name_line; class=class="str">"cmt">//--- variable responsible for the "trend line" name for(class="type">int z=class="num">2; z<=a; z++) { if(type_box[z]!=type_box[z-class="num">1]) { n++; name_line=IntegerToString(magic_numb)+"_Line_"+IntegerToString(n); if(type_box[z]==class="num">1) { if(price_shadow==true) { id=number_id[z-class="num">1]; if((id-class="num">1)>class="num">0 && Price_low[id-class="num">1]<Price_low[id])id--; if(Price_low[id+class="num">1]<Price_low[id])id++; price_2=Price_low[id]; date_2=(class="type">class="kw">datetime)Date[id]; } else { price_2=down_price[z-class="num">1]; date_2=time_box[z-class="num">1]; } func_create_trend_line(name_line,price_1,price_2,date_1,date_2,line_width,line_color_down); price_1=price_2; date_1=date_2; } if(type_box[z]==-class="num">1) { if(price_shadow==true) { id=number_id[z-class="num">1]; if((id-class="num">1)>class="num">0 && Price_high[id-class="num">1]>Price_high[id])id--;
趋势线收尾与对象清理的实现细节
这段逻辑处在摆动高点连线绘制的末尾:当新的峰值高于前一根已记录高点时,下标 id 自增,把 price_2 锁定为 Price_high[id],date_2 转成对应时间的 datetime;若走 else 分支,则直接复用上一区段保存的 up_price[z-1] 与 time_box[z-1],避免重复扫描。 随后调用 func_create_trend_line,把 price_1/date_1 到 price_2/date_2 连成线段,线宽与颜色由 line_width、line_color_up 控制;画完立刻把起点搬成终点,为下一段连线做准备。外汇与贵金属波动剧烈,这类自动连线在高波动段可能频繁重绘,属高风险环境下的视觉辅助。 对象清理由两个函数负责:func_delete_objects 按 name+序号从 0 循环到 number,逐个 ObjectDelete(0,name_del);func_all_delete 先取 ObjectsTotal(0,-1,-1) 得到图表对象总数,再对带 magic_numb 前缀的 _Line_、_Square_、_Frame_ 三组批量删除,最后 ChartRedraw(0) 强制重绘。 在 MT5 里把 magic_numb 设成独立值(如 20240517),可防止你的指标线被其他 EA 的清理函数误删;改 number 上限前先确认 ObjectsTotal 返回值,否则可能漏删残留图形。
if(Price_high[id+class="num">1]>Price_high[id])id++; price_2=Price_high[id]; date_2=(class="type">class="kw">datetime)Date[id]; } else { price_2=up_price[z-class="num">1]; date_2=time_box[z-class="num">1]; } func_create_trend_line(name_line,price_1,price_2,date_1,date_2,line_width,line_color_up); price_1=price_2; date_1=date_2; } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Func Delete Objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void func_delete_objects(class="type">class="kw">string name, class="type">int number) { class="type">class="kw">string name_del; for(class="type">int x=class="num">0; x<=number; x++) { name_del=name+IntegerToString(x); ObjectDelete(class="num">0,name_del); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Func All Delete | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void func_all_delete() { class=class="str">"cmt">//--- the graphical objects calculating obj=ObjectsTotal(class="num">0,-class="num">1,-class="num">1); class=class="str">"cmt">//--- all indicator graphical objects deleting func_delete_objects(IntegerToString(magic_numb)+"_Line_",obj); func_delete_objects(IntegerToString(magic_numb)+"_Square_",obj); func_delete_objects(IntegerToString(magic_numb)+"_Frame_",obj); class=class="str">"cmt">//--- the chart redrawing ChartRedraw(class="num">0); }
◍ 指标内部的级别绘制与函数整合
自定义指标里想让图表干净一点,可以把关键价位直接画在副图窗口的水平线上。func_create_levels 只吃两个参数:级别数量 level_number 和颜色 level_color,用 IndicatorSetInteger(INDICATOR_LEVELS, level_number) 先告诉终端要显示几条线,再逐个填价格和色值。 代码里有个细节:用变量 k 控制从哪根砖开始取价,若 a 大于 level_number 就偏移 k=a-level_number,避免越界。循环里 z 从 0 走到 level_number,把 up_price[k] 写进 INDICATOR_LEVELVALUE,颜色写进 INDICATOR_LEVELCOLOR,副图水平线就出来了。 真正把所有零件拧在一起的是 func_concolidation(原文拼写如此)。它先 func_all_delete 清场,算好 date_stop=TimeCurrent(),再检查 Bars 数是否超过 Price 缓冲区大小,超了就改 date_start 并弹 Alert 提示初始日期被挪过。 随后它依次拷贝价格与日期、按需补 high/low 用于 ZigZag 阴影,调 func_draw_renko 画砖型、func_draw_zig_zag 画拐点、func_draw_renko_main_chart 画主图方块,最后 func_create_levels 挂级别线并 ChartRedraw(0) 强制重绘。外汇与贵金属波动剧烈,这类自绘指标仅作结构参考,实盘前请在 MT5 策略测试器以历史数据验证缓冲与重绘逻辑。 开 MT5 把这段直接塞进你的指标源码,改 levels_number 和 levels_color 两个外部参数,就能直观看到副图级别线随砖型刷新;若发现线数不对,先查 a 与缓冲大小的对应关系。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Func Create Levels | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void func_create_levels(class="type">int level_number, class="type">color level_color) { class=class="str">"cmt">//--- set the number of levels in the indicator window IndicatorSetInteger(INDICATOR_LEVELS,level_number); class=class="str">"cmt">// which brick is taken to draw levels class="type">int k=class="num">0; if(a>level_number)k=a-level_number; class=class="str">"cmt">//--- set levels prices for(class="type">int z=class="num">0;(z<=level_number && k<=a); z++,k++) { IndicatorSetDouble(INDICATOR_LEVELVALUE,z,up_price[k]); IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,level_color); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Func Consolidation | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void func_concolidation() { class=class="str">"cmt">//--- deleting all the graphical objects of the indicator func_all_delete(); class=class="str">"cmt">//--- the current date date_stop=TimeCurrent(); class=class="str">"cmt">//--- the initial date changing due to the restricted buffer size if((bars=Bars(_Symbol,time_frame,date_start,date_stop))>ArraySize(Price)) { date_start=func_calc_date_start(date_start,date_stop); Alert("The initial date was changed due to the lack of the chart size"); date_change=true; class=class="str">"cmt">//--- calculation of bars on the taken timeframe bars=Bars(_Symbol,time_frame,date_start,date_stop); } class=class="str">"cmt">//--- class="type">bool result_copy_price=func_copy_price(Price,time_frame,date_start,date_stop,type_price); class="type">bool result_copy_date=func_copy_date(Date,time_frame,date_start,date_stop); class=class="str">"cmt">//--- change the date parameter if(result_copy_price=true && result_copy_date==true)date_change=class="kw">false; class=class="str">"cmt">//--- if(zig_zag_shadow==true) { func_copy_price(Price_high,time_frame,date_start,date_stop,class="num">2); func_copy_price(Price_low,time_frame,date_start,date_stop,class="num">3); } class=class="str">"cmt">//--- func_draw_renko(Price,Date,filter_number,shadow_print,type_step,step); if(zig_zag==true)func_draw_zig_zag(zig_zag_shadow,zig_zag_width,zig_zag_color_up,zig_zag_color_down); class=class="str">"cmt">//--- func_draw_renko_main_chart(square_color_up,square_color_down,frame_color_up,frame_color_down,square_width,frame_width,square_draw,square_fill,frame_draw); func_create_levels(levels_number,levels_color); class=class="str">"cmt">//--- redraw the chart ChartRedraw(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+
「用静态时间差捕捉新K线」
在 MT5 指标或 EA 里,最怕把上一根 K 线的计算重复跑一遍,白白浪费 CPU 还可能导致信号重绘。下面这段函数用「旧时间 vs 新时间」的比对来判定是否换柱,逻辑极轻量,适合嵌进任何周期监听。 核心做法是声明一个 static datetime 变量 old_times 记住上次时间;每次用 CopyTime 只取最新 1 根柱的时间(参数 0,1),复制成功且时间不同,就返回 true 表示新柱。注意 old_times 初值为 0 时不返回 true,避开了指标刚加载那一下误触。 OnCalculate 里直接拿这个函数去 gate 你的主逻辑:只有 func_new_bar 为真才跑 func_concolidation,其余 tick 直接 return rates_total,不重算。键盘事件段虽然被截断,但结构清晰——CHARTEVENT_KEYDOWN 分支里可以继续挂手动刷新。 外汇与贵金属波动剧烈、杠杆高风险大,这类新柱判定只解决「何时算」的问题,信号本身胜率仍靠你回测。开 MT5 把下面代码贴进指标,把 time_redraw 换成 PERIOD_M5 之类,就能直观看到每根新柱触发一次打印。
class="type">bool func_new_bar(ENUM_TIMEFRAMES period_time) { class=class="str">"cmt">//--- class="kw">static class="type">class="kw">datetime old_times; class=class="str">"cmt">// array for storing old values class="type">bool res=class="kw">false; class=class="str">"cmt">// analysis result variable class="type">class="kw">datetime new_time[class="num">1]; class=class="str">"cmt">// new bar time class=class="str">"cmt">//--- class="type">int copied=CopyTime(_Symbol,period_time,class="num">0,class="num">1,new_time); class=class="str">"cmt">// copy the time of the new bar into the new_time box class=class="str">"cmt">//--- if(copied>class="num">0) class=class="str">"cmt">// все ок. data have been copied { if(old_times!=new_time[class="num">0]) class=class="str">"cmt">// if the bar&class="macro">#x27;s old time is not equal to new one { if(old_times!=class="num">0) res=true; class=class="str">"cmt">// if it is not the first launch, true = new bar old_times=new_time[class="num">0]; class=class="str">"cmt">// store the bar&class="macro">#x27;s time } } class=class="str">"cmt">//--- class="kw">return(res); } class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">int begin, class="kw">const class="type">class="kw">double &price[]) { class=class="str">"cmt">//--- if(func_new_bar(time_redraw)==true) { func_concolidation(); } class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="kw">return(rates_total); } class="type">void OnChartEvent(class="kw">const class="type">int id, class=class="str">"cmt">// event ID class="kw">const class="type">long& lparam, class=class="str">"cmt">// class="type">long type event parameter class="kw">const class="type">class="kw">double& dparam, class=class="str">"cmt">// class="type">class="kw">double type event parameter class="kw">const class="type">class="kw">string& sparam) class=class="str">"cmt">// class="type">class="kw">string type event parameter { class=class="str">"cmt">//--- Keyboard button pressing event if(id==CHARTEVENT_KEYDOWN) {
用快捷键接管指标对象的生命周期
在 MT5 指标脚本里,键盘消息走 OnChartEvent 的 lparam 传递虚拟键码。上面这段把 R 键(键码 82)绑到 func_concolidation(),把 C 键(键码 67)绑到 func_all_delete(),等于把「重绘整理区」和「清空所有对象」两个动作直接交给交易员手控,不用进属性框逐个删。 OnDeinit 里只调了一句 func_all_delete(),说明图表被关闭或指标被移除时,残留的图形对象会被强制清理,避免跨品种切换时旧对象叠在新图上造成误读。外汇与贵金属市场波动剧烈、滑点频繁,这类对象若不清,可能让你把已失效的支撑阻力当成当前结构。 验证方式:把这段代码塞进你自己的自定义指标,编译后切到 EURUSD 的 M5,按 R 看整理函数是否触发,按 C 看对象是否归零;键码写死 82 和 67,改键直接改数字即可。
if(lparam==class="num">82) class=class="str">"cmt">//--- "R" key has been pressed { class=class="str">"cmt">//--- call of the consolidation function func_concolidation(); } if(lparam==class="num">67) class=class="str">"cmt">//--- "C" key has been pressed { class=class="str">"cmt">//--- deletion of all objects of the indicator func_all_delete(); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- class="kw">delete all graphical objects of the indicator func_all_delete(); }
◍ Renko 反转砖里的真实入场缝隙
Renko 图表按价格跳动堆叠砖块,天然过滤了时间噪音,但入场点不能只看颜色翻面。EURUSD H4、砖宽 20 点的样张里标记了 A~F 六个点:A 点是上行砖转下行砖的标准反转;B、C、D 依次走出反转砖。 E 点出了问题——同一方向连出两块砖,且下行阴影落在同级,F 点也出现类似双砖结构。这类位置价格容易回吐,因为强趋势里不该这么快出双砖同色,双砖同色往往意味着反转信号虚弱。 更稳的缝隙在“反转砖带阴影、且后续砖延续同方向”时出现;若一次性砌出两块同色砖,入场安全边际就薄了。 叠加 ZigZag 可做图形分析:GBPUSD H4、20 点样张里能直接读出支撑阻力与头肩结构;等距通道则可跨周期——USDCHF 把指标算在 H1、画在 H4,20 点砖宽下能并发多周期信号。 多周期组合更实用:H1 抓最近反转,H4 剔掉杂讯,D1 确认大方向(GBPUSD H1/H4/D1 同图可见)。另一套画线规则是:相邻红砖间连上线、中间至少隔一块蓝砖,跌破该线后卖;蓝砖间连线、隔至少一块红砖,突破后买(GBPUSD H4、25 点样张以大箭头标出入场)。外汇与贵金属 Renko 交易杠杆高,双砖同色与跨周期冲突都可能是假突破,开 MT5 用 20~25 点砖宽复刻上述样张最直观。
「别急着下结论」
Renko 图表从早年流行到现在,在 MT5 里仍有一席之地,但社区反馈暴露了几个硬伤:有用户在 2021 年 6 月提到按 'R' 键才能看到砖块,说明指标加载逻辑和图表刷新存在耦合;2023 年 2 月有人确认最后两块砖会重绘,这意味着基于末端砖形的突破信号可能失效。 如果你打算把文末的 abcr.mq5(77.52 KB)跑起来,先开一个 EURUSD 的 M5 图表,加载后按 R 强制重绘,观察末砖是否在 tick 更新时变色或位移。外汇与贵金属杠杆高,重绘类指标仅作辅助,实盘前请用策略测试器跑至少 3 个月历史数据。 这类图表适合验证价格行为里的趋势过滤,但别把它当唯一依据;同一套砖形逻辑,你完全可以改 BoxSize 算法去适配 XAUUSD 的跳动。