MQL5 简介(第 16 部分):利用技术图表形态构建 EA 交易·进阶篇
(2/3)· 承接基础篇,手把手把头肩顶识别、绘图与下单逻辑塞进一个 EA 里
「摆点判定的数据搬运与逻辑内核」
在 MT5 里做价格行为分析,第一步是把 K 线四价和时间戳塞进数组。OnTick 里先用 Bars(_Symbol, timeframe) 拿到当前品种与周期的总柱数 rates_total,再用 CopyOpen / CopyClose / CopyLow / CopyHigh / CopyTime 把最近 rates_total 根 BAR 的原始数据整批搬进 open、close、low、high、time 数组,后续所有摆动点计算都基于这批内存数据,不再反复访问图表。 摆动低点的判定由 IsSwingLow 完成:给定 low_price 数组、待检索引 index 与回看窗口 lookback,只要 index 处低价高于 index-i 或 index+i 中任一低价(i 从 1 到 lookback),立即返回 false;全部比完都更低才返回 true。IsSwingHigh 逻辑镜像,要求 index 处高价在左右各 lookback 根内都是最高。 把 lookback 设成 3,意味着一个低点必须比左右各 3 根(共 6 根邻接 K 线)都低才算 swing low。外汇与贵金属波动跳空频繁,lookback 过小易把毛刺当结构,过大则信号严重滞后,建议在 M5 以上周期先跑 2~5 的梯度测试。
class="type">bool IsSwingLow(class="kw">const class="type">class="kw">double &low_price[], class="type">int index, class="type">int lookback) { for(class="type">int i = class="num">1; i <= lookback; i++) { if(low_price[index] > low_price[index - i] || low_price[index] > low_price[index + i]) class="kw">return false; } class="kw">return true; } class="type">bool IsSwingHigh(class="kw">const class="type">class="kw">double &high_price[], class="type">int index, class="type">int lookback) { for(class="type">int i = class="num">1; i <= lookback; i++) { if(high_price[index] < high_price[index - i] || high_price[index] < high_price[index + i]) class="kw">return false; } class="kw">return true; } class="type">void OnTick() { rates_total = Bars(_Symbol, timeframe); CopyOpen(_Symbol, timeframe, class="num">0, rates_total, open); CopyClose(_Symbol, timeframe, class="num">0, rates_total, close); CopyLow(_Symbol, timeframe, class="num">0, rates_total, low); CopyHigh(_Symbol, timeframe, class="num">0, rates_total, high); CopyTime(_Symbol, timeframe, class="num">0, rates_total, time);
◍ 用嵌套循环抓取 XABCD 的空单形态
在 MT5 指标里识别卖出结构的 XABCD,核心是先拿到时间序列,再靠多层循环去比对摆动点。下面这段代码从 CopyTime 拉取当前品种、指定周期的全部 K 线时间,作为后续定位摆点的基准。 外层用 z 从 7 到 10 遍历,代表摆点判定的左右跨度(zigzag 的 leg 长度)。rates_total 必须大于等于 bars_check,否则样本不足直接跳过,避免在小周期加载初期误画。 i 循环找 X(swing low),命中后记录低价与时间,并用 StringFormat 生成唯一标签名防对象重名。随后 j 在 i 之后找更高的 swing high 作为 A,k 在 j 之后找更低的 swing low 作为 B,l 继续向后找 swing high 作为 C 的候选——每一层都用 time 严格递增来保证顺序。 外汇与贵金属波动大,这种形态识别只是概率倾向,实盘前请在策略测试器用不同 z(7~10)跑历史数据,看哪档跨度在你的品种上假信号最少。
CopyTime(_Symbol, timeframe, class="num">0, rates_total, time); class=class="str">"cmt">//FOR SELL if(show_sell) { if(rates_total >= bars_check) { for(class="type">int z = class="num">7; z <= class="num">10; z++) { for(class="type">int i = rates_total - bars_check; i < rates_total - z; i++) { if(IsSwingLow(low, i, z)) { class=class="str">"cmt">// If a swing low is found, store its price, time, and create a name for the objects to mark the X. X = low[i]; class=class="str">"cmt">// Price of the swing low(X). X_time = time[i]; class=class="str">"cmt">// Time of the swing low(X). X_letter = StringFormat("X%d", i); class=class="str">"cmt">// Unique name for the text label object. for(class="type">int j = i; j < rates_total - z; j++) { if(IsSwingHigh(high, j, z) && time[j] > X_time) { A = high[j]; class=class="str">"cmt">// Price of the swing high(A). A_time = time[j]; class=class="str">"cmt">// Time of the swing high(A) A_letter = StringFormat("A%d", j); class=class="str">"cmt">// Unique name for the text label object for(class="type">int k = j; k < rates_total - z; k++) { if(IsSwingLow(low, k, z) && time[k] > A_time) { B = low[k]; class=class="str">"cmt">// Price of the swing low(B). B_time = time[k]; class=class="str">"cmt">// Time of the swing low(B). B_letter = StringFormat("B%d", k); class=class="str">"cmt">// Unique name for the text label object. for(class="type">int l = k ; l < rates_total - z; l++) { if(IsSwingHigh(high, l, z) && time[l] > B_time)
用三重摆动点锁定 C-D-E 结构
在 MT5 自定义指标里,识别 ABC 扩展形态的后半段,核心是先抓住摆动高点 C,再向后找更低的摆动低点 D,最后用减半的摆动窗口去确认回抽高点 E。下面这段逻辑直接嵌套在循环里,靠 IsSwingLow / IsSwingHigh 两个判定函数逐级过滤。 代码先记录 C 点的价格与时间,并用 StringFormat 拼出唯一标签名,避免多个 swing 高点在图表上重名覆盖。随后从 l 开始向后扫,只接受时间晚于 C_time 的摆动低点作为 D。 D 落定后,内层把摆动参数从 z 降到 z/2,意味着 E 的确认允许更窄的局部波动——这是为了让 BC 段回抽更快被捕捉,而不是等完整 z 根 K 线。E 一旦成立,就拿到高点价、时间与标签,后续可直接画对象。 外汇与贵金属波动跳空频繁,z/2 这种减半窗口在重大数据行情里可能把噪音当成 E,实盘前务必在 EURUSD 或 XAUUSD 的 M15 上跑一遍验证命中率。
{
C = high[l]; class=class="str">"cmt">// Price of the swing high(C).
C_time = time[l]; class=class="str">"cmt">// Time of the swing high(C).
C_letter = StringFormat("C%d", l); class=class="str">"cmt">// Unique name for the text label object.
for(class="type">int m = l; m < rates_total - z; m++)
{
if(IsSwingLow(low, m, z) && time[m] > C_time)
{
D = low[m]; class=class="str">"cmt">// Price of the swing low(D).
D_time = time[m]; class=class="str">"cmt">// Time of the swing low(D).
D_letter = StringFormat("D%d", m); class=class="str">"cmt">// Unique name for the text label object.
for(class="type">int n = m ; n < rates_total - (z/class="num">2) - class="num">1; n++)
{
if(IsSwingHigh(high, n, (z/class="num">2)) && time[n] > D_time)
{
E = high[n]; class=class="str">"cmt">// Price of the swing low(B).
E_time = time[n]; class=class="str">"cmt">// Time of the swing low(B).
E_letter = StringFormat("E%d", n); class=class="str">"cmt">// Unique name for the text label object.
「摆动点变量的声明与买卖分支入口」
在识别 Gartley 形态的嵌套循环退出后,需要把 X、A、B、C、D、E 六个摆动点的价格和时点落进独立变量,才能在图表上打标签并做后续比率判断。 下面这段代码声明了各摆动点的 double 价格、datetime 时间与 string 标签名。注意原文注释里把 C/D/E 的注释都误写成了 'swing low (B)',实际它们应分别对应各自摆动点,复制时别照抄错注释。 随后用 if(show_sell) 配合 rates_total >= bars_check 进入卖单分支,bars_check 是你自己设定的最少回溯 bar 数,若总 bar 不足就直接跳过,避免数组越界。 外汇与贵金属杠杆高,形态未走完前切勿据标签提前押注,仅把它当概率参考去 MT5 验证绘制逻辑。
class=class="str">"cmt">//X class="type">class="kw">double X; class=class="str">"cmt">// Price of the swing low(X). class="type">class="kw">datetime X_time; class=class="str">"cmt">// Time of the swing low(X). class="type">class="kw">string X_letter; class=class="str">"cmt">// Unique name for the text label object. class=class="str">"cmt">//A class="type">class="kw">double A; class=class="str">"cmt">// Price of the swing high(A). class="type">class="kw">datetime A_time; class=class="str">"cmt">// Time of the swing high(A). class="type">class="kw">string A_letter; class=class="str">"cmt">// Unique name for the text label object. class=class="str">"cmt">//B class="type">class="kw">double B; class=class="str">"cmt">// Price of the swing low(B). class="type">class="kw">datetime B_time; class=class="str">"cmt">// Time of the swing low(B). class="type">class="kw">string B_letter; class=class="str">"cmt">// Unique name for the text label object. class=class="str">"cmt">//C class="type">class="kw">double C; class=class="str">"cmt">// Price of the swing low(B). class="type">class="kw">datetime C_time; class=class="str">"cmt">// Time of the swing low(B). class="type">class="kw">string C_letter; class=class="str">"cmt">// Unique name for the text label object. class=class="str">"cmt">//D class="type">class="kw">double D; class=class="str">"cmt">// Price of the swing low(B). class="type">class="kw">datetime D_time; class=class="str">"cmt">// Time of the swing low(B). class="type">class="kw">string D_letter; class=class="str">"cmt">// Unique name for the text label object. class=class="str">"cmt">//E class="type">class="kw">double E; class=class="str">"cmt">// Price of the swing low(B). class="type">class="kw">datetime E_time; class=class="str">"cmt">// Time of the swing low(B). class="type">class="kw">string E_letter; class=class="str">"cmt">// Unique name for the text label object. if(show_sell) { if(rates_total >= bars_check) {
◍ 用四重嵌套循环逮住 XABCD 摆点
识别谐波形态的难点不在画线,而在如何不重不漏地扫出合法的摆点序列。下面这段逻辑用 z 从 7 到 10 遍历摆动阈值,意味着同一段行情会按 7~10 根 K 线的最小摆动容差各跑一遍,容差越大越能过滤毛刺噪音。 外层先定位 X(摆动低点),往后在时间轴上找 A(摆动高点)、B(回落低点)、C(再弹高点),四层 for 循环靠 time[] 的先后关系卡死顺序,避免把右侧未发生的点误判进形态。 实盘里若 bars_check 设得过小,rates_total - bars_check 起扫位置太靠右,可能漏掉已完成数小时的旧形态;EURUSD 小时图建议 bars_check 至少 500,才覆盖得完一轮完整的 XABCD 演化。外汇与贵金属杠杆高,形态失效时回撤可能超预期,任何信号都只能当概率参考。 让小布替你跑这套 把 z 的上下限改成 5~12,MT5 策略测试器里对比标记数量:阈值放宽后假摆点会明显增多,但你也会更早捕到雏形,自己权衡噪音与灵敏度的切点。
for(class="type">int z = class="num">7; z <= class="num">10; z++) { for(class="type">int i = rates_total - bars_check; i < rates_total - z; i++) { if(IsSwingLow(low, i, z)) { class=class="str">"cmt">// If a swing low is found, store its price, time, and create a name for the objects to mark the X. X = low[i]; class=class="str">"cmt">// Price of the swing low(X). X_time = time[i]; class=class="str">"cmt">// Time of the swing low(X). X_letter = StringFormat("X%d", i); class=class="str">"cmt">// Unique name for the text label object. for(class="type">int j = i; j < rates_total - z; j++) { if(IsSwingHigh(high, j, z) && time[j] > X_time) { A = high[j]; class=class="str">"cmt">// Price of the swing high(A). A_time = time[j]; class=class="str">"cmt">// Time of the swing high(A) A_letter = StringFormat("A%d", j); class=class="str">"cmt">// Unique name for the text label object for(class="type">int k = j; k < rates_total - z; k++) { if(IsSwingLow(low, k, z) && time[k] > A_time) { B = low[k]; class=class="str">"cmt">// Price of the swing low(B). B_time = time[k]; class=class="str">"cmt">// Time of the swing low(B). B_letter = StringFormat("B%d", k); class=class="str">"cmt">// Unique name for the text label object. for(class="type">int l = k ; l < rates_total - z; l++) { if(IsSwingHigh(high, l, z) && time[l] > B_time) { C = high[l]; class=class="str">"cmt">// Price of the swing high(C). C_time = time[l]; class=class="str">"cmt">// Time of the swing high(C).
ABCD形态里D点之后的E点怎么抓
在摆动点识别逻辑里,D点被确认为摆动低点后,代码并没有停,而是继续向后扫半宽度的摆动高。具体做法是把内循环的上界改成 rates_total - (z/2) - 1,只用原ZigZag宽度的一半去判定,这样E点会比D点更敏感地冒头。 下面这段是嵌套在D点确认后的实际写法:先给文本对象起带索引的唯一名,再在时间条件满足时抓E的价格与时间,最后建X标签做可视化锚点。注意原注释把E错写成swing low(B),实际 high[n] 抓的是摆动高。 C_letter = StringFormat("C%d", l); // 给C点文本对象生成不重名标签 for(int m = l; m < rates_total - z; m++) { if(IsSwingLow(low, m, z) && time[m] > C_time) { D = low[m]; // 摆动低点D的价格 D_time = time[m]; // 摆动低点D的时间 D_letter = StringFormat("D%d", m); // D点文本对象唯一名 for(int n = m ; n < rates_total - (z/2) - 1; n++) { if(IsSwingHigh(high, n, (z/2)) && time[n] > D_time) { E = high[n]; // 实际为摆动高点E的价格 E_time = time[n]; // 摆动高点E的时间 E_letter = StringFormat("E%d", n); // E点文本对象唯一名 ObjectCreate(chart_id,X_letter,OBJ_TEXT,0,X_time,X); ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X"); ObjectSetInteger(chart_id,X_letter,OBJPROP_COLOR,txt_clr); 外汇和贵金属行情里这种形态失效很常见,半宽度参数 z/2 若设得过小,E点可能在一根毛刺K线上就触发,建议在MT5里把z从默认5调到7再观察重绘频率。
C_letter = StringFormat("C%d", l); class=class="str">"cmt">// Unique name for the text label object. for(class="type">int m = l; m < rates_total - z; m++) { if(IsSwingLow(low, m, z) && time[m] > C_time) { D = low[m]; class=class="str">"cmt">// Price of the swing low(D). D_time = time[m]; class=class="str">"cmt">// Time of the swing low(D). D_letter = StringFormat("D%d", m); class=class="str">"cmt">// Unique name for the text label object. for(class="type">int n = m ; n < rates_total - (z/class="num">2) - class="num">1; n++) { if(IsSwingHigh(high, n, (z/class="num">2)) && time[n] > D_time) { E = high[n]; class=class="str">"cmt">// Price of the swing low(B). E_time = time[n]; class=class="str">"cmt">// Time of the swing low(B). E_letter = StringFormat("E%d", n); class=class="str">"cmt">// Unique name for the text label object. ObjectCreate(chart_id,X_letter,OBJ_TEXT,class="num">0,X_time,X); ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X"); ObjectSetInteger(chart_id,X_letter,OBJPROP_COLOR,txt_clr);