MQL5 中的交易策略自动化(第十五部分):可视化价格行为的谐波形态模式·进阶篇
(2/3)· 很多 EA 只报信号不画形态,肉眼复核费时;这篇把 XA/AB/BC/CD 四脚几何变成可视图层与下单逻辑
做谐波形态的人常卡在一件事:指标算出潜在 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 编译,先不开交易,看摆动点捕捉是否符合预期。
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,视觉上比实线弱,适合标参考位而非结构线。 外汇和贵金属波动快,这类画线脚本只做辅助,信号失效概率始终存在,实盘前先在策略测试器跑一遍看重绘情况。
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 处建文本,成功后才接着设属性,避免对象没建出来就写属性导致报错。
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
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 跳过。
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 &&
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 主图缩放时仍看得清腿段关系。外汇与贵金属波动剧烈,赛弗只是概率倾向,实盘前请在策略测试器跑一段历史样本确认容差设置。
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);