使用MQL4和MQL5绘制基于分形指标的趋势线·进阶篇
📐

使用MQL4和MQL5绘制基于分形指标的趋势线·进阶篇

(2/3)· 从分形极点定位到突破推送,手把手拆解跨语言绘线逻辑与坑点

偏理论 第 2/3 篇
很多人在 M15 上直接拿 H1 分形时间画趋势线,结果极点和时间对不上,线一拉就偏。长短周期分形不同步是自动绘线第一道暗坑,靠肉眼校准只会越画越乱。

「用 M15 精度锁定趋势线两端坐标」

画趋势线最怕坐标漂。日线级别看到的分形极点,放到小周期里时间会错位:同一根 H4 极点,在 M15 上可能落在前后两三根 K 线里,直接连日线高低点画线,支撑阻力位会偏出几个点甚至十几点。 实测对比 H4 与 M15 的极点时间,M15 的精度对手动和自动画线都够用——一天 24 小时正好拆成 96 根 15 分钟 K 线,把这个窗口拉出来扫一遍极值就行。 MQL5 的做法和旧版思路一致但函数不同:先在大周期取分形时间,再算下一日线起点,用 Copy 系列把 M15 的 high/low/time 拷进数组,最后 ArrayMaximum / ArrayMinimum 抓出极值和对应时间。下面这段是核心三步的 MQL5 代码。 支撑线取两个低点、阻力线取两个高点,各自用 Low_2_15_time[Min_M15_2] 配 Low_2_15[Min_M15_2] 这样的数组下标拿到时间和价格,连成两点的线就能挂上图表。外汇和贵金属波动快,这种自动锚点在高波动时段可能漏掉瞬时刺透,上线前建议在 MT5 策略测试器用真实 tick 回放验证。

MQL5 / C++
class=class="str">"cmt">// Step class="num">1. 在大时间框架下确定极点时间:
class=class="str">"cmt">//--- 确定分形的时间
  class="type">class="kw">datetime UpFractalTime_1=iTime(NULL, class="num">1440,UpperFractal_1);
  class="type">class="kw">datetime UpFractalTime_2=iTime(NULL, class="num">1440,UpperFractal_2);
  class="type">class="kw">datetime LowFractalTime_1=iTime(NULL, class="num">1440,LowerFractal_1);
  class="type">class="kw">datetime LowFractalTime_2=iTime(NULL, class="num">1440,LowerFractal_2);
class=class="str">"cmt">// Step class="num">2.  在较小的时间框架上确定极点所在K线的索引   
class=class="str">"cmt">//--- 在M15上查找分形索引
  class="type">int UpperFractal_1_m15=iBarShift(NULL, class="num">15, UpFractalTime_1,true);
  class="type">int UpperFractal_2_m15=iBarShift(NULL, class="num">15, UpFractalTime_2,true);
  class="type">int LowerFractal_1_m15=iBarShift(NULL, class="num">15, LowFractalTime_1,true);
  class="type">int LowerFractal_2_m15=iBarShift(NULL, class="num">15, LowFractalTime_2,true);
class=class="str">"cmt">// Step class="num">3. 在M15上使用数组查找极点:
class=class="str">"cmt">//--- 使用数组查找极点
class=class="str">"cmt">//--- 声明i变量用于循环
  class="type">int i;
class=class="str">"cmt">//--- class="num">1. 首先,查找低值极点
class=class="str">"cmt">//--- class="num">3.1 查找最近的低值极点
class=class="str">"cmt">//--- 声明存储K线索引值的数组
  class="type">int Lower_1_m15[class="num">96];
class=class="str">"cmt">//--- 声明存储价格的数组
  class="type">class="kw">double LowerPrice_1_m15[class="num">96];
class=class="str">"cmt">//--- 开始循环:
  for(i=class="num">0;i<=class="num">95;i++)
    {
      class=class="str">"cmt">//--- 用K线索引值填充数组
      Lower_1_m15[i]=LowerFractal_1_m15-i;
      class=class="str">"cmt">//--- 用价格填充数组
      LowerPrice_1_m15[i]=iLow(NULL,class="num">15,LowerFractal_1_m15-i);
    }
class=class="str">"cmt">//--- 确定数组中的最低价
  class="type">int LowestPrice_1_m15=ArrayMinimum(LowerPrice_1_m15,WHOLE_ARRAY,class="num">0);
class=class="str">"cmt">//--- 确定数组中最低价对应的K线索引
  class="type">int LowestBar_1_m15=Lower_1_m15[LowestPrice_1_m15];
class=class="str">"cmt">//--- 确定最低价K线所在的时间
  class="type">class="kw">datetime LowestBarTime_1_m15=iTime(NULL,class="num">15,Lower_1_m15[LowestPrice_1_m15]);
class=class="str">"cmt">//--- class="num">3.2 查找第二极值点
  class="type">int Lower_2_m15[class="num">96];
  class="type">class="kw">double LowerPrice_2_m15[class="num">96];
  for(i=class="num">0;i<=class="num">95;i++)
    {
      class=class="str">"cmt">//--- 用K线索引值填充数组
      Lower_2_m15[i]=LowerFractal_2_m15-i;
      class=class="str">"cmt">//--- 用价格填充数组
      LowerPrice_2_m15[i]=iLow(NULL,class="num">15,LowerFractal_2_m15-i);
    }
class=class="str">"cmt">//--- 确定数组中的最低价
  class="type">int LowestPrice_2_m15=ArrayMinimum(LowerPrice_2_m15,WHOLE_ARRAY,class="num">0);
class=class="str">"cmt">//--- 确定数组中最低价对应的K线索引
  class="type">int LowestBar_2_m15=Lower_2_m15[LowestPrice_2_m15];
class=class="str">"cmt">//--- 确定最低价K线所在的时间

◍ 跨周期极点对齐:把日线分形落到15分钟K线

在 MT5 里做多周期分析,最麻烦的是不同周期的时间轴对不齐。上面这段逻辑先把 M15 上最近两档上分形(UpperFractal_1_m15、UpperFractal_2_m15)各自往前推 96 根 K 线,用 iHigh 把高值塞进数组,再用 ArrayMaximum 抓出区间内最高价及其 K 线索引和时间——这是小周期内部的极值定位。 真正跨周期的部分从 Step 1 开始:用 CopyTime 把日线分形(PERIOD_D1)的起始时间取到四个数组里。注意这里传的是单根索引(UpperFractal_1 等),长度 1,拿到的是分形当根日线的 0 点时间戳。 Step 2 给每个日线时间加 86400 秒,得到「下一日线起始」作为截取终点。Step 3 用 CopyHigh / CopyLow / CopyTime 以日线分形时间为起点、下一日线起点为终点,把 M15 上的高低价和对应时间批量拉进数组。这样你就有了「某次日线分形之后、次根日线之前」的完整 15 分钟波动样本,外汇和贵金属这类高波动品种在此区间假突破概率偏高,需结合上下文过滤。 开 MT5 把这段接在你自己的分形识别后面,改 PERIOD_D1 为 H1 或 H4,就能验证不同大周期对 M15 极值样本的压缩效果。

MQL5 / C++
class="type">class="kw">datetime LowestBarTime_2_m15=iTime(NULL,class="num">15,Lower_2_m15[LowestPrice_2_m15]);
class=class="str">"cmt">//--- class="num">3.3 查找最近的高值极点
class="type">int Upper_1_m15[class="num">96];
class="type">class="kw">double UpperPrice_1_m15[class="num">96];
for(i=class="num">0;i<=class="num">95;i++)
  {
  class=class="str">"cmt">//--- 用K线索引值填充数组
  Upper_1_m15[i]=UpperFractal_1_m15-i;
  class=class="str">"cmt">//--- 用价格填充数组
  UpperPrice_1_m15[i]=iHigh(NULL,class="num">15,UpperFractal_1_m15-i);
  }
class=class="str">"cmt">//--- 确定数组中的最高价
class="type">int HighestPrice_1_m15=ArrayMaximum(UpperPrice_1_m15,WHOLE_ARRAY,class="num">0);
class=class="str">"cmt">//--- 确定数组中最高价对应的K线索引
class="type">int HighestBar_1_m15=Upper_1_m15[HighestPrice_1_m15];
class=class="str">"cmt">//--- 确定最高价K线所在的时间
class="type">class="kw">datetime HighestBarTime_1_m15=iTime(NULL,class="num">15,Upper_1_m15[HighestPrice_1_m15]);
class=class="str">"cmt">//--- class="num">3.4 查找第二高值极值点
class="type">int Upper_2_m15[class="num">96];
class="type">class="kw">double UpperPrice_2_m15[class="num">96];
for(i=class="num">0;i<=class="num">95;i++)
  {
  class=class="str">"cmt">//--- 用K线索引值填充数组
  Upper_2_m15[i]=UpperFractal_2_m15-i;
  class=class="str">"cmt">//--- 用价格填充数组
  UpperPrice_2_m15[i]=iHigh(NULL,class="num">15,UpperFractal_2_m15-i);
  }
class=class="str">"cmt">// Step class="num">1. 在大时间框架下确定极点时间:
class=class="str">"cmt">//--- 声明存储较大时间框架下K线对应的时间的数组
class="type">class="kw">datetime UpFractalTime_1[],LowFractalTime_1[],UpFractalTime_2[],LowFractalTime_2[];
class=class="str">"cmt">//--- 确定较大时间框架下分形的时间
CopyTime(Symbol(),PERIOD_D1,UpperFractal_1,class="num">1,UpFractalTime_1);
CopyTime(Symbol(),PERIOD_D1,LowerFractal_1,class="num">1,LowFractalTime_1);
CopyTime(Symbol(),PERIOD_D1,UpperFractal_2,class="num">1,UpFractalTime_2);
CopyTime(Symbol(),PERIOD_D1,LowerFractal_2,class="num">1,LowFractalTime_2);
class=class="str">"cmt">// Step class="num">2. 确定下一个日线的开始时间
class=class="str">"cmt">//--- 确定下一个日线的开始时间(CopyHigh(),CopyLow() 和 CopyTime()的结束时间)
class="type">class="kw">datetime UpFractalTime_1_15=UpFractalTime_1[class="num">0]+class="num">86400;
class="type">class="kw">datetime UpFractalTime_2_15=UpFractalTime_2[class="num">0]+class="num">86400;
class="type">class="kw">datetime LowFractalTime_1_15=LowFractalTime_1[class="num">0]+class="num">86400;
class="type">class="kw">datetime LowFractalTime_2_15=LowFractalTime_2[class="num">0]+class="num">86400;
class=class="str">"cmt">// Step class="num">3. 声明和填充15分钟时间框架下的时间和价格数组:
class=class="str">"cmt">//--- 声明存储最大和最小价格值的数组
class="type">class="kw">double High_1_15[],Low_1_15[],High_2_15[],Low_2_15[];
class=class="str">"cmt">//--- 用CopyHigh() 和 CopyLow() 函数填充数组
CopyHigh(Symbol(),PERIOD_M15,UpFractalTime_1[class="num">0],UpFractalTime_1_15,High_1_15);
CopyHigh(Symbol(),PERIOD_M15,UpFractalTime_2[class="num">0],UpFractalTime_2_15,High_2_15);
CopyLow(Symbol(),PERIOD_M15,LowFractalTime_1[class="num">0],LowFractalTime_1_15,Low_1_15);
CopyLow(Symbol(),PERIOD_M15,LowFractalTime_2[class="num">0],LowFractalTime_2_15,Low_2_15);
class=class="str">"cmt">//--- 声明存储对应极点所在K线时间的数组
class="type">class="kw">datetime High_1_15_time[],High_2_15_time[],Low_1_15_time[],Low_2_15_time[];
class=class="str">"cmt">//--- 填充数组
CopyTime(Symbol(),PERIOD_M15,UpFractalTime_1[class="num">0],UpFractalTime_1_15,High_1_15_time);
CopyTime(Symbol(),PERIOD_M15,UpFractalTime_2[class="num">0],UpFractalTime_2_15,High_2_15_time);

在 M15 上定位两段区间的极值

把 fractal 时间映射到 M15 后,下一步是找出两段窗口内的最高与最低价及其对应位置。上面这段逻辑先通过 CopyTime 把两个低点 fractal 的起始时间同步到 15 分钟序列,再用 ArrayMaximum / ArrayMinimum 在 96 根 K 线内检索极点。 注意 ArrayMaximum 的第三个参数写死为 96,意味着每段最多扫 96 根 M15 棒(约 24 小时)。若你的 fractal 间距超过一天,这个上限会截断搜索范围,极值可能漏抓——在 MT5 里把 96 改成实际跨度再跑一遍就能验证。 外汇与贵金属波动随机性高,极点只是历史位置标记,不代表后续必然反转,仅用于结构识别。

MQL5 / C++
  CopyTime(Symbol(),PERIOD_M15,LowFractalTime_1[class="num">0],LowFractalTime_1_15,Low_1_15_time);
  CopyTime(Symbol(),PERIOD_M15,LowFractalTime_2[class="num">0],LowFractalTime_2_15,Low_2_15_time);
class=class="str">"cmt">// Step class="num">4. 查找最低和最高价格,以及极点对应的时间值
class=class="str">"cmt">//--- 用ArrayMaximum() 和 ArrayMinimum() 函数确定最高和最低价格以及时间
  class="type">int Max_M15_1=ArrayMaximum(High_1_15,class="num">0,class="num">96);
  class="type">int Max_M15_2=ArrayMaximum(High_2_15,class="num">0,class="num">96);
  class="type">int Min_M15_1=ArrayMinimum(Low_1_15,class="num">0,class="num">96);
  class="type">int Min_M15_2=ArrayMinimum(Low_2_15,class="num">0,class="num">96);

「用 OBJ_TREND 把极点连成可重绘的趋势线」

算好 M15 上的两个低点时间和价格后,下一步就是让 MT5 在图表上真正画出线。ObjectCreate 配合 OBJ_TREND 类型,把 LowestBarTime_2_m15 与 LowestBarTime_1_m15 两组坐标串成支撑线,阻力线同理用两个高点坐标。 早期写法用 ObjectSet 直接改颜色线宽,但在当前版本里整数属性建议走 ObjectSetInteger,比如 OBJPROP_RAY_RIGHT 设 true 让趋势线向右无限延伸,避免只画在两个极点之间。 市场走出新极点后,旧线坐标就失效了。代码里先把已有线的 TIME1/TIME2 读进变量,若和最新 LowestBarTime_1_m15 / LowestBarTime_2_m15 对不上,就 ObjectDelete 把 TL_Support 删掉,下一轮再按新极点重建。外汇和贵金属波动快,这种重绘逻辑能减少假突破误导,但信号滞后和重绘误差仍属高风险。 下面这段可直接贴进 EA 的 OnCalculate 里验证:创建时先判空再画,重绘前先读时间再比对的节奏,能让你在 EURUSD 的 M15 上直观看到线随极点跳动。

MQL5 / C++
class=class="str">"cmt">//--- 创建支撑线
  ObjectCreate(class="num">0,"TL_Support",OBJ_TREND,class="num">0,LowestBarTime_2_m15,LowerPrice_2_m15[LowestPrice_2_m15],
                LowestBarTime_1_m15,LowerPrice_1_m15[LowestPrice_1_m15]);
  ObjectSet("TL_Support",OBJPROP_COLOR,Support_Color);
  ObjectSet("TL_Support",OBJPROP_STYLE,Support_Style);
  ObjectSet("TL_Support",OBJPROP_WIDTH,Support_Width);
class=class="str">"cmt">//--- 创建阻力线
  ObjectCreate(class="num">0,"TL_Resistance",OBJ_TREND,class="num">0,HighestBarTime_2_m15,UpperPrice_2_m15[HighestPrice_2_m15],
                HighestBarTime_1_m15,UpperPrice_1_m15[HighestPrice_1_m15]);
  ObjectSet("TL_Resistance",OBJPROP_COLOR,Resistance_Color);
  ObjectSet("TL_Resistance",OBJPROP_STYLE,Resistance_Style);
  ObjectSet("TL_Resistance",OBJPROP_WIDTH,Resistance_Width);
class=class="str">"cmt">//--- 创建支撑线
  ObjectCreate(class="num">0,"TL_Support",OBJ_TREND,class="num">0,Low_2_15_time[Min_M15_2],Low_2_15[Min_M15_2],Low_1_15_time[Min_M15_1],Low_1_15[Min_M15_1]);
  ObjectSetInteger(class="num">0,"TL_Support",OBJPROP_RAY_RIGHT,true);
  ObjectSetInteger(class="num">0,"TL_Support",OBJPROP_COLOR,Support_Color);
  ObjectSetInteger(class="num">0,"TL_Support",OBJPROP_STYLE,Support_Style);
  ObjectSetInteger(class="num">0,"TL_Support",OBJPROP_WIDTH,Support_Width);
class=class="str">"cmt">//--- 创建阻力线
  ObjectCreate(class="num">0,"TL_Resistance",OBJ_TREND,class="num">0,High_2_15_time[Max_M15_2],High_2_15[Max_M15_2],High_1_15_time[Max_M15_1],High_1_15[Max_M15_1]);
  ObjectSetInteger(class="num">0,"TL_Resistance",OBJPROP_RAY_RIGHT,true);
  ObjectSetInteger(class="num">0,"TL_Resistance",OBJPROP_COLOR,Resistance_Color);
  ObjectSetInteger(class="num">0,"TL_Resistance",OBJPROP_STYLE,Resistance_Style);
  ObjectSetInteger(class="num">0,"TL_Resistance",OBJPROP_WIDTH,Resistance_Width);
class=class="str">"cmt">//--- 重绘支撑线
class=class="str">"cmt">//--- 将支撑线的时间坐标写入变量中
  class="type">class="kw">datetime TL_TimeLow2=ObjectGet("TL_Support",OBJPROP_TIME2);
  class="type">class="kw">datetime TL_TimeLow1=ObjectGet("TL_Support",OBJPROP_TIME1);
class=class="str">"cmt">//--- 如果线的坐标不符合当前坐标
  if(TL_TimeLow2!=LowestBarTime_1_m15 && TL_TimeLow1!=LowestBarTime_2_m15)
    {
    class=class="str">"cmt">//--- 移除直线
      ObjectDelete(class="num">0,"TL_Support");
    }
class=class="str">"cmt">//--- 重绘支撑线
class=class="str">"cmt">//--- 将阻力线的时间坐标写入变量中
  class="type">class="kw">datetime TL_TimeUp2=ObjectGet("TL_Resistance",OBJPROP_TIME2);
  class="type">class="kw">datetime TL_TimeUp1=ObjectGet("TL_Resistance",OBJPROP_TIME1);
class=class="str">"cmt">//--- 如果线的坐标不符合当前坐标
  if(TL_TimeUp2!=HighestBarTime_1_m15 && TL_TimeUp1!=HighestBarTime_2_m15)
    {
    class=class="str">"cmt">//--- 移除直线
      ObjectDelete(class="num">0,"TL_Resistance");
    }
class=class="str">"cmt">//--- 重绘支撑线
class=class="str">"cmt">//--- 将支撑线的时间坐标写入变量中

◍ 支撑阻力线失效就重画

在 M15 周期上跟踪趋势线时,最怕手动画的线跟实际高低点脱节。下面这段逻辑就是专门处理「线还挂着、但锚点已经变了」的情况。 先抓支撑线 TL_Support 的两个时间锚点:OBJPROP_TIME 参数 0 和 1 分别对应线的两端。若它们跟 Low_2_15_time[Min_M15_2] 与 Low_1_15_time[Min_M15_1] 都对不上,说明原支撑线基于的旧低点已失效,直接 ObjectDelete 清掉,等下一轮重绘。 阻力线 TL_Resistance 同理:取 High_2_15_time[Max_M15_2] 与 High_1_15_time[Max_M15_1] 做比对,两端时间坐标都不匹配当前高点时就删线。外汇与贵金属波动快,这种自动清理能避免旧线误导入场,但信号滞后可能发生,实际验证请在 MT5 策略测试器跑一遍。

MQL5 / C++
  class="type">class="kw">datetime TL_TimeLow2=(class="type">class="kw">datetime)ObjectGetInteger(class="num">0,"TL_Support",OBJPROP_TIME,class="num">0);
  class="type">class="kw">datetime TL_TimeLow1=(class="type">class="kw">datetime)ObjectGetInteger(class="num">0,"TL_Support",OBJPROP_TIME,class="num">1);
class=class="str">"cmt">//--- 如果线的坐标不符合当前坐标
  if(TL_TimeLow2!=Low_2_15_time[Min_M15_2] && TL_TimeLow1!=Low_1_15_time[Min_M15_1])
    {
      class=class="str">"cmt">//--- 移除直线
      ObjectDelete(class="num">0,"TL_Support");
    }
class=class="str">"cmt">//--- 重绘支撑线
class=class="str">"cmt">//--- 将阻力线的时间坐标写入变量中
  class="type">class="kw">datetime TL_TimeUp2=(class="type">class="kw">datetime)ObjectGetInteger(class="num">0,"TL_Resistance",OBJPROP_TIME,class="num">0);
  class="type">class="kw">datetime TL_TimeUp1=(class="type">class="kw">datetime)ObjectGetInteger(class="num">0,"TL_Resistance",OBJPROP_TIME,class="num">1);
class=class="str">"cmt">//--- 如果线的坐标不符合当前坐标
  if(TL_TimeUp2!=High_2_15_time[Max_M15_2] && TL_TimeUp1!=High_1_15_time[Max_M15_1])
    {
      class=class="str">"cmt">//--- 移除直线
      ObjectDelete(class="num">0,"TL_Resistance");
    }

M15历史不足会让分形线漏画

实盘回看分形连线时,常发现 M15 上的线段断断续续画不出来。先以为是算法写错,后来定位到根因:MT5 在小周期上默认加载的历史 K 线有限,分形对应的那根 M15 柱根本没进内存,iBarShift 拿不到索引,线就断了。 外汇和贵金属品种在切换小周期时尤其容易踩这个坑,属于高杠杆环境下的常见数据坑,不是策略失效。 解决思路是在绘图前先自检:用 Bars() 查指定时间范围内的 M15 柱数,只要返回 0 就说明该段历史没加载,直接弹 Alert 提醒。下面这段可原样拷进 EA 的 OnCalculate 前段。 别把弹窗当摆设 真在 MT5 跑之前,先手动把 M15 图表向右拖到底、或调大「最大历史柱数」,否则 Alert 会一直刷,误以为代码有 bug。

MQL5 / C++
class=class="str">"cmt">//--- 检查历史数据加载情况
class=class="str">"cmt">//--- 如果至少有一个分形对应的K线在M15上没有找到
   if(UpperFractal_1_m15==-class="num">1 || UpperFractal_2_m15==-class="num">1
      || LowerFractal_1_m15==-class="num">1 || LowerFractal_2_m15==-class="num">1)
    {
      Alert("The loaded history is insufficient for the correct work!");
    }
class=class="str">"cmt">//--- 检查历史数据加载情况
class=class="str">"cmt">//--- class="num">1. 确定特定时间框架下的K线数量
   class="type">int High_M15_1=Bars(Symbol(),PERIOD_M15,UpFractalTime_1[class="num">0],UpFractalTime_1_15);
   class="type">int High_M15_2=Bars(Symbol(),PERIOD_M15,UpFractalTime_2[class="num">0],UpFractalTime_2_15);
   class="type">int Low_M15_1=Bars(Symbol(),PERIOD_M15,LowFractalTime_1[class="num">0],LowFractalTime_1_15);
   class="type">int Low_M15_2=Bars(Symbol(),PERIOD_M15,LowFractalTime_2[class="num">0],LowFractalTime_2_15);
class=class="str">"cmt">//--- class="num">2. 检查用于正确绘制线段的历史数据是否足够
class=class="str">"cmt">//--- 如果至少有一个没有找到
   if(High_M15_1==class="num">0 || High_M15_2==class="num">0 || Low_M15_1==class="num">0 || Low_M15_2==class="num">0)
    {
      Alert("The loaded history is insufficient for the correct work!");
    }
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到分形极点和趋势线的实时标注,你只管判断突破是否有效。

常见问题

长周期分形形成 K 线的时间通常不等于小周期极值的真实时间,直接套用会让趋势线锚点偏移,概率上降低线有效性。
它们缓存 iFractals 指标的输出值,供后续检索最近上下分形对应的价格与 K 线索引,是定位极点的中间载体。
残留对象会堆积在图表上干扰后续分析,也可能在新实例运行时引发坐标冲突,应在 DeInit 中清理。
可以,小布盯盘对应品种页已内置分形极点与趋势线识别,突破发生时会有提示,省去自己写推送逻辑。
倾向不能省,若历史 Bars 不足,分形检索会越界或返回空值,导致趋势线绘制中断或错位。