MQL5 中的交易策略自动化(第十五部分):可视化价格行为的谐波形态模式·进阶篇
📐

MQL5 中的交易策略自动化(第十五部分):可视化价格行为的谐波形态模式·进阶篇

(2/3)· 很多 EA 只报信号不画形态,肉眼复核费时;这篇把 XA/AB/BC/CD 四脚几何变成可视图层与下单逻辑

案例拆解 第 2/3 篇

做谐波形态的人常卡在一件事:指标算出潜在 D 点,但图表上什么痕迹都没留,复盘时根本想不起当时为什么开仓。密码形态靠斐波那契比例嵌套,手动连趋势线、标五个摆动点,错一笔比例整单逻辑就偏了。

Cypher 形态识别的 EA 骨架怎么搭

做谐波形态自动识别,第一步不是算比率,而是把摆动点结构和交易开关先定清楚。下面这段 MQL5 代码给出了 Cypher 模式 EA 的基础框架:严格编译模式、CTrade 对象、五个输入参数,以及 swing point 的结构体定义。 关键点在于输入参数的含义——SwingHighCount 和 SwingLowCount 都默认设为 5,代表左右各看 5 根 K 线确认摆动点;FibonacciTolerance 给 0.10,即形态比率允许 10% 误差;TradeVolume 默认 0.01 手,明显是测试用小仓;TradingEnabled 设为 true 才实际下单,false 则只画线观察。外汇和贵金属杠杆高,先用 false 跑可视化更安全。 Cypher 的几何规则也写在注释里:看涨结构为 X低-A高-B低-C高-D低,AB 回撤 XA 的 0.382–0.618,BC 扩展 AB 的 1.272–1.414,CD 回撤 XC 的 0.786 且 D 低于 X;看跌反之。这些数字就是后面匹配函数的判据。 DrawTriangle 函数负责把识别出的分段画成三角形。它用 ObjectCreate 建 OBJ_TRIANGLE,再逐条设颜色、线型、线宽和填充,方便肉眼核对形态。开 MT5 把这段代码存成 .mq5 编译,先不开交易,看摆动点捕捉是否符合预期。

MQL5 / C++
class="macro">#class="kw">property strict class=class="str">"cmt">//--- Forces strict coding rules to class="kw">catch errors early
class=class="str">"cmt">//--- Include the Trade library from MQL5 to handle trading operations like buying and selling
class="macro">#include <Trade\Trade.mqh>
class=class="str">"cmt">//--- Create an instance(object) of the CTrade class to use for placing trades
CTrade obj_Trade;
class=class="str">"cmt">//--- Input parameters let the user customize the EA without editing the code
input class="type">int    SwingHighCount    = class="num">5;       class=class="str">"cmt">// How many bars to check on the left to find a swing point(high or low)
input class="type">int    SwingLowCount     = class="num">5;       class=class="str">"cmt">// How many bars to check on the right to confirm a swing point
input class="type">class="kw">double FibonacciTolerance = class="num">0.10;   class=class="str">"cmt">// Allowed error margin(class="num">10%) for Fibonacci ratios in the pattern
input class="type">class="kw">double TradeVolume       = class="num">0.01;    class=class="str">"cmt">// Size of the trade(e.g., class="num">0.01 lots is small for testing)
input class="type">bool   TradingEnabled    = true;    class=class="str">"cmt">// True = EA can trade; False = only visualize patterns
class=class="str">"cmt">//--- Define the Cypher pattern rules as a comment for reference
class=class="str">"cmt">//--- Bullish Cypher: X(low), A(high), B(low), C(high), D(low)
class=class="str">"cmt">//---   XA > class="num">0; AB = class="num">0.382-class="num">0.618 XA; BC = class="num">1.272-class="num">1.414 AB; CD = class="num">0.786 XC; D < X
class=class="str">"cmt">//--- Bearish Cypher: X(high), A(low), B(high), C(low), D(high)
class=class="str">"cmt">//---   XA > class="num">0; AB = class="num">0.382-class="num">0.618 XA; BC = class="num">1.272-class="num">1.414 AB; CD = class="num">0.786 XC; D > X
class=class="str">"cmt">//--- Define a structure(like a custom data type) to store swing point info
class="kw">struct SwingPoint {  
   class="type">class="kw">datetime TimeOfSwing;     class=class="str">"cmt">//--- When the swing happened(date and time of the bar)
   class="type">class="kw">double   PriceAtSwing;    class=class="str">"cmt">//--- Price at the swing(high or low)
   class="type">bool     IsSwingHigh;     class=class="str">"cmt">//--- True = swing high; False = swing low
};  
class=class="str">"cmt">//--- Create a dynamic array to hold all detected swing points
SwingPoint SwingPoints[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Helper: Draw a filled triangle                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+  
class=class="str">"cmt">//--- Function to draw a triangle on the chart to highlight pattern segments
class="type">void DrawTriangle(class="type">class="kw">string TriangleName, class="type">class="kw">datetime Time1, class="type">class="kw">double Price1, class="type">class="kw">datetime Time2, class="type">class="kw">double Price2, class="type">class="kw">datetime Time3, class="type">class="kw">double Price3, class="type">class="kw">color LineColor, class="type">int LineWidth, class="type">bool FillTriangle, class="type">bool DrawBehind) {  
   class=class="str">"cmt">//--- Create a triangle object class="kw">using three points(time, price) on the chart
   if(ObjectCreate(class="num">0, TriangleName, OBJ_TRIANGLE, class="num">0, Time1, Price1, Time2, Price2, Time3, Price3)) {  
      ObjectSetInteger(class="num">0, TriangleName, OBJPROP_COLOR, LineColor);        class=class="str">"cmt">//--- Set the triangle’s class="type">class="kw">color (e.g., blue or red)
      ObjectSetInteger(class="num">0, TriangleName, OBJPROP_STYLE, STYLE_SOLID);     class=class="str">"cmt">//--- Use a solid line style
      ObjectSetInteger(class="num">0, TriangleName, OBJPROP_WIDTH, LineWidth);       class=class="str">"cmt">//--- Set the line thickness
      ObjectSetInteger(class="num">0, TriangleName, OBJPROP_FILL, FillTriangle);     class=class="str">"cmt">//--- Fill the triangle with class="type">class="kw">color if true

「画线函数的底层封装逻辑」

在 MT5 里做价格行为标注,别每次都手写 ObjectCreate,把趋势线、点线、文字标签各封一个 helper,后面调形态识别能省掉一半重复代码。下面这三个函数就是干这个的:趋势线吃起止时间加价格,点线专画水平位(比如进场或止盈),文字标签负责把“X”“TP1”锚到具体 K 线上。 趋势线函数里 OBJPROP_BACK 设成 true,图形会沉到蜡烛后面,不挡你看实体。点线用 STYLE_DOT 且宽度锁 1,视觉上比实线弱,适合标参考位而非结构线。 外汇和贵金属波动快,这类画线脚本只做辅助,信号失效概率始终存在,实盘前先在策略测试器跑一遍看重绘情况。

MQL5 / C++
class="type">void DrawTrendLine(class="type">class="kw">string LineName, class="type">class="kw">datetime StartTime, class="type">class="kw">double StartPrice, class="type">class="kw">datetime EndTime, class="type">class="kw">double EndPrice, class="type">class="kw">color LineColor, class="type">int LineWidth, class="type">int LineStyle) {
  if(ObjectCreate(class="num">0, LineName, OBJ_TREND, class="num">0, StartTime, StartPrice, EndTime, EndPrice)) {
    ObjectSetInteger(class="num">0, LineName, OBJPROP_COLOR, LineColor);  class=class="str">"cmt">//--- Set the line class="type">class="kw">color
    ObjectSetInteger(class="num">0, LineName, OBJPROP_STYLE, LineStyle);  class=class="str">"cmt">//--- Set line style(e.g., solid or dashed)
    ObjectSetInteger(class="num">0, LineName, OBJPROP_WIDTH, LineWidth);  class=class="str">"cmt">//--- Set line thickness
    ObjectSetInteger(class="num">0, LineName, OBJPROP_BACK, true);        
  }
}

class="type">void DrawDottedLine(class="type">class="kw">string LineName, class="type">class="kw">datetime StartTime, class="type">class="kw">double LinePrice, class="type">class="kw">datetime EndTime, class="type">class="kw">color LineColor) {
  if(ObjectCreate(class="num">0, LineName, OBJ_TREND, class="num">0, StartTime, LinePrice, EndTime, LinePrice)) {
    ObjectSetInteger(class="num">0, LineName, OBJPROP_COLOR, LineColor);  class=class="str">"cmt">//--- Set the line class="type">class="kw">color
    ObjectSetInteger(class="num">0, LineName, OBJPROP_STYLE, STYLE_DOT);  class=class="str">"cmt">//--- Use dotted style
    ObjectSetInteger(class="num">0, LineName, OBJPROP_WIDTH, class="num">1);          class=class="str">"cmt">//--- Thin line
  }
}

class="type">void DrawTextLabel(class="type">class="kw">string LabelName, class="type">class="kw">string LabelText, class="type">class="kw">datetime LabelTime, class="type">class="kw">double LabelPrice, class="type">class="kw">color TextColor, class="type">int FontSize, class="type">bool IsAbove) {

◍ 在K线高低点挂文字标签的落地写法

MT5 里给摆动点做可视化,最轻量的办法是用 OBJ_TEXT 对象贴坐标。下面这段逻辑在指定时间 LabelTime 与价格 LabelPrice 处建文本,成功后才接着设属性,避免对象没建出来就写属性导致报错。

MQL5 / C++
class=class="str">"cmt">//--- Create a text object at a specific time and price
if(ObjectCreate(class="num">0, LabelName, OBJ_TEXT, class="num">0, LabelTime, LabelPrice)) {
    ObjectSetString(class="num">0, LabelName, OBJPROP_TEXT, LabelText);             class=class="str">"cmt">//--- Set the text to display
    ObjectSetInteger(class="num">0, LabelName, OBJPROP_COLOR, TextColor);           class=class="str">"cmt">//--- Set text class="type">class="kw">color
    ObjectSetInteger(class="num">0, LabelName, OBJPROP_FONTSIZE, FontSize);         class=class="str">"cmt">//--- Set text size
    ObjectSetString(class="num">0, LabelName, OBJPROP_FONT, "Arial Bold");          class=class="str">"cmt">//--- Use bold Arial font
    class=class="str">"cmt">//--- Position text below if it’s a high point, above if it’s a low point
    ObjectSetInteger(class="num">0, LabelName, OBJPROP_ANCHOR, IsAbove ? ANCHOR_BOTTOM : ANCHOR_TOP);
    ObjectSetInteger(class="num">0, LabelName, OBJPROP_ALIGN, ALIGN_CENTER);         class=class="str">"cmt">//--- Center the text
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Main function that runs every time a new price tick arrives
class="type">void OnTick() {
    class=class="str">"cmt">//--- Use a class="kw">static variable to track the last bar’s time so we only process new bars
    class="kw">static class="type">class="kw">datetime LastProcessedBarTime = class="num">0;
    class=class="str">"cmt">//--- Get the time of the second-to-last bar(latest complete bar)
    class="type">class="kw">datetime CurrentBarTime = iTime(_Symbol, _Period, class="num">1);
    class=class="str">"cmt">//--- If no new bar has formed, exit to avoid over-processing
    if(CurrentBarTime == LastProcessedBarTime)
        class="kw">return;
    LastProcessedBarTime = CurrentBarTime;  class=class="str">"cmt">//--- Update to the current bar
}
class=class="str">"cmt">//--- Clear the SwingPoints array to start fresh each time
ArrayResize(SwingPoints, class="num">0);
class=class="str">"cmt">//--- Get the total number of bars on the chart
class="type">int TotalBars = Bars(_Symbol, _Period);
class="type">int StartBarIndex = SwingHighCount;             class=class="str">"cmt">//--- Start checking swings after SwingHighCount bars
class="type">int EndBarIndex = TotalBars - SwingLowCount;    class=class="str">"cmt">//--- Stop before the last SwingLowCount bars

class=class="str">"cmt">//--- Loop through bars to find swing highs and lows(swing points)
for(class="type">int BarIndex = EndBarIndex - class="num">1; BarIndex >= StartBarIndex; BarIndex--) {
    class="type">bool IsSwingHigh = true;  class=class="str">"cmt">//--- Assume it’s a high until proven otherwise
    class="type">bool IsSwingLow = true;   class=class="str">"cmt">//--- Assume it’s a low until proven otherwise
    class="type">class="kw">double CurrentBarHigh = iHigh(_Symbol, _Period, BarIndex); class=class="str">"cmt">//--- Get the high of this bar
    class="type">class="kw">double CurrentBarLow = iLow(_Symbol, _Period, BarIndex);   class=class="str">"cmt">//--- Get the low of this bar
    class=class="str">"cmt">//--- Check bars to the left and right to confirm it’s a swing point
逐行拆一下关键处:ObjectCreate 第4参数填 0 代表用时间轴子窗口0(主图);OBJPROP_ANCHOR 用三目运算,高点把文字锚定在底部(显示在上头)、低点锚定顶部(显示在下头),这样标签不会挡住影线。OnTick 里用 static datetime 存上一根处理时间,只处理 iTime(_Symbol,_Period,1) 即倒数第二根完结棒,每 tick 都重跑摆动检测会被拖死。 扫描区间由 SwingHighCount 与 SwingLowCount 控边界:从 StartBarIndex 往后、到 EndBarIndex 往前留缓冲,避免未完结棒误判。外汇与贵金属波动大,摆动参数设太小(如 1~2 根)会在毛刺上疯狂标信号,建议先按 3~5 根回看验证。

MQL5 / C++
class=class="str">"cmt">//--- Create a text object at a specific time and price
if(ObjectCreate(class="num">0, LabelName, OBJ_TEXT, class="num">0, LabelTime, LabelPrice)) {
    ObjectSetString(class="num">0, LabelName, OBJPROP_TEXT, LabelText);             class=class="str">"cmt">//--- Set the text to display
    ObjectSetInteger(class="num">0, LabelName, OBJPROP_COLOR, TextColor);           class=class="str">"cmt">//--- Set text class="type">class="kw">color
    ObjectSetInteger(class="num">0, LabelName, OBJPROP_FONTSIZE, FontSize);         class=class="str">"cmt">//--- Set text size
    ObjectSetString(class="num">0, LabelName, OBJPROP_FONT, "Arial Bold");          class=class="str">"cmt">//--- Use bold Arial font
    class=class="str">"cmt">//--- Position text below if it’s a high point, above if it’s a low point
    ObjectSetInteger(class="num">0, LabelName, OBJPROP_ANCHOR, IsAbove ? ANCHOR_BOTTOM : ANCHOR_TOP);
    ObjectSetInteger(class="num">0, LabelName, OBJPROP_ALIGN, ALIGN_CENTER);         class=class="str">"cmt">//--- Center the text
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Main function that runs every time a new price tick arrives
class="type">void OnTick() {
    class=class="str">"cmt">//--- Use a class="kw">static variable to track the last bar’s time so we only process new bars
    class="kw">static class="type">class="kw">datetime LastProcessedBarTime = class="num">0;
    class=class="str">"cmt">//--- Get the time of the second-to-last bar(latest complete bar)
    class="type">class="kw">datetime CurrentBarTime = iTime(_Symbol, _Period, class="num">1);
    class=class="str">"cmt">//--- If no new bar has formed, exit to avoid over-processing
    if(CurrentBarTime == LastProcessedBarTime)
        class="kw">return;
    LastProcessedBarTime = CurrentBarTime;  class=class="str">"cmt">//--- Update to the current bar
}
class=class="str">"cmt">//--- Clear the SwingPoints array to start fresh each time
ArrayResize(SwingPoints, class="num">0);
class=class="str">"cmt">//--- Get the total number of bars on the chart
class="type">int TotalBars = Bars(_Symbol, _Period);
class="type">int StartBarIndex = SwingHighCount;             class=class="str">"cmt">//--- Start checking swings after SwingHighCount bars
class="type">int EndBarIndex = TotalBars - SwingLowCount;    class=class="str">"cmt">//--- Stop before the last SwingLowCount bars

class=class="str">"cmt">//--- Loop through bars to find swing highs and lows(swing points)
for(class="type">int BarIndex = EndBarIndex - class="num">1; BarIndex >= StartBarIndex; BarIndex--) {
    class="type">bool IsSwingHigh = true;  class=class="str">"cmt">//--- Assume it’s a high until proven otherwise
    class="type">bool IsSwingLow = true;   class=class="str">"cmt">//--- Assume it’s a low until proven otherwise
    class="type">class="kw">double CurrentBarHigh = iHigh(_Symbol, _Period, BarIndex); class=class="str">"cmt">//--- Get the high of this bar
    class="type">class="kw">double CurrentBarLow = iLow(_Symbol, _Period, BarIndex);   class=class="str">"cmt">//--- Get the low of this bar
    class=class="str">"cmt">//--- Check bars to the left and right to confirm it’s a swing point

摆动点判定与赛弗形态拼接

识别摆高点与摆低点后,要做的第一件事是排除邻 bars 的干扰。下面这段循环从 BarIndex 左侧 SwingHighCount 根扫到右侧 SwingLowCount 根,只要出现更高高点就推翻 IsSwingHigh,出现更低低点就推翻 IsSwingLow,当前 bar 与越界索引直接 continue 跳过。

MQL5 / C++
for(class="type">int NeighborIndex = BarIndex - SwingHighCount; NeighborIndex <= BarIndex + SwingLowCount; NeighborIndex++) {
   if(NeighborIndex < class="num">0 || NeighborIndex >= TotalBars || NeighborIndex == BarIndex) class=class="str">"cmt">//--- Skip invalid bars or current bar
      class="kw">continue;
   if(iHigh(_Symbol, _Period, NeighborIndex) > CurrentBarHigh) class=class="str">"cmt">//--- If any bar is higher, not a high
      IsSwingHigh = class="kw">false;
   if(iLow(_Symbol, _Period, NeighborIndex) < CurrentBarLow)      class=class="str">"cmt">//--- If any bar is lower, not a low
      IsSwingLow = class="kw">false;
}
class=class="str">"cmt">//--- If it’s a high or low, store it in the SwingPoints array
if(IsSwingHigh || IsSwingLow) {
   SwingPoint NewSwing;
   NewSwing.TimeOfSwing = iTime(_Symbol, _Period, BarIndex); class=class="str">"cmt">//--- Store the bar’s time
   NewSwing.PriceAtSwing = IsSwingHigh ? CurrentBarHigh : CurrentBarLow; class=class="str">"cmt">//--- Store high or low price
   NewSwing.IsSwingHigh = IsSwingHigh;                      class=class="str">"cmt">//--- Mark as high or low
   class="type">int CurrentArraySize = ArraySize(SwingPoints);           class=class="str">"cmt">//--- Get current array size
   ArrayResize(SwingPoints, CurrentArraySize + class="num">1);           class=class="str">"cmt">//--- Add one more slot
   SwingPoints[CurrentArraySize] = NewSwing;                class=class="str">"cmt">//--- Add the swing to the array
}
}
class=class="str">"cmt">//--- Check if we have enough swing points(need class="num">5 for Cypher: X, A, B, C, D)
class="type">int TotalSwingPoints = ArraySize(SwingPoints);
if(TotalSwingPoints < class="num">5)
   class="kw">return; class=class="str">"cmt">//--- Exit if not enough swing points
class=class="str">"cmt">//--- Assign the last class="num">5 swing points to X, A, B, C, D(most recent is D)
SwingPoint PointX = SwingPoints[TotalSwingPoints - class="num">5];
SwingPoint PointA = SwingPoints[TotalSwingPoints - class="num">4];
SwingPoint PointB = SwingPoints[TotalSwingPoints - class="num">3];
SwingPoint PointC = SwingPoints[TotalSwingPoints - class="num">2];
SwingPoint PointD = SwingPoints[TotalSwingPoints - class="num">1];
class=class="str">"cmt">//--- Variables to track if we found a pattern and its type
class="type">bool PatternFound = class="kw">false;
class="type">class="kw">string PatternDirection = "";
class=class="str">"cmt">//--- Check for Bearish Cypher pattern
if(PointX.IsSwingHigh && !PointA.IsSwingHigh && PointB.IsSwingHigh && !PointC.IsSwingHigh && PointD.IsSwingHigh) {
   class="type">class="kw">double LegXA = PointX.PriceAtSwing - PointA.PriceAtSwing; class=class="str">"cmt">//--- Calculate XA leg(should be positive)
   if(LegXA > class="num">0) {
      class="type">class="kw">double LegAB = PointB.PriceAtSwing - PointA.PriceAtSwing; class=class="str">"cmt">//--- AB leg
      class="type">class="kw">double LegBC = PointB.PriceAtSwing - PointC.PriceAtSwing; class=class="str">"cmt">//--- BC leg
      class="type">class="kw">double LegXC = PointX.PriceAtSwing - PointC.PriceAtSwing; class=class="str">"cmt">//--- XC leg
      class="type">class="kw">double LegCD = PointD.PriceAtSwing - PointC.PriceAtSwing; class=class="str">"cmt">//--- CD leg
      class=class="str">"cmt">//--- Check Fibonacci rules and D > X for bearish
      if(LegAB >= class="num">0.382 * LegXA && LegAB <= class="num">0.618 * LegXA &&
         LegBC >= class="num">1.272 * LegAB && LegBC <= class="num">1.414 * LegAB &&
逐行看:第1行定义邻 bar 扫描范围;第2–4行过滤无效与自身 bar;第5–6行用 iHigh 比当前高则否掉摆高;第7–8行用 iLow 比当前低则否掉摆低。确认是摆动后,NewSwing 记录时间、价格、类型,ArrayResize 扩一位塞进 SwingPoints。 赛弗形态需要连续 5 个摆动点。代码取数组末五位赋给 X/A/B/C/D,熊赛弗要求 X 高、A 低、B 高、C 低、D 高。LegXA 为正才继续,AB 腿须落在 XA 的 0.382–0.618 倍区间,BC 腿须为 AB 的 1.272–1.414 倍——这两个阈值不达标,形态直接判否。外汇与贵金属波动剧烈,这类谐波识别仅提供概率参考,实盘须自担高风险。 开 MT5 把 SwingHighCount / SwingLowCount 调到 3~5 根测试,看不同邻域宽度下摆动点数量变化,能直观感受噪音过滤的边界。

MQL5 / C++
for(class="type">int NeighborIndex = BarIndex - SwingHighCount; NeighborIndex <= BarIndex + SwingLowCount; NeighborIndex++) {
   if(NeighborIndex < class="num">0 || NeighborIndex >= TotalBars || NeighborIndex == BarIndex) class=class="str">"cmt">//--- Skip invalid bars or current bar
      class="kw">continue;
   if(iHigh(_Symbol, _Period, NeighborIndex) > CurrentBarHigh) class=class="str">"cmt">//--- If any bar is higher, not a high
      IsSwingHigh = class="kw">false;
   if(iLow(_Symbol, _Period, NeighborIndex) < CurrentBarLow)      class=class="str">"cmt">//--- If any bar is lower, not a low
      IsSwingLow = class="kw">false;
}
class=class="str">"cmt">//--- If it’s a high or low, store it in the SwingPoints array
if(IsSwingHigh || IsSwingLow) {
   SwingPoint NewSwing;
   NewSwing.TimeOfSwing = iTime(_Symbol, _Period, BarIndex); class=class="str">"cmt">//--- Store the bar’s time
   NewSwing.PriceAtSwing = IsSwingHigh ? CurrentBarHigh : CurrentBarLow; class=class="str">"cmt">//--- Store high or low price
   NewSwing.IsSwingHigh = IsSwingHigh;                      class=class="str">"cmt">//--- Mark as high or low
   class="type">int CurrentArraySize = ArraySize(SwingPoints);           class=class="str">"cmt">//--- Get current array size
   ArrayResize(SwingPoints, CurrentArraySize + class="num">1);           class=class="str">"cmt">//--- Add one more slot
   SwingPoints[CurrentArraySize] = NewSwing;                class=class="str">"cmt">//--- Add the swing to the array
}
}
class=class="str">"cmt">//--- Check if we have enough swing points(need class="num">5 for Cypher: X, A, B, C, D)
class="type">int TotalSwingPoints = ArraySize(SwingPoints);
if(TotalSwingPoints < class="num">5)
   class="kw">return; class=class="str">"cmt">//--- Exit if not enough swing points
class=class="str">"cmt">//--- Assign the last class="num">5 swing points to X, A, B, C, D(most recent is D)
SwingPoint PointX = SwingPoints[TotalSwingPoints - class="num">5];
SwingPoint PointA = SwingPoints[TotalSwingPoints - class="num">4];
SwingPoint PointB = SwingPoints[TotalSwingPoints - class="num">3];
SwingPoint PointC = SwingPoints[TotalSwingPoints - class="num">2];
SwingPoint PointD = SwingPoints[TotalSwingPoints - class="num">1];
class=class="str">"cmt">//--- Variables to track if we found a pattern and its type
class="type">bool PatternFound = class="kw">false;
class="type">class="kw">string PatternDirection = "";
class=class="str">"cmt">//--- Check for Bearish Cypher pattern
if(PointX.IsSwingHigh && !PointA.IsSwingHigh && PointB.IsSwingHigh && !PointC.IsSwingHigh && PointD.IsSwingHigh) {
   class="type">class="kw">double LegXA = PointX.PriceAtSwing - PointA.PriceAtSwing; class=class="str">"cmt">//--- Calculate XA leg(should be positive)
   if(LegXA > class="num">0) {
      class="type">class="kw">double LegAB = PointB.PriceAtSwing - PointA.PriceAtSwing; class=class="str">"cmt">//--- AB leg
      class="type">class="kw">double LegBC = PointB.PriceAtSwing - PointC.PriceAtSwing; class=class="str">"cmt">//--- BC leg
      class="type">class="kw">double LegXC = PointX.PriceAtSwing - PointC.PriceAtSwing; class=class="str">"cmt">//--- XC leg
      class="type">class="kw">double LegCD = PointD.PriceAtSwing - PointC.PriceAtSwing; class=class="str">"cmt">//--- CD leg
      class=class="str">"cmt">//--- Check Fibonacci rules and D > X for bearish
      if(LegAB >= class="num">0.382 * LegXA && LegAB <= class="num">0.618 * LegXA &&
         LegBC >= class="num">1.272 * LegAB && LegBC <= class="num">1.414 * LegAB &&

「牛旗与熊旗赛弗的可视化落地」

熊向赛弗在 X 为摆动高点、D 高于 X 时判定成立,牛向则要求 X 为非高点且 D 低于 X,CD 段与 0.786 倍 XC 的偏差须落在 FibonacciTolerance * LegXC 内。AB 介于 0.382~0.618 倍 XA、BC 介于 1.272~1.414 倍 AB,这几组比例是肉眼复盘时最容易漏掉的硬约束。 命中后直接往 Experts 日志打一行方向加时间,并用 D 的摆动时间拼出 CY_ 前缀,避免多图案对象重名覆盖。牛向三角填蓝、熊向填红,两笔 DrawTriangle 把 XAB 与 BCD 各自封口,图形一上图就知道是哪一类。 六条连线用 STYLE_SOLID 黑线把 XA、AB、BC、CD 等摆动点串起来,参数线宽统一给 2,方便在 MT5 主图缩放时仍看得清腿段关系。外汇与贵金属波动剧烈,赛弗只是概率倾向,实盘前请在策略测试器跑一段历史样本确认容差设置。

MQL5 / C++
else if(!PointX.IsSwingHigh && PointA.IsSwingHigh && !PointB.IsSwingHigh && PointC.IsSwingHigh && !PointD.IsSwingHigh) {
  class="type">class="kw">double LegXA = PointA.PriceAtSwing - PointX.PriceAtSwing; class=class="str">"cmt">//--- Calculate XA leg(should be positive)
  if(LegXA > class="num">0) {
    class="type">class="kw">double LegAB = PointA.PriceAtSwing - PointB.PriceAtSwing; class=class="str">"cmt">//--- AB leg
    class="type">class="kw">double LegBC = PointC.PriceAtSwing - PointB.PriceAtSwing; class=class="str">"cmt">//--- BC leg
    class="type">class="kw">double LegXC = PointC.PriceAtSwing - PointX.PriceAtSwing; class=class="str">"cmt">//--- XC leg
    class="type">class="kw">double LegCD = PointC.PriceAtSwing - PointD.PriceAtSwing; class=class="str">"cmt">//--- CD leg
    class=class="str">"cmt">//--- Check Fibonacci rules and D < X for bullish
    if(LegAB >= class="num">0.382 * LegXA && LegAB <= class="num">0.618 * LegXA &&
        LegBC >= class="num">1.272 * LegAB && LegBC <= class="num">1.414 * LegAB &&
        MathAbs(LegCD - class="num">0.786 * LegXC) <= FibonacciTolerance * LegXC && PointD.PriceAtSwing < PointX.PriceAtSwing) {
      PatternFound = true;
      PatternDirection = "Bullish";
    }
  }
}
class=class="str">"cmt">//--- If a pattern is found, visualize it and trade
if(PatternFound) {
  class=class="str">"cmt">//--- Log the pattern detection in the Experts tab
  Print(PatternDirection, " Cypher pattern detected at ", TimeToString(PointD.TimeOfSwing, TIME_DATE|TIME_MINUTES));

  class=class="str">"cmt">//--- Create a unique prefix for all chart objects class="kw">using D’s time
  class="type">class="kw">string ObjectPrefix = "CY_" + IntegerToString(PointD.TimeOfSwing);
  class=class="str">"cmt">//--- Set triangle class="type">class="kw">color: blue for bullish, red for bearish
  class="type">class="kw">color TriangleColor = (PatternDirection == "Bullish") ? clrBlue : clrRed;

  class=class="str">"cmt">//--- **Visualization Steps**
  class=class="str">"cmt">//--- class="num">1. Draw two filled triangles to highlight the pattern
  DrawTriangle(ObjectPrefix + "_Triangle1", PointX.TimeOfSwing, PointX.PriceAtSwing, PointA.TimeOfSwing, PointA.PriceAtSwing, PointB.TimeOfSwing, PointB.PriceAtSwing, TriangleColor, class="num">2, true, true);
  DrawTriangle(ObjectPrefix + "_Triangle2", PointB.TimeOfSwing, PointB.PriceAtSwing, PointC.TimeOfSwing, PointC.PriceAtSwing, PointD.TimeOfSwing, PointD.PriceAtSwing, TriangleColor, class="num">2, true, true);
}
class=class="str">"cmt">//--- class="num">2. Draw six trend lines connecting the swing points
DrawTrendLine(ObjectPrefix + "_Line_XA", PointX.TimeOfSwing, PointX.PriceAtSwing, PointA.TimeOfSwing, PointA.PriceAtSwing, clrBlack, class="num">2, STYLE_SOLID);
DrawTrendLine(ObjectPrefix + "_Line_AB", PointA.TimeOfSwing, PointA.PriceAtSwing, PointB.TimeOfSwing, PointB.PriceAtSwing, clrBlack, class="num">2, STYLE_SOLID);
DrawTrendLine(ObjectPrefix + "_Line_BC", PointB.TimeOfSwing, PointB.PriceAtSwing, PointC.TimeOfSwing, PointC.PriceAtSwing, clrBlack, class="num">2, STYLE_SOLID);
DrawTrendLine(ObjectPrefix + "_Line_CD", PointC.TimeOfSwing, PointC.PriceAtSwing, PointD.TimeOfSwing, PointD.PriceAtSwing, clrBlack, class="num">2, STYLE_SOLID);
把形态巡检交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到密码形态的可视化标注与比例回测摘要,你只需要在形态确认后决定仓位。

常见问题

密码形态核心在 BC 延伸 127.2%–141.4% 且 CD 回撤 XC 约 78.6%,蝴蝶更依赖 D 超越 X,蝙蝠则 B 回撤更浅,比例阈值不同导致触发频率与反转区位置差异明显。
通常用 ZigZag 或自定义拐点检测取极值,再按斐波那契区间过滤;固定 K 线数会漏掉真实波段,回测失真概率偏高。
可用 ObjectCreate 配合 OBJ_TRIANGLE 与 OBJ_TREND,绑定时间价格坐标,并在 D 点用 Text 标签标 X/A/B/C/D,注意清理旧对象避免重绘堆积。
可以,小布盯盘的品种页已内置谐波识别与可视化,会自动标出四脚比例与潜在反转区,但外汇贵金属波动剧烈,信号仅作概率参考,决策仍需自主判断。
贵金属与交叉盘流动性差异大,建议按品种历史tick滑点分布设 2–5 点上限,过度宽松会高估胜率,实盘可能倾向不及预期。