English Русский Español Deutsch 日本語
preview
MQL5交易策略自动化(第三十部分):开发具备可视化反馈的价格行为AB-CD谐波形态交易系统

MQL5交易策略自动化(第三十部分):开发具备可视化反馈的价格行为AB-CD谐波形态交易系统

MetaTrader 5 — 交易 |
21 2
Allan Munene Mutiiria
Allan Munene Mutiiria

引言

在前一篇文章(第二十九部分)中,我们基于MetaQuotes Language 5(MQL5)开发了加特利(Gartley)谐波形态交易系统,依靠精确的斐波那契(Fibonacci)比例识别看涨与看跌加特利谐波形态,根据计算得到的入场、止损、止盈点位自动执行交易,并借助三角形、趋势线等图表对象把形态绘制出来。在本文中,我们介绍AB=CD形态系统。加特利系统需要识别由多组斐波那契位定义的复杂多段谐波结构,而AB=CD系统专门用于识别由两段等长价格波段(AB段与CD段)构成的形态:系统通过枢轴点以及特定的回撤、扩展比例完成识别。相比之下,该形态识别逻辑更简洁,同时具备很强的动态特性。这套AB=CD系统采用动态入场与多级止盈目标执行交易,同时搭配三角形、趋势线和文字标签强化可视化效果,清晰展示形态结构。本文将介绍以下内容:

  1. 理解AB=CD谐波形态框架
  2. 在MQL5中的实现
  3. 回测
  4. 结论

阅读完本文,您将获得一套成熟稳定的MQL5 AB=CD谐波形态交易策略,可按需进行个性化定制,让我们开始吧!


理解AB=CD谐波形态框架

AB=CD形态是一种谐波交易结构,该形态依靠四个关键波动拐点A、B、C、D识别潜在反转区域,分为看涨与看跌两种类型,借助斐波那契比例定位高概率交易机会。看涨AB=CD的结构顺序为:高点 — 低点 — 高点 — 低点。其中,A是波动高点,B是波动低点,C是波动高点,D是波动低点(位置低于B点);AB波段与CD波段长度相等,或满足斐波那契回撤与扩展比例关系;看跌AB=CD的结构顺序为低点 — 高点 — 低点 — 高点,且D点高于B点。形态示意图如下:

看跌谐波AB=CD形态:

看跌谐波AB=CD形态

看涨谐波AB=CD形态:

看涨谐波AB=CD形态

我们的实现思路:在指定K线区间内识别这些波动枢轴点;校验形态条件 —— BC波段回撤幅度为AB波段的0.382至0.886,CD波段为BC波段的1.13至2.618倍;使用趋势线、三角形等图表对象绘制形态,直观展示;在D点入场交易,依据斐波那契回撤计算止损与多级止盈价位,捕捉预期的反转行情。接下来,我们进入代码实现环节!


在MQL5中的实现

要在MQL5中创建该程序,请打开MetaEditor,在导航器中找到“Indicators”文件夹,点击“新建”,并按照向导提示创建文件。文件创建完成后,我们首先声明一些将在整个程序中用到的全局变量。

//+------------------------------------------------------------------+
//|                                              ABCD Pattern EA.mq5 |
//|                        Copyright 2025, Forex Algo-Trader, Allan. |
//|                                 "https://t.me/Forex_Algo_Trader" |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property description "This EA trades based on AB=CD Strategy"
#property strict

//--- Include the trading library for order functions
#include <Trade\Trade.mqh> //--- Include Trade library
CTrade obj_Trade; //--- Instantiate a obj_Trade object

//--- Input parameters for user configuration
input int PivotLeft = 5;          // Number of bars to the left for pivot check
input int PivotRight = 5;         // Number of bars to the right for pivot check
input double Tolerance = 0.10;    // Allowed deviation (10% of AB move)
input double LotSize = 0.01;      // Lot size for new orders
input bool AllowTrading = true;   // Enable or disable trading
//---------------------------------------------------------------------------

//--- Structure for a pivot point
struct Pivot {
   datetime time; //--- Bar time of the pivot
   double price;  //--- Pivot price (High for swing high, low for swing low)
   bool isHigh;   //--- True if swing high; false if swing low
};
//--- Global dynamic array for storing pivots in chronological order
Pivot pivots[]; //--- Declare a dynamic array to hold identified pivot points
//--- Global variables to lock in a pattern (avoid trading on repaint)
int g_patternFormationBar = -1; //--- Bar index where the pattern was formed (-1 means none)
datetime g_lockedPatternA = 0;  //--- The key A pivot time for the locked pattern

为搭建AB=CD形态的基础,我们首先引入"<Trade\Trade.mqh>"库,并实例化"obj_Trade"作为CTrade对象,用于管理交易操作,例如执行买卖订单。接着定义可供用户自定义的输入参数:将"PivotLeft"和"PivotRight" 均设置为5根K线,用于设定枢轴识别的回溯范围;"Tolerance"取值为0.10,表示斐波那契比例校验允许±10%的偏差容差;"LotSize"为0.01,即交易手数;将"AllowTrading"设置为true,开启自动交易功能。

接下来,我们定义"Pivot"结构体,包含"time"(日期时间型)、"price"(双精度型)、"isHigh"(布尔值,标记是否为高点),用来存储波动拐点;声明动态数组"pivots"存放所有枢轴点;将全局变量 "g_patternFormationBar"初始化为-1,用于记录形态形成所在K线;将全局变量"g_lockedPatternA"初始化为0,锁定A点的时间戳以完成形态确认。这里使用A点而非X点,是为了贴合AB=CD形态本身只关注AB、CD波段的特点。以上配置搭建起识别和交易AB=CD形态的核心框架。可视化部分,我们会编写函数绘制线条、文字标签以及三角形。

//+------------------------------------------------------------------+
//| Helper: Draw a filled triangle                                   |
//+------------------------------------------------------------------+
void DrawTriangle(string name, datetime t1, double p1, datetime t2, double p2, datetime t3, double p3, color cl, int width, bool fill, bool back) {
   //--- Attempt to create a triangle object with three coordinate points
   if(ObjectCreate(0, name, OBJ_TRIANGLE, 0, t1, p1, t2, p2, t3, p3)) {
      //--- Set the triangle's color
      ObjectSetInteger(0, name, OBJPROP_COLOR, cl);
      //--- Set the triangle's line style to solid
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
      //--- Set the line width of the triangle
      ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
      //--- Determine if the triangle should be filled
      ObjectSetInteger(0, name, OBJPROP_FILL, fill);
      //--- Set whether the object is drawn in the background
      ObjectSetInteger(0, name, OBJPROP_BACK, back);
   }
}
//+------------------------------------------------------------------+
//| Helper: Draw a trend line                                        |
//+------------------------------------------------------------------+
void DrawTrendLine(string name, datetime t1, double p1, datetime t2, double p2, color cl, int width, int style) {
   //--- Create a trend line object connecting two points
   if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2)) {
      //--- Set the trend line's color
      ObjectSetInteger(0, name, OBJPROP_COLOR, cl);
      //--- Set the trend line's style (solid, dotted, etc.)
      ObjectSetInteger(0, name, OBJPROP_STYLE, style);
      //--- Set the width of the trend line
      ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
   }
}
//+------------------------------------------------------------------+
//| Helper: Draw a dotted trend line |
//+------------------------------------------------------------------+
void DrawDottedLine(string name, datetime t1, double p, datetime t2, color lineColor) {
   //--- Create a horizontal trend line at a fixed price level with dotted style
   if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p, t2, p)) {
      //--- Set the dotted line's color
      ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor);
      //--- Set the line style to dotted
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT);
      //--- Set the line width to 1
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   }
}
//+------------------------------------------------------------------+
//| Helper: Draw anchored text label (for pivots)                    |
//| If isHigh is true, anchor at the bottom (label appears above);   |
//| if false, anchor at the top (label appears below).               |
//+------------------------------------------------------------------+
void DrawTextEx(string name, string text, datetime t, double p, color cl, int fontsize, bool isHigh) {
   //--- Create a text label object at the specified time and price
   if(ObjectCreate(0, name, OBJ_TEXT, 0, t, p)) {
      //--- Set the text of the label
      ObjectSetString(0, name, OBJPROP_TEXT, text);
      //--- Set the color of the text
      ObjectSetInteger(0, name, OBJPROP_COLOR, cl);
      //--- Set the font size for the text
      ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontsize);
      //--- Set the font type and style
      ObjectSetString(0, name, OBJPROP_FONT, "Arial Bold");
      //--- Anchor the text depending on whether it's a swing high or low
      if(isHigh)
         ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
      else
         ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_TOP);
      //--- Center-align the text
      ObjectSetInteger(0, name, OBJPROP_ALIGN, ALIGN_CENTER);
   }
}

我们继续实现可视化函数,在图表上清晰绘制AB=CD谐波形态及其交易价位。首先,编写"DrawTriangle"函数:该函数调用ObjectCreate创建填充三角形对象(OBJ_TRIANGLE),三角形由三组时间("t1"、"t2"、"t3")与价格("p1"、"p2"、"p3")坐标定义,通过ObjectSetInteger函数设置属性:设置OBJPROP_COLOR为指定颜色,将"OBJPROP_STYLE"设置为STYLE_SOLID(实线),设置"OBJPROP_WIDTH"为指定线条宽度,"OBJPROP_FILL"控制是否开启填充,并使用"OBJPROP_BACK"设置图形置于图表背景还是前景。

接下来,我们编写"DrawTrendLine"函数,用于在两点之间绘制趋势线(OBJ_TREND);最后实现"DrawTextEx"函数:通过"ObjectCreate"在坐标 ("t","p") 位置创建文本标签(OBJ_TEXT),借助 ObjectSetString和"ObjectSetInteger"设置"OBJPROP_TEXT"为指定文字、"OBJPROP_COLOR"、"OBJPROP_FONTSIZE"以及"OBJPROP_FONT"为"Arial Bold";依据“isHigh”的值设置OBJPROP_ANCHOR:高点标签显示在价格上方,低点标签显示在价格下方,并通过"OBJPROP_ALIGN"设置居中对齐。我们现在进入OnTick事件处理器,开始识别枢轴点,供后续形态识别使用。以下是实现这一逻辑的方法:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   //--- Declare a static variable to store the time of the last processed bar
   static datetime lastBarTime = 0;
   //--- Get the time of the current confirmed bar
   datetime currentBarTime = iTime(_Symbol, _Period, 1);
   //--- If the current bar time is the same as the last processed, exit
   if(currentBarTime == lastBarTime)
      return;
   //--- Update the last processed bar time
   lastBarTime = currentBarTime;
  
   //--- Clear the pivot array for fresh analysis
   ArrayResize(pivots, 0);
   //--- Get the total number of bars available on the chart
   int barsCount = Bars(_Symbol, _Period);
   //--- Define the starting index for pivot detection (ensuring enough left bars)
   int start = PivotLeft;
   //--- Define the ending index for pivot detection (ensuring enough right bars)
   int end = barsCount - PivotRight;
  
   //--- Loop through bars from 'end-1' down to 'start' to find pivot points
   for(int i = end - 1; i >= start; i--) {
      //--- Assume current bar is both a potential swing high and swing low
      bool isPivotHigh = true;
      bool isPivotLow = true;
      //--- Get the high and low of the current bar
      double currentHigh = iHigh(_Symbol, _Period, i);
      double currentLow = iLow(_Symbol, _Period, i);
      //--- Loop through the window of bars around the current bar
      for(int j = i - PivotLeft; j <= i + PivotRight; j++) {
         //--- Skip if the index is out of bounds
         if(j < 0 || j >= barsCount)
            continue;
         //--- Skip comparing the bar with itself
         if(j == i)
            continue;
         //--- If any bar in the window has a higher high, it's not a swing high
         if(iHigh(_Symbol, _Period, j) > currentHigh)
            isPivotHigh = false;
         //--- If any bar in the window has a lower low, it's not a swing low
         if(iLow(_Symbol, _Period, j) < currentLow)
            isPivotLow = false;
      }
      //--- If the current bar qualifies as either a swing high or swing low
      if(isPivotHigh || isPivotLow) {
         //--- Create a new pivot structure
         Pivot p;
         //--- Set the pivot's time
         p.time = iTime(_Symbol, _Period, i);
         //--- Set the pivot's price depending on whether it is a high or low
         p.price = isPivotHigh ? currentHigh : currentLow;
         //--- Set the pivot type (true for swing high, false for swing low)
         p.isHigh = isPivotHigh;
         //--- Get the current size of the pivots array
         int size = ArraySize(pivots);
         //--- Increase the size of the pivots array by one
         ArrayResize(pivots, size + 1);
         //--- Add the new pivot to the array
         pivots[size] = p;
      }
   }
}

这里,我们编写OnTick函数的初始逻辑。首先声明静态变量"lastBarTime"并初始化为0,用来记录上一根已处理K线的时间。从iTime函数获取当前品种、当前周期下偏移量为1的K线时间,赋值给"currentBarTime";如果时间未发生变化则直接退出,避免重复运算。当检测到新K线生成时,更新"lastBarTime"。随后,我们调用ArrayResize清空"pivots"数组,保证每一轮都是全新的分析。接下来,我们调用Bars获取K线总数量;设置枢轴点检测区间:起始位置"start"为"PivotLeft",结束位置"end"等于K线总数减去"PivotRight"。然后从"end - 1"到"start"逆序遍历每一根K线。

对每一根K线,先预设它可能是波段高点("isPivotHigh"为true),也可能是波段低点("isPivotLow"为true),通过iHigh与iLow获取该K线的最高价、最低价,再利用"iHigh"、"iLow"校验该K线左右"PivotLeft"、"PivotRight"范围内的相邻K线,如果窗口内任意相邻K线创出更高的高点,则该K线不是波段高点;创出更低的低点,则不是波段低点。最后,如果该K线满足枢轴点判定条件,就创建一个"Pivot"结构体:用"iTime"赋值"time"成员,根据是否为波段高点,将"price"设为最高价或最低价,并设置"isHigh"标记。之后调用ArrayResize扩容数组,把该枢轴点追加存入"pivots"数组。将该枢轴信息打印出来,即可得到如下数据。

枢轴点数据

基于这些数据,我们即可提取枢轴点信息,当有效拐点数量足够时,即可开始形态的分析与识别。以下是实现该功能的完整逻辑:

//--- Determine the total number of pivots found
int pivotCount = ArraySize(pivots);
//--- If fewer than four pivots are found, the pattern cannot be formed
if(pivotCount < 4) {
   //--- Reset pattern lock variables
   g_patternFormationBar = -1;
   g_lockedPatternA = 0;
   //--- Exit the OnTick function
   return;
}

//--- Extract the last four pivots as A, B, C, and D
Pivot A = pivots[pivotCount - 4];
Pivot B = pivots[pivotCount - 3];
Pivot C = pivots[pivotCount - 2];
Pivot D = pivots[pivotCount - 1];

//--- Initialize a flag to indicate if a valid AB=CD pattern is found
bool patternFound = false;
//--- Initialize pattern type
string patternType = "";
double used_retr = 0.0;
double used_ext = 0.0;
//--- Check for the high-low-high-low (Bullish reversal) structure
if(A.isHigh && (!B.isHigh) && C.isHigh && (!D.isHigh)) {
   //--- Calculate the difference between pivot A and B
   double diff = A.price - B.price;
   //--- Ensure the difference is positive
   if(diff > 0) {
      //--- Calculate the BC leg length
      double BC = C.price - B.price;
      double retrace = BC / diff;
      //--- Calculate the CD leg length
      double CD = C.price - D.price;
      double extension = CD / BC;
      //--- Define fib ratios
      double fib_retr[] = {0.382, 0.5, 0.618, 0.786, 0.886};
      double fib_ext[] = {2.618, 2.0, 1.618, 1.272, 1.13};
      bool valid = false;
      for(int k = 0; k < ArraySize(fib_retr); k++) {
         if(MathAbs(retrace - fib_retr[k]) <= Tolerance && MathAbs(extension - fib_ext[k]) <= Tolerance) {
            valid = true;
            used_retr = fib_retr[k];
            used_ext = fib_ext[k];
            break;
         }
      }
      if(valid && (D.price < B.price)) {
         patternFound = true;
         patternType = "Bullish";
      }
   }
}
//--- Check for the low-high-low-high (Bearish reversal) structure
if((!A.isHigh) && B.isHigh && (!C.isHigh) && D.isHigh) {
   //--- Calculate the difference between pivot B and A
   double diff = B.price - A.price;
   //--- Ensure the difference is positive
   if(diff > 0) {
      //--- Calculate the BC leg length
      double BC = B.price - C.price;
      double retrace = BC / diff;
      //--- Calculate the CD leg length
      double CD = D.price - C.price;
      double extension = CD / BC;
      //--- Define fib ratios
      double fib_retr[] = {0.382, 0.5, 0.618, 0.786, 0.886};
      double fib_ext[] = {2.618, 2.0, 1.618, 1.272, 1.13};
      bool valid = false;
      for(int k = 0; k < ArraySize(fib_retr); k++) {
         if(MathAbs(retrace - fib_retr[k]) <= Tolerance && MathAbs(extension - fib_ext[k]) <= Tolerance) {
            valid = true;
            used_retr = fib_retr[k];
            used_ext = fib_ext[k];
            break;
         }
      }
      if(valid && (D.price > B.price)) {
         patternFound = true;
         patternType = "Bearish";
      }
   }
}

首先,我们通过"ArraySize(pivots)"获取枢轴点总数并存入变量"pivotCount"中;如果枢轴数量不足4个,则直接退出本轮逻辑,并将"g_patternFormationBar"、"g_lockedPatternA"分别重置为-1和0 —— AB=CD形态必须集齐 A、B、C和D四个拐点。之后从"pivots"数组中提取最新的四个枢轴点,依次赋值给"A"(四者中最早)、"B"、"C"和"D"(最新),构成形态结构。

接下来,校验看涨AB=CD形态(A为高点、B为低点、C为高点、D为低点):计算AB波段价差("A.price - B.price")并保证结果为正;计算BC波段长度("C.price - B.price")以及BC相对AB的回撤比例;计算CD波段长度("C.price - D.price")以及CD相对于BC的扩展比例。程序预设斐波那契回撤序列(0.382、0.5、0.618、0.786、0.886)和扩展序列(2.618、2.0、1.618、1.272、1.13)。校验两个比例是否都在容差"Tolerance"范围内,同时满足"D.price < B.price";当条件全部满足时,将"patternFound"设置为true,"patternType"设置为"Bullish",并记录匹配到的回撤值"used_retr"和扩展值"used_ext"。最后,我们校验看跌AB=CD形态(A为低点、B为高点、C为低点、D为高点),采用相似计算方式:AB段("B.price - A.price")、BC段("B.price - C.price")、CD段("D.price - C.price"),使用同一组斐波那契比例校验,并且确保"D.price > B.price";校验通过则将"patternFound"设置为true、"patternType"设置为"Bearish"。一旦识别到形态,就可以在图表上绘制可视化图形。

//--- If a valid AB=CD pattern is detected
if(patternFound) {
   //--- Print a message indicating the pattern type and detection time
   Print(patternType, " AB=CD pattern detected at ", TimeToString(D.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS));
  
   //--- Create a unique prefix for all graphical objects related to this pattern
   string signalPrefix = "AB_" + IntegerToString(A.time);
  
   //--- Choose triangle color based on the pattern type
   color triangleColor = (patternType=="Bullish") ? clrBlue : clrRed;
  
   //--- Draw the first triangle connecting pivots A, B, and C
   DrawTriangle(signalPrefix+"_Triangle1", A.time, A.price, B.time, B.price, C.time, C.price,
                triangleColor, 2, true, true);
   //--- Draw the second triangle connecting pivots B, C, and D
   DrawTriangle(signalPrefix+"_Triangle2", B.time, B.price, C.time, C.price, D.time, D.price,
                triangleColor, 2, true, true);
}

绘制形态的逻辑:首先,如果识别出有效形态("patternFound"为true),就调用Print打印日志,输出"patternType"("Bullish"或"Bearish")以及D枢轴点的时间;时间通过TimeToString格式化,包含日期、分钟与秒。随后生成唯一标识"signalPrefix":将字符串"AB_"和转为字符串的"A.time"(借助IntegerToString)拼接在一起,保证每个图表对象的名称互不重复。

我们接着设置"triangleColor":看涨形态使用蓝色,看跌形态使用红色,以实现视觉区分。最后调用两次"DrawTriangle"完成绘图:第一次绘制ABC三角形,连接A、B和C三个枢轴点;第二次绘制BCD三角形,连接B、C和D三个枢轴点。对象名称由"signalPrefix"分别拼接后缀"_Triangle1"和"_Triangle2"构成,传入对应枢轴的时间与价格、"triangleColor"、线条宽度2,并传入true标识开启填充、置于K线背景层。运行效果如下:

三角形绘制完成

由图可见,程序能够正确定位并绘制识别到的形态。现在,我们继续绘制趋势线,让形态在区间内完整呈现,并添加文字标签,方便快速识别各个价位。

//--- Draw boundary trend lines connecting the pivots for clarity
DrawTrendLine(signalPrefix+"_TL_AB", A.time, A.price, B.time, B.price, clrBlack, 2, STYLE_SOLID);
DrawTrendLine(signalPrefix+"_TL_BC", B.time, B.price, C.time, C.price, clrBlack, 2, STYLE_SOLID);
DrawTrendLine(signalPrefix+"_TL_CD", C.time, C.price, D.time, D.price, clrBlack, 2, STYLE_SOLID);

//--- Retrieve the symbol's point size to calculate offsets for text positioning
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
//--- Calculate an offset (15 points) for positioning text above or below pivots
double offset = 15 * point;

//--- Determine the Y coordinate for each pivot label based on its type
double textY_A = (A.isHigh ? A.price + offset : A.price - offset);
double textY_B = (B.isHigh ? B.price + offset : B.price - offset);
double textY_C = (C.isHigh ? C.price + offset : C.price - offset);
double textY_D = (D.isHigh ? D.price + offset : D.price - offset);

//--- Draw text labels for each pivot with appropriate anchoring
DrawTextEx(signalPrefix+"_Text_A", "A", A.time, textY_A, clrBlack, 11, A.isHigh);
DrawTextEx(signalPrefix+"_Text_B", "B", B.time, textY_B, clrBlack, 11, B.isHigh);
DrawTextEx(signalPrefix+"_Text_C", "C", C.time, textY_C, clrBlack, 11, C.isHigh);
DrawTextEx(signalPrefix+"_Text_D", "D", D.time, textY_D, clrBlack, 11, D.isHigh);

//--- Calculate the central label's time as the midpoint between pivots A and C
datetime centralTime = (A.time + C.time) / 2;
//--- Set the central label's price at pivot D's price
double centralPrice = D.price;
//--- Create the central text label indicating the pattern type
if(ObjectCreate(0, signalPrefix+"_Text_Center", OBJ_TEXT, 0, centralTime, centralPrice)) {
   ObjectSetString(0, signalPrefix+"_Text_Center", OBJPROP_TEXT,
      (patternType=="Bullish") ? "Bullish AB=CD" : "Bearish AB=CD");
   ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_FONTSIZE, 11);
   ObjectSetString(0, signalPrefix+"_Text_Center", OBJPROP_FONT, "Arial Bold");
   ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_ALIGN, ALIGN_CENTER);
}

我们继续丰富已识别形态的可视化效果,新增各类图表对象,清晰勾勒形态结构。首先,我们调用"DrawTrendLine"绘制三条实线趋势线,沿用唯一标识"signalPrefix",分别连接关键枢轴点AB、BC和CD;传入各枢轴的时间与价格(如"A.time"、"A.price"),并通过ObjectSetInteger设置OBJPROP_COLOR为黑色、"OBJPROP_WIDTH"为2、"OBJPROP_STYLE"为实线,勾勒出形态的各个波段。接下来,通过SymbolInfoDouble(_Symbol, SYMBOL_POINT)获取当前品种的点大小(最小价格单位),计算一个15点的偏移量用于标签定位;根据每个枢轴是波动高点("isHigh"为true)还是低点,对价格做加减偏移,得到文字 Y 坐标("textY_A"、"textY_B"、"textY_C"、"textY_D"),实现高点标签放在价格上方、低点标签放在价格下方。

之后,调用"DrawTextEx"为A、B、C和D四个枢轴点添加文字标签,沿用"signalPrefix"并搭配"_Text_A"这类后缀,显示对应的点位字母;标签放置在枢轴时间与调整后的Y坐标位置,颜色黑色、字号11,并根据枢轴的"isHigh"属性设置锚点方向。最后,我们计算中心标签位置:"centralTime"取"A.time"和"C.time"的中点,"centralPrice"取值为"D.price"。通过ObjectCreate创建文本对象,名称为"signalPrefix + '_Text_Center'";根据"patternType"将OBJPROP_TEXT内容设为"Bullish AB=CD"或"Bearish AB=CD"。借助ObjectSetString和"ObjectSetInteger"配置属性:将"OBJPROP_COLOR"设置为"clrBlack"、"OBJPROP_FONTSIZE"设为11、OBJPROP_FONT设为"Arial Bold"、"OBJPROP_ALIGN"设为ALIGN_CENTER。至此,图表上可以完整展示形态结构与形态类型。运行程序后,就能看到如下绘制效果。

带标签与轮廓线的形态图

由图可见,我们已经为形态绘制好轮廓线与点位标签,使形态展示得更加清晰、直观。接下来,我们需要计算该形态对应的交易价位。

//--- Define start and end times for drawing horizontal dotted lines for trade levels
datetime lineStart = D.time;
datetime lineEnd = D.time + PeriodSeconds(_Period)*2;

//--- Declare variables for entry price and take profit levels
double entryPriceLevel, TP1Level, TP2Level, TP3Level;
//--- Calculate pattern range (CD length)
double patternRange = (patternType=="Bullish") ? (C.price - D.price) : (D.price - C.price);
//--- Calculate trade levels based on whether the pattern is Bullish or Bearish
if(patternType=="Bullish") { //--- Bullish → BUY signal
   //--- Use the current ASK price as the entry
   entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   //--- Set TP3 at pivot C's price
   TP3Level = C.price;
   //--- Set TP1 at 0.382 fib retrace from D to C
   TP1Level = D.price + 0.382 * patternRange;
   //--- Set TP2 at 0.618 fib retrace from D to C
   TP2Level = D.price + 0.618 * patternRange;
} else { //--- Bearish → SELL signal
   //--- Use the current BID price as the entry
   entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   //--- Set TP3 at pivot C's price
   TP3Level = C.price;
   //--- Set TP1 at 0.382 fib retrace from D to C
   TP1Level = D.price - 0.382 * patternRange;
   //--- Set TP2 at 0.618 fib retrace from D to C
   TP2Level = D.price - 0.618 * patternRange;
}

//--- Draw dotted horizontal lines to represent the entry and TP levels
DrawDottedLine(signalPrefix+"_EntryLine", lineStart, entryPriceLevel, lineEnd, clrMagenta);
DrawDottedLine(signalPrefix+"_TP1Line", lineStart, TP1Level, lineEnd, clrForestGreen);
DrawDottedLine(signalPrefix+"_TP2Line", lineStart, TP2Level, lineEnd, clrGreen);
DrawDottedLine(signalPrefix+"_TP3Line", lineStart, TP3Level, lineEnd, clrDarkGreen);

//--- Define a label time coordinate positioned just to the right of the dotted lines
datetime labelTime = lineEnd + PeriodSeconds(_Period)/2;

//--- Construct the entry label text with the price
string entryLabel = (patternType=="Bullish") ? "BUY (" : "SELL (";
entryLabel += DoubleToString(entryPriceLevel, _Digits) + ")";
//--- Draw the entry label on the chart
DrawTextEx(signalPrefix+"_EntryLabel", entryLabel, labelTime, entryPriceLevel, clrMagenta, 11, true);

//--- Construct and draw the TP1 label
string tp1Label = "TP1 (" + DoubleToString(TP1Level, _Digits) + ")";
DrawTextEx(signalPrefix+"_TP1Label", tp1Label, labelTime, TP1Level, clrForestGreen, 11, true);

//--- Construct and draw the TP2 label
string tp2Label = "TP2 (" + DoubleToString(TP2Level, _Digits) + ")";
DrawTextEx(signalPrefix+"_TP2Label", tp2Label, labelTime, TP2Level, clrGreen, 11, true);

//--- Construct and draw the TP3 label
string tp3Label = "TP3 (" + DoubleToString(TP3Level, _Digits) + ")";
DrawTextEx(signalPrefix+"_TP3Label", tp3Label, labelTime, TP3Level, clrDarkGreen, 11, true);

为定义并绘制交易价位线,我们将"lineStart"设置为D枢轴点的时间("D.time"),"lineEnd"取往后两根K线的时间,由"PeriodSeconds(_Period) * 2"计算得到;同时声明"entryPriceLevel"、"TP1Level"、"TP2Level"和"TP3Level"变量,用于交易价位计算。接着计算形态波动区间"patternRange",即CD波段的幅度:看涨形态为"C.price - D.price",看跌形态为"D.price - C.price"。看涨形态下,我们将入场价"entryPriceLevel"设置为Ask价,"TP3Level"设为C点价格,"TP1Level"设为"D.price + 0.382 * patternRange","TP2Level"设为"D.price + 0.618 * patternRange";看跌形态下,我们将入场价设置为Bid价,"TP3Level"设为C点价格,"TP1Level"设为"D.price - 0.382 * patternRange","TP2Level"设为"D.price - 0.618 * patternRange"。

接下来调用"DrawDottedLine"绘制四条水平虚线:品红色的"entryPriceLevel"入场价位线,以及分别位于"TP1Level"(森林绿)、"TP2Level"(绿色)、"TP3Level"(深绿色)的止盈线;所有线条的时间范围均从"lineStart"延伸至"lineEnd"。最后,我们将"labelTime"设置为"lineEnd"再加上半个周期。通过DoubleToString函数把价格转为字符串,生成标签文本(例如入场位标注 “BUY (价格)” 或 “SELL (价格)”,还有 “TP1 (价格)” 等)。调用"DrawTextEx",在"labelTime"位置绘制这些标签,使用对应颜色、字号为11,并将文字统一锚定在价位线上方显示。编译后,呈现如下效果:

看跌形态:

看跌形态

看涨形态:

看涨形态

由图可见,我们已经正确绘制好了各项交易价位。现在只剩最后一步 —— 实际开仓,至此整个流程就完成了。

//--- Retrieve the index of the current bar
int currentBarIndex = Bars(_Symbol, _Period) - 1;
//--- If no pattern has been previously locked, lock the current pattern formation
if(g_patternFormationBar == -1) {
   g_patternFormationBar = currentBarIndex;
   g_lockedPatternA = A.time;
   //--- Print a message that the pattern is detected and waiting for confirmation
   Print("Pattern detected on bar ", currentBarIndex, ". Waiting for confirmation on next bar.");
   return;
}
//--- If still on the same formation bar, the pattern is considered to be repainting
if(currentBarIndex == g_patternFormationBar) {
   Print("Pattern is repainting; still on locked formation bar ", currentBarIndex, ". No trade yet.");
   return;
}
//--- If we are on a new bar compared to the locked formation
if(currentBarIndex > g_patternFormationBar) {
   //--- Check if the locked pattern still corresponds to the same A pivot
   if(g_lockedPatternA == A.time) {
      Print("Confirmed pattern (locked on bar ", g_patternFormationBar, "). Opening trade on bar ", currentBarIndex, ".");
      //--- Update the pattern formation bar to the current bar
      g_patternFormationBar = currentBarIndex;
      //--- Only proceed with trading if allowed and if there is no existing position
      if(AllowTrading && !PositionSelect(_Symbol)) {
         double entryPriceTrade = 0, stopLoss = 0, takeProfit = 0;
         point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
         bool tradeResult = false;
         //--- Determine next extension for SL
         double next_ext = 0.0;
         if(MathAbs(used_ext - 1.13) < 0.05) next_ext = 1.272;
         else if(MathAbs(used_ext - 1.272) < 0.05) next_ext = 1.618;
         else if(MathAbs(used_ext - 1.618) < 0.05) next_ext = 2.0;
         else if(MathAbs(used_ext - 2.0) < 0.05) next_ext = 2.618;
         else if(MathAbs(used_ext - 2.618) < 0.05) next_ext = 3.618;
         else next_ext = used_ext * 1.618; // fallback
         //--- For a Bullish pattern, execute a BUY trade
         if(patternType=="Bullish") { //--- BUY signal
            entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double BC_leg = C.price - B.price;
            stopLoss = C.price - next_ext * BC_leg;
            if(stopLoss > D.price) stopLoss = D.price - 10 * point;
            takeProfit = TP2Level;
            tradeResult = obj_Trade.Buy(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "AB=CD Signal");
            if(tradeResult)
               Print("Buy order opened successfully.");
            else
               Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription());
         }
         //--- For a Bearish pattern, execute a SELL trade
         else if(patternType=="Bearish") { //--- SELL signal
            entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double BC_leg = B.price - C.price;
            stopLoss = C.price + next_ext * BC_leg;
            if(stopLoss < D.price) stopLoss = D.price + 10 * point;
            takeProfit = TP2Level;
            tradeResult = obj_Trade.Sell(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "AB=CD Signal");
            if(tradeResult)
               Print("Sell order opened successfully.");
            else
               Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription());
         }
      }
      else {
         //--- If a position is already open, do not execute a new trade
         Print("A position is already open for ", _Symbol, ". No new trade executed.");
      }
   }
   else {
      //--- If the pattern has changed, update the lock with the new formation bar and A pivot
      g_patternFormationBar = currentBarIndex;
      g_lockedPatternA = A.time;
      Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation.");
      return;
   }
}
}
else {
   //--- If no valid AB=CD pattern is detected, reset the pattern lock variables
   g_patternFormationBar = -1;
   g_lockedPatternA = 0;
}

这里,我们完成该形态在tick事件中的最终逻辑,负责已识别AB=CD谐波形态的交易执行与形态确认校验。首先通过"Bars(_Symbol, _Period) - 1"获取当前K线索引,并存储在"currentBarIndex"中。如果当前没有锁定的形态("g_patternFormationBar"为"-1"),则将"g_patternFormationBar"赋值为"currentBarIndex",把A枢轴点时间"A.time"存入"g_lockedPatternA"完成锁定,打印日志提示等待确认,随后退出本轮逻辑。如果仍处于形态形成的同一根K线("currentBarIndex"等于"g_patternFormationBar"),直接退出,防止提前交易。

最后,当新K线已经生成("currentBarIndex > g_patternFormationBar"),并且当前A枢轴点时间与锁定的"g_lockedPatternA"一致时,确认形态有效后,打印日志并更新"g_patternFormationBar",随后检查"AllowTrading"是否允许交易,并通过PositionSelect确认当前无持仓;根据已匹配的扩展比例"used_ext"选取下一档斐波那契扩展值"next_ext"用于止损计算(例如从1.13切换至1.272,最高至3.618,如果无匹配档位则回退方案为"used_ext * 1.618");对于看涨形态,我们将入场价"entryPriceTrade"设置为Ask价,BC波段幅度"BC_leg"为"C.price - B.price",止损位"stopLoss"为"C.price - next_ext * BC_leg"(如果计算出的止损位高于D点,则调整为 "D.price - 10 * point"),止盈位"takeProfit"为 "TP2Level",通过"obj_Trade.Buy"以指定手数"LotSize"和信号注释"AB=CD Signal"执行多单,输出下单成功或失败日志;对于看跌形态,将入场价设置为Bid价,BC波段幅度"BC_leg"为"B.price - C.price",止损位"stopLoss"为"C.price + next_ext * BC_leg"(如果计算出的止损位低于D点,则调整为"D.price + 10 * point"),通过"obj_Trade.Sell"执行空单。如果禁止交易或已有持仓,则打印日志说明不执行开仓;如果形态发生变动,则更新锁定标记并等待下一根K线确认;如果未识别到有效形态,则重置全局变量。编译后,呈现如下效果:

看跌信号:

看跌信号

看涨信号:

看涨信号

由图可见,程序能够绘制谐波形态,并且在形态确认后按规则执行交易,至此我们达成目标:识别、绘制并交易该形态。接下来需要对程序进行回测,具体内容见下一节。


回测

经过全面回测后,我们得到以下结果:

回测图:

图表

回测报告:

报告


结论

综上所述,我们已经在MQL5中完成了一套AB=CD形态交易系统的开发。该系统基于价格行为,依靠精确的斐波那契回撤与扩展比例识别看涨、看跌AB=CD谐波形态,依据计算得到的入场、止损以及多级止盈点位实现自动交易,并通过三角形、趋势线等图表对象完成形态可视化绘制。

免责声明:本文仅用于教学目的。交易存在重大财务风险,市场剧烈波动可能导致资金损失。在实盘操作前,务必进行充分的回测,并做好严格的风险管理。

借助本文介绍的思路与实现方案,您可以对这套AB=CD形态系统进行改造,适配自己的交易风格,进一步完善您的算法交易策略。祝您交易顺利! 

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

附加的文件 |
最近评论 | 前往讨论 (2)
Louis Wetzel
Louis Wetzel | 9 4月 2026 在 18:13

该商标将通过价格波动自然形成的任意四点模式被视为无效,因为所赋予的英文命名作为描述性词汇,无法符合该商标在处理专有知识产权业务时的标准,且无法对这些描述享有专有权。 我认为你在胡说八道,你在商标局白白浪费了350.00-850.00美元,此事必将受到质疑。

这是我的观点。

路易斯·B·韦策尔

Allan Munene Mutiiria
Allan Munene Mutiiria | 9 4月 2026 在 18:57
Louis Wetzel #:

该商标将通过价格波动自然形成的任意四点模式被视为无效,因为所赋予的英文命名作为描述性词汇,无法符合该商标在处理专有知识产权业务时的标准,且无法对这些描述享有专有权。 我根本不买你的账,你白白在商标局浪费了350.00-850.00美元,此事必将受到质疑。

这是我的观点。

路易斯·B·韦策尔

您好。感谢您的反馈。所提出的这些想法旨在实现上述交易策略的自动化。是否有哪部分表述不够清晰或令人困惑,需要进一步说明?谢谢。
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
交易中的神经网络:时间序列查询模型(结论) 交易中的神经网络:时间序列查询模型(结论)
我们很高兴为大家带来TQNet框架开发与测试的最终阶段,将理论落实到真实交易场景中。我们将从历史样本训练转入基于近期市场数据的压力测试,以此评估模型的稳健性与预测精度。最终结果不只是枯燥的统计指标,更是对这套方案实际价值的直观证明。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
机器学习中的高斯过程(第二部分):在 MQL5 中实现和测试分类模型 机器学习中的高斯过程(第二部分):在 MQL5 中实现和测试分类模型
在这个部分中,我们将探讨 MQL5 中高斯过程库关键接口的实现:IKernel、ILikelihood 和 IInference。我们还将演示其在合成数据上的运行情况,并实现分类和回归指标,同时演示其在在线模式下的运行情况 — 即每根新 K 线都会对模型进行重新训练。