MQL5 简介(第 15 部分):构建自定义指标的初学者指南(四)
概述
欢迎回到 MQL5 入门系列! 你会发现,本文将直接基于我们在之前的文章中已经讨论过的想法和技术。由于我们将使用到目前为止学到的大量知识,这一部分实际上看起来更像是一个延续,而不是一个新的开始。到目前为止,您应该已经对 MQL5 基础知识有了扎实的了解,在本文中,我们将进一步结合这些知识来开发更有趣的自定义指标。
你的 MQL5 水平取决于你用它完成的项目,因此本系列课程始终采用基于项目的方法。这是学习和自我提升最有效的方法。在本系列文章的这一部分中,我们将创建一个指标,该指标可以识别趋势、利用结构突破并生成买卖信号。这些信号包含了入场点、止损点和多个止盈点,为您提供了一个可以测试和改进的综合策略。在本文中,您将学习如何使用价格行为概念在 MQL5 中设计自定义指标。要创建趋势跟踪策略,你将学习如何识别重要的市场结构,如更高的高点、更高的低点,以及更低的高点、更低的低点。
在本文中,您将学习:
- 如何创建价格行为指标。
- 识别低点(L)、高点(H)、更高的低点(HL)、更高的高点(HH)、更低的低点(LL)和更低的高点(LH)等重要点,以了解看涨和看跌趋势的结构。
- 根据关键趋势点绘制溢价和折价区域,并标出 50% 回撤位。
- 如何在看涨趋势设置中应用风险回报比来计算潜在利润目标。
- 根据趋势结构计算并标记入场点、止损点 (SL) 和多个止盈点 (TP)。
1.设置项目
1.1.指标的工作原理
该指标将识别出低点、高点、更高的低点和更高的高点,以指示买入信号的上升趋势。然后确定更高低点和更高高点之间的 50% 回撤位。如果价格突破更高的高点结构,则可入场,50% 回撤位将作为止损位(SL)。TP1 的目标是 1:1 的风险回报比,TP2 的目标是 1:2 的风险回报比。

为了识别下跌趋势的卖出信号,该指标首先会识别出高点、低点、更低的高点和更低的低点。然后它会计算出更低高点和更低低点之间的 50% 回撤位。TP1 为 1:1,TP2 为 1:2,止损位设在 50% 水平,入场点设在跌破更低低点时。

2.构建价格行为指标
每一种交易策略都可以转化为指标 —— 只是还没有被可视化而已。任何符合一套准则的内容都可以进行编码并在图表上显示,无论是供需关系、价格走势还是支撑位和阻力位。这就是 MQL5 的用武之地。对于算法交易者来说,它是最伟大、最直接的编程语言之一,使你能够将任何交易逻辑转换成一个有用、视觉上有吸引力的工具。本节我们将开始开发一个指标,该指标分析价格走势,识别市场结构,例如高点、低点、更高的高点和更低的低点,然后利用这些数据生成有洞察力的买卖信号,包括入场点、止损和止盈水平。
在第一章中,我概述了该项目的目的,以及该指标如何发现趋势、发现结构性突破,并生成包括入场、止损和止盈在内的完整交易信号。本章我们将开始把 MQL5 中的所有内容付诸实践。我们将运用讨论过的逻辑,开始一步一步地用代码来实现它。
2.1.识别高点和低点
寻找波动高点和低点是创建价格行动指标的第一阶段。这些重大的市场转折时刻有助于确定趋势结构。通过比较当前烛形的最高价或最低价与前一根和后一根烛形的最高价或最低价,我们可以用 MQL5 来识别它们。检测更高的高点、更高的低点、更低的高点和更低的低点 —— 所有这些对于识别形态和结构突破都至关重要 —— 都将基于此。
示例://+------------------------------------------------------------------+ //| FUNCTION FOR SWING LOWS | //+------------------------------------------------------------------+ bool IsSwingLow(const double &low[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(low[index] > low[index - i] || low[index] > low[index + i]) return false; } return true; } //+------------------------------------------------------------------+ //| FUNCTION FOR SWING HIGHS | //+------------------------------------------------------------------+ bool IsSwingHigh(const double &high[], int index, int lookback) { for(int i = 1; i <= lookback; i++) { if(high[index] < high[index - i] || high[index] < high[index + i]) return false; } return true; }
前一篇文章中使用了这两个函数,它们有助于识别价格波动的高点和低点。
2.2.看涨趋势
利用市场结构,该指标必须首先验证是否存在上升趋势,然后才能发出买入信号。要实现这一目标,必须确定一个低点、一个高点、一个更高的低点和一个更高的高点。这种模式表明市场处于看涨趋势,意味着买方占据主导地位,市场可能会继续上涨。一旦确认此形态,指标将准备发出有效的买入信号。

示例:
// CHART ID long chart_id = ChartID(); // Input parameters input int LookbackBars = 10; // Number of bars to look back/forward for swing points input int bars_check = 1000; // Number of bars to check for swing points input bool show_bullish = true; //Show Buy Signals // Variables for Bullish Market Structure double L; // Low: the starting low point in the up trend datetime L_time; // Time of the low string L_letter; // Label for the low point (e.g., "L") double H; // High: the first high after the low datetime H_time; // Time of the high string H_letter; // Label for the high point (e.g., "H") double HL; // Higher Low: the next low that is higher than the first low datetime HL_time; // Time of the higher low string HL_letter; // Label for the higher low point (e.g., "HL") double HH; // Higher High: the next high that is higher than the first high datetime HH_time; // Time of the higher high string HH_letter; // Label for the higher high point (e.g., "HH") //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(chart_id); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- if(show_bullish == true) // Check if the bullish trend is to be displayed { if(rates_total >= bars_check) // Ensure enough bars are available for analysis { // Loop through the price data starting from a certain point based on bars_check and LookbackBars for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++) { // Check if the current bar is a swing low if(IsSwingLow(low, i, LookbackBars)) { // Store the values for the swing low L = low[i]; L_time = time[i]; L_letter = StringFormat("Low%d", i); // Loop through further to find a swing high after the low for(int j = i; j < rates_total - LookbackBars; j++) { // Check if the current bar is a swing high and occurs after the identified swing low if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time) { // Store the values for the swing high H = high[j]; H_time = time[j]; H_letter = StringFormat("High%d", j); // Loop further to find a higher low after the swing high for(int k = j; k < rates_total - LookbackBars; k++) { // Check if the current bar is a swing low and occurs after the swing high if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time) { // Store the values for the higher low HL = low[k]; HL_time = time[k]; HL_letter = StringFormat("Higher Low%d", j); // Loop further to find a higher high after the higher low for(int l = j ; l < rates_total - LookbackBars; l++) { // Check if the current bar is a swing high and occurs after the higher low if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time) { // Store the values for the higher high HH = high[l]; HH_time = time[l]; HH_letter = StringFormat("Higher High%d", l); // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High if(L < H && HL < H && HL > L && HH > H) { // Create and display text objects for Low, High, Higher Low, and Higher High on the chart ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L); ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L"); ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H); ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H"); ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL); ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL"); ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH); ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH"); ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15); } break; // Exit the loop once the pattern is found } } break; // Exit the loop once the higher low is found } } break; // Exit the loop once the higher high is found } } } } } } //--- return value of prev_calculated for next call return(rates_total); }
输出:

解释:
代码开头设置的输入参数用于检查市场结构。为了确定波动高点或低点并保证与当前市场的相关性,LookbackBars 指定了在当前柱形之前和之后评估的柱形的数量。同时,bars_check 控制要检查的价格数据柱形的数量,使脚本能够搜索多达 1000 个柱,以寻找可能的看涨形态。
虽然这也需要更多的计算能力,但 bars_check 值越大,表示该算法在搜索这些位置时将考虑更广泛的数据范围。一个名为 show_bullish 的布尔输入控制是否显示买入信号(或看涨信号)。如果 show_bullish 设置为 true,则应用程序将继续分析价格走势,并根据波动点确定牛市市场结构。即使满足要求,如果将其设置为 false,脚本也不会绘制或突出显示任何看涨结构。
根据指标的逻辑,首先执行的操作是检查 show_bullish == true。这样就能保证只有当你渴望看到买入信号时,才能识别出看涨结构。然后,程序会检查是否有足够的价格数据可用于分析,以及是否满足相关标准。使用条件 if (rates_total >= bars_check) 来验证这一点。为防止因数据不足而导致的错误,如果可用柱形数量少于所需数量,则跳过分析。然后,脚本会循环遍历价格数据,如果满足条件,则找出价格波动点。
外层循环从 i 开始,它通过从最近的柱形向后扫描价格数据来寻找合格的波动低点。IsSwingLow() 函数确定当前柱是否为 LookbackBars 指定范围内的最低点,从而识别波动低点。一旦识别出波动低点,就会将低点的价格和时间记录到变量 L 和 L_time 中。这为后续的看涨形态发现过程奠定了基础。 在确定了波动低点之后,程序会寻找另一个波动高点。使用 IsSwingHigh(),第二个循环(索引为 j)在每个后续柱中查找波动高点。任何被发现紧随低点之后的高点值都会被记录在 H 和 H_time 中。这就形成了看涨结构的初始部分,它由一个低点和一个高点组成。
在波动高点之后,第三个循环(索引为 k)会寻找更高的低点。脚本再次使用 IsSwingLow() 函数来寻找更高的低点,更高的低点定义为高于初始低点 L 的低点。当找到更高的低点时,HL 和 HL_time 会更新为更高的低点值和时间。在识别出这个更高的低点之后,程序会继续寻找下一个更高的高点。在第四次循环中检查紧随更高低点之后的摆动高点,该循环的索引为 l。如果发现更高的高点,则将其值保存在 HH 和 HH_time 中。该代码确定在找到四个关键点(低点、高点、更高的低点和更高的高点)后,这四个关键点是否遵循合格的看涨模式。第一个低点应该小于第一个高点,较高的低点应该小于第一个高点,较高的低点应该大于第一个低点,较高的高点应该大于第一个高点。这些标准是通过条件 if (L < H && HL < H && HL > L && HH > H) 进行检查的。这证实了上涨趋势,确保了该形态遵循预期的高点和低点不断升高的顺序。
如果所有这些要求都得到满足,程序就会在图表上创建并显示文本对象,以突出显示已确定的要点。图表以标签的形式显示了各个点,并标明了相应的时期和价格:低点 (L)、高点 (H)、更高低点 (HL) 和更高高点 (HH)。ObjectCreate() 用于创建文本对象,而 ObjectSetInteger() 和 ObjectSetString() 用于设置其特征,包括字体大小和颜色。通过突出显示参考点,用户现在可以轻松识别图表上的看涨结构。总而言之,该程序的目标是在价格数据中寻找更高的高点和更高的低点的形态,以评估其是否具有牛市结构。它通过观察预定柱形范围内的波动点,记录相关信息,并确定结构是否表现出适当的形态来实现这一目标。如果形态得到验证,用户可以在图表上直观地看到它。输入参数可根据用户的偏好进行修改,从而控制整个流程。
2.2.1.将溢价和折扣等级从低到高进行映射
该划分有助于定义“高级区”和“折扣区”这两个术语。低于 50% 的折扣区表示价格范围,该范围被认为对可能的购买更有优势或“更便宜”。另一方面,高级区域高于 50% 的水平,表示相对“昂贵”的费率。为了优化风险回报比,交易者通常会选择在折扣区买入;然而,对于这个特定的指标,我们采用了一种略有不同的策略。

在这种情况下,我们只对市场在溢价区上方交易或突破更高高点结构时买入感兴趣。这种形态表明看涨结构依然存在,价格可能会继续上涨。为了顺应趋势并减少在回调或反转时买入的机会,建议等待价格突破更高的高点或优质区域。
示例:
// CHART ID long chart_id = ChartID(); // Input parameters input int LookbackBars = 10; // Number of bars to look back/forward for swing points input int bars_check = 1000; // Number of bars to check for swing points input bool show_bullish = true; //Show Buy Signals // Variables for Bullish Market Structure double L; // Low: the starting low point in the up trend datetime L_time; // Time of the low string L_letter; // Label for the low point (e.g., "L") double H; // High: the first high after the low datetime H_time; // Time of the high string H_letter; // Label for the high point (e.g., "H") double HL; // Higher Low: the next low that is higher than the first low datetime HL_time; // Time of the higher low string HL_letter; // Label for the higher low point (e.g., "HL") double HH; // Higher High: the next high that is higher than the first high datetime HH_time; // Time of the higher high string HH_letter; // Label for the higher high point (e.g., "HH") // Variables for Premium and Discount string pre_dis_box; // Name/ID for the premium-discount zone box (rectangle object on chart) double lvl_50; // The price level representing the 50% retracement between Higher Low and Higher High string lvl_50_line; // Name/ID for the horizontal line marking the 50% level //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(chart_id); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- if(show_bullish == true) // Check if the bullish trend is to be displayed { if(rates_total >= bars_check) // Ensure enough bars are available for analysis { // Loop through the price data starting from a certain point based on bars_check and LookbackBars for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++) { // Check if the current bar is a swing low if(IsSwingLow(low, i, LookbackBars)) { // Store the values for the swing low L = low[i]; L_time = time[i]; L_letter = StringFormat("Low%d", i); // Loop through further to find a swing high after the low for(int j = i; j < rates_total - LookbackBars; j++) { // Check if the current bar is a swing high and occurs after the identified swing low if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time) { // Store the values for the swing high H = high[j]; H_time = time[j]; H_letter = StringFormat("High%d", j); // Loop further to find a higher low after the swing high for(int k = j; k < rates_total - LookbackBars; k++) { // Check if the current bar is a swing low and occurs after the swing high if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time) { // Store the values for the higher low HL = low[k]; HL_time = time[k]; HL_letter = StringFormat("Higher Low%d", j); // Loop further to find a higher high after the higher low for(int l = j ; l < rates_total - LookbackBars; l++) { // Check if the current bar is a swing high and occurs after the higher low if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time) { // Store the values for the higher high HH = high[l]; HH_time = time[l]; HH_letter = StringFormat("Higher High%d", l); // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High if(L < H && HL < H && HL > L && HH > H) { // Create and display text objects for Low, High, Higher Low, and Higher High on the chart ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L); ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L"); ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H); ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H"); ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL); ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL"); ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH); ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH"); ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15); // Calculate the 50% retracement level between the Higher Low and Higher High lvl_50 = HL + ((HH - HL)/2); // Generate unique names for the premium-discount box and the 50% level line using the current loop index pre_dis_box = StringFormat("Premium and Discount Box%d", i); lvl_50_line = StringFormat("Level 50 Line%d", i); // Create a rectangle object representing the premium-discount zone from the Higher Low to the Higher High ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH); // Create a trend line (horizontal line) marking the 50% retracement level ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50); // Set the color of the premium-discount box to dark green ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_COLOR, clrDarkGreen); // Set the color of the 50% level line to dark green ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_COLOR, clrDarkGreen); // Set the width of the premium-discount box for better visibility ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_WIDTH, 2); // Set the width of the 50% level line for better visibility ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_WIDTH, 2); } break; // Exit the loop once the pattern is found } } break; // Exit the loop once the higher low is found } } break; // Exit the loop once the higher high is found } } } } } } //--- return value of prev_calculated for next call return(rates_total); }
输出:

解释:
该代码确定 50% 回撤位,即更高低点 (HL) 和更高高点 (HH) 之间的中间点。高级区(上方)和折扣区(下方)就是以这个等级划分的。HL 和 HH 之间的中点是使用公式 lvl_50 = HL + ((HH - HL)/2) 确定的。接下来,循环索引 i 被包含在两个字符串变量 pre_dis_box 和 lvl_50_line 的名称中。这些标识将用于区分我们将在图表上绘制的可视化元素。如果包含循环索引,则每张图都是独一无二的,不会替换之前的图。
通过 ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH) 线在图表上创建了一个矩形;该矩形以图形方式描绘了从更高的低点到更高的高点的过渡。交易者可以利用这个方框快速确定最近一次上涨行情的范围。矩形的末端以 HH 价格锚定在未来的柱形 (l + LookbackBars) 上,而其起始点则以 HL 的时间和价格锚定。这样一来,方框就能通过稍微向前延伸的方式保持可见。
然后通过以下代码在图表的 50% 水平位置绘制一条水平线:ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50);。这个阈值很重要,因为根据该指标的逻辑,我们只在市场交易价格高于溢价区或高于最近一次上涨行情的 50% 水平时寻找可能的买入信号。方框和线条的颜色设置为 clrDarkGreen,并使用 ObjectSetInteger 函数将它们的粗细(或宽度)增加到 2,使它们在视觉上清晰可见。市场必须完全突破 HH 线,买入信号才被视为有效;换句话说,价格必须收于整个溢价区域之外且高于该区域。换句话说,我们只想在市场结构明显看涨且超过前一个高点(HH)时进行买入。
2.2.2.标示入场点、止损点和止盈点
一旦正确标记出更高低点 (HL) 和更高高点 (HH) 之间的溢价和折价区域,接下来就要寻找可能的买入信号。等待看涨突破柱形突破更高高点 (HH),这表明市场仍在强劲上涨,这是获得有效买入设置的关键。
但仅仅跨越 HH 是不够的。只有当阳线收盘价高于最高价位时,入场才能得到验证,因为我们需要确保突破是真实的。价格收于最高价位上方,表明买入需求持续存在,价格可能会继续上涨。入场点设定在突破确认后,向上突破 HH 的阳线收盘时。我们确信,目前市场已展现出足够的强劲势头,足以让我们进行交易。
我们将止损 (SL) 设置在 50% 水平 (lvl_50),即高点 (HL) 和高点 (HH) 的中间点,以防止可能出现的反转。为了避免跌入折扣区(低于 50% 的水平),这可能表明市场情绪发生了变化,我们将止损位设在这里。 我们的方法基于止盈 (TP) 水平的风险回报比 (R:R)。第一个止盈水平 TP1 的盈利目标等于入场点和止损点之间的风险距离,因为它的盈亏比设置为 1:1。第二个止盈位(TP2)的盈利目标是入场点与止损点之间距离的两倍,盈亏比设置为 1:2。 对于更愿意在 TP1 锁定部分利润的交易者来说,这两个止盈位提供了灵活性,允许他们保留一部分交易,以便在市场继续上涨趋势时获得额外收益。
例子:
// Variables for Entry, Stop Loss, and Take Profit string entry_line; // Line object to represent the entry point on the chart string entry_txt; // Text object for displaying "BUY" at the entry point double lvl_SL; // Stop Loss level (set at the 50% retracement level) string lvl_sl_line; // Line object for representing the Stop Loss level string lvl_sl_txt; // Text object for labeling the Stop Loss level double TP1; // Take Profit 1 level (1:1 risk-reward ratio) double TP2; // Take Profit 2 level (1:2 risk-reward ratio) string lvl_tp_line; // Line object for representing the Take Profit 1 level string lvl_tp2_line; // Line object for representing the Take Profit 2 level string lvl_tp_txt; // Text object for labeling the Take Profit 1 level string lvl_tp2_txt; // Text object for labeling the Take Profit 2 level string buy_object; // Arrow object to indicate the Buy signal on the chart
if(show_bullish == true) // Check if the bullish trend is to be displayed { if(rates_total >= bars_check) // Ensure enough bars are available for analysis { // Loop through the price data starting from a certain point based on bars_check and LookbackBars for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++) { // Check if the current bar is a swing low if(IsSwingLow(low, i, LookbackBars)) { // Store the values for the swing low L = low[i]; L_time = time[i]; L_letter = StringFormat("Low%d", i); // Loop through further to find a swing high after the low for(int j = i; j < rates_total - LookbackBars; j++) { // Check if the current bar is a swing high and occurs after the identified swing low if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time) { // Store the values for the swing high H = high[j]; H_time = time[j]; H_letter = StringFormat("High%d", j); // Loop further to find a higher low after the swing high for(int k = j; k < rates_total - LookbackBars; k++) { // Check if the current bar is a swing low and occurs after the swing high if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time) { // Store the values for the higher low HL = low[k]; HL_time = time[k]; HL_letter = StringFormat("Higher Low%d", j); // Loop further to find a higher high after the higher low for(int l = j ; l < rates_total - LookbackBars; l++) { // Check if the current bar is a swing high and occurs after the higher low if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time) { // Store the values for the higher high HH = high[l]; HH_time = time[l]; HH_letter = StringFormat("Higher High%d", l); // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High if(L < H && HL < H && HL > L && HH > H) { // Create and display text objects for Low, High, Higher Low, and Higher High on the chart ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L); ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L"); ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H); ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H"); ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL); ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL"); ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH); ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH"); ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15); // Calculate the 50% retracement level between the Higher Low and Higher High lvl_50 = HL + ((HH - HL)/2); // Generate unique names for the premium-discount box and the 50% level line using the current loop index pre_dis_box = StringFormat("Premium and Discount Box%d", i); lvl_50_line = StringFormat("Level 50 Line%d", i); // Create a rectangle object representing the premium-discount zone from the Higher Low to the Higher High ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH); // Create a trend line (horizontal line) marking the 50% retracement level ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50); // Set the color of the premium-discount box to dark green ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_COLOR, clrDarkGreen); // Set the color of the 50% level line to dark green ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_COLOR, clrDarkGreen); // Set the width of the premium-discount box for better visibility ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_WIDTH, 2); // Set the width of the 50% level line for better visibility ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_WIDTH, 2); for(int m = l; m < rates_total-1; m++) { if(close[m] > open[m] && close[m] > HH && time[m] >= time[l+LookbackBars]) { TP1 = close[m] + (close[m] - lvl_50); TP2 = TP1 + (close[m] - lvl_50); entry_line = StringFormat("Entry%d", m); lvl_sl_line = StringFormat("SL%d", m); lvl_tp_line = StringFormat("TP%d", m); lvl_tp2_line = StringFormat("TP 2%d", m); ObjectCreate(chart_id,entry_line,OBJ_TREND,0,HL_time,close[m],time[m],close[m]); ObjectCreate(chart_id,lvl_sl_line,OBJ_TREND,0,HL_time,lvl_50,time[m],lvl_50); ObjectCreate(chart_id,lvl_tp_line,OBJ_TREND,0,HL_time,TP1,time[m],TP1); ObjectCreate(chart_id,lvl_tp2_line,OBJ_TREND,0,HL_time,TP2,time[m],TP2); ObjectSetInteger(chart_id,entry_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_sl_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_tp_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_tp2_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,entry_line,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_sl_line,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_tp_line,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_tp2_line,OBJPROP_COLOR,clrDarkGreen); entry_txt = StringFormat("Entry Text%d", m); lvl_sl_txt = StringFormat("SL Text%d", m); lvl_tp_txt = StringFormat("TP 1 Text%d", m); lvl_tp2_txt = StringFormat("TP 2 Text%d", m); ObjectCreate(chart_id, lvl_sl_txt, OBJ_TEXT, 0,time[m],lvl_50); ObjectSetString(chart_id, lvl_sl_txt, OBJPROP_TEXT, "SL"); ObjectSetInteger(chart_id,lvl_sl_txt,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_sl_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, entry_txt, OBJ_TEXT, 0,time[m],close[m]); ObjectSetString(chart_id, entry_txt, OBJPROP_TEXT, "BUY"); ObjectSetInteger(chart_id,entry_txt,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,entry_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, lvl_tp_txt, OBJ_TEXT, 0,time[m],TP1); ObjectSetString(chart_id, lvl_tp_txt, OBJPROP_TEXT, "TP1"); ObjectSetInteger(chart_id,lvl_tp_txt,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_tp_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, lvl_tp2_txt, OBJ_TEXT, 0,time[m],TP2); ObjectSetString(chart_id, lvl_tp2_txt, OBJPROP_TEXT, "TP2"); ObjectSetInteger(chart_id,lvl_tp2_txt,OBJPROP_COLOR,clrDarkGreen); ObjectSetInteger(chart_id,lvl_tp2_txt,OBJPROP_FONTSIZE,15); buy_object = StringFormat("Buy Object%d", m); ObjectCreate(chart_id,buy_object,OBJ_ARROW_BUY,0,time[m],close[m]); break; } } } break; // Exit the loop once the pattern is found } } break; // Exit the loop once the higher low is found } } break; // Exit the loop once the higher high is found } } } } } }输出:

可以看到,上图中溢价区和折价区以及买入信号标记并没有精确绘制。这是因为在图表上绘制对象之前忽略了某些条件。为了改进该方法,我们必须增加更多验证措施,以确保买入信号的合格性。在图表上绘制任何对象之前,我们必须首先确保烛形确实突破了更高的高点 (HH)。突破 HH 意味着上涨趋势的延续,这是买入信号被视为有效的必要条件,因此这是一个关键标准。在这个要求得到满足之前,我们不应该开始进入和风险管理计算。
然后必须计算从较高低点 (HL) 到溢价和折扣框末尾的柱形数量。这保证了价格走势在可接受的范围内,并有助于我们了解市场已经移动了多远。计数完成后,我们需要确认突破更高高点 (HH) 的阳线收盘价接近溢价和折价区间。这就保证了买入信号与预期的市场结构距离不太远,且发生在公平的价格区间内。
例子:
// Declare variables to count bars int n_bars; // Number of bars from Higher Low to the end of the Premium/Discount box int n_bars_2; // Number of bars from the end of the Premium/Discount box to the bullish bar that broke HH
if(show_bullish == true) // Check if the bullish trend is to be displayed { if(rates_total >= bars_check) // Ensure enough bars are available for analysis { // Loop through the price data starting from a certain point based on bars_check and LookbackBars for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++) { // Check if the current bar is a swing low if(IsSwingLow(low, i, LookbackBars)) { // Store the values for the swing low L = low[i]; L_time = time[i]; L_letter = StringFormat("Low%d", i); // Loop through further to find a swing high after the low for(int j = i; j < rates_total - LookbackBars; j++) { // Check if the current bar is a swing high and occurs after the identified swing low if(IsSwingHigh(high, j, LookbackBars) && time[j] > L_time) { // Store the values for the swing high H = high[j]; H_time = time[j]; H_letter = StringFormat("High%d", j); // Loop further to find a higher low after the swing high for(int k = j; k < rates_total - LookbackBars; k++) { // Check if the current bar is a swing low and occurs after the swing high if(IsSwingLow(low, k, LookbackBars) && time[k] > H_time) { // Store the values for the higher low HL = low[k]; HL_time = time[k]; HL_letter = StringFormat("Higher Low%d", j); // Loop further to find a higher high after the higher low for(int l = j ; l < rates_total - LookbackBars; l++) { // Check if the current bar is a swing high and occurs after the higher low if(IsSwingHigh(high, l, LookbackBars) && time[l] > HL_time) { // Store the values for the higher high HH = high[l]; HH_time = time[l]; HH_letter = StringFormat("Higher High%d", l); // Loop through the bars to check for the conditions for entry for(int m = l; m < rates_total-1; m++) { // Check if the current bar is a bullish bar and if the price has broken the higher high (HH) if(close[m] > open[m] && close[m] > HH && time[m] >= time[l+LookbackBars]) { // Count the bars between HL_time and the end of the Premium/Discount box n_bars = Bars(_Symbol, PERIOD_CURRENT, HL_time, time[l + LookbackBars]); // Count the bars between the end of the Premium/Discount box and the candle that broke HH n_bars_2 = Bars(_Symbol, PERIOD_CURRENT, time[l + LookbackBars], time[m]); // Check if the pattern follows the expected bullish structure: Low < High, Higher Low < High, Higher High > High if(L < H && HL < H && HL > L && HH > H && open[l+LookbackBars] <= HH && n_bars_2 < n_bars) { // Create and display text objects for Low, High, Higher Low, and Higher High on the chart ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L); ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L"); ObjectSetInteger(chart_id, L_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, L_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H); ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H"); ObjectSetInteger(chart_id, H_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, H_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HL_letter, OBJ_TEXT, 0, HL_time, HL); ObjectSetString(chart_id, HL_letter, OBJPROP_TEXT, "HL"); ObjectSetInteger(chart_id, HL_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HL_letter, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, HH_letter, OBJ_TEXT, 0, HH_time, HH); ObjectSetString(chart_id, HH_letter, OBJPROP_TEXT, "HH"); ObjectSetInteger(chart_id, HH_letter, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, HH_letter, OBJPROP_FONTSIZE, 15); // Calculate the 50% retracement level between the Higher Low and Higher High lvl_50 = HL + ((HH - HL)/2); // Generate unique names for the premium-discount box and the 50% level line using the current loop index pre_dis_box = StringFormat("Premium and Discount Box%d", i); lvl_50_line = StringFormat("Level 50 Line%d", i); // Create a rectangle object representing the premium-discount zone from the Higher Low to the Higher High ObjectCreate(chart_id, pre_dis_box, OBJ_RECTANGLE, 0, HL_time, HL, time[l + LookbackBars], HH); // Create a trend line (horizontal line) marking the 50% retracement level ObjectCreate(chart_id, lvl_50_line, OBJ_TREND, 0, HL_time, lvl_50, time[l + LookbackBars], lvl_50); // Set the color of the premium-discount box to dark green ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_COLOR, clrDarkGreen); // Set the color of the 50% level line to dark green ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_COLOR, clrDarkGreen); // Set the width of the premium-discount box for better visibility ObjectSetInteger(chart_id, pre_dis_box, OBJPROP_WIDTH, 2); // Set the width of the 50% level line for better visibility ObjectSetInteger(chart_id, lvl_50_line, OBJPROP_WIDTH, 2); // Calculate Take Profit levels based on the 50% retracement TP1 = close[m] + (close[m] - lvl_50); // TP1 at 1:1 risk-reward ratio TP2 = TP1 + (close[m] - lvl_50); // TP2 at 1:2 risk-reward ratio // Create unique object names for Entry, Stop Loss, and Take Profit lines and text entry_line = StringFormat("Entry%d", m); lvl_sl_line = StringFormat("SL%d", m); lvl_tp_line = StringFormat("TP%d", m); lvl_tp2_line = StringFormat("TP 2%d", m); // Create the lines on the chart for Entry, Stop Loss, and Take Profit levels ObjectCreate(chart_id, entry_line, OBJ_TREND, 0, HL_time, close[m], time[m], close[m]); ObjectCreate(chart_id, lvl_sl_line, OBJ_TREND, 0, HL_time, lvl_50, time[m], lvl_50); ObjectCreate(chart_id, lvl_tp_line, OBJ_TREND, 0, HL_time, TP1, time[m], TP1); ObjectCreate(chart_id, lvl_tp2_line, OBJ_TREND, 0, HL_time, TP2, time[m], TP2); // Set the properties for the lines (width, color, etc.) ObjectSetInteger(chart_id, entry_line, OBJPROP_WIDTH, 2); ObjectSetInteger(chart_id, lvl_sl_line, OBJPROP_WIDTH, 2); ObjectSetInteger(chart_id, lvl_tp_line, OBJPROP_WIDTH, 2); ObjectSetInteger(chart_id, lvl_tp2_line, OBJPROP_WIDTH, 2); ObjectSetInteger(chart_id, entry_line, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_sl_line, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_tp_line, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_tp2_line, OBJPROP_COLOR, clrDarkGreen); // Create the text labels for Entry, Stop Loss, and Take Profit levels entry_txt = StringFormat("Entry Text%d", m); lvl_sl_txt = StringFormat("SL Text%d", m); lvl_tp_txt = StringFormat("TP 1 Text%d", m); lvl_tp2_txt = StringFormat("TP 2 Text%d", m); // Create the text objects for the Entry, Stop Loss, and Take Profit labels ObjectCreate(chart_id, lvl_sl_txt, OBJ_TEXT, 0, time[m], lvl_50); ObjectSetString(chart_id, lvl_sl_txt, OBJPROP_TEXT, "SL"); ObjectSetInteger(chart_id, lvl_sl_txt, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_sl_txt, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, entry_txt, OBJ_TEXT, 0, time[m], close[m]); ObjectSetString(chart_id, entry_txt, OBJPROP_TEXT, "BUY"); ObjectSetInteger(chart_id, entry_txt, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, entry_txt, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, lvl_tp_txt, OBJ_TEXT, 0, time[m], TP1); ObjectSetString(chart_id, lvl_tp_txt, OBJPROP_TEXT, "TP1"); ObjectSetInteger(chart_id, lvl_tp_txt, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_tp_txt, OBJPROP_FONTSIZE, 15); ObjectCreate(chart_id, lvl_tp2_txt, OBJ_TEXT, 0, time[m], TP2); ObjectSetString(chart_id, lvl_tp2_txt, OBJPROP_TEXT, "TP2"); ObjectSetInteger(chart_id, lvl_tp2_txt, OBJPROP_COLOR, clrDarkGreen); ObjectSetInteger(chart_id, lvl_tp2_txt, OBJPROP_FONTSIZE, 15); // Create a Buy arrow object to indicate the Buy signal on the chart buy_object = StringFormat("Buy Object%d", m); ObjectCreate(chart_id, buy_object, OBJ_ARROW_BUY, 0, time[m], close[m]); break; // Exit the loop once a Buy signal is found } } } break; // Exit the loop once the pattern is found } } break; // Exit the loop once the higher low is found } } break; // Exit the loop once the higher high is found } } } } } }
输出:

解释:
在代码的全局空间中声明了两个整型变量,n_bars 和 n_bars_2。看涨市场结构形态中重要点之间的烛形(柱)数量由这些变量决定。具体来说,n_bars 是将 Premium/Discount 框的末尾 (time[l + LookbackBars]) 与更高的低点 (HL) 分隔开的柱形数量。然而,n_bars_2 计算的是将突破更高高点 (HH) 的看涨烛形与溢价/折价框的末端分隔开的烛形数量。该计数用于评估价格走势是否偏离最佳交易区域过远,或者买入信号是否仍然有效。
这些变量在代码的后续部分中被用作额外的验证条件,以加强对看涨结构的论证。在确定最低价、最高价、更高最低价和更高最高价之后(确保它们符合 L < H、HL < H、HL > L 和 HH > H 的结构),并验证结束溢价/折价框的蜡烛的开盘价不高于更高最高价,则检查附加条件 n_bars_2 < n_bars。这样可以确保看涨突破烛形(突破 HH 的烛形)不会在形态形成后太远出现,这可能表明设置较弱或无效,并且它会在形态形成后相当接近时出现。
之前用于绘制图表的最低价、最高价、更高最低价、更高最高价、溢价/折扣框、50% 线以及入场/止损/止盈标记的所有 ObjectCreate() 和 ObjectSet*() 例程都转移到了这个 if 语句中,以实施更严格的检查。这意味着只有当所有看涨的结构和时间要求都得到满足时,这些视觉元素才会被制作和展示。通过这样做,图表保持清晰,不会因误导信号而出现错误或过早的项目。
2.3.看跌趋势
该指标必须首先利用市场结构来验证下跌趋势,然后才能发出卖出信号。实现这一目标的方法是找到一系列重要的价格点 —— 一个高价、一个低价、一个更低的高价和一个更低的低价。这种模式表明卖方占据主导地位,市场可能会继续下跌,这支持了看跌势头。一旦该结构得到验证,指标将立即开始寻找合格的卖出信号。

示例:
// Variables for Bearish Market Structure double LH; // Lower High: the high formed after the initial low in a downtrend datetime LH_time; // Time of the Lower High string LH_letter; // Label used to display the Lower High on the chart (e.g., "LH") double LL; // Lower Low: the new low formed after the Lower High in a downtrend datetime LL_time; // Time of the Lower Low string LL_letter; // Label used to display the Lower Low on the chart (e.g., "LL") string sell_object; // Arrow object to indicate the Sell signal on the chart
// BEARISH TREND if(show_bearish == true) // Check if the user enabled the bearish trend display { if(rates_total >= bars_check) // Ensure enough candles are available for processing { // Loop through historical bars to find a swing high (potential start of bearish structure) for(int i = rates_total - bars_check; i < rates_total - LookbackBars; i++) { if(IsSwingHigh(high, i, LookbackBars)) // Detect first swing high { H = high[i]; H_time = time[i]; H_letter = StringFormat("High B%d", i); // Label for the high // From the swing high, look for the next swing low for(int j = i; j < rates_total - LookbackBars; j++) { if(IsSwingLow(low, j, LookbackBars) && time[j] > H_time) // Confirm next swing low { L = low[j]; L_time = time[j]; L_letter = StringFormat("Low B%d", j); // Label for the low // From the swing low, look for the Lower High for(int k = j; k < rates_total - LookbackBars; k++) { if(IsSwingHigh(high, k, LookbackBars) && time[k] > L_time) { LH = high[k]; LH_time = time[k]; LH_letter = StringFormat("Lower High%d", k); // Label for the Lower High // From the LH, find a Lower Low for(int l = j ; l < rates_total - LookbackBars; l++) { if(IsSwingLow(low, l, LookbackBars) && time[l] > LH_time) { LL = low[l]; LL_time = time[l]; LL_letter = StringFormat("Lower Low%d", l); // Label for Lower Low // Calculate 50% retracement level from LH to LL lvl_50 = LL + ((LH - LL)/2); // Prepare object names pre_dis_box = StringFormat("Gan Box B%d", i); lvl_50_line = StringFormat("Level 50 Line B%d", i); // Search for a bearish entry condition for(int m = l; m < rates_total-1; m++) { // Confirm bearish candle breaking below the LL if(close[m] < open[m] && close[m] < LL && time[m] >= time[l+LookbackBars]) { // Count bars for pattern distance validation n_bars = Bars(_Symbol,PERIOD_CURRENT,LH_time, time[l+LookbackBars]); // From LH to box end n_bars_2 = Bars(_Symbol,PERIOD_CURRENT,time[l+LookbackBars], time[m]); // From box end to break candle // Confirm valid bearish structure and proximity of break candle if(H > L && LH > L && LH < H && LL < L && open[l+LookbackBars] >= LL && n_bars_2 < n_bars) { // Draw the Premium/Discount box ObjectCreate(chart_id,pre_dis_box, OBJ_RECTANGLE,0,LH_time,LH, time[l+LookbackBars],LL); ObjectCreate(chart_id,lvl_50_line, OBJ_TREND,0,LH_time,lvl_50, time[l+LookbackBars],lvl_50); ObjectSetInteger(chart_id,pre_dis_box,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_50_line,OBJPROP_WIDTH,2); // Label the structure points ObjectCreate(chart_id, H_letter, OBJ_TEXT, 0, H_time, H); ObjectSetString(chart_id, H_letter, OBJPROP_TEXT, "H"); ObjectSetInteger(chart_id,H_letter,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, L_letter, OBJ_TEXT, 0, L_time, L); ObjectSetString(chart_id, L_letter, OBJPROP_TEXT, "L"); ObjectSetInteger(chart_id,L_letter,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, LH_letter, OBJ_TEXT, 0, LH_time, LH); ObjectSetString(chart_id, LH_letter, OBJPROP_TEXT, "LH"); ObjectSetInteger(chart_id,LH_letter,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, LL_letter, OBJ_TEXT, 0, LL_time, LL); ObjectSetString(chart_id, LL_letter, OBJPROP_TEXT, "LL"); ObjectSetInteger(chart_id,LL_letter,OBJPROP_FONTSIZE,15); ObjectSetInteger(chart_id,H_letter,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,L_letter,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,LL_letter,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,LH_letter,OBJPROP_WIDTH,2); // Calculate Take Profits based on 1:1 and 1:2 RR TP1 = close[m] - (lvl_50 - close[m]); TP2 = TP1 - (lvl_50 - close[m]); // Generate entry, SL and TP object names entry_line = StringFormat("Entry B%d", m); lvl_sl_line = StringFormat("SL B%d", m); lvl_tp_line = StringFormat("TP B%d", m); lvl_tp2_line = StringFormat("TP 2 B%d", m); // Draw entry, SL, TP1, TP2 levels ObjectCreate(chart_id,entry_line,OBJ_TREND,0,LH_time,close[m],time[m],close[m]); ObjectCreate(chart_id,lvl_sl_line, OBJ_TREND,0,LH_time,lvl_50, time[m],lvl_50); ObjectCreate(chart_id,lvl_tp_line, OBJ_TREND,0,LH_time,TP1, time[m],TP1); ObjectCreate(chart_id,lvl_tp2_line, OBJ_TREND,0,LH_time,TP2, time[m],TP2); ObjectSetInteger(chart_id,entry_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_sl_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_tp_line,OBJPROP_WIDTH,2); ObjectSetInteger(chart_id,lvl_tp2_line,OBJPROP_WIDTH,2); // Generate text labels entry_txt = StringFormat("Entry Text B%d", m); lvl_sl_txt = StringFormat("SL Text B%d", m); lvl_tp_txt = StringFormat("TP Text B%d", m); lvl_tp2_txt = StringFormat("TP 2 Text B%d", m); ObjectCreate(chart_id, entry_txt, OBJ_TEXT, 0,time[m],close[m]); ObjectSetString(chart_id, entry_txt, OBJPROP_TEXT, "SELL"); ObjectSetInteger(chart_id,entry_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, lvl_sl_txt, OBJ_TEXT, 0,time[m],lvl_50); ObjectSetString(chart_id, lvl_sl_txt, OBJPROP_TEXT, "SL"); ObjectSetInteger(chart_id,lvl_sl_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, lvl_tp_txt, OBJ_TEXT, 0,time[m],TP1); ObjectSetString(chart_id, lvl_tp_txt, OBJPROP_TEXT, "TP1"); ObjectSetInteger(chart_id,lvl_tp_txt,OBJPROP_FONTSIZE,15); ObjectCreate(chart_id, lvl_tp2_txt, OBJ_TEXT, 0,time[m],TP2); ObjectSetString(chart_id, lvl_tp2_txt, OBJPROP_TEXT, "TP2"); ObjectSetInteger(chart_id,lvl_tp2_txt,OBJPROP_FONTSIZE,15); // Draw sell arrow sell_object = StringFormat("Sell Object%d", m); ObjectCreate(chart_id,sell_object,OBJ_ARROW_SELL,0,time[m],close[m]); } break; // Exit loop after valid setup } } break; // Exit LL search } } break; // Exit LH search } } break; // Exit L search } } } } } }
输出:

解释:
代码首先使用 if(show_bearish == true) 来查看用户是否已激活看跌趋势逻辑。为了找到合法的看跌结构,该指标会在启用且有足够的柱形可用时循环遍历历史柱线(rates_total >= bars_check)。该过程的第一步是确定波动高点(H)。在识别出一个波动高点后,代码会寻找紧随其后的波动低点(L)。如果发现看跌结构,它会继续寻找更低的低点 (LL) 来验证该结构,然后寻找更低的高点 (LH),即低于第一个高点 H 的波动高点。图表上会使用这些值和相关的时间戳创建“H”、“L”、“LH”和“LL”等标签。
接下来,从 LH 到 LL 绘制溢价/折价区域框,并确定 LH 和 LL 之间的 50% 回撤水平(lvl_50 = LL + ((LH - LL)/2))。该指标会寻找收盘价低于 LL 的阴线(收盘价 < 开盘价),然后再设置任何与交易相关的对象(入场点、止损点、TP1、TP2)。通过使用两个变量,n_bars(从较低的最高价 (LH) 到溢价/折价框的末尾计算柱数)和 n_bars_2(从框的末尾到突破较低最低价 (LL) 的看跌烛形),该代码还可以确保结构突破发生在合理的柱数范围内。该代码仅在烛形关闭时绘制入场线,将止损 (SL) 设置在 50% 水平,并将 TP1 和 TP2 分别设置在 1:1 和 1:2 的风险回报水平,前提是满足所有要求,包括适当的结构、有效的突破和适当的距离。
此外,它还在阴线烛形上添加了卖出箭头和“卖出”字样。对于下跌趋势,其逻辑与上涨趋势模式基本相同,只是方向相反。由于看跌趋势只是看涨趋势的反面,而看涨趋势已经进行了详尽的阐述,因此解释就不多赘述了。结构和推理方式相同,只是顺序颠倒,显示的是下降趋势而不是上升趋势。
结论
在本文中,我们构建了一个 MQL5 自定义指标,该指标通过检测低点 (L)、更低的低点 (LL)、更高的低点 (HL)、高点 (H) 和更高的高点 (HH) 等关键点来识别市场结构。利用这些点,该指标可以确定上涨或下跌趋势,并根据结构化模式自动绘制入场点、止损位(50)和止盈位(TP1 和 TP2)。它还标出了溢价区和折扣区,以直观地突出显示价格可能出现波动的区域。只有在满足特定条件时才会绘制所有图表对象,从而确保信号清晰可靠。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17689
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
价格行为分析工具包开发(第二十一部分):市场结构反转检测工具
在交易图表上通过资源驱动的双三次插值图像缩放技术创建动态 MQL5 图形界面
您应当知道的 MQL5 向导技术(第 55 部分):配备优先经验回放的 SAC
MQL5交易策略自动化(第十六部分):基于结构突破(BoS)价格行为的午夜区间突破策略