MQL5 简介(第 17 部分):构建趋势反转 EA 交易·进阶篇
◍ 初始化与逐根 K 线抓取摆动低点
EA 初始化收尾时把 low_price、high_price、time_price 三个数组用 ArraySetAsSeries(..., true) 设为时间序列,索引 0 对应最新一根 K 线;OnDeinit 里直接 ObjectsDeleteAll(chart_id) 清空图表对象,避免残留趋势线干扰下一轮加载。 OnTick 每次触发先用 CopyOpen / CopyClose / CopyLow / CopyHigh / CopyTime 把当前品种、指定周期、从偏移 1 开始的 bars_check 根数据搬进数组,跳过第 0 根未收盘 K 线,回测与实盘都不会把未完成棒算进摆动判定。 允许画上升趋势线时,第一段 for 从 LookbackBars 扫到 bars_check - LookbackBars,调用 IsSwingLow 找最近的一个摆动低点,记下 first_low 与 first_low_time 后 break;第二段再扫同样区间,找时间更早且价格低于 first_low 的第二个摆动低点。两段都限定在 [LookbackBars, bars_check-LookbackBars] 内,意味着若 bars_check=100、LookbackBars=5,实际只在第 5~95 根之间找点,两端各留 5 根缓冲。 外汇与贵金属杠杆高、滑点跳空频繁,这套扫低逻辑只在历史棒上跑,实盘新低若出现在第 0~4 根会被忽略,开 MT5 把 LookbackBars 调小到 2~3 可加快拐点捕获但假突破概率会上升。
ArraySetAsSeries(low_price, true); ArraySetAsSeries(high_price, true); ArraySetAsSeries(time_price, true); class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">// Signal that the EA initialized successfully } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { ObjectsDeleteAll(chart_id); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">// Copy the latest candlestick data into the arrays CopyOpen(_Symbol, time_frame, class="num">1, bars_check, open_price); class=class="str">"cmt">// Open prices CopyClose(_Symbol, time_frame, class="num">1, bars_check, close_price); class=class="str">"cmt">// Close prices CopyLow(_Symbol, time_frame, class="num">1, bars_check, low_price); class=class="str">"cmt">// Low prices CopyHigh(_Symbol, time_frame, class="num">1, bars_check, high_price); class=class="str">"cmt">// High prices CopyTime(_Symbol, time_frame, class="num">1, bars_check, time_price); class=class="str">"cmt">// Candle times class=class="str">"cmt">// If the user allows drawing of ascending trend line if(allow_uptrend) { class=class="str">"cmt">// First loop: Find the most recent swing low(first low) for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++) { class=class="str">"cmt">// Check if current point is a swing low if(IsSwingLow(low_price, i, LookbackBars)) { class=class="str">"cmt">// Store price and time of the first(latest) swing low first_low = low_price[i]; first_low_time = time_price[i]; break; class=class="str">"cmt">// Exit loop after finding the first swing low } } class=class="str">"cmt">// Second loop: Find an earlier swing low that is lower than the first low for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++) { class=class="str">"cmt">// Check for earlier swing low that is lower and occurs before the first low if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time) { class=class="str">"cmt">// Store price and time of the second(older) swing low second_low = low_price[i]; second_low_time = time_price[i]; break; class=class="str">"cmt">// Exit loop after finding the second swing low }
上升趋势线的摆动低点判定与绘制
在 MT5 里自动画上升趋势线,核心是先确认两个摆动低点(swing low)且后一个低于前一个。原文用 IsSwingLow 函数以 lookback 为半径,检查目标 K 线左右各 lookback 根 lows 是否都更高,只有全满足才返回 true。 绘制时先 ObjectCreate 连第二低点到第一低点,再用 OBJPROP_RAY_RIGHT 向右延伸。注意代码里先设 OBJPROP_TIMEFRAMES 为 OBJ_NO_PERIODS 暂时隐藏,等结构 valid(first_low > second_low && second_low > 0)才切到 OBJ_ALL_PERIODS 显示,并上色 clrBlue、线宽 3。 输入参数给了具体默认:LookbackBars=5 控摆动识别灵敏度,bars_check=500 限定回溯根数,allow_uptrend 开关绘制。外汇与贵金属波动大,这类自动画线仅作结构参考,假突破概率不低,需结合实时价行为验证。 下面这段是摆动低点判定与部分声明的原文,可直接拷进 MQ5 测:
class="type">bool IsSwingLow(const class="type">class="kw">double &low[], class="type">int index, class="type">int lookback) { for(class="type">int i = class="num">1; i <= lookback; i++) { if(low[index] > low[index - i] || low[index] > low[index + i]) class="kw">return false; } class="kw">return true; } input ENUM_TIMEFRAMES time_frame = PERIOD_CURRENT; input class="type">bool allow_uptrend = true; input class="type">int LookbackBars = class="num">5; input class="type">bool allow_downtrend = true; class="type">int bars_check = class="num">500; class="type">class="kw">double close_price[]; class="type">class="kw">double open_price[]; class="type">class="kw">double low_price[]; class="type">class="kw">double high_price[]; class="type">class="kw">datetime time_price[]; class="type">class="kw">double first_low; class="type">class="kw">datetime first_low_time; class="type">class="kw">double second_low;
「摆动点变量与数据初始化的落地写法」
要在 MT5 里自动画趋势线,第一步是把摆动高低点的容器和图表句柄先声明好。下面这段把第二低点时间、上升趋势线标签、图表 ID 分开存,逻辑上和后面要找的双低点对齐;高点的那组变量(first_high / second_high 等)则对称地留给下降趋势线用,标签写死成 "Down Trend" 方便后续 ObjectCreate 直接引用。 OnInit 里唯一要紧的事,是把 close/open/low/high/time 五个数组全部 ArraySetAsSeries(true)。不这么设,索引 0 会是历史最老 bar,你后面用 i=1 去 Copy 最新 N 根就全反了。 OnDeinit 只用 ObjectsDeleteAll(chart_id) 一句清掉本图表所有对象,避免 EA 重加载后旧趋势线堆在图上。OnTick 开头用 CopyOpen/CopyClose/CopyLow/CopyHigh/CopyTime 从偏移 1(跳过未收盘的当前 bar)拉取 bars_check 根数据——比如 bars_check 设 200,就是只处理已收盘的近 200 根,外汇与贵金属波动大,回看窗口过短容易把噪声当摆动点。 allow_uptrend 开关打开后,才进第一个 for 循环用 LookbackBars 做边界护垫找最近摆动低:i 从 LookbackBars 跑到 bars_check-LookbackBars,两端各留缓冲,避免数组越界也躲开图表最边缘的半根 K。
class="type">class="kw">datetime second_low_time; class=class="str">"cmt">// Time when the second swing low occurred class="type">class="kw">string up_trend = "Up Trend"; class=class="str">"cmt">// Label used to name the ascending trend line object on the chart class="type">long chart_id = ChartID(); class=class="str">"cmt">// Stores the current chart ID for referencing during object creation or manipulation class="type">class="kw">double first_high; class=class="str">"cmt">// Price value of the first identified swing high(latest high) class="type">class="kw">datetime first_high_time; class=class="str">"cmt">// Time when the first swing high occurred class="type">class="kw">double second_high; class=class="str">"cmt">// Price value of the second identified swing high(older high) class="type">class="kw">datetime second_high_time; class=class="str">"cmt">// Time when the second swing high occurred class="type">class="kw">string down_trend = "Down Trend"; class=class="str">"cmt">// Label used to name the descending trend line object on the chart class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">// Set arrays as series so the newest bar is index class="num">0 ( start from the latest bar) ArraySetAsSeries(close_price, true); ArraySetAsSeries(open_price, true); ArraySetAsSeries(low_price, true); ArraySetAsSeries(high_price, true); ArraySetAsSeries(time_price, true); class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">// Signal that the EA initialized successfully } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { ObjectsDeleteAll(chart_id); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">// Copy the latest candlestick data into the arrays CopyOpen(_Symbol, time_frame, class="num">1, bars_check, open_price); class=class="str">"cmt">// Open prices CopyClose(_Symbol, time_frame, class="num">1, bars_check, close_price); class=class="str">"cmt">// Close prices CopyLow(_Symbol, time_frame, class="num">1, bars_check, low_price); class=class="str">"cmt">// Low prices CopyHigh(_Symbol, time_frame, class="num">1, bars_check, high_price); class=class="str">"cmt">// High prices CopyTime(_Symbol, time_frame, class="num">1, bars_check, time_price); class=class="str">"cmt">// Candle times class=class="str">"cmt">// If the user allows drawing of ascending trend line if(allow_uptrend) { class=class="str">"cmt">// First loop: Find the most recent swing low(first low) for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++) {
◍ 用两个摆动低点拉出上升趋势线
识别上升结构的核心,是先抓到最近的摆动低点,再往前找一个更低的摆动低点。下面这段逻辑用两次循环完成:第一次从近往远扫,命中 IsSwingLow 就记下 first_low 和 first_low_time 并 break;第二次从 LookbackBars 位置继续往后扫,找价格更低且时间更早的 second_low。
class=class="str">"cmt">// Check if current point is a swing low if(IsSwingLow(low_price, i, LookbackBars)) { class=class="str">"cmt">// Store price and time of the first(latest) swing low first_low = low_price[i]; first_low_time = time_price[i]; break; class=class="str">"cmt">// Exit loop after finding the first swing low } } class=class="str">"cmt">// Second loop: Find an earlier swing low that is lower than the first low for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++) { class=class="str">"cmt">// Check for earlier swing low that is lower and occurs before the first low if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time) { class=class="str">"cmt">// Store price and time of the second(older) swing low second_low = low_price[i]; second_low_time = time_price[i]; break; class=class="str">"cmt">// Exit loop after finding the second swing low } } class=class="str">"cmt">// Create an ascending trend line from the second low to the first low ObjectCreate(chart_id, up_trend, OBJ_TREND, class="num">0, second_low_time, second_low, first_low_time, first_low); ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS); class=class="str">"cmt">// Temporarily hide line on all timeframes class=class="str">"cmt">// If the swing structure is valid(i.e., second low is lower than first) if(first_low > second_low && second_low > class="num">0) { class=class="str">"cmt">// Extend the trend line to the right ObjectSetInteger(chart_id, up_trend, OBJPROP_RAY_RIGHT, true); class=class="str">"cmt">// Show the trend line on all timeframes ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS); class=class="str">"cmt">// Set visual properties: class="type">color and thickness ObjectSetInteger(chart_id, up_trend, OBJPROP_COLOR, clrBlue); ObjectSetInteger(chart_id, up_trend, OBJPROP_WIDTH, class="num">3); } } class=class="str">"cmt">// class=class="str">"cmt">// Only proceed if drawing descending trend lines is enabled if(allow_downtrend) { class=class="str">"cmt">// First loop: Find the most recent swing high(first high) for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++) { class=class="str">"cmt">// Check if the current bar is a swing high if(IsSwingHigh(high_price, i, LookbackBars)) { class=class="str">"cmt">// Store the price and time of this latest swing high first_high = high_price[i]; first_high_time = time_price[i];
class=class="str">"cmt">// Check if current point is a swing low if(IsSwingLow(low_price, i, LookbackBars)) { class=class="str">"cmt">// Store price and time of the first(latest) swing low first_low = low_price[i]; first_low_time = time_price[i]; break; class=class="str">"cmt">// Exit loop after finding the first swing low } } class=class="str">"cmt">// Second loop: Find an earlier swing low that is lower than the first low for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++) { class=class="str">"cmt">// Check for earlier swing low that is lower and occurs before the first low if(IsSwingLow(low_price, i, LookbackBars) && low_price[i] < first_low && time_price[i] < first_low_time) { class=class="str">"cmt">// Store price and time of the second(older) swing low second_low = low_price[i]; second_low_time = time_price[i]; break; class=class="str">"cmt">// Exit loop after finding the second swing low } } class=class="str">"cmt">// Create an ascending trend line from the second low to the first low ObjectCreate(chart_id, up_trend, OBJ_TREND, class="num">0, second_low_time, second_low, first_low_time, first_low); ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS); class=class="str">"cmt">// Temporarily hide line on all timeframes class=class="str">"cmt">// If the swing structure is valid(i.e., second low is lower than first) if(first_low > second_low && second_low > class="num">0) { class=class="str">"cmt">// Extend the trend line to the right ObjectSetInteger(chart_id, up_trend, OBJPROP_RAY_RIGHT, true); class=class="str">"cmt">// Show the trend line on all timeframes ObjectSetInteger(chart_id, up_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS); class=class="str">"cmt">// Set visual properties: class="type">color and thickness ObjectSetInteger(chart_id, up_trend, OBJPROP_COLOR, clrBlue); ObjectSetInteger(chart_id, up_trend, OBJPROP_WIDTH, class="num">3); } } class=class="str">"cmt">// class=class="str">"cmt">// Only proceed if drawing descending trend lines is enabled if(allow_downtrend) { class=class="str">"cmt">// First loop: Find the most recent swing high(first high) for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++) { class=class="str">"cmt">// Check if the current bar is a swing high if(IsSwingHigh(high_price, i, LookbackBars)) { class=class="str">"cmt">// Store the price and time of this latest swing high first_high = high_price[i]; first_high_time = time_price[i];
下降趋势线的双顶确认与绘制
第二段循环从 LookbackBars 起向后扫,目标是找到比第一段循环里首个摆动高点更高、且时间更早的摆动高点。条件写成 IsSwingHigh(high_price, i, LookbackBars) && high_price[i] > first_high && time_price[i] < first_high_time,命中即存为 second_high 与 second_high_time 并 break,避免重复占用 CPU。 拿到两个高点后,用 ObjectCreate 在图表上拉一条 OBJ_TREND:起点是更早期的 second_high_time / second_high,终点是 first_high_time / first_high。注意先设 OBJPROP_TIMEFRAMES 为 OBJ_NO_PERIODS 隐藏,防止在全部周期上画出半截线。 结构校验靠 first_high < second_high && second_high > 0:旧高必须高于新高,才能算下降连线。通过后才把 OBJPROP_RAY_RIGHT 开为真向右延伸,时间帧切到 OBJ_ALL_PERIODS,颜色 clrDarkGreen、宽度 3 像素,肉眼更好认。 低点判定交给 IsSwingLow:以 index 为中心向左右各扩 lookback 根 K,只要 low[index] 大于任一侧邻杆低点就返回 false,否则是摆动低点。把 lookback 设成 3 与设成 5,在 M15 黄金上筛出的摆动点数量可能差出 40% 以上,建议开 MT5 自己调参验证。外汇与贵金属波动剧烈,趋势线仅作概率参考,实盘仍属高风险。
break; class=class="str">"cmt">// Exit loop once the first swing high is found } } class=class="str">"cmt">// Second loop: Find an earlier swing high that is higher than the first high and occurred before it for(class="type">int i = LookbackBars; i < bars_check - LookbackBars; i++) { class=class="str">"cmt">// Check for earlier swing high that is higher and happened before the first one if(IsSwingHigh(high_price, i, LookbackBars) && high_price[i] > first_high && time_price[i] < first_high_time) { class=class="str">"cmt">// Store the price and time of this older swing high second_high = high_price[i]; second_high_time = time_price[i]; break; class=class="str">"cmt">// Exit loop once the second swing high is found } } class=class="str">"cmt">// Create a trend line object from the second swing high to the first swing high ObjectCreate(chart_id, down_trend, OBJ_TREND, class="num">0, second_high_time, second_high, first_high_time, first_high); class=class="str">"cmt">// Initially hide the trend line across all timeframes to avoid partial drawing ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS); class=class="str">"cmt">// Validate the swing structure: class=class="str">"cmt">// The older swing high should be higher than the later swing high to confirm a descending trend line if(first_high < second_high && second_high > class="num">0) { class=class="str">"cmt">// Extend the trend line indefinitely to the right for better visual guidance ObjectSetInteger(chart_id, down_trend, OBJPROP_RAY_RIGHT, true); class=class="str">"cmt">// Make the trend line visible on all chart timeframes ObjectSetInteger(chart_id, down_trend, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS); class=class="str">"cmt">// Set the trend line class="type">color to dark green for clear distinction ObjectSetInteger(chart_id, down_trend, OBJPROP_COLOR, clrDarkGreen); class=class="str">"cmt">// Set the thickness of the trend line to class="num">3 pixels for better visibility ObjectSetInteger(chart_id, down_trend, OBJPROP_WIDTH, class="num">3); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| FUNCTION FOR LOWS | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsSwingLow(const class="type">class="kw">double &low[], class="type">int index, class="type">int lookback) { for(class="type">int i = class="num">1; i <= lookback; i++) { if(low[index] > low[index - i] || low[index] > low[index + i]) class="kw">return false; } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+
「摆动高点判定的代码实现」
识别摆动高点(Swing High)是价格行为分析里过滤噪音的第一步。下面这段 MQL5 函数用左右对称回看的方式,判断某根 K 线高点是否真正胜出邻近区间。 函数 IsSwingHigh 接收三个参数:high[] 为高点数组引用,index 为待判定 K 线位置,lookback 为向左右各回看的根数。例如 lookback=2 时,会对比 index 前后各 2 根共 4 根 K 线。 核心逻辑是一个从 1 到 lookback 的循环:只要 index 位置的高点小于左侧任一根(index-i)或右侧任一根(index+i),立即返回 false;全部比对通过才返回 true。外汇与贵金属波动剧烈,lookback 设太小容易把毛刺当拐点,设太大则信号严重滞后,建议先在 EURUSD 的 M15 上用 3~5 做肉眼校验。
class=class="str">"cmt">//| FUNCTION FOR HIGHS | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsSwingHigh(const class="type">class="kw">double &high[], class="type">int index, class="type">int lookback) { for(class="type">int i = class="num">1; i <= lookback; i++) { if(high[index] < high[index - i] || high[index] < high[index + i]) class="kw">return false; } class="kw">return true; }