走势延续模型 - 搜索图表和执行统计·综合运用
📘

走势延续模型 - 搜索图表和执行统计·综合运用

第 3/3 篇

◍ 走势延续模型的底线识别条件

要做走势延续判定,先卡死最小条件集,不满足就直接排除。下跌和上涨两侧逻辑对称,但方向反着来:跌势里修正浪高点(C)必须低于后续极值高点(A),涨势里修正浪低点(C)必须高于后续极值低点(A)。 第二个硬条件是索引顺序。跌势中修正浪低点索引(B)要超过高点索引(C),涨势中修正浪高点索引(B)超过低点索引(C)——这保证了修正段结构在时间上不颠倒。 修正走势持续时间锁死在 2 到 6 根柱线(从 B 点算起)。少于 2 根可能是噪音,多于 6 根则倾向视为另一段行情而非同模型修正,外汇与贵金属市场跳空频繁,这一窗口需结合品种波动重看。 代码把两类条件收进两个 bool 变量,方便后续直接当开关调用。下跌分支用 High_Corr_wave_downtrend_base_tf_double 比 High_base_tf[UpperFractal_downtrend_base_tf] 小来做条件1,上涨分支对称处理。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">4. 描述模型识别条件 (开始)                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- 对于下跌趋势
class=class="str">"cmt">/*class="num">1. 修正浪高点(点 C)低于其后极值的高点(点 А)*/
class=class="str">"cmt">/*class="num">2. 修正浪低点索引(点 В)超过高点索引(点 С)*/
class=class="str">"cmt">/*class="num">3. 修正走势持续时间为 class="num">2 到 class="num">6 根柱线(从点 В 开始的柱线数)*/
  class="type">bool Model_downtrend_base_tf=(
                           class=class="str">"cmt">/*class="num">1.*/High_Corr_wave_downtrend_base_tf_double<High_base_tf[UpperFractal_downtrend_base_tf] && 
                           class=class="str">"cmt">/*class="num">2.*/Low_Corr_wave_downtrend_base_tf>High_Corr_wave_downtrend_base_tf && 
                           class=class="str">"cmt">/*class="num">3.*/Low_Corr_wave_downtrend_base_tf>=class="num">1 && Low_Corr_wave_downtrend_base_tf<=class="num">6
                           );
class=class="str">"cmt">//--- 对于上涨趋势
class=class="str">"cmt">/*class="num">1. 修正浪低点(点 C)高于其后的极值低点(点 А)*/
class=class="str">"cmt">/*class="num">2. 修正浪高点索引(点 В)超过低点索引(点 С)*/
class=class="str">"cmt">/*class="num">3. 修正走势持续时间为 class="num">2 到 class="num">6 根柱线(从点 В 开始的柱线数)*/
  class="type">bool Model_uptrend_base_tf=(
                       class=class="str">"cmt">/*class="num">1.*/Low_Corr_wave_uptrend_base_tf_double>Low_base_tf[LowerFractal_uptrend_base_tf] && 
                       class=class="str">"cmt">/*class="num">2.*/High_Corr_wave_uptrend_base_tf>Low_Corr_wave_uptrend_base_tf && 
                       class=class="str">"cmt">/*class="num">3.*/High_Corr_wave_uptrend_base_tf>=class="num">1 && High_Corr_wave_uptrend_base_tf<=class="num">6
                       );
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">4. 模型识别条件 (结束)                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+

「EA 开仓前的三道闸门」

做延续模型的开仓逻辑,程序至少得卡三道检查。头两道管时效性:价格突破修正低点(点 B)后若再回开仓区,不能算有效信号;第三道管仓位唯一性,强制一个模型只挂一单,避免重复吃同一波。以 AUDJPY H4 为例,虚线框定的开仓区落在点 B 与点 C 之间,价格刺穿点 B 价位后不建议追,风险会明显抬升。 第一道最简单:卖模型要求 bid 大于等于修正浪低点 Low_base_tf[Low_Corr_wave_downtrend_base_tf],买模型反过来卡 bid 小于等于对应高点。第二道用 ArrayMinimum / ArrayMaximum 从 0 号柱扫到修正浪端点,若极值柱索引和修正端点一致,说明价格没越界,可以谈入场;不一致就是走势提前发动、进场已晚。 第三道防重复开仓,思路是顺当前图表已有持仓找最近极值柱(点 C 侧),把它时间和本模型点 C 时间比对,撞上了就禁开。外汇和贵金属杠杆高,这类模型失效时回撤可能很快,上 MT5 跑前先把点 B/C 的索引打印出来核对。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">5.1 在开仓区域形成入场点控制 (开始)                                         |
class=class="str">"cmt">//+------------------------------------------------------------------------+
class=class="str">"cmt">//--- 对于下跌趋势
class="type">bool First_downtrend_control_bool=(bid>=Low_base_tf[Low_Corr_wave_downtrend_base_tf]);
class=class="str">"cmt">//--- 对于上涨趋势
class="type">bool First_uptrend_control_bool=(bid<=High_base_tf[High_Corr_wave_uptrend_base_tf]);
class=class="str">"cmt">//+------------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">5.1 在开仓区域形成入场点控制 (结束)                                         |
class=class="str">"cmt">//+------------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">5.2 价格回滚至开仓区域的控制 (开始)                                               |
class=class="str">"cmt">//+------------------------------------------------------------------------------+
class=class="str">"cmt">//--- 对于下跌趋势
class=class="str">"cmt">//在 #class="num">0 柱线和修正走势的低点之间寻找价格最低的柱线
  class="type">int Second_downtrend_control_int=ArrayMinimum(Low_base_tf,class="num">0,Low_Corr_wave_downtrend_base_tf+class="num">1);
class=class="str">"cmt">//如果当前柱线的低位低于修正走势的低位
  if(Low_base_tf_0[class="num">0]<Low_base_tf[Second_downtrend_control_int])
    {
      Second_downtrend_control_int=class="num">0; class=class="str">"cmt">//this means the minimum is on bar class="num">0
    }
class=class="str">"cmt">//如果价格最低的柱线与修正走势的低点匹配,则这是相同的柱线
class=class="str">"cmt">//这意味着价格没有超越开仓区域
  class="type">bool Second_downtrend_control_bool=(Second_downtrend_control_int==Low_Corr_wave_downtrend_base_tf);
class=class="str">"cmt">//---
class=class="str">"cmt">//--- 对于上涨趋势
class=class="str">"cmt">//在 #class="num">0 柱线和修正走势的高点之间寻找价格最高的柱线
  class="type">int Second_uptrend_control_int=ArrayMaximum(High_base_tf,class="num">0,High_Corr_wave_uptrend_base_tf+class="num">1);
  class=class="str">"cmt">//如果当前柱线的高位超越修正走势的高位
  if(High_base_tf_0[class="num">0]>High_base_tf[Second_uptrend_control_int])
    {
      Second_uptrend_control_int=class="num">0;class=class="str">"cmt">//this means maximum on bar class="num">0
    }
class=class="str">"cmt">//如果价格最高的柱线与修正走势的高点匹配,则这是相同的柱线
class=class="str">"cmt">//这意味着价格没有超越开仓区域
  class="type">bool Second_uptrend_control_bool=(Second_uptrend_control_int==High_Corr_wave_uptrend_base_tf);
class=class="str">"cmt">//+-----------------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">5.2 价格回滚至开仓区域的控制 (结束)                                              |
class=class="str">"cmt">//+-----------------------------------------------------------------------------+
class=class="str">"cmt">//+---------------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">5.3.class="num">1 对于卖出 (开始)                                                       |
class=class="str">"cmt">//+---------------------------------------------------------------------------+
class=class="str">"cmt">//--- 声明变量
  class="type">int Bar_sell_base_tf,High_Corr_wave_downtrend_base_tf_sell;
  class="type">bool Third_downtrend_control_bool=class="kw">false;
class=class="str">"cmt">//--- 迭代全部持仓
  if(PositionsTotal()>class="num">0)
    {
      for(i=class="num">0;i<=PositionsTotal();i++)
        {
         if(PositionGetTicket(i))
           {
            class=class="str">"cmt">//--- 定义仓位品种,时间和类型
            P_symbol=class="type">class="kw">string(PositionGetString(POSITION_SYMBOL));

定位空单修正高点的柱线逻辑

在 MT5 的持仓遍历里,先通过 PositionGetInteger 抓出持仓品种与类型,只处理当前图表符号且 P_type==1(卖出持仓)的分支。接着用 iBarShift 把开仓时间映射到基础周期柱线序号 Bar_sell_base_tf,这是后续找修正走势高点的锚点。 若 Bar_sell_base_tf==0,说明空单开在当下这根基础周期 K 线。此时直接比 High_base_tf_0[0] 与后两根的高点:若同时大于 High_base_tf[1] 和 High_base_tf[2],当前柱即判为修正高点;否则进入 for 循环从 n=0 扫到 bars_base_tf,遇到 High_base_tf[n] 大于相邻两根即 break,把序号赋给 High_Corr_wave_downtrend_base_tf_sell。 控制条件 Third_downtrend_control_bool 只做一件事:校验刚才找到的高点柱时间 Time_base_tf[...] 是否等于预设的 High_Corr_wave_downtrend_base_tf_time。外汇与贵金属杠杆高,这种基于柱线极值的判定在跳空或点差扩大时可能失效,建议你在 MT5 策略测试器用 EURUSD 15 分钟数据跑一遍观察命中率。

MQL5 / C++
P_type=class="type">int(PositionGetInteger(POSITION_TYPE));
P_opentime=class="type">int(PositionGetInteger(POSITION_TIME));
class=class="str">"cmt">//--- 如果持仓品种与当前图表匹配,且交易类型为“卖出”
if(P_symbol==Symbol() && P_type==class="num">1)
  {
   class=class="str">"cmt">//--- 寻找开仓所在的柱线
   Bar_sell_base_tf=iBarShift(Symbol(),base_tf,P_opentime);
   class=class="str">"cmt">//--- 从中寻找修正走势高点
   class=class="str">"cmt">//如果在当前柱线上有开仓,
   if(Bar_sell_base_tf==class="num">0)
     {
      class=class="str">"cmt">//且当前柱线是一个极值
      if(High_base_tf_0[Bar_sell_base_tf]>High_base_tf[Bar_sell_base_tf+class="num">1] && High_base_tf_0[Bar_sell_base_tf]>High_base_tf[Bar_sell_base_tf+class="num">2])
        {
         High_Corr_wave_downtrend_base_tf_sell=Bar_sell_base_tf;class=class="str">"cmt">//修正走势高点等于当前柱线
        }
      else
        {
         class=class="str">"cmt">//如果当前柱线不是极值,则启动循环搜索极值
         for(n=Bar_sell_base_tf; n<(bars_base_tf);n++)
           {
            if(High_base_tf[n]>High_base_tf[n+class="num">1] && High_base_tf[n]>High_base_tf[n+class="num">2])class=class="str">"cmt">//如果发现极值
              class="kw">break;class=class="str">"cmt">//循环中断
           }
         High_Corr_wave_downtrend_base_tf_sell=n;
        }
      class=class="str">"cmt">//--- 描述控制条件
      Third_downtrend_control_bool=(
      class=class="str">"cmt">/*class="num">1. 发现自开仓以来修正走势高点的时间
          匹配当前修正走势高点的时间*/Time_base_tf[High_Corr_wave_downtrend_base_tf_sell]==High_Corr_wave_downtrend_base_tf_time
      );
     }
   class=class="str">"cmt">//--- 如果开仓并非在当前柱线上

◍ 从持仓回推修正波极点

在下跌持仓的管理逻辑里,先判断基准周期开仓柱线编号 Bar_sell_base_tf 不为 0 且不为 1000(1000 通常用作“未初始化”占位),再从该柱向后扫描,找第一个满足 High[n] 同时大于前两根 High[n+1]、High[n+2] 的局部高点,把索引写进 High_Corr_wave_downtrend_base_tf_sell 并 break。这一步只认“右侧两根更低”的裸价格结构,不依赖任何指标。 随后用 Time_base_tf[该索引] 是否等于已记录的修正高点时间 High_Corr_wave_downtrend_base_tf_time,来置位 Third_downtrend_control_bool。若两者时间一致,说明自开仓以来的这段修正反弹高点仍在“未被后续刷新”的状态,持仓的 downtrend 控制条件可能延续。 买入侧对称处理:声明 Bar_buy_base_tf 与 Low_Corr_wave_uptrend_base_tf_buy,Third_uptrend_control_bool 初值 false。遍历 PositionsTotal() 返回的持仓,用 PositionGetTicket(i) 取票,再读 SYMBOL / TYPE / TIME;当 P_symbol==Symbol() 且 P_type==0(买入)时,才进入“寻找开仓所在柱线”的分支。外汇与贵金属杠杆品种跳空频繁,这种靠柱线高低的判定在重大数据窗可能失效,实盘前请在 MT5 策略测试器用真实点差回放验证。

MQL5 / C++
if(Bar_sell_base_tf!=class="num">0 && Bar_sell_base_tf!=class="num">1000)
  {
  class=class="str">"cmt">//--- 启动循环检测极值柱线
  for(n=Bar_sell_base_tf; n<(bars_base_tf);n++)
    {
    class=class="str">"cmt">//--- 如果找到极值
    if(High_base_tf[n]>High_base_tf[n+class="num">1] && High_base_tf[n]>High_base_tf[n+class="num">2])
      class="kw">break;class=class="str">"cmt">//循环中断
    }
  High_Corr_wave_downtrend_base_tf_sell=n;
  }
Third_downtrend_control_bool=(
  class=class="str">"cmt">/*class="num">1. 发现自开仓以来修正走势高点的时间
     匹配当前修正走势高点的时间*/Time_base_tf[High_Corr_wave_downtrend_base_tf_sell]==High_Corr_wave_downtrend_base_tf_time
  );
class=class="str">"cmt">//+---------------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">5.3.class="num">1 对于卖出 (结束)                                                       |
class=class="str">"cmt">//+---------------------------------------------------------------------------+
class=class="str">"cmt">//+---------------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">5.3.class="num">2 对于买入 (开始)                                                       |
class=class="str">"cmt">//+---------------------------------------------------------------------------+
class=class="str">"cmt">//--- 声明变量
  class="type">int Bar_buy_base_tf,Low_Corr_wave_uptrend_base_tf_buy;
  class="type">bool Third_uptrend_control_bool=class="kw">false;
class=class="str">"cmt">//--- 迭代全部持仓
  if(PositionsTotal()>class="num">0)
  {
  for(i=class="num">0;i<=PositionsTotal();i++)
    {
    if(PositionGetTicket(i))
      {
      class=class="str">"cmt">//定义仓位品种,类型和时间
      P_symbol=class="type">class="kw">string(PositionGetString(POSITION_SYMBOL));
      P_type=class="type">int(PositionGetInteger(POSITION_TYPE));
      P_opentime=class="type">int(PositionGetInteger(POSITION_TIME));
      class=class="str">"cmt">//如果持仓品种与当前图表和买入交易类型一致
      if(P_symbol==Symbol() && P_type==class="num">0)
        {
        class=class="str">"cmt">//寻找开仓所在的柱线

「定位多头修正低点的柱线索引」

在基础周期上确认买入信号后,需要把开仓时刻映射回基础周期的 K 线序号,才能继续找修正走势的低点。iBarShift 用开仓时间 P_opentime 反查当前品种、基础周期下的柱线偏移,得到 Bar_buy_base_tf。 若 Bar_buy_base_tf 为 0,说明开仓就发生在基础周期最新一根柱线上。此时先判断这根柱线的低点是否同时低于前两根:Low_base_tf_0[0] < Low_base_tf[1] 且 < Low_base_tf[2],满足则直接把 0 记为修正低点;不满足就进入 for 循环从 0 往后扫,直到某根 n 满足 Low_base_tf[n] 低于 n+1 与 n+2 才 break,把该 n 作为低点索引。 如果开仓不在最新柱(Bar_buy_base_tf 非 0 且非 1000 这种异常值),同样从 Bar_buy_base_tf 起循环检索后续柱线极值。控制变量 Third_uptrend_control_bool 只做一件事:比对已记录的修正低点时间 Low_Corr_wave_uptrend_base_tf_time 与 Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy] 是否相等,用来确认低点未被更新覆盖。 外汇与贵金属杠杆交易高风险,这套索引逻辑仅用于回测或多周期验证,实盘信号失效概率随周期切换显著上升。

MQL5 / C++
Bar_buy_base_tf=iBarShift(Symbol(),base_tf,P_opentime);
class=class="str">"cmt">//从中寻找修正走势低点
class=class="str">"cmt">//如果在当前柱线上有开仓,
if(Bar_buy_base_tf==class="num">0)
  {
  class=class="str">"cmt">//且当前柱线是一个极值
  if(Low_base_tf_0[Bar_buy_base_tf]<Low_base_tf[Bar_buy_base_tf+class="num">1] && Low_base_tf_0[Bar_buy_base_tf]<Low_base_tf[Bar_buy_base_tf+class="num">2])
    {
    Low_Corr_wave_uptrend_base_tf_buy=Bar_buy_base_tf;
    }
  else
    {
    class=class="str">"cmt">//如果当前柱线不是极值,则启动循环搜索极值
    for(n=Bar_buy_base_tf; n<(bars_base_tf);n++)
      {
      if(Low_base_tf[n]<Low_base_tf[n+class="num">1] && Low_base_tf[n]<Low_base_tf[n+class="num">2])class=class="str">"cmt">//如果发现极值
        class="kw">break;class=class="str">"cmt">//循环中断
      }
    Low_Corr_wave_uptrend_base_tf_buy=n;
    }
  class=class="str">"cmt">//--- 描述控制条件
  Third_uptrend_control_bool=(
    class=class="str">"cmt">/*class="num">1. 发现自开仓以来修正走势低点的时间
      匹配当前修正走势低点的时间*/Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy]==Low_Corr_wave_uptrend_base_tf_time
    );
  }
class=class="str">"cmt">//--- 如果开仓并非在当前柱线上
if(Bar_buy_base_tf!=class="num">0 && Bar_buy_base_tf!=class="num">1000)
  {
  class=class="str">"cmt">//--- 启动循环检测极值柱线
  for(n=Bar_buy_base_tf; n<(bars_base_tf);n++)
    {
    class=class="str">"cmt">//--- 如果找到极值

上升修正低点定位与第三条件锁定

在上升趋势持仓过程中,需要动态确认自开仓以来修正走势的最低点位置。代码通过逆向遍历基础周期低点数组 Low_base_tf,当发现某根 K 线的低点同时小于其后两根 K 线低点时,即判定该位置为修正波段的局部谷底,并用 break 中断循环。 具体看这段判断:若 Low_base_tf[n] < Low_base_tf[n+1] 且 < Low_base_tf[n+2],则 n 被记录为 Low_Corr_wave_uptrend_base_tf_buy,代表修正低点索引。该索引对应的时间 Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy] 会与外部传入的 Low_Corr_wave_uptrend_base_tf_time 比较,相等时 Third_uptrend_control_bool 才置真。 实盘意义在于:只有修正低点时间与记录时间吻合,第三道上升趋势控制条件才成立,过滤掉价格假破后低点漂移的干扰。外汇与贵金属波动剧烈,该条件仅降低误判概率,不保证持仓一定盈利。

MQL5 / C++
if(Low_base_tf[n]<Low_base_tf[n+class="num">1] && Low_base_tf[n]<Low_base_tf[n+class="num">2])
            class="kw">break;class=class="str">"cmt">//循环中断
         }
         Low_Corr_wave_uptrend_base_tf_buy=n;
       }
       class=class="str">"cmt">//--- 描述控制条件
       Third_uptrend_control_bool=(
                           class=class="str">"cmt">/*class="num">1. 发现自开仓以来修正走势低点的时间
                            匹配当前修正走势低点的时间*/Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy]==Low_Corr_wave_uptrend_base_tf_time
                          );
     }
   }
  }
 }
class=class="str">"cmt">//+---------------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">5.3.class="num">2 对于买入 (结束)                                                       |
class=class="str">"cmt">//+---------------------------------------------------------------------------+

◍ 用SAR翻转卡住操作周期入场点

入场信号只在操作周期 work_tf 上判定,这样做的直接好处是进场更及时,若趋势顺势启动,止损点数有可能压得更小。 核心读数是抛物线 SAR:当前柱的 SAR 值高于该柱高点、且前一根柱的 SAR 低于那根柱低点,就是空头发力的信号;反过来,前一根 SAR 在高点之上、当前 SAR 被低点击穿,才考虑买入。 下面这段布尔逻辑把上述规则直接落进 EA。注意它比较的是 #1 已完成柱与 #0 形成中柱,实盘里 #0 条件成立瞬间就触发,存在未来函数式的重绘风险,需用收盘价过滤或仅允许柱收尾后进场。 [CODE] //+------------------------------------------------------------------+

//6. 描述市场准入条件 (开始)

//+------------------------------------------------------------------+ //--- 对于卖出 bool PointSell_work_tf_bool=( /*1. #1 柱线低点超越 iSar[1]*/Low_work_tf[1]>Sar_array_work_tf[1] && /*2. #0 柱线高点低于 iSar[0]*/High_work_tf_0[0]<Sar_array_work_tf_0[0] ); //--- 对于买入 bool PointBuy_work_tf_bool=( /*1. #1 柱线高点低于 iSar*/High_work_tf[1]<Sar_array_work_tf[1] && /*2. #0 柱线低点超越 iSar[0]*/Low_work_tf_0[0]>Sar_array_work_tf_0[0] ); //+------------------------------------------------------------------+

//6. 描述市场准入条件 (结束)

//+------------------------------------------------------------------+ [/CODE] 逐行拆解:第 1 个条件 Low_work_tf[1]>Sar_array_work_tf[1] 表示前一根柱低点站上 SAR,说明上一根还是多头防守失败;第 2 个条件 High_work_tf_0[0]<Sar_array_work_tf_0[0] 表示当前柱高点被 SAR 压住,翻转确认。买入组完全镜像,High 与 Low 角色对调。外汇与贵金属波动剧烈,SAR 在震荡市会频繁假翻转,触发概率高但胜率可能偏低,务必先在 MT5 策略测试器跑历史数据验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">6. 描述市场准入条件 (开始)                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- 对于卖出
  class="type">bool PointSell_work_tf_bool=(
                          class=class="str">"cmt">/*class="num">1. #class="num">1 柱线低点超越 iSar[class="num">1]*/Low_work_tf[class="num">1]>Sar_array_work_tf[class="num">1] && 
                          class=class="str">"cmt">/*class="num">2. #class="num">0 柱线高点低于 iSar[class="num">0]*/High_work_tf_0[class="num">0]<Sar_array_work_tf_0[class="num">0]
                          );
class=class="str">"cmt">//--- 对于买入
  class="type">bool PointBuy_work_tf_bool=(
                         class=class="str">"cmt">/*class="num">1. #class="num">1 柱线高点低于 iSar*/High_work_tf[class="num">1]<Sar_array_work_tf[class="num">1] && 
                         class=class="str">"cmt">/*class="num">2. #class="num">0 柱线低点超越 iSar[class="num">0]*/Low_work_tf_0[class="num">0]>Sar_array_work_tf_0[class="num">0]
                         );
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">6. 描述市场准入条件 (结束)                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+

「把散落条件收进一个开关变量」

前面几步分别算出了趋势模型、三层控制开关和 work_tf 上的入场信号,但它们各自独立。这一步要做的,是把它们用逻辑与拼成一个布尔量,让后续下单判断只看 OpenBuy / OpenSell 一个变量。 代码里卖出条件要求:基础周期 downtrend 模型成立、前两层下跌控制为真、第三层控制为假、且 work_tf 上出现 PointSell 信号。五个子条件全真,OpenSell 才置 true,任一不满足就开不了空。 买入对称:uptrend 模型、前两层上涨控制为真、第三层控制为假、PointBuy 信号为真,OpenBuy 才为真。注意第三层控制两边都是 ==false,意味着该层起的是“封锁”作用,触发即禁开。 在 MT5 里把这段直接贴进 EA,打印 OpenSell 和 OpenBuy 到日志,跑一周 EURUSD 的 M15,你能看到多数时间两者同为假——说明模型+三层过滤的合取很严,信号本身就稀。外汇和贵金属波动大、杠杆高,这种低频率更该配合仓位上限来控风险。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">7. 描述交易条件 (开始)                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- 对于卖出
   class="type">bool OpenSell=(
                      class=class="str">"cmt">/*class="num">1. 模型形成*/Model_downtrend_base_tf==true && 
                      class=class="str">"cmt">/*class="num">2. 控制 class="num">1 允许开仓*/First_downtrend_control_bool==true && 
                      class=class="str">"cmt">/*class="num">3. 控制 class="num">2 允许开仓*/Second_downtrend_control_bool==true && 
                      class=class="str">"cmt">/*class="num">4. 控制 class="num">3 允许开仓*/Third_downtrend_control_bool==class="kw">false && 
                      class=class="str">"cmt">/*class="num">5. 入场点 work_tf*/PointSell_work_tf_bool==true
                      );
class=class="str">"cmt">//--- 对于卖出
   class="type">bool OpenBuy=(
                   class=class="str">"cmt">/*class="num">1. 模型形成*/Model_uptrend_base_tf==true && 
                   class=class="str">"cmt">/*class="num">2. 控制 class="num">1 允许开仓*/First_uptrend_control_bool==true && 
                   class=class="str">"cmt">/*class="num">3. 控制 class="num">2 允许开仓*/Second_uptrend_control_bool==true && 
                   class=class="str">"cmt">/*class="num">4. 控制 class="num">3 允许开仓*/Third_uptrend_control_bool==class="kw">false && 
                   class=class="str">"cmt">/*class="num">5. 入场点 work_tf*/PointBuy_work_tf_bool==true
                   );
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">7. 描述交易条件 (结束)                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+

把风控映射成具体手数与挂单

一套交易操作在 MT5 里通常拆成三件事:按信号开仓、给持仓补止盈、价格越过成本后把止损推到盈亏平衡。前两件事必须在同一次 tick 逻辑里算清,否则滑点会把你的风险预算撕开。 下面这段逻辑先根据相关性波浪的高低点加减点差定出止损价,再用固定风险金额反推手数。注意 RiskSize_points 乘了系数 f,若结果为 0 会强制置 1,避免后续除零崩脚本。 开仓时手数用 NormalizeDouble(Lot,2) 限制到两位小数,止损价保留五位小数,这对外汇和贵金属都适用,但贵金属点差跳变时可能触发异常成交,属高风险操作。 止盈部分遍历当前所有持仓,逐个读出 SL、开仓价、TP 与品种名,为后面按波动结构挂 TP 做准备。你可以直接把这段代码塞进 EA 的 OnTick 里,开 MT5 用欧美对 1 小时图跑一遍看手数是否随止损距离反向变动。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">8. 处理交易操作 (开始)                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- 定义止损位
  SLPrice_sell=High_Corr_wave_downtrend_base_tf_double+spread;
  SLPrice_buy=Low_Corr_wave_uptrend_base_tf_double-spread;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">8.1 设置仓位 (开始)                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- 对于卖出
  if(OpenSell==true)
    {
      RiskSize_points=(SLPrice_sell-bid)*f;class=class="str">"cmt">//止损定义为整数点数
      if(RiskSize_points==class="num">0)class=class="str">"cmt">//检查除零
        {
         RiskSize_points=class="num">1;
        }
      CostOfPoint_position=SummRisk/RiskSize_points;class=class="str">"cmt">//参考止损定义仓位价格点数
      Lot=CostOfPoint_position/CostOfPoint;class=class="str">"cmt">//计算开仓手数
      class=class="str">"cmt">//开仓
      trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(Lot,class="num">2),bid,NormalizeDouble(SLPrice_sell,class="num">5),class="num">0,"");
    }
class=class="str">"cmt">//--- 对于买入
  if(OpenBuy==true)
    {
      RiskSize_points=(bid-SLPrice_buy)*f;class=class="str">"cmt">//止损定义为整数点数
      if(RiskSize_points==class="num">0)class=class="str">"cmt">//检查除零
        {
         RiskSize_points=class="num">1;
        }
      CostOfPoint_position=SummRisk/RiskSize_points;class=class="str">"cmt">//参考止损定义仓位价格点数
      Lot=CostOfPoint_position/CostOfPoint;class=class="str">"cmt">//计算开仓手数
      class=class="str">"cmt">//开仓
      trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(Lot,class="num">2),ask,NormalizeDouble(SLPrice_buy,class="num">5),class="num">0,"");
    }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">8.1 设置仓位 (结束)                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">8.2 设置止盈 (开始)                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
  if(TP_mode==true)
    {
      if(PositionsTotal()>class="num">0)
        {
         for(i=class="num">0;i<=PositionsTotal();i++)
           {
            if(PositionGetTicket(i))
              {
               class=class="str">"cmt">//获取持仓值
               SL_double=class="type">class="kw">double (PositionGetDouble(POSITION_SL));
               OP_double=class="type">class="kw">double (PositionGetDouble(POSITION_PRICE_OPEN));
               TP_double=class="type">class="kw">double (PositionGetDouble(POSITION_TP));
               P_symbol=class="type">class="kw">string(PositionGetString(POSITION_SYMBOL));

◍ 按风险倍数自动补止盈与移保本

这段逻辑解决一个常见痛点:开仓时只挂了止损、没设止盈,或者跑了一段想锁成本。EA 在遍历持仓时先抓取品种、类型、利润、ticket 与开仓时间,只处理当前图表品种 Symbol() 的仓位。 对买单(P_type==0)若发现 TP_double==0,就用开仓价 OP_double 减止损价 SL_double 得到止损点数,乘以输入参数 M 作为止盈倍数,推倒出 TP_price_buy 后调用 trade.PositionModify 把止盈补上,价格用 NormalizeDouble 精确到 5 位。卖单(P_type==1)对称处理,止盈价落在开仓价下方。 保本部分另起一段:Size_Summ = breakeven * SummRisk,当 Breakeven_mode 为 true 且 breakeven 不为 0、且有持仓时触发。这里 breakeven 是风险倍数,SummRisk 是单笔风险金额,乘积就是触发平移的盈利阈值。外汇与贵金属杠杆高,这类自动修改若遇点差扩大或报价跳空,modify 可能失败,需在实盘前用 MT5 策略测试器验证返回码。 让小布替你跑这套 把 M 设成 2、breakeven 设成 1,意味着止损 20 点则自动补 40 点止盈,浮盈覆盖一倍风险后移保本;直接拷下面代码到 EA 的持仓循环里就能测。

MQL5 / C++
P_type=class="type">int(PositionGetInteger(POSITION_TYPE));
P_profit=class="type">class="kw">double (PositionGetDouble(POSITION_PROFIT));
P_ticket=class="type">int (PositionGetInteger(POSITION_TICKET));
P_opentime=class="type">int(PositionGetInteger(POSITION_TIME));
if(P_symbol==Symbol())
  {
   if(P_type==class="num">0 && TP_double==class="num">0)
     {
      class="type">class="kw">double SL_size_buy=OP_double-SL_double;class=class="str">"cmt">//定义止损点数
      class="type">class="kw">double TP_size_buy=SL_size_buy*M;class=class="str">"cmt">//止损乘以输入中设置的比率
      class="type">class="kw">double TP_price_buy=OP_double+TP_size_buy;class=class="str">"cmt">//定义止盈位
      class=class="str">"cmt">//修改持仓
      trade.PositionModify(PositionGetInteger(POSITION_TICKET),SL_double,NormalizeDouble(TP_price_buy,class="num">5));
     }
   if(P_type==class="num">1 && TP_double==class="num">0)
     {
      class="type">class="kw">double SL_size_sell=SL_double-OP_double;class=class="str">"cmt">//定义止损点数
      class="type">class="kw">double TP_size_sell=SL_size_sell*M;class=class="str">"cmt">//止损乘以输入中设置的比率
      class="type">class="kw">double TP_price_sell=OP_double-TP_size_sell;class=class="str">"cmt">//定义止盈位
      class=class="str">"cmt">//修改持仓
      trade.PositionModify(PositionGetInteger(POSITION_TICKET),SL_double,NormalizeDouble(TP_price_sell,class="num">5));
     }
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">8.2 设置止盈 (结束)                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">8.3 持仓移至盈亏平衡点 (开始)                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double Size_Summ=breakeven*SummRisk;class=class="str">"cmt">//定义止盈位,之后应将持仓移至盈亏平衡点
if(Breakeven_mode==true && breakeven!=class="num">0)
  {
   if(PositionsTotal()>class="num">0)
     {

「把盈利单推到开仓价保本」

这段逻辑干的事很直接:遍历当前账户所有持仓,把已经跑出足够利润、且止损还压在不利位置的单子,统一把止损拉到开仓价,实现「浮盈不为负」的保本结构。 循环用 PositionsTotal() 取总持仓数,PositionGetTicket(i) 拿到对应持仓的票据号,再从持仓属性里读出 SL、开仓价、TP、品种、类型、利润、票号与开仓时间。注意 P_symbol==Symbol() 这层过滤,只处理当前图表品种,避免跨品种误改。 核心判定分多空两条:P_type==0 为多单,要求 P_profit>=Size_Summ 且 SL_double<OP_double(止损低于开仓价)才改;P_type==1 为空单,要求 P_profit>=Size_Summ 且 SL_double>OP_double(止损高于开仓价)才改。满足条件就调 trade.PositionModify 把止损设成 OP_double,TP 保持不变。 Size_Summ 是外部定义的「触发保本的最小利润额」,单位与账户净值一致。把它设成 0 理论上入场即保本,但实战中点差和滑点可能让修改失败;外汇与贵金属杠杆高,保本后仍可能被瞬扫止损,需自行在 MT5 里跑这段验证触发频率。

MQL5 / C++
for(i=class="num">0;i<=PositionsTotal();i++)
  {
   if(PositionGetTicket(i))
     {
      class=class="str">"cmt">//获取持仓值
      SL_double=class="type">class="kw">double (PositionGetDouble(POSITION_SL));
      OP_double=class="type">class="kw">double (PositionGetDouble(POSITION_PRICE_OPEN));
      TP_double=class="type">class="kw">double (PositionGetDouble(POSITION_TP));
      P_symbol=class="type">class="kw">string(PositionGetString(POSITION_SYMBOL));
      P_type=class="type">int(PositionGetInteger(POSITION_TYPE));
      P_profit=class="type">class="kw">double (PositionGetDouble(POSITION_PROFIT));
      P_ticket=class="type">int (PositionGetInteger(POSITION_TICKET));
      P_opentime=class="type">int(PositionGetInteger(POSITION_TIME));
      if(P_symbol==Symbol())
        {
         if(P_type==class="num">0 && P_profit>=Size_Summ && SL_double<OP_double)
           {
            trade.PositionModify(PositionGetInteger(POSITION_TICKET),OP_double,TP_double);
           }
         if(P_type==class="num">1 && P_profit>=Size_Summ && SL_double>OP_double)
           {
            trade.PositionModify(PositionGetInteger(POSITION_TICKET),OP_double,TP_double);
           }
        }
     }
  }

把回测报告榨成统计原料

做价格行为统计前,先锁定一组固定维度:品种、成交类型、入场时间、开仓价、止损位、止损幅度、最大盈利位、最大盈利幅度、成交区间。这里有个关键假设——最大盈利点取开仓后主周期出现的第一个上/下分形高/低位,不是随口定的峰值。 实盘前得先在策略测试器里跑 EA。拿 AUDJPY 举例,回测区间 2018.01.01–2018.08.29,主时间帧 D1、操作时间帧 H6,每笔风险 $100,持仓移到盈亏平衡用 1/2、止盈用 1/3 的风险回报比。跑完把报告存成 CSV,在终端本地文件夹建 report.csv,只留“订单”部分、删掉平仓行,保留开仓时间/品种/类型/价格/止损这几列。 接下来写个脚本读 report.csv,算出止损大小、最大盈利位、最大盈利大小、成交区间柱线数,再落进 file_stat.csv。下面这段是读分隔符 CSV 进数组的底层函数,借了现成文件读取思路,你直接抄进 OnStart 前面就能用。 openprice 和 sl_price 数组是字符串型,计算前要用 StringReplace 把逗号换成小数点再 StringToDouble,否则小数位会丢。分形定位用 iFractals 句柄,开仓柱索引拿到后向两侧找最近上下分形;若持仓被止损打掉,最大盈利记负值、区间算到止损柱为止。最后把点号换回逗号写文件,基准帧设 D1 跑脚本就能在本地文件夹看到新 file_stat.csv。 外汇与贵金属杠杆高、回测不等于未来,AUDJPY 这类交叉盘滑点可能吞掉统计优势,上 MT5 前先想清楚样本外会怎么漂。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 读取数据至数组的函数 (开始)                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool ReadFileToArrayCSV(class="type">class="kw">string FileName,SLine  &Lines[])
  {
   ResetLastError();
   class="type">int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_CSV,";");
   if(h==INVALID_HANDLE)
     {
       class="type">int ErrNum=GetLastError();
       printf("File open error %s # %i",FileName,ErrNum);
       class="kw">return(class="kw">false);
     }
   class="type">int lcnt=class="num">0; class=class="str">"cmt">// 计算字符串的变量 
   class="type">int fcnt=class="num">0; class=class="str">"cmt">// 计算字符串字段的变量
   class="kw">while(!FileIsEnding(h))
     {
       class="type">class="kw">string str=FileReadString(h);
       class=class="str">"cmt">// 新字符串(新结构数组元素)
       if(lcnt>=ArraySize(Lines))
         { class=class="str">"cmt">// 结构数组完全填满
           ArrayResize(Lines,ArraySize(Lines)+class="num">1024); class=class="str">"cmt">// 将数组大小增加 class="num">1024 个元素
         }
       ArrayResize(Lines[lcnt].field,class="num">64);class=class="str">"cmt">// 更改结构中的数组大小
       Lines[lcnt].field[class="num">0]=str; class=class="str">"cmt">// 分配第一个字段的值
                                        class=class="str">"cmt">// 开始读取字符串中的剩余字段
    fcnt=class="num">1; class=class="str">"cmt">// 字段数组中的一个元素被占用
       class="kw">while(!FileIsLineEnding(h))
         { class=class="str">"cmt">// 读取字符串中的其余字段
          str=FileReadString(h);
          if(fcnt>=ArraySize(Lines[lcnt].field))
            { class=class="str">"cmt">// 字段数组完全填满
               ArrayResize(Lines[lcnt].field,ArraySize(Lines[lcnt].field)+class="num">64); class=class="str">"cmt">// 将数组大小增加 class="num">64 个元素
             }
          Lines[lcnt].field[fcnt]=str; class=class="str">"cmt">// 分配下一个字段的值
          fcnt++; class=class="str">"cmt">// 增加字段计数器
         }
       ArrayResize(Lines[lcnt].field,fcnt); class=class="str">"cmt">// 根据实际的字段数改变字段数组大小
       lcnt++; class=class="str">"cmt">// 增加字符串计数器
     }
   ArrayResize(Lines,lcnt); class=class="str">"cmt">// 根据实际的字符串数更改结构数组(字符串)
   FileClose(h);

◍ 把分形与账户成交拉进同一套数组

这段脚本把基准周期的分形指标和账户历史成交塞进同一组数组,方便后面做波浪统计。输入参数里 sar_step 默认 0.1、maximum_step 默认 0.11,基准周期由 base_tf 外部指定,脚本启动时会弹出输入面板。 先拿 iFractals 句柄,再把 High、Low 以及分形上下数组全部设成时间序列,用 CopyHigh / CopyLow / CopyBuffer 各拉 1000 根柱线进内存。Bars() 取当前柱数,SymbolInfoInteger 读品种小数位,按 5/4/3/2/1 位分别把价格容量 f 设成 100000 到 10,后面算点数直接乘 f 就行。 账户侧另开一堆数组:opentime 存开仓时间、openprice 存开仓价、sl_price 存止损价、down_fractal / up_fractal 存对应分形索引、sl_size_points 存止损点数、maxprofit_* 存浮盈极值、duration 存波浪持续柱数。这些容器不填数据,只是把后面要跑的循环变量先声明好,开 MT5 把这段贴进脚本头部就能直接编译过。

MQL5 / C++
class="macro">#class="kw">property script_show_inputs
class=class="str">"cmt">//--- 输入
class="kw">input ENUM_TIMEFRAMES base_tf;    class=class="str">"cmt">//基准周期时间帧
class="kw">input class="type">class="kw">double sar_step=class="num">0.1;        class=class="str">"cmt">//设置抛物线步幅
class="kw">input class="type">class="kw">double maximum_step=class="num">0.11;   class=class="str">"cmt">//设置抛物线最大步幅
class=class="str">"cmt">//--- 声明指标句柄的变量
class="type">int Fractal_base_tf;              class=class="str">"cmt">//iFractal 指标句柄
class=class="str">"cmt">//--- 声明 base_tf 变量
class="type">class="kw">double High_base_tf[],Low_base_tf[];                  class=class="str">"cmt">//用于存储柱线高/低价格的数组
class="type">class="kw">double FractalDown_base_tf[],FractalUp_base_tf[];     class=class="str">"cmt">//用于存储 iFractall 指标价格的数组
class=class="str">"cmt">//--- 数组结构
class="kw">struct SLine
  {
   class="type">class="kw">string          field[];
  };
class=class="str">"cmt">//--- 获取 iFractal 指标句柄
   Fractal_base_tf=iFractals(Symbol(),base_tf);
class=class="str">"cmt">//--- 设置 base_tf 数组的顺序为时间序列
   ArraySetAsSeries(High_base_tf,true);
   ArraySetAsSeries(Low_base_tf,true);
   ArraySetAsSeries(FractalDown_base_tf,true);
   ArraySetAsSeries(FractalUp_base_tf,true);
class=class="str">"cmt">//--- 初始填充 base_tf 数组
   CopyHigh(Symbol(),base_tf,class="num">0,class="num">1000,High_base_tf);
   CopyLow(Symbol(),base_tf,class="num">0,class="num">1000,Low_base_tf);
   CopyBuffer(Fractal_base_tf,class="num">0,TimeCurrent(),class="num">1000,FractalUp_base_tf);
   CopyBuffer(Fractal_base_tf,class="num">1,TimeCurrent(),class="num">1000,FractalDown_base_tf);
class=class="str">"cmt">//--- 用于存储柱线编号上数据的变量
   class="type">int bars_base_tf=Bars(Symbol(),base_tf);
class=class="str">"cmt">//品种价格中的小数位数
   class="type">int Digit=(class="type">int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
class=class="str">"cmt">//定义当前品种的价格容量
   class="type">class="kw">double f=class="num">1;
   if(Digit==class="num">5) {f=class="num">100000;}
   if(Digit==class="num">4) {f=class="num">10000;}
   if(Digit==class="num">3) {f=class="num">1000;}
   if(Digit==class="num">2) {f=class="num">100;}
   if(Digit==class="num">1) {f=class="num">10;}
class=class="str">"cmt">//--- 声明变量和数组
   class="type">int i,j,n; class=class="str">"cmt">//循环变量
   class="type">class="kw">datetime opentime[];class=class="str">"cmt">//用于存储仓位设定时间的数组
   class="type">class="kw">string symbol[];class=class="str">"cmt">//用于存储品种的数组
   class="type">class="kw">string type[];class=class="str">"cmt">//用于存储成交类型的数组
   class="type">class="kw">string openprice[];class=class="str">"cmt">//存储开仓价格的数组
   class="type">class="kw">string  sl_price[];class=class="str">"cmt">//存储止损价位的数组
   class="type">int index[];class=class="str">"cmt">//存储开仓所在柱线索引的数组
   class="type">int down_fractal[];class=class="str">"cmt">//存储低位分形指数的数组 
   class="type">int up_fractal[];class=class="str">"cmt">//存储高位分形指数的数组
   class="type">class="kw">double sl_size_points[];class=class="str">"cmt">//存储止损大小点数的数组
   class="type">class="kw">string maxprofit_price[];class=class="str">"cmt">//存储最大利润价位的数组
   class="type">class="kw">double maxprofit_size_points[];class=class="str">"cmt">//存储最大利润值的数组
   class="type">int duration[];class=class="str">"cmt">//存储波浪持续柱线数的数组
   class="type">bool maxprofit_bool[];class=class="str">"cmt">//确保不可由止损激活仓位的数组

「把回测 CSV 拆进数组的实战写法」

做 EA 复盘时,把 MT5 导出的 report.csv 读进程序是第一步。下面这段逻辑先建了 maxprofit_int[] 与 lines[] 两个容器,前者配合布尔数组标记最大盈利柱,后者承接逐行文本。 若 ReadFileToArrayCSV 返回 false,直接在 Experts 标签页报警,不往下跑,避免空数组越界。读成功后才用 ArraySize(lines) 统一把十几个业务数组(开仓时间、品种、类型、开价、止损等)一次性Resize,size 就是总行数。 内层四个 for 循环用不同的步长抽字段:j=j+5 取开仓时间,j=j+4 取品种,j=j+3 取成交类型,j=j+2 取开仓价,j=j+1 取止损价。这种错位步长对应 CSV 里每行的固定列序,改列结构时必须同步调步长,否则会错位写到别的数组。 最后对 openprice 做 StringReplace 把逗号转点号,是因为部分环境 CSV 用逗号做小数分隔,不转的话 (double) 强转会丢精度。外汇与贵金属波动快、点差跳变频繁,回测数据清洗这一步省不得,否则后续信号判定可能系统性偏移。

MQL5 / C++
  class="type">int maxprofit_int[];class=class="str">"cmt">//定义最小/最大柱线的数组。 它与 maxprofit_bool[] 一起使用
  SLine lines[];
  class="type">int size=class="num">0;
  if(!ReadFileToArrayCSV("report.csv",lines))
    {
      Alert("Error, see details in the \"Experts\" tab");
    }
  else
    {
      size=ArraySize(lines);
      ArrayResize(opentime,ArraySize(lines));
      ArrayResize(symbol,ArraySize(lines));
      ArrayResize(type,ArraySize(lines));
      ArrayResize(openprice,ArraySize(lines));
      ArrayResize(sl_price,ArraySize(lines));
      ArrayResize(index,ArraySize(lines));
      ArrayResize(down_fractal,ArraySize(lines));
      ArrayResize(up_fractal,ArraySize(lines));
      ArrayResize(sl_size_points,ArraySize(lines));
      ArrayResize(maxprofit_price,ArraySize(lines));
      ArrayResize(maxprofit_size_points,ArraySize(lines));
      ArrayResize(duration,ArraySize(lines));
      ArrayResize(maxprofit_bool,ArraySize(lines));
      ArrayResize(maxprofit_int,ArraySize(lines));
      for(i=class="num">0;i<size;i++)
        {
         for(j=class="num">0;j<ArraySize(lines[i].field);j=j+class="num">5)class=class="str">"cmt">//按开仓时间列选择字段
          {
          opentime[i]=(class="type">class="kw">datetime)(lines[i].field[j]);class=class="str">"cmt">//将数据写入数组
          }
         for(j=class="num">1;j<ArraySize(lines[i].field);j=j+class="num">4)class=class="str">"cmt">//按品种列选择字段
          {
          symbol[i]=(lines[i].field[j]);class=class="str">"cmt">//将数据写入数组
          }
         for(j=class="num">2;j<ArraySize(lines[i].field);j=j+class="num">3)class=class="str">"cmt">//按成交类型列选择字段
          {
          type[i]=(lines[i].field[j]);class=class="str">"cmt">//将数据写入数组
          }
         for(j=class="num">3;j<ArraySize(lines[i].field);j=j+class="num">2)class=class="str">"cmt">//按开仓价格列选择字段
          {
          openprice[i]=(lines[i].field[j]);class=class="str">"cmt">//将数据写入数组
          }
         for(j=class="num">4;j<ArraySize(lines[i].field);j=j+class="num">1)class=class="str">"cmt">//按止损列选择字段
          {
          sl_price[i]=(lines[i].field[j]);class=class="str">"cmt">//将数据写入数组
          }
        }
    }
class=class="str">"cmt">//-----------------------------------------------------
  for(i=class="num">0;i<size;i++)
    {
      StringReplace(openprice[i],",",".");

分形与止损间距的批量标定

这段逻辑干的事很直接:把每笔历史订单的开仓时间映射到 D1 柱索引,再向前回溯最近的卖出分形(FractalDown)和买入分形(FractalUp),把位置写进 down_fractal[] / up_fractal[] 数组。 回溯用 iBarShift(Symbol(), PERIOD_D1, opentime[i]) 拿到开仓柱序号,随后从该柱往 0 号柱方向扫,遇到分形值非 EMPTY_VALUE 就 break。注意循环条件是 n>0,意味着若一直没碰到分形,会停在 1 号柱而非 0 号。 止损点数按品种报价因子 f 换算:sell 单为 (sl_price - openprice)*f,buy 单反过来。这里 sl_price 和 openprice 都是字符串,必须靠 StringToDouble 转双精度,否则直接参与运算会得到 0 或乱值。 为防止止损在摸到最大利润前就被触发,代码又扫了一遍:sell 单从开仓柱向下分形之间逐根比对 High_base_tf[n] >= sl_price,一旦某根高点够到止损价就记下 maxprofit_int[i]=n;若循环跑到 n==down_fractal[i] 才停,maxprofit_bool[i] 置真,说明这段区间价格从未威胁止损。外汇与贵金属杠杆高,这类回溯只是历史标定,实盘触发概率仍随波动变化。 开 MT5 把 FractalDown_base_tf / FractalUp_base_tf 数组接上对应周期缓冲,就能直接复用这套索引映射验证自己的订单历史。

MQL5 / C++
StringReplace(sl_price[i],",",".");
class=class="str">"cmt">//--- 定义开仓所在柱线的索引
for(i=class="num">0;i<size;i++)
  {
   index[i]=iBarShift(Symbol(),PERIOD_D1,opentime[i]);class=class="str">"cmt">//将数据写入数组
  }
class=class="str">"cmt">//--- 搜索卖出分形
for(i=class="num">0;i<size;i++)
  {
   if(type[i]=="sell")
     {
      for(n=index[i];n>class="num">0;n--)
        {
         if(FractalDown_base_tf[n]!=EMPTY_VALUE)
           class="kw">break;
        }
      down_fractal[i]=n;
     }
  }
class=class="str">"cmt">//--- 搜索买入分形
for(i=class="num">0;i<size;i++)
  {
   if(type[i]=="buy")
     {
      for(n=index[i];n>class="num">0;n--)
        {
         if(FractalUp_base_tf[n]!=EMPTY_VALUE)
           class="kw">break;
        }
      up_fractal[i]=n;
     }
  }
class=class="str">"cmt">//--- 止损点数
for(i=class="num">0;i<size;i++)
  {
   if(type[i]=="sell")
     {
      sl_size_points[i]=(StringToDouble(sl_price[i])-StringToDouble(openprice[i]))*f;
     }
   if(type[i]=="buy")
     {
      sl_size_points[i]=(StringToDouble(openprice[i])-StringToDouble(sl_price[i]))*f;
     }
  }
class=class="str">"cmt">//--- 确保在达到最大利润之前,不会由止损平仓
class=class="str">"cmt">//--- 对于卖出
for(i=class="num">0;i<size;i++)
  {
   if(type[i]=="sell")
     {
      for(n=index[i];n>down_fractal[i];n--)
        {
         if(High_base_tf[n]>=StringToDouble(sl_price[i]))
           class="kw">break;
        }
      maxprofit_int[i]=n;
      maxprofit_bool[i]=(n==down_fractal[i]);
     }
  }
class=class="str">"cmt">//--- 对于买入
for(i=class="num">0;i<size;i++)
  {
   if(type[i]=="buy")
     {
      for(n=index[i];n>up_fractal[i];n--)
        {
         if(Low_base_tf[n]<=StringToDouble(sl_price[i]))
           class="kw">break;
        }
      maxprofit_int[i]=n;
      maxprofit_bool[i]=(n==up_fractal[i]);
     }
  }
class=class="str">"cmt">//--- 最大盈利价位
for(i=class="num">0;i<size;i++)
  {

◍ 分方向回填最大盈利与持仓时长

这段逻辑按成交方向(buy/sell)和是否触达基准分形(maxprofit_bool)分别回填价格、点数和持续柱数,是统计模块里最易写错分支的地方。 先看价格与点数:卖单若触达下方分形,maxprofit_price 取 Low_base_tf[down_fractal[i]],点数 = (开仓价 - 该低点) * f;未触达则价格置空、点数取 sl_size_points 的相反数。买单对称处理,用 High_base_tf[up_fractal[i]] 减开仓价。 持仓时长 duration 也分四种情况:触达分形的卖单用 index[i] 减 down_fractal 索引,买单减 up_fractal 索引;未触达的统一用 index[i] 减 maxprofit_int[i],即止损或退出时的柱索引。 最后一轮把 openprice、sl_price、maxprofit_price 里的小数点替换成逗号,适配部分区域的小数格式再写文件。外汇与贵金属波动剧烈,这类统计仅作历史分布参考,实盘仍属高风险。

MQL5 / C++
if(type[i]=="sell" && maxprofit_bool[i]==true)
  {
   maxprofit_price[i]=(class="type">class="kw">string)Low_base_tf[down_fractal[i]];
  }
if(type[i]=="sell" && maxprofit_bool[i]==class="kw">false)
  {
   maxprofit_price[i]="";
  }
if(type[i]=="buy" && maxprofit_bool[i]==true)
  {
   maxprofit_price[i]=(class="type">class="kw">string)High_base_tf[up_fractal[i]];
  }
if(type[i]=="buy" && maxprofit_bool[i]==class="kw">false)
  {
   maxprofit_price[i]="";
  }
 }
 for(i=class="num">0;i<size;i++)
  {
   if(type[i]=="sell" && maxprofit_bool[i]==true)
     {
      maxprofit_size_points[i]=(StringToDouble(openprice[i])-Low_base_tf[down_fractal[i]])*f;
     }
   if(type[i]=="sell" && maxprofit_bool[i]==class="kw">false)
     {
      maxprofit_size_points[i]=sl_size_points[i]*-class="num">1;
     }
   if(type[i]=="buy" && maxprofit_bool[i]==true)
     {
      maxprofit_size_points[i]=(High_base_tf[up_fractal[i]]-StringToDouble(openprice[i]))*f;
     }
   if(type[i]=="buy" && maxprofit_bool[i]==class="kw">false)
     {
      maxprofit_size_points[i]=sl_size_points[i]*-class="num">1;
     }
  }
class=class="str">"cmt">//--- 计算成交持续柱线数
 for(i=class="num">0;i<size;i++)
  {
   if(type[i]=="sell" && maxprofit_bool[i]==true)
     {
      duration[i]=index[i]-(class="type">int)down_fractal[i];
     }
   if(type[i]=="sell" && maxprofit_bool[i]==class="kw">false)
     {
      duration[i]=index[i]-maxprofit_int[i];
     }
   if(type[i]=="buy" && maxprofit_bool[i]==true)
     {
      duration[i]=index[i]-(class="type">int)up_fractal[i];
     }
   if(type[i]=="buy" && maxprofit_bool[i]==class="kw">false)
     {
      duration[i]=index[i]-maxprofit_int[i];
     }
  }
 for(i=class="num">0;i<size;i++)
  {
   StringReplace(openprice[i],".",",");
   StringReplace(sl_price[i],".",",");
   StringReplace(maxprofit_price[i],".",",");
  }
class=class="str">"cmt">//--- 将数据写入新的统计文件

「把成交统计落盘成 CSV」

想把每笔成交的止损幅度、最大盈利和持仓根数留痕,最省事的办法是直接在 EA 里写一份 ANSI 编码的 CSV。下面这段逻辑先尝试以读写方式打开 file_stat.csv,分隔符用分号,打不开就弹窗报警并退出,避免后续空句柄写崩。 文件句柄有效后,第一行先写表头:品种、成交类型、入场时间、开仓价、止损价、止损点数、最大盈利价、最大盈利点数、持续根数,共 9 列。随后 FileSeek 跳到文件尾,按数组 size 循环把每笔历史成交的对应字段写进去;价格类的小数用 NormalizeDouble 截断到 2 位,时间用 TimeToString 转成可读串。 循环结束关句柄并提示文件已生成。MT5 里这份 csv 会落在 MQL5\Files 目录下,拿 Excel 打开就能直接算止损点数分布——外汇和贵金属杠杆高,样本量建议先跑满 200 笔以上再看分布倾向,单看几十笔噪声偏大。

MQL5 / C++
  class="type">int h=FileOpen("file_stat.csv",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_CSV,";");
class=class="str">"cmt">//--- 开仓检查
  if(h==INVALID_HANDLE)
    {
      Alert("Error opening file!");
      class="kw">return;
    }
  else
    {
      FileWrite(h,
                  class=class="str">"cmt">/*class="num">1 品种*/"Symbol",
                  class=class="str">"cmt">/*class="num">2 成交类型*/"Deal type",
                  class=class="str">"cmt">/*class="num">3 入场时间*/"Open time",
                  class=class="str">"cmt">/*class="num">4 开仓价格*/"Open price",
                  class=class="str">"cmt">/*class="num">5 止损价位*/"SL",
                  class=class="str">"cmt">/*class="num">6 止损大小*/"SL size",
                  class=class="str">"cmt">/*class="num">7 最大盈利价位*/"Max profit level",
                  class=class="str">"cmt">/*class="num">8 最大盈利值*/"Max profit value",
                  class=class="str">"cmt">/*class="num">9 持续期*/"Duration in bars");
      class=class="str">"cmt">//--- 转至结束
      FileSeek(h,class="num">0,SEEK_END);
      for(i=class="num">0;i<size;i++)
        {
         FileWrite(h,
                  class=class="str">"cmt">/*class="num">1 品种*/symbol[i],
                  class=class="str">"cmt">/*class="num">2 成交类型*/type[i],
                  class=class="str">"cmt">/*class="num">3 入场时间*/TimeToString(opentime[i]),
                  class=class="str">"cmt">/*class="num">4 开仓价格*/openprice[i],
                  class=class="str">"cmt">/*class="num">5 止损价位*/sl_price[i],
                  class=class="str">"cmt">/*class="num">6 止损大小*/NormalizeDouble(sl_size_points[i],class="num">2),
                  class=class="str">"cmt">/*class="num">7 最大盈利价位*/maxprofit_price[i],
                  class=class="str">"cmt">/*class="num">8 最大盈利大小*/NormalizeDouble(maxprofit_size_points[i],class="num">2),
                  class=class="str">"cmt">/*class="num">9 持续期*/duration[i]);
        }
    }
  FileClose(h);
  Alert("file_stat.csv file created");

模型识别只是起点

前面几节把不依赖指标的修正极值抓取和连续点判定讲清楚了,但落到 EA 里这只是最低限度的骨架。策略测试器跑出来的数组统计能证明模型可被程序稳定识别,实盘之前必须把开仓过滤、止损逻辑和品种波动适配补进控制集合。 外汇和贵金属杠杆高、滑点跳空频繁,直接用最小控制集上实盘,回测里看着顺的延续模型在 live 环境可能频繁假突破。建议先拿 EURUSD 的 M15 把极值容差参数从 3 跳调到 8 跳逐档验证。 图例 8 到 11 展示了四个不同品种的延续模型识别样例,你可以照着把代码里的 ExtremumDepth 改小,看识别密度怎么变——这比读结论直观。

◍ 别急着下结论

这套延续形态识别方案落地时,真正要用的就三个件:Trade.mqh 管下单、MQL5_POST_final 跑走势延续模型、Report_read 负责把统计结果拽出来。ZIP 包 128.44 KB,丢进 MT5 的 MQL5 目录就能编译,不用自己拼逻辑。 有读者在评论里挑明,作者原本就没把它包装成完整交易系统,只是给出“定义延续形态的一种可能方法”。所以拿去跑 EURUSD 或 XAUUSD 之前,先想清楚:外汇和贵金属杠杆高、滑点跳空随时来,模型在假突破里的表现没人兜底。 真要验证,先开策略测试器用 Report_read 导一轮数据,看 ABC 段识别命中率再谈改参数。模型本身不承诺胜率,只是把“大浪—修正浪”的肉眼活儿交给了代码。

常见问题

先看走势延续模型的底线识别条件:原方向波走完且出现反向修正波雏形,才允许找修正高点,别在主线没走完时提前摸顶。
依次核对:底线条件成立、修正极点已定位、第三条件(如结构确认)锁定;三道全过再放单,少一道就跳过。
小布可读取持仓与柱线数据,自动标出空单修正高点和多单修正低点的柱索引,并提示第三条件是否满足,省去手动数柱。
用开仓价与浮盈方向回推:空单成本上方最近的反向次高即修正极点,多单成本下方最近反向次低即修正低点。
取上升波回撤中最低收盘价对应的柱索引,且需第三条件(如后续阳线吞没)确认,单看最低价容易吃到毛刺。