English Deutsch 日本語
preview
价格行为分析工具包开发(第二十一部分):市场结构反转检测工具

价格行为分析工具包开发(第二十一部分):市场结构反转检测工具

MetaTrader 5交易系统 |
229 1
Christian Benjamin
Christian Benjamin

内容


概述

该EA直面交易中最棘手的挑战之一:虚假反转信号。在价格剧烈震荡的行情中,简单的枢轴点算法会频繁失效,将交易者困于反复止损的“锯齿陷阱”(whipsaws)中。市场结构反转检测器通过将ATR转化为动态的K线数量过滤器。它忽略微小波动,仅捕捉有效的高低点;当更高高点转为更低高点时,标记为看跌反转;而当更低低点转为更高低点时,标记为看涨反转。在此过程中,您将了解如何:

  • 将ATR转化为一种深度度量指标,以便在市场剧烈波动时扩张,在市场平静时收缩。
  • 通过扫描候选高点或低点两侧固定数量的K线,确认枢轴点的有效性。
  • 保持趋势偏向性,确保反转信号仅在前一个市场结构被突破后触发。
  • 在图表上用箭头、枢轴标签和实时统计面板标注信号,跟踪反转次数与时间点。

最终,您将获得一款抗干扰性强的EA,它能穿透市场噪音,仅提供基于规则的纯净反转信号,并在真实反转发生时触发声音和推送警报。


工具重要性

在震荡行情中,超过一半的枢轴点触发信号可能是虚假的,导致频繁止损和负期望值。我们将摆动点过滤器与威尔德(Wilder)于1978年提出的ATR绑定——ATR比简单的高低点范围更能精准衡量波动率,从而实时调整枢轴点所需的“深度”。当ATR因波动加剧而上升时,过滤器放宽阈值,忽略小型、不规则波动;而当市场平静时,过滤器收紧阈值,迅速捕捉真实转向。

每根K线的真实波幅(TR)定义为:

TR公式


ATR是TR的n周期简单移动平均值(默认n = 14)。

我们通过以下公式将ATR 转化为基于K线数量的深度指标d

ATR

这里, Point为最小价格变动单位;m为ATR乘数;f为宽松因子。 

看跌反转的触发条件为:先出现一个更高高点,随后下一个波动高点跌破该峰值。看涨反转的触发条件则相反:先出现一个更低低点,随后下一个波动低点突破该谷值。假设收益率服从均值为0、方差为“波动率平方”的正态分布,则在“两倍深度阈值加1”的区间内,任意单根K线成为最高点的概率仅为1/(2×深度阈值+1)。由于我们将深度阈值直接与市场波动率挂钩——通过将波动率估算为ATR除以二分之一π的平方根——我们得以直接控制虚假信号的发生率。实际应用中,此方法允许我们调整参数,将噪声信号比例控制在约5%以内。

TradeStation的研究显示,基于ATR的枢轴窗口在标普500指数5年数据中,将噪声驱动的交易减少了约40%,同时使净利润提升了约22%。QuantifiedStrategies.com的回测报告指出,ATR过滤后的枢轴点在欧元兑美元(EURUSD)和标普500迷你期货(ES)中,将命中率从约35%提高至58%,并将平均盈亏比从约1.1提升至1.8。TradingView社区的反馈表明,ATR窗口化枢轴工具与机构订单流的突破点高度吻合,尤其在1小时和4小时图表中效果显著。


行动计划大纲

该EA通过将当前ATR值转化为动态“深度”窗口来过滤市场噪声:在波动剧烈时窗口扩大,在市场平静时窗口缩小,随后验证每根K线的收盘高点或低点是否在该窗口内形成有效枢轴点。系统会记录最近两个确认的波动高点和低点,并跟踪一个简单的趋势方向标识:当新高突破前高时标识转为“多头”,当新低跌破前低时标识转为“空头”。当市场结构反转时——即在多头趋势中最新高点低于先前高点(看跌反转),或在空头趋势中最新低点高于先前低点(看涨反转)——EA会在图表上标注彩色箭头,标记两个枢轴点,更新实时统计面板,并可触发声音或推送警报。此方法确保您仅识别真实的“高点转低点”或“低点转高点”反转信号。

看跌反转

当价格连续形成两个更高高点时,判定为看涨趋势。EA随后寻找低于前一个波动高点的新波动高点。将基于ATR动态窗口内的最高点(窗口大小随波动率调整)定义为波动高点。当EA处于“看涨”状态且检测到此类“低高点”时,在对应K线上标注红色箭头并标记“LH”。除可视化标记外,EA会生成警报并记录看跌反转信号,表明卖方开始占据主动。

看跌反转

看涨反转

当价格连续形成两个更低低点时,判定为看跌趋势。随后,EA会寻找一个高于前一个波动低点的新波动低点。将基于ATR动态窗口内的最低点(窗口大小随市场波动率调整)定义为波动地点。当EA处于“看跌”状态且检测到此类“高低点”时,会在对应K线上标注绿色箭头并标记“HL”。此外,EA会生成警报并记录看涨反转信号,表明买方力量可能开始回升。

看涨反转


EA剖析

在编写任何MQL5文件时,我们首先会添加#property指令来定义文件的基本属性。通过启用严格编译模式,编译器会检查代码中是否存在不安全的类型转换或已弃用的函数调用。我们还会添加版权声明、链接和版本标签,确保阅读代码的人能明确作者信息、获取更多细节的途径,以及当前查看的代码版本。这些指令不会影响程序逻辑,仅用于标记文件身份(类似“数字签名”)。

#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

接下来,我们将公开所有交易者可能需要调整的参数设置。以下三个参数用于定义如何通过ATR识别枢轴点(关键高低点)。

  • InpAtrPeriod:设置ATR计算所使用的K线数量。短周期(如7)响应迅速,但可能捕捉到市场噪声(虚假信号)。长周期(如21)过滤波动尖峰,但会引入滞后性。
  • InpAtrMultiplier:将ATR值转换为枢轴点识别的最小波动宽度。设置为1.0:价格需波动1个ATR值才标记为枢轴点。提高至1.5或2.0:过滤更严格,仅识别更大幅度的波动。
  • InpAtrLoosenFactor:在0到1之间按比例缩小波动宽度要求。设置为0.5:将波动要求减半,枢轴点更早出现(适用于低波动市场)。

接下来是与图表显示相关的参数设置。

  • InpAutoShift:当新K线生成时,自动在图表右侧保留空白区域。
  • InpShiftBars:定义预留的空白K线数量(默认值为5)。

通过预留空白区域,避免信号箭头、标签和统计面板遮挡价格走势。

最后,提供两种警报通知方式:

  • InpEnableSound:每次信号触发时,EA播放WAV文件。
  • InpSoundFile:命名MetaTrader 5声音文件夹中的文件名。
  • InpEnablePush:向MetaTrader 5手机应用发送推送通知。

借助这些选项,您可以自由选择仅在电脑端接收声音警报、仅在手机端接收推送,或同时启用两者。

input int    InpAtrPeriod       = 14;      // How many bars for ATR
input double InpAtrMultiplier   = 1.0;     // Scale ATR into bar‑depth
input double InpAtrLoosenFactor = 0.5;     // Optional: loosen the swing filter
input bool   InpAutoShift       = true;    // Push bars left for visibility
input int    InpShiftBars       = 5;       // Number of bars for right margin
input bool   InpEnableSound     = true;    // Play a sound on flip
input string InpSoundFile       = "alert.wav";
input bool   InpEnablePush      = false;   // Send push notifications

启动EA启动时,OnInit函数首先检查InpAutoShift参数(是否启用图表自动右移功能)。如果启用,则调用ChartSetInteger函数,设置CHART_SHIFT属性为InpShiftBars指定的K线数量,将新生成的K线向左推移,为后续的信号标注预留空白区域;随后,EA通过iATR函数请求MetaTrader内置的ATR指标句柄,并将其存储在atrHandle 变量中,如果句柄无效(如指标加载失败),EA会立即终止初始化流程并返回INIT_FAILED错误;最后,EA创建一个左上角标签(类型为OBJ_LABEL,名称为panelName),将其固定在图表左上角,并设置水平和垂直偏移量为10像素,字体大小为10,颜色为黄色。完成所有初始化后,EA返回INIT_SUCCEEDED,表明ATR数据接口和统计面板已就绪,可进入OnTick主循环处理实时行情。

int OnInit()
{
   if(InpAutoShift)
      ChartSetInteger(0, CHART_SHIFT, InpShiftBars);

   atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
   if(atrHandle == INVALID_HANDLE)
      return INIT_FAILED;

   ObjectCreate(0, panelName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, panelName, OBJPROP_CORNER,    CORNER_LEFT_UPPER);
   ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_FONTSIZE,  10);
   ObjectSetInteger(0, panelName, OBJPROP_COLOR,     clrYellow);

   return INIT_SUCCEEDED;
}

每当我们移除EA或关闭MetaTrader 5时,OnDeinit函数会清除EA运行时创建的箭头标记和文本标注,删除状态标签,并且释放ATR句柄。这样可以防止图表杂乱无章并及时释放资源。

void OnDeinit(const int reason)
{
   ObjectsDeleteAll(0, -1, OBJ_ARROW);
   ObjectsDeleteAll(0, -1, OBJ_TEXT);
   ObjectDelete(0, panelName);
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
}

OnTick主循环中,我们通过引入静态变量lastBar(记录上一根K线的开盘时间)来避免在每次价格更新时重复执行枢轴点逻辑。用iTime(...,1) 获取最新已收盘的开盘时间thisBar ,并与lastBar进行比较。如果二者相同,说明仍在同一根K线内,直接返回即可。一旦二者不同,我们则更新lastBar并继续执行后续逻辑。

随后对ATR句柄调用CopyBuffer,仅取最新值,如果调用失败则立刻终止,避免使用无效数据。获取到有效ATR后,将ATR的价格波动值转换为K线数量,计算“深度”。深度 = ATR ÷ SYMBOL_POINT × InpAtrMultiplier × InpAtrLoosenFactor,通过MathMax确保深度至少为1根K线。当波动加剧时(ATR值增大)深度增大(需要更大的波段),当波动平缓时(ATR值减小)深度缩小(允许更紧的波段),需要将动态深度值传递给实际的枢轴点检测函数。

void OnTick()
{
   static datetime lastBar=0;
   datetime thisBar = iTime(_Symbol,_Period,1);
   if(thisBar == lastBar) return;
   lastBar = thisBar;

   double atrBuf[];
   if(CopyBuffer(atrHandle, 0, 1, 1, atrBuf) <= 0) return;
   double atr = atrBuf[0];

   int depth = MathMax(1,
      int(atr / SymbolInfoDouble(_Symbol, SYMBOL_POINT)
          * InpAtrMultiplier * InpAtrLoosenFactor));
   // … pivot checks follow …
}

在确定动态深度后,我们通过两个简单的循环来验证枢轴点。对于枢轴高点,调用IsSwingHigh(1, depth)函数,检查当前候选高点是否满足条件:在其左右各depth根K线范围内,没有任何一根K线的高点超过该候选值;对于枢轴低点,调用IsSwingLow(1, depth)函数执行相反逻辑。当发现新高或新低时,我们将lastHigh移入prevHigh(低点同理)并记录时间戳。同时跟踪“先前枢轴”与“当前枢轴”,便于进行后续比较。

bool newHigh = IsSwingHigh(1, depth);
bool newLow  = IsSwingLow (1, depth);
double h = iHigh(_Symbol,_Period,1), l = iLow(_Symbol,_Period,1);

if(newHigh)
{
   prevHigh     = lastHigh;
   prevHighTime = lastHighTime;
   lastHigh     = h;
   lastHighTime = thisBar;
}
if(newLow)
{
   prevLow      = lastLow;
   prevLowTime  = lastLowTime;
   lastLow      = l;
   lastLowTime  = thisBar;
}

一旦确认好枢轴点,我们通过更新structState结构体来反映当前趋势倾向:出现新高点时将状态设为1(表示可能即将发生看跌反转),出现新低点时将状态设为2(表示可能即将发生看涨反转)。随后,让我们检测实际反转情况:状态1下,如果新高点低于前一个高点(即未创新高),则确认看跌反转;状态2下,如果新低点高于前一个低点(即未创新低),则确认看涨反转。一旦触发反转,即调用绘图与通知函数,并将计数器递增。

// Update bias
if(newHigh && prevHigh>0 && lastHigh > prevHigh) structState = 1;
if(newLow  && prevLow>0  && lastLow  < prevLow) structState = 2;

// Bearish flip
if(newHigh && structState==1 && lastHigh < prevHigh)
{
   PlotArrow(...);  PlotLabel(...);  Notify(...);
   if(countBear>0) sumBearInterval += (lastHighTime - prevLowTime)/60.0;
   countBear++;
}

// Bullish flip
if(newLow && structState==2 && lastLow > prevLow)
{
   PlotArrow(...);  PlotLabel(...);  Notify(...);
   if(countBull>0) sumBullInterval += (lastLowTime - prevHighTime)/60.0;
   countBull++;
}

我们将所有图表绘制封装成两个辅助函数——PlotArrow与PlotLabel——兼顾效率与清晰。对于每个函数,首先调用ObjectFind(0, name)函数,按唯一名称查找已有对象;该操作的时间复杂度为O(n)(与图表对象数量成线性关系),但在现代计算机上,对于每根K线的偶发性检查而言,其执行速度已经足够高效。如果对象尚不存在(ObjectFind返回-1),则通过ObjectCreate仅创建一次该对象,并根据需求选择合适的对象类型(例如绘制箭头时用箭头对象,绘制标签时用文本标签对象)。

随后,我们自定义对象属性:对于箭头对象,通过设置OBJPROP_ARROWCODE选择所需的符号样式(例如,使用Wingdings字体中代码为234的红色向下箭头),并通过OBJPROP_COLOR定义其颜色;对于标签对象,通过OBJPROP_TEXT设置显示文本(如“LH”或“HL”),同时调整偏移量和字体大小。通过避免重复调用ObjectCreate,我们防止了因长时间堆积数百或数千个相同对象而导致的性能下降和内存占用激增问题。这种模式还能确保每个关键点标记具有稳定且可预测的唯一标识符,因此,如果后续需要调整其OBJPROP_ZORDER(绘制优先级)或在特定条件下删除对象,只需通过名称引用即可完全避免误操作其他图表元素。

void PlotArrow(string name, datetime t, double price, int code, color c)
{
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_ARROW, 0, t, price);
      ObjectSetInteger(0, name, OBJPROP_ARROWCODE, code);
      ObjectSetInteger(0, name, OBJPROP_COLOR, c);
   }
}
// PlotLabel is identical, but creates OBJ_TEXT and sets OBJPROP_TEXT.

在每根K线收盘后,我们重新更新名为panelName的标签面板,其中包括:

  • 当前关键点枢轴深度
  • 多空反转总次数
  • 反转间隔平均时间(单位:分钟)(需至少发生两次反转后开始计算)

通过这些数据,您可以立即获得关于目前ATR参数设置下价格结构突破频率的反馈。

string txt = StringFormat("Depth: %d\nBull Flips: %d\nBear Flips: %d",
                          depth, countBull, countBear);
if(countBull>1)
   txt += "\nAvg HL Int: " + DoubleToString(sumBullInterval/(countBull-1),1) + "m";
if(countBear>1)
   txt += "\nAvg LH Int: " + DoubleToString(sumBearInterval/(countBear-1),1) + "m";

ObjectSetString(0, panelName, OBJPROP_TEXT, txt);

最后,我们的Notify(msg)函数将所有警报方法集中封装在一个接口中。无论何时触发警报,系统都会默认调用Alert(msg)生成MetaTrader 5的弹窗提示,同时根据用户配置可选地播放声音(PlaySound)或发送推送通知(SendNotification)。这种集中式设计使得后续扩展电子邮件或Webhook警报功能变得轻而易举。

void Notify(string msg)
{
   Alert(msg);
   if(InpEnableSound) PlaySound(InpSoundFile);
   if(InpEnablePush)  SendNotification(msg);
}


源代码清单

//+------------------------------------------------------------------+
//|                                 Market Structure Flip Detector EA|
//|                                   Copyright 2025, MetaQuotes Ltd.|
//|                           https://www.mql5.com/en/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

//--- user inputs
input int    InpAtrPeriod       = 14;      // ATR lookback
input double InpAtrMultiplier   = 1.0;     // ATR to swing depth factor (lower = looser)
input double InpAtrLoosenFactor = 0.5;     // Loosen factor for ATR confirmation (0-1)
input bool   InpAutoShift       = true;    // Auto-enable chart right shift
input int    InpShiftBars       = 5;       // Bars for right margin
input bool   InpEnableSound     = true;
input string InpSoundFile       = "alert.wav";
input bool   InpEnablePush      = false;

//--- global vars
string   panelName = "FlipPanel";
int      atrHandle;
int      structState = 0;
double   prevHigh=0, lastHigh=0;
datetime prevHighTime=0, lastHighTime=0;
double   prevLow=0, lastLow=0;
datetime prevLowTime=0, lastLowTime=0;
int      countBull=0, countBear=0;
double   sumBullInterval=0, sumBearInterval=0;

//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpAutoShift)
      ChartSetInteger(0, CHART_SHIFT, InpShiftBars);

   atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
   if(atrHandle == INVALID_HANDLE)
      return(INIT_FAILED);

   ObjectCreate(0, panelName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, panelName, OBJPROP_CORNER,    CORNER_LEFT_UPPER);
   ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_FONTSIZE,  10);
   ObjectSetInteger(0, panelName, OBJPROP_COLOR,     clrYellow);
   ObjectSetInteger(0, panelName, OBJPROP_BACK,      false);
   ObjectSetInteger(0, panelName, OBJPROP_ZORDER,    1);

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, -1, OBJ_ARROW);
   ObjectsDeleteAll(0, -1, OBJ_TEXT);
   ObjectDelete(0, panelName);
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
  }

//+------------------------------------------------------------------+
void OnTick()
  {
   static datetime lastBar=0;
   datetime thisBar = iTime(_Symbol,_Period,1);
   if(thisBar==lastBar)
      return;
   lastBar=thisBar;

   double atrBuf[];
   if(CopyBuffer(atrHandle,0,1,1,atrBuf)<=0)
      return;
   double atr = atrBuf[0];

// loosen ATR confirmation by InpAtrLoosenFactor (0-1)
   double rawDepth = atr/SymbolInfoDouble(_Symbol,SYMBOL_POINT)*InpAtrMultiplier;
   int depth = MathMax(1, (int)(rawDepth * InpAtrLoosenFactor));

   bool newHigh=false,newLow=false;
   double h=iHigh(_Symbol,_Period,1), l=iLow(_Symbol,_Period,1);

   if(IsSwingHigh(1,depth))
     {
      prevHigh = lastHigh;
      prevHighTime = lastHighTime;
      lastHigh = h;
      lastHighTime = thisBar;
      newHigh = true;
     }
   if(IsSwingLow(1,depth))
     {
      prevLow = lastLow;
      prevLowTime = lastLowTime;
      lastLow = l;
      lastLowTime = thisBar;
      newLow = true;
     }

   double off = SymbolInfoDouble(_Symbol,SYMBOL_POINT)*10;

// Bearish Flip: Lower High after a Higher High
   if(newHigh && structState==1 && prevHigh>0 && lastHigh<prevHigh)
     {
      // signal arrow and label at current LH
      PlotArrow("Bear_"+IntegerToString((int)lastHighTime), lastHighTime, lastHigh, 234, clrRed);
      PlotLabel("LH_"+IntegerToString((int)lastHighTime), lastHighTime, lastHigh+off, "LH", clrRed);
      // label the previous LH used for comparison
      PlotLabel("PrevLH_"+IntegerToString((int)prevHighTime), prevHighTime, prevHigh+off, "LH_prev", clrRed);
      Notify("Bearish Flip (LH) at "+TimeToString(lastHighTime,TIME_DATE|TIME_MINUTES));
      if(countBear>0)
         sumBearInterval += (lastHighTime-prevLowTime)/60.0;
      countBear++;
     }

// Bullish Flip: Higher Low after a Lower Low
   if(newLow && structState==2 && prevLow>0 && lastLow>prevLow)
     {
      // signal arrow and label at current HL
      PlotArrow("Bull_"+IntegerToString((int)lastLowTime), lastLowTime, lastLow, 233, clrLime);
      PlotLabel("HL_"+IntegerToString((int)lastLowTime), lastLowTime, lastLow-off, "HL", clrLime);
      // label the previous HL used for comparison
      PlotLabel("PrevHL_"+IntegerToString((int)prevLowTime), prevLowTime, prevLow-off, "HL_prev", clrLime);
      Notify("Bullish Flip (HL) at "+TimeToString(lastLowTime,TIME_DATE|TIME_MINUTES));
      if(countBull>0)
         sumBullInterval += (lastLowTime-prevHighTime)/60.0;
      countBull++;
     }

// update structure state
   if(newHigh && prevHigh>0 && lastHigh>prevHigh)
      structState = 1;
   if(newLow  && prevLow>0  && lastLow <prevLow)
      structState = 2;

// update panel stats
   string txt = "Depth: "+IntegerToString(depth)+"\n";
   txt += "Bull Flips: "+IntegerToString(countBull)+"\n";
   txt += "Bear Flips: "+IntegerToString(countBear);
   if(countBull>1)
      txt += "\nAvg HL Int: "+DoubleToString(sumBullInterval/(countBull-1),1)+"m";
   if(countBear>1)
      txt += "\nAvg LH Int: "+DoubleToString(sumBearInterval/(countBear-1),1)+"m";
   ObjectSetString(0, panelName, OBJPROP_TEXT, txt);
  }

//+------------------------------------------------------------------+
bool IsSwingHigh(int shift,int depth)
  {
   double p = iHigh(_Symbol,_Period,shift);
   for(int i=shift-depth; i<=shift+depth; i++)
      if(i>=0 && iHigh(_Symbol,_Period,i) > p)
         return false;
   return true;
  }

//+------------------------------------------------------------------+
bool IsSwingLow(int shift,int depth)
  {
   double p = iLow(_Symbol,_Period,shift);
   for(int i=shift-depth; i<=shift+depth; i++)
      if(i>=0 && iLow(_Symbol,_Period,i) < p)
         return false;
   return true;
  }

//+------------------------------------------------------------------+
void PlotArrow(string nm,datetime t,double price,int code,color c)
  {
   if(ObjectFind(0,nm) < 0)
     {
      ObjectCreate(0, nm, OBJ_ARROW, 0, t, price);
      ObjectSetInteger(0, nm, OBJPROP_ARROWCODE, code);
      ObjectSetInteger(0, nm, OBJPROP_COLOR, c);
      ObjectSetInteger(0, nm, OBJPROP_WIDTH, 2);
     }
  }

//+------------------------------------------------------------------+
void PlotLabel(string nm,datetime t,double price,string txt,color c)
  {
   if(ObjectFind(0,nm) < 0)
     {
      ObjectCreate(0, nm, OBJ_TEXT, 0, t, price);
      ObjectSetString(0, nm, OBJPROP_TEXT, txt);
      ObjectSetInteger(0, nm, OBJPROP_COLOR, c);
      ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, 10);
     }
  }

//+------------------------------------------------------------------+
void Notify(string msg)
  {
   Alert(msg);
   if(InpEnableSound)
      PlaySound(InpSoundFile);
   if(InpEnablePush)
      SendNotification(msg);
  }
//+------------------------------------------------------------------+


性能表现

以下将分别阐述我们在实盘交易与回测环境下的测试结果。

实盘交易

实盘交易结果

在上方图表中,EA首先识别出一个标记为“LH_prev”的波段高点,该点由连续两个更高的高点构成,确立了上升结构(up-structure)。数根K线后,EA检测到另一个波段高点,但其峰值未突破前高——这种上升趋势中的更低高点触发EA在该K线上绘制红色箭头及“LH”标签。这一看跌反转信号标志着看涨契机衰竭,预示可能启动下行走势。

实盘交易动态演示(GIF)

以下GIF展示了该EA在欧元兑美元1分钟图上的运行效果。每当一根1分钟K线收盘时,EA会持续追踪连续低点,直至发现一个突破前低的波段低点。当该更高低点出现时,EA会放置绿色“HL”箭头标记看涨反转。与此同时,顶部面板数据实时更新——当前显示12次看涨反转、1次看跌反转,以及HL间隔平均值为108.0分钟——以反映最新统计结果。这段视频清晰展现了市场结构从下行趋势向潜在上行走势的转变过程。

实盘交易结果

回测

以下为多周期步长指数分析结果的汇总表格。“有效信号”指市场在该信号出现后,沿指示方向持续运行一段时间的案例。

5分钟时间框架

信号类型 总信号 积极信号 胜率
卖出 56 39 70%
买入 53 44 83%

15分钟时间框架

信号类型 总信号 积极信号 胜率
卖出 7 5 71%
买入 14 9 64%

分析摘要显示,市场结构反转检测工具持续输出盈利信号,尤其在较短时间框架时表现显著。做空策略的成功率达70%以上,充分印证了该工具的实战效能。这一突破标志着价格行为分析自动化领域的重大进展,为我们构建全面系统化、低延迟的量化交易工具包。



结论

我们已在实盘交易与回测环境中对该工具进行了开发与验证,分析结果显示其性能表现强劲稳定,尤其在较短时间框架的剥头皮(Scalping)策略中收益显著。然而,在实盘交易前,请务必谨慎操作,需通过附加确认方法对信号进行二次验证。此外,跨多货币对的测试对于识别该工具的最优表现场景至关重要。您可以在测试过程中灵活调整输入参数以进一步优化性能。

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

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

附加的文件 |
Flip_Detector.mq5 (14.5 KB)
最近评论 | 前往讨论 (1)
Alexander P.
Alexander P. | 7 5月 2025 在 11:08
bool IsSwingHigh( int shift, int depth)
  {
   double p = iHigh ( _Symbol , _Period ,shift);
   for ( int i= shift-depth; i<=shift+depth; i++)
       if (i>= 0 && iHigh ( _Symbol , _Period ,i) > p)
         return false ;
   return true ;
  }

你好,我不明白你为什么要写int i=shift-depth,难道不能直接用int i=0 吗?

能解释一下吗?谢谢。

您应当知道的 MQL5 向导技术(第 55 部分):配备优先经验回放的 SAC 您应当知道的 MQL5 向导技术(第 55 部分):配备优先经验回放的 SAC
强化学习中的回放缓冲区对于像 DQN 或 SAC 这样的无政策算法尤为重要。这样就会聚光在该记忆缓冲区的抽样过程。举例,SAC 默认选项从该缓冲区随机选择,而优先经验回放缓冲区则基于 TD 分数从缓冲区中抽样对其优调。我们回顾强化学习的重要性,并一如既往,在由向导汇编的智能系统中验证这一假设(而‘非交叉验证)。
MQL5 简介(第 15 部分):构建自定义指标的初学者指南(四) MQL5 简介(第 15 部分):构建自定义指标的初学者指南(四)
在本文中,您将学习如何在 MQL5 中构建价格行为指标,重点关注低点 (L)、高点 (H)、更高的低点 (HL)、更高的高点 (HH)、更低的低点 (LL) 和更低的高点 (LH) 等关键点,以分析趋势。你还将学习如何识别溢价和折价区域,标记 50% 回撤位,以及如何使用风险回报比来计算利润目标。文章还介绍了如何根据趋势结构确定入场点、止损 (SL) 和止盈 (TP) 水平。
通过原始代码优化和调整来改进回测结果 通过原始代码优化和调整来改进回测结果
通过优化逻辑、细化计算和减少执行时间来提高回测精度,从而增强 MQL5 代码。微调参数,优化循环,消除低效,以获得更好的性能。
在交易图表上通过资源驱动的双三次插值图像缩放技术创建动态 MQL5 图形界面 在交易图表上通过资源驱动的双三次插值图像缩放技术创建动态 MQL5 图形界面
本文探讨了动态 MQL5 图形界面,利用双三次插值技术在交易图表上实现高质量的图像缩放。我们详细介绍了灵活的定位选项,支持通过自定义偏移量实现动态居中或位置定位。