English Русский Deutsch 日本語
preview
价格行为分析工具包开发(第十六部分):引入四分之一理论(2)—— 侵入探测器智能交易系统(EA)

价格行为分析工具包开发(第十六部分):引入四分之一理论(2)—— 侵入探测器智能交易系统(EA)

MetaTrader 5交易系统 |
287 0
Christian Benjamin
Christian Benjamin

概述

在前一篇文章中,我们介绍了四分位绘图脚本,这一工具旨在在图表上直观地绘制出四分位价位水平,让市场分析更加直观易懂。这一概念源自伊利安·约托夫(Ilian Yotov)提出的四分位理论。事实证明,成功绘制出这些四分位是简化价格行为分析的一种有效方法。然而,在价格与这些价位产生交互时,手动监控这些价位水平需要投入大量的时间和精力。

为了应对这一挑战,我很高兴地推出侵入探测器EA,这一解决方案旨在实现监控流程的自动化。该EA会持续跟踪图表,当价格达到任何四分位水平(无论是小四分位、大四分位、主要四分位、上冲还是下探水平)时,都能进行检测。此外,还会根据伊利安·约托夫的四分位理论,即时提供评论和见解,帮助每位交易者预测潜在的市场反应。在本文中,我们将首先回顾“四分位绘图工具”,然后深入探讨策略概念和MQL5实现,分析测试结果,最后总结关键要点。请参阅以下内容目录,以获取结构化总览。

内容



上一篇文章回顾

我不会在这一部分过多赘述,因为我们在上一篇文章中已经对四分位绘图脚本进行了详尽地讨论。然而,如果您还没有阅读过那篇文章,我强烈建议您点击提供的链接,以充分理解相关基础概念。那篇文章主要聚焦于实现四分位水平的自动绘制,让市场分析更加条理清晰、高效便捷。

如前文所述,四分位理论是由伊利安·约托夫发现并随后引入交易界的。有一点至关重要,或许在前一篇文章中并未着重强调,那就是该理论尤其适用于货币对,因此它是外汇交易者不可或缺的工具。为了更清晰地了解这些四分位水平是如何运作的,请查看以下图表,其直观展示了该理论的结构。

四分位水平

图例1. 四分位水平

我们的四分位绘图工具取得了显著的成效,成功绘制了大四分位价位、上冲价位、下探价位以及小四分位价位。更重要的是,我们观察到价格持续与这些价位产生交互并作出反应,这进一步证实了约托夫理论的合理性与有效性。这并非单纯的理论推演,该工具在真实市场环境中验证了四分位水平的强大作用。现在,让我们分析下图,重温一些关键发现与见解。

成果

图例2. 成果


策略概念和MQL5实现

核心逻辑

侵入探测器EA的核心在于运用四分位理论来绘制关键的心理价位水平。它将市场划分为1000点(pip)的区间范围,以主要整数价位作为支撑框架。在这些区间内,它会标记出大四分位价位(250点的区域),如果启用,还会标记出更细粒度的小四分位价位。该EA还会绘制出上冲区域和下探区域,以捕捉可能误导交易者的价格延伸情况。在每一次价格变动时,都会扫描当前价格,检查其是否在设定的容差范围内(即误差范围)触及这些关键价位水平,如果发生情况,就会触发警报。

这里的核心目标是,在潜在转折点突破行情假突破行情变得显而易见之前,就将其识别出来。如果价格在主要整数价位附近徘徊,EA会将其标记为关键支撑或阻力区域。如果价格在大四分位价位附近波动,就会发出信号,表明可能即将出现250点的行情走势。上冲区域和下探区域作用如何?它们有助于识别价格可能大幅反转的陷阱。该EA通过跟踪价格侵入情况来确保不会频繁发出警报,并且会更新一个即时评论面板,让您随时了解市场动态。整个系统旨在通过将四分位理论变得实用且可操作,让您领先于其他交易者。

实现

在该EA中,我们首先编写一个包含EA元数据的头部,例如其名称(“侵入探测器”)、版权详情以及指向开发者个人资料的链接。#property指令 用于指定这些详细信息,并强制实施严格的编译规则,确保代码符合当前MQL5标准。

//+------------------------------------------------------------------+
//|                                             Intrusion Detector   |
//|                             Copyright 2025, Christian Benjamin   |
//|                        https://www.mql5.com/en/users/lynnchris   |
//+------------------------------------------------------------------+
#property copyright "Christian Benjamin"
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

接下来,我们定义一组输入参数,让您无需修改代码即可自定义EA的行为。这些参数包括数值型参数,如MajorStep(用于确定主要价位之间的间隔,实际上定义了一个1000点的区间范围)和AlertTolerance(用于设置检测价格“触及”特定价位时的接近阈值)。布尔型输入参数则控制EA是否绘制附加的线条,例如大四分位线、小四分位线以及主要价位周围的上冲区域。

此外,颜色设置使用十六进制值(或预定义颜色)进行定义,以确保图表上的每种线条类型(无论是主要价位线、大四分位线、小四分位线还是上冲线)都能以预期的可视化样式呈现。随后是线条样式和粗细设置,让您能够进一步自定义这些绘制线条的外观。

配置设置

input double MajorStep          = 0.1000;   // Difference between Major Whole Numbers (defines the 1000-PIP Range)
input bool   DrawLargeQuarters  = true;     // Draw intermediate Large Quarter lines.
input bool   DrawSmallQuarters  = false;    // Draw Small Quarter lines.
input bool   DrawOvershootAreas = true;     // Mark overshoot/undershoot areas for Large Quarter lines.
input double AlertTolerance     = 0.0025;   // Tolerance for detecting a "touch"

  • MajorStep:定义主要价位之间的间隔(例如,一个1000点的区间范围)。
  • DrawLargeQuartersDrawSmallQuarters:布尔值,控制EA是否应在区间内绘制附加的线条。
  • DrawOvershootAreas:决定是否应在大四分位水平附近绘制附加的“上冲”和“下探”线条。
  • AlertTolerance:指定价格必须接近某一价位到何种程度(在0.0025范围内)才被视为“触及”。

颜色设置

input color  MajorColor         = 0x2F4F4F; // Dark Slate Gray for Major lines.
input color  LargeQuarterColor  = 0x8B0000; // Dark Red for Large Quarter lines.
input color  SmallQuarterColor  = 0x00008B; // Dark Blue for Small Quarter lines.
input color  OvershootColor     = clrRed;   // Red for overshoot/undershoot lines.

每种线条类型都定义了相应的颜色,以便绘制在图表上时,其可视化效果与描述相符。

线条样式与粗细设置

input ENUM_LINE_STYLE MajorLineStyle       = STYLE_SOLID;
input int    MajorLineWidth                 = 4;
input ENUM_LINE_STYLE LargeQuarterLineStyle  = STYLE_DOT;
input int    LargeQuarterLineWidth          = 3;
input ENUM_LINE_STYLE OvershootLineStyle     = STYLE_DASH;
input int    OvershootLineWidth             = 1;
input ENUM_LINE_STYLE SmallQuarterLineStyle  = STYLE_SOLID;
input int    SmallQuarterLineWidth          = 1;

每种线条类型(主要价位线、大四分位线、上冲线、小四分位线)都有其样式(实线、点线、虚线)和宽度设置。

允许自定义的评论信息

当价格“触及”某一价位时,这些信息会描述该价位的重要意义。它们后续将用于构建评论表格。

  • 主要支撑位原因(MajorSupportReason):表明市场中存在一个重要的支撑位。如果价格跌破此价位,则意味着交易区间可能发生转变,进而可能导致价格进一步下跌。
  • 主要阻力位原因(MajorResistanceReason):代表一个关键的阻力位。如果价格突破此阻力位,则可能预示着新的交易区间的开始,进而可能导致价格上涨。
  • 大四分位原因(LargeQuarterReason):这一表述指出,价格在此价位发生决定性突破可能导致大幅价格波动,潜在幅度可达250点。这意味着交易者应密切关注此价位,以寻找潜在的交易机会。
  • 上冲原因(OvershootReason):暗示价格正在测试突破价位,如果无法维持上涨势头,则价格方向很可能发生反转。这意味着,如果价格在没有强劲买盘支撑的情况下飙升至关键价位之上,交易者应保持谨慎。
  • 下探原因(UndershootReason):表明市场中多头势头不足,暗示价格可能反转至下跌趋势。交易者应密切关注这一信号,以寻找潜在的做空机会。
  • 小四分位原因(SmallQuarterReason):指的是市场中的小幅价格波动。这表明价格正在小幅变动,可能并不预示着任何重大的交易机会或趋势变化。

input string MajorSupportReason    = "Key support level. Break below signals range shift.";
input string MajorResistanceReason = "Pivotal resistance. Breakout above may start new range.";
input string LargeQuarterReason    = "Decisive break could trigger next 250-PIP move.";
input string OvershootReason       = "Test of breakout; reversal likely if momentum fails.";
input string UndershootReason      = "Insufficient bullish force; possible bearish reversal.";
input string SmallQuarterReason    = "Minor fluctuation.";

一个全局布尔型变量intrusionAlerted被用于确保每个价格侵入事件仅触发一次警报,从而避免当价格在某一价位附近徘徊时反复发出通知。

// Global flag to avoid repeated alerts while price lingers at a level
bool intrusionAlerted = false;

函数 DrawHorizontalLine是该EA可视化输出的核心。该函数接收线条名称、价格、颜色、宽度和样式等参数,并首先检查是否已存在同名线条——若存在,则删除该线条。随后,它在指定价格位置创建一条新的水平线,并相应设置其属性,确保线条延伸至图表右侧。这种模块化方法使得在需要绘制新价位时能够轻松复用该函数。

void DrawHorizontalLine(string name, double price, color lineColor, int width, ENUM_LINE_STYLE style)
{
   if(ObjectFind(0, name) != -1)
      ObjectDelete(0, name);

   if(!ObjectCreate(0, name, OBJ_HLINE, 0, 0, price))
   {
      Print("Failed to create line: ", name);
      return;
   }
   ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor);
   ObjectSetInteger(0, name, OBJPROP_STYLE, style);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
   ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, true);
}

DrawQuarters函数利用DrawHorizontalLine函数绘制基于当前价格计算得出的1000点范围的主要边界线,以及该范围内的其他辅助线。如果启用此功能,EA会将该范围划分为四个等份,绘制“大四分位”线。对于这些线条中的每一条,如果启用了上冲区域设置,该函数还会在主价位线的稍上方和稍下方绘制线条,以指示潜在的上冲或下探情况。当开启该选项时,EA甚至会将该范围进一步细分为更精细的“小四分位”线,从而为您提供有关价格结构的更详细的可视化提示。

void DrawQuarters(double currentPrice)
{
   // Calculate the boundaries of the current 1000-PIP Range
   double lowerMajor = MathFloor(currentPrice / MajorStep) * MajorStep;
   double upperMajor = lowerMajor + MajorStep;

   // Draw Major Whole Number lines (defining the 1000-PIP Range)
   DrawHorizontalLine("MajorLower", lowerMajor, MajorColor, MajorLineWidth, MajorLineStyle);
   DrawHorizontalLine("MajorUpper", upperMajor, MajorColor, MajorLineWidth, MajorLineStyle);

   // Draw Large Quarter lines and their overshoot/undershoot areas if enabled
   if(DrawLargeQuarters)
   {
      double LQIncrement = MajorStep / 4.0;
      for(int i = 1; i < 4; i++)
      {
         double level = lowerMajor + i * LQIncrement;
         string objName = "LargeQuarter_" + IntegerToString(i);
         DrawHorizontalLine(objName, level, LargeQuarterColor, LargeQuarterLineWidth, LargeQuarterLineStyle);

         if(DrawOvershootAreas)
         {
            double offset = MajorStep / 40.0; // approximately 25 pips if MajorStep=0.1000
            DrawHorizontalLine("Overshoot_" + IntegerToString(i) + "_up", level + offset, OvershootColor, OvershootLineWidth, OvershootLineStyle);
            DrawHorizontalLine("Undershoot_" + IntegerToString(i) + "_down", level - offset, OvershootColor, OvershootLineWidth, OvershootLineStyle);
         }
      }
   }

   // Draw Small Quarter lines if enabled (optional, finer divisions)
   if(DrawSmallQuarters)
   {
      double segStep = MajorStep / 10.0;
      double smallQuarter = segStep / 4.0;
      for(int seg = 0; seg < 10; seg++)
      {
         double segStart = lowerMajor + seg * segStep;
         for(int j = 1; j < 4; j++)
         {
            double level = segStart + j * smallQuarter;
            string objName = "SmallQuarter_" + IntegerToString(seg) + "_" + IntegerToString(j);
            DrawHorizontalLine(objName, level, SmallQuarterColor, SmallQuarterLineWidth, SmallQuarterLineStyle);
         }
      }
   }
}

另一个关键函数是CreateOrUpdateLabel,该函数负责在图表上显示文本。此函数会检查标签是否已存在,如果不存在则创建标签。随后,会使用等宽字体(Courier New)更新标签的文本、颜色、字号及其他属性,以确保任何表格数据都能保持整齐对齐。对于更新解释市场状况的评论内容而言,此函数尤为重要。

void CreateOrUpdateLabel(string name, string text, int corner, int xdist, int ydist, color txtColor, int fontSize)
{
   if(ObjectFind(0, name) == -1)
   {
      ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
      ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xdist);
      ObjectSetInteger(0, name, OBJPROP_YDISTANCE, ydist);
      // Set a monospaced font for tabular display (Courier New)
      ObjectSetString(0, name, OBJPROP_FONT, "Courier New");
   }
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_COLOR, txtColor);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
}

当初始化EA时(在OnInit函数中),会在图表的左上角创建一个持久性标签,显示信息为“侵入检测器已初始化”。相反,当EA被移除时(通过OnDeinit函数),它会删除这个标签以保持图表整洁。

int OnInit()
{
   // Create a persistent commentary label in the top-left corner
   CreateOrUpdateLabel("IntrusionCommentary", "Intrusion Detector Initialized", CORNER_LEFT_UPPER, 10, 10, clrWhite, 14);
   return(INIT_SUCCEEDED);
}

该EA的核心在于OnTick函数,该函数会在市场每出现一个新的价格变动(tick)时执行。当EA接收到新的价格变动时,OnTick函数即被触发。该函数的第一步是将一个名为intrusionDetected的标识初始化为false,并使用SymbolInfoDouble(_Symbol, SYMBOL_BID)获取当前市场的买入价。如果返回的价格为0(表示无效或不可用值),该函数则立即退出。

void OnTick()
{
   bool intrusionDetected = false;
   double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   if(currentPrice == 0)
      return;

接下来,EA会调用DrawQuarters函数,并传入当前价格作为参数。调用后在图表上绘制所有关键价位,包括主要价位线、大四分位线,以及(如果已启用)小四分位线,从而提供界定我们价格区间的可视化结构。紧接着,EA会通过以下方式重新计算当前1000点区间的边界:使用MathFloor(currentPrice / MajorStep) * MajorStep确定下界主要价位(lowerMajor),然后加上主要价位间隔(MajorStep)以找到上界主要价位(upperMajor)。

   // Draw the quarter lines first
   DrawQuarters(currentPrice);

为了清晰地说明EA所检测到的情况,需要构建一个表格形式的字符串。该表格以一个表头开头,表头定义了三列:区域、价格和原因。当价格接近各个价位时,这些列用于列出每个价位的重要意义。

   double lowerMajor = MathFloor(currentPrice / MajorStep) * MajorStep;
   double upperMajor = lowerMajor + MajorStep;

接下来的步骤是检查价格是否接近关键价位水平。EA首先会检测价格是否处于下边界(主要支撑位)或上边界(Major Resistance)的指定容差范围内。如果满足任一条件,该函数会在评论表格中添加一行,显示相应的提示信息(使用预定义的信息,如支撑位对应“触及关键支撑位。跌破此位表明价格区间将发生转变”,阻力位也有类似提示信息),并将 intrusionDetected标识设置为true。

// Check for Major Support
if(MathAbs(currentPrice - lowerMajor) <= AlertTolerance)
{
   table += StringFormat("%-18s | %-8s | %s\n", "Major Support", DoubleToString(lowerMajor,4), MajorSupportReason);
   intrusionDetected = true;
}
// Check for Major Resistance
if(MathAbs(currentPrice - upperMajor) <= AlertTolerance)
{
   table += StringFormat("%-18s | %-8s | %s\n", "Major Resistance", DoubleToString(upperMajor,4), MajorResistanceReason);
   intrusionDetected = true;
}

如果启用了大四分位线的绘制功能,EA随后会将价格区间划分为四等份,并逐一遍历这些中间价位。对于每个大四分位价位,其会检查价格是否处于容差范围内;如果处于该范围内,则会在表格中添加一行相应信息(例如“有效突破可能引发下一个250点的行情走势”)。另外,如果启用了上冲区域功能,该函数会计算每个大四分位价位上方和下方的一小段偏移量,并检查价格是否触及这些上冲或下探区域;如果满足条件,同样会在表格中添加一行信息。

if(DrawLargeQuarters)
{
   double LQIncrement = MajorStep / 4.0;
   for(int i = 1; i < 4; i++)
   {
      double level = lowerMajor + i * LQIncrement;
      if(MathAbs(currentPrice - level) <= AlertTolerance)
      {
         table += StringFormat("%-18s | %-8s | %s\n", "Large Quarter", DoubleToString(level,4), LargeQuarterReason);
         intrusionDetected = true;
      }
      if(DrawOvershootAreas)
      {
         double offset = MajorStep / 40.0; // ~25 pips
         double overshootUp = level + offset;
         double undershootDown = level - offset;
         if(MathAbs(currentPrice - overshootUp) <= AlertTolerance)
         {
            table += StringFormat("%-18s | %-8s | %s\n", "Overshoot", DoubleToString(overshootUp,4), OvershootReason);
            intrusionDetected = true;
         }
         if(MathAbs(currentPrice - undershootDown) <= AlertTolerance)
         {
            table += StringFormat("%-18s | %-8s | %s\n", "Undershoot", DoubleToString(undershootDown,4), UndershootReason);
            intrusionDetected = true;
         }
      }
   }
}

如果EA被配置为绘制小四分位线,那么价格区间会被进一步细分。该函数会遍历这些更精细的划分,并执行类似的接近程度检查;每当价格接近其中一个小四分位价位时,就会在表格中添加一行带有“小幅波动”提示信息的记录。

if(DrawSmallQuarters)
{
   double segStep = MajorStep / 10.0;
   double smallQuarter = segStep / 4.0;
   for(int seg = 0; seg < 10; seg++)
   {
      double segStart = lowerMajor + seg * segStep;
      for(int j = 1; j < 4; j++)
      {
         double level = segStart + j * smallQuarter;
         if(MathAbs(currentPrice - level) <= AlertTolerance)
         {
            table += StringFormat("%-18s | %-8s | %s\n", "Small Quarter", DoubleToString(level,4), SmallQuarterReason);
            intrusionDetected = true;
         }
      }
   }
}

如果没有任何价位水平触发价格“侵入”信号(即intrusionDetected标识仍为false),EA会生成一条默认提示信息。该信息会告知用户未检测到显著的价格突破,且市场目前似乎处于盘整状态,同时还会显示当前价格。

   // If no zones were triggered, still provide full information
   if(!intrusionDetected)
   {
      table = StringFormat("No significant intrusion detected.\nCurrent Price: %s\nMarket consolidating within established quarters.\n", DoubleToString(currentPrice,4));
   }

构建完评论表格后,EA会使用CreateOrUpdateLabel函数更新图表标签,确保图表上清晰显示最新的分析结果。最后,如果检测到价格突破且尚未发送过警报(通过intrusionAlerted标识跟踪),EA会触发包含表格内容的警报,并将该标识设置为true,以防止重复通知。为确保所有新创建的对象和更新内容能立即显示,该函数会在结束时调用ChartRedraw(图表重绘)功能。

   // Update the label with the commentary table.
   CreateOrUpdateLabel("IntrusionCommentary", table, CORNER_LEFT_UPPER, 10, 10, clrWhite, 14);

   // Trigger an alert only once per intrusion event.
   if(intrusionDetected && !intrusionAlerted)
   {
      Alert(table);
      intrusionAlerted = true;
   }
   if(!intrusionDetected)
      intrusionAlerted = false;

   ChartRedraw();
}

完整的MQL5代码

//+------------------------------------------------------------------+
//|                                               Intrusion Detector |
//|                               Copyright 2025, Christian Benjamin |
//|                          https://www.mql5.com/en/users/lynnchris |
//+------------------------------------------------------------------+
#property copyright "Christian Benjamin"
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

//---- Input parameters -------------------------------------------------
input double MajorStep          = 0.1000;   // Difference between Major Whole Numbers (defines the 1000-PIP Range)
input bool   DrawLargeQuarters  = true;     // Draw intermediate Large Quarter lines.
input bool   DrawSmallQuarters  = false;    // Draw Small Quarter lines.
input bool   DrawOvershootAreas = true;     // Mark overshoot/undershoot areas for Large Quarter lines.
input double AlertTolerance     = 0.0025;   // Tolerance for detecting a "touch" (e.g., ~25 pips for a pair where 1 pip=0.0001)

//---- Color settings ---------------------------------------------------
input color  MajorColor         = 0x2F4F4F; // Dark Slate Gray for Major lines.
input color  LargeQuarterColor  = 0x8B0000; // Dark Red for Large Quarter lines.
input color  SmallQuarterColor  = 0x00008B; // Dark Blue for Small Quarter lines.
input color  OvershootColor     = clrRed;   // Red for overshoot/undershoot lines.

//---- Line style and thickness settings -------------------------------
input ENUM_LINE_STYLE MajorLineStyle       = STYLE_SOLID;
input int    MajorLineWidth                 = 4;
input ENUM_LINE_STYLE LargeQuarterLineStyle  = STYLE_DOT;
input int    LargeQuarterLineWidth          = 3;
input ENUM_LINE_STYLE OvershootLineStyle     = STYLE_DASH;
input int    OvershootLineWidth             = 1;
input ENUM_LINE_STYLE SmallQuarterLineStyle  = STYLE_SOLID;
input int    SmallQuarterLineWidth          = 1;

//---- Commentary Messages (customizable) -----------------------------
input string MajorSupportReason    = "Key support level. Break below signals range shift.";
input string MajorResistanceReason = "Pivotal resistance. Breakout above may start new range.";
input string LargeQuarterReason    = "Decisive break could trigger next 250-PIP move.";
input string OvershootReason       = "Test of breakout; reversal likely if momentum fails.";
input string UndershootReason      = "Insufficient bullish force; possible bearish reversal.";
input string SmallQuarterReason    = "Minor fluctuation.";

// Global flag to avoid repeated alerts while price lingers at a level
bool intrusionAlerted = false;

//+------------------------------------------------------------------+
//| DrawHorizontalLine: Creates or replaces a horizontal line        |
//+------------------------------------------------------------------+
void DrawHorizontalLine(string name, double price, color lineColor, int width, ENUM_LINE_STYLE style)
  {
   if(ObjectFind(0, name) != -1)
      ObjectDelete(0, name);

   if(!ObjectCreate(0, name, OBJ_HLINE, 0, 0, price))
     {
      Print("Failed to create line: ", name);
      return;
     }
   ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor);
   ObjectSetInteger(0, name, OBJPROP_STYLE, style);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
   ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, true);
  }

//+------------------------------------------------------------------+
//| DrawQuarters: Draws all quarter lines based on the current price |
//+------------------------------------------------------------------+
void DrawQuarters(double currentPrice)
  {
// Calculate the boundaries of the current 1000-PIP Range
   double lowerMajor = MathFloor(currentPrice / MajorStep) * MajorStep;
   double upperMajor = lowerMajor + MajorStep;

// Draw Major Whole Number lines (defining the 1000-PIP Range)
   DrawHorizontalLine("MajorLower", lowerMajor, MajorColor, MajorLineWidth, MajorLineStyle);
   DrawHorizontalLine("MajorUpper", upperMajor, MajorColor, MajorLineWidth, MajorLineStyle);

// Draw Large Quarter lines and their overshoot/undershoot areas if enabled
   if(DrawLargeQuarters)
     {
      double LQIncrement = MajorStep / 4.0;
      for(int i = 1; i < 4; i++)
        {
         double level = lowerMajor + i * LQIncrement;
         string objName = "LargeQuarter_" + IntegerToString(i);
         DrawHorizontalLine(objName, level, LargeQuarterColor, LargeQuarterLineWidth, LargeQuarterLineStyle);

         if(DrawOvershootAreas)
           {
            double offset = MajorStep / 40.0; // approximately 25 pips if MajorStep=0.1000
            DrawHorizontalLine("Overshoot_" + IntegerToString(i) + "_up", level + offset, OvershootColor, OvershootLineWidth, OvershootLineStyle);
            DrawHorizontalLine("Undershoot_" + IntegerToString(i) + "_down", level - offset, OvershootColor, OvershootLineWidth, OvershootLineStyle);
           }
        }
     }

// Draw Small Quarter lines if enabled (optional, finer divisions)
   if(DrawSmallQuarters)
     {
      double segStep = MajorStep / 10.0;
      double smallQuarter = segStep / 4.0;
      for(int seg = 0; seg < 10; seg++)
        {
         double segStart = lowerMajor + seg * segStep;
         for(int j = 1; j < 4; j++)
           {
            double level = segStart + j * smallQuarter;
            string objName = "SmallQuarter_" + IntegerToString(seg) + "_" + IntegerToString(j);
            DrawHorizontalLine(objName, level, SmallQuarterColor, SmallQuarterLineWidth, SmallQuarterLineStyle);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| CreateOrUpdateLabel: Creates or updates a label with given text  |
//+------------------------------------------------------------------+
void CreateOrUpdateLabel(string name, string text, int corner, int xdist, int ydist, color txtColor, int fontSize)
  {
   if(ObjectFind(0, name) == -1)
     {
      ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
      ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xdist);
      ObjectSetInteger(0, name, OBJPROP_YDISTANCE, ydist);
      // Set a monospaced font for tabular display (Courier New)
      ObjectSetString(0, name, OBJPROP_FONT, "Courier New");
     }
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_COLOR, txtColor);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
  }

//+------------------------------------------------------------------+
//| OnInit: Initialization function for the EA                       |
//+------------------------------------------------------------------+
int OnInit()
  {
// Create a persistent commentary label in the top-left corner
   CreateOrUpdateLabel("IntrusionCommentary", "Intrusion Detector Initialized", CORNER_LEFT_UPPER, 10, 10, clrWhite, 14);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| OnDeinit: Deinitialization function for the EA                   |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
// Remove the commentary label on exit
   ObjectDelete(0, "IntrusionCommentary");
  }

//+------------------------------------------------------------------+
//| OnTick: Main function called on every tick                       |
//+------------------------------------------------------------------+
void OnTick()
  {
   bool intrusionDetected = true;
   double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   if(currentPrice == 0)
      return;

// Draw the quarter lines first
   DrawQuarters(currentPrice);

// Calculate boundaries of the current 1000-PIP Range
   double lowerMajor = MathFloor(currentPrice / MajorStep) * MajorStep;
   double upperMajor = lowerMajor + MajorStep;

// Build a tabular commentary string with a header.
   string header = StringFormat("%-18s | %-8s | %s\n", "Zone", "Price", "Reason");
   string separator = "----------------------------------------------\n";
   string table = header + separator;

// Check for Major Support
   if(MathAbs(currentPrice - lowerMajor) <= AlertTolerance)
     {
      table += StringFormat("%-18s | %-8s | %s\n", "Major Support", DoubleToString(lowerMajor,4), MajorSupportReason);
      intrusionDetected = true;
     }
// Check for Major Resistance
   if(MathAbs(currentPrice - upperMajor) <= AlertTolerance)
     {
      table += StringFormat("%-18s | %-8s | %s\n", "Major Resistance", DoubleToString(upperMajor,4), MajorResistanceReason);
      intrusionDetected = true;
     }

// Check Large Quarter Levels and Overshoot/Undershoot Zones
   if(DrawLargeQuarters)
     {
      double LQIncrement = MajorStep / 4.0;
      for(int i = 1; i < 4; i++)
        {
         double level = lowerMajor + i * LQIncrement;
         if(MathAbs(currentPrice - level) <= AlertTolerance)
           {
            table += StringFormat("%-18s | %-8s | %s\n", "Large Quarter", DoubleToString(level,4), LargeQuarterReason);
            intrusionDetected = true;
           }
         if(DrawOvershootAreas)
           {
            double offset = MajorStep / 40.0; // ~25 pips
            double overshootUp = level + offset;
            double undershootDown = level - offset;
            if(MathAbs(currentPrice - overshootUp) <= AlertTolerance)
              {
               table += StringFormat("%-18s | %-8s | %s\n", "Overshoot", DoubleToString(overshootUp,4), OvershootReason);
               intrusionDetected = true;
              }
            if(MathAbs(currentPrice - undershootDown) <= AlertTolerance)
              {
               table += StringFormat("%-18s | %-8s | %s\n", "Undershoot", DoubleToString(undershootDown,4), UndershootReason);
               intrusionDetected = true;
              }
           }
        }
     }

// Check Small Quarter Levels (if enabled)
   if(DrawSmallQuarters)
     {
      double segStep = MajorStep / 10.0;
      double smallQuarter = segStep / 4.0;
      for(int seg = 0; seg < 10; seg++)
        {
         double segStart = lowerMajor + seg * segStep;
         for(int j = 1; j < 4; j++)
           {
            double level = segStart + j * smallQuarter;
            if(MathAbs(currentPrice - level) <= AlertTolerance)
              {
               table += StringFormat("%-18s | %-8s | %s\n", "Small Quarter", DoubleToString(level,4), SmallQuarterReason);
               intrusionDetected = true;
              }
           }
        }
     }

// If no zones were triggered, still provide full information
   if(!intrusionDetected)
     {
      table = StringFormat("No significant intrusion detected.\nCurrent Price: %s\nMarket consolidating within established quarters.\n", DoubleToString(currentPrice,4));
     }

// Update the label with the commentary table.
   CreateOrUpdateLabel("IntrusionCommentary", table, CORNER_LEFT_UPPER, 10, 10, clrWhite, 14);

// Trigger an alert only once per intrusion event.
   if(intrusionDetected && !intrusionAlerted)
     {
      Alert(table);
      // Alternatively, you could use: PlaySound("alert.wav");
      intrusionAlerted = true;
     }
   if(!intrusionDetected)
      intrusionAlerted = false;

   ChartRedraw();
  }
//+------------------------------------------------------------------+


成果

这里,我将展示我在真实市场环境中测试该工具后所获得的结果,尽管我是使用模拟账户进行测试的。下图展示了新西兰元(NZD)对美元(USD)的汇率走势。价格接近下探区域,会触发警报。 

该警报提供了关键信息,包括所识别的区域,在此案例中为下探区域。检测到该区域的具体价格水平为0.5725。此外,警报还包含了对该价格水平下市场状况的分析,指出看涨动能不足,并存在看跌反转的可能性。

新西兰元兑美元

图例3. 新西兰元与美元

以下是MetaTrader 5中“Experts”选项卡记录的信息:

2025.02.25 16:55:37.188 Intrusion Detector EA (NZDUSD,H1)       Alert: Zone               | Price    | Reason
2025.02.25 16:55:37.188 Intrusion Detector EA (NZDUSD,H1)       ----------------------------------------------
2025.02.25 16:55:37.188 Intrusion Detector EA (NZDUSD,H1)       Undershoot         | 0.5725   | Insufficient bullish force; possible bearish reversal.
2025.02.25 16:55:37.188 Intrusion Detector EA (NZDUSD,H1)       

让我们来看一下在此次检测及进一步分析后所执行的交易。

已执行的交易

图例3. 交易测试

以下是我迅速平仓后,市场的最终走势情况。

市场走势

图例5. 市场走势

下图6是一个GIF动图,展示了我对美元兑加元(USD/CAD)货币对进行的一项测试,其中识别出了两个价格对上冲区域和大四分位价位产生反应的区间。

美元兑加元

图例6. 美元兑加元



结论

我们的EA堪称强大的分析助手,专为按照四分位理论监测价格区间而设计。对于将四分位理论融入市场分析的交易者而言,这一工具很有价值。基于我们的大量测试,该EA在检测关键价格区间、及时发出警报以及提供有效的市场背景监测方面表现出色。这一成果标志着利用四分位理论实现市场分析自动化迈出了重要的一步。此前,我们专注于实现四分位绘制的自动化,如今已进阶至实时四分位监测阶段。这一改进确保交易者在价格与这些关键价位产生交互时能及时收到通知,并附带对潜在市场走势的简要说明。

然而,这并非我们探索之旅的终点。在将四分位理论应用于市场自动化方面,期待涌现出更多的创新。话虽如此,我鼓励所有使用这一工具的交易者结合自身策略,而非仅依赖所提供的信号。将自动化工具与个人专业经验相结合的全面方法,有助于做出更明智的交易决策。

日期工具名 描述版本 更新 备注
01/10/24图表展示器以重影效果覆盖前一日价格走势的脚本1.0初始版本工具1
18/11/24分析评论以表格形式提供前一日的信息,并预测市场的未来方向1.0初始版本工具2
27/11/24分析大师每两小时定期更新市场指标 1.01第二个版本工具3
02/12/24分析预测 集成Telegram通知功能,每两小时定时更新市场指标1.1第三个版本工具4
09/12/24波动率导航仪该EA通过布林带、RSI和ATR三大指标综合分析市场状况1.0初始版本工具5
19/12/24均值回归信号收割器 运用均值回归策略分析市场并提供交易信号 1.0 初始版本 工具6 
9/01/25 信号脉冲 多时间框架分析器1.0 初始版本 工具7 
17/01/25 指标看板 带分析按钮的控制面板 1.0 初始版本工具8 
21/01/25外部数据流通过外部库进行分析1.0 初始版本工具9 
27/01/25VWAP成交量加权平均价  1.3 初始版本 工具10 
02/02/25 Heikin Ashi 平滑趋势和反转信号识别 1.0 初始版本 工具11
04/02/25 FibVWAP 通过python分析生成信号 1.0 初始版本 工具12
14/02/25 RSI背离 价格走势与RSI背离 1.0 初始版本 工具13 
17/02/25 抛物线止损与反转指标 (PSAR) 自动化PSAR策略1.0初始版本 工具14
20/02/25 四分位绘制脚本 在图表上绘制四分位水平 1.0 初始版本 工具15 
27/02/25侵入探测器当价格触及四分位价位水平时检测并发出警报1.0初始版本工具16

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17321

附加的文件 |
事后交易分析:在策略测试器中选择尾随停止和新的止损位 事后交易分析:在策略测试器中选择尾随停止和新的止损位
我们继续在策略测试器中分析已完结成交的主题,以便提升交易品质。我们看看使用不同的尾随停止如何改变我们现有的交易结果。
卡尔曼滤波器在外汇均值回归策略中的应用 卡尔曼滤波器在外汇均值回归策略中的应用
卡尔曼滤波器是一种递归算法,在算法交易中用于通过滤除价格走势中的噪声来估计金融时间序列的真实状态。它能够根据新的市场数据动态更新预测,这使得它在均值回归等自适应策略中极具价值。本文首先介绍卡尔曼滤波器,涵盖其计算方法和实现方式。接下来,我们以外汇领域一个经典的均值回归策略为例,应用该滤波器。最后,我们通过将卡尔曼滤波器与移动平均线(MA)在外汇不同货币对上进行比较,开展各种统计分析。
从基础到中级:模板和类型名称(一) 从基础到中级:模板和类型名称(一)
在本文中,我们开始考虑许多初学者避免的概念之一。这与模板不是一个容易的话题有关,因为许多人不理解模板的基本原理:函数和过程的重载。
市场模拟(第二部分):跨期订单(二) 市场模拟(第二部分):跨期订单(二)
与上一篇文章中所做的不同,这里我们将使用 EA 交易来测试选择选项。虽然这还不是最终的解决方案,但目前已经足够了。在本文的帮助下,您将能够理解如何实现一种可能的解决方案。