English Русский Deutsch 日本語
preview
从新手到专家:自动几何分析系统

从新手到专家:自动几何分析系统

MetaTrader 5示例 |
89 0
Clemence Benjamin
Clemence Benjamin

目录:

  1. 概述
  2. 背景
  3. 概览
  4. 实现
  5. 结果与测试
  6. 结论

概述

今天的讨论旨在解决运用几何方法分析蜡烛图形态的难题。在我们最近的文章,从新手到专家:蜡烛图编程中,我们专注于识别简单的蜡烛图形态,这些形态通常只由几根烛形组成。然而,当处理更大的烛形序列时,形态识别会变得更加复杂,因为随着序列的延长,一致性往往会降低。

不过,一个显而易见的结论是,我们仍然可以从数据中识别出主要的高点和低点。将这些点联系起来可以帮助我们更有效地判断趋势。

当我们最初学习外汇交易时,许多教育资源都介绍了三角形形态和其他市场价格走势中的几何形态的概念。事实上,几何学也存在于市场中 —— 它可以提供市场运行情况的简化和直观的概括。

大多数交易者习惯于通过在图表上绘制趋势线或放置几何对象来手动识别这些形状。今天,我们的目标是通过利用 MQL5 自动化这一过程,消除手动干预的需要,实现更快、更一致的分析,从而更进一步。


背景

面向对象编程 (OOP) 的优势在于它能够高效地建模和解决现实世界的计算问题。MQL5 是一种源自 C++ 的语言,它继承了这一优势,是致力于开发交易算法的宝贵资产。考虑到当前的问题,由于 OOP 提供的结构和模块化优势,我们已经具备了显著的优势。

在本节中,我们将在探索用于识别交易中几何形状的传统方法之前,简要定义与我们的主题相关的关键术语。技术分析中存在许多形状,但我们将重点关注两个常见的例子:矩形和三角形。

什么是几何学?

几何学是数学的一个分支,它研究空间的性质和关系。这包括对距离、形状、大小、角度以及图形相对位置的研究。几何学为解释物理世界提供了一个强大的框架,使我们能够对空间关系进行建模和分析。

为什么几何学在交易中如此重要?

几何学为理解和解释市场行为提供了一个视觉和结构框架。交易者通常依赖于价格行为中出现的形态和形状,而这些形态本质上是几何的。它有助于形态识别,以下是大多数交易者熟知的常见形态列表:

  • 三角形(上升三角形、下降三角形、对称三角形)
  • 矩形(稳定区域)
  • 通道(平行趋势线)
  • 头肩、双顶/底 —— 都取决于几何对称性

这些形态有助于交易者:

  • 识别潜在的突破或反转点
  • 衡量趋势强度
  • 设置入场点/出场点

在这张表格中,我汇总了将几何分析应用于市场价格数据时需要考虑的关键因素。

方面 几何的作用
视觉清晰度 有助于将复杂的市场数据简化为可识别的形式
决策支持 形状指导入场、出场和交易设置
客观性 通过使用精确的空间逻辑减少猜测
自动化 能够通过算法检测形态和关键级别
市场心理学 形状通常反映交易者的集体行为(例如,三角形表示压缩)。

让我们来看看三角形和矩形几何图形在交易中的传统应用。我将概述不同三角模式通常对价格行为的影响,但我不会深入探讨具体的交易策略。这是因为我观察到,许多普遍接受的规则在实际市场条件下往往面临无效。虽然这些形态在一定程度上确实有效,但密切关注几何结构形成之前的价格行为是明智的。

例如,虽然人们普遍预期上升三角形会导致看涨突破,但我见过很多相反的情况。这些形状通常代表市场中的决策点,但不是保证。因此,虽然这种形态可能暗示着某种方向性的走势,但至关重要的是要将你的解读与形态出现之前的实际价格环境相一致。仅依赖教科书预期而不考虑市场背景可能会导致误导性结论。

三角形

三角形是由三条边和三个角(顶点)定义的基本几何形状。在外汇交易中,三角形形态表明潜在的趋势延续或反转。三角形形态的主要类型有:

1.对称三角形:

表示波动性降低的盘整期;它通常预示着任何一个方向的突破。

对称三角形

对称三角形

2.上升三角形:

以平坦或略微倾斜的阻力位和上升的支撑为特征;通常是看涨形态,预示着潜在的向上突破。

上升三角形

上升三角形

3.下降三角形:

具有平坦或略微倾斜的支撑线和下降的阻力;通常看跌,暗示向下突破。

下降三角形

下降三角形

矩形

矩形是一种四边形几何图形,其对边相等且平行。它的每个角都是直角(每个角都是 90 度)。 在外汇和技术分析中,矩形(也称为交易区间或盘整区)是一种图表形态,其中价格在水平支撑位和阻力位之间来回移动。它表明,在市场可能延续之前的趋势或逆转之前,有一段犹豫不决的时期。它在 MetaTrader 5 终端的市场分析工具中提供。

以下是主要特点:

  • 价格在明显的上方阻力线和下方支撑线之间波动。
  • 在图表上,该形态看起来像一个矩形或方框。
  • 突破阻力位或跌破支撑位通常预示着新趋势的开始。

这个概念也可以使用内置的平行线工具来说明,水平对齐以表示支撑和阻力水平。由于它由两条平行线组成,因此有效地突出了合并期间的价格范围。然而,矩形仍然是标记盘整区域的首选,因为它在视觉上“框定”了价格横盘整理的特定时期。需要记住的是,盘整只是一个暂时的阶段 —— 最终,市场会突破这个区间,继续其趋势或反转方向。

矩形结构

矩形结构

向下突破矩形

向下突破矩形结构


概览

现在我们已经为我们的讨论奠定了理论基础,下面是对实际实现的概述。我们将使用模块化编程技术来构建几何结构检测系统的组件。具体来说,我们将开发单独的类来检测市场结构中的三角形和矩形结构。这些只是初步示例 —— 该方法具有可扩展性,允许我们根据需要添加对其他形状的支持。这是使用 MQL5 面向对象和模块化编程功能的关键优势之一,它使我们能够用干净、可维护的代码解决复杂的现实世界问题。

本节创建的头文件可以轻松集成到主 EA 交易或指标中。此外,它们可以在多个项目中重复使用,从而提高一致性和开发效率。

在我的方法中,每个几何形状都需要价格数据中的一组参考点。通常我们需要两个高点和两个低点。这些点通过线条连接起来形成各种形状。例如,两点可以构成一条直线,两条直线将来可以相交于一点,从而形成一个三角形。另一方面,当阻力水平和支撑水平上都有两次接触时,就可以识别出一个矩形。一旦满足这些条件,就可以通过连接相应的参考点来绘制几何形状。

为了帮助您理解,我在下面附上了一些插图,演示如何使用计算机逻辑在图表上构建这些形状。我们通常至少需要四个关键点来勾勒出每个形状。在图像中,我们首先确定点 a、b、cd —— 这些点是我们形状结构的基础锚点。根据这些点,可以预先预测形状。红色虚线代表理论预期,表明市场可能会对预期形状边界做出怎样的反应。价格与这些线条的互动方式可以揭示未来价格走势的重要线索,例如潜在的突破或反弹。

三角形构成

三角形的理论检测

在上图中的三角形示意图中,波动点 ad 被确定为参考点,由此可以构造一个三角形,汇聚线相交于点 X。ef 代表理论上的未来接触点 —— 它们是推测性的,可能不会完全按照所示的方式发生。它们的目的是帮助我们理解形态,以便我们能够将想法转化为代码。同样的概念也适用于下面的矩形图,其中的结构是通过类似的逻辑来检测的;然而,关键区别在于它的边是平行的。

矩形构成

矩形结构检测


实现

从这个阶段开始,真正的开发工作才正式启动。我们将首先创建一个名为 GeometricPatternDetector.mqh 的头文件,其中将包含检测几何图案所需的所有必要类。一旦标题准备就绪,我们将继续开发一个 EA 交易,以演示如何有效地利用这些类。现在,让我们通过以下步骤来了解所有环节是如何衔接起来的。

包含必要的头文件

GeometricPatternDetector.mqh 的最顶端,我们引入了三个必要的 MQL5 库,为我们的检测器提供它所需的所有工具。首先,我们包含 arrays\ArrayObj.mqh ,这样我们就可以使用 CArrayObj(一个动态的、面向对象的数组类)来存储我们的波动枢轴和形态结果,而无需处理原始指针或手动内存管理。

接下来, Indicators\Indicators.mqh 引入了 MetaTrader 的内置指标函数,例如 iATR、CopyHigh、CopyLow 和 CopyTime,使我们能够无缝地获取历史价格数据并计算平均真实范围。最后, Math\Stat\Math.mqh 提供了诸如 M_PI 之类的数学常数和诸如 MathArctan 之类的实用函数,我们在模式检测算法中使用这些函数来计算斜率、角度和平坦度容差。这些要素共同构成了编写稳健、易读、易维护代码的基础。

#include <Arrays\ArrayObj.mqh>       // For CArrayObj: dynamic arrays of objects 
#include <Indicators\Indicators.mqh> // For iATR, CopyHigh, CopyLow, CopyTime
#include <Math\Stat\Math.mqh>        // For M_PI, MathArctan, and other math utilities

容器类

GeometricPatternDetector.mqh 头文件首先定义了两个简单的容器类:SwingPointPatternResult 。两者都继承自 MQL5 的基类 CObjectSwingPoint 类包含时间戳、价格以及一个布尔标志,该标志指示枢轴点是高点还是低点。这种设计使我们能够在一个对象数组中收集和管理各个市场转折点。

PatternResult 类封装了描述检测到的形态所需的所有信息 —— 即交易品种、时间周期、形态类型、三个定义“顶点”以及标签应放置的位置。通过将这些数据元素打包到对象中,检测器代码保持简洁一致,依靠 CArrayObj 进行存储,而不是手动并行使用基本元素数组。

// SwingPoint: holds one market pivot
class SwingPoint : public CObject {
public:
   datetime time;
   double   price;
   bool     isHigh;
   SwingPoint(datetime t=0,double p=0.0,bool h=false)
     : time(t), price(p), isHigh(h) {}
};

// PatternResult: holds the details of one detected pattern
class PatternResult : public CObject {
public:
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   ENUM_PATTERN_TYPE type;
   datetime          detectionTime;
   datetime          labelTime;
   double            labelPrice;
   datetime          vertex1Time;
   double            vertex1Price;
   datetime          vertex2Time;
   double            vertex2Price;
   datetime          vertex3Time;
   double            vertex3Price;
   PatternResult(const string _s,const ENUM_TIMEFRAMES _tf,const ENUM_PATTERN_TYPE _t,
                 datetime lt,double lp,
                 datetime v1t,double v1p,
                 datetime v2t,double v2p,
                 datetime v3t,double v3p)
     : symbol(_s), timeframe(_tf), type(_t), detectionTime(TimeCurrent()),
       labelTime(lt), labelPrice(lp),
       vertex1Time(v1t), vertex1Price(v1p),
       vertex2Time(v2t), vertex2Price(v2p),
       vertex3Time(v3t), vertex3Price(v3p) {}
};

检测器类结构

头文件的核心是 CGeometricPatternDetector 类。其私有部分声明了用于存储枢轴对象、配置参数(例如波动回溯、ATR 乘数和最小接触点)以及防止重复绘制所需的状态(最后一个三角形和矩形的名称和哈希值,以及它们的检测时间)的成员变量。

四个辅助方法 —— IsNewBar、UpdateSwingPointsCalculateATRGetSwingHash —— 被声明为私有方法。这些实用程序负责识别新柱、从最近的高点/低点数据中提取波动枢轴点、通过 ATR 计算市场敏感度指标以及为每组四个枢轴点生成唯一的字符串键等机制。它们共同支持两个主要的公有检测例程 DetectTriangleDetectRectangle ,以及一个将所有内容联系起来的 Update 方法,还有用于检索结果和清理资源的 GetLastPatternClearPatterns 方法。

class CGeometricPatternDetector {
private:
   CArrayObj   m_swings;
   int         m_swingLookback;
   double      m_atrMultiplier;
   int         m_minTouchPoints;

   string      m_lastTriangle;
   string      m_lastSwingHash;
   datetime    m_lastTriangleTime;

   string      m_lastRectangle;
   string      m_lastRectangleHash;
   datetime    m_lastRectangleTime;

   bool    IsNewBar(const string sym,const ENUM_TIMEFRAMES tf);
   void    UpdateSwingPoints(const string sym,const ENUM_TIMEFRAMES tf);
   double  CalculateATR(const string sym,const ENUM_TIMEFRAMES tf,const int period=14);
   string  GetSwingHash(SwingPoint *p1,SwingPoint *p2,SwingPoint *p3,SwingPoint *p4);

public:
   CGeometricPatternDetector(int swingLookback=3,double atrMultiplier=1.5,int minTouchPoints=2);
   ~CGeometricPatternDetector();

   void Update(const string sym,const ENUM_TIMEFRAMES tf);
   void DetectTriangle(const string sym,const ENUM_TIMEFRAMES tf);
   void DetectRectangle(const string sym,const ENUM_TIMEFRAMES tf);
   ENUM_PATTERN_TYPE GetLastPattern();
   void ClearPatterns();

   CArrayObj m_currentPatterns;
};

波动点提取

摆动点提取方法 UpdateSwingPoints 在每个新柱上都会被调用。它会从图表中复制一个包含高点、低点和时间戳的窗口,然后将该窗口中的中心柱指定为波动高点(如果它的价格高于两个相邻柱),或者指定为波动低点(如果它的价格低于两个相邻柱)。每个已确认的枢轴点都被包装在一个 SwingPoint 对象中,并添加到 m_swings 数组中。

当数组增长超过固定容量时,最旧的条目将被丢弃。这份不断变化的枢轴点列表构成了三角形和矩形检测的基础,确保只考虑最近和最重要的市场转折点。

void CGeometricPatternDetector::UpdateSwingPoints(const string sym,const ENUM_TIMEFRAMES tf)
{
   int bars = m_swingLookback*2 + 1;
   double highs[], lows[];
   datetime times[];
   ArraySetAsSeries(highs,true);
   ArraySetAsSeries(lows,true);
   ArraySetAsSeries(times,true);

   if(CopyHigh(sym,tf,0,bars,highs)<bars ||
      CopyLow(sym,tf,0,bars,lows)<bars   ||
      CopyTime(sym,tf,0,bars,times)<bars)
   {
      Print("Error: Failed to copy price/time data");
      return;
   }

   int mid = m_swingLookback;
   datetime t = times[mid];
   double h = highs[mid], l = lows[mid];

   bool isH = true;
   for(int i=1; i<=m_swingLookback; i++)
      if(h<=highs[mid-i] || h<=highs[mid+i]) { isH=false; break; }
   if(isH) {
      m_swings.Add(new SwingPoint(t,h,true));
      Print("Swing High detected at ",TimeToString(t)," Price: ",h);
   }

   bool isL = true;
   for(int i=1; i<=m_swingLookback; i++)
      if(l>=lows[mid-i] || l>=lows[mid+i]) { isL=false; break; }
   if(isL) {
      m_swings.Add(new SwingPoint(t,l,false));
      Print("Swing Low detected at ",TimeToString(t)," Price: ",l);
   }

   while(m_swings.Total()>50) {
      delete (SwingPoint*)m_swings.At(0);
      m_swings.Delete(0);
   }
}

CalculateATR 封装了 MQL5 内置的 ATR 指标,以提供对市场敏感的幅度单位,而 GetSwingHash 将四个枢轴时间和价格连接成一个字符串键。ATR 值可以调整三角形的“平面度”和矩形的对齐度的公差。哈希字符串确保每个唯一的枢轴集在每个锁定周期内只被抽取一次。

double CGeometricPatternDetector::CalculateATR(const string sym,const ENUM_TIMEFRAMES tf,const int period)
{
   int h = iATR(sym,tf,period);
   if(h==INVALID_HANDLE) { Print("ATR handle error"); return 0; }
   double buf[]; ArraySetAsSeries(buf,true);
   if(CopyBuffer(h,0,0,1,buf)!=1) { Print("ATR copy error"); return 0; }
   return buf[0];
}

string CGeometricPatternDetector::GetSwingHash(SwingPoint *p1,SwingPoint *p2,SwingPoint *p3,SwingPoint *p4)
{
   return TimeToString(p1.time)+"*"+DoubleToString(p1.price,8)+"*"
        + TimeToString(p2.time)+"*"+DoubleToString(p2.price,8)+"*"
        + TimeToString(p3.time)+"*"+DoubleToString(p3.price,8)+"*"
        + TimeToString(p4.time)+"_"+DoubleToString(p4.price,8);
}

三角形检测逻辑

三角形检测程序严格寻找从低到高和从低到高的连续四次波动。在对每条腿强制执行最小柱跨度后,它通过将价格差异除以时间差异来计算下侧和上侧的斜率。基于 ATR 的容差决定了三角形的顶部或底部是否足够平坦,从而符合下降或上升的定义;如果两侧都不够平坦,则该模式被归类为对称模式。

然后通过解析方法求出两条边线的交点,从而找到三角形在未来某个时间的顶点。使用两个枢轴点和计算出的顶点绘制单个 OBJ_TRIANGLE 对象,并创建和存储 PatternResult 对象。为了防止闪烁,检测器将四个枢轴的哈希值与最后绘制的形态进行比较,并锁定固定数量的柱形以内相同形状的重绘。

void CGeometricPatternDetector::DetectTriangle(const string sym,const ENUM_TIMEFRAMES tf)
{
   int tot = m_swings.Total();
   if(tot < 4) return;
   ulong barSec = PeriodSeconds(tf);
   ulong minSpan = (ulong)m_swingLookback * barSec;

   for(int i=0; i<=tot-4; i++)
   {
      SwingPoint *p1 = (SwingPoint*)m_swings.At(i);
      SwingPoint *p2 = (SwingPoint*)m_swings.At(i+1);
      SwingPoint *p3 = (SwingPoint*)m_swings.At(i+2);
      SwingPoint *p4 = (SwingPoint*)m_swings.At(i+3);

      if(!( !p1.isHigh && p2.isHigh && !p3.isHigh && p4.isHigh )) continue;
      if((ulong)(p2.time-p1.time)<minSpan ||
         (ulong)(p3.time-p2.time)<minSpan ||
         (ulong)(p4.time-p3.time)<minSpan) continue;

      double m_low  = (p3.price - p1.price) / double(p3.time - p1.time);
      double m_high = (p4.price - p2.price) / double(p4.time - p2.time);
      double tolFlat = CalculateATR(sym,tf,14) * m_atrMultiplier;

      bool lowerFlat = MathAbs(p3.price-p1.price) < tolFlat;
      bool upperFlat = MathAbs(p4.price-p2.price) < tolFlat;

      ENUM_PATTERN_TYPE type;
      if(lowerFlat && m_high < 0)             type = PATTERN_TRIANGLE_DESCENDING;
      else if(upperFlat && m_low > 0)         type = PATTERN_TRIANGLE_ASCENDING;
      else                                    type = PATTERN_TRIANGLE_SYMMETRICAL;

      double denom = m_low - m_high;
      if(MathAbs(denom)<1e-12) continue;
      double num = (p2.price - p1.price) + (m_low*p1.time - m_high*p2.time);
      double tx  = num/denom;
      double px  = p1.price + m_low*(tx-p1.time);

      datetime latest = MathMax(p1.time,p2.time);
      if(tx<=latest || tx>TimeCurrent()+barSec*50) continue;

      if(StringLen(m_lastTriangle)>0)
         ObjectDelete(0,m_lastTriangle);

      string base = "GPD_"+sym+"_"+EnumToString(tf)+"_"+TimeToString(TimeCurrent(),TIME_SECONDS);
      long cid    = ChartID();
      m_lastTriangle = base+"_T";

      ObjectCreate(cid,m_lastTriangle,OBJ_TRIANGLE,0,
                   p1.time,p1.price,
                   p2.time,p2.price,
                   tx,      px);
      ObjectSetInteger(cid,m_lastTriangle,OBJPROP_COLOR,clrOrange);
      ObjectSetInteger(cid,m_lastTriangle,OBJPROP_WIDTH,2);
      ObjectSetInteger(cid,m_lastTriangle,OBJPROP_FILL,false);

      m_lastSwingHash    = GetSwingHash(p1,p2,p3,p4);
      m_lastTriangleTime = TimeCurrent();
      m_currentPatterns.Add(new PatternResult(
         sym,tf,type,
         latest,(p2.price+p4.price)/2.0,
         p1.time,p1.price,
         p2.time,p2.price,
         (datetime)tx,px
      ));

      ChartRedraw(cid);
      break;
   }
}

矩形检测逻辑

矩形检测遵循类似的枢轴序列,但应用更严格的约束条件。它首先识别出同一低-高到低-高形态中的四个波动,要求每个波动(低到高和第二个低到高)至少跨越五个柱,并超过 ATR 缩放幅度阈值。矩形的底部与两个低点的平均值对齐,前提是它们在 ATR 范围内。如果两个最高点也在该范围内对齐,则将最高点设置为它们的平均值;否则,使用等于一个 ATR 单位的最小高度,以便矩形保持可见。

使用 OBJ_RECTANGLE 绘制矩形,从最早的枢轴点时间到正好二十个柱之后,防止无限增长。唯一的枢轴哈希确保每个不同的矩形在每个锁定周期内只绘制一次,并且其详细信息记录在 PatternResult 中。

void CGeometricPatternDetector::DetectRectangle(const string sym,const ENUM_TIMEFRAMES tf)
{
   int tot = m_swings.Total();
   if(tot < 4) return;

   ulong barSec    = PeriodSeconds(tf);
   ulong minSpan5  = 5 * barSec;                        
   double tolATR   = CalculateATR(sym,tf,14) * m_atrMultiplier;

   for(int i=0; i<=tot-4; i++)
   {
      SwingPoint *a = (SwingPoint*)m_swings.At(i);
      SwingPoint *b = (SwingPoint*)m_swings.At(i+1);
      SwingPoint *c = (SwingPoint*)m_swings.At(i+2);
      SwingPoint *d = (SwingPoint*)m_swings.At(i+3);

      if(!( !a.isHigh && b.isHigh && !c.isHigh && d.isHigh )) continue;
      if((ulong)(b.time - a.time) < minSpan5 ||
         (ulong)(d.time - c.time) < minSpan5) continue;
      if(MathAbs(b.price - a.price) < tolATR ||
         MathAbs(d.price - c.price) < tolATR) continue;
      if(MathAbs(a.price - c.price) > tolATR) continue;

      bool highAligned = MathAbs(b.price - d.price) < tolATR;
      double lowP  = (a.price + c.price) / 2.0;
      double highP = highAligned ? (b.price + d.price)/2.0 : lowP + tolATR;

      datetime leftT  = MathMin(a.time, c.time);
      datetime rightT = leftT + (datetime)(20 * barSec);

      string rh = TimeToString(leftT,TIME_SECONDS) + "_" +
                  DoubleToString(lowP,8) + "_" +
                  DoubleToString(highP,8);
      datetime lockT = m_lastRectangleTime + (datetime)(40 * barSec);
      if(rh == m_lastRectangleHash && TimeCurrent() < lockT) return;

      if(StringLen(m_lastRectangle) > 0)
         ObjectDelete(0,m_lastRectangle);

      string base = "GPD_"+sym+"_"+EnumToString(tf)+"_"+TimeToString(TimeCurrent(),TIME_SECONDS);
      long cid    = ChartID();
      m_lastRectangle = base+"_Rect";

      ObjectCreate(cid,m_lastRectangle,OBJ_RECTANGLE,0,
                   leftT,   highP,
                   rightT,  lowP);
      ObjectSetInteger(cid,m_lastRectangle,OBJPROP_COLOR, clrBlue);
      ObjectSetInteger(cid,m_lastRectangle,OBJPROP_WIDTH, 2);
      ObjectSetInteger(cid,m_lastRectangle,OBJPROP_FILL,  false);

      m_currentPatterns.Add(new PatternResult(
         sym,tf,PATTERN_RECTANGLE,
         leftT,(highP+lowP)/2.0,
         leftT,highP,
         leftT,lowP,
         rightT,lowP
      ));
      m_lastRectangleHash = rh;
      m_lastRectangleTime = TimeCurrent();

      ChartRedraw(cid);
      break;
   }
}

在 EA 交易中使用头文件

将此头文件集成到 EA 交易中非常简单。我们只需将其包含在内,使用我们首选的参数实例化一个 CGeometricPatternDetector ,并在 OnTick 事件处理程序中调用其 Update(Symbol(), _Period) 方法。每次更新后, GetLastPattern 都会显示是否检测到了新的三角形或矩形,您可以检查 detector.m_currentPatterns 以检索完整详细信息、发出警报或放置图表标签。

EA 不需要了解任何底层几何结构或枢轴逻辑;它只需驱动检测器并对高级结果做出反应即可。这种分离 —— 在头文件中声明,在同一文件中详细实现,以及在 EA 中简单使用 —— 展示了 MQL5 中的面向对象设计如何封装复杂性并生成可重用、可维护的代码。让我们一起来看看下面的详细步骤。

头文件包含和全局状态

GeometryAnalyzerEA.mq5 的最顶部,我们包含了检测器头文件,以便 EA 交易可以访问 CGeometricPatternDetector 类及其支持类型。随后,我们实例化一个具有选定参数的单个检测器对象 —— 枢轴回溯三根柱形,容差 ATR 乘数为 1.5,形态最小接触点为两个。我们还声明了三个全局变量: lastAlerted 记住我们发出警报的最后一个形态的类型,这样我们就不会在同一根柱形上重复它; lastBarTime 跟踪新柱形何时到达; lastLabelName 保存我们放置的文本标签的名称,以便在出现新形态时将其删除。

#include <GeometricPatternDetector.mqh>

//–– detector instance & state
CGeometricPatternDetector  detector(3, 1.5, 2);
ENUM_PATTERN_TYPE          lastAlerted    = PATTERN_NONE;
datetime                   lastBarTime    = 0;
string                     lastLabelName  = "";

初始化和清理

OnInit 函数在 EA 启动时运行一次。这里我们只是简单地打印一条消息来确认初始化,不过如果需要的话,你也可以启动一个计时器或者分配资源。相反,当 EA 被移除或图表被关闭时, OnDeinit 会执行。在该例程中,我们通过 detector.ClearPatterns() 清除所有绘制的图案,并按名称删除任何残留的文本标签。最后,我们会记录去初始化的原因,以便更容易诊断 EA 停止运行的原因。

int OnInit()
{
   Print("GeometryAnalyzerEA initialized");
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   detector.ClearPatterns();
   if(StringLen(lastLabelName) > 0)
      ObjectDelete(0, lastLabelName);
   Print("GeometryAnalyzerEA deinitialized, reason=", reason);
}

检测到新柱形

OnTick 函数内部,第一步是确定传入的分时报价是否属于新形成的柱形。我们获取当前柱形的开盘时间,并将其与我们存储的 lastBarTime 进行比较。如果是新柱形,我们会重置 lastAlerted,以便再次触发形态,并删除之前的文本标签以保持图表干净。如果该柱不是新柱,我们就直接返回,不做任何其他操作,确保形态检测每个柱只运行一次。

void OnTick()
{
   datetime curBar = iTime(Symbol(), _Period, 0);
   bool isNewBar  = (curBar != lastBarTime);
   if(isNewBar)
   {
      lastBarTime = curBar;
      lastAlerted = PATTERN_NONE;
      if(StringLen(lastLabelName) > 0)
      {
         ObjectDelete(0, lastLabelName);
         lastLabelName = "";
      }
   }
   if(!isNewBar)
      return;
   // …
}

运行检测器

一旦我们确认出现新的柱形,我们就调用检测器的 Update 方法,传入当前交易品种和时间周期。这个单一的调用会更新波动点,并在底层执行三角形和矩形检测例程。Update 返回后,我们查询 GetLastPattern() 以查看是否在此柱上找到了有效的形态。如果没有出现新的形态,或者与我们已经发出警报的形态相同,我们将提前退出。

detector.Update(Symbol(), _Period);

ENUM_PATTERN_TYPE pattern = detector.GetLastPattern();
if(pattern == PATTERN_NONE || pattern == lastAlerted)
   return;
lastAlerted = pattern;

处理检测到的形态

如果出现新的形态,我们会从检测器的模式数组中检索最新的 PatternResult 。然后我们将形态枚举转换为人类可读的名称,并格式化其顶点以进行日志记录。矩形需要特殊处理,因为它们只提供三个角点;我们近似计算第四个角点,以呈现一组完整的坐标。我们同时发出警报弹出窗口和打印语句,显示形态名称、时间、价格和角点坐标。

int count = detector.m_currentPatterns.Total();
PatternResult *pr = (PatternResult*)detector.m_currentPatterns.At(count - 1);

string name = (pattern == PATTERN_RECTANGLE) ? "Rectangle" :
              (pattern == PATTERN_TRIANGLE_ASCENDING)   ? "Ascending Triangle" :
              (pattern == PATTERN_TRIANGLE_DESCENDING)  ? "Descending Triangle" :
                                                         "Symmetrical Triangle";

Alert("GeometryAnalyzerEA: Detected ", name, " on ", Symbol(), " ", EnumToString(_Period));
Print("GeometryAnalyzerEA: ", name,
      " @", TimeToString(pr.labelTime, TIME_SECONDS),
      " Price=", pr.labelPrice);

绘制标签

最后,我们在图表上该形态指定的时间和价格处添加文本标签。将模式名称与标签时间组合起来,即可生成唯一的对象名称。我们使用图表对象函数绘制 OBJ_TEXT,并设置其属性(文本内容、颜色、字体大小和背景),使其在图表上清晰突出。我们会记录最后一个标签名称,以便在下一个新柱形上绘制新标签之前将其删除。调用重绘图表的命令可确保立即渲染。

   lastLabelName = name + "_" + TimeToString(pr.labelTime, TIME_SECONDS);
   long chartId = ChartID();
   if(ObjectCreate(chartId, lastLabelName, OBJ_TEXT, 0, pr.labelTime, pr.labelPrice))
   {
      ObjectSetString(chartId, lastLabelName, OBJPROP_TEXT,     name);
      ObjectSetInteger(chartId, lastLabelName, OBJPROP_COLOR,    clrOrangeRed);
      ObjectSetInteger(chartId, lastLabelName, OBJPROP_FONTSIZE, 12);
      ObjectSetInteger(chartId, lastLabelName, OBJPROP_BACK,     true);
   }
   ChartRedraw(chartId);
}


测试及结果

为了快速评估 EA 的性能,我们使用策略测试器来预览其在历史数据上的表现。结果令人满意,形态被正确检测,形状也按预期绘制。此外,我还有机会观察 EA 在实时图表上的运行情况,它的表现非常出色。形态信号与实时形状渲染无缝触发,证实了集成的稳健性。现在,请看下面的演示。

策略测试可视化:GeometricAnalyzerEA

策略测试可视化:GeometricAnalyzerEA

下面是一个日志,显示了在测试过程中检测到的上升三角形。

2025.05.20 13:40:54.241 2025.01.14 18:45:00   Alert: GeometryAnalyzerEA: Detected Ascending Triangle on AUDJPY.0 PERIOD_M1
2025.05.20 13:40:54.241 2025.01.14 18:45:00   GeometryAnalyzerEA: Ascending Triangle @14:03:00 Price=97.45599999999999 → Vertices: (1736863200@97.381), (1736863380@97.448), (1736873819@97.912)
2025.05.20 13:40:54.242 2025.01.14 18:46:00   Swing High detected at 2025.01.14 18:43 Price: 97.789


结论

总之,我们成功地使用 MQL5 开发了一个能够检测三角形和矩形市场结构的自动化系统。这些几何形态在技术分析中早已得到认可,它们为市场行为提供了有意义的见解,尤其是在识别潜在的延续或反转区域方面。通过关注上升三角形和矩形等定义明确的形状,我们展示了几何学如何将价格走势转化为可操作且可靠的视觉算法结构。

我们实现的方法为构建更复杂的分析系统奠定了坚实的基础。GeometricPatternDetector 类是模块化和可重用的 —— 它可以集成到其他项目中,并进行扩展以提高检测精度和灵活性。

虽然我们的系统能够准确地在图表上绘制形状,但结构检测逻辑还有改进的空间,以处理极端情况并提高形态识别精度。该项目展示了算法方法如何简化复杂市场模式的检测,从而简化原本非常复杂的手动流程。

这是一段不断学习的旅程。在此过程中,我们已经看到 MQL5 中基于类的开发不仅增强了模块化,而且还促进了代码重用和接口抽象。这允许其他开发人员甚至最终用户使用干净的高级界面,而不需要理解低级检测逻辑。

对于我们这些构建此类系统的人来说,掌握内部实现至关重要。但由于封装的设计,其他人仍然可以从功能中受益,而无需深入研究底层代码。有了这种结构,我们现在可以自信地开发和扩展该系统,通过简单地设计适当的类来检测任何市场模式。

下一个合乎逻辑的进步是整合基于已确认模式的订单执行逻辑 —— 例如,在突破上升三角形时执行买入订单,在最近的波动点设置止损,以及使用模式的高度设置动态止盈。更高级的增强功能可能包括使用成交量或震荡指标确认形态、应用多时间周期分析进行验证,以及实施排名系统以优先考虑高质量的设置。


附件:

文件 描述
GeometricPatternDetector.mqh
该头文件包含几何形态检测逻辑的完整类实现。它定义了波动点和形态结果的数据结构,管理波动点检测,使用 ATR 计算市场敏感度,并包含用于识别三角形和矩形的例程。该文件旨在模块化集成到 EA 交易中。
GeometryAnalyzerEA.mq5
该 EA 交易演示了形态检测头文件的实际应用。它会在每个新柱上初始化和更新 ` CGeometricPatternDetector` 类,获取形态结果,并在图表上以可视方式标注检测到的模式。它提供了一个简单的、现实世界的例子,说明如何将面向对象的形态识别融入到交易策略中。

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

附加的文件 |
在MQL5中创建交易管理员面板(第十一部分):现代化功能通信接口(1) 在MQL5中创建交易管理员面板(第十一部分):现代化功能通信接口(1)
今天,我们将聚焦于升级通信面板的消息交互界面,使其符合现代高性能通信应用的标准。这一改进将通过更新CommunicationsDialog类来实现。欢迎加入本文的探讨与讨论,我们将共同剖析关键要点,并规划使用MQL5推进界面编程的下一步方向。
MQL5开发专属调试与性能分析工具(第一部分):高级日志记录 MQL5开发专属调试与性能分析工具(第一部分):高级日志记录
学习如何为MQL5实现一个强大的自定义日志框架,该框架超越简单的Print()语句,支持日志严重级别、多输出处理器和自动文件轮转——所有功能均可动态配置。将单例CLogger与ConsoleLogHandler(控制台日志处理器)和FileLogHandler(文件日志处理器)集成,在“Experts”选项卡和持续的文件中捕获带时间戳的内容日志。通过清晰、可定制的日志格式和集中控制,简化智能交易系统(EA)的调试与性能跟踪工作。
使用 MetaTrader 5 Python 构建类似 MQL5 的交易类 使用 MetaTrader 5 Python 构建类似 MQL5 的交易类
MetaTrader 5 Python 包提供了一种使用 Python 语言为 MetaTrader 5 平台构建交易应用程序的简便方法。虽然它是一个强大而有用的工具,但在创建算法交易解决方案方面,该模块不如 MQL5 编程语言那么容易。在本文中,我们将构建类似于 MQL5 中提供的交易类,以创建类似的语法,使在 Python 中创建交易机器人比在 MQL5 中更容易。
MQL5交易工具(第二部分):为交互式交易助手添加动态视觉反馈 MQL5交易工具(第二部分):为交互式交易助手添加动态视觉反馈
本文通过引入拖拽面板功能和悬停交互效果,对交易助手工具进行全面升级,使界面操作更直观且响应更迅速。我们优化了工具的实时订单验证机制,确保交易参数能根据市场价格动态校准。同时,我们通过回测验证了这些改进的可靠性。