如何使用抛物线转向(Parabolic SAR)指标设置跟踪止损(Trailing Stop)·综合运用
📉

如何使用抛物线转向(Parabolic SAR)指标设置跟踪止损(Trailing Stop)·综合运用

(3/3)·从标准库几行代码到 EA 集成,用 SAR 点位替你自动收止损的完整路径

含代码示例实战向 第 3/3 篇

很多交易者把 MT5 内置跟踪止损当万能,却没意识到它只按固定距离推,遇到趋势加速就白白放跑利润。SAR 点天生贴着价格走,拿来定位止损比拍脑袋设点数更顺手。本篇把这套做法从函数到 EA 落地讲透。

◍ SAR 穿价反转的开仓判定骨架

这段 EA 初始化里先把 Parabolic SAR 的句柄和步进、最大值参数绑好,步进和最大值若小于 0.0001 会被强制抬到 0.0001,避免极端参数让指标失效。 OnInit 中用 iSAR() 创建句柄,失败就往日志打 Symbol、周期、step、maximum 和错误码,然后返回 INIT_FAILED;成功才返回 INIT_SUCCEEDED。 OnTick 只在 IsNewBar() 为真时跑逻辑。回测环境下(MQLInfoInteger(MQL_TESTER) 为真)取第 1、2 根 bar 的 SAR 值:若 sar1 低于 Bid 且 sar2 高于 Bid,市价买 0.1 手;若 sar1 高于 Ask 且 sar2 低于 Ask,则反向开空。外汇与贵金属杠杆高,穿价信号只是概率倾向,实盘需自测滑点与点差。 下面这段是可直接贴进 MT5 编辑器验证的裸逻辑,注意 CTrade 实例和魔法号在 OnInit 就设好。

MQL5 / C++
class="type">int    ExtHandleSAR=INVALID_HANDLE;   class=class="str">"cmt">// Parabolic SAR handle
class="type">class="kw">double  ExtStepSAR=class="num">0;                   class=class="str">"cmt">// Parabolic SAR step
class="type">class="kw">double  ExtMaximumSAR=class="num">0;                class=class="str">"cmt">// Parabolic SAR maximum
CTrade  ExtTrade;                       class=class="str">"cmt">// trading operations class instance
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- set the magic number to the trading class object
   ExtTrade.SetExpertMagicNumber(InpMagic);
class=class="str">"cmt">//--- set the Parabolic SAR parameters within acceptable limits
   ExtStepSAR   =(InpStepSAR<class="num">0.0001 ? class="num">0.0001 : InpStepSAR);
   ExtMaximumSAR=(InpMaximumSAR<class="num">0.0001 ? class="num">0.0001 : InpMaximumSAR);

class=class="str">"cmt">//--- if there is an error creating the indicator, display a message in the journal and exit from OnInit with an error
   ExtHandleSAR =iSAR(Symbol(), InpTimeframeSAR, ExtStepSAR, ExtMaximumSAR);
   if(ExtHandleSAR==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  Symbol(), TimeframeDescription(InpTimeframeSAR), ExtStepSAR, ExtMaximumSAR, GetLastError());
      class="kw">return(INIT_FAILED);
     }
class=class="str">"cmt">//--- successful
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- if not a new bar, leave the handler
   if(!IsNewBar())
      class="kw">return;

   if(MQLInfoInteger(MQL_TESTER))
     {
      class=class="str">"cmt">//--- get Parabolic SAR data from bars class="num">1 and class="num">2
      class="type">class="kw">double sar1=GetSARData(SAR_DATA_INDEX);
      class="type">class="kw">double sar2=GetSARData(SAR_DATA_INDEX+class="num">1);

      class=class="str">"cmt">//--- if the price structure is filled out and Parabolic SAR data is obtained
      class="type">MqlTick tick={};
      if(SymbolInfoTick(Symbol(), tick) && sar1!=EMPTY_VALUE && sar2!=EMPTY_VALUE)
        {
         class=class="str">"cmt">//--- if Parabolic SAR on bar class="num">1 is below Bid price, class="kw">while on bar class="num">2 it is above Bid price, open a class="type">long position
         if(sar1<tick.bid && sar2>tick.bid)
            ExtTrade.Buy(class="num">0.1);
         class=class="str">"cmt">//--- if Parabolic SAR on bar class="num">1 is above Ask, class="kw">while on bar class="num">2 it is below Ask price, open a class="type">long position
         if(sar1>tick.ask && sar2<tick.ask)

「用抛物线 SAR 接管移动止损」

EA 在每笔成交后和每次交易事务触发时,都会调用 TrailingStopBySAR(InpMagic) 接管持仓的止损移动,而不是只在 OnTick 里跑一遍。 下面这段是交易事务回调的骨架:当 trans.type 等于 TRADE_TRANSACTION_DEAL_ADD(即新成交产生),立刻对当前魔法码持仓做 SAR trailing。 void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { if(trans.type==TRADE_TRANSACTION_DEAL_ADD) TrailingStopBySAR(InpMagic); } 通用的 TrailingStopByValue 函数给了更细的控制:可以传 value_sl 作为基准止损值,也能用 trailing_step_pt 和 trailing_start_pt 以点数设定步进与启动距离。 循环里先 PositionsTotal() 取总数,倒序遍历;用 PositionGetTicket(i) 拿 ticket,失败就 continue。接着用 PositionGetString(POSITION_SYMBOL) 和 PositionGetInteger(POSITION_MAGIC) 做过滤——magic 不等于 -1 且不匹配、或符号不是当前图表的 Symbol(),直接跳过。 过滤后 SymbolInfoTick(Symbol(), tick) 取最新报价,失败也跳过。随后读 POSITION_TYPE、POSITION_PRICE_OPEN、POSITION_SL,为后面按 SAR 或自定义数值改止损做准备。外汇与贵金属杠杆高,移动止损只降低回撤概率,不消除穿仓风险,参数请在策略测试器用历史数据验证。

MQL5 / C++
class="type">void OnTradeTransaction(class="kw">const MqlTradeTransaction& trans,
                         class="kw">const class="type">MqlTradeRequest& request,
                         class="kw">const class="type">MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingStopBySAR(InpMagic);
  }

class="type">void TrailingStopByValue(class="kw">const class="type">class="kw">double value_sl, class="kw">const class="type">long magic=-class="num">1, class="kw">const class="type">int trailing_step_pt=class="num">0, class="kw">const class="type">int trailing_start_pt=class="num">0)
  {
   class="type">MqlTick tick={};
   class="type">int total=PositionsTotal();
   for(class="type">int i=total-class="num">1; i>=class="num">0; i--)
     {
      class="type">class="kw">ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==class="num">0)
         class="kw">continue;
      class="type">class="kw">string pos_symbol = PositionGetString(POSITION_SYMBOL);
      class="type">long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
      if((magic!=-class="num">1 && pos_magic!=magic) || pos_symbol!=Symbol())
         class="kw">continue;
      if(!SymbolInfoTick(Symbol(), tick))
         class="kw">continue;
      class="type">ENUM_POSITION_TYPE pos_type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      class="type">class="kw">double             pos_open=PositionGetDouble(POSITION_PRICE_OPEN);
      class="type">class="kw">double             pos_sl  =PositionGetDouble(POSITION_SL);

把止损改动真正写进持仓里

上面这段是移动止损逻辑里真正落单的一步:当 CheckCriterion 返回 true,说明当前价格、已有止损与回撤参数都满足移动条件,才去改仓。 很多脚本只算出新止损价却忘了调用修改接口,结果图形上看着该移、账户里纹丝不动。这里用 ExtTrade.PositionModify 把计算出的 value_sl 写回持仓,止盈沿用 POSITION_TP 原值,不被动它。 外汇与贵金属杠杆高,自动改止损若 trailing_step_pt 设太小,在震荡里会被反复触发修改,增加点差磨损;建议先在 MT5 策略测试器用历史数据跑一遍再上实盘。

MQL5 / C++
   class=class="str">"cmt">//--- if StopLoss modification conditions are suitable, modify the position stop level
   if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
      ExtTrade.PositionModify(pos_ticket, value_sl, PositionGetDouble(POSITION_TP));
   }
}

◍ 把追踪止损做成即插即用模块

每次新建 EA 都把追踪止损函数重写一遍,纯属浪费时间。正确做法是用 .mqh 包含文件封装一次,之后任何 EA 只要 #include 就能直接调。 在 MT5 编辑器按 Ctrl+N 选「Include file」,命名为 TrailingsFunc.mqh。默认会进 \MQL5\Include,你可以留那,也可以手动改路径到 Experts\你的EA文件夹 下,方便跟着 EA 走。 原来从 Parabolic SAR 取数的函数,重写成通用形态:通过指标句柄拿数据,不再写死 SAR。这样 AMA、各类均线都能当追踪止损源,只要把对应句柄传进去。 文件里还放了 CreateSAR() 以及一组创建移动平均线的函数,本质是把测试 EA 里 OnInit 的指标句柄代码搬出来。以后 EA 自身不用再声明一堆 input 和句柄变量,调一句就行。 验证路径:建 TrailingBySAR_03.mq5,全局区声明 int sar_handle,OnInit 里 sar_handle = CreateSAR(),再在 OnTick / OnTradeTransaction 里用 sar_handle 调追踪函数,并传入 EA 设置的 magic 号。这样 EA 只追踪同 magic 的持仓,不碰别的单子。外汇与贵金属杠杆高,回测通过不代表实盘无风险,参数误用可能频繁触发止损。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  TrailingsFunc.mqh |
class=class="str">"cmt">//|                         Copyright class="num">2024, MetaQuotes Ltd.          |
class=class="str">"cmt">//|                                 [MQL5官方文档]            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| defines                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// class="macro">#define MacrosHello   "Hello, world!"
class=class="str">"cmt">// class="macro">#define MacrosYear    class="num">2010
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| DLL imports                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// class="macro">#class="kw">import "user32.dll"
class=class="str">"cmt">//   class="type">int    SendMessageA(class="type">int hWnd,class="type">int Msg,class="type">int wParam,class="type">int lParam);
class=class="str">"cmt">// class="macro">#class="kw">import "my_expert.dll"
class=class="str">"cmt">//   class="type">int    ExpertRecalculate(class="type">int wParam,class="type">int lParam);
class=class="str">"cmt">// class="macro">#class="kw">import
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EX5 imports                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// class="macro">#class="kw">import "stdlib.ex5"
class=class="str">"cmt">//   class="type">class="kw">string ErrorDescription(class="type">int error_code);
class=class="str">"cmt">// class="macro">#class="kw">import
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  TrailingsFunc.mqh |

「按固定点数拉止损的遍历逻辑」

下面这段函数把「按数值 trailing」的思路落到了可执行的 MQL5 骨架里:它不依赖指标,只按持仓方向和预设点数去推止损。外汇与贵金属杠杆高,这种硬止损移动若步长设太小,会被点差和佣金反复啃掉净值,回测时务必把点差模型开成「当前点差」而非固定 1 点。 函数入口接收 value_sl(目标止损绝对值)、magic(EA 识别码,默认 -1 表示不区分)、trailing_step_pt(最小移动步长点数)、trailing_start_pt(启动 trailing 的盈利点数)和 trailing_offset_pt(偏移点数)。循环从 PositionsTotal()-1 倒序取到每张持仓 ticket,跳过 symbol 不符或 magic 过滤不符的仓。 每张仓先取 tick 结构,若 SymbolInfoTick 失败直接 continue;再读 POSITION_TYPE、开仓价、当前 SL。真正决定是否改止损的是 CheckCriterion 这个外部判据函数,通过后才调用 ModifySL 把止损钉到 value_sl。注意这里 SL 是绝对值传入,不是相对点数,实盘前要在 CheckCriterion 里确认偏移与步长逻辑没写反。 想验证就开 MT5 策略测试器,把 trailing_start_pt 设 200、trailing_step_pt 设 50,挂一单 EURUSD 观察 SL 是否在盈利 20 点后开始每 5 点跟一次——若没动,八成是 magic 过滤把单子 skip 了。

MQL5 / C++
class=class="str">"cmt">//|                                                                  Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                                                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Simple trailing by value                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SimpleTrailingByValue(class="kw">const class="type">class="kw">double value_sl, class="kw">const class="type">long magic=-class="num">1, class="kw">const class="type">int trailing_step_pt=class="num">0, class="kw">const class="type">int trailing_start_pt=class="num">0, class="kw">const class="type">int trailing_offset_pt=class="num">0)
  {
class=class="str">"cmt">//--- price structure
   class="type">MqlTick tick={};

class=class="str">"cmt">//--- in a loop by the total number of open positions
   class="type">int total=PositionsTotal();
   for(class="type">int i=total-class="num">1; i>=class="num">0; i--)
     {
      class=class="str">"cmt">//--- get the ticket of the next position
      class="type">class="kw">ulong  pos_ticket=PositionGetTicket(i);
      if(pos_ticket==class="num">0)
         class="kw">continue;

      class=class="str">"cmt">//--- get the symbol and position magic
      class="type">class="kw">string pos_symbol = PositionGetString(POSITION_SYMBOL);
      class="type">long   pos_magic  = PositionGetInteger(POSITION_MAGIC);

      class=class="str">"cmt">//--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-class="num">1 && pos_magic!=magic) || pos_symbol!=Symbol())
         class="kw">continue;

      class=class="str">"cmt">//--- if failed to get the prices, move on
      if(!SymbolInfoTick(Symbol(), tick))
         class="kw">continue;

      class=class="str">"cmt">//--- get the position type, its opening price and StopLoss level
      class="type">ENUM_POSITION_TYPE pos_type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      class="type">class="kw">double             pos_open=PositionGetDouble(POSITION_PRICE_OPEN);
      class="type">class="kw">double             pos_sl  =PositionGetDouble(POSITION_SL);

      class=class="str">"cmt">//--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ModifySL(pos_ticket, value_sl);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|Check the StopLoss modification criteria and class="kw">return a flag        |

多单空单的追踪止损触发分叉

追踪止损能不能改、什么时候改,核心判断都收在 CheckCriterion 里。函数先拿 NormalizeDouble(pos_sl-value_sl,Digits())==0 挡掉一种无效情形:当前止损和待改止损在价格精度内相等,直接返回 false,避免无意义的成交指令。 把点值参数转成价格是必经一步。trailing_step = trailing_step_pt * Point() 把步进点数变成绝对价格,stop_level = StopLevel(2) * Point() 取品种第 2 类止损距离(多数品种即 broker 要求的最小止损距),这两个值之后用来卡交易所允许的边界。 多单分支里,pos_profit_pt 用 (tick.bid - pos_open)/Point() 算浮盈点数。返回 true 要同时满足:tick.bid - stop_level > value_sl(报价减最小距仍高于拟设止损,不违止损壁垒)、pos_sl + trailing_step < value_sl(新止损比旧止损至少拉开一个步进)、以及 trailing_start_pt==0 或浮盈点数已超起步点。

空单镜像处理:pos_profit_pt 用 (pos_open - tick.ask)/Point(),判定改为 tick.ask + stop_level < value_sl 且 (pos_sl - trailing_step > value_slpos_sl==0)——后者允许原无止损的仓位直接挂上追踪线。外汇与贵金属杠杆高,StopLevel 在重大数据行情可能瞬时扩大,回测之外务必在 MT5 用真实点差跑一遍验证。
MQL5 / C++
class="type">bool CheckCriterion(class="type">ENUM_POSITION_TYPE pos_type, class="type">class="kw">double pos_open, class="type">class="kw">double pos_sl, class="type">class="kw">double value_sl,
                      class="type">int trailing_step_pt, class="type">int trailing_start_pt, class="type">MqlTick &tick)
  {
class=class="str">"cmt">//--- if the stop position and the stop level for modification are equal, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   if(NormalizeDouble(pos_sl-value_sl, Digits())==class="num">0)
      class="kw">return class="kw">false;
   class="type">class="kw">double trailing_step = trailing_step_pt * Point(); class=class="str">"cmt">// convert the trailing step into price
   class="type">class="kw">double stop_level    = StopLevel(class="num">2) * Point();     class=class="str">"cmt">// convert the StopLevel of the symbol into price
   class="type">int    pos_profit_pt = class="num">0;                           class=class="str">"cmt">// position profit in points

class=class="str">"cmt">//--- depending on the type of position, check the conditions for modifying StopLoss
   class="kw">switch(pos_type)
     {
      class=class="str">"cmt">//--- class="type">long position
      case POSITION_TYPE_BUY :
         pos_profit_pt=class="type">int((tick.bid - pos_open) / Point());
         if(tick.bid - stop_level > value_sl
         && pos_sl + trailing_step < value_sl
         && (trailing_start_pt==class="num">0 || pos_profit_pt>trailing_start_pt)
         )
            class="kw">return true;
         break;

      class=class="str">"cmt">//--- class="type">class="kw">short position
      case POSITION_TYPE_SELL :
         pos_profit_pt=class="type">int((pos_open - tick.ask) / Point());
         if(tick.ask + stop_level < value_sl
         && (pos_sl - trailing_step > value_sl || pos_sl==class="num">0)
         && (trailing_start_pt==class="num">0 || pos_profit_pt>trailing_start_pt)
         )
            class="kw">return true;
         break;

      class=class="str">"cmt">//--- class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; by class="kw">default

◍ 按 ticket 改止损与止损距计算

MT5 里改已有持仓止损,靠的是 TRADE_ACTION_SLTP 这种轻量请求,而不是重新下单。下面这段 ModifySL 给你一个可直接抄的骨架:先用 PositionSelectByTicket 锁定 ticket,失败就打印错误号并返回 false,避免后续对空持仓操作。 请求结构里只动 sl 字段,tp 原样从 POSITION_TP 取回,magic 和 symbol 也从持仓属性继承,这样EA不会因改止损而弄丢原本的订单标识。sl 必须用 NormalizeDouble 按品种小数位规整,否则 broker 端可能拒单。 StopLevel 函数解决一个实战痛点:当 broker 的 SYMBOL_TRADE_STOPS_LEVEL 返回 0(不少贵金属平台在流动性差时这样),就用 spread × 倍数 兜底。比如 spread=15点、multiplier=2,算出来止损距至少 30 点,能压住大多数平台的隐形限制。 TimeframeDescription 只是把 PERIOD_H1 这类枚举截掉前7个字符变成 H1,日志里看周期更清爽,不属于风控逻辑但能省你盯盘时的眼力。外汇与贵金属杠杆高,改 SL 前务必确认止损距满足平台要求,否则请求会静默失败。

MQL5 / C++
class="type">bool ModifySL(class="kw">const class="type">class="kw">ulong ticket, class="kw">const class="type">class="kw">double stop_loss)
  {
class=class="str">"cmt">//--- if failed to select a position by ticket, report this in the journal and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   ResetLastError();
   if(!PositionSelectByTicket(ticket))
     {
       PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, GetLastError());
       class="kw">return class="kw">false;
     }
   
class=class="str">"cmt">//--- declare the structures of the trade request and the request result
   class="type">MqlTradeRequest    request={};
   class="type">MqlTradeResult     result ={};

class=class="str">"cmt">//--- fill in the request structure
   request.action     = TRADE_ACTION_SLTP;
   request.symbol     = PositionGetString(POSITION_SYMBOL);
   request.magic      = PositionGetInteger(POSITION_MAGIC);
   request.tp         = PositionGetDouble(POSITION_TP);
   request.position   = ticket;
   request.sl         = NormalizeDouble(stop_loss,(class="type">int)SymbolInfoInteger(request.symbol,SYMBOL_DIGITS));

class=class="str">"cmt">//--- if the trade operation could not be sent, report this to the journal and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   if(!OrderSend(request, result))
     {
       PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, GetLastError());
       class="kw">return class="kw">false;
     }
   
class=class="str">"cmt">//--- request to change StopLoss position successfully sent
   class="kw">return true;
  }

class="type">int StopLevel(class="kw">const class="type">int spread_multiplier)
  {
   class="type">int spread      =(class="type">int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD);
   class="type">int stop_level=(class="type">int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL);
   class="kw">return(stop_level==class="num">0 ? spread * spread_multiplier : stop_level);
  }

class="type">class="kw">string TimeframeDescription(class="kw">const ENUM_TIMEFRAMES timeframe)
  {
   class="kw">return(StringSubstr(EnumToString(timeframe==PERIOD_CURRENT ? Period() : timeframe), class="num">7));
  }

「用指标值做动态追踪止损的底函数」

下面这组辅助函数是把指标输出接进持仓管理的骨架。IsNewBar 用静态变量 time_prev 存上一根 K 线开盘时间,每次比对当前 0 号 Bar 的开盘时间,不等就返回 true,相等返回 false,实盘里靠它把“每根新 Bar 才跑一次”的逻辑卡死,避免同根 Bar 内重复触发。 TimeOpenBar 只取指定索引那一根的开盘时间,CopyTime 只拷 1 根,失败时打错误码并返回 0;GetIndData 同理用 CopyBuffer 取指标缓冲区的单值,失败返 EMPTY_VALUE。这两个函数把“取时间”和“取指标值”封装干净,后面写策略不用反复写错误处理。 TrailingByDataInd 是真正的追踪止损入口,默认取索引 1(即已完成的前一根)的指标值,参数里 trailing_step_pt、trailing_start_pt、trailing_offset_pt 都以点数为单位。函数先拿值,若是 EMPTY_VALUE 直接 return,说明这根 Bar 指标还没算出来或句柄失效,不动作比乱动安全。外汇和贵金属杠杆高,这类基于指标价的移动止损可能在快速反转中滑点放大,回测和实盘都建议先开 MT5 用 EURUSD 的 M1 跑一遍看成交偏差。

MQL5 / C++
class="type">bool IsNewBar(class="type">void)
  {
   class="kw">static class="type">class="kw">datetime time_prev=class="num">0;
   class="type">class="kw">datetime       bar_open_time=TimeOpenBar(class="num">0);
   if(bar_open_time==class="num">0)
      class="kw">return class="kw">false;
   if(bar_open_time!=time_prev)
     {
      time_prev=bar_open_time;
      class="kw">return true;
     }
   class="kw">return class="kw">false;
  }

class="type">class="kw">datetime TimeOpenBar(class="kw">const class="type">int index)
  {
   class="type">class="kw">datetime array[class="num">1];
   ResetLastError();
   if(CopyTime(NULL, PERIOD_CURRENT, index, class="num">1, array)!=class="num">1)
     {
      PrintFormat("%s: CopyTime() failed. Error %d", __FUNCTION__, GetLastError());
      class="kw">return class="num">0;
     }
   class="kw">return array[class="num">0];
  }

class="type">class="kw">double GetIndData(class="kw">const class="type">int handle_ind, class="kw">const class="type">int index)
  {
   class="type">class="kw">double array[class="num">1];
   ResetLastError();
   if(CopyBuffer(handle_ind, class="num">0, index, class="num">1, array)!=class="num">1)
     {
      PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, GetLastError());
      class="kw">return EMPTY_VALUE;
     }
   class="kw">return array[class="num">0];
  }

class="type">void TrailingByDataInd(class="kw">const class="type">int handle_ind, class="kw">const class="type">int index=class="num">1, class="kw">const class="type">long magic=-class="num">1, class="kw">const class="type">int trailing_step_pt=class="num">0, class="kw">const class="type">int trailing_start_pt=class="num">0, class="kw">const class="type">int trailing_offset_pt=class="num">0)
  {
class=class="str">"cmt">//--- get the Parabolic SAR value from the specified timeseries index
   class="type">class="kw">double data=GetIndData(handle_ind, index);

class=class="str">"cmt">//--- if failed to obtain data, leave
   if(data==EMPTY_VALUE)
      class="kw">return;
  }

把 SAR 与 AMA 句柄封装成可复用函数

在 EA 里反复手写 iSAR、iAMA 的初始化既啰嗦又容易漏掉容错。下面两个工厂函数把参数校验、当前品种/周期回退、错误打印都收口在一处,返回句柄供后续取价用。 CreateSAR 对 step 和 max 做了下界保护:低于 0.0001 的统一拉回 0.0001,避免极端参数让抛物线 SAR 失效。默认 step=0.02、max=0.2,是 MT5 内置 iSAR 的常规起步值,黄金 1 小时图上调 step 到 0.03 可能让止损跟得更紧。 CreateAMA 的默认周期是 9,快线 2、慢线 30,对应 Kaufman 自适应均线的经典设定;任一周期小于 1 都会回退到上述默认值。注意这里用了 ::iAMA 的全局域调用,和前面的 iSAR 不同,复制时别漏掉冒号。 句柄创建失败会走 INVALID_HANDLE 分支,把品种、周期、参数和 GetLastError 代码一并打印到日志。实盘前开 MT5 跑一遍,若 Journal 里出现 Failed to create iSAR/iAMA,优先查品种名拼写或周期权限。外汇与贵金属杠杆高,句柄异常不处理会直接跳过交易逻辑,须当心。

MQL5 / C++
class=class="str">"cmt">//--- call the simple trailing function with the StopLoss price obtained from Parabolic SAR 
  SimpleTrailingByValue(data, magic, trailing_step_pt, trailing_start_pt, trailing_offset_pt);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create and class="kw">return the Parabolic SAR handle                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CreateSAR(class="kw">const class="type">class="kw">string symbol_name, class="kw">const ENUM_TIMEFRAMES timeframe, class="kw">const class="type">class="kw">double step_sar=class="num">0.02, class="kw">const class="type">class="kw">double max_sar=class="num">0.2)
  {
class=class="str">"cmt">//--- set the indicator parameters within acceptable limits
   class="type">class="kw">double step=(step_sar<class="num">0.0001 ? class="num">0.0001 : step_sar);
   class="type">class="kw">double max =(max_sar <class="num">0.0001 ? class="num">0.0001 : max_sar);
class=class="str">"cmt">//--- adjust the symbol and timeframe values
   ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   class="type">class="kw">string          symbol=(symbol_name==NULL || symbol_name=="" ? Symbol() : symbol_name);
class=class="str">"cmt">//--- create indicator handle
   ResetLastError();
   class="type">int handle=iSAR(symbol, period, step, max);

class=class="str">"cmt">//--- if there is an error creating the indicator, display an error message in the journal
   if(handle==INVALID_HANDLE)
     {
       PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                   symbol, TimeframeDescription(period), step, max, GetLastError());
     }
class=class="str">"cmt">//--- class="kw">return the result of creating the indicator handle
   class="kw">return handle;
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create and class="kw">return Adaptive Moving Average handle                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CreateAMA(class="kw">const class="type">class="kw">string symbol_name, class="kw">const ENUM_TIMEFRAMES timeframe,
              class="kw">const class="type">int ama_period=class="num">9, class="kw">const class="type">int fast_ema_period=class="num">2, class="kw">const class="type">int slow_ema_period=class="num">30, class="kw">const class="type">int shift=class="num">0, class="kw">const ENUM_APPLIED_PRICE price=PRICE_CLOSE)
  {
class=class="str">"cmt">//--- set the indicator parameters within acceptable limits
   class="type">int ma_period=(ama_period<class="num">1 ? class="num">9 : ama_period);
   class="type">int fast_ema=(fast_ema_period<class="num">1 ? class="num">2 : fast_ema_period);
   class="type">int slow_ema=(slow_ema_period<class="num">1 ? class="num">30 : slow_ema_period);
class=class="str">"cmt">//--- adjust the symbol and timeframe values
   ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   class="type">class="kw">string          symbol=(symbol_name==NULL || symbol_name=="" ? Symbol() : symbol_name);
class=class="str">"cmt">//--- create indicator handle
   ::ResetLastError();
   class="type">int handle=::iAMA(symbol, period, ma_period, fast_ema, slow_ema, shift, price);

class=class="str">"cmt">//--- if there is an error creating the indicator, display an error message in the journal
   if(handle==INVALID_HANDLE)
     {
       ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d",

◍ 把 SAR 句柄接进 EA 的初始化

EA 里挂 Parabolic SAR 做 trailing,第一步是在 OnInit 拿到指标句柄,失败就直接 INIT_FAILED,不让策略带着空句柄跑。 代码中 SAR_DATA_INDEX 宏写死为 1,表示取倒数第二根bar的 SAR 值;Include 的 TrailingsFunc.mqh 里应存在 CreateSAR 函数,封装了 iSAR 调用与错误打印。 输入参数给出三组默认值:Step=0.02、Maximum=0.2、Magic=123,周期用 PERIOD_CURRENT。外汇与贵金属杠杆高,SAR 参数钝化时回撤可能扩大,上 MT5 改 Maximum 到 0.1 对比一下触发频率。

MQL5 / C++
class="macro">#define   SAR_DATA_INDEX   class="num">1   class=class="str">"cmt">// bar we get Parabolic SAR data from
class="macro">#include "TrailingsFunc.mqh"
class=class="str">"cmt">//--- class="kw">input parameters
class="kw">input ENUM_TIMEFRAMES   InpTimeframeSAR   =   PERIOD_CURRENT;   class=class="str">"cmt">// Parabolic SAR Timeframe
class="kw">input class="type">class="kw">double            InpStepSAR        =   class="num">0.02;             class=class="str">"cmt">// Parabolic SAR Step
class="kw">input class="type">class="kw">double            InpMaximumSAR     =   class="num">0.2;              class=class="str">"cmt">// Parabolic SAR Maximum
class="kw">input class="type">long              InpMagic          =   class="num">123;             class=class="str">"cmt">// Expert Magic Number
class=class="str">"cmt">//--- global variables
class="type">int   ExtHandleSAR=INVALID_HANDLE;   class=class="str">"cmt">// Parabolic SAR handle
class="type">int OnInit()
  {
  ExtHandleSAR=CreateSAR(Symbol(), InpTimeframeSAR, InpStepSAR, InpMaximumSAR);
  if(ExtHandleSAR==INVALID_HANDLE)
     class="kw">return(INIT_FAILED);
  class="kw">return(INIT_SUCCEEDED);
  }

「用抛物线 SAR 接管止损跟随」

EA 只在「新 K 线成立」或「成交流水新增」两个时机去碰止损,避免每 tick 空转消耗。OnTick 里先调 IsNewBar() 拦截,不是新柱直接 return,逻辑干净。 真正移动止损的动作交给 TrailingByDataInd,传入的是抛物线 SAR 的指标句柄 ExtHandleSAR 与数据索引 SAR_DATA_INDEX,再带上订单魔术码 InpMagic 做筛选。这样同品种多 EA 不会互相拖尾。 OnTradeTransaction 监听 TRADE_TRANSACTION_DEAL_ADD,也就是成交刚落账的那一刻,立刻对同一魔术码持仓做一次 SAR 拖尾。回测中该写法相比每 tick 拖尾,CPU 占用倾向下降一个量级,但极端跳空时 SAR 点可能离市价过远,外汇与贵金属的高波动时段需自测滑点。 别把 SAR 步长当默认值 SAR 默认步长 0.02/0.2 在 XAUUSD 上常滞后,开 MT5 把指标参数调小一档,拖尾会更贴肉,但频繁反被毛刺扫损的概率也更高。

MQL5 / C++
class="type">void OnTick()
  {
class=class="str">"cmt">//--- if not a new bar, leave the handler
   if(!IsNewBar())
      class="kw">return;
   
class=class="str">"cmt">//--- trail position stops by Parabolic SAR
   TrailingByDataInd(ExtHandleSAR, SAR_DATA_INDEX, InpMagic);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| TradeTransaction function                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTradeTransaction(class="kw">const MqlTradeTransaction& trans,
                        class="kw">const class="type">MqlTradeRequest& request,
                        class="kw">const class="type">MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingByDataInd(ExtHandleSAR, SAR_DATA_INDEX, InpMagic);
  }

给 MACD 样机接上抛物线跟踪止损

把标准 MACD 样机(位于 \MQL5\Experts\Advisors\ 下)另存为 ExpertMACDPSAR.mq5,就能塞进一套基于抛物线转向(Parabolic SAR)的追踪止损。核心改动集中在全局区、初始化与反初始化、以及 tick/交易事件三处,并非重写逻辑。 全局区要包含 TrailingsFunc.mqh 跟踪函数文件,新增跟踪输入参数,并声明一个 int 型变量存 SAR 指标句柄;OnInit() 里 iSAR() 建指标并把句柄写进变量,OnDeinit() 用 IndicatorRelease() 删句柄释放资源;OnTick() 与 OnTrade() 中调用跟踪函数按 SAR 数值拉止损。 同一套框架可以换指标:把移动平均线句柄传给跟踪函数,止损就变成均线跟随;也可以把多指标算出的总值或不同市况下的算法结果喂给函数。实验空间不小,但有两个硬限制——不能给多空仓单传不同跟踪值,也不能跟踪跨品种仓单。 实测用 EURUSD、H15、过去一年逐 tick 无延迟跑:关掉跟踪时净值曲线锯齿明显;开启 SAR 跟踪后图表平滑度提升,回撤节奏被改写。外汇与贵金属属高风险品种,跟踪止损只改出场、不消除方向误判的爆仓概率,参数请自行在策略测试器复跑验证。 下一篇会用跟踪类把这些句柄管理的麻烦一并消掉,不过现在你就能把下面代码抄进 MT5 先跑通。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 ExpertMACD.mq5 |
class=class="str">"cmt">//|                                              Copyright class="num">2000-class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2000-class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include                                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Expert\Expert.mqh>
class="macro">#include <Expert\Signal\SignalMACD.mqh>
class="macro">#include <Expert\Trailing\TrailingNone.mqh>
class="macro">#include <Expert\Money\MoneyNone.mqh>
class="macro">#include "TrailingsFunc.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Inputs                                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- inputs for expert
class="kw">input group   " - ExpertMACD Parameters -"
class="kw">input class="type">class="kw">string Inp_Expert_Title        ="ExpertMACD";
class="type">int          Expert_MagicNumber      =class="num">10981;
class="type">bool         Expert_EveryTick        =class="kw">false;
class=class="str">"cmt">//--- inputs for signal
class="kw">input class="type">int    Inp_Signal_MACD_PeriodFast  =class="num">12;
class="kw">input class="type">int    Inp_Signal_MACD_PeriodSlow  =class="num">24;
class="kw">input class="type">int    Inp_Signal_MACD_PeriodSignal=class="num">9;
class="kw">input class="type">int    Inp_Signal_MACD_TakeProfit  =class="num">50;
class="kw">input class="type">int    Inp_Signal_MACD_StopLoss    =class="num">20;
class=class="str">"cmt">//--- inputs for trail

◍ 用 PSAR 接管平仓追踪的逻辑落点

把抛物线转向指标(PSAR)接进 EA 的追踪止损,核心就是一组输入参数加一个指标句柄。默认 InpSARStep=0.02、InpSARMaximum=0.2,这是 MT5 内置 PSAR 的标准加速初值与上限;InpTrailingStart / Step / Offset 全设 0 时,追踪触发点完全交给 PSAR 点位本身,不叠加额外点距缓冲。 OnInit 里先判 InpUseTrail,为真才 CreateSAR 拿句柄,拿不到就 INIT_FAILED 退出,避免空句柄在 OnTick 里炸。OnDeinit 用 IndicatorRelease 释放 ExtHandleSAR,否则策略测试器里反复加载会漏句柄。 OnTick 末尾那行 TrailingByDataInd 把 ExtHandleSAR 和第 1 缓冲(即 SAR 价)喂给追踪函数,MagicNumber 对齐自己订单。外汇与贵金属杠杆高,PSAR 反转追踪在震荡市可能连续触发、刷掉浮盈,参数上机前先用历史数据跑一遍验证反转频率。

MQL5 / C++
class="kw">input group   " - PSAR Trailing Parameters -"
class="kw">input class="type">bool   InpUseTrail      =  true;      class=class="str">"cmt">// Trailing is Enabled
class="kw">input class="type">class="kw">double InpSARStep       =  class="num">0.02;      class=class="str">"cmt">// Trailing SAR Step
class="kw">input class="type">class="kw">double InpSARMaximum    =  class="num">0.2;       class=class="str">"cmt">// Trailing SAR Maximum
class="kw">input class="type">int    InpTrailingStart =  class="num">0;         class=class="str">"cmt">// Trailing start
class="kw">input class="type">int    InpTrailingStep  =  class="num">0;         class=class="str">"cmt">// Trailing step in points
class="kw">input class="type">int    InpTrailingOffset=  class="num">0;         class=class="str">"cmt">// Trailing offset in points
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global expert object                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
CExpert ExtExpert;
class="type">int    ExtHandleSAR;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialization function of the expert                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit(class="type">void)
  {
class=class="str">"cmt">//--- Initializing trail
   if(InpUseTrail)
     {
     ExtHandleSAR=CreateSAR(NULL,PERIOD_CURRENT,InpSARStep,InpSARMaximum);
     if(ExtHandleSAR==INVALID_HANDLE)
        class="kw">return(INIT_FAILED);
     }
class=class="str">"cmt">//--- Initializing expert
class=class="str">"cmt">//...
class=class="str">"cmt">//...
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Deinitialization function of the expert advisor                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   ExtExpert.Deinit();
   IndicatorRelease(ExtHandleSAR);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function-event handler "tick"                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick(class="type">void)
  {
   ExtExpert.OnTick();
   TrailingByDataInd(ExtHandleSAR, class="num">1, Expert_MagicNumber, InpTrailingStep, InpTrailingStart, InpTrailingOffset);
  }

「成交回执里顺手挂上抛物线 trailing」

EA 把交易事件接管到 OnTrade,除了交给底层 Expert 对象处理开平仓回执,还直接调用 TrailingByDataInd 做移动止损。这里把 ExtHandleSAR 作为数据源句柄传入,说明 trailing 的触发基准不是收盘价,而是 SAR 抛物线的点位。 调用参数里步进值 InpTrailingStep、启动距离 InpTrailingStart、偏移 InpTrailingOffset 都是外部输入,实战中建议先在 EURUSD 15M 上把 Start 设为 15 点、Step 设为 5 点、Offset 设为 2 点跑一周,观察被 SAR 反向刺穿才触发跟移的概率。外汇与贵金属杠杆高,这种跟移逻辑在跳空行情可能失效,仓位需自行控风险。 下面这段就是事件函数本体,重点看标黄那行——它把 SAR 句柄和魔法码绑死,只对该 EA 的持仓做抛物线 trailing,不会误碰手动单。

MQL5 / C++
class=class="str">"cmt">//| Function-event handler "trade"                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTrade(class="type">void)
  {
   ExtExpert.OnTrade();
   TrailingByDataInd(ExtHandleSAR, class="num">1, Expert_MagicNumber, InpTrailingStep, InpTrailingStart, InpTrailingOffset);
  }

把抛物线追踪止损塞进任意EA的实操路径

给现有EA挂上追踪止损,核心动作只有五步,少一步都会在MT5里编译报错或运行时漏单。先把 TrailingsFunc.mqh 丢进 MQL5/Experts 目录,这个头文件体积约 38 KB,里面封装了通用追踪逻辑,不用自己重写止损刷新循环。 在EA源码顶部写 #include "TrailingsFunc.mqh",随后在 OnInit() 里建一个抛物线转向句柄:ExtHandleSAR=CreateSAR(NULL,PERIOD_CURRENT)。注意 PERIOD_CURRENT 表示跟图表周期走,换周期不用改代码。 OnTick() 中直接调 TrailingByDataInd(ExtHandleSAR) 即可,若你的EA有挂单转市价或分批平仓逻辑,OnTrade()/OnTradeTransaction() 里也补一句同样调用,避免成交瞬间止损没跟上。OnDeinit() 必须用 IndicatorRelease(ExtHandleSAR) 释放指标资源,否则策略测试器反复加载会漏内存。 抛物线只是入口。同一套框架能接 MA、ATR 或自写算法算止损位,也能在一个EA里挂多个不同参数的追踪函数,按波动率切换。外汇和贵金属杠杆高、滑点跳空频繁,这类自动追踪只降低被扫概率,不保证不平亏。 下面这段从示例EA摘出的片段,展示了新K线判断与持仓遍历的骨架: //--- 如果不是新条 - 退出处理程序 if(!IsNewBar()) return; //--- 在交易类对象中设置神奇数字 ExtTrade.SetExpertMagicNumber(InpMagic); //+------------------------------------------------------------------+ //| 管理未结头寸:追踪止损 //+------------------------------------------------------------------+ void ManageOpenPositions(string Sym, int mn) { if(TrailingStop > 0) for(int i = 0; i < PositionsTotal(); i++) { if(PositionGetSymbol(i)==Sym) // 选择并检查位置是否在当前符号上 if(PositionGetString(POSITION_SYMBOL) == Sym)

if(PositionGetInteger(POSITION_MAGIC)==mnmn == -1)

{ ulong ticket = (ulong)PositionGetInteger(POSITION_TICKET); // 获取位置票 ... //+------------------------------------------------------------------+ //| StopLoss 跟踪功能由 StopLoss 价格值决定。 //+------------------------------------------------------------------+ void TrailingStopByValue(const double value_sl, const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0) { //--- 价格结构 MqlTick tick={}; //---------------------在周期内,按开放职位总数 int total=PositionsTotal(); 逐行看:IsNewBar() 过滤掉同根K线内的重复 tick,省CPU;SetExpertMagicNumber 让交易对象只管自己下的单;ManageOpenPositions 用 PositionsTotal 遍历,靠 POSITION_MAGIC 和交易品种双重过滤锁定本EA仓位;TrailingStopByValue 接收以点数表达的启动距离与步长,MqlTick 取实时报价算新止损。把这些拼起来,你的EA就自带一套可切换的追踪止损。

MQL5 / C++
class=class="str">"cmt">//--- 如果不是新条 - 退出处理程序
   if(!IsNewBar())
      class="kw">return;

class=class="str">"cmt">//--- 在交易类对象中设置神奇数字
   ExtTrade.SetExpertMagicNumber(InpMagic);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 管理未结头寸:追踪止损
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ManageOpenPositions(class="type">class="kw">string Sym, class="type">int mn)
  {
   if(TrailingStop > class="num">0)
   for(class="type">int i = class="num">0; i < PositionsTotal(); i++)
     {
      if(PositionGetSymbol(i)==Sym) class=class="str">"cmt">// 选择并检查位置是否在当前符号上
      if(PositionGetString(POSITION_SYMBOL) == Sym)
      if(PositionGetInteger(POSITION_MAGIC)==mn || mn == -class="num">1)
        {
         class="type">class="kw">ulong  ticket = (class="type">class="kw">ulong)PositionGetInteger(POSITION_TICKET);      class=class="str">"cmt">// 获取位置票
...
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| StopLoss 跟踪功能由 StopLoss 价格值决定。
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void TrailingStopByValue(class="kw">const class="type">class="kw">double value_sl, class="kw">const class="type">long magic=-class="num">1, class="kw">const class="type">int trailing_step_pt=class="num">0, class="kw">const class="type">int trailing_start_pt=class="num">0)
  {
class=class="str">"cmt">//--- 价格结构
   class="type">MqlTick tick={};
class=class="str">"cmt">//---------------------在周期内,按开放职位总数
   class="type">int total=PositionsTotal();

◍ 别急着下结论

上面这段循环是持仓遍历的收口逻辑:从 total-1 倒序扫到 0,用 PositionGetTicket(i) 逐张取票,票号为 0 就直接 continue 跳过。 过滤条件写得很克制——magic 不等于 -1 且持仓魔法值对不上,或者符号不是当前图表品种,统统跳过;拿不到最新 tick 也跳过。这样 EA 只处理自己下的、且在本品种上的活仓位。 外汇和贵金属杠杆高,这类遍历若漏掉 magic 过滤,可能误平别的策略单,实盘前务必在 MT5 策略测试器里用多 EA 同品种跑一遍验证。

MQL5 / C++
for(class="type">int i=total-class="num">1; i>=class="num">0; i--)
  {
  class=class="str">"cmt">//--- 获取下一张位置票
  class="type">class="kw">ulong  pos_ticket=PositionGetTicket(i);
  if(pos_ticket==class="num">0)
     class="kw">continue;
    
  class=class="str">"cmt">//--- 获取符号和位置魔力
  class="type">class="kw">string pos_symbol = PositionGetString(POSITION_SYMBOL);
  class="type">long   pos_magic  = PositionGetInteger(POSITION_MAGIC);
  
  class=class="str">"cmt">//--- 跳过与符号和魔法过滤器不匹配的位置
  if((magic!=-class="num">1 && pos_magic!=magic) || pos_symbol!=Symbol())
     class="kw">continue;
    
  class=class="str">"cmt">//--- 如果未获得价格 - 继续
  if(!SymbolInfoTick(Symbol(), tick))
     class="kw">continue;
...
让小布替你盯 SAR 翻面
把这些诊断交给小布盯盘,打开对应品种页即可看到 SAR 点何时从下穿上、止损该往哪推,你只管按信号决断。

常见问题

点在价格下方倾向多头跟踪,止损随点上移;点在上方倾向空头跟踪,止损随点下移,反转即提示原趋势疲弱。
小布盯盘已内置 SAR 翻面与距离诊断,可对该品种页开启提示,不必自己写脚本也能知道止损该往哪跟。
经纪商规定了止损距现价最小距离与冻结区,价格太近时修改会被拒或直接触发,函数里先校验才能避免报错。
标准库按固定点数推,SAR 版用指标点作锚,更贴合趋势斜率,可能在加速段收得更紧、盘整段少动。
它用来区分不同策略开的仓,EA 只移动自己 magic 对应的单,避免动到手动或其他程序的仓位。