事后交易分析:在策略测试器中选择尾随停止和新的止损位·综合运用
(3/3)·把 SAR 与均线尾随接进回测 EA,看哪些止损组合能把实盘亏损翻成测试盈利
接上篇,我们已经能把真实账户交易复现到策略测试器并改止损位扭亏为盈。这一篇把抛物线转向和移动平均线两类尾随停止接进同一套 EA,在测试器里跑不同参数组合,看尾随逻辑会怎么改写原先的盈亏结构。
「尾随止损的实例分支与激活逻辑」
上面这段 MT5 自定义类代码,展示了两种尾随止损模式的实例化分支,以及失败兜底与激活判定。
当模式为 VIDYA 尾随时,若传入的 period_cmo 小于 1,则强制用 9 作为 CMO 周期;ma_period 为 0 时退回 12。SAR 模式里,sar_step 低于 0.0001 则用 0.02,sar_max 低于 0.02 则用 0.2——这些硬编码下限能避免参数误传导致指标失效。
实例化后若 m_trailing 仍为 NULL,函数直接返回 false,说明对象没建起来;否则调用 SetActive(true) 并返回 true,尾随才算挂上。
CSymbolTradeExt::Trailing(ulong pos_ticket) 里只做了一件事:指针非空就跑 Run(pos_ticket)。你在 EA 里每 tick 调这个函数并传持仓 ticket,就能让对应持仓按选定模式自动挪止损。外汇与贵金属波动剧烈,自动尾随只降低回撤概率,不保证不扫损。
this.m_trailing=new CTrailingByVIDYA(this.Symbol(), this.m_timeframe, magic, (period_cmo<class="num">1 ? class="num">9 : period_cmo), (ma_period==class="num">0 ? class="num">12 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_SAR : this.m_trailing=new CTrailingBySAR(this.Symbol(), this.m_timeframe, magic, (sar_step<class="num">0.0001 ? class="num">0.02 : sar_step), (sar_max<class="num">0.02 ? class="num">0.2 : sar_max), start, step, offset); break; class="kw">default : break; } class=class="str">"cmt">//--- something went wrong - class="kw">return &class="macro">#x27;false&class="macro">#x27; if(this.m_trailing==NULL) class="kw">return false; class=class="str">"cmt">//--- all is well - make the trail active and class="kw">return &class="macro">#x27;true&class="macro">#x27; this.m_trailing.SetActive(true); class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Start trailing of the position specified by the ticket | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSymbolTradeExt::Trailing(class="kw">const class="type">ulong pos_ticket) { if(this.m_trailing!=NULL) this.m_trailing.Run(pos_ticket); }
◍ 尾随停止实测:哪种算法能把亏损盘拉正
把前一篇的_history EA 复制进同一目录,把 CSymbolTrade 类头换成 CSymbolTradeExt,再挂上 Trailings.mqh,就能在原有历史重放框架里加尾随逻辑。EA 输入里新增尾随类型枚举,InpMAPeriod 默认给 0,意思是各均线自带周期默认值,传 0 时尾随类自动用指标内部默认,免得手写错周期。 测试器里先跑原始交易:同账户、同杠杆、同历史成交,结果净亏 658 美元。接着把初始止损统一设 100 点,尾随算法逐个轮换,其余参数不动。 简单尾随反而亏 746.1 美元,比裸盘更糟;抛物线转向(SAR)尾随翻正,盈利 541.8 美元。换均线类尾随后分化明显:VIDYA 亏 283.3 美元,MA 赚 563.1,AMA 赚 806.5,FRAMA 赚 1291.6,TEMA 赚 1355.1,DEMA 赚 1397.1 美元。 外汇与贵金属品种波动跳空频繁,回测盈利不等于实盘可复制,DEMA 尾随的高收益只是在该历史样本上的概率性优势,实盘仍可能回撤。开 MT5 把附带 EA 拖进策略测试器,按流水账推荐的初始日期和杠杆重跑上面九种模式,重点看 DEMA 和 FRAMA 在近期行情里是否还维持正期望。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TradingByHistoryDeals_Ext.mq5 | 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="macro">#class="kw">property version "class="num">1.00" class="macro">#include "SymbolTradeExt.mqh" class="macro">#include "Trailings.mqh" enum ENUM_TESTING_MODE { TESTING_MODE_ORIGIN, class=class="str">"cmt">/* Original trading */ TESTING_MODE_SLTP, class=class="str">"cmt">/* Specified StopLoss and TakeProfit values */ TESTING_MODE_TRAIL_SIMPLE, class=class="str">"cmt">/* Simple Trailing */ TESTING_MODE_TRAIL_SAR, class=class="str">"cmt">/* Trailing by Parabolic SAR indicator */ TESTING_MODE_TRAIL_AMA, class=class="str">"cmt">/* Trailing by AMA indicator */
跟踪止损的多种指标切换入口
EA 在测试模式下用枚举把 trailing 方式拆得很细:DEMA、FRAMA、MA、TEMA、VIDYA 各占一项,方便在策略测试器里横向比哪条曲线跟单更跟手。 输入参数里 InpTestingMode 默认是 TESTING_MODE_ORIGIN,也就是不干预、沿用原始止损;要测别的轨道就把这个枚举改掉。InpStopLoss 写死 300 点,是点数不是价格,黄金和欧美这种不同点值品种直接套会差出好几倍风险,外汇贵金属本身高杠杆高波动,调参前先在 MT5 里确认合约规格。 InpTestedSymbol 留空代表取当前测试品种,InpTestedMagic 默认 -1 表示不挑魔术码全接;InpShowDataInLog 默认 false,想扒每笔跟踪数据再开 true,不然日志干净但看不见底层。
TESTING_MODE_TRAIL_DEMA, class=class="str">"cmt">/* Trailing by DEMA indicator */ TESTING_MODE_TRAIL_FRAMA, class=class="str">"cmt">/* Trailing by FRAMA indicator */ TESTING_MODE_TRAIL_MA, class=class="str">"cmt">/* Trailing by MA indicator */ TESTING_MODE_TRAIL_TEMA, class=class="str">"cmt">/* Trailing by TEMA indicator */ TESTING_MODE_TRAIL_VIDYA, class=class="str">"cmt">/* Trailing by VIDYA indicator */ }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- input parameters input group " - Strategy parameters - " input class="type">class="kw">string InpTestedSymbol = ""; class=class="str">"cmt">/* The symbol being tested in the tester */ input class="type">long InpTestedMagic = -class="num">1; class=class="str">"cmt">/* The magic number being tested in the tester */ input class="type">bool InpShowDataInLog = false; class=class="str">"cmt">/* Show collected data in the log */ input group " - Stops parameters - " input ENUM_TESTING_MODE InpTestingMode = TESTING_MODE_ORIGIN; class=class="str">"cmt">/* Testing Mode */ input class="type">int InpStopLoss = class="num">300; class=class="str">"cmt">/* StopLoss in points */
「EA 输入参数的分组与风控初值」
在 MT5 的 EA 源码里,用 input group 可以把杂乱的外部参数按逻辑归组,上面这段就把止盈、 trailing 和指标参数切成了三块,面板上调参不会一头雾水。 InpTakeProfit 默认 500 点,意味着开仓后若价格朝有利方向跑够 500 点才可能触碰止盈;外汇与贵金属杠杆高,实际点值随合约规格变动,盲设容易过早离场。 Initial StopLoss 和 TakeProfit 都由布尔开关控制(默认 true),关掉就只靠 trailing 护身,适合想让均线自己拽止损的人。 Trailing 三件套:Start=150 点才启动,Step=50 点跟进,Offset=0 表示止损贴着极值走;若你做 XAUUSD 这种毛刺多的品种,Offset 设个 20~30 点可能减少被洗。 指标组里 InpMAPeriod=0 是个暗坑——0 在 iMA 里常代表用收盘价线或报错,真要挂 MA 过滤得填 10/20 这类实在周期,否则 trailing 计算可能直接废掉。
input class="type">int InpTakeProfit = class="num">500; class=class="str">"cmt">/* TakeProfit in points */ input group " - Trailing Parameters -" input class="type">bool InpSetStopLoss = true; class=class="str">"cmt">/* Set Initial StopLoss */ input class="type">bool InpSetTakeProfit = true; class=class="str">"cmt">/* Set Initial TakeProfit */ input class="type">int InpTrailingStart = class="num">150; class=class="str">"cmt">/* Trailing start */ class=class="str">"cmt">// Profit in points to start trailing input class="type">int InpTrailingStep = class="num">50; class=class="str">"cmt">/* Trailing step in points */ input class="type">int InpTrailingOffset = class="num">0; class=class="str">"cmt">/* Trailing offset in points */ input group " - Indicator Parameters -" input ENUM_TIMEFRAMES InpIndTimeframe = PERIOD_CURRENT; class=class="str">"cmt">/* Indicator&class="macro">#x27;s timeframe */ class=class="str">"cmt">// Timeframe of the indicator used in trailing calculation input class="type">int InpMAPeriod = class="num">0; class=class="str">"cmt">/* MA Period */
◍ 多指标协同的参数入口怎么配
把均线、AMA、VIDYA 与抛物线 SAR 揉进同一个 EA 时,最先要定的是 input 参数块。下面这段声明直接决定了后续信号计算的采样窗口与平滑方式,复制进 MT5 的 EA 头文件即可编译。 InpMAShift=0 表示均线不做水平偏移;InpFastEMAPeriod=2 与 InpSlowEMAPeriod=30 构成 AMA 快慢线跨度,2 周期对价格反应极敏、30 周期滤噪偏稳。InpCMOPeriod=9 是 VIDYA 用的钱德动量周期,9 根 K 线是短线惯用长度。 SAR 的步长与上限给的是 0.02 与 0.2,即每次反转加速 2 个基点、封顶 20 个基点,这是 MT5 默认抛物线参数,黄金与欧美盘都可直接测。InpAppliedPrice 锁 PRICE_CLOSE、InpMAMethod 用 MODE_SMA,说明均线以收盘价算简单平均。 InpDataIndex=1 指定从指标缓冲区取第 1 号柱(0 为最新未收线柱),回测时若发现信号慢一根,多半是这里没改对。外汇与贵金属杠杆高,参数跑通不等于实盘能扛回撤,先用策略测试器看样本外表现。
input class="type">int InpMAShift = class="num">0; class=class="str">"cmt">/* MA Shift */ input class="type">int InpFastEMAPeriod = class="num">2; class=class="str">"cmt">/* AMA Fast EMA Period */ input class="type">int InpSlowEMAPeriod = class="num">30; class=class="str">"cmt">/* AMA Slow EMA Period */ input class="type">int InpCMOPeriod = class="num">9; class=class="str">"cmt">/* VIDYA CMO Period */ input class="type">class="kw">double InpSARStep = class="num">0.02; class=class="str">"cmt">/* Parabolic SAR Step */ input class="type">class="kw">double InpSARMax = class="num">0.2; class=class="str">"cmt">/* Parabolic SAR Max */ input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE; class=class="str">"cmt">/* MA Applied Price */ input ENUM_MA_METHOD InpMAMethod = MODE_SMA; class=class="str">"cmt">/* MA Smoothing Method */ input class="type">int InpDataIndex = class="num">1; class=class="str">"cmt">/* Indicator data index */ class=class="str">"cmt">// Bar of data received frrom the indicator class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the pointer to the symbol trading object by name | class=class="str">"cmt">//+------------------------------------------------------------------+ CSymbolTrade *GetSymbolTrade(class="kw">const class="type">class="kw">string symbol, CArrayObj *list)
按成交记录聚合交易品种对象
多品种 EA 在跑批处理时,常需要把一笔笔成交按 symbol 归并到各自的交易对象里。下面这段逻辑就是干这件事:遍历成交数组,遇非买卖单直接跳过,只把真实持仓单纳入统计。 核心在 CreateListSymbolTrades:先判空数组,空则打印错误并返回 false;否则循环里用 SymbTradeTmp 做探针,list_symbols.Search 查重,命中就复用,未命中(index==WRONG_VALUE)才 new 一个 CSymbolTradeExt 并加入列表。 注意两个容易漏的点:添加对象失败必须 delete 防内存泄漏;res 用 &= 累积,只要有一笔处理失败,整体返回就是 false。外汇与贵金属杠杆高,这类对象管理 bug 可能在极端滑点行情下放大持仓风险。 代码片段里高亮行展示了从设置读取 trailing 参数与模式枚举的接法,开 MT5 把 InpTestingMode 改成不同 ENUM_TRAILING_MODE 值,能直接观察追踪止损行为差异。
{
SymbTradeTmp.SetSymbol(symbol);
list.Sort();
class="type">int index=list.Search(&SymbTradeTmp);
class="kw">return list.At(index);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Creates an array of used symbols |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CreateListSymbolTrades(SDeal &array_deals[], CArrayObj *list_symbols)
{
class="type">bool res=true; class=class="str">"cmt">// result
class="type">MqlParam param[class="num">7]={}; class=class="str">"cmt">// trailing parameters
class="type">int total=(class="type">int)array_deals.Size();class=class="str">"cmt">// total number of deals in the array
class=class="str">"cmt">//--- if the deal array is empty, class="kw">return &class="macro">#x27;false&class="macro">#x27;
if(total==class="num">0)
{
PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__);
class="kw">return false;
}
class=class="str">"cmt">//--- in a loop through the deal array
CSymbolTradeExt *SymbolTrade=NULL;
for(class="type">int i=class="num">0; i<total; i++)
{
class=class="str">"cmt">//--- get the next deal and, if it is neither buy nor sell, move on to the next one
SDeal deal_str=array_deals[i];
if(deal_str.type!=DEAL_TYPE_BUY && deal_str.type!=DEAL_TYPE_SELL)
class="kw">continue;
class=class="str">"cmt">//--- find a trading object in the list whose symbol is equal to the deal symbol
class="type">class="kw">string symbol=deal_str.Symbol();
SymbTradeTmp.SetSymbol(symbol);
list_symbols.Sort();
class="type">int index=list_symbols.Search(&SymbTradeTmp);
class=class="str">"cmt">//--- if the index of the desired object in the list is -class="num">1, there is no such object in the list
if(index==WRONG_VALUE)
{
class=class="str">"cmt">//--- we create a new trading symbol object and, if creation fails,
class=class="str">"cmt">//--- add &class="macro">#x27;false&class="macro">#x27; to the result and move on to the next deal
SymbolTrade=new CSymbolTradeExt(symbol, InpIndTimeframe);
if(SymbolTrade==NULL)
{
res &=false;
class="kw">continue;
}
class=class="str">"cmt">//--- if failed to add a symbol trading object to the list,
class=class="str">"cmt">//--- class="kw">delete the newly created object, add &class="macro">#x27;false&class="macro">#x27; to the result
class=class="str">"cmt">//--- and we move on to the next deal
if(!list_symbols.Add(SymbolTrade))
{
class="kw">delete SymbolTrade;
res &=false;
class="kw">continue;
}
class=class="str">"cmt">//--- initialize trailing specified in the settings in the trading object
ENUM_TRAILING_MODE mode=(ENUM_TRAILING_MODE)InpTestingMode;
「跟踪止损参数的分支装配逻辑」
多品种跟踪止损框架里,参数不是写死的,而是按模式在运行时灌进 MqlParam 数组。SetTrailingParams 这个函数干的就是这件事:先 ZeroMemory 清掉旧值,再用 switch 根据 ENUM_TRAILING_MODE 把外部输入变量塞进对应下标。 SAR 模式只占用 param[0]、param[1] 两个双精度槽位,分别放步进 InpSARStep 和上限 InpSARMax;AMA 模式则铺得更宽,从 param[0] 的周期到 param[5] 的慢速 EMA 周期,中间还跳过了 param[3] 直接写 param[4],说明该指标内部枚举留了空位。 这种写法意味着你加一种新尾随模式,只需在 switch 里补一个 case 并约定好 param 下标含义,SymbolTrade.SetTrailing 的调用签名不用动。开 MT5 把这段贴进 EA,改 InpSARStep 从 0.02 调到 0.05,回测里止损触发频率会明显上升,外汇与贵金属杠杆品种须警惕滑点放大带来的额外风险。
class="type">void SetTrailingParams(class="kw">const ENUM_TRAILING_MODE mode, class="type">MqlParam ¶m[]) { class=class="str">"cmt">//--- reset all parameters ZeroMemory(param); class=class="str">"cmt">//--- depending on the selected trailing type, we set the indicator parameters class="kw">switch(mode) { case TRAILING_MODE_SAR : param[class="num">0].type=TYPE_DOUBLE; param[class="num">0].double_value=InpSARStep; param[class="num">1].type=TYPE_DOUBLE; param[class="num">1].double_value=InpSARMax; break; case TRAILING_MODE_AMA : param[class="num">0].type=TYPE_INT; param[class="num">0].integer_value=InpMAPeriod; param[class="num">1].type=TYPE_INT; param[class="num">1].integer_value=InpMAShift; param[class="num">2].type=TYPE_INT; param[class="num">2].integer_value=InpAppliedPrice; param[class="num">4].type=TYPE_INT; param[class="num">4].integer_value=InpFastEMAPeriod; param[class="num">5].type=TYPE_INT; param[class="num">5].integer_value=InpSlowEMAPeriod; break; case TRAILING_MODE_DEMA :
◍ 追踪止损各模式的参数注入差异
在 EA 的追踪止损模块里,不同模式通过 switch-case 把外部输入变量写进 param[] 结构体数组,供后续指标句柄调用。 FRAMA、MA、TEMA、VIDYA 四种模式都先填 param[0]~param[2]:周期 InpMAPeriod、偏移 InpMAShift、应用价格 InpAppliedPrice,类型统一为 TYPE_INT。 MA 模式比前几个多塞了一个 param[3].integer_value=InpMAMethod,用来指定平滑方法(SMA/EMA 等);VIDYA 则跳到 param[6] 写 InpCMOPeriod,说明 CMO 周期在参数表里的槽位是第 7 个。 SIMPLE 和 default 分支直接 break,不注入任何 param——意味着这两个模式走的是固定点数或外部逻辑,不依赖指标参数。开 MT5 把这段贴进 trailing 函数,改 TRAILING_MODE_XXX 宏就能看到 param 槽位变化。
param[class="num">0].type=TYPE_INT; param[class="num">0].integer_value=InpMAPeriod; param[class="num">1].type=TYPE_INT; param[class="num">1].integer_value=InpMAShift; param[class="num">2].type=TYPE_INT; param[class="num">2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_FRAMA : param[class="num">0].type=TYPE_INT; param[class="num">0].integer_value=InpMAPeriod; param[class="num">1].type=TYPE_INT; param[class="num">1].integer_value=InpMAShift; param[class="num">2].type=TYPE_INT; param[class="num">2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_MA : param[class="num">0].type=TYPE_INT; param[class="num">0].integer_value=InpMAPeriod; param[class="num">1].type=TYPE_INT; param[class="num">1].integer_value=InpMAShift; param[class="num">2].type=TYPE_INT; param[class="num">2].integer_value=InpAppliedPrice; param[class="num">3].type=TYPE_INT; param[class="num">3].integer_value=InpMAMethod; break; case TRAILING_MODE_TEMA : param[class="num">0].type=TYPE_INT; param[class="num">0].integer_value=InpMAPeriod; param[class="num">1].type=TYPE_INT; param[class="num">1].integer_value=InpMAShift; param[class="num">2].type=TYPE_INT; param[class="num">2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_VIDYA : param[class="num">0].type=TYPE_INT; param[class="num">0].integer_value=InpMAPeriod; param[class="num">1].type=TYPE_INT; param[class="num">1].integer_value=InpMAShift; param[class="num">2].type=TYPE_INT; param[class="num">2].integer_value=InpAppliedPrice; param[class="num">6].type=TYPE_INT; param[class="num">6].integer_value=InpCMOPeriod; break; case TRAILING_MODE_SIMPLE : break; class="kw">default: break; }
按历史成交回放下单的逻辑骨架
在 MT5 里做历史回放式交易,核心是先声明两个开关:是否给每笔初始单挂止损、挂止盈。下面这段输入变量把 InpSetStopLoss 和 InpSetTakeProfit 都默认置为 true,意味着回放时原样复刻带防护单的成交,若你只想测裸进场,改 false 即可。
TradeByHistory() 这个函数承担遍历任务:它从 ExtListSymbols 取总数,逐个取出 CSymbolTradeExt 对象,再拿对象里的当前成交 CDeal。过滤条件很硬——必须按 magic 和 symbol 匹配,且只认 DEAL_TYPE_BUY / DEAL_TYPE_SELL 两类市价成交,其余全 continue 跳过。
时间维度上,函数用 deal.TicketTester()>0 排除已在测试器处理过的单,再用 obj.CheckTime(deal.Time()) 卡进场时刻。只有 entry==DEAL_ENTRY_IN 的市价入场成交,才会进入后续止损止盈尺寸设定;注释里写明原版交易初期不挂停止单,也就是回放引擎把防护单的挂载时机留给了后面分支。外汇与贵金属品种波动剧烈,回放时若漏掉止损开关,实盘映射可能放大滑点风险。
input class="type">bool InpSetStopLoss = true; class=class="str">"cmt">/* Set Initial StopLoss */ input class="type">bool InpSetTakeProfit = true; class=class="str">"cmt">/* Set Initial TakeProfit */ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Trading by history | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void TradeByHistory(class="kw">const class="type">class="kw">string symbol="", class="kw">const class="type">long magic=-class="num">1) { class="type">class="kw">datetime time=class="num">0; class="type">int total=ExtListSymbols.Total(); class=class="str">"cmt">// number of trading objects in the list class=class="str">"cmt">//--- in a loop by all symbol trading objects for(class="type">int i=class="num">0; i<total; i++) { class=class="str">"cmt">//--- get another trading object CSymbolTradeExt *obj=ExtListSymbols.At(i); if(obj==NULL) class="kw">continue; class=class="str">"cmt">//--- get the current deal pointed to by the deal list index CDeal *deal=obj.GetDealCurrent(); if(deal==NULL) class="kw">continue; class=class="str">"cmt">//--- sort the deal by magic number and symbol if((magic>-class="num">1 && deal.Magic()!=magic) || (symbol!="" && deal.Symbol()!=symbol)) class="kw">continue; class=class="str">"cmt">//--- sort the deal by type(only buy/sell deals) ENUM_DEAL_TYPE type=deal.TypeDeal(); if(type!=DEAL_TYPE_BUY && type!=DEAL_TYPE_SELL) class="kw">continue; class=class="str">"cmt">//--- if this is a deal already handled in the tester, move on to the next one if(deal.TicketTester()>class="num">0) class="kw">continue; class=class="str">"cmt">//--- if the deal time has not yet arrived, move to the next trading object of the next symbol if(!obj.CheckTime(deal.Time())) class="kw">continue; class=class="str">"cmt">//--- in case of a market entry deal ENUM_DEAL_ENTRY entry=deal.Entry(); if(entry==DEAL_ENTRY_IN) { class=class="str">"cmt">//--- set the sizes of stop orders depending on the stop setting method class=class="str">"cmt">//--- stop orders are not used initially(for original trading)
「跟单开仓时止损止盈的两种赋值路径」
在复制账户成交记录做回测时,止损止盈的写入逻辑分两条岔路:一种是强制按预设 SL/TP 值下单,另一种是走移动止损模式、按开关逐项判定。 先看第一段:当测试模式被设为 TESTING_MODE_SLTP,直接调用 CorrectStopLoss / CorrectTakeProfit 按品种规范化 ExtStopLoss、ExtTakeProfit,不给其他分支留口子。 第二段 else 分支里,只要不是 TESTING_MODE_ORIGIN,就根据 InpSetStopLoss、InpSetTakeProfit 两个布尔开关分别决定是否挂 SL 和 TP;若两个都关,sl、tp 保持初始 0,相当于市价裸单进场。外汇与贵金属杠杆高,裸单在跳空时可能瞬间扩大浮亏。 最后用 obj.Buy / obj.Sell 把成交推出去,返回 ticket 大于 0 才算真正开仓成功,随后把处理计数 +1 并回写测试器票据号。建议直接把下面代码粘进 MT5 脚本,把 InpTestingMode 切到两种模式各跑一次,对比成交单的 SL/TP 字段差异。
ENUM_ORDER_TYPE order_type=(deal.TypeDeal()==DEAL_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); class="type">class="kw">double sl=class="num">0; class="type">class="kw">double tp=class="num">0; class=class="str">"cmt">//--- in case of the mode for setting the specified stop order values if(InpTestingMode==TESTING_MODE_SLTP) { class=class="str">"cmt">//--- get correct values for StopLoss and TakeProfit sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } class=class="str">"cmt">//--- otherwise, if testing with trailing stops else { if(InpTestingMode!=TESTING_MODE_ORIGIN) { class=class="str">"cmt">//--- if allowed in the settings, we set correct stop orders for the positions being opened if(InpSetStopLoss) sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); if(InpSetTakeProfit) tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } } class=class="str">"cmt">//--- open a position by deal type class="type">ulong ticket=(type==DEAL_TYPE_BUY ? obj.Buy(deal.Volume(), deal.Magic(), sl, tp, deal.Comment()) : type==DEAL_TYPE_SELL ? obj.Sell(deal.Volume(),deal.Magic(), sl, tp, deal.Comment()) : class="num">0); class=class="str">"cmt">//--- if a position is opened(we received its ticket) if(ticket>class="num">0) { class=class="str">"cmt">//--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object obj.SetNumProcessedDeals(obj.NumProcessedDeals()+class="num">1); deal.SetTicketTester(ticket); class=class="str">"cmt">//--- get the position ID in the tester and write it to the properties of the deal object
◍ 平仓单在回测器里的两种消化路径
把真实账户历史里的平仓成交(DEAL_ENTRY_OUT / INOUT / OUT_BY)映射到 MT5 策略测试器时,核心是先拿到这笔平仓对应的开仓成交对象。代码里用 GetDealInByPosID 按持仓 ID 反查,若返回 NULL 就直接 continue 跳过,说明该持仓的建仓记录已丢失或尚未载入。 若反查到的开仓成交里存了测试器 ticket(ticket_tester),但值为 0,大概率该持仓在测试器中已经关闭。此时 PrintFormat 打出 'Could not get position ticket, apparently position #%I64d (#%I64d) is already closed',并调用 SetNextDealIndex 后 continue,避免重复平仓报错。 在原始历史复现模式(TESTING_MODE_ORIGIN)下,会真正用 ClosePos(ticket_tester) 在测试器里平掉该仓位;成功则返回真,把已处理成交数 NumProcessedDeals 加 1,并把测试器 ticket 写回 deal 对象。其他模式(如挂单算法模式)则跳过实际平仓动作,仅计数并登记 ticket,因为平仓单不参与策略信号构建。 最后只要 deal.TicketTester() 大于 0,就认为这笔成交已成功消化,调用 SetNextDealIndex 推进列表指针。外汇与贵金属品种波动剧烈,回测映射逻辑务必在实盘历史样本上跑通再用于风控推演。
class="type">long pos_id_tester=class="num">0; if(HistoryDealSelect(ticket)) { pos_id_tester=HistoryDealGetInteger(ticket, DEAL_POSITION_ID); deal.SetPosIDTester(pos_id_tester); } } class=class="str">"cmt">//--- in case of a market exit deal if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT || entry==DEAL_ENTRY_OUT_BY) { class=class="str">"cmt">//--- get a deal a newly opened position is based on CDeal *deal_in=obj.GetDealInByPosID(deal.PositionID()); if(deal_in==NULL) class="kw">continue; class=class="str">"cmt">//--- get the position ticket in the tester from the properties of the opening deal class=class="str">"cmt">//--- if the ticket is zero, then most likely the position in the tester is already closed class="type">ulong ticket_tester=deal_in.TicketTester(); if(ticket_tester==class="num">0) { PrintFormat("Could not get position ticket, apparently position #%I64d(#%I64d) is already closed \n", deal.PositionID(), deal_in.PosIDTester()); obj.SetNextDealIndex(); class="kw">continue; } class=class="str">"cmt">//--- if we reproduce the original trading history in the tester, if(InpTestingMode==TESTING_MODE_ORIGIN) { class=class="str">"cmt">//--- if the position is closed by ticket if(obj.ClosePos(ticket_tester)) { class=class="str">"cmt">//--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object obj.SetNumProcessedDeals(obj.NumProcessedDeals()+class="num">1); deal.SetTicketTester(ticket_tester); } } class=class="str">"cmt">//--- otherwise, in the tester we work with stop orders placed according to different algorithms, and closing deals are skipped; class=class="str">"cmt">//--- accordingly, for the closing deal, we simply increase the number of deals handled by the tester and class=class="str">"cmt">//--- write the deal ticket in the tester to the properties of the deal object else { obj.SetNumProcessedDeals(obj.NumProcessedDeals()+class="num">1); deal.SetTicketTester(ticket_tester); } } class=class="str">"cmt">//--- if a ticket is now set in the deal object, then the deal has been successfully handled - class=class="str">"cmt">//--- set the deal index in the list to the next deal if(deal.TicketTester()>class="num">0) { obj.SetNextDealIndex(); }
用 OnTick 跑历史成交的追踪止损
这段逻辑把「追踪持仓」和「回放历史成交」拆成了两个独立调用,只在策略测试器里生效。OnTick 首行用 MQLInfoInteger(MQL_TESTER) 拦截实盘,避免你不小心挂到真账户上乱尾随。 Trailing() 函数遍历 PositionsTotal() 得到的全部持仓,按票据取 magic 与 symbol,再用外部输入 InpTestedMagic / InpTestedSymbol 做过滤,只处理匹配的那部分。匹配到的品种会拿到对应的 CSymbolTradeExt 对象,调用其 Trailing(ticket) 方法移动止损。 回测区间 2024.09.13 至今、初始本金 3000 且杠杆 1:500 的设定下,AUDUSD 成交对象累计 222 笔、EURJPY 累计 120 笔。外汇与贵金属杠杆交易风险高,回测表现不代表实盘概率,请先在 MT5 策略测试器用同样参数验证再考虑迁移。 让小布替你跑这套 把上面代码直接贴进 EA 的 OnTick,设 InpTestedSymbol="AUDUSD" 跑一遍,看 222 笔成交里 trailing 触发了几回,比读文档直观。
class="type">void Trailing(class="type">void) { class=class="str">"cmt">//--- variables for getting position properties class="type">long magic=-class="num">1; class="type">class="kw">string symbol=""; class=class="str">"cmt">//--- in a loop through all 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 ticket=PositionGetTicket(i); if(ticket==class="num">0) class="kw">continue; class=class="str">"cmt">//--- get the magic number and position symbol ResetLastError(); if(!PositionGetInteger(POSITION_MAGIC, magic)) { Print("PositionGetInteger() failed. Error ", GetLastError()); class="kw">continue; } if(!PositionGetString(POSITION_SYMBOL, symbol)) { Print("PositionGetString() failed. Error ", GetLastError()); class="kw">continue; } class=class="str">"cmt">//--- if the position does not meet the specified conditions of the magic number and symbol, we move to the next one if((InpTestedMagic>-class="num">1 && magic!=InpTestedMagic) || (InpTestedSymbol!="" && symbol!=InpTestedSymbol)) class="kw">continue; class=class="str">"cmt">//--- get a trading object by a symbol name and call its method for trailing a position by ticket CSymbolTradeExt *obj=GetSymbolTrade(symbol, &ExtListSymbols); if(obj!=NULL) obj.Trailing(ticket); } } class="type">void OnTick() { class=class="str">"cmt">//--- work only in the strategy tester if(!MQLInfoInteger(MQL_TESTER)) class="kw">return; class=class="str">"cmt">//--- Trail open positions Trailing(); class=class="str">"cmt">//--- Handle the list of deals from the file TradeByHistory(InpTestedSymbol, InpTestedMagic); }
「各货币对回测成交笔数分布」
把多品种 EA 跑完历史回测后,交易对象的总成交笔数差异极大,这直接反映各品种在样本期内的信号触发频率。 EURUSD 累计 526 笔、GBPUSD 352 笔、NZDUSD 182 笔,主流欧系与商品系货币对贡献了绝大多数成交。USDCHF 250 笔、USDJPY 150 笔、XAUUSD(现货黄金)118 笔,也具备统计意义。 USDCAD 仅 22 笔,样本过薄,据此做参数优化可能失真,建议单独拉长测试周期或剔除。外汇与贵金属杠杆交易高风险,回测笔数不等于实盘胜率。
class="num">3. EURUSD trade object. Total deals: class="num">526 class="num">4. GBPUSD trade object. Total deals: class="num">352 class="num">5. NZDUSD trade object. Total deals: class="num">182 class="num">6. USDCAD trade object. Total deals: class="num">22 class="num">7. USDCHF trade object. Total deals: class="num">250 class="num">8. USDJPY trade object. Total deals: class="num">150 class="num">9. XAUUSD trade object. Total deals: class="num">118
◍ 把工具请下神坛
这套尾随测试 EA 的设计目标很直接:在策略测试器里复现历史成交,把止损、止盈和尾随停止拎出来挨个试。原文给出的类库与 EA 总体积约 276 KB(Trailings.mqh 101.41 KB、SymbolTrade.mqh 53.86 KB、SymbolTradeExt.mqh 12.95 KB、TradingByHistoryDeals_Ext.mq5 81.36 KB、MQL5.zip 26.49 KB),解压进终端 MQL5 目录就能跑,不挑版本。 尾随参数在测试中是随机取的,指标全用标准值,所以别把它当圣杯。更合理的做法是对每个品种单独测,再按波动特征挑尾随档位;当前 EA 没开放逐品种自定义接口,但改造并不难,懂一点 MQL5 就能加上。 外汇和贵金属杠杆高、滑点跳空频繁,随机尾随回测盈利不代表实盘能复现,任何设置都先在小资金或模拟盘验证。 真要把它用起来,就下那几个文件丢进终端,选自己常做的货币对跑一遍历史,看哪组尾随让回撤更顺眼——工具的价值在手里而不在神坛上。