价格行为分析工具包开发(第十四部分):抛物线转向与反转工具·进阶篇
(2/3)· 当PSAR点翻越价格,你的EA能否在反转临界点自动识别并退出?
不少交易者把抛物线转向只当画图辅助,真到写EA时却卡在信号阈值和前两根K线确认逻辑上。PSAR翻面若不加一致性过滤,震荡市会连续打脸。这一篇把反转工具的MQL5实现拆开给你看。
「SAR 翻转与实体过滤的买卖判定」
这段逻辑只在每根 K 线收盘后跑一次,用 Parabolic SAR 点阵位置切换配合当根阳阴线来给方向。核心是先取前三根 K 线的开收与 SAR 值:open2/close2 是倒数第三根,sar0~sar2 分别对应最近三根的点位。 买信号要求当前收高于开(阳线)且 sar0 已落到收盘价下方,同时前两根 sar1、sar2 都高于各自收盘价——也就是 SAR 刚从价格上方翻到下方。代码里还数了 countBearish(前两根 SAR 在价上的次数)并算 sar1 与 sar2 的绝对差 dotGap,用 MaxDotGapPercentage 换算成基于 close0 的阈值 gapThreshold 做过滤。 卖信号对称:阴线且 sar0 在收上方,sar1、sar2 在收下方,countBullish 与 dotGap 同样受 MinConsecutiveDots 和 gapThreshold 约束才返 SELL_SIGNAL。两个分支都不满足就返 NO_SIGNAL。 外汇与贵金属波动剧烈,SAR 点距过滤参数若设太宽,在镑美 M5 上可能一晚触发多次假信号;建议把 MaxDotGapPercentage 先按 0.3 试,再上 MT5 用策略测试器看近 3 个月回放。
class="type">class="kw">double open2 = openArray[class="num">2], close2 = closeArray[class="num">2]; class=class="str">"cmt">// Check for BUY signal: if((close0 > open0) && (sar0 < close0) && (sar1 > close1) && (sar2 > close2)) { class="type">int countBearish = class="num">0; if(sar1 > close1) countBearish++; if(sar2 > close2) countBearish++; class="type">class="kw">double dotGap = MathAbs(sar1 - sar2); class="type">class="kw">double gapThreshold = (MaxDotGapPercentage / class="num">100.0) * close0; if(countBearish >= MinConsecutiveDots && dotGap <= gapThreshold) class="kw">return BUY_SIGNAL; } class=class="str">"cmt">// Check for SELL signal: if((close0 < open0) && (sar0 > close0) && (sar1 < close1) && (sar2 < close2)) { class="type">int countBullish = class="num">0; if(sar1 < close1) countBullish++; if(sar2 < close2) countBullish++; class="type">class="kw">double dotGap = MathAbs(sar1 - sar2); class="type">class="kw">double gapThreshold = (MaxDotGapPercentage / class="num">100.0) * close0; if(countBullish >= MinConsecutiveDots && dotGap <= gapThreshold) class="kw">return SELL_SIGNAL; } class="kw">return NO_SIGNAL; } class="type">bool IsNewBar() { class="type">class="kw">datetime times[]; if(CopyTime(_Symbol, _Period, class="num">1, class="num">1, times) <= class="num">0) { Print("Failed to retrieve bar time in IsNewBar()."); class="kw">return false; } if(times[class="num">0] != lastBarTime) { lastBarTime = times[class="num">0]; class="kw">return true; } class="kw">return false; } class="type">int OnInit() { class=class="str">"cmt">// Create the built-in Parabolic SAR indicator handle sarHandle = iSAR(_Symbol, _Period, SARStep, SARMaximum); if(sarHandle == INVALID_HANDLE) { Print("Error creating PSAR handle"); class="kw">return INIT_FAILED; } Print("SAR EA initialized successfully."); class="kw">return INIT_SUCCEEDED; } class="type">void OnTick() { class=class="str">"cmt">// Process only once per new closed bar if(!IsNewBar()) class="kw">return; class=class="str">"cmt">// Retrieve PSAR and price data class="type">class="kw">double sarArray[class="num">4]; if(CopyBuffer(sarHandle, class="num">0, class="num">1, class="num">4, sarArray) < class="num">3) { class=class="str">"cmt">/* error handling */ } class="type">class="kw">double openArray[class="num">4], closeArray[class="num">4]; if(CopyOpen(_Symbol, _Period, class="num">1, class="num">4, openArray) < class="num">3 ||
◍ 挂起信号的等待与假信号甄别
EA 在拿到初步反转意图后不会立刻下单,而是把 pendingSignal 挂起,用 waitCount 记等待次数。每根新 K 线进来 waitCount++,给价格最多 3 根 K 线的确认窗口。 以 BUY 挂起为例:若最新收盘价 closeArray[0] 跌破 pendingReversalLevel(即信号触发时的 PSAR 值),视为反转确认并关闭警报;若连等 3 根 K 线收盘价都未触及该位,则提示可能是假 BUY 信号。SELL 逻辑对称,收盘价站上 PSAR 才确认,否则等待超 3 根即预警。 无挂起信号时,CheckForSignal 用 sarArray、openArray、closeArray 探测新信号;一旦返回 BUY/SELL,便重置 waitCount=0 并把 pendingReversalLevel 存为当前 sarArray[0]。外汇与贵金属波动剧烈,3 根窗口只是概率过滤,不代表信号必然有效,实盘前请在 MT5 用历史数据回测该等待逻辑。
if(CopyClose(_Symbol, _Period, class="num">1, class="num">4, closeArray) < class="num">3) { class=class="str">"cmt">/* error handling */ } class=class="str">"cmt">// Pending Signal Logic... if(pendingSignal != NO_SIGNAL) { waitCount++; class=class="str">"cmt">// Increment waiting counter if(pendingSignal == BUY_SIGNAL) { if(closeArray[class="num">0] <= pendingReversalLevel) { class=class="str">"cmt">// Confirm reversal: Close alert for BUY signal } else if(waitCount >= class="num">3) { class=class="str">"cmt">// Warn about a possible fake BUY signal } } else if(pendingSignal == SELL_SIGNAL) { if(closeArray[class="num">0] >= pendingReversalLevel) { class=class="str">"cmt">// Confirm reversal: Close alert for SELL signal } else if(waitCount >= class="num">3) { class=class="str">"cmt">// Warn about a possible fake SELL signal } } class="kw">return; class=class="str">"cmt">// Wait until pending signal is resolved } class=class="str">"cmt">// Check for a new reversal signal if no pending signal exists SignalType newSignal = CheckForSignal(sarArray, openArray, closeArray); if(newSignal != NO_SIGNAL) { pendingSignal = newSignal; waitCount = class="num">0; class=class="str">"cmt">// Reset counter pendingReversalLevel = sarArray[class="num">0]; class=class="str">"cmt">// Store current PSAR value class=class="str">"cmt">// Alert and optionally draw an arrow based on the new signal if(newSignal == BUY_SIGNAL) { class=class="str">"cmt">/* process BUY signal */ } else if(newSignal == SELL_SIGNAL) { class=class="str">"cmt">/* process SELL signal */ } }
PSAR 信号过滤与箭头绘制的参数骨架
下面这段 MQL5 输入与全局定义,决定了 PSAR 反转信号要不要画箭头、怎么画。SARStep 设 0.1、SARMaximum 设 1.0,是抛物线加速因子的常规起手;MinConsecutiveDots 要求至少 2 根同向 K 线才认趋势,MaxDotGapPercentage 限制相邻点差不超过收盘价的 1.0%,这两个过滤项直接砍掉毛刺反转。 EnableAlerts / EnableSound / EnableArrows 三个布尔开关分开管弹窗、声音和图表箭头;BuyArrowSymbol 用 Wingdings 的 "233"(上箭)、SellArrowSymbol 用 "234"(下箭),ArrowOffsetMultiplier=5 控制箭头离价的偏移倍数,实盘里黄金 M5 常要把它调到 8~10 才不被 K 线吞掉。 pendingSignal 配合 waitCount、pendingReversalLevel 做“待确认”机制:检测到反转先不画,等新收 bar 数累积后再落锤,能降低假突破概率,但也会慢半拍。外汇与贵金属波动剧烈,这套参数仅降低噪声,不消除方向误判风险。
input class="type">class="kw">double SARStep = class="num">0.1; class=class="str">"cmt">// Acceleration factor for PSAR input class="type">class="kw">double SARMaximum = class="num">1; class=class="str">"cmt">// Maximum acceleration for PSAR class=class="str">"cmt">// Input parameters for refining the signal based on PSAR dots input class="type">int MinConsecutiveDots = class="num">2; class=class="str">"cmt">// Require at least class="num">2 consecutive bars in one trend before reversal input class="type">class="kw">double MaxDotGapPercentage = class="num">1.0; class=class="str">"cmt">// Maximum allowed gap between consecutive PSAR dots(% of current close) class=class="str">"cmt">// Input parameters for alerts and arrow drawing input class="type">bool EnableAlerts = true; class=class="str">"cmt">// Enable popup alerts input class="type">bool EnableSound = true; class=class="str">"cmt">// Enable sound alerts input class="type">bool EnableArrows = true; class=class="str">"cmt">// Draw arrows on chart input class="type">class="kw">string BuyArrowSymbol = "class="num">233"; class=class="str">"cmt">// Wingdings up arrow(as class="type">class="kw">string) input class="type">class="kw">string SellArrowSymbol = "class="num">234"; class=class="str">"cmt">// Wingdings down arrow(as class="type">class="kw">string) input class="type">int ArrowWidth = class="num">2; class=class="str">"cmt">// Arrow thickness input class="type">class="kw">double ArrowOffsetMultiplier = class="num">5; class=class="str">"cmt">// Multiplier for arrow placement offset class=class="str">"cmt">// Global indicator handle for PSAR class="type">int sarHandle = INVALID_HANDLE; class=class="str">"cmt">// Global variable to track last processed bar time class="type">class="kw">datetime lastBarTime = class="num">0; class=class="str">"cmt">// Enumeration for signal types enum SignalType { NO_SIGNAL, BUY_SIGNAL, SELL_SIGNAL }; class=class="str">"cmt">// Global variables for pending signal mechanism SignalType pendingSignal = NO_SIGNAL; class="type">int waitCount = class="num">0; class=class="str">"cmt">// Counts new closed bars since signal detection class="type">class="kw">double pendingReversalLevel = class="num">0.0; class=class="str">"cmt">// Stores the PSAR value at signal detection class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| DrawSignalArrow - Draws an arrow object on the chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void DrawSignalArrow(class="type">class="kw">string prefix, class="type">class="kw">datetime barTime, class="type">class="kw">double price, class="type">class="kw">color arrowColor, class="type">long arrowCode) { class="type">class="kw">string arrowName = prefix + "_" + TimeToString(barTime, TIME_SECONDS); class=class="str">"cmt">// Remove existing object with the same name to prevent duplicates if(ObjectFind(class="num">0, arrowName) != -class="num">1) ObjectDelete(class="num">0, arrowName); if(ObjectCreate(class="num">0, arrowName, OBJ_ARROW, class="num">0, barTime, price)) { ObjectSetInteger(class="num">0, arrowName, OBJPROP_COLOR, arrowColor); ObjectSetInteger(class="num">0, arrowName, OBJPROP_ARROWCODE, arrowCode);
「信号判定与K线刷新这两个底层函数」
PSAR 翻转配合价格收线,是这套策略触发箭头的前提。CheckForSignal 把数组下标做了硬性映射:0 是刚收盘的当前候选 bar,1 是上一根,2 是再前一根,读代码时别把索引顺手当成时间顺序的反向。 买信号要求 close0>open0 且 sar0 已落到 close0 下方,同时 sar1、sar2 都在对应收盘价上方——也就是 PSAR 点从价格上方翻到下方,且之前至少连续两根处于空头点阵。代码里用 countBearish 累加 sar1、sar2 高于收盘的次数,再和 MinConsecutiveDots 比;若设 MinConsecutiveDots=2,则必须两根都满足条件才放行。 sar1 与 sar2 的绝对差被 MathAbs 取出,和 (MaxDotGapPercentage/100)*close0 这根阈值比。比如 MaxDotGapPercentage 填 0.5、close0 是 1.2000,点距超过 0.006 就过滤掉,避免点阵拉伸过大时的假翻转。卖信号逻辑对称,close0<open0 且 sar0 站上 close0,前两根点阵在价格下。 IsNewBar 不依赖指标重绘,直接用 CopyTime 取最近一根已闭 bar 的时间戳,和模块级变量 lastBarTime 比对。不等就更新并返回 true,等于就 false。外汇和贵金属波动大、滑点凶,这种基于闭 bar 的触发比每 tick 判断更不容易在盯盘时被毛刺骗进单。 把这两段直接塞进 EA,开 MT5 用欧美对 1H 跑一遍,把 MinConsecutiveDots 从 2 调到 1,看箭头密度怎么变——这是验证信号层有没有写错的最快办法。
SignalType CheckForSignal(const class="type">class="kw">double &sarArray[], const class="type">class="kw">double &openArray[], const class="type">class="kw">double &closeArray) { class=class="str">"cmt">// Mapping indices: class=class="str">"cmt">// Index class="num">0: Last closed bar(current candidate) class=class="str">"cmt">// Index class="num">1: Previous bar class=class="str">"cmt">// Index class="num">2: Bar before previous class="type">class="kw">double sar0 = sarArray[class="num">0], sar1 = sarArray[class="num">1], sar2 = sarArray[class="num">2]; class="type">class="kw">double open0 = openArray[class="num">0], close0 = closeArray[class="num">0]; class="type">class="kw">double open1 = openArray[class="num">1], close1 = closeArray[class="num">1]; class="type">class="kw">double open2 = openArray[class="num">2], close2 = closeArray[class="num">2]; class=class="str">"cmt">// Check for BUY signal: if((close0 > open0) && (sar0 < close0) && (sar1 > close1) && (sar2 > close2)) { class="type">int countBearish = class="num">0; if(sar1 > close1) countBearish++; if(sar2 > close2) countBearish++; class="type">class="kw">double dotGap = MathAbs(sar1 - sar2); class="type">class="kw">double gapThreshold = (MaxDotGapPercentage / class="num">100.0) * close0; if(countBearish >= MinConsecutiveDots && dotGap <= gapThreshold) class="kw">return BUY_SIGNAL; } class=class="str">"cmt">// Check for SELL signal: if((close0 < open0) && (sar0 > close0) && (sar1 < close1) && (sar2 < close2)) { class="type">int countBullish = class="num">0; if(sar1 < close1) countBullish++; if(sar2 < close2) countBullish++; class="type">class="kw">double dotGap = MathAbs(sar1 - sar2); class="type">class="kw">double gapThreshold = (MaxDotGapPercentage / class="num">100.0) * close0; if(countBullish >= MinConsecutiveDots && dotGap <= gapThreshold) class="kw">return SELL_SIGNAL; } class="kw">return NO_SIGNAL; } class="type">bool IsNewBar() { class="type">class="kw">datetime times[]; if(CopyTime(_Symbol, _Period, class="num">1, class="num">1, times) <= class="num">0) { Print("Failed to retrieve bar time in IsNewBar()."); class="kw">return false; } if(times[class="num">0] != lastBarTime) { lastBarTime = times[class="num">0]; class="kw">return true; } class="kw">return false; }
◍ 把抛物线转向逻辑落进EA骨架
在 MT5 里跑 PSAR 信号,第一步是拿到指标句柄并在初始化阶段就拦掉失败。下面这段 OnInit 用 iSAR 绑定当前品种与周期,步长 SARStep 和上限 SARMaximum 是外部参数,若返回 INVALID_HANDLE 直接 INIT_FAILED,避免后续空跑。 OnDeinit 里除了释放句柄,还用 ObjectsDeleteAll(0, (int)OBJ_ARROW, 0) 清掉图表上的箭头对象。实盘切换周期或移除 EA 时,残留箭头会误导肉眼判断,这个清理不能省。 OnTick 的核心约束是「每根收盘 Bar 只处理一次」:用 IsNewBar() 拦截,否则同根 Bar 内多次报价会重复发信号。CopyBuffer 取最近 4 个 PSAR 值,但只要求 >=3 个有效,说明倒数第 4 根可能尚在成型,逻辑上只用前 3 根做确认。 pendingSignal 机制给信号留了等待窗口:BUY_SIGNAL 挂起后 waitCount 自增,若最新收盘 closeArray[0] 跌破 pendingReversalLevel 就判定反转到位平仓。外汇与贵金属波动大,这种二次确认能过滤部分假突破,但无法消除跳空造成的滑点风险,参数需自己在历史数据上回测。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">// Create the built-in Parabolic SAR indicator handle sarHandle = iSAR(_Symbol, _Period, SARStep, SARMaximum); if(sarHandle == INVALID_HANDLE) { Print("Error creating PSAR handle"); class="kw">return INIT_FAILED; } Print("SAR EA initialized successfully."); class="kw">return INIT_SUCCEEDED; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { if(sarHandle != INVALID_HANDLE) IndicatorRelease(sarHandle); class=class="str">"cmt">// Remove all arrow objects from the current chart window ObjectsDeleteAll(class="num">0, (class="type">int)OBJ_ARROW, class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">// Process only once per new closed bar if(!IsNewBar()) class="kw">return; class=class="str">"cmt">// Retrieve the last class="num">4 PSAR values(we require at least class="num">3 values) class="type">class="kw">double sarArray[class="num">4]; if(CopyBuffer(sarHandle, class="num">0, class="num">1, class="num">4, sarArray) < class="num">3) { Print("Failed to retrieve PSAR data."); class="kw">return; } class=class="str">"cmt">// Retrieve the last class="num">4 bars&class="macro">#x27; price data(Open and Close) class="type">class="kw">double openArray[class="num">4], closeArray[class="num">4]; if(CopyOpen(_Symbol, _Period, class="num">1, class="num">4, openArray) < class="num">3 || CopyClose(_Symbol, _Period, class="num">1, class="num">4, closeArray) < class="num">3) { Print("Failed to retrieve price data."); class="kw">return; } class=class="str">"cmt">// Process pending signal logic if a signal is waiting confirmation if(pendingSignal != NO_SIGNAL) { waitCount++; class=class="str">"cmt">// Increment the waiting counter if(pendingSignal == BUY_SIGNAL) { if(closeArray[class="num">0] <= pendingReversalLevel) { Print("Reversal level reached for BUY signal. Close here."); if(EnableAlerts)