EA 交易运行期间平衡曲线斜率的控制·进阶篇
📉

EA 交易运行期间平衡曲线斜率的控制·进阶篇

(2/3)·恒定手数 EA 在连亏段容易把回撤拉爆,用斜率反馈自动削量才是进阶风控的缺口

偏理论 第 2/3 篇
不少老手以为 EA 跑恒定手数就够稳,其实连亏段斜率塌下去时账户已在裸奔。给平衡曲线装一条反馈链,让交易量随坡度自己收放,比人工盯盘切仓更跟手。

◍ 账户余额历史的过滤与基类骨架

TBalanceHistory 是后续多个统计类的基类,核心职责只有一件:把 MT5 账户的交易历史按条件捞出来,供斜率、回撤等指标复用。它本身不管画图,只管把“哪些交易算数”这件事定清楚。 过滤条件通过 SetFiltrParams 落地,三个输入参数直接决定采样范围:magic 填 0 代表不区分 EA 幻数;type 接受 DEAL_TYPE_BUY、DEAL_TYPE_SELL 或 -1(长短线通吃);limit 为 0 时拉取全部可用历史,非零则限制回溯深度。对象构造时默认值即 magic=0、type=-1、limit=0,也就是“全量无过滤”起步。 真正产生数据的是 GetTradeResultsArray,它用 max_trades 控制写入数组的交易条数——这个值的下限是 2,因为算平衡曲线坡度至少得两个坐标点;填 0 则分析整段历史。方法内部先校验品种有效性与参数,再用 HistoryDealGetTicket 逐笔取成交 tick,借 HistoryDealGetInteger(DEAL_ENTRY) 筛出 DEAL_ENTRY_OUT 的平仓记录,按幻数、类型、品种、时间四项过关后才写入原始数组。 原始数组就绪后,SortMasterSlaveArray 用冒泡法对时间做升序,并让结果数组同步位移;再把同一分钟内的平仓归组进 group_datetime_array 与 group_result_array,消除秒级“颤振”。最后反转顺序使最早交易对应零号元素,并把逐笔结果替换为累积余额。外汇与贵金属账户历史受 broker 留存限制,实盘验证时 limit=0 未必能拿到早年全部 tick,建议先用策略测试器跑一段再比对数组长度。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span>
<span class="comment">class=class="str">"cmt">//&nbsp;&nbsp;结余历史的操作:</span>
<span class="comment">class=class="str">"cmt">//---------------------------------------------------------------------</span>
<span class="keyword">class</span> TBalanceHistory
{
<span class="keyword">class="kw">private</span>:
&nbsp;&nbsp;<span class="keyword">class="type">long</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;current_magic;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 当存取历史合约时的 "幻数"  ( class="num">0 - 任意数字 )</span>
&nbsp;&nbsp;<span class="keyword">class="type">long</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;current_type;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 合约类型 ( -class="num">1 - 全部 )</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; current_limit_history;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 历史深度限制 ( class="num">0 - 所有历史 )</span>
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">datetime</span>&nbsp;&nbsp; monitoring_begin_date;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 监视合约历史的开始时间</span>
&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; real_trades;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 已执行的实际交易数量</span>
<span class="keyword">class="kw">protected</span>:
&nbsp;&nbsp;TradeSymbol&nbsp;&nbsp;trade_symbol;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 工作交易品种的操作</span>
<span class="keyword">class="kw">protected</span>:
<span class="comment">class=class="str">"cmt">//&nbsp;&nbsp;"原始" 数组:</span>
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;org_datetime_array[ ];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 交易日期时间</span>
&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;org_result_array[ ];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 交易结果</span>
<span class="comment">class=class="str">"cmt">//&nbsp;&nbsp;以时间归组的数据数组:</span>

「交易历史类的字段与取数接口」

做账户净值曲线或交易分布图时,先把要存的数据结构定清楚。下面这组声明把每笔交易的日期时间和结果分开放进双精度数组,X 轴用 last_datetime_array 存时间戳,Y 轴用 last_result_array 存盈亏点数,两组必须同步排序否则画图会错位。 类里公开了几个直接能用的接口:SetTradeSymbol() 改监控品种,SetMonitoringBeginDate() 限定统计起点,SetFiltrParams() 按 magic number、订单类型、笔数上限过滤。GetTradeResultsArray(int _max_trades) 是核心取数函数,返回实际读到的交易笔数,且不会超过传入的 _max_trades。 私有方法 SortMasterSlaveArray() 负责把主数组和从数组按升序一起重排,调用方不用自己管下标对齐。想在 MT5 里验证,新建 EA 包含这个类,传 _max_trades=500 拉最近 500 笔,看返回的 count 是否等于 HistoryDealsTotal() 中符合过滤条件的真实数目。 外汇与贵金属杠杆高,历史统计只反映过去,样本外的分布可能明显偏离,结论仅作概率参考。

MQL5 / C++
class="type">class="kw">double     group_datetime_array[ ];                                                                       class=class="str">"cmt">// 交易日期时间
class="type">class="kw">double     group_result_array[ ];                                                                              class=class="str">"cmt">// 交易结果
class="type">class="kw">double     last_result_array[ ];   class=class="str">"cmt">// 保存最后交易结果的数组 ( Y 轴上的点数 )
class="type">class="kw">double     last_datetime_array[ ]; class=class="str">"cmt">// 存最后交易时间的数组 ( X 轴上的点数 )
class="kw">private:
   class="type">void      SortMasterSlaveArray( class="type">class="kw">double& _m[ ], class="type">class="kw">double& _s[ ] );   class=class="str">"cmt">// 同步两个升序数组
class="kw">public:
   class="type">void      SetTradeSymbol( class="type">class="kw">string _symbol );                                                            class=class="str">"cmt">// 设置/改变 工作交易品种
   class="type">class="kw">string    GetTradeSymbol( );                                                                           class=class="str">"cmt">// 获取工作交易品种
   class="type">void      RefreshSymbolInfo( );                                                                        class=class="str">"cmt">// 刷新工作交易品种的市场情报
   class="type">void      SetMonitoringBeginDate( class="type">class="kw">datetime _dt );                                                      class=class="str">"cmt">// 设置监视开始时间
   class="type">class="kw">datetime  GetMonitoringBeginDate( );                                                                   class=class="str">"cmt">// 获取监视开始时间
   class="type">void      SetFiltrParams( class="type">long _magic, class="type">long _type = -class="num">1, class="type">int _limit = class="num">0 );class=class="str">"cmt">// 设置合约过滤参数
class="kw">public:
class=class="str">"cmt">// 得到最后交易结果:
   class="type">int       GetTradeResultsArray( class="type">int _max_trades );
class="kw">public:
   class="type">void      TBalanceHistory( );      class=class="str">"cmt">// 构造器
   class="type">void      ~TBalanceHistory( );     class=class="str">"cmt">// 析构器
};
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//  读取数组最近(按时间)交易的结果:
class=class="str">"cmt">//---------------------------------------------------------------------
class=class="str">"cmt">//  - 返回实际读取交易数量但不超过指定数量;
class=class="str">"cmt">//---------------------------------------------------------------------
class="type">int
TBalanceHistory::GetTradeResultsArray( class="type">int _max_trades )
{
   class="type">int       index, limit, count;
   class="type">long      deal_type, deal_magic, deal_entry;

历史成交的过滤与数组装载

这段逻辑干的事很直接:从 MT5 历史里捞平仓单,按品种、幻数、类型和时间窗口筛一遍,再把合格记录塞进原始数组供后续统计。外汇与贵金属杠杆高,历史回测结果仅代表过去样本,实盘复制可能偏离。 先卡两道硬门槛:_max_trades 小于 2 直接返回 0,工作品种 symbol 为空也返回 0。接着 HistorySelect 拉从 monitoring_begin_date 到当前的所有历史,失败就退出;HistoryDealsTotal 拿到总数 count,若 count 小于所需交易数同样放弃。 若设了 current_limit_history 且历史多于它,就从尾部截掉多余部分(limit = count - current_limit_history),否则 limit 为 0。ArrayResize 把 org_datetime_array 和 org_result_array 调到 count - limit 大小,避免越界。 倒序遍历时每层都做 continue 过滤:DEAL_ENTRY 不是 OUT(未平仓)跳过;幻数不匹配跳过;品种不对跳过;类型不符或不是纯买卖单跳过;平仓时间早于监控起点也跳过。留下的才是真实样本。 让小布替你跑这套 把 monitoring_begin_date 设成最近 90 天、current_magic 填你 EA 的幻数,能快速验证这段过滤是否漏掉了手动平仓单。

MQL5 / C++
  class="type">class="kw">datetime  deal_close_time, current_time;
  class="type">ulong     deal_ticket;                         class=class="str">"cmt">// 合约的单号
  class="type">class="kw">double    trade_result;
  class="type">class="kw">string    symbol, deal_symbol;
  real_trades = class="num">0;
class=class="str">"cmt">//  交易数量不得少于 class="num">2:
  if( _max_trades < class="num">2 )
  {
    class="kw">return( class="num">0 );
  }
class=class="str">"cmt">//  如果工作交易品种未指定, 不做任何事:
  symbol = trade_symbol.GetTradeSymbol( );
  if( symbol == NULL )
  {
    class="kw">return( class="num">0 );
  }
class=class="str">"cmt">//  请求从指定时间至当前时刻的历史合约与订单:
  if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true )
  {
    class="kw">return( class="num">0 );
  }
class=class="str">"cmt">//  计算交易数量:
  count = HistoryDealsTotal( );
class=class="str">"cmt">//  如果历史交易数量少于必要数量, 则退出:
  if( count < _max_trades )
  {
    class="kw">return( class="num">0 );
  }
class=class="str">"cmt">//  如果历史交易数量多于必要数量, 则限制它们:
  if( current_limit_history > class="num">0 && count > current_limit_history )
  {
    limit = count - current_limit_history;
  }
  else
  {
    limit = class="num">0;
  }
class=class="str">"cmt">//  如果需要, 调整 "原始" 数组的维度至指定的交易数量:
  if(( ArraySize( org_datetime_array )) != ( count - limit ))
  {
    ArrayResize( org_datetime_array, count - limit );
    ArrayResize( org_result_array, count - limit );
  }
class=class="str">"cmt">//  以历史交易填充 "原始" 数组:
  real_trades = class="num">0;
  for( index = count - class="num">1; index >= limit; index-- )
  {
    deal_ticket = HistoryDealGetTicket( index );
class=class="str">"cmt">//  如果合约未平仓, 不要继续:
    deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY );
    if( deal_entry != DEAL_ENTRY_OUT )
    {
      class="kw">continue;
    }
class=class="str">"cmt">//  检查合约的 "幻数",如果必要:
    deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC );
    if( current_magic != class="num">0 && deal_magic != current_magic )
    {
      class="kw">continue;
    }
class=class="str">"cmt">//  检查合约交易品种:
    deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL );
    if( symbol != deal_symbol )
    {
      class="kw">continue;
    }
class=class="str">"cmt">//  检查合约类型,如果必要:
    deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE );
    if( current_type != -class="num">1 && deal_type != current_type )
    {
      class="kw">continue;
    }
    else if( current_type == -class="num">1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL ))
    {
      class="kw">continue;
    }
class=class="str">"cmt">//  检查合约平仓时间:
    deal_close_time = ( class="type">class="kw">datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME );
    if( deal_close_time < monitoring_begin_date )
    {
      class="kw">continue;
    }

◍ 按平仓时间归并历史成交明细

做历史成交统计时,先把每笔平仓时间和单笔盈亏(利润除以成交量)记进原始数组,再用 SortMasterSlaveArray 按时间主从排序,避免后续分组错乱。若实际交易数 real_trades 小于预设的 _max_trades,函数直接 return(0),说明样本不足,外汇与贵金属品种在高波动时段可能频繁出现样本断层,需调大监控窗口。 分组阶段以平仓时间为键:遍历原始数组,若输出数组当前时间戳大于0且与时点差值的绝对值大于0.0,就下移指针写入新元素;否则把盈亏累加进同一时间槽。注意此处用 group_datetime_array[real_trades] 作判等基准,若 real_trades 初值未对齐易漏并,开 MT5 跑这段代码时建议先打印 real_trades 与 index 的对应值。 最后把分组结果逆序写回 last_result_array,并对前 _max_trades 项做前缀累加,使数组第 n 位表示前 n 笔的累计盈亏。下面这段是原文核心逻辑的代码原文,可直接复制到 MQ5 文件里逐行验证。

MQL5 / C++
    org_datetime_array[ real_trades ] = deal_close_time / class="num">60;
    org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME );
    real_trades++;
  }
class=class="str">"cmt">//  如果此时少于必要交易数, 返回:
  if( real_trades < _max_trades )
  {
    class="kw">return( class="num">0 );
  }
  count = real_trades;
class=class="str">"cmt">//  以订单关闭时间对 "原始" 数组排序:
  SortMasterSlaveArray( org_datetime_array, org_result_array );
class=class="str">"cmt">// 如果必要, 调整 group 数组的维度至指定的点数:
  if(( ArraySize( group_datetime_array )) != count )
  {
    ArrayResize( group_datetime_array, count );
    ArrayResize( group_result_array, count );
  }
  ArrayInitialize( group_datetime_array, class="num">0.0 );
  ArrayInitialize( group_result_array, class="num">0.0 );
class=class="str">"cmt">//  以分组数据填充输出数组 ( 平仓时间为标识的分组 ):
  for( index = class="num">0; index < count; index++ )
  {
class=class="str">"cmt">//  得到其它交易:
    deal_close_time = ( class="type">class="kw">datetime )org_datetime_array[ index ];
    trade_result = org_result_array[ index ];
class=class="str">"cmt">//  现在检查是否在输出数组中存在时间相同:
    current_time = ( class="type">class="kw">datetime )group_datetime_array[ real_trades ];
    if( current_time > class="num">0 && MathAbs( current_time - deal_close_time ) > class="num">0.0 )
    {
      real_trades++;                                        class=class="str">"cmt">// 移动指针至下一个元素
      group_result_array[ real_trades ] = trade_result;
      group_datetime_array[ real_trades ] = deal_close_time;
    }
    else
    {
      group_result_array[ real_trades ] += trade_result;
      group_datetime_array[ real_trades ] = deal_close_time;
    }
  }
  real_trades++;                                          class=class="str">"cmt">// 现在是唯一元素的数量
class=class="str">"cmt">//  如果此时少于必要交易数, 退出:
  if( real_trades < _max_trades )
  {
    class="kw">return( class="num">0 );
  }
  if( ArraySize( last_result_array ) != _max_trades )
  {
    ArrayResize( last_result_array, _max_trades );
    ArrayResize( last_datetime_array, _max_trades );
  }
class=class="str">"cmt">//  以逆向索引写积累的数据到输出数组:
  for( index = class="num">0; index < _max_trades; index++ )
  {
    last_result_array[ _max_trades - class="num">1 - index ] = group_result_array[ index ];
    last_datetime_array[ _max_trades - class="num">1 - index ] = group_datetime_array[ index ];
  }
class=class="str">"cmt">//  在输出数组中,以积累的总和取代单个交易的结果:
  for( index = class="num">1; index < _max_trades; index++ )
  {
    last_result_array[ index ] += last_result_array[ index - class="num">1 ];
  }
  class="kw">return( _max_trades );
}
class=class="str">"cmt">//  请求从指定时间至当前时刻的历史合约与订单:
  if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true )
  {
    class="kw">return( class="num">0 );
  }
class=class="str">"cmt">//  计算交易数量:
  count = HistoryDealsTotal( );
class=class="str">"cmt">//  如果历史交易数量少于必要数量, 则退出:
  if( count < _max_trades )
  {
    class="kw">return( class="num">0 );
  }
class=class="str">"cmt">//  以历史交易填充 "原始" 数组:
  real_trades = class="num">0;
  for( index = count - class="num">1; index >= limit; index-- )
  {
    deal_ticket = HistoryDealGetTicket( index );
class=class="str">"cmt">//  如果交易未关闭, 不要继续:

「按平仓时间归并历史成交明细」

在回测或实盘统计里,直接遍历 HistoryDeal 拿到的每笔成交是散的。上面这段代码先过滤:只保留 DEAL_ENTRY_OUT 的平仓单,再按幻数、品种、类型和时间窗一层层 continue 掉不符的,把有效平仓的「分钟级时间」和「单手利润」分别塞进 org_datetime_array 与 org_result_array。 单手利润用 HistoryDealGetDouble(deal_ticket, DEAL_PROFIT) 除以同笔 DEAL_VOLUME 得到,这一步把不同手数的成交归一化,方便横向比。若 real_trades 小于传入的 _max_trades,函数直接 return(0),说明样本不够,后续分布检验无意义。 归并逻辑在第二层循环:以平仓时间为 key,若 group_datetime_array 里当前元素与这笔成交时间差绝对值大于 0.0(即不同分钟),就新开一组;否则把 trade_result 累加进同组。最终 real_trades 自增后代表唯一时间组的数量,外汇与贵金属杠杆高,这种按分钟聚合的方式可能掩盖滑点导致的秒级偏差,开 MT5 跑时建议把 0.0 阈值换成具体秒数验证。

MQL5 / C++
deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY );
if( deal_entry != DEAL_ENTRY_OUT )
{
   class="kw">continue;
}
class=class="str">"cmt">//  检查合约的 "幻数",如果必要:
   deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC );
   if( _magic != class="num">0 && deal_magic != _magic )
   {
      class="kw">continue;
   }
class=class="str">"cmt">//  检查合约交易品种:
   deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL );
   if( symbol != deal_symbol )
   {
      class="kw">continue;
   }
class=class="str">"cmt">//  检查合约类型,如果必要:
   deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE );
   if( _type != -class="num">1 && deal_type != _type )
   {
      class="kw">continue;
   }
   else if( _type == -class="num">1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL ))
   {
      class="kw">continue;
   }
class=class="str">"cmt">//  检查合约平仓时间:
   deal_close_time = ( class="type">class="kw">datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME );
   if( deal_close_time < monitoring_begin_date )
   {
      class="kw">continue;
   }
class=class="str">"cmt">//  所以, 我们可以读其它交易:
   org_datetime_array[ real_trades ] = deal_close_time / class="num">60;
   org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME );
   real_trades++;
}
class=class="str">"cmt">//  如果此时少于必要交易数, 退出:
   if( real_trades < _max_trades )
   {
      class="kw">return( class="num">0 );
   }
class=class="str">"cmt">//  检查合约平仓时间:
   deal_close_time = ( class="type">class="kw">datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME );
   if( deal_close_time < monitoring_begin_date )
   {
      class="kw">continue;
   }
class=class="str">"cmt">//  以分组数据填充输出数组 ( 平仓时间为标识的分组 ):
   real_trades = class="num">0;
   for( index = class="num">0; index < count; index++ )
   {
class=class="str">"cmt">//  得到其它交易:
      deal_close_time = ( class="type">class="kw">datetime )org_datetime_array[ index ];
      trade_result = org_result_array[ index ];
class=class="str">"cmt">//  现在检查是否在输出数组中存在时间相同:
      current_time = ( class="type">class="kw">datetime )group_datetime_array[ real_trades ];
      if( current_time > class="num">0 && MathAbs( current_time - deal_close_time ) > class="num">0.0 )
      {
         real_trades++;
         group_result_array[ real_trades ] = trade_result;
         group_datetime_array[ real_trades ] = deal_close_time;
      }
      else
      {
         group_result_array[ real_trades ] += trade_result;
         group_datetime_array[ real_trades ] = deal_close_time;
      }
   }
   real_trades++;
class=class="str">"cmt">//  以逆向索引写积累的数据到输出数组:
   for( index = class="num">0; index < _max_trades; index++ )
   {

把单笔结果翻成累计曲线

上面这段逻辑干的事很直接:先把最近 _max_trades 笔交易的盈亏和开仓时间,从临时分组数组倒序塞进 last_result_array 与 last_datetime_array。倒序写入的偏移量是 _max_trades - 1 - index,保证数组头部是最早那笔、尾部是最近一笔。 随后一个从 index=1 开始的循环,把 last_result_array 里每一项都加上前一项,等价于在数组里就地算出累计盈亏序列。MT5 里把这组数丢进缓冲区画线,就能直观看到策略在窗口期内的净值爬坡,而不是被单笔毛刺带偏。 外汇与贵金属品种点差和滑点会吃掉毛刺交易,实盘前务必把 _max_trades 调到与你复盘周期匹配,否则累计曲线可能只覆盖了几根 H1 就断片。

MQL5 / C++
  last_result_array[ _max_trades - class="num">1 - index ] = group_result_array[ index ];
  last_datetime_array[ _max_trades - class="num">1 - index ] = group_datetime_array[ index ];
}
class=class="str">"cmt">//  在输出数组中,以积累的总和取代单个交易的结果:
  for( index = class="num">1; index < _max_trades; index++ )
  {
    last_result_array[ index ] += last_result_array[ index - class="num">1 ];
  }
把斜率监测交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时坡度与自动削量提示,你只管判断要不要手动干预。

常见问题

它不改入场信号,只在资金管理(MM)层加反馈:用前两笔平仓交易算坡度,低于阈值就按步进或线性函数减仓,恢复后加回。
小布盯盘已内置坡度诊断与风险预警,可对照本文的调节函数做可视化核对,但嵌入自有 EA 仍需按文中库改写代码。
步进是离散步进从最大到最小跳变,反应快但曲线锯齿;线性在调节区间内平滑过渡,对平衡曲线损伤更小但滞后略大。
因 Т8 到 Т10 处于从最小爬回最大的转换区,部分盈利单手数没满血,总利润较初始 Vmax 段偏低,属调节副作用。
不能。它只是 MM 补充,降低不适宜期的暴露,不改变策略期望,亏损 EA 该亏还是亏,只是亏得慢些。