价格行为分析工具包开发(第二十七部分):利用移动平均线进行流动性扫单
内容
概述
大型机构交易者对市场的影响并非偶然。他们的策略往往包括推动价格突破广为人知的支撑位或阻力位,故意触发止损单和挂单等较小订单。这种短暂的价格波动被称为流动性扫单,它让机构交易者能够以更优的价格买入或卖出大额头寸,且不至于立刻逆势而行。
对于许多零售交易者而言,这些价格走势感觉像是陷阱。价格可能会跌破一个熟悉的低点,触发止损单,随后又迅速反弹。相反,价格也可能突破阻力位,触发止损单,然后急剧反转。一旦主要交易者吸收了这股涌入的流动性,市场往往会以更强的力度恢复先前的趋势。尽早识别出这些流动性扫单,有助于避免交易者过早被止损出局,从而顺势跟随机构资金的流向,不恐慌市场的临时震荡。
在本文中,我们将开发一款MQL5智能交易系统(EA),旨在实时识别流动性扫单。该EA会首先分析那些突破前一波段高低点、随后又回落至该区间内的K线,这是潜在流动性吸收的迹象。它还融入了可选的过滤器,如K线颜色变化或移动平均线(MA)确认,以确保信号与您的市场倾向相符。当检测到有效形态时,EA会在图表上用箭头或标签直观地标记出来,并发出警报。
阅读完本文,您将拥有一款全面、清晰且易于理解的EA,能够立即突出显示流动性扫单。您将了解该EA如何检测这些特定的市场形态,如何运用过滤器减少虚假信号,以及如何帮助您与聪明钱(机构资金)的行为保持一致。借助这一工具,您就能在止损被触发之前预见到机构交易者的动向,从而在任何交易环境中都能获得战略性的优势。
策略解析
流动性扫单有两种形成方式,一种是看涨扫单(即跌破支撑位的虚假突破),另一种是看跌扫单(即突破阻力位的虚假突破)。EA的核心检测逻辑在DetectLiquiditySweep函数中。以下将逐步解析该函数如何区分这两种方式,随后给出执行布尔(boolean)检查的精确MQL5代码段。
每当一根新K线收盘时,EA会调用
DetectLiquiditySweep(1);
传递参数shift=1表示:
- 索引1(shift)指的是刚刚收盘的K线(即我们想要测试的“当前”K线)。
- 索引2(shift + 1)指的是紧邻其前的K线(即“前一根”K线)。
- 在DetectLiquiditySweep函数内部,通过以下几行代码获取这两根K线的开盘价、最高价、最低价和收盘价:
double o = iOpen(Symbol(), Period(), shift); double c = iClose(Symbol(), Period(), shift); double h = iHigh(Symbol(), Period(), shift); double l = iLow(Symbol(), Period(), shift); double o1 = iOpen(Symbol(), Period(), shift + 1); double c1 = iClose(Symbol(), Period(), shift + 1); double h1 = iHigh(Symbol(), Period(), shift + 1); double l1 = iLow(Symbol(), Period(), shift + 1);
- (o,h,l,c)分别为新K线的开盘价、最高价、最低价和收盘价。
- (o1,h1,l1,c1)分别为紧邻前一根K线的对应价格。
EA允许两种略有不同的定义方式,由SignalStrict输入参数控制:
1. 宽松模式(默认设置)
看涨扫单:
- 新K线必须收盘高于开盘:c > o。
- 其最低价必须跌破前一根K线的最低价:l < l1。
- 其收盘价也必须高于前一根K线的开盘价:c > o1。(从而排除了十字星或极小K线被误判为有效信号的情况)
- 前一根K线不是十字星:c1 ≠ o1。
- 新K线必须收盘低于开盘:c < o。
- 其最高价必须突破前一根K线的最高价:h > h1。
- 其收盘价也必须低于前一根K线的开盘价:c < o1。
- 前一根K线不是十字星:c1 ≠ o1。
2. 严格模式
看涨扫单:
与宽松模式完全相同,但第2步的条件更为严格:不仅要求“最低价低于前一根K线的最低价 ”,还要求收盘价高于前一根K线最高价(c > h1)。换句话说,新K线必须先跌破前低,然后在收盘前重新站上前高。
看跌扫单:
同理,新K线必须先突破前高(h > h1),然后收盘价低于前一根K线最低价(c < l1) ,而不仅仅是刺破前高。
这些布尔测试逻辑体现在以下代码块中:
//--- Liquidity sweep logic (LessStrict vs Strict) bool bullSweep, bearSweep; if (SignalStrict == LessStrict) { bullSweep = (c > o && // 1) Bullish candle l < l1 && // 2) Low dipped below previous low c > o1 && // 3) Close above previous open c1 != o1); // 4) Previous bar was not a doji bearSweep = (c < o && // 1) Bearish candle h > h1 && // 2) High spiked above previous high c < o1 && // 3) Close below previous open c1 != o1); // 4) Previous bar was not a doji } else // Strict { bullSweep = (c > o && // 1) Bullish candle l < l1 && // 2) Low dipped below previous low c > h1 && // 3) Close above previous high (stricter) c1 != o1); // 4) Previous bar was not a doji bearSweep = (c < o && // 1) Bearish candle h > h1 && // 2) High spiked above previous high c < l1 && // 3) Close below previous low (stricter) c1 != o1); // 4) Previous bar was not a doji }
- 在宽松模式下,第3步仅要求收盘价高于前一根K线开盘价(c > o1)(看涨情况)或收盘价低于前一根K线开盘价(c < o1)(看跌情况)。
- 在严格模式下,第3步的条件变更为收盘价高于前一根K线最高价(c > h1)(看涨情况)或收盘价低于前一根K线最低价(c < l1)(看跌情况)。
如果用户设置ColorChangeOnly为true,则EA要求新K线的颜色必须与前一根K线相反。具体说明:
bool bullCC = (c > o && c1 < o1); // New bar bullish, old bar bearish bool bearCC = (c < o && c1 > o1); // New bar bearish, old bar bullish if (ColorChangeOnly) { bullSweep &= bullCC; // Only a bullish sweep if also a bull‐after‐bear color flip bearSweep &= bearCC; // Only a bearish sweep if also a bear‐after‐bull color flip }
如果ColorChangeOnly为false,则以下两行代码不产生任何影响,且bullSweep和bearSweep的结果将仅由先前的价格测试条件决定。
一旦bullSweep或bearSweep为true(在通过价格测试及可选的颜色测试后),EA可进一步基于MA进行过滤。这一过滤功能通过UseMAFilter和PriceAboveMA两个参数控制。具体如下:
- 在索引shift(即刚刚收盘的K线)处计算一个MA值,该数值可通过内置的iMA()函数(用于计算简单移动平均线SMA、指数移动平均线EMA、线性加权移动平均线LWMA或平滑移动平均线RMA)或自定义函数(如计算成交量加权移动平均线CalcVWMA或计算赫尔移动平均线CalcHMA)来获取。
bool cond = PriceAboveMA ? (c > maValue) : (c < maValue);
- 如果PriceAboveMA为true,则EA仅在c > maValue时保留bullSweep,并强制将bearSweep直接过滤掉。
- 如果PriceAboveMA为false,则EA仅在c < maValue时保留bearSweep,并强制将bullSweep直接过滤掉。
if (UseMAFilter) { double maValue = 0.0; if (MAType == VWMA) maValue = CalcVWMA(shift); else if (MAType == HMA) maValue = CalcHMA(shift); else { double buf[]; if (CopyBuffer(MAHandle, 0, shift, 1, buf) != 1) return; // not enough MA data yet maValue = buf[0]; } // Keep only bullish sweeps if price > MA, or only bearish if price < MA bool cond = PriceAboveMA ? (c > maValue) : (c < maValue); bullSweep &= cond; bearSweep &= !cond; }
总结
看涨扫单:
- K线收盘价高于开盘价,且最低价下探至前一根K线最低价下方,同时收盘价高于前一根K线开盘价(宽松模式)或前一根K线最高价(严格模式)。
- (可选条件)该K线必须为“前一根看跌、当前看涨”的颜色反转形态。
- (可选条件)收盘价必须位于MA的上方。
- 如果所有条件均满足,EA将在该K线最低价下方数点处绘制一个绿色向上箭头(OBJ_ARROW_UP)。

图例1. 看涨扫单
看跌扫单:
- K线收盘价低于开盘价,且最高价上穿至前一根K线最高价上方,同时收盘价低于前一根K线开盘价(宽松模式)或前一根K线最低价(严格模式)。
- (可选条件)该K线必须为“前一根看涨、当前看跌”的颜色反转形态。
- (可选条件)收盘价必须位于MA的下方。
- 如果所有条件均满足,EA将在该K线最高价上方数点处绘制一个红色向下箭头(OBJ_ARROW_DOWN)。

图例2. 看跌扫单
代码分解
“利用移动平均线进行流动性扫单”的EA旨在识别并在MetaTrader 5平台的任意图表上以可视化方式标记假突破行情 —— 这种行情通常被称为流动性扫单。该EA的核心逻辑是监测那些向下(或向上)突破前一根K线波段低点(或高点)的K线形态,随后以暗示订单被套牢的方式收盘。通过将这种检测逻辑与可选的MA过滤器相结合,交易者能够优化交易信号,使其与更广泛的趋势背景保持一致。以下内容将逐步解析该EA的每个核心组件,既说明各部分的功能,也阐释其在整体系统中的重要性。
首先,EA文件在开头声明其基础数据 —— 包括标题、版权信息、作者链接和版本号 —— 并紧跟#property strict指令。这些代码声明并非流于形式,而是起着两方面的作用。其一,当用户浏览多个EA时,这些基础数据能明确标识脚本作者及其更多文章来源。其二,通过指定#property strict,编译器将执行更严格的语法和类型检查,在代码运行前捕获常见错误(如未声明变量或类型不匹配)。这种清晰的基础数据声明与严格的编译规范体现了专业标准,确保代码的可维护性和运行的可靠性。
//+------------------------------------------------------------------+ //| Liquidity Sweep with MA filter| //| 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
接下来,该EA引入Trade.mqh库文件。尽管当前版本不会自动执行真实交易,但引入<Trade\Trade.mqh>具有前瞻性意义:它为未来可能添加的开仓、平仓或仓位调整功能预留了强大的CTrade类接口。在教学场景中,同时也向读者传递了一个信号 —— 只需在检测逻辑中调用trade.Buy(...)或trade.Sell(...),该EA即可轻松升级为全自动策略执行器。通过前置展示这一设计,作者引导读者同时考虑行情分析与交易执行两个维度。
#include <Trade\Trade.mqh> 在引入库文件后,EA定义了一组输入参数,这些参数在将EA附加到图表时,显示在MetaTrader’s的"输入参数"对话框中。这些参数按逻辑分为三组。第一组参数用于MA设置 —— 允许用户启用/禁用MA(UseMAFilter)、选择是否在图表上显示该MA(ShowMA)、设置MA周期(MALength),以及从六种MA类型(SMA、EMA、LWMA、VWMA、RMA或HMA)中选择。布尔参数PriceAboveMA则规定:EA应要求价格位于MA上方(看涨扫单时)还是下方(看跌扫单时)。
实际应用中,该过滤器可确保交易者仅顺应主要趋势方向捕捉流动性扫单信号,从而在逆势行情中减少错误信号的产生。
//--- Inputs: Moving Average Filter input bool UseMAFilter = false; // Enable Moving Average Filter input bool ShowMA = false; // Show MA on chart input int MALength = 20; // MA period (must be >=1) enum MA_Type {SMA=0, EMA, LWMA, VWMA, RMA, HMA}; input MA_Type MAType = SMA; // Moving Average type input bool PriceAboveMA = true; // Filter: price above MA?
第二组输入参数用于控制EA对“流动性扫单”的判定严格程度。枚举参数SignalStrict提供两种模式:“宽松模式”仅要求新K线的低点(看涨信号时)低于前一根K线低点(看跌信号时则相反),而“严格模式”则额外要求突破前一根K线的高点(或低点)。通过展示这一标识,读者可学习如何调整信号敏感度:部分交易者偏好宽松判定以捕捉更多信号,另一部分则倾向保守策略,等待明确的价格突破。
补充的布尔参数ColorChangeOnly进一步收紧判定条件,要求新K线的颜色(看涨/看跌)必须与前一根K线颜色相反。在众多交易理念中,K线颜色反转被视为动量转变的信号,因此该选项专为希望标记与明显K线反转同步的反转行情的交易者设计。
//--- Inputs: Sweep Definition Strictness enum Strictness {LessStrict=0, Strict}; input Strictness SignalStrict = LessStrict; // Signal strictness input bool ColorChangeOnly = false; // Only on color-change candles
第三组参数用于图表上的可视化显示设置。枚举参数LabelType提供三种选择:不显示标签、显示简短双字母标签(看涨扫单用"BS"表示,看跌扫单用"SS"表示),或显示完整文本标签("Bull Sweep"或"Bear Sweep")。与此同时,PlotArrow参数允许交易者选择用箭头替代(或补充)文本标记。为避免图表杂乱并确保标签不与K线重叠,ArrowOffsetPoints参数可将每个箭头或文本对象移动至指定点数位置 —— 看跌信号时显示在K线上方,看涨信号时显示在K线下方。
最后,两个颜色参数 —— BullishColor和BearishColor —— 赋予用户完全控制标记颜色的权限,可使其与图表主题保持一致。通过将显示选项与检测逻辑分离,该EA保持了高度的灵活性:交易者既可将其配置为"静默提醒"模式(不显示任何图表对象),也可根据个人偏好或系统性能需求切换至完整的"图表标注"模式。
//--- Inputs: Label/Arrow & Color Customization enum LabelType {None=0, Short, Full}; input LabelType LblType = Full; // Label type (None/Short/Full) input bool PlotArrow = true; // Draw arrow on signal input int ArrowOffsetPoints = 10; // Offset (in points) above/below candles input color BullishColor = clrLime; // Color for bullish signals input color BearishColor = clrRed; // Color for bearish signals
完成输入参数部分后,该EA立即声明了两个全局变量。第一个全局变量为日期时间型lastBarTime,用于存储最近处理过的K线的时间戳。该变量至关重要:在每个行情数据更新(tick)时,EA会将当前K线的起始时间(通过(iTime(Symbol(),Period(),0)获取)与lastBarTime进行比较。如果两者不同,则表明已形成新K线,此时EA才会执行其信号检测逻辑。一旦缺少这一校验步骤,检测逻辑将在每根正在形成的K线的每个tick上运行,导致同一根K线上出现多个信号,进而引发通常所说的“信号重绘”问题。
第二个全局变量为整型MAHandle,用于存储每当创建内置MA时,由iMA(...)函数返回的指标句柄。通过将其初始化为INVALID_HANDLE,代码可在后续读取指标数据前,先检验该句柄是否有效。
//--- Globals datetime lastBarTime = 0; // Timestamp of the last processed bar int MAHandle = INVALID_HANDLE; // Handle for built‐in MA indicator
OnInit()函数用于EA的初始化设置。当EA首次加载到图表,或用户修改任何输入参数时,交易平台会自动调用OnInit()函数。该函数的首要任务是验证MALength的有效性。由于MQL5在编译后会将所有输入参数视为常量,因此无法直接为其重新赋值安全值。为此,代码通过if(MALength < 1)条件判断,如果用户设置了无效周期(小于1),则打印错误信息并返回INIT_FAILED状态码。这种防御性编程机制可以确保EA永远不会尝试基于0或负数的K线数量计算EA,从而避免运行时错误或产生无意义的结果。接下来,将lastBarTime初始化为当前K线的起始时间,为后续的tick处理逻辑提供初始基准,防止EA在加载当根K线期间立即触发信号检测。
int OnInit() { // Validate MALength (cannot assign to an input directly) if(MALength < 1) { Print("ERROR: MALength must be at least 1. Current value = ", MALength); return(INIT_FAILED); } // Initialize timing so we only run when a new bar closes lastBarTime = iTime(Symbol(), Period(), 0); // … (rest of OnInit follows) … return(INIT_SUCCEEDED); }
在OnInit()函数内部继续执行时,EA会判断是否需要创建内置移动平均线指标句柄。如果用户选择了四种标准移动平均线类型之一(SMA、EMA、LWMA或RMA),并且启用了UseMAFilter或ShowMA中的任意一项,代码将调用iMA(Symbol(), Period(), MALength, 0, (ENUM_MA_METHOD)MAType, PRICE_CLOSE)来创建指标句柄。如果调用失败,EA会输出诊断错误信息并终止初始化流程。假设创建成功,代码会进一步检查if(ShowMA == true),如果条件成立则调用ChartIndicatorAdd(0, 0, MAHandle)将MA绘制到图表上。初始化流程结束时,EA会输出消息 —— “流动性扫单EA初始化成功”,以此确认所有前提条件(有效周期、MA句柄)均已就绪,EA可正常运行。
// Create MA handle if using a built-in MA (SMA, EMA, LWMA, RMA) if((MAType != VWMA && MAType != HMA) && (UseMAFilter || ShowMA)) { ENUM_MA_METHOD method = (ENUM_MA_METHOD)MAType; MAHandle = iMA(Symbol(), Period(), MALength, 0, method, PRICE_CLOSE); if(MAHandle == INVALID_HANDLE) { Print("Failed to create MA handle (type=", EnumToString(MAType), ", length=", MALength, ")"); return(INIT_FAILED); } if(ShowMA) ChartIndicatorAdd(0, 0, MAHandle); }
当用户移除EA或关闭MetaTrader时,将执行OnDeinit()函数。该函数的唯一职责是进行资源清理:如果已设置MAHandle(即不等于INVALID_HANDLE),则调用 IndicatorRelease(MAHandle)释放内存并清除所有残留的引用。通过这种方式,EA可避免资源泄漏 —— 这是在运行过程中创建指标句柄时必须遵循的重要的最优实践。尽管当前版本的MetaTrader会自动管理部分资源,但释放句柄仍能防止长时间运行会话,以及频繁重新加载EA进行参数优化时出现的图表臃肿问题。
void OnDeinit(const int reason) { if(MAHandle != INVALID_HANDLE) IndicatorRelease(MAHandle); }
OnTick()函数作为EA的"心跳"机制 —— 每当MetaTrader接收到新tick时都会调用它。然而,由于我们只需在每根K线完成时检测一次流动性扫单,OnTick()首先通过iTime(Symbol(),Period(),0)获取最新K线的时间戳,并与lastBarTime进行比较。如果两者时间相同,说明EA仍在处理同一根K线,此时不执行任何操作。
只有当新K线出现时(即iTime(...) != lastBarTime),代码才会调用DetectLiquiditySweep(1),并传入偏移量1以分析刚闭合的K线与其前一根K线的关系。调用完成后,立即更新lastBarTime值,确保EA必须等待下一根K线闭合后才会再次检测。这种严谨的机制保证了每根K线最多产生一个信号,有效消除噪声干扰并防止单根K线上出现多次警报。
void OnTick() { // Retrieve the current bar’s start time datetime current = iTime(Symbol(), Period(), 0); // If the bar start time changed, call the detection routine once if(current != lastBarTime) { DetectLiquiditySweep(1); lastBarTime = current; } }
核心逻辑函数DetectLiquiditySweep(int shift)是该EA区别于简单形态标记工具的关键所在。首先,它会确保存在足够的历史数据来计算自定义的MA。通过计算requiredBars = shift + MALength(所需K线数=偏移量+移动平均周期),并验证if(Bars(Symbol(),Period()) <= requiredBars) return,代码可防止数组越界访问。假如图表上仅有15根K线而用户设置了MALength=20,则EA将不会尝试计算MA或进行形态检测,因为历史数据不足。这种防护性条件判断体现了严谨的错误预防机制,对于构建健壮的EA系统至关重要。
void DetectLiquiditySweep(int shift) { // Ensure there are at least (shift + MALength) bars of history int requiredBars = shift + MALength; if(Bars(Symbol(), Period()) <= requiredBars) { // Not enough bars to compute custom MA or compare prices return; } // … (next steps in the function) … }
历史数据校验通过后,DetectLiquiditySweep会读取8个关键价格数据 —— 当前已闭合K线(索引=shift)和前一根K线(索引=shift+1)的开盘价、最高价、最低价和收盘价。这些数据被分别存入8个独立的双精度浮点型变量中:当前K线用o、c、h、l表示,前一根K线则用o1、c1、h1、l1表示。获取这些数据后,EA即可评估是否发生了流动性扫单。
值得注意的是,代码还计算两个布尔标识位 —— bullCC (看涨颜色反转)和bearCC(看跌颜色反转),只需检查当前K线的收盘价是否高于其开盘价,而前一根K线的收盘价低于其开盘价,反之亦然。这两个标识位在启用ColorChangeOnly时发挥作用,确保EA仅标记与K线颜色反转同步出现的流动性扫单信号。
//--- Bar data for the current completed candle (index = shift) double o = iOpen(Symbol(), Period(), shift); double c = iClose(Symbol(), Period(), shift); double h = iHigh(Symbol(), Period(), shift); double l = iLow(Symbol(), Period(), shift); //--- Bar data for the prior candle (index = shift + 1) double o1 = iOpen(Symbol(), Period(), shift + 1); double c1 = iClose(Symbol(), Period(), shift + 1); double h1 = iHigh(Symbol(), Period(), shift + 1); double l1 = iLow(Symbol(), Period(), shift + 1);
接下来,实现“宽松模式”与“严格模式”两种流动性扫单的定义逻辑。如果用户选择了宽松模式,如果当前K线收盘价高于开盘价(c > o),最低价下破前一根K线最低价(l < l1),收盘价高于前一根K线开盘价(c > o1),且前一根K线不是十字星(c1 != o1)时,触发看涨扫单信号。看跌扫单的判定条件呈对称关系:当前K线收盘价低于开盘价(c < o),最高价上破前一根K线最高价(h > h1),收盘价低于前一根K线开盘价(c < o1),且前一根K线不是十字星(c1 ≠ o1)。
如果用户选择严格模式,EA会强化判定条件:看涨扫单要求当前收盘价必须突破前一根K线的最高价(c > h1),看跌扫单要求当前收盘价必须跌破前一根K线的最低价(c < l1)。这种严格模式通过要求更明确的支撑/阻力位突破,减少了浅幅回调时的误报信号,迫使价格必须形成更具决定性的破位走势。通过提供两种模式选项,该EA向读者展示了如何通过微调逻辑条件显著改变信号的频率与质量。
// Compute color-change flags bool bullCC = (c > o && c1 < o1); bool bearCC = (c < o && c1 > o1); // Liquidity sweep flags (LessStrict or Strict) bool bullSweep, bearSweep; if(SignalStrict == LessStrict) { bullSweep = (c > o && l < l1 && c > o1 && c1 != o1); bearSweep = (c < o && h > h1 && c < o1 && c1 != o1); } else // Strict { bullSweep = (c > o && l < l1 && c > h1 && c1 != o1); bearSweep = (c < o && h > h1 && c < l1 && c1 != o1); }
在完成原始流动性扫单条件检测后,EA会选择性地应用颜色反转过滤机制。当设置ColorChangeOnly 参数为true时,代码执行bullSweep&=bullCC和bearSweep&=bearCC按位操作。这一操作的实际效果是:任何未伴随K线颜色反转的扫单信号都将被立即过滤掉。许多交易者将颜色反转(如绿K后接红K)视为反转趋势的确认信号,因此通过将扫单信号与该条件进行绑定,EA有效降低了标记缺乏明显价格排斥反应的虚假扫单信号的概率。这种通过简单按位与操作实现的过滤机制,巧妙地将价格结构(扫单条件)与K线图心理学(颜色反转)两个互补信号整合为单一过滤层。
// If only color-change sweeps are desired, AND‐combine with the raw sweep flags if(ColorChangeOnly) { bullSweep &= bullCC; // must also be a bullish color reversal bearSweep &= bearCC; // must also be a bearish color reversal }
在完成原始扫单条件检测和可选颜色过滤后,如果UseMAFilter为true,则EA将进一步应用MA过滤机制。代码首先声明双精度型变量maValue并初始化为0.0,随后通过三种方式之一计算MA值:如果用户选择"VWMA",调用自定义辅助函数CalcVWMA(shift)。如果用户选择"HMA",调用CalcHMA(shift)。否则,默认使用内置的MA类型(SMA/EMA/LWMA/RMA),通过CopyBuffer(MAHandle, 0, shift, 1, buf)获取1个MA值。如果CopyBuffer未能返回恰好1个结果,则EA将直接跳过当前K线的所有后续逻辑处理。
获取maValue后,通过布尔条件判断PriceAboveMA的值:如果为true,执行(c > maValue)检验价格是否在MA上方;反之如果为false,则执行(c < maValue)检验价格是否在MA下方。在实际应用中,当PriceAboveMA为true时,EA仅保留收盘价高于MA的看涨扫单信号(通过设置bearSweep=false过滤看跌信号),当PriceAboveMA为false时,则仅保留收盘价低于MA的看跌扫单信号。这种过滤机制确保流动性扫单信号与趋势方向保持一致:看涨扫单信号仅在价格位于MA上方时有效,而看跌扫单信号仅在价格位于MA下方时成立。
if(UseMAFilter) { double maValue = 0.0; // Compute MA value at the same 'shift' if(MAType == VWMA) maValue = CalcVWMA(shift); else if(MAType == HMA) maValue = CalcHMA(shift); else { // Built‐in MA handle (SMA, EMA, LWMA, RMA) double buf[]; if(CopyBuffer(MAHandle, 0, shift, 1, buf) != 1) return; // no valid MA data available maValue = buf[0]; } // Only allow bullish sweeps above MA or bearish sweeps below MA bool cond = PriceAboveMA ? (c > maValue) : (c < maValue); bullSweep &= cond; bearSweep &= !cond; }
在所有过滤条件应用完毕后,DetectLiquiditySweep通过两个独立的if语句分别检查bullSweep和bearSweep标识位。当bullSweep 为true时,调用DrawSignal(shift, true)在图表上绘制看涨信号标记,同时在“专家”日志中记录类似"Bullish sweep detected at [time], price=[close]"的信息。如果bearSweep为true,则执行DrawSignal(shift, false)并记录对应的看跌信号日志。
无论触发哪种信号,只要任一标识位为true,代码最终都会调用:("Liquidity Sweep detected on ", Symbol(), " ", EnumToString(Period()));以弹出屏幕警报通知交易者。这种将"图表标记绘制"与"警报发送"分离的设计颇具巧思,使得交易者可以自由选择仅接收声音/弹窗通知,或同时在图表上保留可视化信号标记。
// If a bullish sweep remains true after all filters, draw and log it if(bullSweep) { DrawSignal(shift, true); PrintFormat("Bullish sweep detected at %s, price=%.5f", TimeToString(iTime(Symbol(),Period(),shift)), c); } // If a bearish sweep remains true after all filters, draw and log it if(bearSweep) { DrawSignal(shift, false); PrintFormat("Bearish sweep detected at %s, price=%.5f", TimeToString(iTime(Symbol(),Period(),shift)), c); } // In either case, fire a pop‐up alert if(bullSweep || bearSweep) { Alert("Liquidity Sweep detected on ", Symbol(), " ", EnumToString(Period())); }
DrawSignal(int shift, bool bullish)函数专门负责放置图表对象。它首先读取K线的时间戳 t = iTime(Symbol(),Period(),shift),之后通过取最低价减去ArrowOffsetPoints * _Point(看涨箭头)或最高价加上相同偏移量(看跌箭头)来计算显示价格。使用_Point能确保偏移量以该品种的最小tick为单位计算,无论交易品种是欧元兑美元(EURUSD,0.0001增量)还是美元兑日元(USDJPY,0.01增量)。接下来,通过StringFormat("LS_%I64u", (long)t)组合成为唯一的对象名称。由于(long)t是一个反映K线确切开盘时间的64位整数,因此,没有两根K线会产生相同的对象名称。
绘制前调用ObjectFind(0, name)检查同名对象是否存在,如果存在则删除 —— 这一步骤可以防止EA重新加载或图表刷新时产生冗余标记。最后,如果PlotArrow为true,调用ObjectCreate在计算的价格处绘制彩色箭头(看涨向上/看跌向下)。如果PlotArrow为false且LblType非空,该函数则改为绘制短文本或文本标签(如简写"BS"或完整"Bull Sweep")。将所有图表对象代码封装于独立函数中,EA实现了检测逻辑与视觉注释的解耦,体现了简洁和模块化设计。
void DrawSignal(int shift, bool bullish) { // Get the exact bar start time for this sweep datetime t = iTime(Symbol(), Period(), shift); // Determine the on‐chart Y‐coordinate: offset a few points above/below the candle double price = bullish ? (iLow(Symbol(), Period(), shift) - ArrowOffsetPoints * _Point) : (iHigh(Symbol(), Period(), shift) + ArrowOffsetPoints * _Point); // Compose a unique name using the 64-bit timestamp string name = StringFormat("LS_%I64u", (long)t); // If an object with that name already exists, delete it first if(ObjectFind(0, name) >= 0) ObjectDelete(0, name); // (Next lines will choose arrow vs. text drawing) }
在底层实现中,两个辅助函数用于计算非标准MA。CalcVWMA(int shift)实现了典型的成交量加权移动平均(VWMA)计算:初始化两个累加变量numerator(分子)和denominator(分母)为0.0。接下来,通过循环遍历从shift到shift + MALength – 1的每个K线索引:读取收盘价price = iClose(...)和读取成交量vol = iVolume(...)(以64位长整型存储)。计算时将成交量转换为双精度浮点型:numerator += price * (double)vol;denominator += (double)vol,这样可以避免因隐式类型转换而引发的编译器警告。
循环结束后,如果denominator > 0.0,返回numerator / denominator;否则安全返回0.0(防止除0错误)。最终,每个VWMA点的值=过去MALength根K线的(价格 × 成交量)总和 ÷ 成交量总和,这正是交易者对成交量加权平均的标准定义。
double CalcVWMA(int shift) { double numerator = 0.0; double denominator = 0.0; // Loop over 'MALength' bars, starting at 'shift' for(int i = shift; i < shift + MALength; i++) { double price = iClose(Symbol(), Period(), i); long vol = iVolume(Symbol(), Period(), i); // Accumulate (price × volume) and sum of volume numerator += price * (double)vol; denominator += (double)vol; } // Return VWMA = sum(price×volume) / sum(volume), or 0 if volume = 0 return (denominator > 0.0) ? (numerator / denominator) : 0.0; }
最后,通过CalcHMA(int shift)实现了HMA的计算 —— 这是一种通过两个阶段加权处理来显著降低滞后性的移动平均算法:第一阶段,设定半周期长度half = MALength / 2,遍历从i = shift到i < shift + half的K线,为每根K线分配递减权重(从half递减至1)。累加加权价格和w1与权重总和sw1,计算半周期MA加权平均w1 / sw1。第二阶段,遍历从i = shift到i < shift + MALength的K线,为每根K线分配递减权重(从MALength递减至1),累加加权价格和w2与权重总和sw2,计算全周期MA加权平均w2 / sw2。
最终HMA值如下:2 × w1 - w2。实际上,实际上,将半周期MA翻倍并减去全周期MA,可以增强MA对近期价格变化的灵敏度,许多交易者认为这种特性在波动市场中的表现更优异。与VWMA的实现类似,EA通过仅在Bars(Symbol(),Period()) > shift + MALength时才调用CalcHMA,避免因历史数据不足导致计算错误。
//+------------------------------------------------------------------+ //| Compute Hull Moving Average (HMA) | //+------------------------------------------------------------------+ double CalcHMA(int shift) { int half = MALength / 2; double w1 = 0.0, sw1 = 0.0; double w2 = 0.0, sw2 = 0.0; // 1) Weighted MA over half period for(int i = shift; i < shift + half; i++) { double p = iClose(Symbol(), Period(), i); int weight = half - (i - shift); w1 += p * weight; sw1 += weight; } w1 = (sw1 > 0.0) ? (w1 / sw1) : 0.0; // 2) Weighted MA over full period for(int i = shift; i < shift + MALength; i++) { double p = iClose(Symbol(), Period(), i); int weight = MALength - (i - shift); w2 += p * weight; sw2 += weight; } w2 = (sw2 > 0.0) ? (w2 / sw2) : 0.0; // 3) Final HMA value = 2 * (MA over half) – (MA over full) return 2.0 * w1 - w2; }
测试结果
为评估并全面理解本工具的性能表现,我们开展了系统性测试。通过回测复盘历史行情数据,验证工具在实时市场条件下的即时表现。上述测试所获得的关键结果如下图所示:

图例3. 英镑兑美元实盘测试
该图表清晰展示了本工具识别流动性扫单的能力:图中先后出现明显的看跌扫单与看涨扫单,表明工具可以精准地捕捉市场反转信号及机构资金动向。该图表清晰展示了本工具识别流动性扫单的能力:图中先后出现明显的看跌扫单与看涨扫单,表明工具可以精准地捕捉市场反转信号及机构资金动向。

图例4. 步进指数实盘测试
上述测试结果充分验证了该工具在识别流动性扫单方面的有效性。通过清晰的视觉标记精准捕捉关键市场反转点,可为交易策略的制定提供有力的支持。

图例5. 欧元兑美元回测分析
该回测图表充分验证了该工具在历史数据中精准识别流动性扫单模式的能力。通过解析历史价格走势,工具可标记出价格短暂突破支撑/阻力位后反转的关键节点,这些往往与机构资金动向密切相关。识别出的点位可作为预测未来市场反转或趋势延续的重要参考。图表结果直观证明了检测机制的有效性,为其在实盘交易环境中的应用提供了坚实的依据。
结论
该EA在历史回测与实盘模拟中均展现出持续稳定的流动性扫单识别能力,在价格突破并回抽前期波段高点/低点时,系统会以箭头精准标注每个有效点位。通过多种MA过滤设置,信号与后续趋势延续高度吻合,在实时市场条件下可靠性显著。无论是回溯历史数据还是实盘运行,其识别"止损猎杀"(机构通过触发止损单制造流动性)的准确性均已得到反复验证。您可将该扫单检测整合到现有策略中,其代码已在模拟盘与实盘环境中双重验证,具备高精度与即时响应性。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18379
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
MQL5交易管理面板开发(第十二部分):汇率计算器的集成
价格行为分析工具包开发(第二十六部分):针形线、吞没形态与RSI背离(多模式)工具