使用MQL5实现抛物线SAR趋势策略的自动化交易:打造高效的EA·进阶篇
「用抛物线 SAR 给开仓定方向」
抛物线 SAR 是 MT5 里判断趋势惯性的轻量指标,核心逻辑就一句:收盘价在 SAR 上方偏多、下方偏空。把这条规则写进 EA,就能在无持仓时自动选边。 下面这段 MQL5 先取当前品种和周期的 SAR 值与上一根 K 线收盘价,再做方向判断: double sar = iSAR(_Symbol, _Period, SAR_Step, SAR_Maximum); // 计算抛物线SAR数值,步长与上限由外部参数传入 double lastPrice = iClose(_Symbol, _Period, 1); // 取前一根蜡烛的收盘价,避免用未闭合的当前线 if(!IsPositionOpen(_Symbol)) // 检查当前品种是否已有持仓,无仓才进场 if(lastPrice > sar) // 昨收在SAR之上 OpenBuyOrder(); // 开多 else if(lastPrice < sar) // 昨收在SAR之下 OpenSellOrder(); // 开空 实盘里 EURUSD 在 H1 周期用默认步长 0.02、上限 0.2 时,SAR 翻转往往滞后 1~2 根 K 线,追单容易吃假突破。外汇与贵金属杠杆高,这类裸 SAR 信号仅作方向参考,仓位要控小。 开 MT5 把 iSAR 拖到图表,对照上面逻辑改两行参数,就能看信号翻转是否和你手感一致。
class="type">class="kw">double sar = iSAR(_Symbol, _Period, SAR_Step, SAR_Maximum); class=class="str">"cmt">// Calculate the Parabolic SAR value class="type">class="kw">double lastPrice = iClose(_Symbol, _Period, class="num">1); class=class="str">"cmt">// Get the close price of the previous candle class=class="str">"cmt">//--- Check if there is an existing position if(!IsPositionOpen(_Symbol)) class=class="str">"cmt">//--- Determine trade direction based on SAR if(lastPrice > sar) { OpenBuyOrder(); class=class="str">"cmt">// If the last price is above the SAR, open a buy order } else if(lastPrice < sar) { OpenSellOrder(); class=class="str">"cmt">// If the last price is below the SAR, open a sell order } }
SAR 翻转触发开仓的判定次序
抛物线转向指标(SAR)的核心用法之一,是把价格与 SAR 值的相对位置当作趋势开关。当最新价向上越过 SAR,系统读为上升波段开启,倾向调用买入函数;当最新价向下跌破 SAR,则读为下降波段,倾向调用卖出函数。外汇与贵金属杠杆高,这种翻转信号失效时回撤会很快,只能当作概率倾向而非确定性。 这段逻辑在代码里是顺序判断,先测买入再测卖出:只要 lastprice > sar 成立就直接开多,不往下走;只有该条件不成立,才进入 lastPrice < sar 的分支去开空。实盘里意味着同一根 K 线不会既测多又测空,避免重复发单。 你可以把下列片段直接丢进 MT5 的 EA 初始化逻辑后跑一遍,观察在 EURUSD 的 M15 上 SAR 翻转当根 K 线的成交偏向,验证优先买入是否如预期压过卖出分支。
if(lastprice > sar) OpenBuyOrder(); else if(lastPrice < sar) OpenSellOrder();
◍ 持仓自检与双向下单的函数骨架
先说 IsPositionOpen 这个布尔函数,它接收一个 string 类型的 symbol 参数,用来判断当前图表品种是否已有持仓。内部先调 PositionsTotal() 拿到终端持仓总数,再用 for 循环从 0 跑到 total-1,逐个取 PositionGetSymbol(i) 比对 _Symbol。一旦匹配就 return true,循环走完没撞上就 return false,逻辑直接拦住同品种重复开仓。 比如你在 EURUSD 图表上跑 EA,函数返回 true 时策略就倾向只管已有仓位、不再新开。外汇和贵金属波动快、杠杆高,这种防重复机制能降低因逻辑漏洞导致的双向加仓风险。 OpenBuyOrder 和 OpenSellOrder 都是 void 型,不返回值只做执行。Buy 这边用 trade.Buy(LotSize, NULL, 0, 0, 0, "Buy Order"):第二参 NULL 吃当前图表品种,三、四、五参全 0 代表市价、无止损、无止盈,当前示例手数 LotSize 为 0.2。成功就 Print 成功信息,失败走 GetLastError() 把错误码打出来方便排查。 Sell 一侧结构对称,trade.Sell 参数排布完全一致,注释换成 "Sell order"。两个函数都不设初始止损止盈,实盘前你大概率要把那两个 0 改成基于 ATR 或固定点的数值,否则裸奔市价单在贵金属跳空时可能亏得超出预期。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Check if there is an open position for the given symbol | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsPositionOpen(class="type">class="kw">string symbol) { class="type">uint total=PositionsTotal(); class=class="str">"cmt">//Get the total number of open positions for(class="type">uint i=class="num">0; i<total; i++) { class="type">class="kw">string POSITION_SYMBOL=PositionGetSymbol(i); class=class="str">"cmt">//Get the symbol of the position if(POSITION_SYMBOL == _Symbol) class=class="str">"cmt">//Check if the symbol matches the current symbol { class="kw">return true; } } class="kw">return class="kw">false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Open a Buy order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OpenBuyOrder() { if(trade.Buy(LotSize, NULL, class="num">0, class="num">0, class="num">0, "Buy Order")) { Print("Buy order placed successfully"); } else { Print("Error placing buy order: ", GetLastError()); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Open a Sell order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OpenSellOrder() {
「把卖出信号接进下单函数」
上面那段 if(trade.Sell(...)) 是 EA 里真正触发空单的落点:用 CTrade 类的 Sell 方法,手数走 LotSize 输入变量,品种填 NULL 表示当前图表品种,三个 0 分别对应挂单价、止损、止盈——全 0 即市价单不设防护。
成功就 Print 一条日志,失败则把 GetLastError() 的错误码打出来,这一步在 MT5 回测里能直接看到下单失败原因,比盲调参数省时间。
文件头部的输入参数给了可验证的默认值:LotSize=0.2、SAR_Step=0.02、SAR_Maximum=0.2、Slippage=3。外汇与贵金属杠杆高,0.2 手在迷你账户可能占保证金过半,实盘前务必按自己账户校准。
OnInit 只打印初始化成功,OnDeinit 打印卸载,OnTick 开头留了获取 SAR 值的注释位——也就是说本段并没把 SAR 翻转判断写进去,只展示了下单壳,真正信号过滤得你自己补。
if(trade.Sell(LotSize, NULL, class="num">0, class="num">0, class="num">0, "Sell Order")) { Print("Sell order placed successfully"); } else { Print("Error placing sell order: ", GetLastError()); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ParabolicSAR_EA.mq5 | class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd."Duke" | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd." "Duke" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include <Trade\Trade.mqh> class=class="str">"cmt">//--- Input parameters class="kw">input class="type">class="kw">double LotSize = class="num">0.2; class=class="str">"cmt">// Lot size for trading class="kw">input class="type">class="kw">double SAR_Step = class="num">0.02; class=class="str">"cmt">// Parabolic SAR step class="kw">input class="type">class="kw">double SAR_Maximum = class="num">0.2; class=class="str">"cmt">// Parabolic SAR maximum class="kw">input class="type">int Slippage = class="num">3; class=class="str">"cmt">// Slippage for orders class=class="str">"cmt">//--- Trade object CTrade trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Initialization code here Print("Parabolic SAR EA Initialized"); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- Cleanup code here Print("Parabolic SAR EA Deinitialized"); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- Get the Parabolic SAR value