价格行为分析工具包开发(第十四部分):抛物线转向与反转工具(基础篇)
价格行为工具包里的抛物转向起点
这一节是整套价格行为分析工具包的第 14 部分,落点在 MetaTrader 5 平台上用 MQL5 把抛物线转向(Parabolic SAR)与反转识别做成可复用模块。原文给出的发布时间为 2025 年 10 月 30 日,截至统计页面显示阅读量 935、评论 0,说明这套东西还处在低频曝光、未引发社区争论的阶段。 作者 Christian Benjamin 把全文拆成引言、概念阐释、MQL5 实现、测试及结果、结论五个块。对交易者来说,重点不在阅读量,而在「实现」和「测试结果」这两段——前者决定你能否直接抄进 MT5 自定义指标,后者决定参数在什么周期上更不容易被毛刺洗掉。 外汇与贵金属市场高杠杆、滑点随机,任何转向工具都只是概率过滤器。开 MT5 把 SAR 默认步长 0.02、最大值 0.2 调出来,先跑 EURUSD 的 H1,看反转信号和人工目测的价格行为是否对得上,再谈改装。
「为什么用指标搭反转EA」
技术指标本质是对历史价格、成交量、持仓量等数据的模式抽取,给交易者一套可重复的启发式判读框架。它不预测未来,只是把已验证的趋势与统计关系转成可视信号,降低主观随意性。 本篇要落地的,是一个用 MQL5 写的自动交易系统,专盯潜在市场反转。它借抛物线转向(SAR)做信号探测:实时盯指标、评估信号有效性,并在触及预设价位时锁定退出点。外汇与贵金属杠杆高,反转识别错了就是连续止损,实盘前务必在 MT5 策略测试器跑通。 下面先交待基础策略逻辑与用指标的根本理由,再进 MQL5 实现、回测与调参建议。
◍ PSAR 翻转与 K 线确认的生成逻辑
PSAR 里的“抛物线”指点的排布随趋势加速外扩,离近期极值越远,点距拉得越开,直观反映动能堆叠;“SAR”则是停止并反转的触发带,点一旦从价格一侧翻到另一侧,当前趋势倾向衰竭。 把 PSAR 加到 MT5 很简单:插入 → 指标 → 趋势 → 抛物线转向,参数务必和你 EA 里调用的步长、步长上限一致,否则回测和肉眼看图会对不上。 买入触发要求三根 K 线协同:当下这根收阳且 PSAR 值低于收盘价;前两根靠 PSAR 位于收盘价之上确认此前的空头;同时前几根 PSAR 差值落在阈值内,避免点距突变造成的假翻面。卖出镜像处理——收阴、PSAR 在收上、前两根 PSAR 在收下。 外汇与贵金属杠杆高,PSAR 翻转只代表概率倾向反转,实盘前应在 MT5 用历史数据跑一遍上述三根 K 线条件,确认信号频次和滑点下的表现再上仓。
把 PSAR 反转逻辑落进 MT5 的 EA 骨架
EA 顶部先用 #property strict 强制类型检查,这能在编译期拦掉多数参数误用;实盘或发布前都建议开着,减少运行时报错和后续 MT5 更新的兼容坑。
输入参数直接暴露给 MT5 界面:SARStep=0.02 是加速因子,SARMaximum=0.2 封顶加速度;MinConsecutiveDots=2 要求反转前至少 2 根同趋势 K 线,MaxDotGapPercentage=1.0 限制相邻 PSAR 点间距不超过收盘价的 1%。箭头符号走 Wingdings 字体,买卖可分别设 ✓ 和 X,调宽度能提升扫图辨识度。
全局里 sarHandle 存指标句柄,lastBarTime 防重复处理,SignalType 枚举无/买/卖三态。pendingReversalLevel 记下信号发生时的 PSAR 值,作为后续真假反转的参照线——这是过滤假突破的关键 checkpoint。
IsNewBar 比当前 K 线时间与 lastBarTime,不同才更新并返回 true,保证每根 K 线只算一次,避免 tick 内重复报警。CheckForSignal 读最近三根:买需本根收高于开且 PSAR 在下方、前两根看跌且 PSAR 在上方,卖反之;间隙超阈值即弃信号。
OnTick 里若有 pending 信号则 waitCount++,价触 pendingReversalLevel 报平仓提示,满 3 根未确认则警告可能假信号;无 pending 才调 CheckForSignal 建仓并画箭头。OnInit 建句柄失败就报错退出,OnDeinit 清箭头等对象,不留垃圾在图上。
外汇与贵金属波动大、杠杆高,这套 EA 仅提供信号参考,实盘前请在策略测试器用历史数据验证参数敏感性。
class=class="str">"cmt">//+--------------------------------------------------------------------+ class=class="str">"cmt">//| Parabolic SAR EA.mql5 | class=class="str">"cmt">//| Copyright class="num">2025, Christian Benjamin | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+--------------------------------------------------------------------+ class="macro">#class="kw">property copyright "class="num">2025, Christian Benjamin" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.0" class="macro">#class="kw">property strict class=class="str">"cmt">// Input parameters for the Parabolic SAR indicator input class="type">class="kw">double SARStep = class="num">0.02; class=class="str">"cmt">// Acceleration factor for PSAR input class="type">class="kw">double SARMaximum = class="num">0.2; 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
「把信号画上图:箭头与等待机制的代码骨架」
PSAR 翻转信号若只在日志里闪一下,盯盘时根本抓不住。上面这段全局声明把提醒方式、箭头样式和一套‘待定信号’机制先铺好:弹窗、声音、箭头三个开关默认全开,Wingdings 编码 233/234 分别对应涨箭头和跌箭头,ArrowOffsetMultiplier=5 控制箭头相对 K 线的偏移倍数。 pendingSignal 配合 waitCount、pendingReversalLevel 实现‘检测后不立刻下手’的逻辑——信号出现时先存下当时的 PSAR 值,之后每收一根新柱 waitCount 加一,给交易者留出人工确认的时间窗。外汇与贵金属波动剧烈,这种延迟确认可能降低假突破干扰,但也可能错过早期入场,属概率权衡。 DrawSignalArrow 函数负责把箭头钉在图上:用 TimeToString(barTime, TIME_SECONDS) 拼出唯一对象名,先 ObjectFind 删重再 ObjectCreate 画 OBJ_ARROW,颜色、箭头码、线宽分别由 OBJPROP_COLOR / OBJPROP_ARROWCODE / OBJPROP_WIDTH 设定。开 MT5 把这段代码贴进 EA,改 BuyArrowSymbol 的 Wingdings 码就能换你顺眼的标记。 CheckForSignal 的索引约定很直接:数组 0 是刚收的柱、1 是前一柱、2 是更前一根,sar0/sar1/sar2 与对应 open/close 取出后供翻转判定。这套索引映射若不记牢,后面写条件时极易把‘上一根’当‘当前’用。
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="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); ObjectSetInteger(class="num">0, arrowName, OBJPROP_WIDTH, ArrowWidth); } else { Print("Failed to create arrow object: ", arrowName); } } 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]; }