MQL5 简介(第 16 部分):利用技术图表形态构建 EA 交易(基础篇)
「用图表形态给 EA 装一双眼睛」
在 MT5 里做自动化交易,EA 不能只盯指标数值,还得认得出价格在图上画出的形状。把技术图表形态写进 MQL5 逻辑,等于让程序拥有了「读图」能力,而不只是算均线。 以头肩顶为例,若左肩、头部、右肩的相对高度与回落幅度满足经验阈值,EA 可在右肩形成后挂突破单。这种形态识别类 EA 的胜率高度依赖形态判定容差,容差设太窄会漏单,设太宽会吃假突破。 一篇 2026 年 1 月发布的形态 EA 示例帖,单个帖子在发布当日即获 523 次查看、6 条读者反馈,说明社群对「图形化 EA」的需求很实。外汇与贵金属杠杆高,形态失效时回撤可能远超历史样本,任何形态 EA 都先用策略测试器跑历史再上模拟盘。
用 EA 把头肩形态跑起来
这一节我们直接做一个能用的东西:把头肩形态写成 MT5 的 EA,同时它也能当指标在图上把形态画出来。形态本身是用来抓趋势反转的,EA 负责自动认形态、自动挂单,你省去盯盘找肩膀的功夫。 项目覆盖几条硬技能:程序化识别头肩、用代码标出波段拐点、调用 MQL5 的图表对象画图、以及把入场/止损/止盈写成规则。还有一个容易踩的坑——信号重复触发,会在代码里用状态锁掉。 外汇和贵金属波动大、杠杆高,头肩反转失败是常态。EA 只是把形态可视化加自动执行,不替你过滤宏观风险,跑之前先在策略测试器用历史数据验证信号质量。
◍ 把价格图形当成多空博弈的现场痕迹
价格图表上那些能用来推测后续走向的视觉轮廓,就是图表形态。它们是买卖双方反复交手留下的痕迹,往往提示现有趋势会延续还是掉头。比如头肩顶常出现在升势末端,暗示向上动能可能衰竭并转跌;而上升旗形多在趋势中途出现,意味着整理后继续上冲的概率偏大。能认出三角形、矩形、双顶这类结构,你从过去价格行为里挖潜在机会的命中率会明显提升。 图表形态分三类,先说反转形态:它出现在趋势尾声,提示方向可能翻盘。下跌途中冒出双底、头肩底,往往代表卖压耗尽、买盘准备接管;头肩顶则相反。这类形态本质是原有动量减弱的信号,像追踪者看到沙迹突然拐弯,得警觉对方换了方向。 持续形态说明市场只是歇脚。矩形、旗形都算典型——上升里的旗形横盘几天,多半是上车中继而非终点。类比沿着海岸走看到脚印停一下系鞋带,方向没变。动能没逆转,只是短暂停滞,MT5 上把这类形态标出来,能避开过早反手。 中性形态是僵局,对称三角形最常见。高低点不断收敛,双方都没占优,价格困在窄幅里。此时硬猜突破方向是赌,正确做法是挂突破单等确认:上方破位跟多、下方破位跟空,外汇与贵金属波动剧烈,这类形态假突破率高,必须带止损。
「EA 怎么认头肩并下单」
这套 EA 的核心动作是自动抓头肩结构并依突破方向开仓,同时往图上画左肩、头部、右肩的图形对象,方便你回测时一眼核对形态识别对不对。 反手做多走的是反向头肩:依次找 X 高、A 低、B 高(低于 X 高于 A)、C 低(低于 A,头部)、D 高(与 B 同高,颈线)、E 低(高于 C、与 A 同高)。收盘价站上 D 点才买,SL 放在 E 最低,TP 先看 X。 有个硬约束——若入场到 X 的距离不足止损的 1 倍,就丢掉 X 目标,直接按止损距离铺 1:3 盈亏比。这样能拦掉风险回报太瘪的setup。 正向头肩卖空镜像处理:X 低、A 高、B 低、C 高(头部)、D 低(颈线)、E 高。收盘破 D 才卖,SL 取 E 最高,TP 先看 X;同样距离不够 1 倍止损时切 1:3 固定回报。 代码层只是示范 MQL5 如何处理图表形态,不是实盘方案。外汇和贵金属杠杆高、滑点跳空频繁,真要跑必须先 MT5 历史回测、自验参数。
在 MT5 图表上把头肩波动点画出来
头肩形态自动化识别的第一步,不是直接下单,而是先让 EA 在图表上把 X、A、B、C、D、E 六个波动点标出来。这样做能在回测早期暴露逻辑错误,也能确认代码识别的转折点和真实价格走势对得上。一个实用的头肩检测器应该兼具两种角色:既是交易机器人,也是形态指标——把历史中的类似结构高亮,方便你肉眼复核。 EA 取数不能像自定义指标那样白拿 OnCalculate 的参数,必须用 CopyOpen()、CopyHigh() 等函数手动把最近 1000 根烛形的开高低收和时间戳搬进数组。时间周期用 PERIOD_CURRENT 默认跟随图表,不改动代码就能切周期验证。OnTick() 里每次报价触发,先用 Bars() 确认柱数,再灌数据,这是后续所有识别的地基。 波动点靠 IsSwingLow() / IsSwingHigh() 判定:以 lookback 为窗,当前柱最低价低于左右各 z 根柱最低价即为摆动低点,高点反之。外层循环把 z 从 7 扫到 10,相当于用不同灵敏度多跑几遍,内嵌循环按时间严格递增依次抓 X→A→B→C→D→E,最内层 E 点用 z/2 更窄窗口收尾后立即 break,省算力。 光抓出六个点还不够,原文代码目前只按价格顺序记录,并未校验是否真成头肩。有效头肩顶要求 A>X、B 居中(高于 X 低于 A)、C 为头高于 A、E 与 A 近似、D 靠近 B。验证通过后,用 ObjectCreate() 画 OBJ_TREND 连线 XA/AB/BC/CD/DE/EX,用 OBJ_TEXT 标字母,再用矩形框出 A_zone(A 高到 E 低)和 B_zone(B 低到 D 高)作决策参考区。外汇与贵金属波动剧烈,这类图形仅是概率信号,实盘前务必在策略测试器用多周期复盘。 下面这段是原文用于声明 XABCDE 结构变量与绘图标识的核心片段,逐行拆完你能直接抄进 EA 骨架:
class=class="str">"cmt">// 波动点价格与时间的双精度/时间变量 class="type">class="kw">double X, A, B, C, D, E; class=class="str">"cmt">// 各波动点精确价格 class="type">class="kw">datetime X_time, A_time, B_time, C_time, D_time, E_time; class=class="str">"cmt">// 各点发生时间 class="type">class="kw">string X_letter, A_letter, B_letter, C_letter, D_letter, E_letter; class=class="str">"cmt">// 图表文本标签名 class=class="str">"cmt">// 各段柱数、极值索引与价格 class="type">class="kw">double x_a_bars, a_b_bars, b_c_bars, d_e_bars; class=class="str">"cmt">// 段内柱数 class="type">int x_lowest_index, a_highest_index, b_lowest_index, d_lowest_index; class=class="str">"cmt">// 极值索引 class="type">class="kw">double x_a_ll, a_b_hh, b_c_ll, d_e_ll; class=class="str">"cmt">// 段内最低/最高价 class="type">class="kw">datetime x_a_ll_t, a_b_hh_t, b_c_ll_t, d_e_ll_t; class=class="str">"cmt">// 对应时间 class=class="str">"cmt">// 区域与趋势线对象ID class="type">class="kw">string A_zone, B_zone; class="type">class="kw">double A_low, B_high; class=class="str">"cmt">// 矩形区域上下界 class="type">class="kw">double D_3bar_high, E_3bar_low; class=class="str">"cmt">// 点附近3柱参考高低 class="type">class="kw">double c_d_hh; class=class="str">"cmt">// C-D段最高 class="type">class="kw">string xa, ab, bc, cd, de, ex; class=class="str">"cmt">// 趋势线唯一名称 class=class="str">"cmt">// 图表绘制示例(满足条件后) ObjectCreate(class="num">0, X_letter, OBJ_TEXT, class="num">0, X_time, X); class=class="str">"cmt">// 在X点写文本 ObjectCreate(class="num">0, xa, OBJ_TREND, class="num">0, X_time, X, A_time, A); class=class="str">"cmt">// 连XA趋势线 ObjectCreate(class="num">0, A_zone, OBJ_RECTANGLE, class="num">0, A_time, A, E_time, E); class=class="str">"cmt">// 画A区矩形
◍ 用三角形框出头肩结构并标定进出场
标完 X、A、B、C、D、E 这些波动点后,别急着画整段价格连线。用 XAB、BCD、DEX 三组三角形只框住头部与两肩的转折峰谷,图表清爽且结构一眼可辨:XAB 连 X-A-B 覆盖左肩到头部起点,BCD 连 B-C-D 抓主头部,DEX 连 D-E-X 收住右肩折返。 为了动态命名不冲突,代码里用 X_A_B、B_C_D、D_E_X 三个字符串变量承载三角形对象名,并用 xa_line_t、ex_line_t 两个 datetime 变量抓锚点时间。先以 ObjectCreate() 拉出“A ZONE”“B ZONE”两个矩形:A 区从 A 低点到 A-B 间高点 a_b_hh 并延伸到 E 点时间;B 区从 B-C 间低点 b_c_ll 到 B 高点结束于 D-E 段。 接着 ObjectGetTimeByValue() 按价格反查时间,把三角形锚到正确价位;OBJPROP_FILL 填色、OBJPROP_BACK 置底,肩部和头部用 shoulder_clr、head_clr 区分,每个形态实例名带索引 i 以免重名。 交易参数这样挂:收盘价跌破 D 点(颈线区)才视为入场信号,价格可能已拒绝颈线并向形态预判方向走;止损放在 E 点上方,因 E 是成型前最近高点;止盈首看 X 点,若入场到 X 的距离风险回报比不足 1:1,则 TP 延长到至少 1:2。外汇与贵金属波动剧烈,该结构失效概率不低,仅作几何参考。 代码用 n_bars、n_bars_2 比柱数确认时效,if (close[o] < d_e_ll && time[o] >= time[n+(z/2)]) 捕捉破位,且回扫确认此前无收破 D 的烛形以剔除假突破。若 TP 小于 SL 距离,脚本把 TP 区重算为 SL 三倍并改画箭头与文字标签。下面这段输入与数组声明是整套绘制逻辑的起点,复制进 MT5 脚本可验证结构。
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; class=class="str">"cmt">// MA Time Frame input class="type">int bars_check = class="num">1000; class=class="str">"cmt">// Number of bars to check for swing points class=class="str">"cmt">// Variable to store how many bars are available on the chart for the selected timeframe class="type">int rates_total; class="type">class="kw">double open[]; class=class="str">"cmt">// Array for opening prices class="type">class="kw">double close[]; class=class="str">"cmt">// Array for closing prices class="type">class="kw">double low[]; class=class="str">"cmt">// Array for lowest prices class="type">class="kw">double high[]; class=class="str">"cmt">// Array for highest prices class="type">class="kw">datetime time[]; class=class="str">"cmt">// Array for time(timestamps) of each bar class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 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(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- class=class="str">"cmt">// Get the total number of bars available on the selected symbol and timeframe rates_total = Bars(_Symbol, timeframe); class=class="str">"cmt">// Copy the open prices of the last &class="macro">#x27;rates_total&class="macro">#x27; bars into the &class="macro">#x27;open&class="macro">#x27; array