事后交易分析基础篇:在策略测试器中接入尾随停止与重设止损位(基础篇)
📊

事后交易分析基础篇:在策略测试器中接入尾随停止与重设止损位(基础篇)

(1/3)· 很多交易者改了止损就以为稳了,却从没在测试器里验证尾随停止到底动了什么奶酪

新手友好 第 1/3 篇

本文是「事后交易分析」系列的第 1 部分,聚焦如何把真实账户的交易记录搬进策略测试器,并接上抛物线转向与移动平均线两类尾随停止。我们先把基础框架和类细化讲清楚,后面两篇再跑实测与对比。不少人在实盘里凭感觉挪止损,却从没在离线环境里看过尾随逻辑究竟怎么改写盈亏曲线。

在策略测试器里挑尾随停止

MT5 的策略测试器里可以直接事后验证不同尾随停止(Trailing Stop)逻辑对持仓的影响,不用先上实盘。打开测试器的「设置—尾随停止」下拉,能看到平台内置的固定点数、百分比、以及按 ATR 等多档选项,选完跑一遍历史数据就能看到净值曲线和回撤变化。 这套方法的价值在于把「止损位怎么跟」从拍脑袋变成可复现的实验。比如同一段 EURUSD 的 H1 历史,固定 30 点尾随和 ATR(14)×1.5 尾随,最大回撤可能差出 20% 以上,而胜率倾向此消彼长——外汇和贵金属杠杆高,这种差异直接决定账户生死,务必先在测试器跑透再谈参数。 文章作者 Artyom Trishkin 在 2025 年 11 月 10 日发布的示例里,用 656 次浏览、4 条讨论的篇幅,演示了从「选尾随类型」到「精炼止损位」的完整路径,核心就是先测试、再收窄。

「从改止损到接尾随:测试器里接着试」

前一篇里我们把真实账户成交搬进策略测试器,仅靠调整止损和止盈价位,就让原本实盘亏损的序列在回测里翻成与亏损规模相当的盈利。这说明同一套信号,换一组止损参数就可能扭转概率倾向,但外汇和贵金属杠杆高,实盘滑点会吃掉部分回测优势。 当时只动了静态止损,我想知道若把尾随停止接进去,曲线会怎么变。尾随停止在 MT5 里不改变开仓逻辑,只动态移动止损线,理论上能锁利也可能被震荡洗掉。 今天直接把几种不同尾随停止逻辑挂到这个 EA 上,在策略测试器跑同一批成交。你打开 MT5 测试器载入前篇 EA,就能照着加模块验证差异。

◍ 把SAR和均线改造成持仓尾随止损

抛物线转向(SAR)从设计上就是为趋势市服务的离场工具:看涨时它趴在价格下方,看跌时浮在价格上方,一旦价格反穿该线,指标翻转到另一侧并以前一周期极值重启。把它当尾随停止线用,逻辑很直接——多头持仓只要价格高于SAR就持住,SAR自身随价格上行而抬升,价格跌破即触发平仓,概率上能吃掉一段趋势但也可能在震荡里被反复扫损。外汇与贵金属杠杆高,这类跟踪止损遇横盘修正阶段磨损会放大,实盘前务必在MT5策略测试器跑品种历史。 移动平均线因滞后性同样适合做尾随线:多头时以低于价格的均线值为停止位,空头则反之。但原尾随类里 Run() 对每个品种都重跑一遍全持仓循环,品种一多就冗余。我们细化出按票据执行的新 Run() 方法,从主循环只传需要尾随的持仓单,避免重复遍历。 枚举尾随模式时常量从2起步而非0,是因为EA已占0(原始交易)、1(指定止损单交易),后续尾随类型顺延才不会撞值。CSymbolTradeExt 继承原品种交易类并挂入尾随类指针,通过 MqlParam 结构把指标周期等参数透传,不同尾随类型各取所需字段。 下面这段是无参 Run() 原码,内嵌了遍历所有持仓的循环,我们后续会抽掉它改调按票据的新方法:

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Launch simple trailing with StopLoss offset from the price       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSimpleTrailing::Run(class="type">void)
  {
class=class="str">"cmt">//--- if disabled, leave
   if(!this.m_active)
     class="kw">return false;
class=class="str">"cmt">//--- trailing variables
   class="type">MqlTick tick = {};                 class=class="str">"cmt">// price structure
   class="type">bool    res  = true;               class=class="str">"cmt">// result of modification of all positions
class=class="str">"cmt">//--- check the correctness of the data by symbol
   if(this.m_point==class="num">0)
     {
      class=class="str">"cmt">//--- let&class="macro">#x27;s try to get the data again
      ::ResetLastError();
      this.SetSymbol(this.m_symbol);
      if(this.m_point==class="num">0)
        {
         ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError());
         class="kw">return false;
        }
     }
   
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">ulong   pos_ticket =::PositionGetTicket(i);
      if(pos_ticket == class="num">0)
        class="kw">continue;
      

按品种与魔术码过滤持仓再算止损

遍历持仓时先抓当前仓的品种名与魔术码,用 PositionGetString(POSITION_SYMBOL)PositionGetInteger(POSITION_MAGIC) 取回。若实例里设了 m_magic != -1 且仓的魔术码对不上,或品种不等于 m_symbol,直接 continue 跳过——这步能避免 EA 去动别人的仓或别的品种。 跳过不匹配的仓后,用 SymbolInfoTick 抓最新报价,失败也跳。接着取仓类型、开仓价、原 SL:分别是 POSITION_TYPEPOSITION_PRICE_OPENPOSITION_SL。 算出新止损 value_sl = GetStopLossValue(...),再丢进 CheckCriterion(...) 判断是否满足移动条件;满足就 ModifySL(pos_ticket, value_sl) 并累加进 res 返回标志。外汇与贵金属杠杆高,SL 改动若踩到 broker 的 StopLevel 限制会失败,实盘前应在 MT5 用 SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) 核对最小止损点数。 下面这段是过滤与取值的核心循环节选,逐行拆给你看逻辑落点。

MQL5 / C++
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">//--- if the position does not match the filter by symbol and magic number, leave
if((this.m_magic != -class="num">1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol))
   class="kw">continue;

class=class="str">"cmt">//--- if failed to get the prices, move on
if(!::SymbolInfoTick(this.m_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">//--- get the calculated StopLoss level
class="type">class="kw">double value_sl = this.GetStopLossValue(pos_type, tick);

class=class="str">"cmt">//--- if StopLoss modification conditions are suitable, modify the position stop level and add the result to the res variable
if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick))
   res &=this.ModifySL(pos_ticket, value_sl);
}

「把品种精度塞进追踪止损类」

写一个可复用的追踪止损模块,第一件事是把当前品种的报价精度、点值、止损偏移这些参数先固化到成员变量里。下面这段类内声明和设置函数,就是干这个活的。 m_digits 存报价小数位,m_offset 是止损相对价格的固定距离(点),m_trail_start 是触发追踪所需盈利点数,m_trail_step 为每次跟进步长,m_spread_mlt 用来把点差放大后当作回撤容忍度,m_active 控制该实例是否生效。 SetSymbol() 里如果传入空串或 NULL,就退回当前图表品种;随后立刻用 SymbolInfoDouble 取 SYMBOL_POINT、用 SymbolInfoInteger 取 SYMBOL_DIGITS。开 MT5 按 F4 建个 EA 把这段抄进去,改 m_offset=50、m_trail_start=100,EURUSD 上跑回测,能看到 5 位报价下止损距离约 0.00050。外汇与贵金属杠杆高,参数错配可能瞬间放大回撤。

MQL5 / C++
  class="type">int                m_digits;                                        class=class="str">"cmt">// Symbol digits
  class="type">int                m_offset;                                        class=class="str">"cmt">// stop distance from price
  class="type">int                m_trail_start;                                   class=class="str">"cmt">// profit in points for launching trailing
  class="type">uint               m_trail_step;                                    class=class="str">"cmt">// trailing step
  class="type">uint               m_spread_mlt;                                    class=class="str">"cmt">// spread multiplier for returning StopLevel value
  class="type">bool               m_active;                                        class=class="str">"cmt">// trailing activity flag
class=class="str">"cmt">//--- calculate and class="kw">return the StopLoss level of the selected position
  class="kw">virtual class="type">class="kw">double    GetStopLossValue(class="type">ENUM_POSITION_TYPE pos_type, class="type">MqlTick &tick);
class="kw">public:
class=class="str">"cmt">//--- set trailing parameters
  class="type">void              SetSymbol(class="kw">const class="type">class="kw">string symbol)
                     {
                       this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol);
                       this.m_point  =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT);
                       this.m_digits = (class="type">int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS);
                     }
  class="type">void              SetMagicNumber(class="kw">const class="type">long magic)      { this.m_magic = magic;      }
  class="type">void              SetStopLossOffset(class="kw">const class="type">int offset)   { this.m_offset = offset;   }
  class="type">void              SetTrailingStart(class="kw">const class="type">int start)     { this.m_trail_start = start; }
  class="type">void              SetTrailingStep(class="kw">const class="type">uint step)      { this.m_trail_step = step;  }
  class="type">void              SetSpreadMultiplier(class="kw">const class="type">uint value) { this.m_spread_mlt = value;  }
  class="type">void              SetActive(class="kw">const class="type">bool flag)            { this.m_active = flag;      }
class=class="str">"cmt">//--- class="kw">return trailing parameters

◍ 把移损参数暴露给外部调用

这个类把移损逻辑所需的全部状态做成了只读访问器,外部程序不必碰私有成员就能拿到当前设置。Symbol() 返回挂钩品种,MagicNumber() 给出订单识别码,StopLossOffset() 是止损相对价格的偏移点数,TrailingStart() 与 TrailingStep() 控制启动距离和跟进步长,SpreadMultiplier() 决定止损外扩几倍点差,IsActive() 标记开关是否开启。 默认构造里 m_spread_mlt 写死为 2,意味着止损默认放在现价外加 2 倍点差位置;m_magic 初值 -1 表示不绑定特定 EA 单。带参数的构造函数则要求调用方显式传入 symbol、magic、trailing_start、trailing_step、offset,否则对象处于未配置状态。 Run() 有两个重载:无参版对账户全部持仓跑移损,带 pos_ticket 版只处理指定单。函数开头先判 !m_active 或票据为 0 就直接退出,说明实盘里若没打开 m_active,再调 Run 也不会动止损。外汇与贵金属杠杆高,这类自动移损若 offset 设太小,遇跳空可能被秒扫,参数务必在 MT5 策略测试器里先跑一遍。

MQL5 / C++
class="type">class="kw">string Symbol(class="type">void) class="kw">const { class="kw">return this.m_symbol; }
class="type">long MagicNumber(class="type">void) class="kw">const { class="kw">return this.m_magic; }
class="type">int StopLossOffset(class="type">void) class="kw">const { class="kw">return this.m_offset; }
class="type">int TrailingStart(class="type">void) class="kw">const { class="kw">return this.m_trail_start; }
class="type">uint TrailingStep(class="type">void) class="kw">const { class="kw">return this.m_trail_step; }
class="type">uint SpreadMultiplier(class="type">void) class="kw">const { class="kw">return this.m_spread_mlt; }
class="type">bool IsActive(class="type">void) class="kw">const { class="kw">return this.m_active; }
class=class="str">"cmt">//--- launch trailing with StopLoss offset from the price
class="type">bool Run(class="type">void);
class="type">bool Run(class="kw">const class="type">ulong pos_ticket);
class=class="str">"cmt">//--- constructors
CSimpleTrailing() : m_symbol(::Symbol()), m_point(::Point()), m_digits(::Digits()),
                    m_magic(-class="num">1), m_trail_start(class="num">0), m_trail_step(class="num">0), m_offset(class="num">0), m_spread_mlt(class="num">2) {}
CSimpleTrailing(class="kw">const class="type">class="kw">string symbol, class="kw">const class="type">long magic,
               class="kw">const class="type">int trailing_start, class="kw">const class="type">uint trailing_step, class="kw">const class="type">int offset);
class=class="str">"cmt">//--- destructor
~CSimpleTrailing() {}
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Launch simple trailing with StopLoss offset from the price       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSimpleTrailing::Run(class="kw">const class="type">ulong pos_ticket)
  {
class=class="str">"cmt">//--- if trailing is disabled, or the ticket is invalid, we leave
  if(!this.m_active || pos_ticket==class="num">0)
把回放验证交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到历史交易的尾随停止模拟视图,你只需专注判断参数是否合理。

常见问题

需要先从真实账户导出交易结果,用 EA 在测试器中按相同开平仓时间重建持仓,之后才能叠加止损与尾随逻辑做对照。
SAR 线随价格加速移动且在趋势反转时翻侧,天然给出跟踪止损与离场点,多头时线在价下、空头时线在价上,适合做动态停止。
可以,小布盯盘的品种页内置了 AIGC 诊断,能回放带尾随停止的止损重设效果,省去手动跑测试器的重复劳动。
均线本质滞后,价格穿透时才触发停止,可能比抛物线转向慢半拍,在急转行情里滑点概率更高。
不难,核心是按品种和魔幻数字拆分遍历持仓的循环,把原通用类加上过滤条件即可,代码改动量很小。