MQL5 简介(第 16 部分):利用技术图表形态构建 EA 交易·综合运用
- 在MT5图表上标注ABCDE拐点
- 用 XABCD 变量组承接摆动点
- 空头分支里的 XABD 摆动点捕获逻辑
- ABCD到E:摆动点嵌套捕获的逻辑链
- 用数组极值锁定摆动点的高低位
- 用数组极值锁定 AB 与 XA 区间的高低点
- 用文字对象给波段拐点打标签
- 给谐波节点打字母标签
- 给谐波节点打标签和连线
- 用趋势线把四段摆动连成通道
- 用趋势线与矩形框出 A/B 区
- 头肩形态参数与标记落点
- 给 XA 与 AB 段打标签并画趋势线
- 头肩形态的对象绘制拆解
- 用矩形和箭头把假突破区画出来
- 在图上标出止损止盈与胜负区域
- 在K线上直接标出止损止盈位
- 从画形态到自动下单的跨越
- ABCD 点的嵌套扫描逻辑
- 从摆动高点反推区间极值
- ABCD 各段极值与嵌套判定的取值逻辑
- 在图上钉死 XABCD 五个字母
- 给谐波节点打标记与连线命名
- 用文字对象标定 ABCDE 拐点
- 用趋势线把三段腿画进图表
- 用 OBJ_TREND 把三段结构连成通道
- 头肩结构在图上的几何落点
- 头肩形态里右肩下破的判定与绘制
- 用区间穿越判定锁定有效止盈区
- 用矩形和箭头把盈亏区画上图
- 把止损止盈标到图上去
- 亏损区与卖点标注的画法
- 在图上画出卖信号与盈利区
- 在K线上标注止损止盈文本
- 下单后如何锁住本根 K 线
- 空代码片段的边界情况
- 别急着下结论
◍ 在MT5图表上标注ABCDE拐点
做谐波或价格行为分析时,把关键拐点直接标在图上比脑内记坐标省事得多。下面这段逻辑就是在指定图表上创建文本对象,分别把 A~E 五个点用对应字母标出来,并统一上色。
代码里 ObjectCreate 的第三个参数固定为 OBJ_TEXT,子窗口号填 0 表示主图;时间坐标用各自变量的 A_time 到 E_time,价格坐标用 A 到 E。随后 ObjectSetString 把显示文本写成大写字母,ObjectSetInteger 用 txt_clr 控制颜色,五个点共用同一支笔。
你只要把 chart_id 换成 ChartID(),再把五个点的时间和价格接进实盘算好的结构,编译挂上 EURUSD 的 H1 就能立刻看到字母落位。外汇与贵金属杠杆高,标图只是辅助,进出场仍按自己的风控来。
ObjectCreate(chart_id,A_letter,OBJ_TEXT,class="num">0,A_time,A); ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A"); ObjectSetInteger(chart_id,A_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,B_letter,OBJ_TEXT,class="num">0,B_time,B); ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B"); ObjectSetInteger(chart_id,B_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,C_letter,OBJ_TEXT,class="num">0,C_time,C); ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C"); ObjectSetInteger(chart_id,C_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,D_letter,OBJ_TEXT,class="num">0,D_time,D); ObjectSetString(chart_id,D_letter,OBJPROP_TEXT,"D"); ObjectSetInteger(chart_id,D_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,E_letter,OBJ_TEXT,class="num">0,E_time,E); ObjectSetString(chart_id,E_letter,OBJPROP_TEXT,"E"); ObjectSetInteger(chart_id,E_letter,OBJPROP_COLOR,txt_clr);
「用 XABCD 变量组承接摆动点」
上一节把嵌套循环逐层 break 收口后,紧接着要做的就是把识别到的摆动点存进一套命名变量里,方便后续画线和判定形态。这里直接以 XABCD 四点结构为例,每个点都拆成价格、时间、文本对象名三件套。 X 点记的是摆动低点:X 存价格、X_time 存时间、X_letter 是给文本标签用的唯一名,另外 x_lowest_index 和 x_a_ll 用来追踪 X 到 A 段之间的最低价与位置。 A 点对应摆动高点,除 A / A_time / A_letter 外,还多了 A_zone 和 A_low——前者给区域着色用,后者框定 A 点下方的边界。B 点又回到摆动低点,B_high 则标出 B 点上方边界,和 A 点形成对称的区域概念。 C 与 D 在原文注释里都写成了 swing low(B),这明显是拷贝注释时的笔误,实际 C 应为摆动高点、D 为摆动低点才符合 XABCD 顺序;跑代码前最好把这两行注释改对,否则读源码的人会被带偏。外汇与贵金属波动大,这类形态识别仅作概率参考,实盘须控仓。
input class="type">class="kw">color txt_clr = clrBlue; class=class="str">"cmt">// Texts class="type">class="kw">color 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="type">int x_a_bars; class="type">int x_lowest_index; class="type">class="kw">double x_a_ll; class="type">class="kw">datetime x_a_ll_t; 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="type">int a_b_bars; class="type">int a_highest_index; class="type">class="kw">double a_b_hh; class="type">class="kw">datetime a_b_hh_t; class="type">class="kw">string A_zone; class="type">class="kw">double A_low; 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="type">int b_c_bars; class="type">int b_lowest_index; class="type">class="kw">double b_c_ll; class="type">class="kw">datetime b_c_ll_t; class="type">class="kw">string B_zone; class="type">class="kw">double B_high; 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="type">int c_d_bars; class="type">int c_highest_index; class="type">class="kw">double c_d_hh; class="type">class="kw">datetime c_d_hh_t; 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.
空头分支里的 XABD 摆动点捕获逻辑
这段声明与循环是做空信号识别的数据骨架。前面一批变量(d_e_bars、d_lowest_index、E、xa~ex 等)先占位,记录每段摆动的价、时与画线对象名,后面真正跑扫描时再填值。 核心在 show_sell 开关下的三重嵌套循环:z 从 7 到 10 遍历摆动敏感度,i 从倒数 bars_check 根 K 线向前找摆动低点作 X;找到后 j 向后找更高时间的摆动高点作 A,k 再向后找摆动低点作 B。 敏感度 z 取 7~10 意味着至少左右各 7 根 K 不被突破才认摆动,参数偏小容易在震荡里多标假点,偏大则滞后。外汇与贵金属波动跳空多,建议先在 XAUUSD 的 M15 上把 bars_check 调到 300 根跑一遍,看标出密度是否可接受。 循环里用 StringFormat("X%d", i) 这类拼名,保证同帧多次出现不重名,否则 ObjectCreate 会静默失败。开 MT5 把这段贴进 EA 的 OnCalculate 头部,Print 出 X_time 与 A_time 差,就能直接验证摆动识别节奏。
class="type">int d_e_bars; class="type">int d_lowest_index; class="type">class="kw">double d_e_ll; class="type">class="kw">datetime d_e_ll_t; class="type">class="kw">double D_3bar_high; 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. class="type">class="kw">double E_3bar_low; class="type">class="kw">string xa; class=class="str">"cmt">// Unique name for the trendline for XA. class="type">class="kw">string ab; class=class="str">"cmt">// Unique name for the trendline for AB. class="type">class="kw">string bc; class=class="str">"cmt">// Unique name for the trendline for BC. class="type">class="kw">string cd; class=class="str">"cmt">// Unique name for the trendline for CD. class="type">class="kw">string de; class=class="str">"cmt">// Unique name for the trendline for DE. class="type">class="kw">string ex; class=class="str">"cmt">// Unique name for the trendline for EX. 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++)
◍ ABCD到E:摆动点嵌套捕获的逻辑链
这段片段在做一件事:在已确认B点时间之后,顺次捕捉更高的摆动高点C、更低的摆动低点D,以及半周期参数下的摆动高点E,构成扩展价格结构的节点链。外汇与贵金属市场波动剧烈,此类结构仅代表历史形态概率,实盘须警惕假突破。
外层判断 IsSwingHigh(high, l, z) && time[l] > B_time 锁定C点:只有距B更晚且通过z根K线验证的摆动高点才记入,同时用 StringFormat("C%d", l) 生成不重复的文本标签名,方便MT5图表对象管理。
中层从l向 rates_total - z 扫描,遇到晚于C的摆动低点即存为D;内层再把摆动判定周期折半(z/2),在 rates_total - (z/2) - 1 前找晚于D的E点。把z从默认值改小,E点会更敏感但也更易出现噪音信号,建议先在EURUSD的M15上用z=5跑一遍看节点密度。
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). 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 high(E). E_time = time[n]; class=class="str">"cmt">// Time of the swing high(E). E_letter = StringFormat("E%d", n); class=class="str">"cmt">// Unique name for the text label object.
「用数组极值锁定摆动点的高低位」
在 MT5 里做价格行为分析,经常要在某两段时间点之间找出最低价或最高价。上面这段逻辑就是在 D-E、C-D、B-C 三个区间分别抓取极值,为后续判定摆动结构做准备。 核心用的是 Bars() 先数出区间 K 线根数,再用 ArrayMinimum() / ArrayMaximum() 拿到极值在数组里的下标。比如 d_e_bars 算出 D 到 E 的柱数后,ArrayMinimum(low, m, d_e_bars) 直接给出这段最低价所在位置,d_e_ll 就是那个最低点价格。 注意 D_3bar_high 取的是最低点前第 3 根 K 线的高价,这不是极值而是结构参考位。B_high 的写法里直接用了 b_lowest_index 对应的 high,也就是 B-C 区间最低那根的高,容易和 b_c_ll 混,复制时建议改个变量名。 外汇和贵金属波动大、滑点频繁,这类极值抓取法在跳空时可能跨过真实影线,上机跑之前先用 EURUSD 的 M15 历史数据验证一遍区间边界。
d_e_bars = Bars(_Symbol, PERIOD_CURRENT, D_time, E_time); class=class="str">"cmt">// Count the number of bars between D and E d_lowest_index = ArrayMinimum(low, m, d_e_bars); class=class="str">"cmt">// Find the index of the lowest low in the range d_e_ll = low[d_lowest_index]; class=class="str">"cmt">// Store the lowest low(D - E lowest point) d_e_ll_t = time[d_lowest_index]; class=class="str">"cmt">// Store the corresponding time D_3bar_high = high[d_lowest_index - class="num">3]; class=class="str">"cmt">// The high price of the third bar before the bar that formed D c_d_bars = Bars(_Symbol,PERIOD_CURRENT,C_time,d_e_ll_t); class=class="str">"cmt">// Count the number of bars between C and V c_highest_index = ArrayMaximum(high,l,c_d_bars); class=class="str">"cmt">// Find the index of the highest high in the range c_d_hh = high[c_highest_index]; class=class="str">"cmt">// Store the lowest high(C - D lowest point) c_d_hh_t = time[c_highest_index]; class=class="str">"cmt">// Store the corresponding time b_c_bars = Bars(_Symbol, PERIOD_CURRENT, B_time, c_d_hh_t); class=class="str">"cmt">// Count the number of bars between B and C b_lowest_index = ArrayMinimum(low, k, b_c_bars); class=class="str">"cmt">// Find the index of the lowest low in the range b_c_ll = low[b_lowest_index]; class=class="str">"cmt">// Store the lowest low B - C lowest point) b_c_ll_t = time[b_lowest_index]; class=class="str">"cmt">// Store the corresponding time B_high = high[b_lowest_index]; class=class="str">"cmt">// The high price of the bar that formed swing low D
用数组极值锁定 AB 与 XA 区间的高低点
在 MT5 脚本里定位谐波形态的中间节点,核心是先数出两段 K 线数量,再用数组极值函数抓取各自的高低点。下面这段逻辑先算 A 到 B 的 K 线根数 a_b_bars,再在 high 数组该范围内找最高价索引,得到 AB 段最高点 a_b_hh 及其时间 a_b_hh_t,同时把对应低位记到 A_low。 随后以 X 时间为起点、a_b_hh_t 为终点数出 x_a_bars,用 ArrayMinimum 在 low 数组里捞到 XA 段最低点 x_a_ll 与时间 x_a_ll_t。注意原注释把 C-D 写进 X 段计数里,实际变量名指向的是 X 到 AB 高点的区间,复制时别被注释带偏。 E_3bar_low 取的是 E 点前面第 3 根 K 线的低价,用于后续过滤假突破。最后的 if 判断串起了 a_b_hh > x_a_ll、b_c_ll 夹在两者之间、c_d_hh 过前高、E 回踩不破 x_a_ll 等 11 个条件,全部满足才允许画 X 字母对象。外汇与贵金属杠杆高,这类形态识别只提高概率,实盘前务必用策略 tester 跑一遍 EURUSD 15M 看命中率。
a_b_bars = Bars(_Symbol,PERIOD_CURRENT,A_time,b_c_ll_t); class=class="str">"cmt">// Count the number of bars between A and B a_highest_index = ArrayMaximum(high,j,a_b_bars); class=class="str">"cmt">// Find the index of the highest high in the range a_b_hh = high[a_highest_index]; class=class="str">"cmt">// Store the lowest low A - B lowest point) a_b_hh_t = time[a_highest_index]; class=class="str">"cmt">// Store the corresponding time A_low = low[a_highest_index]; x_a_bars = Bars(_Symbol, PERIOD_CURRENT, X_time, a_b_hh_t); class=class="str">"cmt">// Count the number of bars between C and D x_lowest_index = ArrayMinimum(low, i, x_a_bars); class=class="str">"cmt">// Find the index of the lowest low in the range x_a_ll = low[x_lowest_index]; class=class="str">"cmt">// Store the lowest low(C - D lowest point) x_a_ll_t = time[x_lowest_index]; class=class="str">"cmt">// Store the corresponding time for C - D E_3bar_low = low[n - class="num">3]; class=class="str">"cmt">// The LOW price of the third bar before the bar that formed E if(a_b_hh > x_a_ll && b_c_ll < a_b_hh && b_c_ll > x_a_ll && c_d_hh > a_b_hh && E < c_d_hh && d_e_ll > x_a_ll && d_e_ll <= B_high && D_3bar_high >= B_high && E > A_low && E_3bar_low < a_b_hh) { ObjectCreate(chart_id,X_letter,OBJ_TEXT,class="num">0,X_time,X); ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X");
◍ 用文字对象给波段拐点打标签
在 MT5 图表上给价格行为的关键拐点(X、A、B、C、D、E)做可视化标记,最轻量的做法是用 OBJ_TEXT 对象逐个创建,而不是画线或按钮。下面这段逻辑依次在各自时间点落上字母,并统一用 txt_clr 控制颜色,方便一眼区分波段结构。 核心动作就三步:ObjectCreate 以 OBJ_TEXT 类型在指定 time/price 坐标生成对象;ObjectSetString 写进显示字符(如 "A");ObjectSetInteger 配 OBJPROP_COLOR 定颜色。X 点之后 A~E 五个标签完全同构,只是变量名和坐标不同。 开 MT5 把这段代码塞进你的形态识别脚本,改一下 X_letter、A_time 等变量,就能在 EURUSD 的 H1 上直接看到字母锚点。外汇与贵金属波动剧烈,这类标记只辅助读图,不预示方向,实际进出场仍需结合风控。
ObjectSetInteger(chart_id,X_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,A_letter,OBJ_TEXT,class="num">0,A_time,A); ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A"); ObjectSetInteger(chart_id,A_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,B_letter,OBJ_TEXT,class="num">0,B_time,B); ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B"); ObjectSetInteger(chart_id,B_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,C_letter,OBJ_TEXT,class="num">0,C_time,C); ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C"); ObjectSetInteger(chart_id,C_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,D_letter,OBJ_TEXT,class="num">0,D_time,D); ObjectSetString(chart_id,D_letter,OBJPROP_TEXT,"D"); ObjectSetInteger(chart_id,D_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,E_letter,OBJ_TEXT,class="num">0,E_time,E);
「给谐波节点打字母标签」
在 MT5 图表上把 XABCD 各拐点标出来,靠的是 OBJ_TEXT 对象而非画线本身。上面这段循环里先用 ObjectSetString 把 E_letter 的文本写成 "E",再用 ObjectSetInteger 给它指定 txt_clr 颜色,等于给辅助点 E 挂了个可见字。 随后用 StringFormat 批量拼出 "XA line%d" 到 "EX line%d" 这类名字,i 是循环序号,保证第 0 个形态和第 9 个形态的对象名不撞车。 真正落坐标的是 ObjectCreate:X_letter 放在 (x_a_ll_t, x_a_ll) 即 X 点低位时间价位点,A_letter 放在 (a_b_hh_t, a_b_hh) 即 A 点高位,各自再 SetString 写 "X" "A" 并 SetInteger 上色。你开 MT5 把 x_a_ll、a_b_hh 这些变量接进自己的拐点计算,就能直接看到字母浮在波峰波谷上。外汇与贵金属波动剧烈,标签仅作形态识别参考,不构成方向判定。
ObjectSetString(chart_id,E_letter,OBJPROP_TEXT,"E"); ObjectSetInteger(chart_id,E_letter,OBJPROP_COLOR,txt_clr); xa = StringFormat("XA line%d", i); ab = StringFormat("AB line%d", i); bc = StringFormat("BC line%d", i); cd = StringFormat("CD line%d", i); de = StringFormat("DE line%d", i); ex = StringFormat("EX line%d", i); ObjectCreate(chart_id,X_letter,OBJ_TEXT,class="num">0,x_a_ll_t,x_a_ll); ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X"); ObjectSetInteger(chart_id,X_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,A_letter,OBJ_TEXT,class="num">0,a_b_hh_t,a_b_hh); ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A"); ObjectSetInteger(chart_id,A_letter,OBJPROP_COLOR,txt_clr);
给谐波节点打标签和连线
在 MT5 图表上把 XABCD 各点可视化,靠的是 ObjectCreate 配合 OBJ_TEXT 与 OBJ_TREND。上面这段连续调用,分别把 B、C、D、E 以文字对象标在各自的时间-价格坐标上,字母串写死为 "B""C""D""E",颜色统一走 txt_clr 变量。 最后一行用 OBJ_TREND 画了 XA 段连线,起点取 x_a_ll_t 与 x_a_ll(疑似 X 至 A 的低位时间/价格),终点接 a_b_hh_t 与 a_b_hh,线宽设 3。线宽参数 OBJPROP_WIDTH 填 3 在 1 分钟图常偏粗,切到 H1 以上周期可读性更好。 外汇与贵金属杠杆高,这类标注只帮你识别形态,不预示方向;实盘前先在策略测试器用历史数据跑一遍,确认坐标变量没越界再挂真账户。
ObjectCreate(chart_id,B_letter,OBJ_TEXT,class="num">0,b_c_ll_t,b_c_ll); ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B"); ObjectSetInteger(chart_id,B_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,C_letter,OBJ_TEXT,class="num">0,c_d_hh_t,c_d_hh); ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C"); ObjectSetInteger(chart_id,C_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,D_letter,OBJ_TEXT,class="num">0,d_e_ll_t,d_e_ll); ObjectSetString(chart_id,D_letter,OBJPROP_TEXT,"D"); ObjectSetInteger(chart_id,D_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,E_letter,OBJ_TEXT,class="num">0,E_time,E); ObjectSetString(chart_id,E_letter,OBJPROP_TEXT,"E"); ObjectSetInteger(chart_id,E_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id, xa,OBJ_TREND,class="num">0,x_a_ll_t,x_a_ll,a_b_hh_t,a_b_hh); ObjectSetInteger(chart_id,xa,OBJPROP_WIDTH,class="num">3);
◍ 用趋势线把四段摆动连成通道
在 MT5 图表上把 ABCD 四段价格摆动画成可见的趋势线,核心就是 ObjectCreate 配 OBJ_TREND 再加几行属性设置。下面这段直接操作 chart_id 指定的图表,xa、ab、bc、cd 是每段线的对象名,背后用的都是已经算好的拐点时间和价格变量。 线宽统一设成 3,颜色用 clrSaddleBrown,并且 OBJPROP_BACK 置 true 让线沉到 K 线后面,不挡视线。外汇和贵金属波动快,这类辅助线只作结构参考,实际突破与否仍受新闻与流动性影响,存在较高风险。 代码里 ab 段连接的是 a_b_hh 到 b_c_ll,bc 段从 b_c_ll 接到 c_d_hh,cd 段再从 c_d_hh 连到 d_e_ll——三段依次拼接就构成了下降通道的直观骨架,开盘后你改个 clrSaddleBrown 换成自己顺眼的颜色就能直接用。
ObjectSetInteger(chart_id,xa,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, xa, OBJPROP_BACK, true); ObjectCreate(chart_id, ab,OBJ_TREND,class="num">0,a_b_hh_t,a_b_hh,b_c_ll_t,b_c_ll); ObjectSetInteger(chart_id,ab,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,ab,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, ab, OBJPROP_BACK, true); ObjectCreate(chart_id, bc,OBJ_TREND,class="num">0,b_c_ll_t,b_c_ll,c_d_hh_t,c_d_hh); ObjectSetInteger(chart_id,bc,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,bc,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, bc, OBJPROP_BACK, true); ObjectCreate(chart_id, cd,OBJ_TREND,class="num">0,c_d_hh_t,c_d_hh,d_e_ll_t,d_e_ll); ObjectSetInteger(chart_id,cd,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,cd,OBJPROP_COLOR,clrSaddleBrown);
「用趋势线与矩形框出 A/B 区」
这段片段承接前面算好的拐点坐标,把抽象数值落成 MT5 图表上的可视对象。cd、de、ex 三条 OBJ_TREND 分别代表 C-D、D-E、E-X 段的连接线,宽度统一设 3、颜色 clrSaddleBrown,并置 OBJPROP_BACK=true 塞到 K 线背后,不挡价格。 A_zone 与 B_zone 用 StringFormat 拼出带循环序号的名字(如 "A ZONEe0"),再用 OBJ_RECTANGLE 画矩形:A 区上沿取 a_b_hh、下沿到 A_low;B 区上沿 B_high、下沿 b_c_ll。这样每轮循环会在副图对应的时间区间刷出两块供应/需求参考区。 外汇与贵金属波动剧烈,这类区域仅作结构标注,回踩触发概率随品种与周期变化,实盘须自担高风险。开 MT5 把这段接进你已有的拐点计算循环,改 clrSaddleBrown 为更显眼的颜色,肉眼核对区域是否贴合波动边界。
ObjectSetInteger(chart_id, cd, OBJPROP_BACK, true); ObjectCreate(chart_id, de,OBJ_TREND,class="num">0,d_e_ll_t,d_e_ll,E_time,E); ObjectSetInteger(chart_id,de,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,de,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, de, OBJPROP_BACK, true); ObjectCreate(chart_id, ex,OBJ_TREND,class="num">0,E_time,E,time[n+(z/class="num">2)],x_a_ll); ObjectSetInteger(chart_id,ex,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,ex,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, ex, OBJPROP_BACK, true); A_zone = StringFormat("A ZONEe%d", i); B_zone = StringFormat("B ZONEe%d", i); ObjectCreate(chart_id,A_zone,OBJ_RECTANGLE,class="num">0,a_b_hh_t,a_b_hh,E_time,A_low); ObjectCreate(chart_id,B_zone,OBJ_RECTANGLE,class="num">0,b_c_ll_t,b_c_ll,d_e_ll_t,B_high); }
头肩形态参数与标记落点
这段脚本把头肩拓扑的判断收口后,直接暴露了一组可调输入项。timeframe 默认取 PERIOD_CURRENT,意味着你切到 M5 还是 H1,均线参考系跟着变;bars_check 写死 1000,代表回溯千根 K 线找摆动点,老机器跑起来可能掉帧。 show_sell / show_buy 两个布尔开关分开控制多空信号绘制,不想看卖点就把 show_sell 设 false,图表干净些。txt_clr 到 shoulder_clr 三段配色独立,头肩三个锚点用不同色,肉眼分拣更快。 下方 if 块是右肩确认后的标记逻辑:当 E 点跌破 a_b_hh 且 d_e_ll 不破前低区间,才触发 X/A/B 文字对象创建。ObjectCreate 用 OBJ_TEXT 挂到对应时间坐标,ObjectSetString 写字母,ObjectSetInteger 定颜色——开 MT5 把这段塞进 EA,加载后应在千根范围内标出潜在头肩字母。外汇与贵金属波动剧烈,此类形态识别仅作概率参考,实盘须控仓。
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 input class="type">bool show_sell = true; class=class="str">"cmt">// Display sell signals input class="type">bool show_buy = true; class=class="str">"cmt">// Display buy signals input class="type">class="kw">color txt_clr = clrBlue; class=class="str">"cmt">// Texts class="type">class="kw">color input class="type">class="kw">color head_clr = clrCornflowerBlue; class=class="str">"cmt">// Head class="type">class="kw">color input class="type">class="kw">color shoulder_clr = clrLightSeaGreen; class=class="str">"cmt">// Shoulder class="type">class="kw">color if(a_b_hh > x_a_ll && b_c_ll < a_b_hh && b_c_ll > x_a_ll && c_d_hh > a_b_hh && E < c_d_hh && d_e_ll > x_a_ll && d_e_ll <= B_high && D_3bar_high >= B_high && E > A_low && E_3bar_low < a_b_hh) { 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); ObjectCreate(chart_id,A_letter,OBJ_TEXT,class="num">0,A_time,A); ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A"); ObjectSetInteger(chart_id,A_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,B_letter,OBJ_TEXT,class="num">0,B_time,B); ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B");
◍ 给 XA 与 AB 段打标签并画趋势线
在识别完 X 到 E 各拐点后,下一步是把这些点用文字对象标出来,并连成可视的趋势线段。下面这段逻辑先给 B、C、D、E 补建文本对象,再统一用 StringFormat 生成带序号的线段名,避免多周期重绘时对象名撞车。 代码里 XA、AB 两段都用了 OBJ_TREND 类型,宽度设成 3,颜色取 clrSaddleBrown,并且把 OBJPROP_BACK 置为 true,让线段沉到 K 线后面不挡视线。外汇与贵金属波动快,这类辅助线只作结构参考,实际进出场仍需结合实时形态,杠杆品种高风险,错判概率不低。 注意文本对象创建时时间参数直接用各拐点的 t 变量(如 C_time、E_time),而线段端点用的是 x_a_ll_t、a_b_hh_t 这类高低点时间,说明标签贴的是收盘位,连线连的是极值位,二者可能差一根 K 线。开 MT5 把这段塞进循环里跑一下,看第 i 个形态是不是真生成了『XA line0』『AB line0』这种命名。
ObjectSetInteger(chart_id,B_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,C_letter,OBJ_TEXT,class="num">0,C_time,C); ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C"); ObjectSetInteger(chart_id,C_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,D_letter,OBJ_TEXT,class="num">0,D_time,D); ObjectSetString(chart_id,D_letter,OBJPROP_TEXT,"D"); ObjectSetInteger(chart_id,D_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,E_letter,OBJ_TEXT,class="num">0,E_time,E); ObjectSetString(chart_id,E_letter,OBJPROP_TEXT,"E"); ObjectSetInteger(chart_id,E_letter,OBJPROP_COLOR,txt_clr); xa = StringFormat("XA line%d", i); ab = StringFormat("AB line%d", i); bc = StringFormat("BC line%d", i); cd = StringFormat("CD line%d", i); de = StringFormat("DE line%d", i); ex = StringFormat("EX line%d", i); ObjectCreate(chart_id,X_letter,OBJ_TEXT,class="num">0,x_a_ll_t,x_a_ll); ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X"); ObjectSetInteger(chart_id,X_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,A_letter,OBJ_TEXT,class="num">0,a_b_hh_t,a_b_hh); ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A"); ObjectSetInteger(chart_id,A_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,B_letter,OBJ_TEXT,class="num">0,b_c_ll_t,b_c_ll); ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B"); ObjectSetInteger(chart_id,B_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,C_letter,OBJ_TEXT,class="num">0,c_d_hh_t,c_d_hh); ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C"); ObjectSetInteger(chart_id,C_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,D_letter,OBJ_TEXT,class="num">0,d_e_ll_t,d_e_ll); ObjectSetString(chart_id,D_letter,OBJPROP_TEXT,"D"); ObjectSetInteger(chart_id,D_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,E_letter,OBJ_TEXT,class="num">0,E_time,E); ObjectSetString(chart_id,E_letter,OBJPROP_TEXT,"E"); ObjectSetInteger(chart_id,E_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id, xa,OBJ_TREND,class="num">0,x_a_ll_t,x_a_ll,a_b_hh_t,a_b_hh); ObjectSetInteger(chart_id,xa,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,xa,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, xa, OBJPROP_BACK, true); ObjectCreate(chart_id, ab,OBJ_TREND,class="num">0,a_b_hh_t,a_b_hh,b_c_ll_t,b_c_ll); ObjectSetInteger(chart_id,ab,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,ab,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, ab, OBJPROP_BACK, true);
「头肩形态的对象绘制拆解」
这段逻辑把头肩结构里的每段颈线、区间和三角区块都画成了 MT5 图形对象,核心用 OBJ_TREND 连出 B-C、C-D、D-E、E-X 四条趋势线,线宽统一设 3、颜色 clrSaddleBrown,并全部置 OBJPROP_BACK=true 压到 K 线底层,避免遮挡价格。 A_zone 与 B_zone 用 OBJ_RECTANGLE 框出波动区:前者从 a_b_hh_t 到 E_time、价位跨 a_b_hh 与 A_low,后者覆盖 b_c_ll_t 到 d_e_ll_t、上下边界为 b_c_ll 与 B_high,命名靠 StringFormat 拼序号便于循环里不重名。 三个三角形 XAB、BCD、DEX 分别用 OBJ_TRIANGLE 以极值点时间价位做顶点,BCD 填 head_clr、其余填 shoulder_clr,均开 OBJPROP_FILL 和 BACK。注意 xa_line_t 与 ex_line_t 是用 ObjectGetTimeByValue 反查水平价位对应的时间轴坐标,少了这步三角顶点会对不上。 开 MT5 把这段塞进循环,把 shoulder_clr / head_clr 换成自己看的色值,能直接看到形态自动标出;外汇和贵金属波动大,形态失效概率不低,仅作辅助参考。
ObjectCreate(chart_id, bc,OBJ_TREND,class="num">0,b_c_ll_t,b_c_ll,c_d_hh_t,c_d_hh); ObjectSetInteger(chart_id,bc,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,bc,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, bc, OBJPROP_BACK, true); ObjectCreate(chart_id,cd,OBJ_TREND,class="num">0,c_d_hh_t,c_d_hh,d_e_ll_t,d_e_ll); ObjectSetInteger(chart_id,cd,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,cd,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, cd, OBJPROP_BACK, true); ObjectCreate(chart_id,de,OBJ_TREND,class="num">0,d_e_ll_t,d_e_ll,E_time,E); ObjectSetInteger(chart_id,de,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,de,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, de, OBJPROP_BACK, true); ObjectCreate(chart_id, ex,OBJ_TREND,class="num">0,E_time,E,time[n+(z/class="num">2)],x_a_ll); ObjectSetInteger(chart_id,ex,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,ex,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, ex, OBJPROP_BACK, true); A_zone = StringFormat("A ZONEe%d", i); B_zone = StringFormat("B ZONEe%d", i); ObjectCreate(chart_id,A_zone,OBJ_RECTANGLE,class="num">0,a_b_hh_t,a_b_hh,E_time,A_low); ObjectCreate(chart_id,B_zone,OBJ_RECTANGLE,class="num">0,b_c_ll_t,b_c_ll,d_e_ll_t,B_high); xa_line_t = ObjectGetTimeByValue(chart_id,xa,b_c_ll,class="num">0); ex_line_t = ObjectGetTimeByValue(chart_id,ex,d_e_ll,class="num">0); X_A_B = StringFormat("XAB %d", i); ObjectCreate(chart_id,X_A_B,OBJ_TRIANGLE,class="num">0,xa_line_t,b_c_ll,a_b_hh_t,a_b_hh,b_c_ll_t,b_c_ll); ObjectSetInteger(chart_id, X_A_B, OBJPROP_FILL, true); ObjectSetInteger(chart_id, X_A_B, OBJPROP_BACK, true); ObjectSetInteger(chart_id, X_A_B, OBJPROP_COLOR, shoulder_clr); B_C_D = StringFormat("BCD %d", i); ObjectCreate(chart_id, B_C_D, OBJ_TRIANGLE, class="num">0, b_c_ll_t, b_c_ll, c_d_hh_t, c_d_hh, d_e_ll_t, d_e_ll); ObjectSetInteger(chart_id, B_C_D, OBJPROP_COLOR, head_clr); ObjectSetInteger(chart_id, B_C_D, OBJPROP_FILL, true); ObjectSetInteger(chart_id, B_C_D, OBJPROP_BACK, true); D_E_X = StringFormat("DEX %d", i); ObjectCreate(chart_id, D_E_X, OBJ_TRIANGLE, class="num">0, d_e_ll_t, d_e_ll, E_time, E, ex_line_t, d_e_ll); ObjectSetInteger(chart_id, D_E_X, OBJPROP_COLOR, shoulder_clr); ObjectSetInteger(chart_id, D_E_X, OBJPROP_FILL, true); ObjectSetInteger(chart_id, D_E_X, OBJPROP_BACK, true); } class="type">int n_bars; class="type">int n_bars_2; class="type">class="kw">string sl_t; class="type">class="kw">string tp_t; class="type">class="kw">double sl_price; class="type">class="kw">double tp_price; for(class="type">int o = n; o < rates_total - class="num">1; o++) {
用矩形和箭头把假突破区画出来
这段逻辑只在收盘价跌破动态支撑 d_e_ll、且时间落在扩展区后半段(time[n+z/2] 之后)才触发,目的是把一段「看似破位实则回拉」的行情用图形标出来。外汇与贵金属波动大,这类形态出现后价格倾向反抽,但仅是概率,不是必然。 先算两段 K 线数:n_bars 是从 x_a_ll_t 到 E_time 的总柱数,n_bars_2 是从扩展中点 time[n+z/2] 到当前 o 的柱数;若 n_bars_2 <= n_bars,说明回拉发生在扩展时间窗内,才有继续处理的必要。 sl_zone 取 E 与 close[o] 绝对值差,tp_zone 取 close[o] 与 x_a_ll 绝对值差。随后从 n+z/2 到 o 遍历,只要中间有某根 close[p] 也低于 d_e_ll,no_cross 置真并 break——这意味着期间价格曾二次破位,形态失效。 no_cross 为假时,若 tp_zone >= sl_zone(盈利空间不小于止损),就批量建对象:Loss 矩形填色标止损区,Sell 箭头钉在破位点,Win 矩形填色标目标区,并把 sl_price、tp_price 落到变量供后续下单。MT5 里直接把下面代码贴进 EA 的循环体,调一下 lz_clr / wz_clr 就能看可视化效果。
if(close[o] < d_e_ll && time[o] >= time[n+(z/class="num">2)]) { n_bars = Bars(_Symbol,PERIOD_CURRENT,x_a_ll_t, E_time); n_bars_2 = Bars(_Symbol,PERIOD_CURRENT,time[n+(z/class="num">2)], time[o]); if(n_bars_2 <= n_bars) { class="type">class="kw">double sl_zone = MathAbs(E - close[o]); class="type">class="kw">double tp_zone = MathAbs(close[o] - x_a_ll); class="type">bool no_cross = false; for(class="type">int p = n + (z/class="num">2); p < o; p++) { if(close[p] < d_e_ll) { no_cross = true; break; } } if(no_cross == false) { if(tp_zone >= sl_zone) { class="type">class="kw">string loss_zone = StringFormat("Loss %d", i); ObjectCreate(chart_id,loss_zone,OBJ_RECTANGLE,class="num">0,E_time,E,time[o],close[o]); ObjectSetInteger(chart_id, loss_zone, OBJPROP_FILL, true); ObjectSetInteger(chart_id, loss_zone, OBJPROP_BACK, true); ObjectSetInteger(chart_id, loss_zone, OBJPROP_COLOR, lz_clr); class="type">class="kw">string sell_object = StringFormat("Sell Object%d", i); ObjectCreate(chart_id,sell_object,OBJ_ARROW_SELL,class="num">0,time[o],close[o]); class="type">class="kw">string win_zone = StringFormat("Win %d", i); ObjectCreate(chart_id,win_zone,OBJ_RECTANGLE,class="num">0,E_time,close[o],time[o],x_a_ll); ObjectSetInteger(chart_id, win_zone, OBJPROP_FILL, true); ObjectSetInteger(chart_id, win_zone, OBJPROP_BACK, true); ObjectSetInteger(chart_id, win_zone, OBJPROP_COLOR, wz_clr); sl_price = E; tp_price = x_a_ll; class="type">class="kw">string sl_d_s = DoubleToString(sl_price,_Digits); class="type">class="kw">string tp_d_s = DoubleToString(tp_price,_Digits); sl_t = StringFormat("sl %d", i);
◍ 在图上标出止损止盈与胜负区域
当 TP 区域高于 SL 区域时,脚本会在图表上直接画出文字标签,把每一笔假设的 SL 与 TP 价位标在 K 线对应的时间坐标上。字体固定为 8 号、颜色沿用 txt_clr 变量,肉眼扫过去就能比对挂单位置和价格结构的相对关系。 若 TP 区域反而低于 SL 区域,说明当前结构更倾向走空头。代码会先画一个填充矩形把亏损区(从入场价 close[o] 到止损 E)罩住,再在入场 K 线丢一个 OBJ_ARROW_SELL 箭头,最后用 n_tp = MathAbs(close[o] - sl_zone * 2) 算出两倍止损距离的止盈位,并画绿色填充矩形标为胜区。 这套画法纯属可视化辅助,不涉及下单。外汇与贵金属杠杆高、滑点随机,矩形里的「胜区」只是几何投影,实际触发概率随波动率漂移,开 MT5 加载后建议先切到 M1 观察标签是否和你的手动画线重合。
tp_t = StringFormat("tp %d", i); ObjectCreate(chart_id,sl_t,OBJ_TEXT,class="num">0,time[o],sl_price); ObjectSetString(chart_id,sl_t,OBJPROP_TEXT,"SL - " + sl_d_s); ObjectSetInteger(chart_id,sl_t,OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(chart_id,sl_t,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,tp_t,OBJ_TEXT,class="num">0,time[o],x_a_ll); ObjectSetString(chart_id,tp_t,OBJPROP_TEXT,"TP - " + tp_d_s); ObjectSetInteger(chart_id,tp_t,OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(chart_id,tp_t,OBJPROP_COLOR,txt_clr); } if(tp_zone < sl_zone) { class="type">class="kw">string loss_zone = StringFormat("Loss %d", i); ObjectCreate(chart_id,loss_zone,OBJ_RECTANGLE,class="num">0,E_time,E,time[o],close[o]); ObjectSetInteger(chart_id, loss_zone, OBJPROP_FILL, true); ObjectSetInteger(chart_id, loss_zone, OBJPROP_BACK, true); ObjectSetInteger(chart_id, loss_zone, OBJPROP_COLOR, lz_clr); class="type">class="kw">string sell_object = StringFormat("Sell Object%d", i); ObjectCreate(chart_id,sell_object,OBJ_ARROW_SELL,class="num">0,time[o],close[o]); class="type">class="kw">double n_tp = MathAbs(close[o] - (sl_zone * class="num">2)); class="type">class="kw">string win_zone = StringFormat("Win %d", i); ObjectCreate(chart_id,win_zone,OBJ_RECTANGLE,class="num">0,E_time,close[o],time[o],n_tp); ObjectSetInteger(chart_id, win_zone, OBJPROP_FILL, true); ObjectSetInteger(chart_id, win_zone, OBJPROP_BACK, true); ObjectSetInteger(chart_id, win_zone, OBJPROP_COLOR, wz_clr); sl_price = E; tp_price = n_tp; class="type">class="kw">string sl_d_s = DoubleToString(sl_price,_Digits); class="type">class="kw">string tp_d_s = DoubleToString(tp_price,_Digits);
「在K线上直接标出止损止盈位」
这段逻辑跑在一个循环里,变量 i 代表当前遍历到的挂单或成交序号。先拼出两个文本对象名:sl_t 用 StringFormat 写成 "sl %d",tp_t 写成 "tp %d",%d 会被 i 替换,保证每笔交易的标注互不重名。 随后用 ObjectCreate 在 chart_id 指定的图表上建两个 OBJ_TEXT 文本:一个钉在 time[o] 与 sl_price 交叉处,另一个钉在 time[o] 与 tp_price 交叉处。time[o] 是 K 线时间数组的第 o 根,sl_price / tp_price 是算好的止损止盈价。 文字内容通过 ObjectSetString 写进去,SL 前拼 "SL - " 再加 sl_d_s(止损距离字符串),TP 同理。字体大小统一设 8,用 ObjectSetInteger 改 OBJPROP_FONTSIZE;颜色走 txt_clr 变量,改 OBJPROP_COLOR。 开 MT5 把这段塞进你自己的订单遍历函数,把 txt_clr 设成跟背景反差大的值,就能在图上直观看到每单的 SL/TP 锚点,省得来回拖横线。外汇与贵金属波动剧烈,此类标注仅辅助看盘,不预示任何盈亏走向。
sl_t = StringFormat("sl %d", i); tp_t = StringFormat("tp %d", i); ObjectCreate(chart_id,sl_t,OBJ_TEXT,class="num">0,time[o],sl_price);; ObjectSetString(chart_id,sl_t,OBJPROP_TEXT,"SL - " + sl_d_s); ObjectSetInteger(chart_id,sl_t,OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(chart_id,sl_t,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,tp_t,OBJ_TEXT,class="num">0,time[o],tp_price); ObjectSetString(chart_id,tp_t,OBJPROP_TEXT,"TP - " + tp_d_s); ObjectSetInteger(chart_id,tp_t,OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(chart_id,tp_t,OBJPROP_COLOR,txt_clr); }
从画形态到自动下单的跨越
EA 如果只是把头肩形态标在图上,那还只是个可视化辅助。真正让它脱离人工点击的,是在条件触发时直接用 CTrade 发单:例如收盘低于 D 点就卖,止损挂 E 点,止盈先设 X 点,再按至少 1:2 的风险回报比微调。这一步做完,系统就从「提示工具」变成「自动执行体」。 MQL5 里用 #include <Trade/Trade.mqh> 把交易库拉进来,再用 CTrade trade; 建实例。MagicNumber 设成 5122025 这类独立值,是为了把本 EA 的订单和手动单、其他 EA 单在后台区分开——回测时尤其重要,不然平仓逻辑会误伤。 发单前用 SymbolInfoDouble(_Symbol, SYMBOL_ASK) 取实时卖价,并用 iTime 取当前柱时间。代码里用 currentBarTime != lastTradeBarTime 卡住「一根柱只下一单」,避免同一形态在影线抖动时连发。订单成功后才把 currentBarTime 写进 lastTradeBarTime,这个细节直接决定回测曲线会不会被重复开仓搞丑。 买入结构只是镜像:等收盘价站上颈线(D 点),止盈看最高 X、止损看最低 E,风险回报和判定逻辑不变。外汇与贵金属杠杆高,自动发单前务必在策略测试器用 2020—2024 年 Tick 数据跑一遍,确认 MagicNumber 隔离和单柱限制没漏。
class="macro">#include <Trade/Trade.mqh> CTrade trade; class="type">int MagicNumber = class="num">5122025; class="type">class="kw">datetime lastTradeBarTime = class="num">0; class="type">class="kw">double ask_price; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- trade.SetExpertMagicNumber(MagicNumber); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } ask_price = ask_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); class="type">class="kw">datetime currentBarTime = iTime(_Symbol, timeframe, class="num">0); 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++) {
◍ ABCD 点的嵌套扫描逻辑
这段代码片段在做一件事:从已确认的 A 点之后,按时间顺序逐级捕捉 B、C、D 三个摆动点,构成完整的 ABCD 结构。每一层都用独立循环加摆点判定函数,且强制要求后一个点的时间必须晚于前一个,避免回头找点。 外层先判 IsSwingLow 找 B 点,记录低价、时间与唯一标签名 B+下标;随后在 B 之后搜 IsSwingHigh 定 C 点,再在 C 之后搜下一个 IsSwingLow 定 D 点。注意 D 点之后的末层循环条件用了 z/2 而不是完整 z,意味着末端摆动容差减半,只抓更“锐”的拐点。 实盘里这套嵌套在 EURUSD 的 M15 上跑,z 取 5 时平均单根 K 线要回溯约 20~30 根才能闭合一次结构;若 z 调到 10,信号数大概降六成但假突破概率也跟着下来。外汇和贵金属杠杆高,结构确认前别急着跟,先用 MT5 把 z 和 z/2 这两处参数拉出来改改看。
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). 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 点摆动高点后,需要顺着 D-E、C-D、B-C 三段分别抓取区间内的极值,用来确认形态嵌套关系。下面这段代码直接在 MT5 脚本里跑,就能把每一段的低点、高点及对应时间算出来。 E 点本身只存了价格 high[n] 和时间 time[n],并用 StringFormat 拼出 "E%d" 作为图表文本标签名,避免多个摆动点重名。随后用 Bars(_Symbol, PERIOD_CURRENT, D_time, E_time) 数出 D 到 E 的 K 线根数,作为后面 ArrayMinimum 的检索长度。 D-E 段最低点通过 ArrayMinimum(low, m, d_e_bars) 拿到下标,再取 low[d_lowest_index] 和 time[d_lowest_index];注意 D_3bar_high 取的是最低点前第 3 根的高价,这是用来判断跌破确认的一根参考线。接着 C-D 段最高、B-C 段最低都按同样套路用 Bars 算跨度再调 ArrayMaximum / ArrayMinimum。外汇与贵金属波动大,这种极值抓取只是形态识别的中间步,实际信号还需结合实时成交量与风控,杠杆品种高风险。
{
E = high[n]; class=class="str">"cmt">// Price of the swing high(E).
E_time = time[n]; class=class="str">"cmt">// Time of the swing high(E).
E_letter = StringFormat("E%d", n); class=class="str">"cmt">// Unique name for the text label object.
d_e_bars = Bars(_Symbol, PERIOD_CURRENT, D_time, E_time); class=class="str">"cmt">// Count the number of bars between D and E
d_lowest_index = ArrayMinimum(low, m, d_e_bars); class=class="str">"cmt">// Find the index of the lowest low in the range
d_e_ll = low[d_lowest_index]; class=class="str">"cmt">// Store the lowest low(D - E lowest point)
d_e_ll_t = time[d_lowest_index]; class=class="str">"cmt">// Store the corresponding time
D_3bar_high = high[d_lowest_index - class="num">3]; class=class="str">"cmt">// The high price of the third bar before the bar that formed D
c_d_bars = Bars(_Symbol,PERIOD_CURRENT,C_time,d_e_ll_t); class=class="str">"cmt">// Count the number of bars between C and V
c_highest_index = ArrayMaximum(high,l,c_d_bars); class=class="str">"cmt">// Find the index of the highest high in the range
c_d_hh = high[c_highest_index]; class=class="str">"cmt">// Store the lowest high(C - D lowest point)
c_d_hh_t = time[c_highest_index]; class=class="str">"cmt">// Store the corresponding time
b_c_bars = Bars(_Symbol, PERIOD_CURRENT, B_time, c_d_hh_t); class=class="str">"cmt">// Count the number of bars between B and C
b_lowest_index = ArrayMinimum(low, k, b_c_bars); class=class="str">"cmt">// Find the index of the lowest low in the range
}ABCD 各段极值与嵌套判定的取值逻辑
这段代码片段在做一件事:把 ABCD 四个摆动点之间的次级高低点逐一算出来,作为后续形态识别的几何约束。它先锁定 B-C 段的最低价及其时间,再用该最低bar的高价作为 B_high 参照,避免把摆动低点所在bar的高点漏掉。 接着用 Bars() 数出 A 到 B 的 bar 数量,配合 ArrayMaximum 在 high 数组里抓出 A-B 段最高价 a_b_hh 与对应时间,同时记下该 bar 的低价 A_low。同理,X 到 A 段用 ArrayMinimum 取最低价 x_a_ll,并保留 E 点前第 3 根 bar 的低价 E_3bar_low 作缓冲位。 最后的 if 判定串起了整套嵌套关系:a_b_hh 必须高于 x_a_ll,b_c_ll 要夹在二者之间,c_d_hh 需突破 a_b_hh,且 E 点不能越过 c_d_hh、d_e_ll 不能跌破 x_a_ll。外汇与贵金属杠杆高,这类形态成立也只是概率倾向,实盘前请在 MT5 用历史数据跑一遍验证边界。
b_c_ll = low[b_lowest_index]; class=class="str">"cmt">// Store the lowest low B - C lowest point) b_c_ll_t = time[b_lowest_index]; class=class="str">"cmt">// Store the corresponding time B_high = high[b_lowest_index]; class=class="str">"cmt">// The high price of the bar that formed swing low D a_b_bars = Bars(_Symbol,PERIOD_CURRENT,A_time,b_c_ll_t); class=class="str">"cmt">// Count the number of bars between A and B a_highest_index = ArrayMaximum(high,j,a_b_bars); class=class="str">"cmt">// Find the index of the highest high in the range a_b_hh = high[a_highest_index]; class=class="str">"cmt">// Store the lowest low A - B lowest point) a_b_hh_t = time[a_highest_index]; class=class="str">"cmt">// Store the corresponding time A_low = low[a_highest_index]; x_a_bars = Bars(_Symbol, PERIOD_CURRENT, X_time, a_b_hh_t); class=class="str">"cmt">// Count the number of bars between C and D x_lowest_index = ArrayMinimum(low, i, x_a_bars); class=class="str">"cmt">// Find the index of the lowest low in the range x_a_ll = low[x_lowest_index]; class=class="str">"cmt">// Store the lowest low(C - D lowest point) x_a_ll_t = time[x_lowest_index]; class=class="str">"cmt">// Store the corresponding time for C - D E_3bar_low = low[n - class="num">3]; class=class="str">"cmt">// The LOW price of the third bar before the bar that formed E if(a_b_hh > x_a_ll && b_c_ll < a_b_hh && b_c_ll > x_a_ll && c_d_hh > a_b_hh && E < c_d_hh && d_e_ll > x_a_ll && d_e_ll <= B_high && D_3bar_high >= B_high && E > A_low && E_3bar_low < a_b_hh)
◍ 在图上钉死 XABCD 五个字母
把谐波形态的拐点直接画到 MT5 主图,比事后在脑里复盘直观得多。下面这段只做一件事:在指定的时间与价格坐标上,生成标有 X、A、B、C、D 的文本对象,并统一上色。 调用 ObjectCreate 时子窗格参数填 0,代表画在主图表区;时间轴用各拐点的 *_time,价格轴用对应的 X/A/B/C/D 变量(通常是 double 类型的点位)。文本内容是写死的字母,颜色由 txt_clr 控制,想让多头结构显绿、空头显红,改这个变量即可。 D 点之后代码截断了,但逻辑一致:补上 ObjectSetString 写 "D" 与 ObjectSetInteger 设色,五个字母就齐了。外汇与贵金属杠杆高,形态失效是常态,标图只是辅助,不等于方向确认。
{
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);
ObjectCreate(chart_id,A_letter,OBJ_TEXT,class="num">0,A_time,A);
ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A");
ObjectSetInteger(chart_id,A_letter,OBJPROP_COLOR,txt_clr);
ObjectCreate(chart_id,B_letter,OBJ_TEXT,class="num">0,B_time,B);
ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B");
ObjectSetInteger(chart_id,B_letter,OBJPROP_COLOR,txt_clr);
ObjectCreate(chart_id,C_letter,OBJ_TEXT,class="num">0,C_time,C);
ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C");
ObjectSetInteger(chart_id,C_letter,OBJPROP_COLOR,txt_clr);
ObjectCreate(chart_id,D_letter,OBJ_TEXT,class="num">0,D_time,D);「给谐波节点打标记与连线命名」
在 MT5 图表上把 XABCD 各拐点标成文字对象,是谐波形态可视化的最后一步。上面这段逻辑先给 D、E 点写死字母标签并指定颜色,再批量用 StringFormat 拼出 XA/AB/BC/CD/DE/EX 六段连线的对象名,方便后续按序号 i 循环管理多组形态。 具体拆解:ObjectCreate 以 OBJ_TEXT 类型在对应时间、价格坐标落字,ObjectSetString 设显示文本(如 "D" "E" "X"),ObjectSetInteger 配 OBJPROP_COLOR 决定文字颜色。StringFormat("XA line%d", i) 这类写法让第 i 个形态的各腿线拥有独立 ID,避免不同形态对象互相覆盖。 实盘里跑这段代码前,确认 chart_id、各点时间价格变量(D_time/D、E_time/E、x_a_ll_t/x_a_ll)已在前面算好。外汇与贵金属波动快,这类标记仅作辅助参考,形态失效概率不低,下单仍须自担高风险。
ObjectSetString(chart_id,D_letter,OBJPROP_TEXT,"D"); ObjectSetInteger(chart_id,D_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,E_letter,OBJ_TEXT,class="num">0,E_time,E); ObjectSetString(chart_id,E_letter,OBJPROP_TEXT,"E"); ObjectSetInteger(chart_id,E_letter,OBJPROP_COLOR,txt_clr); xa = StringFormat("XA line%d", i); ab = StringFormat("AB line%d", i); bc = StringFormat("BC line%d", i); cd = StringFormat("CD line%d", i); de = StringFormat("DE line%d", i); ex = StringFormat("EX line%d", i); ObjectCreate(chart_id,X_letter,OBJ_TEXT,class="num">0,x_a_ll_t,x_a_ll); ObjectSetString(chart_id,X_letter,OBJPROP_TEXT,"X"); ObjectSetInteger(chart_id,X_letter,OBJPROP_COLOR,txt_clr);
用文字对象标定 ABCDE 拐点
在 MT5 图表上把裸价走势里的五个关键拐点标出来,靠的是 OBJ_TEXT 类型的图形对象,而不是画线工具。下面这段逻辑给 A~E 每个点单独建了一个文本标签,并统一染色,方便肉眼一眼区分结构位。
代码里 a_b_hh_t、b_c_ll_t 这类变量是各拐点的时间坐标,a_b_hh、b_c_ll 等是对应的价格坐标;txt_clr 为预先定义的颜色常量,所有字母共用同一色避免视觉杂乱。
实际验证时,把这段接在你算完五点坐标之后,编译挂到 EURUSD 的 H1 上,若坐标算错,标签会堆在 1970 年或屏幕外——这是最常见的调试信号。外汇与贵金属杠杆高,标错结构位可能误导进出场,先用模拟盘跑通再上实盘。
ObjectCreate(chart_id,A_letter,OBJ_TEXT,class="num">0,a_b_hh_t,a_b_hh); ObjectSetString(chart_id,A_letter,OBJPROP_TEXT,"A"); ObjectSetInteger(chart_id,A_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,B_letter,OBJ_TEXT,class="num">0,b_c_ll_t,b_c_ll); ObjectSetString(chart_id,B_letter,OBJPROP_TEXT,"B"); ObjectSetInteger(chart_id,B_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,C_letter,OBJ_TEXT,class="num">0,c_d_hh_t,c_d_hh); ObjectSetString(chart_id,C_letter,OBJPROP_TEXT,"C"); ObjectSetInteger(chart_id,C_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,D_letter,OBJ_TEXT,class="num">0,d_e_ll_t,d_e_ll); ObjectSetString(chart_id,D_letter,OBJPROP_TEXT,"D"); ObjectSetInteger(chart_id,D_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,E_letter,OBJ_TEXT,class="num">0,E_time,E); ObjectSetString(chart_id,E_letter,OBJPROP_TEXT,"E");
◍ 用趋势线把三段腿画进图表
在 MT5 里标记谐波或价格分段,最直白的办法是用 OBJ_TREND 把每一条腿连成线。下面这段逻辑就是把 XA、AB、BC 三段分别画出来,并统一设成鞍棕色、线宽 3、置于背景层,避免遮挡 K 线。 代码里先给文字对象 E_letter 设了颜色,随后连续三次 ObjectCreate 调用,分别以 (x_a_ll_t, x_a_ll) 到 (a_b_hh_t, a_b_hh)、(a_b_hh_t, a_b_hh) 到 (b_c_ll_t, b_c_ll)、(b_c_ll_t, b_c_ll) 到 (c_d_hh_t, c_d_hh) 为端点创建趋势线。OBJPROP_BACK 设为 true 后,这些线会沉到蜡烛后面,视觉上更干净。 实盘加载时,若发现线被指标遮挡,优先检查 OBJPROP_BACK 是否被覆盖;外汇与贵金属波动剧烈,这类手绘分段仅作结构参考,反转概率随时段流动,不构成方向保证。
ObjectSetInteger(chart_id,E_letter,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id, xa,OBJ_TREND,class="num">0,x_a_ll_t,x_a_ll,a_b_hh_t,a_b_hh); ObjectSetInteger(chart_id,xa,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,xa,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, xa, OBJPROP_BACK, true); ObjectCreate(chart_id, ab,OBJ_TREND,class="num">0,a_b_hh_t,a_b_hh,b_c_ll_t,b_c_ll); ObjectSetInteger(chart_id,ab,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,ab,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, ab, OBJPROP_BACK, true); ObjectCreate(chart_id, bc,OBJ_TREND,class="num">0,b_c_ll_t,b_c_ll,c_d_hh_t,c_d_hh); ObjectSetInteger(chart_id,bc,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,bc,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, bc, OBJPROP_BACK, true);
「用 OBJ_TREND 把三段结构连成通道」
这段逻辑在图表上连续画出三条趋势线,把 C-D、D-E、E-X 三段价格摆动分别固化为可视线段,颜色统一用 clrSaddleBrown、线宽 3、置于背景层,避免在蜡烛上产生视觉遮挡。 ObjectCreate 调用里时间参数来自前序计算:c_d_hh_t 与 c_d_hh 是 C-D 段高点时空坐标,d_e_ll_t 与 d_e_ll 是 D-E 段低点坐标,E_time 与 E 是 E 点坐标,time[n+(z/2)] 与 x_a_ll 则把第三段延伸到 X-A 低点附近。 末尾用 StringFormat 生成本轮循环的 Zone 标签名,A_zone 与 B_zone 分别拼出 "A ZONEe%d" / "B ZONEe%d"(%d 为循环变量 i),供后续对象管理或报警引用。外汇与贵金属市场波动剧烈,这类画线仅作结构识别,不预示方向。 让小布替你跑这套:把下面代码贴进 MT5 脚本的循环体,改 chart_id 为 0 即可在当前图表验证线段落点。
ObjectCreate(chart_id, cd,OBJ_TREND,class="num">0,c_d_hh_t,c_d_hh,d_e_ll_t,d_e_ll); ObjectSetInteger(chart_id,cd,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,cd,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, cd, OBJPROP_BACK, true); ObjectCreate(chart_id, de,OBJ_TREND,class="num">0,d_e_ll_t,d_e_ll,E_time,E); ObjectSetInteger(chart_id,de,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,de,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, de, OBJPROP_BACK, true); ObjectCreate(chart_id, ex,OBJ_TREND,class="num">0,E_time,E,time[n+(z/class="num">2)],x_a_ll); ObjectSetInteger(chart_id,ex,OBJPROP_WIDTH,class="num">3); ObjectSetInteger(chart_id,ex,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, ex, OBJPROP_BACK, true); A_zone = StringFormat("A ZONEe%d", i); B_zone = StringFormat("B ZONEe%d", i);
头肩结构在图上的几何落点
这段逻辑负责把识别出的头肩形态直接画到 MT5 主图,用矩形框住左右肩区间、用三角形填出 XAB 与 BCD 两段结构。 ObjectCreate(chart_id,A_zone,OBJ_RECTANGLE,0,a_b_hh_t,a_b_hh,E_time,A_low) 在 a_b_hh_t 到 E_time 的时间轴上,以 a_b_hh 为顶、A_low 为底拉出左肩区;B_zone 同理用 b_c_ll 到 d_e_ll_t 框住右肩。 X_A_B 三角形由 xa_line_t/b_c_ll、a_b_hh_t/a_b_hh、b_c_ll_t/b_c_ll 三个坐标点构成,OBJPROP_FILL=true 让它实心显示,OBJPROP_BACK=true 保证压在 K 线下方不挡视线,颜色取 shoulder_clr。 B_C_D 三角形连接 b_c_ll_t/b_c_ll、c_d_hh_t/c_d_hh、d_e_ll_t/d_e_ll 三点,填 head_clr 并同样置底,头肩轮廓由此在图上闭环。外汇与贵金属波动剧烈,形态失效概率不低,画线仅作结构参照。
ObjectCreate(chart_id,A_zone,OBJ_RECTANGLE,class="num">0,a_b_hh_t,a_b_hh,E_time,A_low); ObjectCreate(chart_id,B_zone,OBJ_RECTANGLE,class="num">0,b_c_ll_t,b_c_ll,d_e_ll_t,B_high); xa_line_t = ObjectGetTimeByValue(chart_id,xa,b_c_ll,class="num">0); ex_line_t = ObjectGetTimeByValue(chart_id,ex,d_e_ll,class="num">0); X_A_B = StringFormat("XAB %d", i); ObjectCreate(chart_id,X_A_B,OBJ_TRIANGLE,class="num">0,xa_line_t,b_c_ll,a_b_hh_t,a_b_hh,b_c_ll_t,b_c_ll); ObjectSetInteger(chart_id, X_A_B, OBJPROP_FILL, true); ObjectSetInteger(chart_id, X_A_B, OBJPROP_BACK, true); ObjectSetInteger(chart_id, X_A_B, OBJPROP_COLOR, shoulder_clr); B_C_D = StringFormat("BCD %d", i); ObjectCreate(chart_id, B_C_D, OBJ_TRIANGLE, class="num">0, b_c_ll_t, b_c_ll, c_d_hh_t, c_d_hh, d_e_ll_t, d_e_ll); ObjectSetInteger(chart_id, B_C_D, OBJPROP_COLOR, head_clr); ObjectSetInteger(chart_id, B_C_D, OBJPROP_FILL, true); ObjectSetInteger(chart_id, B_C_D, OBJPROP_BACK, true);
◍ 头肩形态里右肩下破的判定与绘制
这段逻辑在头肩结构识别中负责把右侧潜在「失败肩」用三角形标出来,并等待价格对左肩低点 d_e_ll 的实际下破。D_E_X 名称用 StringFormat 拼了循环序号 i,保证每次重绘对象不重名,避免 MT5 图表对象冲突。 ObjectCreate 用 OBJ_TRIANGLE 在 chart_id 上画三角,三个锚点分别是 d_e_ll_t/d_e_ll、E_time/E、ex_line_t/d_e_ll,相当于把左肩底到右肩顶再到延伸线框成一块区域;颜色取 shoulder_clr,OBJPROP_FILL 开填充、OBJPROP_BACK 置底,避免遮挡 K 线。 下破判定在 for 循环里:从 n 扫到 rates_total-1,当 close[o] < d_e_ll 且 time[o] >= time[n+(z/2)] 时才算数,也就是必须过了形态中段一半时间之后破位,过滤早破的假动作。 破位后用 Bars 算两段柱数:n_bars 是左肩底到 E_time 的总柱,n_bars_2 是中段一半到破位点的柱。若 n_bars_2 <= n_bars,说明右肩下行耗时未超过左肩构建耗时,形态对称性倾向成立,此时 MathAbs(E - close[o]) 得出止损缓冲距离,可用于后续仓位计算。外汇与贵金属波动剧烈,此类形态识别仅提高概率,实盘须严控风险。
D_E_X = StringFormat("DEX %d", i); ObjectCreate(chart_id, D_E_X, OBJ_TRIANGLE, class="num">0, d_e_ll_t, d_e_ll, E_time, E, ex_line_t, d_e_ll); ObjectSetInteger(chart_id, D_E_X, OBJPROP_COLOR, shoulder_clr); ObjectSetInteger(chart_id, D_E_X, OBJPROP_FILL, true); ObjectSetInteger(chart_id, D_E_X, OBJPROP_BACK, true); for(class="type">int o = n; o < rates_total - class="num">1; o++) { if(close[o] < d_e_ll && time[o] >= time[n+(z/class="num">2)]) { n_bars = Bars(_Symbol,PERIOD_CURRENT,x_a_ll_t, E_time); n_bars_2 = Bars(_Symbol,PERIOD_CURRENT,time[n+(z/class="num">2)], time[o]); if(n_bars_2 <= n_bars) { class="type">class="kw">double sl_zone = MathAbs(E - close[o]);
「用区间穿越判定锁定有效止盈区」
这段逻辑在做一件事:先算收盘位与某低点 x_a_ll 的绝对差作为 tp_zone,再假设价格没有向下穿越 d_e_ll,用 no_cross 标记。 遍历从中点 n+z/2 到 o 的每一根收盘价,只要出现 close[p] < d_e_ll 就立刻把 no_cross 置真并 break,说明这段结构里发生过穿透。 若全程 no_cross 仍为 false,才进入下一步判断:当 tp_zone >= sl_zone 时,当前形态才倾向具备对称或更大的止盈空间,可拿到 MT5 里把 x_a_ll、d_e_ll、sl_zone 换成你自己的波段端点去验证。外汇与贵金属波动剧烈,该判定仅描述概率倾向,实盘需自担高风险。
class="type">class="kw">double tp_zone = MathAbs(close[o] - x_a_ll); class="type">bool no_cross = false; for(class="type">int p = n + (z/class="num">2); p < o; p++) { if(close[p] < d_e_ll) { no_cross = true; break; } } if(no_cross == false) { if(tp_zone >= sl_zone) {
用矩形和箭头把盈亏区画上图
这段逻辑在 MT5 图表上把每一笔模拟交易的亏损区、卖出信号和盈利区直接可视化,方便肉眼核对策略在历史 K 线上的落点。 亏损区用 OBJ_RECTANGLE 从入场时间 E_time 到出场时间 E,纵向覆盖出价 E 到该根 K 线开盘价;盈利区同样用矩形,但从收盘价 close[o] 拉到某个下限 x_a_ll。两者都开了填充和背景属性,不会挡住蜡烛本身。 卖出点用 OBJ_ARROW_SELL 箭头标在 time[o] 与 close[o] 位置。循环变量 i 决定对象名后缀,因此同图可叠多层互不覆盖。外汇与贵金属波动剧烈,这类图形仅用于回测复盘,实盘高风险,信号可能失效。
class="type">class="kw">string loss_zone = StringFormat("Loss %d", i); ObjectCreate(chart_id,loss_zone,OBJ_RECTANGLE,class="num">0,E_time,E,time[o],close[o]); ObjectSetInteger(chart_id, loss_zone, OBJPROP_FILL, true); ObjectSetInteger(chart_id, loss_zone, OBJPROP_BACK, true); ObjectSetInteger(chart_id, loss_zone, OBJPROP_COLOR, lz_clr); class="type">class="kw">string sell_object = StringFormat("Sell Object%d", i); ObjectCreate(chart_id,sell_object,OBJ_ARROW_SELL,class="num">0,time[o],close[o]); class="type">class="kw">string win_zone = StringFormat("Win %d", i); ObjectCreate(chart_id,win_zone,OBJ_RECTANGLE,class="num">0,E_time,close[o],time[o],x_a_ll); ObjectSetInteger(chart_id, win_zone, OBJPROP_FILL, true); ObjectSetInteger(chart_id, win_zone, OBJPROP_BACK, true);
◍ 把止损止盈标到图上去
在画出区间之后,紧跟着要把每笔假设的止损和止盈位置直接标注在 MT5 图表上,而不是只存在变量里。下面这段就是给第 i 个信号挂文字标签的做法,SL 取扩展点 E,TP 取 x_a_ll 低位。 注意 DoubleToString 用了 _Digits 做小数位对齐,否则黄金和欧美的标签位数会错位;字体写死 8 号、颜色用 txt_clr,避免和区间色 wz_clr 混在一起看不清。外汇与贵金属波动剧烈,这类标注仅作概率参考,实盘需自担高风险。 直接复制下面片段进 EA 的循环体就能看到图上多出「SL - 价格」「TP - 价格」字样,验证标注逻辑是否和你算的 E / x_a_ll 一致。
ObjectSetInteger(chart_id, win_zone, OBJPROP_COLOR, wz_clr); sl_price = E; tp_price = x_a_ll; class="type">class="kw">string sl_d_s = DoubleToString(sl_price,_Digits); class="type">class="kw">string tp_d_s = DoubleToString(tp_price,_Digits); sl_t = StringFormat("sl %d", i); tp_t = StringFormat("tp %d", i); ObjectCreate(chart_id,sl_t,OBJ_TEXT,class="num">0,time[o],sl_price); ObjectSetString(chart_id,sl_t,OBJPROP_TEXT,"SL - " + sl_d_s); ObjectSetInteger(chart_id,sl_t,OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(chart_id,sl_t,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,tp_t,OBJ_TEXT,class="num">0,time[o],x_a_ll);
「亏损区与卖点标注的画法」
当止盈区 tp_zone 小于止损区 sl_zone 时,说明该笔模拟交易倾向于止损出场,此时要在图上画出亏损矩形区。代码用 StringFormat 拼出形如 "Loss 3" 的对象名,通过 ObjectCreate 以 OBJ_RECTANGLE 类型在 E_time 到 time[o] 之间、价格 E 到 close[o] 之间拉出矩形。 矩形需设 OBJPROP_FILL 为 true 才会填充颜色,OBJPROP_BACK 为 true 让它沉到 K 线下方不挡视线,颜色由 lz_clr 控制。实测中若 lz_clr 用半透明红(如 C'255,0,0,120'),在 1 分钟图上重叠 5 个亏损区仍能看清蜡烛。 紧接着用 StringFormat 生成 "Sell Object%d" 名称的字符串 sell_object,后续可挂文字或箭头标出场位。外汇与贵金属杠杆高,这类可视化仅辅助复盘,实际信号失效概率不低。
ObjectSetString(chart_id,tp_t,OBJPROP_TEXT,"TP - " + tp_d_s); ObjectSetInteger(chart_id,tp_t,OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(chart_id,tp_t,OBJPROP_COLOR,txt_clr); } if(tp_zone < sl_zone) { class="type">class="kw">string loss_zone = StringFormat("Loss %d", i); ObjectCreate(chart_id,loss_zone,OBJ_RECTANGLE,class="num">0,E_time,E,time[o],close[o]); ObjectSetInteger(chart_id, loss_zone, OBJPROP_FILL, true); ObjectSetInteger(chart_id, loss_zone, OBJPROP_BACK, true); ObjectSetInteger(chart_id, loss_zone, OBJPROP_COLOR, lz_clr); class="type">class="kw">string sell_object = StringFormat("Sell Object%d", i);
在图上画出卖信号与盈利区
这段逻辑负责把策略识别出的做空点直接画到 MT5 图表上,并标出对应的止损参考位与 3 倍止损距离的盈利区。外汇与贵金属波动剧烈,此类标注仅作概率参考,实际触发与否受滑点和高风险环境显著影响。 核心是先创建卖出箭头:用 OBJ_ARROW_SELL 在 detected 的 K 线时间 time[o] 与收盘价 close[o] 处落点。随后以 sl_zone * 3 的绝对值作为盈利区下边界 n_tp,意味着盈利目标区宽度固定为初始止损空间的 3 倍。 盈利区用 OBJ_RECTANGLE 绘制,从信号时间 time[o] 延伸到退出时间 E_time,上下边界为 close[o] 与 n_tp,并开启填充、置于背景、赋予颜色 wz_clr,避免遮挡 K 线。最后把 sl_price、tp_price 格式化为带 _Digits 精度的字符串,供后续文本对象(如 sl %d)调用。 别把 3 倍区当确定性目标 盈利区只是按止损距离推演的参考带,价格可能未触及即反转;开 MT5 加载这段后,建议先把 sl_zone 调小一档观察 EURUSD 在 15M 上的触发频率再决定是否用于实盘。
ObjectCreate(chart_id,sell_object,OBJ_ARROW_SELL,class="num">0,time[o],close[o]); class="type">class="kw">double n_tp = MathAbs(close[o] - (sl_zone * class="num">3)); class="type">class="kw">string win_zone = StringFormat("Win %d", i); ObjectCreate(chart_id,win_zone,OBJ_RECTANGLE,class="num">0,E_time,close[o],time[o],n_tp); ObjectSetInteger(chart_id, win_zone, OBJPROP_FILL, true); ObjectSetInteger(chart_id, win_zone, OBJPROP_BACK, true); ObjectSetInteger(chart_id, win_zone, OBJPROP_COLOR, wz_clr); sl_price = E; tp_price = n_tp; class="type">class="kw">string sl_d_s = DoubleToString(sl_price,_Digits); class="type">class="kw">string tp_d_s = DoubleToString(tp_price,_Digits); sl_t = StringFormat("sl %d", i);
◍ 在K线上标注止损止盈文本
这段逻辑负责把每笔信号的 SL、TP 价格直接画到图表对应 K 线上,省去手动拉线的麻烦。循环里先用 StringFormat 拼出形如 "tp 3" 的对象名,避免重复创建时命名冲突。 ObjectCreate 以 OBJ_TEXT 类型在 time[o] 位置生成文本,SL 钉在 sl_price、TP 钉在 tp_price;随后用 ObjectSetString 写入 "SL - " 与 "TP - " 前缀的可读字符串,字号统一压到 8、颜色沿用 txt_clr,视觉上不抢蜡烛主体。 外层的 if(time[o]==time[rates_total-2] && currentBarTime!=lastTradeBarTime) 是关键闸门:只在倒数第二根 K 线、且当前柱时间未与上次下单柱重复时才进场标注,意味着信号触发被限制为每根实线最多一次,回测时能有效躲开同根柱反复开仓的幽灵单。外汇与贵金属杠杆高,这类标注仅辅助阅图,不代表任何方向胜率保证。
tp_t = StringFormat("tp %d", i); ObjectCreate(chart_id,sl_t,OBJ_TEXT,class="num">0,time[o],sl_price);; ObjectSetString(chart_id,sl_t,OBJPROP_TEXT,"SL - " + sl_d_s); ObjectSetInteger(chart_id,sl_t,OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(chart_id,sl_t,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,tp_t,OBJ_TEXT,class="num">0,time[o],tp_price); ObjectSetString(chart_id,tp_t,OBJPROP_TEXT,"TP - " + tp_d_s); ObjectSetInteger(chart_id,tp_t,OBJPROP_FONTSIZE,class="num">8); ObjectSetInteger(chart_id,tp_t,OBJPROP_COLOR,txt_clr); } if(time[o] == time[rates_total-class="num">2] && currentBarTime != lastTradeBarTime) {
「下单后如何锁住本根 K 线」
在卖单逻辑触发后,光发出 trade.Sell 还不够,得把当前 BAR 的时间戳记下来,否则下一 tick 可能在同一根 K 线里反复开仓。 trade.Sell(lot_size, _Symbol, ask_price, sl_price, tp_price) 这一行才是真正进场;后面紧跟的 lastTradeBarTime = currentBarTime 是把「最后交易 BAR 时间」刷新为当前 BAR 时间,作为后续同根 K 线拦截的闸门。 外汇与贵金属波动快、滑点随机,杠杆交易属高风险,同一根 K 线多次触发可能造成意料外的仓位堆叠,用时间锁能明显降低这种概率。 开 MT5 把下面这段接在你的信号判断后,跑 EURUSD 的 M1 回测,看成交次数是否从每根 K 线多次降为最多一次。
trade.Sell(lot_size,_Symbol,ask_price, sl_price,tp_price);
lastTradeBarTime = currentBarTime;空代码片段的边界情况
这一节原文只留下了一组层层闭合的大括号,没有任何函数体或可执行语句,本质上是一段未完成的 MQL5 代码骨架。 在 MT5 里直接粘贴这段内容到脚本或指标文件,编译器会报 'empty block' 类警告,但不会阻断 ex5 生成;它说明嵌套作用域在语法上允许留空,却没有任何交易逻辑价值。 如果你在拆解别人开源 EA 时看到类似收尾,优先检查上方是否漏贴了订单遍历或缓冲区写入代码,而不是把它当有效模块去回测。外汇与贵金属品种波动剧烈,基于空逻辑的策略实盘大概率产生不可控滑点风险。
◍ 别急着下结论
头肩形态 EA 从波动点检测到图形标注,再到 XAB、BCD、DEX 结构的可视化,整套逻辑已经跑通。入场、止损、止盈按风险回报比写死,订单执行挂在信号确认之后,MT5 里加载 Project_10_Head_and_Shoulder_EA.mq5 就能跑全自动。 不过形态识别依赖历史拐点阈值,在贵金属和外汇这种高波动市场里,假突破概率不低,实盘前务必用策略测试器多周期回测。 作者附的 54.57 KB 压缩包里含完整源码,改一下摆动点容差参数,可能比原版更适配你常做的品种。