构建K线图的趋势约束模型(第四部分):为各个趋势波段自定义显示样式·进阶篇
(2/3)· 只会画箭头远远不够,用 MQL5 绘图样式让每个趋势波段自带可读信号
很多交易者把指标信号画成千篇一律的箭头,盯盘半小时就眼花,更看不出日线情绪和当前波段的从属关系。当 D1 看跌却在小周期画出醒目买点,视觉噪音会直接干扰判断。把显示样式和趋势约束绑定,是低成本提升读图效率的一步。
「多周期数组与时间轴对齐的初始化套路」
在 MT5 自定义指标里,OnCalculate 拿到的 time[]、open[] 等是当前图表周期的历史序列,但要做跨周期参考(比如用 M1 开盘价和 D1 收盘价),必须自己用 iBarShift 把时间轴对齐到目标周期,否则数组下标错位会直接画出鬼线。 下面这段是典型开头:先用 rates_total - prev_calculated 算本次需处理的根数,limit 在 prev_calculated>=1 时再 +1,保证只增量计算不重算全历史。四个输出缓冲区 Buffer1~4 都设成序列方向(ArraySetAsSeries(...,true)),这样 Buffer[0] 永远是最新一根 K。 首根之前(prev_calculated<1)要把四个 Buffer 用 ArrayInitialize 填 EMPTY_VALUE,避免残留 0 值被当成有效信号画出来。随后 CopyTime 取当前周期时间轴到 TimeShift,也设成序列方向,作为跨周期映射的基准钟。 映射循环里对每个 i 调 iBarShift(Symbol(),PERIOD_M1,TimeShift[i]) 和 PERIOD_D1 版本,分别存进 barshift_M1[]、barshift_D1[],长度都按 rates_total 用 ArrayResize 提前开好。这样后续取 M1 Open 或 D1 Close 时,直接用 barshift_M1[i] 下标就不会偏。 RSI_handle 和 MA_handle 是两个外部指标句柄,BarsCalculated 返回 <=0 就直接 return(0) 中断,等下一 tick 再试;CopyBuffer(RSI_handle,0,0,rates_total,RSI) 把 RSI 主缓冲拉进来后同样设序列方向。Open 取 M1、Close 取 D1,任一步 Copy 失败就 return(rates_total) 保住已画部分。外汇和贵金属波动大,这类跨周期指标在跳空时 iBarShift 可能返回 -1,实盘前务必在 MT5 用周末数据回放验证边界。
const class="type">class="kw">datetime& time[], const class="type">class="kw">double& open[], const class="type">class="kw">double& high[], const class="type">class="kw">double& low[], const class="type">class="kw">double& close[], const class="type">long& tick_volume[], const class="type">long& volume[], const class="type">int& spread[]) { class="type">int limit = rates_total - prev_calculated; class=class="str">"cmt">//--- counting from class="num">0 to rates_total ArraySetAsSeries(Buffer1, true); ArraySetAsSeries(Buffer2, true); ArraySetAsSeries(Buffer3, true); ArraySetAsSeries(Buffer4, true); class=class="str">"cmt">//--- initial zero if(prev_calculated < class="num">1) { ArrayInitialize(Buffer1, EMPTY_VALUE); ArrayInitialize(Buffer2, EMPTY_VALUE); ArrayInitialize(Buffer3, EMPTY_VALUE); ArrayInitialize(Buffer4, EMPTY_VALUE); } else limit++; class="type">class="kw">datetime Time[]; class="type">class="kw">datetime TimeShift[]; if(CopyTime(Symbol(), PERIOD_CURRENT, class="num">0, rates_total, TimeShift) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(TimeShift, true); class="type">int barshift_M1[]; ArrayResize(barshift_M1, rates_total); class="type">int barshift_D1[]; ArrayResize(barshift_D1, rates_total); for(class="type">int i = class="num">0; i < rates_total; i++) { barshift_M1[i] = iBarShift(Symbol(), PERIOD_M1, TimeShift[i]); barshift_D1[i] = iBarShift(Symbol(), PERIOD_D1, TimeShift[i]); } if(BarsCalculated(RSI_handle) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(RSI_handle, class="num">0, class="num">0, rates_total, RSI) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(RSI, true); if(CopyOpen(Symbol(), PERIOD_M1, class="num">0, rates_total, Open) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(Open, true); if(CopyClose(Symbol(), PERIOD_D1, class="num">0, rates_total, Close) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(Close, true); if(BarsCalculated(MA_handle) <= class="num">0) class="kw">return(class="num">0);
多均线叠加RSI的缓冲读取与主循环
指标先把四条均线句柄的数据通过 CopyBuffer 拉进数组,每次返回值小于等于 0 就直接 return 退出,避免空数据往下跑。MA、MA2、MA3、MA4 分别用 ArraySetAsSeries 设成时间序列倒序,这样 MA[i] 对应的就是当前 bar,写条件时不用再倒腾下标。 低价和高价数组用 CopyLow / CopyHigh 按 PERIOD_CURRENT 抓取,Time 数组用 CopyTime 同步,全部同样倒序排列。BarsCalculated 对 MA_handle2、MA_handle3、MA_handle4 做存在性检查,没算完就 return(0),这是 MT5 多指标句柄调用的标准防呆。 主循环从 limit-1 往 0 跑,开头用 MathMin(PLOT_MAXIMUM_BARS_BACK-1, rates_total-1-OMIT_OLDEST_BARS) 跳过最老的几根 bar,既防 Array out of range 也省计算量。barshift_M1 和 barshift_D1 越界时 continue,保证跨周期下标安全。 触发买点信号的逻辑是:RSI[i] 低于超卖线且前一根 RSI[i+1] 在线上方(下穿),M1 开盘价 >= D1 前一根收盘价,且 MA[i] > MA2[i]、MA3[i] > MA4[i] 两组均线多头排列。满足条件时 Buffer1[i] 记在 Low[1+i],并在 i==1 且时间变换时弹 myAlert("indicator","Buy")。外汇与贵金属杠杆高,这类多条件共振只提高概率,实盘前务必在 MT5 策略测试器按品种跑一遍验证。
if(CopyBuffer(MA_handle, class="num">0, class="num">0, rates_total, MA) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(MA, true); if(BarsCalculated(MA_handle2) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(MA_handle2, class="num">0, class="num">0, rates_total, MA2) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(MA2, true); if(BarsCalculated(MA_handle3) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(MA_handle3, class="num">0, class="num">0, rates_total, MA3) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(MA3, true); if(BarsCalculated(MA_handle4) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(MA_handle4, class="num">0, class="num">0, rates_total, MA4) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(MA4, true); if(CopyLow(Symbol(), PERIOD_CURRENT, class="num">0, rates_total, Low) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(Low, true); if(CopyHigh(Symbol(), PERIOD_CURRENT, class="num">0, rates_total, High) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(High, true); if(CopyTime(Symbol(), Period(), class="num">0, rates_total, Time) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(Time, true); class=class="str">"cmt">//--- main loop for(class="type">int i = limit-class="num">1; i >= class="num">0; i--) { if (i >= MathMin(PLOT_MAXIMUM_BARS_BACK-class="num">1, rates_total-class="num">1-OMIT_OLDEST_BARS)) class="kw">continue; class=class="str">"cmt">//omit some old rates to prevent "Array out of range" or slow calculation if(barshift_M1[i] < class="num">0 || barshift_M1[i] >= rates_total) class="kw">continue; if(barshift_D1[i] < class="num">0 || barshift_D1[i] >= rates_total) class="kw">continue; class=class="str">"cmt">//Indicator Buffer class="num">1 if(RSI[i] < Oversold && RSI[i+class="num">1] > Oversold class=class="str">"cmt">//Relative Strength Index crosses below fixed value && Open[barshift_M1[i]] >= Close[class="num">1+barshift_D1[i]] class=class="str">"cmt">//Candlestick Open >= Candlestick Close && MA[i] > MA2[i] class=class="str">"cmt">//Moving Average > Moving Average && MA3[i] > MA4[i] class=class="str">"cmt">//Moving Average > Moving Average ) { Buffer1[i] = Low[class="num">1+i]; class=class="str">"cmt">//Set indicator value at Candlestick Low if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Buy"); class=class="str">"cmt">//Alert on next bar open time_alert = Time[class="num">1];
◍ 卖信号与均线反转的缓冲赋值
这段逻辑接着处理指标缓冲区的后四组条件,核心是把 RSI 超买回穿与多周期均线位置关系映射到 Buffer2,再把两组均线交叉映射到 Buffer3、Buffer4。外汇与贵金属波动剧烈,这类信号只代表概率倾向,实盘须自担高风险。 Buffer2 的写入要求 RSI[i] 大于超买线且前一根 RSI[i+1] 已低于该线,同时 M1 周期 K 线开盘价不超过 D1 前一根收盘价,并且 MA < MA2、MA3 < MA4 双重压制。满足条件时取 High[1+i] 作箭头位,i==1 且时间未报警则弹 Sell 提示。 Buffer3 与 Buffer4 只看 MA3 与 MA4 的交叉方向:前者上穿给 Low[i] 并报警 Buy Reversal,后者下穿给 High[i] 并报警 Sell Reversal。四个缓冲区不满足时统一写 EMPTY_VALUE,避免历史残留画线。 直接把下面代码贴进 MT5 自定义指标 OnCalculate 末尾,改 Overbought 或均线周期,就能在 EURUSD 的 M15 上验证报警触发频率。
}
else
{
Buffer1[i] = EMPTY_VALUE;
}
class=class="str">"cmt">//Indicator Buffer class="num">2
if(RSI[i] > Overbought
&& RSI[i+class="num">1] < Overbought class=class="str">"cmt">//Relative Strength Index crosses above fixed value
&& Open[barshift_M1[i]] <= Close[class="num">1+barshift_D1[i]] class=class="str">"cmt">//Candlestick Open <= Candlestick Close
&& MA[i] < MA2[i] class=class="str">"cmt">//Moving Average < Moving Average
&& MA3[i] < MA4[i] class=class="str">"cmt">//Moving Average < Moving Average
)
{
Buffer2[i] = High[class="num">1+i]; class=class="str">"cmt">//Set indicator value at Candlestick High
if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Sell"); class=class="str">"cmt">//Alert on next bar open
time_alert = Time[class="num">1];
}
else
{
Buffer2[i] = EMPTY_VALUE;
}
class=class="str">"cmt">//Indicator Buffer class="num">3
if(MA3[i] > MA4[i]
&& MA3[i+class="num">1] < MA4[i+class="num">1] class=class="str">"cmt">//Moving Average crosses above Moving Average
)
{
Buffer3[i] = Low[i]; class=class="str">"cmt">//Set indicator value at Candlestick Low
if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Buy Reversal"); class=class="str">"cmt">//Alert on next bar open
time_alert = Time[class="num">1];
}
else
{
Buffer3[i] = EMPTY_VALUE;
}
class=class="str">"cmt">//Indicator Buffer class="num">4
if(MA3[i] < MA4[i]
&& MA3[i+class="num">1] > MA4[i+class="num">1] class=class="str">"cmt">//Moving Average crosses below Moving Average
)
{
Buffer4[i] = High[i]; class=class="str">"cmt">//Set indicator value at Candlestick High
if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Sell Reversal"); class=class="str">"cmt">//Alert on next bar open
time_alert = Time[class="num">1];
}
else
{
Buffer4[i] = EMPTY_VALUE;
}
}
class="kw">return(rates_total);
}「MQL5 指标绘图样式怎么选」
MQL5 给指标内置了多种绘图样式,开发时选对样式能直接减少交易者盯盘负担——计算机把信号用视觉或声音推出来,人不用每根 K 线都看。 常用五种:DRAW_ARROW 在指定位置画箭头,偏向标买入/卖出信号或关键事件;DRAW_LINE 连点成线,移动平均线、趋势线都走这个;DRAW_HISTOGRAM 画柱状,成交量、MACD 柱用得最多;DRAW_FILLING 填两条线之间的区域,看数据区间或两指标差很直观;DRAW_NONE 则不画任何图,常用于只发声报警的隐形指标。 外汇和贵金属波动快、杠杆高,用箭头或填充把反转区标出来,能降低漏看信号的概率,但任何样式都不保证信号一定有效。
用双色线替掉拥挤的趋势标记
在 MT5 里做趋势过滤,最怕主图被箭头和均线堆死。Trend Constraint 从 v1.00 到 v1.04 的思路变化很直接:原来靠 H4 以上周期 MA100/MA200 交叉判方向,现在把视觉输出压成一条线,跟着波段方向自动换色。 具体落地是在原有 4 个箭头缓冲区之外,再开缓冲区 5 和 6。缓冲区 5 负责 MA100 在 MA200 上方时画橙线(代码里色值 0xFFAA00,标签 Buy Trend);缓冲区 6 负责 MA100 在 MA200 下方时画蓝线(色值 0x0000FF,标签 Sell Trend)。这样图表上只留一条连贯的线,多空一眼能分。 代码层就是两组 DRAW_LINE 的属性声明。下面这段是 v1.04 指标头里跟线相关的部分,逐行拆一下: #property indicator_type5 DRAW_LINE —— 第5缓冲区画直线 #property indicator_style5 STYLE_SOLID —— 实线 #property indicator_width5 2 —— 线宽2像素 #property indicator_color5 0xFFAA00 —— 橙色调 #property indicator_label5 "Buy Trend" —— 图例名 #property indicator_type6 DRAW_LINE —— 第6缓冲区也画直线 #property indicator_style6 STYLE_SOLID —— 实线 #property indicator_width6 2 —— 线宽2 #property indicator_color6 0x0000FF —— 蓝色调 #property indicator_label6 "Sell Trend" —— 图例名 实盘接 USDJPY 或 Boom 500 这类品种时,这条线只是概率倾向的辅助,不代表趋势一定延续。外汇和贵金属杠杆高,橙色转蓝的一刻可能是假突破,开 MT5 把缓冲区数从 4 改到 6 自己跑一遍最实在。
class="macro">#class="kw">property indicator_type5 DRAW_LINE class="macro">#class="kw">property indicator_style5 STYLE_SOLID class="macro">#class="kw">property indicator_width5 class="num">2 class="macro">#class="kw">property indicator_color5 0xFFAA00 class="macro">#class="kw">property indicator_label5 "Buy Trend" class="macro">#class="kw">property indicator_type6 DRAW_LINE class="macro">#class="kw">property indicator_style6 STYLE_SOLID class="macro">#class="kw">property indicator_width6 class="num">2 class="macro">#class="kw">property indicator_color6 0x0000FF class="macro">#class="kw">property indicator_label6 "Sell Trend"
◍ 把多均线约束写进指标缓冲区
这段声明把第六号绘图线的颜色锁成蓝色(0x0000FF),标签定为 "Sell Trend",意味着它专门承担空头趋势的可视化任务。回看历史深度由 PLOT_MAXIMUM_BARS_BACK 控在 5000 根,最旧 50 根(OMIT_OLDEST_BARS)直接跳过不画,避免早期数据抖动干扰判读。 六个 double 型缓冲区 Buffer1~Buffer6 对应六条线,输入参数里 RSI 的超买超卖边界设为 70 / 30,慢快均线周期分别给到 200 和 100,属于典型的趋势过滤组合。Audible_Alerts 与 Push_Notifications 默认开启,说明信号触发后既要弹窗也要推手机。 myAlert 函数按 type 分流:print 进日志,error 带 "Trend Constraint V1.04" 前缀报错,indicator 类则同时调 Alert 和 SendNotification。注意 order / modify 分支是空壳,说明这指标本身不下单,只做提醒。 OnInit 里 SetIndexBuffer(0, Buffer1) 把第一缓冲绑给 0 号绘图,PLOT_EMPTY_VALUE 设成 EMPTY_VALUE 让无效点不显示;PLOT_DRAW_BEGIN 用 MathMax(Bars-POST+1, OMIT+1) 动态算起绘位置,箭头代号 241 是向下的小三角。开 MT5 把这段直接贴进自定义指标,调慢快周期参数就能看六线如何约束入场。
class="macro">#class="kw">property indicator_color6 0x0000FF class="macro">#class="kw">property indicator_label6 "Sell Trend" class="macro">#define PLOT_MAXIMUM_BARS_BACK class="num">5000 class="macro">#define OMIT_OLDEST_BARS class="num">50 class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double Buffer1[]; class="type">class="kw">double Buffer2[]; class="type">class="kw">double Buffer3[]; class="type">class="kw">double Buffer4[]; class="type">class="kw">double Buffer5[]; class="type">class="kw">double Buffer6[]; input class="type">class="kw">double Oversold = class="num">30; input class="type">class="kw">double Overbought = class="num">70; input class="type">int Slow_MA_period = class="num">200; input class="type">int Fast_MA_period = class="num">100; class="type">class="kw">datetime time_alert; class=class="str">"cmt">//used when sending alert input class="type">bool Audible_Alerts = true; input class="type">bool Push_Notifications = true; class="type">class="kw">double myPoint; class=class="str">"cmt">//initialized in OnInit class="type">int RSI_handle; class="type">class="kw">double RSI[]; class="type">class="kw">double Open[]; class="type">class="kw">double Close[]; class="type">int MA_handle; class="type">class="kw">double MA[]; class="type">int MA_handle2; class="type">class="kw">double MA2[]; class="type">int MA_handle3; class="type">class="kw">double MA3[]; class="type">int MA_handle4; class="type">class="kw">double MA4[]; class="type">class="kw">double Low[]; class="type">class="kw">double High[]; class="type">int MA_handle5; class="type">class="kw">double MA5[]; class="type">int MA_handle6; class="type">class="kw">double MA6[]; class="type">void myAlert(class="type">class="kw">string type, class="type">class="kw">string message) { if(type == "print") Print(message); else if(type == "error") { Print(type+" | Trend Constraint V1.class="num">04 @ "+Symbol()+","+IntegerToString(Period())+" | "+message); } else if(type == "order") { } else if(type == "modify") { } else if(type == "indicator") { if(Audible_Alerts) Alert(type+" | Trend Constraint V1.class="num">04 @ "+Symbol()+","+IntegerToString(Period())+" | "+message); if(Push_Notifications) SendNotification(type+" | Trend Constraint V1.class="num">04 @ "+Symbol()+","+IntegerToString(Period())+" | "+message); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { SetIndexBuffer(class="num">0, Buffer1); PlotIndexSetDouble(class="num">0, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">0, PLOT_ARROW, class="num">241);