MQL5 简介(第 16 部分):利用技术图表形态构建 EA 交易
概述
欢迎回到 MQL5 入门系列的第 16 部分!这一部分将特别有趣,因为我们将一如既往地使用基于项目的方法,在迄今为止所学的一切基础上继续发展。我们将共同完成一个结合技术分析形态和 MQL5 编码的实际项目,创建一个 EA 交易,通过实践经验帮助您加深技能。
我们将重点关注头肩形态 —— 一种用于识别潜在趋势反转的常用技术形态。我们的项目将被设计成一个 EA 交易,它可以自动识别这种形态并据此执行交易。此外,它还可以作为指标,在图表上直观地突出显示头肩形态,使您更容易在实际交易场景中发现和理解这种形态。
在本文中,你会学到:
- 自动化图表形态交易
- 如何识别头肩形态
- 以编程方式绘制波动点
- 在 MQL5 中使用图表对象
- 定义入场点、止损点和止盈点
- 避免信号重复
1.理解图表形态
价格图表上可以用来预测未来市场走势的视觉形态称为图表形态。这些趋势通常是判断趋势是会持续还是会逆转的指标,它们是买卖双方不断博弈的产物。例如,头肩形态通常预示着上升趋势可能转为下降趋势,而上升趋势中的旗形形态则通常意味着该趋势可能会继续下去。如果您能够识别三角形、矩形和双顶等图表形态,那么您根据过去的价格行为发现潜在交易机会的能力将会提高。
类比
就像沙滩上的脚印一样,图表形态是买卖双方持续冲突留下的明显线索。图表形态可以帮助你了解市场已经发生的事情,并预测接下来可能会发生的事情,就像动物的踪迹可以帮助经验丰富的追踪者确定哪只动物经过以及它要去哪里一样。价格图表上的这些图形形态显示了市场参与者的集体行为,并且经常表明当前的趋势是否可能继续或反转。
1.1.形态的类别
图表形态通常分为三大类:
1.1.1. 反转形态
图表形态中的反转形态可以用来发现可能的市场转折点。它们表明,一个新的趋势可能正朝着相反的方向开始,而现有的趋势可能正在结束。例如,如果市场处于下跌趋势,反转形态可能表明价格即将进入上涨趋势。同样,这些形态可能预示着市场上涨趋势可能会出现逆转。 头肩顶形态和双底形态是典型的反转形态。
类比
反转形态是市场方向可能发生转变的指标。这些形态表明,无论是上升趋势还是下降趋势,都可能正在减弱并准备逆转,就像沙滩上转弯的脚印表明有人改变了方向一样。它们通过指出买方或卖方何时开始发力,帮助交易者识别价格走势的潜在变化。

1.1.2. 持续形态
被称为持续形态的图表形态表明市场最有可能维持当前走势。它们通常在价格走势恢复之前的盘整或短暂停留时出现。例如,上升趋势中的持续形态可能表明,价格在小幅横盘整理后将继续上涨。在下跌趋势中,这意味着价格在暂停之后可能会继续下跌。矩形和旗形是典型的持续形态。
类比
与脚印类似,持续形态会短暂地停顿,但仍然指向同一方向。想象一下你正跟随一个人沿着海岸漫步,沿着连续的脚印,你会发现脚印会停顿片刻 —— 可能是为了系鞋带或环顾四周 —— 但随后它们会继续朝同一方向前进。这表明他们只是短暂地停顿了一下,并没有改变主意。 同样,持续形态表明市场暂时停滞,但可能会继续按照目前的轨迹发展。这些形态表明,无论市场是上涨还是下跌,动能都没有逆转,只是暂时停滞。

1.1.3.中性形态
图表上显示的盘整期形态,在此期间市场可能朝任何方向突破,被称为中性形态。由于双方都没有明显的优势,这些形态表明买卖双方之间处于平衡状态。因此,交易者被迫等待确认突破后才能采取行动,价格也在更窄的范围内波动。尽管他们无法预测突破的路径,但中性形态有助于交易者为可能的走势做好准备。中性形态很常见,例如对称三角形。
类比
在交易中,中性形态类似于观察沙滩上犹豫不决的人,不确定下一步该怎么做。中性形态,即买卖双方势均力敌的模式,反映了市场的犹豫不决,就像你无法预测他们的下一步行动,除非他们做出承诺。虽然没有明显的上涨或下跌趋势,但这些形态表明突破可能会朝任何方向发生。

2.设置项目
2.1.EA 的工作原理
本项目中的 EA 交易旨在自动识别市场中的头肩形态,并根据该结构执行交易。无论是标准的头肩顶形态还是反向头肩顶形态,EA 都能识别出有效的形态,选择最佳的入场点,并按照预期的突破方向执行交易。
但 EA 的行动远不止于此。为了使图表上的形态更加清晰易懂,我们还将使用图形对象来标记左肩、头部和右肩。这些视觉标记有助于交易者确认形态,并在查看图表或回测策略时提高清晰度。 除了自动化和形态识别之外,该方法还提供了一个可见的确认层,可以改进调试、学习,甚至实时监控。
2.1.1.买入逻辑
为了触发买入交易,EA 将通过依次检测六个特定的波动点来识别反向头肩形态:
- 波动高点 (X):初始高点,标记为 X。
- 波动低点 (A):X 之后的更低低点,标记为 A。
- 波动高点 (B):反弹形成一个比 X 低但比 A 高的峰值,标记为 B。
- 波动低点 (C):比 A 更低的低点,标记为 C —— 这就是头部。
- 波动高点 (D):与 B 大致处于同一水平的波动高点,标记为 D —— 这是颈线的一部分。
- 波动低点 (E):与 C 相比,低点更高,与 A 大致处于同一水平 —— 这是第二个肩部。
一旦这种结构到位:
- EA 等待烛形收盘价高于 D 点(颈线)。
- 当这种情况发生时,它将执行买入交易。
- 止损点 (SL) 将设置在 E 点的最低点。
- 止盈点 (TP) 最初将设定在 X 点(第一个高点)。
但是,如果入场点与目标价位 (X) 之间的距离小于止损距离的 1 倍,则 EA 将忽略 X 作为目标价位,而是根据止损距离设置固定的 1:3 风险回报目标。这确保了该策略保持最低的风险回报率,并避免进行不值得冒险的低回报交易。

2.1.1.卖出逻辑
为了触发卖出交易,EA 将通过依次检测六个特定的波动点来识别标准的头肩顶形态:
- 波动低点 (X):初始低点,标记为 X。
- 波动高点 (A):X 之后更高的峰值,标记为 A。
- 波动低点 (B):回调形成比 X 高但比 A 低的低点,标记为 B。
- 波动高点 (C):比 A 更高的高点,标记为 C —— 这就是头部。
- 波动低点 (D):在与 B 大致相同的高度处向下波动,标记为 D —— 这是颈线的一部分。
- 波动高点 (E):与 C 相比,高度较低,与 A 大致处于同一水平 —— 这是第二个肩部。
一旦确认了这种结构:
- EA 等待烛形收盘价低于 D 点(颈线)。
- 当这种情况发生时,它会执行卖出交易。
- 止损点 (SL) 设置在 E 点的最高点。
- 止盈点 (TP) 最初设定在 X 点(第一个低点)。
如果入场点到目标价位 (X) 的距离小于止损距离的 1 倍,则 EA 将 X 作为目标价位,并根据止损大小应用固定的 1:3 风险回报目标。

请注意: 本项目将探讨一种交易策略,其主要目标是提高您对 MQL5 编程理念的理解,特别是如何处理图表形态和创建有用的 EA 交易。它不适用于真实资金交易或实时交易。在将任何技术应用于真实市场之前,务必进行全面的回测,并向金融专家寻求建议。
3.识别图表上的头肩形态
到现在,我相信你已经牢牢掌握了图表形态的概念,并且确切地知道我们希望我们的 EA 交易执行什么操作。即使在 EA 进行交易之前,能够在图表上看到头肩形态也至关重要。这有助于在测试过程的早期发现任何运行时故障或逻辑问题,并确认 EA 逻辑与实际价格走势相符。
本章将介绍如何使用各种图表组件(包括趋势线、文本标签和形状)手动突出显示和验证图表上的头肩顶结构。在 EA 的后续阶段,这将有助于为自动化检测奠定坚实的基础。
3.1.获取烛形数据
第一步是获取烛形数据,在图表上找到头肩形态。这里包含了每根烛形的开盘价、最高价、最低价、成交时间和收盘价等详细信息。这些数值至关重要,因为价格在几根烛形中的波动方式决定了形态的结构。 然而,我们收集这些信息不仅仅是为了发现趋势。我们还希望它能起到指标的作用,因为我们想创建一个能够识别这些形态并自行进行交易的 EA 交易。这意味着它必须能够引起人们对图表上先前的头肩形态的注意,以便您可以再次检查和测试它们。
简而言之,我们正在开发的 EA 将有两个功能:它将是一个交易机器人,可以识别头肩形态并自动执行交易;它还将是一个形态指标,通过突出显示历史数据中的类似结构来进行判断。除了实现自动交易外,这种双重功能还使交易者能够直观地验证信号,并检查该形态在过去是如何发展和表现的。
示例:input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; // MA Time Frame input int bars_check = 1000; // Number of bars to check for swing points // Variable to store how many bars are available on the chart for the selected timeframe int rates_total; double open[]; // Array for opening prices double close[]; // Array for closing prices double low[]; // Array for lowest prices double high[]; // Array for highest prices datetime time[]; // Array for time (timestamps) of each bar //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- // Get the total number of bars available on the selected symbol and timeframe rates_total = Bars(_Symbol, timeframe); // Copy the open prices of the last 'rates_total' bars into the 'open' array CopyOpen(_Symbol, timeframe, 0, rates_total, open); // Copy the close prices of the last 'rates_total' bars into the 'close' array CopyClose(_Symbol, timeframe, 0, rates_total, close); // Copy the low prices of the last 'rates_total' bars into the 'low' array CopyLow(_Symbol, timeframe, 0, rates_total, low); // Copy the high prices of the last 'rates_total' bars into the 'high' array CopyHigh(_Symbol, timeframe, 0, rates_total, high); // Copy the time (timestamps) of the last 'rates_total' bars into the 'time' array CopyTime(_Symbol, timeframe, 0, rates_total, time); }
解释:
函数开始时有两个用户自定义的输入变量。第一个选项是时间周期,它允许用户选择 EA 交易将使用的精确时间段。PERIOD_CURRENT 是默认设置。这样的话,EA 将使用与其链接的图表相同的时间周期。得益于这种灵活性,您无需更改代码即可检查多个时间段。EA 通过第二个参数 bars_check 来指示在检查价格行为时要查看多少个历史烛形(或柱形)。由于本例中设置的数值为 1000,EA 将在最近 1000 根烛形中寻找可能的形态结构。
在输入定义之后,代码声明了一些数组和一个用于保存市场数据的变量。所选交易品种和时间周期内可用的柱形(烛形)总数将存储在 rates_total 变量中。每根烛形的相关价格数据存储在诸如 open[]、close[]、low[] 和 high[] 之类的数组中。我们还可以通过使用存储在 time[] 数组中的每个柱形的时间戳来确定每个柱形的精确时间。因为它们为 EA 提供了检查图表和发现头肩定等形态所需的信息,所以这些数组至关重要。
该项目需要我们使用 CopyOpen()、CopyHigh() 等函数手动复制柱形数据,因为它被设计成一个 EA 交易,而不是一个自定义指标。如果这是一个使用 OnCalculate() 方法的指标,那么这些信息已经通过函数参数自动提供,省去了我们复制的麻烦。
EA 的结构中还包含了三个常用的 MQL5 函数 —— OnInit()、OnDeinit() 和 OnTick()。当 EA 加载时,OnInit() 方法会被调用一次。它只是返回 INIT_SUCCEEDED,表示 EA 已准备好执行。OnDeinit() 函数还没有清理逻辑,当终端关闭或 EA 被移除时,会调用该函数。
OnTick() 函数会在每次价格更新(分时报价)时运行,实际操作就发生在这里。Bars() 函数首先确定该函数内部图表上当前可用的柱形数量。然后使用五个复制函数将价格和时间数据载入到相应的数组中:开盘价通过 CopyOpen() 填充到 open[] 数组中,收盘价通过 CopyClose() 填充到 close[] 数组中,最低价和最高价分别通过 CopyLow() 和 CopyHigh() 存储,每根烛形的时间戳通过 CopyTime() 存储。因为它准备了 EA 将用来寻找图表形态和检查市场活动的所有历史数据,所以这种设置至关重要。
3.2.识别波动
一旦我们成功收集了历史烛形数据,接下来就要找到构成头肩顶或反向头肩顶形态结构的重要波动点 —— X、A、B、C、D 和 E。EA 必须准确识别这些波动,才能确定合格的图表形态,这些形态是价格走势的转折点。EA 将通过检查选定数量的柱线中的高点和低点来识别主要反转,从而帮助它逐步绘制出形态。
示例://+------------------------------------------------------------------+ //| FUNCTION FOR SWING LOW | //+------------------------------------------------------------------+ bool IsSwingLow(const double &low_price[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(low_price[index] > low_price[index - i] || low_price[index] > low_price[index + i]) return false; } return true; } //+------------------------------------------------------------------+ //| FUNCTION FOR SWING HIGH | //+------------------------------------------------------------------+ bool IsSwingHigh(const double &high_price[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(high_price[index] < high_price[index - i] || high_price[index] < high_price[index + i]) return false; // If the current high is not the highest, return false. } return true; }
解释:
我们使用两个函数 IsSwingLow() 和 IsSwingHigh() 来确定价格波动点。这些特征决定了给定的烛形相对于其附近的烛形,是否形成波动高点或波动低点。根据 lookback 值,该函数保证在出现波动低点的情况下,当前烛形的最低价低于其前后预定数量的烛形的最低价。同样,对于波动高点,它验证当前烛形的最高价是否大于周围烛形的最高价。由于本系列第 14 部分已经详细介绍过这个思路,所以我们在这里就不多赘述了。
3.2.1.识别 XABCDE
文章强调了在买卖情况下,精确确定主要波动点(X、A、B、C 和 D)对于识别头肩形态的重要性。这些点代表着影响形态并指导 EA 交易行为的重要高点和低点。
示例:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- // Get the total number of bars available on the selected symbol and timeframe rates_total = Bars(_Symbol, timeframe); // Copy the open prices of the last 'rates_total' bars into the 'open' array CopyOpen(_Symbol, timeframe, 0, rates_total, open); // Copy the close prices of the last 'rates_total' bars into the 'close' array CopyClose(_Symbol, timeframe, 0, rates_total, close); // Copy the low prices of the last 'rates_total' bars into the 'low' array CopyLow(_Symbol, timeframe, 0, rates_total, low); // Copy the high prices of the last 'rates_total' bars into the 'high' array CopyHigh(_Symbol, timeframe, 0, rates_total, high); // Copy the time (timestamps) of the last 'rates_total' bars into the 'time' array CopyTime(_Symbol, timeframe, 0, rates_total, time); //FOR SELL if(show_sell) { if(rates_total >= bars_check) { for(int z = 7; z <= 10; z++) { for(int i = rates_total - bars_check; i < rates_total - z; i++) { if(IsSwingLow(low, i, z)) { // If a swing low is found, store its price, time, and create a name for the objects to mark the X. X = low[i]; // Price of the swing low (X). X_time = time[i]; // Time of the swing low (X). X_letter = StringFormat("X%d", i); // Unique name for the text label object. for(int j = i; j < rates_total - z; j++) { if(IsSwingHigh(high, j, z) && time[j] > X_time) { A = high[j]; // Price of the swing high (A). A_time = time[j]; // Time of the swing high (A) A_letter = StringFormat("A%d", j); // Unique name for the text label object for(int k = j; k < rates_total - z; k++) { if(IsSwingLow(low, k, z) && time[k] > A_time) { B = low[k]; // Price of the swing low (B). B_time = time[k]; // Time of the swing low (B). B_letter = StringFormat("B%d", k); // Unique name for the text label object. for(int l = k ; l < rates_total - z; l++) { if(IsSwingHigh(high, l, z) && time[l] > B_time) { C = high[l]; // Price of the swing high (C). C_time = time[l]; // Time of the swing high (C). C_letter = StringFormat("C%d", l); // Unique name for the text label object. for(int m = l; m < rates_total - z; m++) { if(IsSwingLow(low, m, z) && time[m] > C_time) { D = low[m]; // Price of the swing low (D). D_time = time[m]; // Time of the swing low (D). D_letter = StringFormat("D%d", m); // Unique name for the text label object. for(int n = m ; n < rates_total - (z/2) - 1; n++) { if(IsSwingHigh(high, n, (z/2)) && time[n] > D_time) { E = high[n]; // Price of the swing low (B). E_time = time[n]; // Time of the swing low (B). E_letter = StringFormat("E%d", n); // Unique name for the text label object. break; } } break; } } break; } } break; } } break; } } } } } } } }解释:
//X double X; // Price of the swing low (X). datetime X_time; // Time of the swing low (X). string X_letter; // Unique name for the text label object. //A double A; // Price of the swing high (A). datetime A_time; // Time of the swing high (A). string A_letter; // Unique name for the text label object. //B double B; // Price of the swing low (B). datetime B_time; // Time of the swing low (B). string B_letter; // Unique name for the text label object. //C double C; // Price of the swing low (B). datetime C_time; // Time of the swing low (B). string C_letter; // Unique name for the text label object. //D double D; // Price of the swing low (B). datetime D_time; // Time of the swing low (B). string D_letter; // Unique name for the text label object. //E double E; // Price of the swing low (B). datetime E_time; // Time of the swing low (B). string E_letter; // Unique name for the text label object.
这段代码使用了三种不同的变量来表示该形态的每个主要波动点,分别是 X、A、B、C、D 和 E。每个波动点的精确价格水平首先存储在一个双精度变量中。双 A;标有 A 的波动价格较高,而双 X;标有 X 的波动价格较低。每个波动点除了记录价格外,还使用 datetime 变量来记录其发生的时间。这使得 EA 能够准确地按时间顺序排列图表上的波动点。例如,datetime X_time; 记录波动低点 X 的时间,datetime A_time; 记录波动高点 A 的时间。
最后,使用字符串变量为每个波动点生成一个独特的标签名称。为了直观地指示每个波动点的位置,这些标签(例如 X_letter 或 A_letter)用于在图表上构建文本对象。EA 能够更好地组织和显示这些重要的点,以便交易者能够通过这种标记系统注意到正在形成的形态。 价格、时间和标签是 EA 用来排列每个波动点的三个信息,以便它能够正确识别并在图表上以图形方式描绘头肩形态。形态识别和易于交易者理解的视觉提示的放置都依赖于这种系统的方法。
if(show_sell) { if(rates_total >= bars_check) { for(int z = 7; z <= 10; z++) { for(int i = rates_total - bars_check; i < rates_total - z; i++) { if(IsSwingLow(low, i, z)) { // If a swing low is found, store its price, time, and create a name for the objects to mark the X. X = low[i]; // Price of the swing low (X). X_time = time[i]; // Time of the swing low (X). X_letter = StringFormat("X%d", i); // Unique name for the text label object. for(int j = i; j < rates_total - z; j++) { if(IsSwingHigh(high, j, z) && time[j] > X_time) { A = high[j]; // Price of the swing high (A). A_time = time[j]; // Time of the swing high (A) A_letter = StringFormat("A%d", j); // Unique name for the text label object for(int k = j; k < rates_total - z; k++) { if(IsSwingLow(low, k, z) && time[k] > A_time) { B = low[k]; // Price of the swing low (B). B_time = time[k]; // Time of the swing low (B). B_letter = StringFormat("B%d", k); // Unique name for the text label object. for(int l = k ; l < rates_total - z; l++) { if(IsSwingHigh(high, l, z) && time[l] > B_time) { C = high[l]; // Price of the swing high (C). C_time = time[l]; // Time of the swing high (C). C_letter = StringFormat("C%d", l); // Unique name for the text label object. for(int m = l; m < rates_total - z; m++) { if(IsSwingLow(low, m, z) && time[m] > C_time) { D = low[m]; // Price of the swing low (D). D_time = time[m]; // Time of the swing low (D). D_letter = StringFormat("D%d", m); // Unique name for the text label object. for(int n = m ; n < rates_total - (z/2) - 1; n++) { if(IsSwingHigh(high, n, (z/2)) && time[n] > D_time) { E = high[n]; // Price of the swing low (B). E_time = time[n]; // Time of the swing low (B). E_letter = StringFormat("E%d", n); // Unique name for the text label object. 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); ObjectCreate(chart_id,A_letter,OBJ_TEXT,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,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,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,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,0,E_time,E); ObjectSetString(chart_id,E_letter,OBJPROP_TEXT,"E"); ObjectSetInteger(chart_id,E_letter,OBJPROP_COLOR,txt_clr); break; } } break; } } break; } } break; } } break; } } } } } } }
输出:

解释:
为了确定程序现在是否配置为检测卖出模式,代码首先确定变量 show_sell 是否为 true。当不需要进行交易形态检测时,这个简单的条件门可以停止无意义的计算。接下来,它会检查图表中的柱形总数(rates_total)是否超过或等于 bars_check 最小阈值。这保证了有足够的先前数据来进行准确的形态识别。
变量 z 通常用作波动点检测的窗口大小或回溯时间,在最外层循环中,该变量会在有限的值范围内迭代。该技术旨在通过在 z = 7 到 z = 10 之间操作多个回溯周期来试验,从而提高波动检测的灵活性和精确度。该循环使算法能够以略微不同的分辨率或灵敏度寻找形态。
从 rates_total - bars_check 到 rates_total - z,接下来的 for 循环遍历图表上的最新柱形。该范围通过将搜索区域限制在最近的柱形中,着重寻找与当前价格走势更相关的可能形态。本循环中使用函数 IsSwingLow(low, i, z) 来搜索波动低点,并根据回溯窗口 z 确定位置 i 处的柱是否为局部最小值。
当在位置 i 检测到波动低点时,代码使用 StringFormat("X%d", i) 生成一个名为 X_letter 的唯一标签字符串,将其价格存储在变量 X 中,将其时间戳存储在 X_time 中。借助此标签,EA 可以更轻松地在图表上突出显示此点,并对其进行唯一识别,以便稍后进行处理。头肩顶形态以 X 点为参考点。
为了确定时间 X 之后发生的下一个波动高点 A,代码随后开始另一个嵌套循环,从 i 开始。为了保持形态所需的时间顺序,它使用 IsSwingHigh(high, j, z) 来确定 j 处的柱形是否为波动高点,并确保其时间 time[j] 严格大于 X_time。如果发现合格的高点,则 A 的价格、时间和标签的记录方式与 X 类似。
通过这种嵌套循环结构,可以按顺序识别出以下几个点:B、C、D 和 E。通过遍历柱形,可以找到每个波动点,验证它是否是合格的波动低点或高点,并且它的时间戳是否严格大于前一个点的时间戳。该代码会为每个已确认的波动创建一个独特的标签字符串,并保存价格和持续时间。通过这种严格的顺序检查,可以保持形态点的正确顺序。
最内层的循环使用稍窄的回溯窗口(z/2)来寻找点 E,即该形态的最后一个波动高点。该算法可以利用这种差异来调整最后一个点的检测灵敏度。当找到 E 时,代码会立即退出循环,以避免不必要的额外搜索,从而提高效率。然后它会设定价格、时间和标签。
得益于嵌套循环和时间检查,整个过程中可以按正确的波动点顺序精确地检测到头肩形态。使用“X%d”或“A%d”等不同的标签,可以让 EA 生成和控制图表上的文本或图形元素,从而为交易者提供形态的视觉表示。得益于这种有条不紊、结构化的方法,计算机可以可靠地识别复杂的价格走势形态,用于交易决策。
该代码示例的目的是识别识别头肩形态所必需的六个关键波动点 —— X、A、B、C、D 和 E。然而,目前的实现方式尚未应用验证头肩顶结构所需的精确逻辑约束;相反,它只是根据价格波动及其时间顺序来确定这些波动点。该算法收集这些点的价格、时间和标签,但它不会验证它们是否真的构成了独特的形态。
我们将使用几个重要的基于价格结构的标准来验证头肩形态卖出形态的有效性。A 点必须高于 X 点,B 点必须位于中间(高于 X 点但低于 A 点),C 点(头部)必须高于 B 点。右肩的峰值由 E 点与 A 点重合构成,而右肩的谷底则由 D 点靠近 B 点构成。此外,在发出任何交易信号之前,必须通过观察这些点之间的一系列高点和低点来验证特定形态的结构。
示例:
input color txt_clr = clrBlue; // Texts color //X double X; // Price of the swing low (X). datetime X_time; // Time of the swing low (X). string X_letter; // Unique name for the text label object. int x_a_bars; int x_lowest_index; double x_a_ll; datetime x_a_ll_t; //A double A; // Price of the swing high (A). datetime A_time; // Time of the swing high (A). string A_letter; // Unique name for the text label object. int a_b_bars; int a_highest_index; double a_b_hh; datetime a_b_hh_t; string A_zone; double A_low; //B double B; // Price of the swing low (B). datetime B_time; // Time of the swing low (B). string B_letter; // Unique name for the text label object. int b_c_bars; int b_lowest_index; double b_c_ll; datetime b_c_ll_t; string B_zone; double B_high; //C double C; // Price of the swing low (B). datetime C_time; // Time of the swing low (B). string C_letter; // Unique name for the text label object. int c_d_bars; int c_highest_index; double c_d_hh; datetime c_d_hh_t; //D double D; // Price of the swing low (B). datetime D_time; // Time of the swing low (B). string D_letter; // Unique name for the text label object. int d_e_bars; int d_lowest_index; double d_e_ll; datetime d_e_ll_t; double D_3bar_high; //E double E; // Price of the swing low (B). datetime E_time; // Time of the swing low (B). string E_letter; // Unique name for the text label object. double E_3bar_low; string xa; // Unique name for the trendline for XA. string ab; // Unique name for the trendline for AB. string bc; // Unique name for the trendline for BC. string cd; // Unique name for the trendline for CD. string de; // Unique name for the trendline for DE. string ex; // Unique name for the trendline for EX. //FOR SELL if(show_sell) { if(rates_total >= bars_check) { for(int z = 7; z <= 10; z++) { for(int i = rates_total - bars_check; i < rates_total - z; i++) { if(IsSwingLow(low, i, z)) { // If a swing low is found, store its price, time, and create a name for the objects to mark the X. X = low[i]; // Price of the swing low (X). X_time = time[i]; // Time of the swing low (X). X_letter = StringFormat("X%d", i); // Unique name for the text label object. for(int j = i; j < rates_total - z; j++) { if(IsSwingHigh(high, j, z) && time[j] > X_time) { A = high[j]; // Price of the swing high (A). A_time = time[j]; // Time of the swing high (A) A_letter = StringFormat("A%d", j); // Unique name for the text label object for(int k = j; k < rates_total - z; k++) { if(IsSwingLow(low, k, z) && time[k] > A_time) { B = low[k]; // Price of the swing low (B). B_time = time[k]; // Time of the swing low (B). B_letter = StringFormat("B%d", k); // Unique name for the text label object. for(int l = k ; l < rates_total - z; l++) { if(IsSwingHigh(high, l, z) && time[l] > B_time) { C = high[l]; // Price of the swing high (C). C_time = time[l]; // Time of the swing high (C). C_letter = StringFormat("C%d", l); // Unique name for the text label object. for(int m = l; m < rates_total - z; m++) { if(IsSwingLow(low, m, z) && time[m] > C_time) { D = low[m]; // Price of the swing low (D). D_time = time[m]; // Time of the swing low (D). D_letter = StringFormat("D%d", m); // Unique name for the text label object. for(int n = m ; n < rates_total - (z/2) - 1; n++) { if(IsSwingHigh(high, n, (z/2)) && time[n] > D_time) { E = high[n]; // Price of the swing high (E). E_time = time[n]; // Time of the swing high (E). E_letter = StringFormat("E%d", n); // Unique name for the text label object. d_e_bars = Bars(_Symbol, PERIOD_CURRENT, D_time, E_time); // Count the number of bars between D and E d_lowest_index = ArrayMinimum(low, m, d_e_bars); // Find the index of the lowest low in the range d_e_ll = low[d_lowest_index]; // Store the lowest low (D - E lowest point) d_e_ll_t = time[d_lowest_index]; // Store the corresponding time D_3bar_high = high[d_lowest_index - 3]; // 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); // Count the number of bars between C and V c_highest_index = ArrayMaximum(high,l,c_d_bars); // Find the index of the highest high in the range c_d_hh = high[c_highest_index]; // Store the lowest high (C - D lowest point) c_d_hh_t = time[c_highest_index]; // Store the corresponding time b_c_bars = Bars(_Symbol, PERIOD_CURRENT, B_time, c_d_hh_t); // Count the number of bars between B and C b_lowest_index = ArrayMinimum(low, k, b_c_bars); // Find the index of the lowest low in the range b_c_ll = low[b_lowest_index]; // Store the lowest low B - C lowest point) b_c_ll_t = time[b_lowest_index]; // Store the corresponding time B_high = high[b_lowest_index]; // The high price of the bar that formed swing low D a_b_bars = Bars(_Symbol,PERIOD_CURRENT,A_time,b_c_ll_t); // Count the number of bars between A and B a_highest_index = ArrayMaximum(high,j,a_b_bars); // Find the index of the highest high in the range a_b_hh = high[a_highest_index]; // Store the lowest low A - B lowest point) a_b_hh_t = time[a_highest_index]; // Store the corresponding time A_low = low[a_highest_index]; x_a_bars = Bars(_Symbol, PERIOD_CURRENT, X_time, a_b_hh_t); // Count the number of bars between C and D x_lowest_index = ArrayMinimum(low, i, x_a_bars); // Find the index of the lowest low in the range x_a_ll = low[x_lowest_index]; // Store the lowest low (C - D lowest point) x_a_ll_t = time[x_lowest_index]; // Store the corresponding time for C - D E_3bar_low = low[n - 3]; // 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,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,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,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,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,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,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,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,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,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,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,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,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,0,x_a_ll_t,x_a_ll,a_b_hh_t,a_b_hh); ObjectSetInteger(chart_id,xa,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,xa,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, xa, OBJPROP_BACK, true); ObjectCreate(chart_id, ab,OBJ_TREND,0,a_b_hh_t,a_b_hh,b_c_ll_t,b_c_ll); ObjectSetInteger(chart_id,ab,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,ab,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, ab, OBJPROP_BACK, true); ObjectCreate(chart_id, bc,OBJ_TREND,0,b_c_ll_t,b_c_ll,c_d_hh_t,c_d_hh); ObjectSetInteger(chart_id,bc,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,bc,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, bc, OBJPROP_BACK, true); ObjectCreate(chart_id, cd,OBJ_TREND,0,c_d_hh_t,c_d_hh,d_e_ll_t,d_e_ll); ObjectSetInteger(chart_id,cd,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,cd,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, cd, OBJPROP_BACK, true); ObjectCreate(chart_id, de,OBJ_TREND,0,d_e_ll_t,d_e_ll,E_time,E); ObjectSetInteger(chart_id,de,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,de,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, de, OBJPROP_BACK, true); ObjectCreate(chart_id, ex,OBJ_TREND,0,E_time,E,time[n+(z/2)],x_a_ll); ObjectSetInteger(chart_id,ex,OBJPROP_WIDTH,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,0,a_b_hh_t,a_b_hh,E_time,A_low); ObjectCreate(chart_id,B_zone,OBJ_RECTANGLE,0,b_c_ll_t,b_c_ll,d_e_ll_t,B_high); } break; } } break; } } break; } } break; } } break; } } } } } } }
输出:

解释:
该代码包含变量声明,用于存储有关价格行为中自定义 XABCDE 形态的结构和组成部分的重要信息。每个变量在识别和可视化构成该形态的波动高点和低点,以及在图表上标记和绘制结构方面都发挥着特定的作用。 第一组变量与从点 X 到 A 的线段相关。x_a_bars 存储这两点之间的柱线数量。x_lowest_index 存储该线段中最低价柱形的索引,x_a_ll 保存实际的最低价(“更低的低点”),而 x_a_ll_t 保存该低点出现的相应时间。
从 A 到 B 的过程由以下组负责。本部分中的柱形数量为 a_b_bars。最高柱形的索引由 a_highest_index 标识,而实际最高价格或“更高高点”存储在 a_b_hh 中,a_b_hh_t 记录其发生的时间。这些结果支持了 B 点是更高低点,A 点是波动高点的观点。 变量 b_c_bars、b_lowest_index、b_c_ll 和 b_c_ll_t 分别记录从 B 到 C 段的柱形数量、最低索引、最低价格和该价格出现的时间。这用于验证该结构随后出现的更低低点。
字符串变量 A_zone 和 B_zone 可能用于存储不同的对象名称,以便创建靠近 A 点和 B 点的矩形区域。价格水平 A_low 和 B_high 作为图表上的视觉提示或决策区域,标示出这些区域的下限和上限。与之前的变量类似,d_e_bars、d_lowest_index、d_e_ll 和 d_e_ll_t 存储从 D 到 E 段的柱数、索引、价格和时间数据,有助于定位形态的最后一段。
D_3bar_high 和 E_3bar_low 用于存储点 D 附近的最高价和点 E 附近的最低价,通常根据 3 柱形结构计算得出。这些有助于验证波动点的真实性并减少误报。 最后,将在形态的每个转折点之间创建的趋势线的字符串 ID(从 X 到 A,从 A 到 B,依此类推)是变量 xa、ab、bc、cd、de 和 ex。这些字符串保证每个趋势线对象都有一个独特的名称,从而能够在图表上精确、和谐地图形化地描绘整个形态。
通过分析六个关键位置(分别用字母 X、A、B、C、D 和 E 表示)之间的价格走势,我们假设这些点对应于形成特定结构关系的波动高点和低点。脚本的第一步是找到点 D 和点 E 之间的最低点 (d_e_ll)。然后,它获取该低点之前三个柱形的参考高点(D_3bar_high)。同样地,它找到了点 C 和此 d_e_ll 之间的最高点 (c_d_hh)。然后,它收集所有相关的波动高点和低点及其对应的时间戳,并反向重复此过程直至 X 点。通过这种方式,该代码利用柱形分析构建整个 XABCDE 波动结构。
通过 Bars() 函数计算两个时间戳之间的烛形数量。使用 ArrayMaximum() 和 ArrayMinimum(),可以帮助分离波动点之间的特定范围,以便代码可以检查其中的高点和低点。为了找到最高价或最低价,这些函数会从给定的偏移量(i、j、k 等)扫描特定数量的柱形。这有助于确定波动点。例如,ArrayMaximum(high, l, c_d_bars) 可以找到 C 和 D 之间的最高点,从而得到点 C。通过在整个结构中重复此推理,即可得到点 X。
通过评估一系列条件来验证各点之间的结构关系。这些标准比较各段的最高点和最低点;例如,它确定 A 点是否高于 X 点,B 点是否低于 A 点但仍然高于 X 点,C 点是否高于 A 点,等等。在绘制价格走势图之前,这可以确认它是否真的形成了预期的形态。E > A_low 和 E_3bar_low < a_b_hh 是保证 E 侧相对于结构其余部分处于合格位置的比较示例。
该方法使用 ObjectCreate() 在图表上以图形方式指定识别出的点,并添加文本标签(“X”、“A”、“B”等)和连接它们的趋势线,前提是该形态满足所有要求。这些趋势线使用 OBJ_TREND 对象绘制形态的 XA、AB、BC、CD、DE 和 EX 边,并使用 OBJ_TEXT 对象表示点。通过颜色、线宽和视觉分层(通过 OBJPROP_BACK)增强可读性。
最后,代码用矩形突出显示了 A 区和 B 区这两个关键的价格区域。A 区从 A 点的高点延伸到 E 点的低点,而 B 区从 B 点的低点延伸到 D 点的高点。在制定交易决策(例如入场点、止损点或止盈点)时,这些矩形通常被用作视觉参考,以识别特定区域内的反应或汇合点。借助此显示,交易者可以更容易地理解程序自动发现的复杂形态。
3.2.2.利用三角形突出显示形态结构
在找到并标记所有重要的波动点(即 X、A、B、C、D 和 E)之后,下一步是绘制三角形来突出形态的结构,尤其是在出现头肩顶等形态时。这些三角形将指示构成每种形态“头部”和“肩部”的峰值和谷值,而不是跟踪整个价格走势。头肩形态中,线条不低于颈线的图形可以用 XAB 三角形来表示,该三角形连接 X、A 和 B。
连接 B、C 和 D 的 BCD 三角形强调了结构的第二波,是该方法的下一步。DEX 三角形连接 D、E 和 X,使图形更加完整。这些三角形作为一种视觉辅助工具,能够帮助交易者更快地识别关键转折点和形态几何结构,而无需在图表上堆砌过多线形。
示例:
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; // MA Time Frame input int bars_check = 1000; // Number of bars to check for swing points input bool show_sell = true; // Display sell signals input bool show_buy = true; // Display buy signals input color txt_clr = clrBlue; // Texts color input color head_clr = clrCornflowerBlue; // Head color input color shoulder_clr = clrLightSeaGreen; // Shoulder 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,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,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,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,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,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,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,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,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,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,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,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,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,0,x_a_ll_t,x_a_ll,a_b_hh_t,a_b_hh); ObjectSetInteger(chart_id,xa,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,xa,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, xa, OBJPROP_BACK, true); ObjectCreate(chart_id, ab,OBJ_TREND,0,a_b_hh_t,a_b_hh,b_c_ll_t,b_c_ll); ObjectSetInteger(chart_id,ab,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,ab,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, ab, OBJPROP_BACK, true); ObjectCreate(chart_id, bc,OBJ_TREND,0,b_c_ll_t,b_c_ll,c_d_hh_t,c_d_hh); ObjectSetInteger(chart_id,bc,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,bc,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, bc, OBJPROP_BACK, true); ObjectCreate(chart_id, cd,OBJ_TREND,0,c_d_hh_t,c_d_hh,d_e_ll_t,d_e_ll); ObjectSetInteger(chart_id,cd,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,cd,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, cd, OBJPROP_BACK, true); ObjectCreate(chart_id, de,OBJ_TREND,0,d_e_ll_t,d_e_ll,E_time,E); ObjectSetInteger(chart_id,de,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,de,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, de, OBJPROP_BACK, true); ObjectCreate(chart_id, ex,OBJ_TREND,0,E_time,E,time[n+(z/2)],x_a_ll); ObjectSetInteger(chart_id,ex,OBJPROP_WIDTH,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,0,a_b_hh_t,a_b_hh,E_time,A_low); ObjectCreate(chart_id,B_zone,OBJ_RECTANGLE,0,b_c_ll_t,b_c_ll,d_e_ll_t,B_high); xa_line_t = ObjectGetTimeByValue(chart_id,xa,b_c_ll,0); ex_line_t = ObjectGetTimeByValue(chart_id,ex,d_e_ll,0); X_A_B = StringFormat("XAB %d", i); ObjectCreate(chart_id,X_A_B,OBJ_TRIANGLE,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, 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, 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); }
输出:

解释:
这段代码主要介绍如何使用三角形和矩形在 MetaTrader 5 图表上以图形方式描绘形态结构,特别是 XAB、BCD 和 DEX。为了保持将要生成的三角形对象的动态名称,声明了三个字符串变量 —— X_A_B、B_C_D 和 D_E_X。使用两个日期时间变量(xa_line_t 和 ex_line_t)检索特定时间坐标,将三角形形状的部分锚定在图表上的适当位置。
为了提高交易者的理解能力,代码首先建立了两个矩形区域,分别称为“A ZONE”和“B ZONE”。这些矩形突出显示了价格结构的重要区域,它们是使用 ObjectCreate() 创建的。A 区延伸至 E 点,从 A 点和 B 点之间的高点 (a_b_hh) 延伸至 A 点的低点 (A_low)。类似地,B 区从 B 点和 C 点之间的低点 (b_c_ll) 延伸到最高点 (B_high),结束于 D 到 E 段的时间坐标。
以下部分调用 ObjectGetTimeByValue() 来确定锚定 XAB 和 DEX 三角形部分所需的时间坐标。该函数通过搜索对象旅程中的特定价格值并返回匹配的时间,确保视觉标记与其旨在突出显示的价格水平适当匹配。
然后,每个三角形都被描绘成一个单独的形态边。虽然它没有连接整个边缘,但 XAB 三角形表示第一个波动结构。为了防止视觉混乱并专注于结构,它只将注意力吸引到类似肩部的重要转折点上。与此类似,BCD 三角形连接低点 (B)、峰值 (C) 和回撤点 (D),强调了该形态的主要头部。DEX 三角形描绘了反映原始肩部的折返动作,最终完成了整个框架。
OBJPROP_FILL 和 OBJPROP_BACK 等属性用于填充所有三个三角形的颜色,并将它们放置在其他图表元素的后面。由于采用了颜色,交易者可以更容易地一眼识别出这种形态,这些颜色保存在 shoulder_clr 和 head_clr 等变量中。为了处理和识别同一图表上的许多此类形态实例,每个项目的名称中都包含索引 i。
3.2.3.标示入场点、止损点和止盈点
下一阶段是在用三角形标出 XAB、BCD 和 DEX 结构,并强调头肩形状之后,指定交易参数,包括入场点、止损 (SL) 和止盈 (TP)。这些组成部分对于将已识别的形态转化为全面的交易策略是必不可少的。 当烛形收盘价低于 D 点时,即可入场。这表明价格可能已经拒绝了颈线区域,并正在朝着该形态预测的方向移动。现在这笔交易被认为是合适的,可以执行。
止损点设置在 E 点上方。如果形态失效,价格意外反转,将止损点放在这里有助于保护交易,因为 E 是形态完成前最近的高点。此外,它还保持了设置的合理性,只有当形态被违反时才会使交易无效。 由于 X 点(即该形态的起源点)是一个可靠的参考水平,价格之前曾在此反转,因此止盈点最初就设在那里。但是,如果从入场点到 X 的距离没有产生至少 1:1 的风险回报比 (RRR),则目标价位 (TP) 将进一步延长至至少 1:2。任何可持续交易策略的关键要素都是确保交易具有与风险金额相匹配的有利回报潜力。
示例:int n_bars; int n_bars_2; string sl_t; string tp_t; double sl_price; double tp_price;
for(int o = n; o < rates_total - 1; o++) { if(close[o] < d_e_ll && time[o] >= time[n+(z/2)]) { n_bars = Bars(_Symbol,PERIOD_CURRENT,x_a_ll_t, E_time); n_bars_2 = Bars(_Symbol,PERIOD_CURRENT,time[n+(z/2)], time[o]); if(n_bars_2 <= n_bars) { double sl_zone = MathAbs(E - close[o]); double tp_zone = MathAbs(close[o] - x_a_ll); bool no_cross = false; for(int p = n + (z/2); p < o; p++) { if(close[p] < d_e_ll) { no_cross = true; break; } } if(no_cross == false) { if(tp_zone >= sl_zone) { string loss_zone = StringFormat("Loss %d", i); ObjectCreate(chart_id,loss_zone,OBJ_RECTANGLE,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); string sell_object = StringFormat("Sell Object%d", i); ObjectCreate(chart_id,sell_object,OBJ_ARROW_SELL,0,time[o],close[o]); string win_zone = StringFormat("Win %d", i); ObjectCreate(chart_id,win_zone,OBJ_RECTANGLE,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; string sl_d_s = DoubleToString(sl_price,_Digits); 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,0,time[o],sl_price); ObjectSetString(chart_id,sl_t,OBJPROP_TEXT,"SL - " + sl_d_s); ObjectSetInteger(chart_id,sl_t,OBJPROP_FONTSIZE,8); ObjectSetInteger(chart_id,sl_t,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,tp_t,OBJ_TEXT,0,time[o],x_a_ll); ObjectSetString(chart_id,tp_t,OBJPROP_TEXT,"TP - " + tp_d_s); ObjectSetInteger(chart_id,tp_t,OBJPROP_FONTSIZE,8); ObjectSetInteger(chart_id,tp_t,OBJPROP_COLOR,txt_clr); } if(tp_zone < sl_zone) { string loss_zone = StringFormat("Loss %d", i); ObjectCreate(chart_id,loss_zone,OBJ_RECTANGLE,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); string sell_object = StringFormat("Sell Object%d", i); ObjectCreate(chart_id,sell_object,OBJ_ARROW_SELL,0,time[o],close[o]); double n_tp = MathAbs(close[o] - (sl_zone * 2)); string win_zone = StringFormat("Win %d", i); ObjectCreate(chart_id,win_zone,OBJ_RECTANGLE,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; string sl_d_s = DoubleToString(sl_price,_Digits); 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,0,time[o],sl_price);; ObjectSetString(chart_id,sl_t,OBJPROP_TEXT,"SL - " + sl_d_s); ObjectSetInteger(chart_id,sl_t,OBJPROP_FONTSIZE,8); ObjectSetInteger(chart_id,sl_t,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,tp_t,OBJ_TEXT,0,time[o],tp_price); ObjectSetString(chart_id,tp_t,OBJPROP_TEXT,"TP - " + tp_d_s); ObjectSetInteger(chart_id,tp_t,OBJPROP_FONTSIZE,8); ObjectSetInteger(chart_id,tp_t,OBJPROP_COLOR,txt_clr); } } } break; } }
输出:

解释:
该代码区域的逻辑是围绕着使用前面提到的头肩结构来确定合格的交易环境而展开的。然后利用图形对象在图表上以图形方式指示入场点、止损 (SL) 和止盈 (TP)。 使用两个整数变量 n_bars 和 n_bars_2 计算并比较重要点之间的烛形数量。这有助于判断当前价格走势是否仍在设置的可接受范围内。sl_price 和 tp_price 分别存储各自的价格水平,而 sl_t 和 tp_t 字符串用于动态命名 SL 和 TP 文本对象。
从形态的完成点 (n) 开始,主要逻辑在一个循环中遍历柱形。如果收盘价低于颈线水平(D 点),则表示可能的入场点,条件为 if (close[o] < d_e_ll && time[o] >= time[n+(z/2)])。如果满足此要求,则确定点 X 和 E 之间的柱数 (n_bars) 以及形态的中点和当前烛形之间的柱数 (n_bars_2)。这样做可以保证交易在合理的时间范围内有效。
然后,该代码将入场价格分别与点 E 和 X 进行比较,以确定可能的止损 (sl_zone) 和止盈 (tp_zone) 的金额。为了排除此入场点为初始突破点的可能性,它还会执行一个循环,以确保在当前烛形之前没有先前的烛形收盘价低于 D 点。如果检查成功,脚本将继续运行。
为了在图表上直观地显示止损和止盈区域,如果潜在回报 (TP) 大于或等于风险 (SL),则代码使用 OBJ_RECTANGLE 生成图形矩形。此外,入场点附近还有一个卖出箭头(OBJ_ARROW_SELL)。sl_price 和 tp_price 变量包含实际的 SL 和 TP 价格值。使用 OBJ_TEXT 创建标签,通过颜色和字体选择在图表上突出显示这些水平。 为了提供至少 1:2 的更有利的 RRR,如果风险回报比小于 1:1(即,TP 小于 SL),脚本会通过计算一个新的 TP 区域来调整,该区域是 SL 距离的三倍。然后,对于这种替代配置,创建并修改相同的图形对象。
4.根据形态执行交易
这部分的目标不仅仅是识别和展示交易情况。本节主要讲解如何根据已识别的模式实际执行交易,而前一部分则侧重于在图表上标记入场点、止损点 (SL) 和止盈点 (TP)。当满足条件时,EA 应该自动发起交易,例如当价格收盘价低于 D 点时,即可进行卖出交易。止盈点应设置在 X 点,或根据至少 1:2 的风险回报比进行调整,止损点应设置在 E 点。通过这一步骤,EA 就从一个可视化工具转变为一个完全自动化的系统,无需人工干预即可进场和管理交易。
示例:
#include <Trade/Trade.mqh> CTrade trade; int MagicNumber = 5122025; datetime lastTradeBarTime = 0; double ask_price; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- trade.SetExpertMagicNumber(MagicNumber); //--- return(INIT_SUCCEEDED); }
ask_price = ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); datetime currentBarTime = iTime(_Symbol, timeframe, 0); //FOR SELL if(show_sell) { if(rates_total >= bars_check) { for(int z = 7; z <= 10; z++) { for(int i = rates_total - bars_check; i < rates_total - z; i++) { if(IsSwingLow(low, i, z)) { // If a swing low is found, store its price, time, and create a name for the objects to mark the X. X = low[i]; // Price of the swing low (X). X_time = time[i]; // Time of the swing low (X). X_letter = StringFormat("X%d", i); // Unique name for the text label object. for(int j = i; j < rates_total - z; j++) { if(IsSwingHigh(high, j, z) && time[j] > X_time) { A = high[j]; // Price of the swing high (A). A_time = time[j]; // Time of the swing high (A) A_letter = StringFormat("A%d", j); // Unique name for the text label object for(int k = j; k < rates_total - z; k++) { if(IsSwingLow(low, k, z) && time[k] > A_time) { B = low[k]; // Price of the swing low (B). B_time = time[k]; // Time of the swing low (B). B_letter = StringFormat("B%d", k); // Unique name for the text label object. for(int l = k ; l < rates_total - z; l++) { if(IsSwingHigh(high, l, z) && time[l] > B_time) { C = high[l]; // Price of the swing high (C). C_time = time[l]; // Time of the swing high (C). C_letter = StringFormat("C%d", l); // Unique name for the text label object. for(int m = l; m < rates_total - z; m++) { if(IsSwingLow(low, m, z) && time[m] > C_time) { D = low[m]; // Price of the swing low (D). D_time = time[m]; // Time of the swing low (D). D_letter = StringFormat("D%d", m); // Unique name for the text label object. for(int n = m ; n < rates_total - (z/2) - 1; n++) { if(IsSwingHigh(high, n, (z/2)) && time[n] > D_time) { E = high[n]; // Price of the swing high (E). E_time = time[n]; // Time of the swing high (E). E_letter = StringFormat("E%d", n); // Unique name for the text label object. d_e_bars = Bars(_Symbol, PERIOD_CURRENT, D_time, E_time); // Count the number of bars between D and E d_lowest_index = ArrayMinimum(low, m, d_e_bars); // Find the index of the lowest low in the range d_e_ll = low[d_lowest_index]; // Store the lowest low (D - E lowest point) d_e_ll_t = time[d_lowest_index]; // Store the corresponding time D_3bar_high = high[d_lowest_index - 3]; // 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); // Count the number of bars between C and V c_highest_index = ArrayMaximum(high,l,c_d_bars); // Find the index of the highest high in the range c_d_hh = high[c_highest_index]; // Store the lowest high (C - D lowest point) c_d_hh_t = time[c_highest_index]; // Store the corresponding time b_c_bars = Bars(_Symbol, PERIOD_CURRENT, B_time, c_d_hh_t); // Count the number of bars between B and C b_lowest_index = ArrayMinimum(low, k, b_c_bars); // Find the index of the lowest low in the range b_c_ll = low[b_lowest_index]; // Store the lowest low B - C lowest point) b_c_ll_t = time[b_lowest_index]; // Store the corresponding time B_high = high[b_lowest_index]; // The high price of the bar that formed swing low D a_b_bars = Bars(_Symbol,PERIOD_CURRENT,A_time,b_c_ll_t); // Count the number of bars between A and B a_highest_index = ArrayMaximum(high,j,a_b_bars); // Find the index of the highest high in the range a_b_hh = high[a_highest_index]; // Store the lowest low A - B lowest point) a_b_hh_t = time[a_highest_index]; // Store the corresponding time A_low = low[a_highest_index]; x_a_bars = Bars(_Symbol, PERIOD_CURRENT, X_time, a_b_hh_t); // Count the number of bars between C and D x_lowest_index = ArrayMinimum(low, i, x_a_bars); // Find the index of the lowest low in the range x_a_ll = low[x_lowest_index]; // Store the lowest low (C - D lowest point) x_a_ll_t = time[x_lowest_index]; // Store the corresponding time for C - D E_3bar_low = low[n - 3]; // 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,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,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,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,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,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,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,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,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,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,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,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,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,0,x_a_ll_t,x_a_ll,a_b_hh_t,a_b_hh); ObjectSetInteger(chart_id,xa,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,xa,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, xa, OBJPROP_BACK, true); ObjectCreate(chart_id, ab,OBJ_TREND,0,a_b_hh_t,a_b_hh,b_c_ll_t,b_c_ll); ObjectSetInteger(chart_id,ab,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,ab,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, ab, OBJPROP_BACK, true); ObjectCreate(chart_id, bc,OBJ_TREND,0,b_c_ll_t,b_c_ll,c_d_hh_t,c_d_hh); ObjectSetInteger(chart_id,bc,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,bc,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, bc, OBJPROP_BACK, true); ObjectCreate(chart_id, cd,OBJ_TREND,0,c_d_hh_t,c_d_hh,d_e_ll_t,d_e_ll); ObjectSetInteger(chart_id,cd,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,cd,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, cd, OBJPROP_BACK, true); ObjectCreate(chart_id, de,OBJ_TREND,0,d_e_ll_t,d_e_ll,E_time,E); ObjectSetInteger(chart_id,de,OBJPROP_WIDTH,3); ObjectSetInteger(chart_id,de,OBJPROP_COLOR,clrSaddleBrown); ObjectSetInteger(chart_id, de, OBJPROP_BACK, true); ObjectCreate(chart_id, ex,OBJ_TREND,0,E_time,E,time[n+(z/2)],x_a_ll); ObjectSetInteger(chart_id,ex,OBJPROP_WIDTH,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,0,a_b_hh_t,a_b_hh,E_time,A_low); ObjectCreate(chart_id,B_zone,OBJ_RECTANGLE,0,b_c_ll_t,b_c_ll,d_e_ll_t,B_high); xa_line_t = ObjectGetTimeByValue(chart_id,xa,b_c_ll,0); ex_line_t = ObjectGetTimeByValue(chart_id,ex,d_e_ll,0); X_A_B = StringFormat("XAB %d", i); ObjectCreate(chart_id,X_A_B,OBJ_TRIANGLE,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, 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, 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(int o = n; o < rates_total - 1; o++) { if(close[o] < d_e_ll && time[o] >= time[n+(z/2)]) { n_bars = Bars(_Symbol,PERIOD_CURRENT,x_a_ll_t, E_time); n_bars_2 = Bars(_Symbol,PERIOD_CURRENT,time[n+(z/2)], time[o]); if(n_bars_2 <= n_bars) { double sl_zone = MathAbs(E - close[o]); double tp_zone = MathAbs(close[o] - x_a_ll); bool no_cross = false; for(int p = n + (z/2); p < o; p++) { if(close[p] < d_e_ll) { no_cross = true; break; } } if(no_cross == false) { if(tp_zone >= sl_zone) { string loss_zone = StringFormat("Loss %d", i); ObjectCreate(chart_id,loss_zone,OBJ_RECTANGLE,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); string sell_object = StringFormat("Sell Object%d", i); ObjectCreate(chart_id,sell_object,OBJ_ARROW_SELL,0,time[o],close[o]); string win_zone = StringFormat("Win %d", i); ObjectCreate(chart_id,win_zone,OBJ_RECTANGLE,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; string sl_d_s = DoubleToString(sl_price,_Digits); 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,0,time[o],sl_price); ObjectSetString(chart_id,sl_t,OBJPROP_TEXT,"SL - " + sl_d_s); ObjectSetInteger(chart_id,sl_t,OBJPROP_FONTSIZE,8); ObjectSetInteger(chart_id,sl_t,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,tp_t,OBJ_TEXT,0,time[o],x_a_ll); ObjectSetString(chart_id,tp_t,OBJPROP_TEXT,"TP - " + tp_d_s); ObjectSetInteger(chart_id,tp_t,OBJPROP_FONTSIZE,8); ObjectSetInteger(chart_id,tp_t,OBJPROP_COLOR,txt_clr); } if(tp_zone < sl_zone) { string loss_zone = StringFormat("Loss %d", i); ObjectCreate(chart_id,loss_zone,OBJ_RECTANGLE,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); string sell_object = StringFormat("Sell Object%d", i); ObjectCreate(chart_id,sell_object,OBJ_ARROW_SELL,0,time[o],close[o]); double n_tp = MathAbs(close[o] - (sl_zone * 3)); string win_zone = StringFormat("Win %d", i); ObjectCreate(chart_id,win_zone,OBJ_RECTANGLE,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; string sl_d_s = DoubleToString(sl_price,_Digits); 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,0,time[o],sl_price);; ObjectSetString(chart_id,sl_t,OBJPROP_TEXT,"SL - " + sl_d_s); ObjectSetInteger(chart_id,sl_t,OBJPROP_FONTSIZE,8); ObjectSetInteger(chart_id,sl_t,OBJPROP_COLOR,txt_clr); ObjectCreate(chart_id,tp_t,OBJ_TEXT,0,time[o],tp_price); ObjectSetString(chart_id,tp_t,OBJPROP_TEXT,"TP - " + tp_d_s); ObjectSetInteger(chart_id,tp_t,OBJPROP_FONTSIZE,8); ObjectSetInteger(chart_id,tp_t,OBJPROP_COLOR,txt_clr); } if(time[o] == time[rates_total-2] && currentBarTime != lastTradeBarTime) { trade.Sell(lot_size,_Symbol,ask_price, sl_price,tp_price); lastTradeBarTime = currentBarTime; } } } break; } } } break; } } break; } } break; } } break; } } break; } } } } } } }
输出:

解释:
该代码块使用 MQL5 内置的 CTrade 类来管理交易执行,从而简化了包括下单、编辑和关闭订单在内的交易任务。交易库(提供对 CTrade 类的访问)通过 #include <Trade/Trade.mqh> 包含在顶部。接下来,使用 CTrade trade; 创建此类的一个实例,并使用 int MagicNumber = 5122025; 定义此 EA 交易执行的交易的唯一标识。由于 MagicNumber 有助于区分此 EA 执行的交易与手动或其他 EA 执行的交易,因此它至关重要。Trade 使用 SetExpertMagicNumber 方法设置其 MagicNumber。
SymbolInfoDouble(_Symbol, SYMBOL_ASK); 用于用交易品种的当前卖价初始化变量 ask_price。将按此价格执行卖出指令。然后,代码使用 time[o] == time[rates_total-2] 来确定当前柱是否为最新柱。此外,条件 currentBarTime!= lastTradeBarTime 用于确认当前柱形上尚未进行任何交易。由于这项检查,EA 无法在同一柱形下多个订单。
如果两个条件都满足,EA 会使用该交易发出卖单。Sell() 函数。Ask_price 是入场价格,sl_price 是止损价位,tp_price 是止盈价位,lot_size 是要交易的手数,_Symbol 是当前交易品种(例如 EURUSD)。EA 在交易成功执行后,将 currentBarTime 的值添加到 lastTradeBarTime 变量中。此次更新至关重要,因为它确保每个合格的形态指示只进行一次交易,从而阻止 EA 基于同一根柱形进行多次交易。
必须记住,用于识别和显示卖出设置的相同推理方法,只需在每个阶段执行相反的操作,即可同样用于识别和显示买入设置。买入策略是等待烛形收盘价高于颈线(D 点),而不是低于颈线。之后,止盈点将设在最高点(X),止损点将设在最低点(E)。风险回报推理、视觉线索和条件都相同;它们只是针对看涨结构进行了镜像处理。帖子中将包含完整的源代码,供任何想要进一步查看或修改的人使用,该源代码将完整地实现这一逻辑。
结论
在本文中,我们探讨了如何在 MQL5 中构建一个 EA 交易,该 EA 可以根据技术图表形态(特别是头肩形态)进行识别和交易。我们首先检测并标记重要的波动点,然后使用三角形和矩形等图形工具来直观地表示 XAB、BCD 和 DEX 结构。接下来,我们明确定义了交易参数,包括入场点、止损点和止盈点,同时确保风险回报比符合逻辑标准。最后,我们实现了基于已确认信号下达实际订单的交易执行逻辑,使 EA 完全自动化。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18147
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
MQL5交易工具(第二部分):为交互式交易助手添加动态视觉反馈
您应当知道的 MQL5 向导技术(第 56 部分):比尔·威廉姆斯(Bill Williams)分形
MQL5开发专属调试与性能分析工具(第一部分):高级日志记录
MQL5 中的高级订单执行算法:TWAP、VWAP 和冰山订单
谢谢你,这就是我一直期待你的文章的原因。很有解释力!
你太棒了,以色列!我经常将您的文章正式翻译成西班牙语,每次都很高兴能参与其中。
,请继续努力!❤️
你太棒了,以色列!我经常将您的文章正式翻译成西班牙语,每次都很高兴能参与其中。
,继续保持出色的工作!❤️
你好,米格尔。
谢谢您的赞誉,这对我意义重大❤️
我不是程序员,所以理解起来要花点时间,但它的分解方式和一步一步的过程给了我动力。💯
你好,丹尼尔斯。
很高兴听到你这么说!❤️