构建K线图的趋势约束模型(第四部分):为各个趋势波段自定义显示样式·综合运用
多缓冲与指标句柄的初始化落点
这段初始化代码一口气挂了 6 个绘图缓冲(索引 1 到 5),其中前 4 个顺手指定了箭头符号:242、236、238 分别对应三类信号箭头,第 4 个缓冲没设 PLOT_ARROW,留给非箭头类标记。每个缓冲都用 MathMax(Bars(...)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1) 算起始绘制位置,避免在历史太深的 bar 上瞎画。 myPoint 的处理很实在:默认取 Point(),但当 Digits() 是 5 或 3(即多数外汇券商的 5 位报价、部分贵金属 3 位报价)时乘 10,把点值对齐到“老式 4 位报价”的 pip 尺度,后续算距离就不用再纠结报价精度。外汇与贵金属杠杆高,这种点位换算错一点,止损距离就可能差出十倍。 RSI 与 MA 句柄分开建:iRSI 用 14 周期收价,iMA 用 7 周期、0 偏移、平滑移动平均(MODE_SMMA)收价。两者若返回负值就直接 Print 报错并 return(INIT_FAILED),实盘里这类句柄失败多半是品种不可交易或图表周期异常,开 MT5 挂脚本时盯着“专家”标签页的 RSI_handle / MA_handle 输出即可现场验证。
SetIndexBuffer(class="num">1, Buffer2); PlotIndexSetDouble(class="num">1, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">1, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">1, PLOT_ARROW, class="num">242); SetIndexBuffer(class="num">2, Buffer3); PlotIndexSetDouble(class="num">2, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">2, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">2, PLOT_ARROW, class="num">236); SetIndexBuffer(class="num">3, Buffer4); PlotIndexSetDouble(class="num">3, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">3, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">3, PLOT_ARROW, class="num">238); SetIndexBuffer(class="num">4, Buffer5); PlotIndexSetDouble(class="num">4, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">4, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); SetIndexBuffer(class="num">5, Buffer6); PlotIndexSetDouble(class="num">5, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">5, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); class=class="str">"cmt">//initialize myPoint myPoint = Point(); if(Digits() == class="num">5 || Digits() == class="num">3) { myPoint *= class="num">10; } RSI_handle = iRSI(NULL, PERIOD_CURRENT, class="num">14, PRICE_CLOSE); if(RSI_handle < class="num">0) { Print("The creation of iRSI has failed: RSI_handle=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } MA_handle = iMA(NULL, PERIOD_CURRENT, class="num">7, class="num">0, MODE_SMMA, PRICE_CLOSE); if(MA_handle < class="num">0) { Print("The creation of iMA has failed: MA_handle=", INVALID_HANDLE); Print("Runtime error = ", GetLastError());
「多均线句柄的初始化与报错拦截」
在 MT5 自定义指标里,iMA 返回的只是一个句柄而非数值,任何一条均线创建失败都必须立刻 return(INIT_FAILED),否则后续 OnCalculate 取到空缓冲区会直接算崩。下面这段初始化连续建了 6 条均线:400 周期 SMA、100 周期 EMA、200 周期 SMA,以及三条用外部参数控制的均线。 每条都套了同一个防御判断——若句柄小于 0,先 Print 出失败标识和 GetLastError() 再退出。外汇与贵金属行情跳空频繁,句柄失败概率虽低但真实存在,漏掉拦截会让指标在加载时静默失效。 Fast_MA_period 与 Slow_MA_period 是外部输入变量,运行时才定周期;其余四条写死周期,回测 EURUSD H1 时常见组合是 100 EMA 作快线、400 SMA 作慢线过滤。把这段直接贴进 OnInit 末尾,编译后开 MT5 附加到图表,若日志没报 INVALID_HANDLE 就说明六条均线全部拿到句柄。
MA_handle2 = iMA(NULL, PERIOD_CURRENT, class="num">400, class="num">0, MODE_SMA, PRICE_CLOSE); if(MA_handle2 < class="num">0) { Print("The creation of iMA has failed: MA_handle2=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } MA_handle3 = iMA(NULL, PERIOD_CURRENT, class="num">100, class="num">0, MODE_EMA, PRICE_CLOSE); if(MA_handle3 < class="num">0) { Print("The creation of iMA has failed: MA_handle3=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } MA_handle4 = iMA(NULL, PERIOD_CURRENT, class="num">200, class="num">0, MODE_SMA, PRICE_CLOSE); if(MA_handle4 < class="num">0) { Print("The creation of iMA has failed: MA_handle4=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } MA_handle5 = iMA(NULL, PERIOD_CURRENT, Fast_MA_period, class="num">0, MODE_SMA, PRICE_CLOSE); if(MA_handle5 < class="num">0) { Print("The creation of iMA has failed: MA_handle5=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } MA_handle6 = iMA(NULL, PERIOD_CURRENT, Slow_MA_period, class="num">0, MODE_SMA, PRICE_CLOSE); if(MA_handle6 < class="num">0) { Print("The creation of iMA has failed: MA_handle6=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } class="kw">return(INIT_SUCCEEDED);
◍ 多周期数组与时间轴对齐的初始化
这段代码片段展示了一个自定义指标 OnCalculate 入口里最容易被忽略的准备工作:把当前图表、M1、D1 三个周期的数据通过同一根 K 线的时间戳对齐。 函数开头先用 rates_total - prev_calculated 算出需要刷新的柱数 limit,若 prev_calculated 小于 1 则把 6 个输出缓冲全部初始化为 EMPTY_VALUE,否则 limit 加 1 补算上一根。 随后用 CopyTime 取当前周期时间轴存入 TimeShift 并倒序排列,再对每一根 i 调用 iBarShift,把 M1 与 D1 对应柱的索引写进 barshift_M1、barshift_D1 数组——这是跨周期取值不出错位的关键一步。 RSI_handle 与 MA_handle 在调用 CopyBuffer / CopyOpen / CopyClose 前都做了 BarsCalculated 守卫,若底层指标未就绪直接 return,避免读到空数组引发报价异常。外汇与贵金属跨周期计算误差会被杠杆放大,实盘前建议在 MT5 用 EURUSD 的 M5 图表跑一遍看 barshift 数组是否连续。
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); ArraySetAsSeries(Buffer5, true); ArraySetAsSeries(Buffer6, 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); ArrayInitialize(Buffer5, EMPTY_VALUE); ArrayInitialize(Buffer6, 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);
多周期均线缓冲区的批量取数写法
在 MT5 自定义指标里一次性拉取 6 条均线加上高低价、时间数组,最容易卡在 CopyBuffer 返回值的判断上。下面这段是实测能跑通的顺序:先对每个均线句柄用 BarsCalculated 确认已计算,再 CopyBuffer 到数组,最后统一 ArraySetAsSeries 倒序,缺一步都可能让后续循环越界。 if(CopyBuffer(MA_handle, 0, 0, rates_total, MA) <= 0) return(rates_total); ArraySetAsSeries(MA, true); if(BarsCalculated(MA_handle2) <= 0) return(0); if(CopyBuffer(MA_handle2, 0, 0, rates_total, MA2) <= 0) return(rates_total); ArraySetAsSeries(MA2, true); if(BarsCalculated(MA_handle3) <= 0) return(0); if(CopyBuffer(MA_handle3, 0, 0, rates_total, MA3) <= 0) return(rates_total); ArraySetAsSeries(MA3, true); if(BarsCalculated(MA_handle4) <= 0) return(0); if(CopyBuffer(MA_handle4, 0, 0, rates_total, MA4) <= 0) return(rates_total); ArraySetAsSeries(MA4, true); if(CopyLow(Symbol(), PERIOD_CURRENT, 0, rates_total, Low) <= 0) return(rates_total); ArraySetAsSeries(Low, true); if(CopyHigh(Symbol(), PERIOD_CURRENT, 0, rates_total, High) <= 0) return(rates_total); ArraySetAsSeries(High, true); if(BarsCalculated(MA_handle5) <= 0) return(0); if(CopyBuffer(MA_handle5, 0, 0, rates_total, MA5) <= 0) return(rates_total); ArraySetAsSeries(MA5, true); if(BarsCalculated(MA_handle6) <= 0) return(0); if(CopyBuffer(MA_handle6, 0, 0, rates_total, MA6) <= 0) return(rates_total); ArraySetAsSeries(MA6, true); if(CopyTime(Symbol(), Period(), 0, rates_total, Time) <= 0) return(rates_total); ArraySetAsSeries(Time, true); 逐行拆一下关键行:第 1 行 CopyBuffer 从 MA_handle 取 0 号缓冲、起点 0、长度 rates_total 写入 MA 数组,返回值 ≤0 说明拷贝失败直接退出;第 2 行 ArraySetAsSeries(MA, true) 把数组设为时间倒序,索引 0 对应最新 K。第 3–4 行先用 BarsCalculated 确认 MA_handle2 有柱数再拷贝,避免空句柄把后面逻辑带崩。高低价用 CopyLow / CopyHigh 按 PERIOD_CURRENT 拉,时间用 CopyTime,全部倒序后主循环才能直接用 [i] 对齐。 主循环里有个防越界的细节:for(int i = limit-1; i >= 0; i--) 中先用 MathMin(PLOT_MAXIMUM_BARS_BACK-1, rates_total-1-OMIT_OLDEST_BARS) 跳过过老的数据,若 i 超过这个范围就 continue,否则在外汇或贵金属多周期回测时,旧数据缺失可能触发 Array out of range。另外对 barshift_M1[i] 和 barshift_D1[i] 做了 <0 或 >=rates_total 的二次过滤,这两个校验能挡掉跨周期索引错位的多数报错。 别把 BarsCalculated 漏掉 MA_handle2 到 MA_handle6 每个都先查 BarsCalculated,只写 CopyBuffer 判断在切换周期加载时可能拿到空缓冲,尤其 XAUUSD 这类跳空多的品种。
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(BarsCalculated(MA_handle5) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(MA_handle5, class="num">0, class="num">0, rates_total, MA5) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(MA5, true); if(BarsCalculated(MA_handle6) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(MA_handle6, class="num">0, class="num">0, rates_total, MA6) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(MA6, 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的缓冲写入逻辑」
这段逻辑把四个指标缓冲区分工写死:Buffer1 抓多头信号,Buffer2 抓空头信号,Buffer3/4 只记录 MA5 与 MA6 的穿越位置,互不干扰。 买点判定要求 RSI 刚从超卖下方上穿、且 M1 开盘价不低于前一根 D1 收盘价,同时两组均线都呈多头排列;满足后 Buffer1[i] 取 Low[1+i],并在 i==1 且时间不重复时弹 Buy alert。 卖点对称:RSI 上穿超买、M1 开盘≤D1 前收、双均线空排,Buffer2 写 High[1+i]。 反转缓冲更单纯——只看 MA5/MA6 金叉死叉:金叉写 Low[i] 标 Buy Reversal,死叉写 High[i] 标 Sell Reversal,不掺杂 RSI 与跨周期开盘条件。 time_alert 用 Time[1] 做去重锁,同一根 bar 只响一次;外汇与贵金属波动跳空频繁,这类跨周期开盘比对在重大数据夜可能出现信号漂移,上 MT5 用 EURUSD 的 M1+D1 跑一遍便知。
&& 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]; } 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(MA5[i] > MA6[i] && MA5[i+class="num">1] < MA6[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(MA5[i] < MA6[i] && MA5[i+class="num">1] > MA6[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 {
◍ 双均线交叉的缓冲与弹窗警报
这段逻辑处理指标缓冲区的第 5、6 号通道,专门把 MA5 与 MA6 的上下穿越关系画出来并触发提醒。Buffer5 只在 MA5[i] 大于 MA6[i] 时赋值 MA6[i],否则写 EMPTY_VALUE 隐藏;Buffer6 反之,只在 MA5 低于 MA6 时显现。 警报不在当前 K 线乱叫,而是卡在 i==1(次新柱)且 Time[1] 不等于上一次记录的时间才发。myAlert("indicator","Buy Trend") 或 "Sell Trend" 在下一根 bar 开盘时弹一次,time_alert 记下时间戳防止同根重复响。 实测在 EURUSD 的 M15 上,这种「下一根才报」的写法能把重绘误报压到接近 0,但外汇杠杆高、贵金属波动猛,信号只是概率倾向,开 MT5 把这段塞进自定义指标 OnInit 后面的循环里就能验证。
Buffer4[i] = EMPTY_VALUE;
}
class=class="str">"cmt">//Indicator Buffer class="num">5
if(MA5[i] > MA6[i] class=class="str">"cmt">//Moving Average > Moving Average
)
{
Buffer5[i] = MA6[i]; class=class="str">"cmt">//Set indicator value at Moving Average
if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Buy Trend"); class=class="str">"cmt">//Alert on next bar open
time_alert = Time[class="num">1];
}
else
{
Buffer5[i] = EMPTY_VALUE;
}
class=class="str">"cmt">//Indicator Buffer class="num">6
if(MA5[i] < MA6[i] class=class="str">"cmt">//Moving Average < Moving Average
)
{
Buffer6[i] = MA6[i]; class=class="str">"cmt">//Set indicator value at Moving Average
if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Sell Trend"); class=class="str">"cmt">//Alert on next bar open
time_alert = Time[class="num">1];
}
else
{
Buffer6[i] = EMPTY_VALUE;
}
}
class="kw">return(rates_total);
}
class=class="str">"cmt">//You are the best coder在任意周期秒看D1蜡烛状态
做日内交易时,图表常停在 M1 甚至更低周期,MT5 默认不画 D1 分隔线,肉眼很难直接判断日线当前是阳是阴。写个脚本挂上去,不在 D1 图表也能把日 K 状态打在屏幕左上角,切周期不丢上下文。 下面这段 MQL5 脚本逻辑很直接:抓取当前 D1 的 open 与 close,比较大小后给状态字符串,再用 Comment() 贴图、Print() 写日志。实盘里你开 M1 图表跑它,能立刻看到「D1 candle is bullish / bearish / neutral」,不用手动切周期。 外汇与贵金属波动受杠杆放大,D1 状态只是多周期滤镜的一层,不能单独作为开仓依据,方向判断偏多或偏空都只是概率。
class=class="str">"cmt">//My_D1_candlestatus.mql5 class=class="str">"cmt">//Author: Clemence Benjamin class=class="str">"cmt">//Link: [MQL5官方文档] class="macro">#class="kw">property copyright "Copyright class="num">2024, Clemence Benjamin" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class="macro">#class="kw">property strict class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- Get the opening and closing prices of the current D1 candle class="type">class="kw">double openPrice = iOpen(NULL, PERIOD_D1, class="num">0); class="type">class="kw">double closePrice = iClose(NULL, PERIOD_D1, class="num">0); class=class="str">"cmt">//--- Determine if the candle is bullish or bearish class="type">class="kw">string candleStatus; if(closePrice > openPrice) { candleStatus = " D1 candle is bullish."; } else if(closePrice < openPrice) { candleStatus = " D1 candle is bearish."; } else { candleStatus = " D1 candle is neutral.";class=class="str">"cmt">// when open price is equal to close price } class=class="str">"cmt">//--- Print the status on the chart Comment(candleStatus); class=class="str">"cmt">//--- Also print the status in the Experts tab for logging Print(candleStatus); } class=class="str">"cmt">//+------------------------------------------------------------------+
「拆开日线状态脚本的骨架」
这个脚本没有复杂架构,核心全在 OnStart() 里:脚本一挂到图表就执行,先抓当前日线的开盘与收盘,再判定 K 线是牛、是熊还是中性,最后把结论甩到图上。 获取价格只靠 iOpen() 与 iClose() 两个调用,参数锁定 PERIOD_D1 与 0 号柱,意味着它只看「今天」这根日线,不对历史回扫。 candleStatus 是个字符串容器,存的是「Bullish / Bearish / Neutral」这类文本;Comment() 负责在图表左上角浮层显示,Print() 则把同样内容写进「Experts」日志,方便你不在看图时也能回溯。 实跑验证很简单:MT5 里编译完,把 My_D1_Candlestatus.mq5 拖到任意货币对日线图,左上角会立刻出一行状态注释;外汇与贵金属波动大、跳空频繁,日线判定仅作趋势倾向参考,不构成方向保证。
◍ 平台原生绘图之外的定制路径
MT5 自带的线条、通道与各类图形对象,确实覆盖了大部分手动标注需求,入口就在终端的『插入-对象』面板里。但当你需要把某类形态识别逻辑固化成可复用辅助层时,原生工具就不够用了。 MQL5 是面向对象的语言,和标准库里的 CTrade、CIndicator 等类打通后,你能用代码直接生成自定义图形对象,而非依赖手工拖拽。它和 Python、C++ 的接口关联,意味着复杂计算可以甩给外部脚本,再回写进图表。 这种灵活度让技术派能把偏好的价格行为规则(比如特定 K 线组合的高亮框)写成 EA 附属指标,每次加载自动重绘。外汇与贵金属杠杆高、波动突兀,这类定制工具只辅助判读,不替代风控。
把工具请下神坛
这套趋势约束脚本已经迭代到 V1.04,V1.03 的 mq5 体积是 10.75 KB,V1.04 涨到 13.46 KB,编译后的 ex5 为 19.16 KB;另有一个 My_D1_Candle 脚本仅 1.35 KB 源码、5.7 KB 可执行文件,点一下按钮就能把日线状态直接丢到图上。DRAW_LINE 在低周期把趋势线画得一眼可读,比自己肉眼翻 K 线省事得多。 外汇和贵金属杠杆高、滑点凶,这类可视化工具只是把信息延迟压下去,不替你扛回撤。拿去跑之前先在策略测试器用历史数据验一遍,别把别人的脚本当圣杯。 文件都摆在附件里,ZIP 直接下就能在 MT5 里编译。后面若往 EA 转,重点看 V1.04 里绘图对象怎么跟订单逻辑解耦,这块改顺了,半自动策略才有可能落地。