构建一个K线图趋势约束模型(第二部分):融合原生指标·综合运用
(3/3)·前两轮只靠裸K与几何约束,这一篇把原生指标接进模型,看信号如何被过滤与增强
很多交易者把移动均线和RSI直接当买卖开关,却忽略它们与原K线约束之间的冲突。当均线说趋势向上、RSI却跌进超卖,模型该信谁?这一篇用程序把两者缝进同一套约束里。
把RSI当极端区探针而非圣杯
RSI 的核心用途是圈出市场的超买、超卖极端区,给潜在反转或趋势延续位置做初筛。Wilder 在 1978 年给出的原始算法至今仍是 MT5 内置 RS 的计算基准:RSI = 100 - (100/(1+RS)),其中 RS = 平均获利 / 平均亏损,平均收益与平均亏损分别取指定周期内盈亏总和除以周期数。 单看 RSI 容易误判,实战里它更多用来确认趋势强度或抓价格—动能背离。比如价格创新高但 RSI 高点走低,往往暗示上行动能衰减,反转概率上升;这种背离在 Boom 500 这类指数上出现的假信号也偏多,需叠加结构过滤。 外汇与贵金属波动受新闻与情绪驱动,RSI 单独使用风险高,任何信号都只是概率倾向。开 MT5 把 RSI 周期从默认 14 调到 9,对比极端区触发频率,你会直观看到参数对灵敏度的真实影响。
◍ 把RSI阈值接进指标程序
原先用均线交叉做触发,现在换成 RSI 水平结合既有趋势约束,指标版本升到 V1.02。逻辑上只在大周期蜡烛方向确认后,才允许 RSI 进入超卖区给买、超买区给卖,这样噪音信号会被砍掉一截。 超卖默认 30、超买默认 70,这两个 input 参数直接暴露给用户,跑不同品种(比如 XAUUSD 和 EURUSD)时大概率要重调。外汇和贵金属杠杆高,RSI 逆势摸顶抄底一旦碰上单边延伸,回撤可能很凶,参数别照抄不验。 下面这段是核心声明片段,逐行拆一下: input double Oversold = 30; —— 暴露超卖阈值,默认30,可在属性框改 input double Overbought = 70; —— 暴露超买阈值,默认70 //I have set... —— 注释提醒默认值可随策略和品种改 ///Indicator Name: Trend Constraint —— 指标名标注 #property copyright "Clemence Benjamin" —— 版权声明 #property link "[MQL5官方文档] —— 链接属性(实际用MT5时这行可删) #property version "1.02" —— 版本号 #property description "..." —— 仅D1熊烛给卖、牛烛给买的描述 #property indicator_chart_window —— 主图叠加 #property indicator_buffers 2 —— 两个缓冲区 #property indicator_plots 2 —— 两个绘制层 #property indicator_type1 DRAW_ARROW —— 买信号画箭头 #property indicator_width1 5 —— 箭头宽度5 #property indicator_color1 0xFF3C00 —— 买箭橙红 #property indicator_label1 "Buy" —— 图层标签Buy #property indicator_type2 DRAW_ARROW —— 卖信号画箭头 #property indicator_width2 5 —— 宽度5 #property indicator_color2 0x0000FF —— 卖箭蓝 #property indicator_label2 "Sell" —— 标签Sell #define PLOT_MAXIMUM_BARS_BACK 5000 —— 回溯最多5000根 #define OMIT_OLDEST_BARS 50 —— 最老50根不绘 //--- indicator buffers —— 缓冲区注释 double Buffer1[]; —— 买缓冲 double Buffer2[]; —— 卖缓冲 input double Oversold = 30; —— 重复声明(编译会警告,建议删重) input double Overbought = 70; —— 重复声明 double myPoint; —— 点值变量,OnInit里初始化 int RSI_handle; —— RSI句柄 double RSI[]; —— RSI数据数组 后续 Open/Close/MA 等数组与句柄都是为趋势约束和信号判定备料。 开 MT5 把这段贴进自定义指标,先不改参数跑一遍 EURUSD D1,看橙红/蓝箭头是否只在同向蜡烛出现;若 30/70 信号过密,把 Oversold 调到 25、Overbought 调到 75 再比对。
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> Oversold = <span class="number">class="num">30</span>; <span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> Overbought = <span class="number">class="num">70</span>; <span class="comment">class=class="str">"cmt">//I have set the class="kw">default standard values, but you can alter them to suit your strategy and instrument being traded.</span> <span class="comment">class=class="str">"cmt">///Indicator Name: Trend Constraint</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright</span> <span class="class="type">class="kw">string">"Clemence Benjamin"</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">link</span> <span class="class="type">class="kw">string">"[MQL5官方文档] <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">version</span> <span class="class="type">class="kw">string">"class="num">1.02"</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">description</span> <span class="class="type">class="kw">string">"A model that seek to produce sell signal when D1 candle is Bearish only and buy signal when it is Bullish"</span> <span class="comment">class=class="str">"cmt">///--- indicator settings</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">indicator_chart_window</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">indicator_buffers</span> <span class="number">class="num">2</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">indicator_plots</span> <span class="number">class="num">2</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">indicator_type1</span> <span class="macro">DRAW_ARROW</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">indicator_width1</span> <span class="number">class="num">5</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">indicator_color1</span> <span class="number">0xFF3C00</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">indicator_label1</span> <span class="class="type">class="kw">string">"Buy"</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">indicator_type2</span> <span class="macro">DRAW_ARROW</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">indicator_width2</span> <span class="number">class="num">5</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">indicator_color2</span> <span class="number">0x0000FF</span> <span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">indicator_label2</span> <span class="class="type">class="kw">string">"Sell"</span> <span class="preprocessor">class="macro">#define </span>PLOT_MAXIMUM_BARS_BACK <span class="number">class="num">5000</span> <span class="preprocessor">class="macro">#define </span>OMIT_OLDEST_BARS <span class="number">class="num">50</span> <span class="comment">class=class="str">"cmt">//--- indicator buffers</span> <span class="keyword">class="type">class="kw">double</span> Buffer1[]; <span class="keyword">class="type">class="kw">double</span> Buffer2[]; <span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> Oversold = <span class="number">class="num">30</span>; <span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> Overbought = <span class="number">class="num">70</span>; <span class="keyword">class="type">class="kw">double</span> myPoint; <span class="comment">class=class="str">"cmt">//initialized in OnInit</span> <span class="keyword">class="type">int</span> RSI_handle; <span class="keyword">class="type">class="kw">double</span> RSI[]; <span class="keyword">class="type">class="kw">double</span> Open[]; <span class="keyword">class="type">class="kw">double</span> Close[]; <span class="keyword">class="type">int</span> MA_handle; <span class="keyword">class="type">class="kw">double</span> MA[]; <span class="keyword">class="type">int</span> MA_handle2; <span class="keyword">class="type">class="kw">double</span> MA2[]; <span class="keyword">class="type">class="kw">double</span> Low[]; <span class="keyword">class="type">class="kw">double</span> High[]; <span class="keyword">class="type">void</span> myAlert(<span class="keyword">class="type">class="kw">string</span> type, <span class="keyword">class="type">class="kw">string</span> message) { <span class="keyword">if</span>(type == <span class="class="type">class="kw">string">"print"</span>) <span class="functions">Print</span>(message); <span class="keyword">else</span> <span class="keyword">if</span>(type == <span class="class="type">class="kw">string">"error"</span>) { <span class="functions">Print</span>(type+<span class="class="type">class="kw">string">" | Trend Constraint V1.class="num">02 @ "</span>+<span class="functions">Symbol</span>()+<span class="class="type">class="kw">string">","</span>+<span class="functions">IntegerToString</span>(<span class="functions">Period</span>())+<span class="class="type">class="kw">string">" | "</span>+message); } <span class="keyword">else</span> <span class="keyword">if</span>(type == <span class="class="type">class="kw">string">"order"</span>) { } <span class="keyword">else</span> <span class="keyword">if</span>(type == <span class="class="type">class="kw">string">"modify"</span>) { } } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Custom indicator initialization function |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">int</span> <span class="functions">OnInit</span>() { <span class="functions">SetIndexBuffer</span>(<span class="number">class="num">0</span>, Buffer1);
「双箭头缓冲与指标句柄的初始化细节」
这段初始化代码同时挂了两个箭头缓冲:plot 0 用 241 号箭头,plot 1 用 242 号箭头,分别对应多空信号。两个缓冲都设了 PLOT_EMPTY_VALUE 为 EMPTY_VALUE,并用 MathMax 限制起绘位置,避免在最老的历史棒上乱画。 myPoint 用来统一点值:当报价小数位是 5 或 3 位(即美分/日元后多一位)时,把 Point() 乘 10,后续计算止损距离就不会因经纪商报价精度而偏差。 RSI 取 14 周期收盘价,均线则开了两条——7 周期 SMMA 与 400 周期 SMA,前者抓短线节奏,后者定大周期偏向。任一句柄创建失败就打印错误码并返回 INIT_FAILED,开 MT5 加载时若终端报 iRSI 或 iMA 失败,直接看 GetLastError() 输出即可定位。 外汇与贵金属杠杆高,这类多指标共振信号仅作概率参考,实盘前务必在策略测试器跑足样本。
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); 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); 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()); class="kw">return(INIT_FAILED); } 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); } class="kw">return(INIT_SUCCEEDED); }
跨周期取数前的数组与时间对齐
这段逻辑出现在自定义指标的主计算函数入口,参数列表里已经通过引用拿到了 high[]、low[]、close[]、tick_volume[]、volume[]、spread[] 六类当前周期序列,但真正要计算多周期信号,还得自己把 M1 和 D1 的 K 线位置对上。 先算需要刷新的 bar 数量:limit = rates_total - prev_calculated。若 prev_calculated 小于 1,说明是首根加载,用 ArrayInitialize 把 Buffer1、Buffer2 填成 EMPTY_VALUE 避免残值;否则 limit 加 1,把上一根也重算一遍。 时间轴对齐靠 CopyTime 拉出当前周期每根 K 线的开盘时间存入 TimeShift[],并设为序列排列。随后对每个 i 调 iBarShift,分别拿到该时间在 PERIOD_M1 和 PERIOD_D1 上对应的 bar 下标,写进 barshift_M1[] 与 barshift_D1[]。外汇与贵金属跨周期引用存在滑点与时区偏差,实盘高风险,建议先在 MT5 策略测试器用 2023 年 XAUUSD 数据验证下标是否连续。 指标句柄校验不能省:RSI_handle、MA_handle、MA_handle2 都要先 BarsCalculated 大于 0,再用 CopyBuffer 把 RSI[]、MA[]、MA2[] 拷进来并全部 ArraySetAsSeries 置为时间序列。Open 取 M1、Close 取 D1,任何一次 Copy 返回小于等于 0 就直接 return 退出,保证后续计算用的全是有效数据。
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); class=class="str">"cmt">//--- initial zero if(prev_calculated < class="num">1) { ArrayInitialize(Buffer1, EMPTY_VALUE); ArrayInitialize(Buffer2, EMPTY_VALUE); } else limit++; 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); 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);
◍ RSI 背离信号的缓冲区落地逻辑
这段核心循环把多周期条件压进两个指标缓冲区。先通过 CopyLow / CopyHigh 把当前周期的高低价拉进数组并倒序排列,避免后面按索引取价时方向搞反。 主循环从 limit-1 往 0 跑,开头两道 continue 很关键:一是跳过超过 PLOT_MAXIMUM_BARS_BACK 减 1 与 rates_total-1-OMIT_OLDEST_BARS 二者较小值的老 K,防止数组越界和拖慢计算;二是过滤掉 barshift_M1 或 barshift_D1 越界的柱,保证跨周期定位有效。 Buffer1 的写入条件是 RSI[i] 小于超卖且 RSI[i+1] 大于超卖(即下穿固定超卖值),同时 M1 那根 K 的 Open 大于等于 D1 前一根 Close,且快线 MA 大于慢线 MA2;命中就把 Buffer1[i] 设为该 K 最低价,否则置 EMPTY_VALUE。 Buffer2 对称处理超买:RSI 上穿超买、M1 的 Open 小于等于 D1 前收、MA 小于 MA2,则在最高价挂值。外汇与贵金属波动大,这类多条件信号只在概率上倾向过滤假突破,实盘前请在 MT5 用策略测试器按品种核对 OMIT_OLDEST_BARS 取值。
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); 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 ) { Buffer1[i] = Low[i]; class=class="str">"cmt">//Set indicator value at Candlestick Low } 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 ) { Buffer2[i] = High[i]; class=class="str">"cmt">//Set indicator value at Candlestick High } else { Buffer2[i] = EMPTY_VALUE; } } class="kw">return(rates_total); }
「多信号叠加后的图表实感」
把前几篇的改动揉到一起后,图表的信号密度和可读性确实上了个台阶。RSI 与移动平均线作为可视化层,帮我们盯住趋势切换的节点;长周期 K 线的趋势约束模块也比早先版本更稳。 补进 SMA 400 之后,对 Boom 500 指数这类品种的潜在反转位置多了一层参照。图 4 与图 5 的 Trend Constraint v1.02 在 Boom 500 上跑出来的形态,回头对照文章开头那版系统,能直接看出原先漏掉的几类假信号被压下去了。 这类合成信号对外汇、贵金属同样只是概率参考,杠杆品种波动剧烈,实盘前务必在 MT5 用历史数据复验。下一步建议把 SMA 周期从 400 往 200/600 两端拉一下,看信号触发频率怎么变。
画得少,看得清
趋势约束指标本身不是移动平均线的替代品,两者叠加在 MT5 上往往能压住噪点、让方向更易读。内置指标是自定义开发的底座,V1.01r 与 V1.02 两个脚本(7.3 KB 与 6.91 KB)已经把基础框架跑通。 当前阶段真正的坑在权重分配与刷新频率——这两处没调好,图表就会在快速行情里滞后或半残。外汇与贵金属波动剧烈,这类工具只降低判断成本,不消除高风险。 下一轮重点就是啃掉权重与刷新的硬骨头,在那之前,先把现有 mq5 丢进策略测试器看一次回放,比空想参数有用。