MQL5 简介(第 10 部分):MQL5 中使用内置指标的初学者指南·进阶篇
(2/3)· 从指标句柄到图表标记,新手也能写出内存干净、风险可控的 RSI 自动交易程序
EA 初始化与百根 K 线数据接管
这段 MT5 代码把 RSI 和最近 100 根蜡烛的 OHLC 全部拉进数组,方便后续做高低点扫描。注意 CopyBuffer 的起始位是 1 而不是 0,意味着实时那根未完成 K 线被刻意排除,回测里能少踩很多假信号坑。 初始化阶段先用 ArraySetAsSeries 把 rsi_buffer、open、close、high、low、time 全部倒序,这样 [0] 永远是最新一根。RSI 句柄用 iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 建,周期参数写死 14 是经典设置,但你可以改第二个数字切到 M15 或 H1 验证不同品种反应。 OnTick 里每次报价都重拷 100 根:CopyOpen/CopyClose/CopyHigh/CopyLow 的第三参都是 1、第四参 100,和 RSI 的拷贝窗口完全对齐。外汇和贵金属波动大,这种高频重拷会吃 CPU,实盘前建议在策略测试器里跑一遍看是否掉帧。 max_high、min_low 及对应时间变量留作后面比对新高/new低用,目前只是清零占位。MagicNumber=1111 是订单指纹,多 EA 同品种跑时改这个数就能隔离仓位。
input class="type">int MagicNumber = class="num">1111; class="type">int rsi_handle; class="type">class="kw">double rsi_buffer[]; class="type">class="kw">double open[]; class="type">class="kw">double close[]; class="type">class="kw">double high[]; class="type">class="kw">double low[]; class="type">class="kw">datetime time[]; class="type">class="kw">double max_high = class="num">0; class=class="str">"cmt">// Maximum high for the candlesticks class="type">class="kw">datetime min_time1 = class="num">0; class=class="str">"cmt">// Time of the maximum high candlestick class="type">class="kw">double min_low = class="num">0; class=class="str">"cmt">// Minimum low for the candlesticks class="type">class="kw">datetime min_time2 = class="num">0; class=class="str">"cmt">// Time of the minimum low candlestick class="type">class="kw">datetime time_low = class="num">0; class=class="str">"cmt">// Time of the RSI low class="type">class="kw">datetime times_high = class="num">0; class=class="str">"cmt">// Time of the RSI high class="type">int OnInit() { ArraySetAsSeries(rsi_buffer, true); rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE); ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); trade.SetExpertMagicNumber(MagicNumber); class="kw">return (INIT_SUCCEEDED); } class="type">void OnTick() { CopyBuffer(rsi_handle, class="num">0, class="num">1, class="num">100, rsi_buffer); CopyOpen(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, open); CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, close); CopyHigh(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, high); CopyLow(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, low);
◍ 用前12根K线锚定反转结构
这段逻辑只扫最近 12 根蜡烛,寻找两类价格形态与 RSI 极值的时间锚点。外汇与贵金属波动快,12 根窗口太宽会吞掉局部结构,太窄又容易漏掉隔根反转,实盘里可以拿 EURUSD 的 M15 先跑一遍看命中率。 第一个循环找看涨反转:当前根收低于开、前一根收高于开,即阴包阳后的反向孕线,取两根最高价中的较大值作为 max_high,并记录较早时间 min_time1。第二个循环镜像处理看跌:当前收高于开、前一根收低于开,取两根最低价较小值 min_low 与较早时间 min_time2。 RSI 部分分两头:当 rsi_buffer[i+1] 小于 30 且比前一根更低,判为超卖低点,记 time_low;当 rsi_buffer[i+1] 大于 70 且比前一根更高,判为超买高点,记 times_high。阈值 30/70 是经典设置,但黄金常在亚盘假破,建议接小布盯盘回看近一月触 70 后的回撤概率再决定是否收紧。 所有循环都在首次命中后 break,意味着只取最近一次信号。若你想统计 12 根内共出现几次背离,得把 break 去掉并改用数组收集——这是验证策略样本量的第一步。
CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time); class=class="str">"cmt">// Loop to find the maximum high from a bullish candlestick pattern for(class="type">int i = class="num">0; i < class="num">12; i++) { class=class="str">"cmt">// Check for a bullish pattern: current close < open and previous close > open if(close[i] < open[i] && close[i+class="num">1] > open[i+class="num">1]) { class=class="str">"cmt">// Calculate the maximum high between the two candlesticks max_high = MathMax(high[i], high[i+class="num">1]); class=class="str">"cmt">// Record the time of the corresponding candlestick min_time1 = MathMin(time[i], time[i+class="num">1]); class="kw">break; } } class=class="str">"cmt">// Loop to find the minimum low from a bearish candlestick pattern for(class="type">int i = class="num">0; i < class="num">12; i++) { class=class="str">"cmt">// Check for a bearish pattern: current close > open and previous close < open if(close[i] > open[i] && close[i+class="num">1] < open[i+class="num">1]) { class=class="str">"cmt">// Calculate the minimum low between the two candlesticks min_low = MathMin(low[i], low[i+class="num">1]); class=class="str">"cmt">// Record the time of the corresponding candlestick min_time2 = MathMin(time[i], time[i+class="num">1]); class="kw">break; } } class=class="str">"cmt">// Loop to find the RSI low point for(class="type">int i = class="num">0; i < class="num">12; i++) { class=class="str">"cmt">// Check if the RSI is oversold and forms a low point if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1]) { class=class="str">"cmt">// Record the time of the RSI low time_low = time[i+class="num">1]; class="kw">break; } } class=class="str">"cmt">// Loop to find the RSI high point for(class="type">int i = class="num">0; i < class="num">12; i++) { class=class="str">"cmt">// Check if the RSI is overbought and forms a high point if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1]) { class=class="str">"cmt">// Record the time of the RSI high times_high = time[i+class="num">1]; class="kw">break; } } } class=class="str">"cmt">// Loop to find RSI and candlestick lows for(class="type">int i = class="num">0; i < class="num">12; i++) { class=class="str">"cmt">// Check if the RSI is oversold and forms a low point if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1]) { class=class="str">"cmt">// Record the time of the RSI low time_low = time[i+class="num">1];
「RSI背离扫描的变量与初始化落点」
这段逻辑干的事很直接:在最近 12 根 K 线里,先扫 RSI 低于阈值且形成局部低点的组合,取两根 K 线的最低低价写进 min_low 并记下时间;再反向扫 RSI 大于 70 且抬头的组合,取两根 K 线的最高高价写进 max_high。循环里一旦命中就 break,说明只认第一个信号,不追溯更早的背离。 初始化部分把 rsi_buffer 和 OHLC 数组全设成 ArraySetAsSeries(true),索引 0 就是最新一根柱,回看历史直接加下标即可。RSI 句柄用 iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 建,周期跟当前图表走,长度 14 是默认参数,实战里可以改成 7 或 21 看敏感度变化。 MagicNumber 设成 1111 只是订单标识,多 EA 同跑时靠它隔离持仓。OnTick 里 CopyBuffer 从偏移 1 拷 100 根 RSI、CopyOpen/CopyClose 同理,意味着第 0 根(成型中阳线)被故意跳过,只处理已收盘数据,能降低重绘导致的信号抖动。外汇与贵金属杠杆高,这类扫描仅作概率参考,实盘前请在 MT5 策略测试器用历史数据验证命中率。
min_low = (class="type">class="kw">double)MathMin(low[i], low[i+class="num">1]); class="kw">break; } } class=class="str">"cmt">// Loop to find RSI and candlestick highs for(class="type">int i = class="num">0; i < class="num">12; i++) { class=class="str">"cmt">// Check if the RSI is overbought and forms a high point if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1]) { times_high = time[i+class="num">1]; max_high = (class="type">class="kw">double)MathMax(high[i], high[i+class="num">1]); class="kw">break; } } input class="type">int MagicNumber = class="num">1111; class="type">int rsi_handle; class="type">class="kw">double rsi_buffer[]; class="type">class="kw">double open[]; class="type">class="kw">double close[]; class="type">class="kw">double high[]; class="type">class="kw">double low[]; class="type">class="kw">datetime time[]; class="type">class="kw">double max_high = class="num">0; class="type">class="kw">datetime min_time1 = class="num">0; class="type">class="kw">double min_low = class="num">0; class="type">class="kw">datetime min_time2 = class="num">0; class="type">class="kw">datetime time_low = class="num">0; class="type">class="kw">datetime times_high = class="num">0; class="type">int OnInit() { ArraySetAsSeries(rsi_buffer, true); rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE); ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); trade.SetExpertMagicNumber(MagicNumber); class="kw">return (INIT_SUCCEEDED); } class="type">void OnTick() { CopyBuffer(rsi_handle, class="num">0, class="num">1, class="num">100, rsi_buffer); CopyOpen(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, open); CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, close);
用静态变量锁住最近的高低点拐点
这段逻辑先把当前品种、当前周期最近 100 根 K 线的高、低、时间分别拉进数组,供后面扫描使用。注意 CopyHigh/CopyLow/CopyTime 的起始位置是 1 而非 0,意味着跳过了正在形成的那根实时 K 线,避免未闭合数据干扰判定。 四个 static 变量(max_high_static、min_time1_static、min_low_static、min_time2_static)负责跨 tick 保留上一次找到的拐点。外汇与贵金属波动剧烈,实时刷新容易把瞬时噪声当拐点,用 static 缓存能减少重复计算与抖动。 两段 12 根内的 for 循环分别找「阴线接阳线」的最高价与最早时间、「阳线接阴线」的最低价与最早时间,作为潜在顶底。另两段循环在 RSI 缓冲里抓跌破 30 后回升、突破 70 后回落的时刻,记下 time_low / times_high。 Bars() 统计从静态时间点到现在的 Bar 数。若 total_bar_high 为 0 或大于 12,且 min_time1 与 times_high 重合,才把本次算出的 max_high、min_time1 写回静态变量;否则当 min_time1 不等 times_high 且 Bar 数超 13,就把静态高点与时间清零。开 MT5 把这段贴进 EA,把 12、13、30、70 几个数改小改大,能直接看到拐点捕获频率的变化。
CopyHigh(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, high); CopyLow(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, low); CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time); class="kw">static class="type">class="kw">double max_high_static = max_high; class="kw">static class="type">class="kw">datetime min_time1_static = min_time1; class="kw">static class="type">class="kw">double min_low_static = min_low; class="kw">static class="type">class="kw">datetime min_time2_static = min_time2; class="type">int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent()); for(class="type">int i = class="num">0; i < class="num">12; i++) { if(close[i] < open[i] && close[i+class="num">1] > open[i+class="num">1]) { max_high = (class="type">class="kw">double)MathMax(high[i],high[i+class="num">1]); min_time1 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]); class="kw">break; } } class="type">int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(class="type">int i = class="num">0; i < class="num">12; i++) { if(close[i] > open[i] && close[i+class="num">1] < open[i+class="num">1]) { min_low = (class="type">class="kw">double)MathMin(low[i],low[i+class="num">1]); min_time2 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]); class="kw">break; } } for(class="type">int i = class="num">0; i < class="num">12; i++) { if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1]) { time_low = time[i+class="num">1]; class="kw">break; } } for(class="type">int i = class="num">0; i < class="num">12; i++) { if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1]) { times_high = time[i+class="num">1]; class="kw">break; } } if((total_bar_high == class="num">0 || total_bar_high > class="num">12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > class="num">13) { max_high_static = class="num">0; min_time1_static = class="num">0; }
◍ 用静态变量锁住高低点参照
这段代码把前一段算出的极值用 static 变量固化下来,避免每根 tick 重算导致参照线跳变。注意 total_bar_low 的判定阈值:等于 0 或大于 12 且时间吻合时才更新最低价静态值,否则当偏离超过 13 根就清空,相当于承认原波段失效。 初始化里 rsi_handle 用 iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE) 建句柄,14 周期是我们后面筛背离的基准。所有价格数组都 ArraySetAsSeries(true),让索引 0 永远是当前未完成 K 线,回看历史用正数偏移即可。 OnTick 每次拉 100 根数据从偏移 1 开始,刻意跳过第 0 根实时 Bar,这样 RSI 与 OHLC 的序列对齐不会错位。max_high_static 和 min_time1_static 用 static 承接,保证跨 tick 的极值记忆不丢,这是画线判断突破的前提。外汇与贵金属波动剧烈,static 缓存若不定期随市场结构重置,可能捕捉到早已失效的旧极值,实盘前应在 MT5 策略测试器用不同品种验证重置逻辑。
if((total_bar_low == class="num">0 || total_bar_low > class="num">12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > class="num">13) { min_low_static = class="num">0; min_time2_static = class="num">0; } } class="macro">#include <Trade/Trade.mqh> CTrade trade; class=class="str">"cmt">// Magic number input class="type">int MagicNumber = class="num">1111; class="type">int rsi_handle; class="type">class="kw">double rsi_buffer[]; class="type">class="kw">double open[]; class="type">class="kw">double close[]; class="type">class="kw">double high[]; class="type">class="kw">double low[]; class="type">class="kw">datetime time[]; class="type">class="kw">double max_high = class="num">0; class="type">class="kw">datetime min_time1 = class="num">0; class="type">class="kw">double min_low = class="num">0; class="type">class="kw">datetime min_time2 = class="num">0; class="type">class="kw">datetime time_low = class="num">0; class="type">class="kw">datetime times_high = class="num">0; class="type">class="kw">string high_obj_name = "High_Line"; class="type">class="kw">string low_obj_name = "Low_Line"; class="type">long chart_id; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">// Configure RSI buffer as a series for easier indexing ArraySetAsSeries(rsi_buffer, true); class=class="str">"cmt">// Initialize RSI handle for the current symbol, timeframe, and parameters rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE); class=class="str">"cmt">// Configure candlestick arrays as series ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); class=class="str">"cmt">// Set the magic number trade.SetExpertMagicNumber(MagicNumber); class="kw">return (INIT_SUCCEEDED); class=class="str">"cmt">// Indicate successful initialization } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">// Copy RSI values from the indicator into the buffer CopyBuffer(rsi_handle, class="num">0, class="num">1, class="num">100, rsi_buffer); class=class="str">"cmt">// Copy candlestick data CopyOpen(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, open); CopyClose(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, close); CopyHigh(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, high); CopyLow(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, low); CopyTime(_Symbol, PERIOD_CURRENT, class="num">1, class="num">100, time); class="kw">static class="type">class="kw">double max_high_static = max_high; class="kw">static class="type">class="kw">datetime min_time1_static = min_time1;
「用静态变量锁住高低点扫描状态」
这段逻辑干的事,是在当前图表上把最近一段的潜在顶底和 RSI 极值时间锚定下来,靠 static 变量跨 tick 保留上一次结果。外汇和贵金属波动快,这种状态保持能避免每帧重算把信号抖掉,但高频误触风险也高,参数要自己压。 先抓图表 ID 并算两个 Bars 计数:从记录的 min_time1_static / min_time2_static 到当前时间,在 PERIOD_CURRENT 上一共有多少根 K。若返回 0 或大于 12,说明锚点已失效或太老,后续才允许更新。 接着四个循环各扫 12 根:前两个找「阴线接阳线」取两K最高价、和「阳线接阴线」取两K最低价;后两个看 RSI 缓冲,rsi<30 且回升、rsi>70 且走高,分别记下时间。注意循环上限写死 12,改大可能吞掉更早形态。 最后用 total_bar 计数和 RSI 时间做交叉验证:只有 Bars 异常或超 12 且时间对得上,才把扫描值写回 static;若时间不对且 Bars>13,直接把 static 清零。这样老信号不会一直挂屏,但清零后下一帧若条件不满足就空跑,建议开 MT5 用 EURUSD 15M 跑一遍看 static 归零频率。
class="kw">static class="type">class="kw">double min_low_static = min_low; class="kw">static class="type">class="kw">datetime min_time2_static = min_time2; class=class="str">"cmt">//CHART ID chart_id = ChartID(); class="type">int total_bar_high = Bars(_Symbol,PERIOD_CURRENT,min_time1_static,TimeCurrent()); for(class="type">int i = class="num">0; i < class="num">12; i++) { if(close[i] < open[i] && close[i+class="num">1] > open[i+class="num">1]) { max_high = (class="type">class="kw">double)MathMax(high[i],high[i+class="num">1]); min_time1 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]); class="kw">break; } } class="type">int total_bar_low = Bars(_Symbol,PERIOD_CURRENT,min_time2_static,TimeCurrent()); for(class="type">int i = class="num">0; i < class="num">12; i++) { if(close[i] > open[i] && close[i+class="num">1] < open[i+class="num">1]) { min_low = (class="type">class="kw">double)MathMin(low[i],low[i+class="num">1]); min_time2 = (class="type">class="kw">datetime)MathMin(time[i],time[i+class="num">1]); class="kw">break; } } for(class="type">int i = class="num">0; i < class="num">12; i++) { if(rsi_buffer[i+class="num">1] < class="num">30 && rsi_buffer[i] > rsi_buffer[i+class="num">1]) { time_low = time[i+class="num">1]; class="kw">break; } } for(class="type">int i = class="num">0; i < class="num">12; i++) { if(rsi_buffer[i+class="num">1] > class="num">70 && rsi_buffer[i] < rsi_buffer[i+class="num">1]) { times_high = time[i+class="num">1]; class="kw">break; } } if((total_bar_high == class="num">0 || total_bar_high > class="num">12) && (min_time1 == times_high)) { max_high_static = max_high; min_time1_static = min_time1; } else if(min_time1 != times_high && total_bar_high > class="num">13) { max_high_static = class="num">0; min_time1_static = class="num">0; } if((total_bar_low == class="num">0 || total_bar_low > class="num">12) && (min_time2 == time_low)) { min_low_static = min_low; min_time2_static = min_time2; } else if(min_time2 != time_low && total_bar_low > class="num">13) { min_low_static = class="num">0; min_time2_static = class="num">0; }