MQL5 自定义指标入门:用价格行为把趋势结构画成信号(基础篇)
(1/3)· 很多交易者脑子里有结构突破逻辑,却卡在不会把它变成图表上的可视工具
◍ MQL5 自定义指标的内存与句柄机制
在 MT5 里写自定义指标,第一道坎不是画图,而是搞清数据怎么进、怎么存。MQL5 指标不直接读价格数组,而是通过 CopyBuffer 从已创建的技术指标句柄拉数据,这和 MQL4 里直接访问数组的写法完全不同。
一个容易踩的坑:指标句柄必须在 OnInit 里用 iMA / iRSI 等函数创建,若返回 INVALID_HANDLE(值为 -1),后续 CopyBuffer 会静默失败,指标线空白却不报错。实盘前务必在策略测试器用 2020–2025 年 XAUUSD 的 M15 数据跑一遍,确认句柄有效。
外汇与贵金属杠杆高、滑点突变频繁,句柄失效可能导致信号延迟甚至反向,回测通过不等于 live 环境稳。开 MT5 按 F4 建个空指标模板,先 Print(Handle) 看返回值再写逻辑。
用项目把 MQL5 基础串成趋势指标
本篇不是从零讲语法,而是接着前面几篇已经铺开的基础继续往前走。如果你已经写过简单的脚本或指标,这里直接进项目:做一个能识别趋势、抓结构突破并吐出买卖信号的自定义指标。 指标会标出 L、H、HL、HH、LL、LH 这些结构点,据此画溢价区、折价区和 50% 回撤位;在多头结构里按风险回报比算潜在利润目标,并给出入场、止损与多档止盈。 学习方法只有一条:水平由你做完的项目定义。本系列始终用基于项目的方式推进,这一节就落地一个价格行为趋势跟踪指标,你开 MT5 新建自定义指标就能跟着复现。外汇与贵金属波动剧烈,这类信号仅作概率参考,实盘前务必在策略测试器跑历史数据验证。
「指标怎么判定趋势与挂单位」
这套指标先把市场结构拆成四类点:低点、高点、更高的低点、更高的高点。上升趋势里,它取更高低点到更高高点的区间,算出 50% 回撤位,价格突破更高高点时触发买入,止损就压在那根 50% 线上。 下跌方向反过来处理:先抓高点、低点、更低的高点、更低的低点,用更低高点到更低低点的中间位做止损,价格跌破更低低点才进场卖。 盈亏比写死在两档:TP1 是 1:1,TP2 是 1:2。外汇和贵金属波动大、滑点频繁,这种固定比值的止损止盈在重大数据行情里可能被瞬间扫掉,实盘前建议在 MT5 用历史数据跑一遍结构识别准确率。
◍ 用 MQL5 把波段点拼成趋势结构
任何交易逻辑只要能写成规则,就能在 MT5 里变成可视化指标。价格行为最核心的原材料是摆动高点与摆动低点:把当前柱的最高/最低价,同前一根、后一根比较,就能用 IsSwingLow() / IsSwingHigh() 抓出转折。更高的高点、更高的低点、更低的高点、更低的低点,全部建立在这个基础上。 看涨结构要求依序出现低点 L、高点 H、更高低点 HL、更高高点 HH,且必须满足 L < H && HL < H && HL > L && HH > H。代码中 LookbackBars 控制识别摆动点时前后回看几根;bars_check 控制总共扫描多少根(示例给到 1000),值越大覆盖越广但更吃算力。show_bullish 为 false 时即使结构成立也不画任何东西。 确认四点后,从 HL 到 HH 拉一个矩形框,用 lvl_50 = HL + ((HH - HL)/2) 算出 50% 线。框上方为溢价区、下方为折扣区。该指标不抄底折扣区,而是等价格收在 HH 上方、完全脱离溢价区才认买入——这过滤了多数回调假突破。 入场放在突破 HH 的阳线收盘,止损设在 lvl_50,TP1 为 1:1、TP2 为 1:2。n_bars 算框尾到 HL 的柱数,n_bars_2 算框尾到突破柱的柱数;要求 n_bars_2 < n_bars,避免信号离结构太远。所有 ObjectCreate 只在全部条件通过后执行,图表才不会堆满半成品。 看跌镜像处理:H、L、LH、LL 满足 H > L && LH < H && LH > L && LL < L 后,从 LH 到 LL 画框,等阴线收破 LL 才标卖出,SL 同样放 50% 线。外汇与贵金属杠杆高,结构失效时回撤可能快而深,任何信号都只是概率倾向。
下跌结构只是镜像翻转
下跌趋势的识别逻辑与上涨完全对称,只是把高点、低点的大小关系反过来:原来看涨要找更高的低点(HL)和更高的高点(HH),看跌就变成更低的低点(LL)和更低的高点(LH)。指标在阴线烛形上打卖出箭头和“卖出”字样,本质是把同一套摆动点判定函数倒序调用。 摆点判定的核心是两个函数:IsSwingLow 检查某根 K 线低点是否同时小于左右各 lookback 根 K 线的低点,是则为真;IsSwingHigh 则反过来比高点。下面这段是原文里的函数与参数声明,直接贴出来逐行看。 //+------------------------------------------------------------------+
| // | FUNCTION FOR SWING LOWS |
|---|
//+------------------------------------------------------------------+ bool IsSwingLow(const double &low[], int index, int lookback) { for(int i = 1; i <= lookback; i++) {
| if(low[index] > low[index - i] | low[index] > low[index + i]) |
|---|
return false; } return true; } //+------------------------------------------------------------------+
| // | FUNCTION FOR SWING HIGHS |
|---|
//+------------------------------------------------------------------+ bool IsSwingHigh(const double &high[], int index, int lookback) { for(int i = 1; i <= lookback; i++) {
| if(high[index] < high[index - i] | high[index] < high[index + i]) |
|---|
return false; } return true; } 上面 IsSwingLow 中,low[index] 是当前考察 K 线低点,循环比对左侧 index-i 与右侧 index+i 共 2*lookback 根 K 线,只要有一根更低就返回 false,全段更低才认作摆动低点。IsSwingHigh 同理,只是比较方向换成“有更高就否”。 输入参数里 LookbackBars=10 控制摆点左右回看根数,bars_check=1000 是总共扫描的历史 bar 数,show_bullish=true 控制是否绘制买入信号。外汇与贵金属杠杆高、滑点跳空频繁,摆点识别在极端行情中可能滞后,实盘前务必在 MT5 用历史数据跑一遍验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| FUNCTION FOR SWING 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 class="kw">false; } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| FUNCTION FOR SWING 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 class="kw">false; } class="kw">return true; } class=class="str">"cmt">// CHART ID class="type">long chart_id = ChartID(); class=class="str">"cmt">// Input parameters input class="type">int LookbackBars = class="num">10; class=class="str">"cmt">// Number of bars to look back/forward for swing points input class="type">int bars_check = class="num">1000; class=class="str">"cmt">// Number of bars to check for swing points input class="type">bool show_bullish = true; class=class="str">"cmt">//Show Buy Signals class=class="str">"cmt">// Variables for Bullish Market Structure class="type">class="kw">double L; class=class="str">"cmt">// Low: the starting low point in the up trend class="type">class="kw">datetime L_time; class=class="str">"cmt">// Time of the low class="type">class="kw">string L_letter; class=class="str">"cmt">// Label for the low point(e.g., "L") class="type">class="kw">double H; class=class="str">"cmt">// High: the first high after the low class="type">class="kw">datetime H_time; class=class="str">"cmt">// Time of the high class="type">class="kw">string H_letter; class=class="str">"cmt">// Label for the high point(e.g., "H") class="type">class="kw">double HL; class=class="str">"cmt">// Higher Low: the next low that is higher than the first low class="type">class="kw">datetime HL_time; class=class="str">"cmt">// Time of the higher low class="type">class="kw">string HL_letter; class=class="str">"cmt">// Label for the higher low point(e.g., "HL") class="type">class="kw">double HH; class=class="str">"cmt">// Higher High: the next high that is higher than the first high class="type">class="kw">datetime HH_time; class=class="str">"cmt">// Time of the higher high class="type">class="kw">string HH_letter; class=class="str">"cmt">// Label for the higher high point(e.g., "HH") class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() {
「摆动点识别的循环收尾与对象清理」
指标的退出函数只做一件事:把当前图表上由本程序绘制的全部图形对象清掉。 OnDeinit 里直接调用 ObjectsDeleteAll(chart_id),避免切换周期或移除 EA 时残留箭头和线段。 OnCalculate 的牛势分支里,先判断 show_bullish 开关,再用 rates_total >= bars_check 确认 K 线数量够用,否则不进循环。 内层从 i = rates_total - bars_check 扫到 rates_total - LookbackBars,遇到 IsSwingLow 就记下低价 L、时间 L_time 和标签名,再嵌套 j 循环向后找 IsSwingHigh 且时间晚于 L_time 的摆动高点,从而配对出一段可能的上升结构。外汇与贵金属波动剧烈,这类结构仅作概率参考,实盘须自担高风险。
class=class="str">"cmt">//--- indicator buffers mapping class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } 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">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, 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=class="str">"cmt">//--- if(show_bullish == true) class=class="str">"cmt">// Check if the bullish trend is to be displayed { if(rates_total >= bars_check) class=class="str">"cmt">// Ensure enough bars are available for analysis { class=class="str">"cmt">// Loop through the price data starting from a certain point based on bars_check and LookbackBars for(class="type">int i = rates_total - bars_check; i < rates_total - LookbackBars; i++) { class=class="str">"cmt">// Check if the current bar is a swing low if(IsSwingLow(low, i, LookbackBars)) { class=class="str">"cmt">// Store the values for the swing low L = low[i]; L_time = time[i]; L_letter = StringFormat("Low%d", i); class=class="str">"cmt">// Loop through further to find a swing high after the low for(class="type">int j = i; j < rates_total - LookbackBars; j++) { class=class="str">"cmt">// Check if the current bar is a swing high and occurs after the identified swing low if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time) {