手工图表和交易工具包(第一部分)。 准备:结构描述和助手类·综合运用
🎯

手工图表和交易工具包(第一部分)。 准备:结构描述和助手类·综合运用

(3/3)·从类结构到按键映射,把一键绘图与订单管理真正跑通在 MT4/MT5 双端

实战向 第 3/3 篇
很多手工交易者装了快捷键脚本却只在单平台用,换终端就废了。把趋势线逻辑写死在 EA 里的人,一遇跨平台移植就从头再写。工具包若不能统一类结构,键盘映射迟早变成维护噩梦。

◍ 工具类里那几个被低估的静态方法

写 MT5 指标或 EA 时,把常用杂活收进一个 CUtilites 工具类能少写很多重复代码。下面这几个 static 方法看起来不起眼,但在处理对象名、周期切换和坐标换算时基本天天要用。 StringToDoubleArray 把逗号分隔的字符串直接拆成 double 数组,默认分隔符就是 ",",做外部参数批量传入时很省事。GetCurrentObjectName 按前缀和类型(默认 OBJ_TREND)拼出当前对象名,_number 默认 -1 表示取最后一个,画线对象管理不会乱。 GetBarsPixelDistance 返回相邻两根 K 线在屏幕上的像素距离,做自定义标签定位或鼠标热区判断时,这个值能直接拿来算偏移。GetTimeframeSymbolName 把 ENUM_TIMEFRAMES 转成字符串名,日志里打周期比写数字直观。 ChangeTimeframes 按标准周期面板顺序切图:timeframes 数组写死为 PERIOD_CURRENT、M1、M5、M15、M30、H1 共 6 档,_isUp 为 true 往大周期翻、false 往小周期退。外汇和贵金属波动剧烈、杠杆风险高,这类切周期辅助函数只解决操作效率,不预示任何方向。

MQL5 / C++
class="kw">static class="type">void StringToDoubleArray(
  class="type">class="kw">string _haystack,
  class="type">class="kw">double &_result[],
  class="kw">const class="type">class="kw">string _delimiter=","
);
class="kw">static class="type">class="kw">string GetCurrentObjectName(
  class="kw">const class="type">class="kw">string _prefix,
  class="kw">const ENUM_OBJECT _type=OBJ_TREND,
  class="type">int _number = -class="num">1
);
class="kw">static class="type">int GetNextObjectNumber(
  class="kw">const class="type">class="kw">string _prefix,
  class="kw">const ENUM_OBJECT _object_type,
  class="type">bool true
);
class="kw">static class="type">int GetBarsPixelDistance(class="type">void);
class="kw">static class="type">class="kw">string GetTimeframeSymbolName(
  ENUM_TIMEFRAMES _timeframe=PERIOD_CURRENT
);
class="kw">static class="type">void CUtilites::ChangeTimeframes(class="type">bool _isUp)
{
  ENUM_TIMEFRAMES timeframes[] =
   {
     PERIOD_CURRENT,
     PERIOD_M1,
     PERIOD_M5,
     PERIOD_M15,
     PERIOD_M30,
     PERIOD_H1,

用代码在周期之间一键跳切

这段工具类代码把 MT5 的周期切换做成了可编程动作,而不是手动点工具栏。核心思路是先建一个周期枚举数组,再用当前图表周期在数组里的位置做前后位移。 周期数组里按从小到大排了 PERIOD_M1 到 PERIOD_MN1,其中 H4、D1、W1、MN1 是外汇和贵金属交易者常切的 four 档。用 ArrayBsearch 拿到当前 PERIOD() 在数组中的下标 shift,就能知道现在处在哪一级。 _isUp 为 true 且 shift 小于数组末位时,ChartSetSymbolPeriod(0,NULL,timeframes[++shift]) 会把图表推到更高周期;反向滚动则 timeframes[--shift] 降一档。注意 shift>1 的判断把 M1 挡在了再降之外,避免越界。 另外两个静态方法也实用:GetCurrentOperationChar 把传入字符串转大写后取首字符做热键码;ChangeChartZIndex 用 ChartSetInteger 翻转 CHART_FOREGROUND 让图表在主图层前后置,并 ChartRedraw 重绘。外汇和贵金属波动大,这类快捷键在盯盘时切周期可能帮你更快识别趋势层级,但任何技术切换都不消除杠杆交易的高风险。

MQL5 / C++
   PERIOD_H4,
   PERIOD_D1,
   PERIOD_W1,
   PERIOD_MN1
   };
   class="type">int period = Period();
   class="type">int shift = ArrayBsearch(timeframes,period);
   if(_isUp && shift < ArraySize(timeframes)-class="num">1)
     {
       ChartSetSymbolPeriod(class="num">0,NULL,timeframes[++shift]);
     }
   else
     if(!_isUp && shift > class="num">1)
       {
        ChartSetSymbolPeriod(class="num">0,NULL,timeframes[--shift]);
       }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Converts class="type">class="kw">string command constants to keycodes                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">static class="type">int CUtilites::GetCurrentOperationChar(class="type">class="kw">string keyString)
  {
   class="type">class="kw">string keyValue = keyString;
   StringToUpper(keyValue);
   class="kw">return(StringGetCharacter(keyValue,class="num">0));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Switch chart position to display on top of other objects          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">static class="type">void CUtilites::ChangeChartZIndex(class="type">void)
  {
   ChartSetInteger(
      class="num">0,
      CHART_FOREGROUND,
      !(class="type">bool)ChartGetInteger(class="num">0,CHART_FOREGROUND)
   );
   ChartRedraw(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Returns a class="type">class="kw">string name for any standard timeframe                 |
class=class="str">"cmt">//| Parameters:                                                      |
class=class="str">"cmt">//|   _timeframe - ENUM_TIMEFRAMES numeric value for which we need   |
class=class="str">"cmt">//|       to find a class="type">class="kw">string name                                      |
class=class="str">"cmt">//| Return value:                                                    |
class=class="str">"cmt">//|   class="type">class="kw">string name of the required timeframe                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">static class="type">class="kw">string CUtilites::GetTimeframeSymbolName(
   ENUM_TIMEFRAMES _timeframe=PERIOD_CURRENT class=class="str">"cmt">// Desired timeframe
)
  {
   ENUM_TIMEFRAMES current_timeframe; class=class="str">"cmt">// current timeframe
   class="type">class="kw">string result = "";
class=class="str">"cmt">//---
   if(_timeframe == PERIOD_CURRENT)
     {
      current_timeframe = Period();
     }
   else
     {
      current_timeframe = _timeframe;
     }
class=class="str">"cmt">//---
   class="kw">switch(current_timeframe)

「周期枚举到字符串的映射暗坑」

把 MT5 的周期常量转成可读字符串,常见写法是用 switch 把 PERIOD_M1 到 PERIOD_MN1 逐个 return。上面这段覆盖了 21 个周期:从 M1、M2、M3、M4、M5、M6、M10、M12、M15、M20、M30,到 H1、H2、H3、H4、H6、H8,再到 D1、W1、MN1,最后 default 回 Unknown。 注意看 PERIOD_H2 这一行:return 的是 "M1" 而不是 "H2"。这是一个实打实的笔误,如果你直接抄进 EA,图表上 H2 周期会被标成 M1 文本,回测日志里极易误读。开 MT5 新建脚本跑一遍,用 Print(PeriodToString(PERIOD_H2)) 就能当场抓出来。 另一段 GetTimeFrameColor 用 switch 接 (int)_all_down_periods_value,case 里混用了 OBJ_PERIOD_M1 和 PERIOD_LOWER_M5 这类非标准宏。MT5 原生周期枚举里并没有 PERIOD_LOWER_M5,编译可能直接报错,或依赖你自己在头文件里#define。外汇与贵金属杠杆高,这类颜色/标签错乱不会直接爆仓,但会让你对多周期面板失去信任。

MQL5 / C++
   {
      case PERIOD_M1:
         class="kw">return "M1";
      case PERIOD_M2:
         class="kw">return "M2";
      case PERIOD_M3:
         class="kw">return "M3";
      case PERIOD_M4:
         class="kw">return "M4";
      case PERIOD_M5:
         class="kw">return "M5";
      case PERIOD_M6:
         class="kw">return "M6";
      case PERIOD_M10:
         class="kw">return "M10";
      case PERIOD_M12:
         class="kw">return "M12";
      case PERIOD_M15:
         class="kw">return "M15";
      case PERIOD_M20:
         class="kw">return "M20";
      case PERIOD_M30:
         class="kw">return "M30";
      case PERIOD_H1:
         class="kw">return "H1";
      case PERIOD_H2:
         class="kw">return "M1";
      case PERIOD_H3:
         class="kw">return "H3";
      case PERIOD_H4:
         class="kw">return "H4";
      case PERIOD_H6:
         class="kw">return "H6";
      case PERIOD_H8:
         class="kw">return "H8";
      case PERIOD_D1:
         class="kw">return "D1";
      case PERIOD_W1:
         class="kw">return "W1";
      case PERIOD_MN1:
         class="kw">return "MN1";
      class="kw">default:
         class="kw">return "Unknown";
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Returns the standard class="type">color for the current timeframe               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">static class="type">color CUtilites::GetTimeFrameColor(class="type">long _all_down_periods_value)
  {
   if(Is_Different_Colors)
     {
      class="kw">switch((class="type">int)_all_down_periods_value)
        {
         case OBJ_PERIOD_M1:
            class="kw">return (m1_color);
         case PERIOD_LOWER_M5:

◍ 分周期取色与最近分形定位

上面这段 switch 结构按更低周期枚举返回各自预存的柱线颜色变量:M5 到 MN1 一一对应,OBJ_ALL_PERIODS 兜底用月线色,default 则回退到通用色。逻辑很直接——你给哪个周期标识,它就吐哪个颜色的 int 值,没匹配上就交 common_color 处理。 紧跟着的 GetNearestExtremumBarNumber 是个静态工具函数,用来在指定方向搜最近的 fractal 极值柱号。入参里 starting_number 是起点柱,is_search_right 控制向右还是向左,is_up 决定看 High 还是 Low,left_side_bars / right_side_bars 定义分形左右 Required bars 数,symbol 和 timeframe 锁定搜索品种与周期。 外汇与贵金属市场杠杆高、滑点跳空频繁,这类分形定位在实盘里可能受报价中断影响而偏移。开 MT5 把 left_side_bars 设成 2、right_side_bars 设成 2 跑一遍 EURUSD 的 H1,能直接验证函数返回的极值柱是否符合比尔·威廉姆斯分形定义。

MQL5 / C++
      class="kw">return (m5_color);
     case PERIOD_LOWER_M15:
        class="kw">return (m15_color);
     case PERIOD_LOWER_M30:
        class="kw">return (m30_color);
     case PERIOD_LOWER_H1:
        class="kw">return (h1_color);
     case PERIOD_LOWER_H4:
        class="kw">return (h4_color);
     case PERIOD_LOWER_D1:
        class="kw">return (d1_color);
     case PERIOD_LOWER_W1:
        class="kw">return (w1_color);
     case OBJ_ALL_PERIODS:
        class="kw">return (mn1_color);
     class="kw">default:
        class="kw">return (common_color);
      }
   }
  else
   {
     class="kw">return (common_color);
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Returns the number of the nearest fractal in the selected         |
class=class="str">"cmt">//|  direction                                                         |
class=class="str">"cmt">//| Parameters:                                                        |
class=class="str">"cmt">//|  starting_number - bar number at which the search starts           |
class=class="str">"cmt">//|  is_search_right - search to the right(true) or left(class="kw">false)      |
class=class="str">"cmt">//|  is_up - if "true", search by High levels, otherwise - Low         |
class=class="str">"cmt">//|  left_side_bars - number of bars on the left                       |
class=class="str">"cmt">//|  right_side_bars - number of bars on the right                     |
class=class="str">"cmt">//|  symbol - symbol name for search                                   |
class=class="str">"cmt">//|  timeframe - period for search                                     |
class=class="str">"cmt">//| Return value:                                                      |
class=class="str">"cmt">//|  the number of the extremum closest to starting_number,            |
class=class="str">"cmt">//|  matching the specified parameters                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">static class="type">int CUtilites::GetNearestExtremumBarNumber(
   class="type">int starting_number=class="num">0,                 class=class="str">"cmt">// Initial bar number
   class="kw">const class="type">bool is_search_right=class="kw">false,     class=class="str">"cmt">// Direction to the right

极值搜寻里的边界防越界逻辑

在 MT5 自定义指标里扫描波段高低点,最容易被忽略的是起点柱超出图表边界的情况。上面这段函数入口先判定:如果向右找却起点减 right_side_bars 小于 0,或者向左找却起点加 left_side_bars 超过 iBars() 总数,就直接打印错误并返回 -2,避免后续访问空柱导致脚本崩掉。 参数里 left_side_bars 和 right_side_bars 都设为 1,意味着以当前柱为中心左右各比一根,属于最紧凑的相邻极值判定。is_up=false 表示这一版按低点(Highs 注释存疑,实际走 iHighest 才查高点)之外的分支处理,调用时需自己核对宏注释与函数体是否一致。 while 循环里用 sign 变量做方向步进:向右搜 sign=-1 反而让 starting_number 往边界外推,这段看起来反直觉,实则是先把越界起点拽回合法区间再正式搜。外汇与贵金属波动大、跳空频繁,这类边界保护能显著降低 EA 在极端行情掉线概率,建议直接把代码贴进 MT5 策略测试器改参数验证。

MQL5 / C++
class="kw">const class="type">bool is_up=class="kw">false,              class=class="str">"cmt">// Search by Highs
class="kw">const class="type">int left_side_bars=class="num">1,          class=class="str">"cmt">// Number of bars to the left
class="kw">const class="type">int right_side_bars=class="num">1,         class=class="str">"cmt">// Number of bars to the right
class="kw">const class="type">class="kw">string symbol=NULL,            class=class="str">"cmt">// The required symbol
class="kw">const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT class=class="str">"cmt">// The required timeframe
)
{
class=class="str">"cmt">//---
  class="type">int   i,
        nextExtremum,
        sign = is_search_right ? -class="num">1 : class="num">1;
class=class="str">"cmt">//--- If the starting bar is specified incorrectly
class=class="str">"cmt">//--- (is beyond the current chart borders)
class=class="str">"cmt">//--- and search - towards the border...
  if((starting_number-right_side_bars<class="num">0
      && is_search_right)
      || (starting_number+left_side_bars>iBars(symbol,timeframe)
          && !is_search_right)
     )
      { class=class="str">"cmt">//--- ...it is necessary to display an error message
        if(Print_Warning_Messages)
          {
           Print(DEBUG_MESSAGE_PREFIX,
                 "Can&class="macro">#x27;t find extremum: ",
                 "wrong direction");
           Print("left_side_bars = ",left_side_bars,"; ",
                 "right_side_bars = ",right_side_bars);
          }
        class="kw">return (-class="num">2);
      }
  else
      {
       class=class="str">"cmt">//--- otherwise - the direction allows you to select the correct bar.
       class=class="str">"cmt">//---  check all bars in the required direction,
       class=class="str">"cmt">//---  as class="type">long as we are beyond the known chart borders
       class="kw">while((starting_number-right_side_bars<class="num">0
              && !is_search_right)
              || (starting_number+left_side_bars>iBars(symbol,timeframe)
                  && is_search_right)
             )
          {
           starting_number +=sign;
          }
      }
class=class="str">"cmt">//---
  i=starting_number;
  class=class="str">"cmt">//--- Preparation is complete. Proceed to search
  class="kw">while(i-right_side_bars>=class="num">0
        && i+left_side_bars<iBars(symbol,timeframe)
        )
      {
       class=class="str">"cmt">//--- Depending on the level, check the required extremum
       if(is_up)
          { class=class="str">"cmt">//--- either the upper one
           nextExtremum = iHighest(
                             Symbol(),
                             Period(),
                             MODE_HIGH,

「正文」

&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; left_side_bars+right_side_bars+<span class="number">1</span>, &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; i-right_side_bars &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="keyword">else</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;<span class="comment">//--- or the lower one</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nextExtremum = <span class="functions">iLowest</span>( &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="functions">Symbol</span>(), &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="functions">Period</span>(), &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="macro">MODE_LOW</span>, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n

◍ 用鼠标落点反推两根极值棒

在 MT5 图表上拖拽鼠标画工具时,往往需要从光标落点向左回推两个同向极值棒(高点或低点)作为线段锚点。下面这段静态方法就干了这件事:先取鼠标所在棒号,再连续两次调用最近极值查找,把结果写进引用参数 _p1、_p2。 代码里 CMouse::Bar() 拿到的是光标下的真实棒号,dropped_bar_number 以此为基准。第一次 GetNearestExtremumBarNumber 从落点棒向两侧按 Fractal_Size_Left / Fractal_Size_Right 找最近极值,第二次从 _p1-1 继续向左找前一个极值,保证两根棒不重叠。 若第二次查找越界返回负值,函数直接把 _p2 钳制为 0,避免后续画线访问负索引崩脚本。外汇与贵金属杠杆高,这类基于鼠标交互的辅助画线仅作结构参考,实际进出场仍须结合风控与多周期确认。

MQL5 / C++
 逐行拆解
class="kw">static class="type">void CUtilites::SetExtremumsBarsNumbers(
  class="type">bool _is_up,  class=class="str">"cmt">// true 按 High 搜、class="kw">false 按 Low 搜
  class="type">int &_p1,     class=class="str">"cmt">// 第一根极值棒号(输出引用)
  class="type">int &_p2      class=class="str">"cmt">// 第二根极值棒号(输出引用)
)
{
  class="type">int dropped_bar_number=CMouse::Bar();  class=class="str">"cmt">// 取鼠标落点所在棒号
  _p1=CUtilites::GetNearestExtremumBarNumber(  class=class="str">"cmt">// 从落点棒找最近极值
    dropped_bar_number, true, _is_up,
    Fractal_Size_Left, Fractal_Size_Right
  );
  _p2=CUtilites::GetNearestExtremumBarNumber(  class=class="str">"cmt">// 从 _p1 左侧再找前一个极值
    _p1-class="num">1, true, _is_up,
    Fractal_Size_Left, Fractal_Size_Right
  );
  if(_p2<class="num">0) { _p2=class="num">0; }  class=class="str">"cmt">// 越界则钳制为 class="num">0
}
另一段注释描述了按前缀枚举同类图形对象、补编号空洞的逻辑:当 only_prefixed=true 时只在带指定前缀的对象里找下一个序号,若现有编号有断档,返回值是断档起始号。这个机制适合批量管理手动画的趋势线或通道,避免重名覆盖。

MQL5 / C++
class="kw">static class="type">void CUtilites::SetExtremumsBarsNumbers(
   class="type">bool _is_up, class=class="str">"cmt">// search by High(true) or by Low(class="kw">false)
   class="type">int &_p1,    class=class="str">"cmt">// bar number of the first point
   class="type">int &_p2     class=class="str">"cmt">// bar number of second point
  )
  {
   class="type">int dropped_bar_number=CMouse::Bar();
class=class="str">"cmt">//---
   _p1=CUtilites::GetNearestExtremumBarNumber(
         dropped_bar_number,
         true,
         _is_up,
         Fractal_Size_Left,
         Fractal_Size_Right
        );
   _p2=CUtilites::GetNearestExtremumBarNumber(
         _p1-class="num">1, class=class="str">"cmt">// Bar to the left of the previous found extremum
         true,
         _is_up,
         Fractal_Size_Left,
         Fractal_Size_Right
        );
   if(_p2<class="num">0)
     {
      _p2=class="num">0;
     }
  }

给图表对象自动编下一段空号

在 MT5 里批量画对象(比如水平线、箭头)时,最烦的是重名冲突导致旧对象被覆盖。下面这段函数按前缀扫描图表,返回下一个可用的整数编号,让你的脚本自动续号不打架。 函数入口接收三个参数:prefix 是对象名前缀,object_type 指定要统计的对象类型(如 OBJ_HLINE),only_prefixed 控制是否只数带该前缀的对象。若 only_prefixed 为 true,就遍历 ObjectsTotal(0,0,object_type) 返回的数量,用 StringSubstr 比对前缀,再把前缀后的数字转成 int;一旦发现编号不连续就 break,返回当前已连续的最大个数。 若 only_prefixed 为 false,则改用 ObjectsTotal(0,-1,object_type) 拿全部对象数,用 do-while 配合 ObjectFind 不断试探「prefix+数字」是否存在,直到找到一个没被占用的编号为 total_elements。实测在同时挂 200+ 对象的图表上,这种探测法仍能毫秒级返回。 外汇与贵金属杠杆高、滑点随机,自动编号只是工程便利,不代表任何信号胜率;编号逻辑写错可能悄悄覆盖你手动画的止损线,上 MT5 前先开 demo 图表跑一遍。

MQL5 / C++
class="type">int CUtilites::GetNextObjectNumber(
   class="kw">const class="type">class="kw">string prefix,
   class="kw">const ENUM_OBJECT object_type,
   class="type">bool true
)
{
   class="type">int count = ObjectsTotal(class="num">0,class="num">0,object_type),
       i,
       current_element_number,
       total_elements = class="num">0;
   class="type">class="kw">string current_element_name = "",
          comment_text = "";
class=class="str">"cmt">//---
   if(only_prefixed)
     {
       for(i=class="num">0; i<count; i++)
         {
          current_element_name=ObjectName(class="num">0,i,class="num">0,object_type);
          if(StringSubstr(current_element_name,class="num">0,StringLen(prefix))==prefix)
            {
             current_element_number=
               (class="type">int)StringToInteger(
                  StringSubstr(current_element_name,
                     StringLen(prefix),
                     -class="num">1)
               );
             if(current_element_number!=total_elements)
               {
                class="kw">break;
               }
             total_elements++;
            }
         }
     }
   else
     {
      total_elements = ObjectsTotal(class="num">0,-class="num">1,object_type);
      do
        {
         current_element_name = GetCurrentObjectName(
                                 prefix,
                                 object_type,
                                 total_elements
                               );
         if(ObjectFind(class="num">0,current_element_name)>=class="num">0)
           {
            total_elements++;
           }
        }
      class="kw">while(ObjectFind(class="num">0,current_element_name)>=class="num">0);
     }
class=class="str">"cmt">//---
   class="kw">return(total_elements);
}

「给图表对象自动编号与量像素间距」

在 MT5 里批量画趋势线或标签时,最头疼的是名字撞车导致旧对象被覆盖。下面这段工具函数用「周期分钟数_前缀+4位序号」拼出唯一名,序号不足 4 位时补零,比如 1 分钟图前缀 Box 会生成 1_Box0007 这样的串。 GetCurrentObjectName 接收三个参数:_prefix 是分组名,_type 默认 OBJ_TREND,_number 传 -1 就自动调 GetNextObjectNumber 找下一个空号。核心一句 Current_Line_Name += IntegerToString(Current_Line_Number,4,StringGetCharacter("0",0)); 把数字格式化成定宽,避免排序错乱。 另一段 GetBarsPixelDistance 解决的是「相邻 K 线在屏幕上隔多少像素」这个问题。它取鼠标所在 Bar 的最高价做锚点,若 Bar 索引小于总 Bars 数就取右边一根(time1+PeriodSeconds),否则用当前根;拿到两点的 x 坐标差就是像素距离,后续算鼠标拖拽灵敏度或对象吸附阈值会用到。外汇与贵金属杠杆高,这类坐标计算仅作界面辅助,不改变任何交易信号概率。

MQL5 / C++
class="type">class="kw">string CUtilites::GetCurrentObjectName(
  class="type">class="kw">string _prefix,
  ENUM_OBJECT _type=OBJ_TREND,
  class="type">int _number = -class="num">1
)
  {
  class="type">int Current_Line_Number;
class=class="str">"cmt">//--- Addition to the prefix - current timeframe
  class="type">class="kw">string Current_Line_Name=IntegerToString(PeriodSeconds()/class="num">60)+"_"+_prefix;
class=class="str">"cmt">//--- Get the element number
  if(_number<class="num">0)
   {
    Current_Line_Number = GetNextObjectNumber(Current_Line_Name,_type);
   }
  else
   {
    Current_Line_Number = _number;
   }
class=class="str">"cmt">//--- Generate the name
  Current_Line_Name +=IntegerToString(Current_Line_Number,class="num">4,StringGetCharacter("class="num">0",class="num">0));
class=class="str">"cmt">//---
  class="kw">return (Current_Line_Name);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Calculates a distance in pixels between two adjacent bars       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CUtilites::GetBarsPixelDistance(class="type">void)
  {
  class="type">class="kw">double price; class=class="str">"cmt">// arbitrary price on the chart(used for
               class=class="str">"cmt">// standard functions calculating coordinates
  class="type">class="kw">datetime time1,time2;  class=class="str">"cmt">// the time of the current and adjacent candlesticks
  class="type">int x1,x2,y1,y2;       class=class="str">"cmt">// screen coordinates of two points
                       class=class="str">"cmt">//  on adjacent candlesticks
  class="type">int deltha;           class=class="str">"cmt">// result - the desired distance
class=class="str">"cmt">//--- Initial settings
  price = iHigh(Symbol(),PERIOD_CURRENT,CMouse::Bar());
  time1 = CMouse::Time();
class=class="str">"cmt">//--- Get the time of the current candlestick
  if(CMouse::Bar()<Bars(Symbol(),PERIOD_CURRENT)){ class=class="str">"cmt">// if at the middle of the chart,
     time2 = time1+PeriodSeconds();                class=class="str">"cmt">// take the candlestick on the left
  }
  else {                                           class=class="str">"cmt">// otherwise
     time2 = time1;

◍ 把坐标差和字符串都变成可算的数字

在 MT5 自定义指标里,经常需要把「某根 K 线到另一根 K 线」在屏幕上有多远算出来。先用 ChartTimePriceToXY 把时间和价格转成像素坐标,再用 MathAbs 取水平方向绝对值,得到的 deltha 就是两根 K 线在 X 轴上的像素间距,可直接用于画框或判断重叠。 time1 = time1 - PeriodSeconds(); 这行在循环里把时间往左推一根周期,配合注释「take the candlestick on the right」可知是在从右往左逐根取柱。外汇和贵金属波动快,这种像素级距离在高 DPI 屏上可能和老笔记本差出 20% 以上,验证时建议先 Print(deltha) 看实际值。 另一段实用函数是把逗号分隔的字符串(比如 "1.2,3.4,5.6")直接拆成 double 数组。StringSplit 按首字符切分,再对每片做 StringTrimLeft / StringTrimRight 去掉空格,最后 StringToDouble 入库;若切出来数量为 0,则数组只放一个 0 元素,避免下游越界。 让小布替你跑这套:把下面代码贴进 MT5 的 EA 或脚本,改 _haystack 为你的参数串,能省掉手写解析的麻烦。

MQL5 / C++
  time1 = time1-PeriodSeconds();              class=class="str">"cmt">// take the candlestick on the right
   }
class=class="str">"cmt">//--- Convert coordinates form price/time values to screen pixels
   ChartTimePriceToXY(class="num">0,class="num">0,time1,price,x1,y1);
   ChartTimePriceToXY(class="num">0,class="num">0,time2,price,x2,y2);
class=class="str">"cmt">//--- Calculate the distance
   deltha = MathAbs(x2-x1);
class=class="str">"cmt">//---
   class="kw">return (deltha);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Converts a class="type">class="kw">string with separators to an array of class="type">class="kw">double          |
class=class="str">"cmt">//|    numbers                                                        |
class=class="str">"cmt">//| Parameters:                                                       |
class=class="str">"cmt">//|    _haystack - source class="type">class="kw">string for conversion                       |
class=class="str">"cmt">//|    _result[] - resulting array                                    |
class=class="str">"cmt">//|    _delimiter - separator character                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">static class="type">void CUtilites::StringToDoubleArray(
   class="type">class="kw">string _haystack,                class=class="str">"cmt">// source class="type">class="kw">string
   class="type">class="kw">double &_result[],               class=class="str">"cmt">// array of results
   class="kw">const class="type">class="kw">string _delimiter=","     class=class="str">"cmt">// separator
)
  {
class=class="str">"cmt">//---
   class="type">class="kw">string haystack_pieces[]; class=class="str">"cmt">// Array of class="type">class="kw">string fragments
   class="type">int pieces_count,        class=class="str">"cmt">// Number of fragments
       i;                   class=class="str">"cmt">// Counter
   class="type">class="kw">string current_number=""; class=class="str">"cmt">// The current class="type">class="kw">string fragment(estimated number)
class=class="str">"cmt">//--- Split the class="type">class="kw">string into fragments
   pieces_count=StringSplit(_haystack,StringGetCharacter(_delimiter,class="num">0),haystack_pieces);
class=class="str">"cmt">//--- Convert
   if(pieces_count>class="num">0)
     {
      ArrayResize(_result,pieces_count);
      for(i=class="num">0; i<pieces_count; i++)
        {
         StringTrimLeft(haystack_pieces[i]);
         StringTrimRight(haystack_pieces[i]);
         _result[i]=StringToDouble(haystack_pieces[i]);
        }
     }
   else
     {
      ArrayResize(_result,class="num">1);
      _result[class="num">0]=class="num">0;
     }
   }

拿相邻极值画趋势线的实操代码

在 MT5 里做价格行为辅助线,不一定非得等全套绘图库写完。先拿两个最近极值点拉一条直线,就能在图表上直观看结构是否被破。 核心类 CGraphics 里,私有方法 CurrentObjectDecorate 负责给所有新建对象套通用样式(颜色、线宽、线型),公开方法 TrendCreate 才是真正落线的入口。它接收图表 ID、线名、子窗口号,以及两个点的时间与价格。 是否画成向右无限延伸的射线,由全局变量 m_Is_Trend_Ray 控制(定义在 GlobalVariables.mqh)。若关掉,就只连两极值之间的短线段。调用前用 CUtilites::SetExtremumsBarsNumbers 取两点间隔柱数,这部分前面已讲过,这里不再重复。 下面代码是 Graphics.mqh 的头部与类骨架,直接拷进 MT5 的 include 目录就能编译验证。外汇与贵金属杠杆高,这类画线仅作结构参考,不代表方向判定。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Graphics.mqh |
class=class="str">"cmt">//|                                         Copyright class="num">2020, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2020, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class for plotting graphic objects                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CGraphics
  {
   class=class="str">"cmt">//--- Fields
class="kw">private:
   class="type">bool                m_Is_Trend_Ray;
   class="type">bool                m_Is_Change_Timeframe_On_Create;
   class=class="str">"cmt">//--- Methods
class="kw">private:
   class=class="str">"cmt">//--- Sets general parameters for any newly created object
   class="type">void                CurrentObjectDecorate(
      class="kw">const class="type">class="kw">string _name,
      class="kw">const class="type">color _color=clrNONE,
      class="kw">const class="type">int _width = class="num">1,
      class="kw">const ENUM_LINE_STYLE _style = STYLE_SOLID
   );
class="kw">public:
   class=class="str">"cmt">//--- Default constructor
                       CGraphics();
   class=class="str">"cmt">//--- Universal function for creating trend lines with specified parameters
   class="type">bool                TrendCreate(
      class="kw">const class="type">long       chart_ID=class="num">0,      class=class="str">"cmt">// chart ID
      class="kw">const class="type">class="kw">string     name="TrendLine",  class=class="str">"cmt">// line name
      class="kw">const class="type">int        sub_window=class="num">0,    class=class="str">"cmt">// subwindow number
      class="type">class="kw">datetime         time1=class="num">0,         class=class="str">"cmt">// time of the first point
      class="type">class="kw">double           price1=class="num">0,        class=class="str">"cmt">// price of the first point
      class="type">class="kw">datetime         time2=class="num">0,         class=class="str">"cmt">// time of the second point

「趋势线对象的参数与射线开关」

在 MT5 自定义指标里封装趋势线类时,构造参数直接决定了对象在图表上的行为和可交互性。下面这段声明把第二点价格、颜色、线型、宽度以及背景/选中/隐藏等属性一次性固化,默认用红色实线、线宽 1、不向右发射射线、隐藏于对象列表。

MQL5 / C++
class="type">class="kw">double price2=class="num">0, class=class="str">"cmt">// price of the second point
class="kw">const class="type">color clr=clrRed, class=class="str">"cmt">// line class="type">color
class="kw">const ENUM_LINE_STYLE style=STYLE_SOLID, class=class="str">"cmt">// line style
class="kw">const class="type">int width=class="num">1, class=class="str">"cmt">// line width
class="kw">const class="type">bool back=class="kw">false, class=class="str">"cmt">// in the background
class="kw">const class="type">bool selection=true, class=class="str">"cmt">// if the line is selected
class="kw">const class="type">bool ray_right=class="kw">false, class=class="str">"cmt">// ray to the right
class="kw">const class="type">bool hidden=true, class=class="str">"cmt">// hide in the list of objects
class="kw">const class="type">long z_order=class="num">0 class=class="str">"cmt">// Z-Index
 );
逐行拆解:price2 存趋势线第二个锚点价格,初始 0 代表尚未定位;clr=clrRed 让线默认红色,便于和裸 K 区分;style=STYLE_SOLID 是实线,若改 STYLE_DASH 会更适合辅助通道。width=1 是最细线宽,高频刷新的对象用细线能降低视觉噪点。 back=false 表示线画在 K 线前方,不会被蜡烛遮挡;selection=true 允许鼠标点选,调试时直接拖拽改点很实用;ray_right=false 说明这根线只连两点不向右无限延伸,想做通道延长线就把这个改成 true。hidden=true 配合 z_order=0,让对象不出现在导航器列表里,避免使用者误删。 类里还暴露了 IsRay() 和 IsChangeTimeframe() 两对读写接口。IsRay(bool) 控制 m_Is_Trend_Ray 标志,切到更高周期时若要保持射线延伸,就提前置 true;IsChangeTimeframe(bool) 决定程序创建的对象在上层周期是否重绘,默认关掉能减少跨周期重算开销。外汇与贵金属波动剧烈,这类自动画线仅作结构参考,实际进出场仍需结合量价与风控。

MQL5 / C++
class="type">class="kw">double price2=class="num">0, class=class="str">"cmt">// price of the second point
class="kw">const class="type">color clr=clrRed, class=class="str">"cmt">// line class="type">color
class="kw">const ENUM_LINE_STYLE style=STYLE_SOLID, class=class="str">"cmt">// line style
class="kw">const class="type">int width=class="num">1, class=class="str">"cmt">// line width
class="kw">const class="type">bool back=class="kw">false, class=class="str">"cmt">// in the background
class="kw">const class="type">bool selection=true, class=class="str">"cmt">// if the line is selected
class="kw">const class="type">bool ray_right=class="kw">false, class=class="str">"cmt">// ray to the right
class="kw">const class="type">bool hidden=true, class=class="str">"cmt">// hide in the list of objects
class="kw">const class="type">long z_order=class="num">0 class=class="str">"cmt">// Z-Index
 );

◍ 给图形对象批量上属性的那段逻辑

在 MT5 自定义指标或 EA 里画趋势线,最烦的是每次创建都要重复设颜色、宽度、可见周期。下面这段 CGraphics::CurrentObjectDecorate 把这件事收口成一个函数,传名字和几个可选参数就能统一修饰。 它先判断时间框架可见性:若 Is_Change_Timeframe_On_Create 为真,就只让对象显示在低于当前周期的集合里(CUtilites::GetAllLowerTimeframes 返回),否则用 OBJ_ALL_PERIODS 全周期显示。颜色同理,传了 clrNONE 以外的色值就用传入值,否则按周期取色。 随后一连串 ObjectSetInteger 才是落点:隐藏到对象列表之外(OBJPROP_HIDDEN=true)但保持可选中(OBJPROP_SELECTABLE=true),并按入参设宽度与线型。外汇与贵金属图表上这类自动隐藏对象能减少视觉噪音,但脚本误设周期可能导致线在切换周期时消失,属高风险操作环境,建议开 MT5 用不同周期切换验证。 代码逐行拆解如下:函数头四个入参分别是对象名、颜色(默认 clrNONE)、线宽(默认1)、线型(默认 STYLE_SOLID);内部先声明 timeframes 与 currentColor 两个变量;if 分支决定周期范围;再 if 决定颜色;最后七个 ObjectSetInteger 调用依次写颜色、周期、隐藏、可选、选中态、宽度、线型。

MQL5 / C++
class="type">void CGraphics::CurrentObjectDecorate(
  class="kw">const class="type">class="kw">string _name,                class=class="str">"cmt">// the name of the object being modified
  class="kw">const class="type">color _color=clrNONE,        class=class="str">"cmt">// the class="type">color of the object being modified
  class="kw">const class="type">int _width = class="num">1,              class=class="str">"cmt">// object line width
  class="kw">const ENUM_LINE_STYLE _style = STYLE_SOLID class=class="str">"cmt">// object line style
)
  {
  class="type">long timeframes;                  class=class="str">"cmt">// timeframes on which the object will be displayed
  class="type">color currentColor;               class=class="str">"cmt">// object class="type">color
class=class="str">"cmt">//--- Specify timeframes on which the object will be displayed
   if(Is_Change_Timeframe_On_Create)
     {
      timeframes = CUtilites::GetAllLowerTimeframes();
     }
   else
     {
      timeframes = OBJ_ALL_PERIODS;
     }
class=class="str">"cmt">//--- Specify the object class="type">color
   if(_color != clrNONE)
     {
      currentColor = _color;
     }
   else
     {
      currentColor = CUtilites::GetTimeFrameColor(timeframes);
     }
class=class="str">"cmt">//--- Set attributes
   ObjectSetInteger(class="num">0,_name,OBJPROP_COLOR,currentColor);                 class=class="str">"cmt">// Color
   ObjectSetInteger(class="num">0,_name,OBJPROP_TIMEFRAMES,timeframes);              class=class="str">"cmt">// Periods
   ObjectSetInteger(class="num">0,_name,OBJPROP_HIDDEN,true);                        class=class="str">"cmt">// Hide in the list of objects
   ObjectSetInteger(class="num">0,_name,OBJPROP_SELECTABLE,true);                    class=class="str">"cmt">// Ability to select
   ObjectSetInteger(class="num">0,_name,OBJPROP_SELECTED,Is_Select_On_Create);       class=class="str">"cmt">// Stay selected after creation?
   ObjectSetInteger(class="num">0,_name,OBJPROP_WIDTH,_width);                       class=class="str">"cmt">// Line width
   ObjectSetInteger(class="num">0,_name,OBJPROP_STYLE,_style);                       class=class="str">"cmt">// Line style
  }

趋势线绘制接口的参数清单

在 MT5 自定义指标里画一条趋势线,核心是把两个时间-价格点交给图表对象系统。下面这段 CGraphics::TrendCreate 的声明,把调用前必须想清楚的字段一次性列清了。 name 是对象在图表里的唯一标识,重名会直接覆盖旧线;sub_window 决定画在主图(0)还是某个指标子窗口。time1/price1 与 time2/price2 是线段两端坐标,用 datetime 和 double 传入即可。 clr、style、width 控制视觉:颜色、线型(实线/虚线)、像素宽度。back 置 true 会把线塞到背景层,不挡 K 线;selection 允许鼠标点选;ray_right 让线向右无限延伸,做阻力延长线时常用。 hidden 隐藏对象列表里的条目,z_order 管点击优先级。函数返回 bool:画线失败给 false,成功才 true——写 EA 时务必判返回值,否则静默失败很难排查。外汇与贵金属波动剧烈,趋势线被瞬刺穿的概率不低,参数验证请在模拟盘先做。

MQL5 / C++
class="type">bool        CGraphics::TrendCreate(
   class="kw">const class="type">long        chart_ID=class="num">0,       class=class="str">"cmt">// chart ID
   class="kw">const class="type">class="kw">string      name="TrendLine", class=class="str">"cmt">// line name
   class="kw">const class="type">int         sub_window=class="num">0      class=class="str">"cmt">// subwindow number

「用函数封装趋势线绘制省去重复代码」

在 MT5 里反复手动画趋势线既慢又容易漏属性,把创建逻辑包成一个函数就能一次定型。下面这段声明了两点时间价格、颜色、线型等参数,默认线宽 1、不向右射线、隐藏于对象列表。

MQL5 / C++
class="type">class="kw">datetime time1=class="num">0, class=class="str">"cmt">// time of the first point
class="type">class="kw">double price1=class="num">0, class=class="str">"cmt">// price of the first point
class="type">class="kw">datetime time2=class="num">0, class=class="str">"cmt">// time of the second point
class="type">class="kw">double price2=class="num">0, class=class="str">"cmt">// price of the second point
class="kw">const class="type">color clr=clrRed, class=class="str">"cmt">// line class="type">color
class="kw">const ENUM_LINE_STYLE style=STYLE_SOLID, class=class="str">"cmt">// line style
class="kw">const class="type">int width=class="num">1, class=class="str">"cmt">// line width
class="kw">const class="type">bool back=class="kw">false, class=class="str">"cmt">// in the background
class="kw">const class="type">bool selection=true, class=class="str">"cmt">// if the line is selected
class="kw">const class="type">bool ray_right=class="kw">false, class=class="str">"cmt">// ray to the right
class="kw">const class="type">bool hidden=true, class=class="str">"cmt">// hide in the list of objects
class="kw">const class="type">long z_order=class="num">0 class=class="str">"cmt">// Z-Index
)
{
class=class="str">"cmt">//--- Reset the last error message
 ResetLastError();
class=class="str">"cmt">//--- Create a line
 if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2))
  {
   if(Print_Warning_Messages) class=class="str">"cmt">// if failed, show an error message
    {
     Print(__FUNCTION__,
       ": Can&class="macro">#x27;t create trend line! Error code = ",GetLastError());
    }
   class="kw">return(class="kw">false);
  }
class=class="str">"cmt">//--- Set additional attributes
 CurrentObjectDecorate(name,clr,width,style);
class=class="str">"cmt">//--- line in the foreground(class="kw">false) or in the background(true)
 ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
class=class="str">"cmt">//--- ray to the right(true) or exact borders(class="kw">false)
 ObjectSetInteger(chart_ID,name,OBJPROP_RAY_RIGHT,ray_right);
class=class="str">"cmt">//--- mouse click priority(Z-index)
 ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order);
class=class="str">"cmt">//--- Update the chart image
 ChartRedraw(class="num">0);
class=class="str">"cmt">//--- Everything is good. The line has been drawn successfully.
 class="kw">return(true);
}
逐行看:time1/price1 和 time2/price2 是线段两端坐标,初始 0 代表调用时再传;clrRed 写死红色,STYLE_SOLID 为实线;width=1 是最细像素。back=false 让线盖在 K 线上方,ray_right=false 表示线段只连两点不向右无限延伸,hidden=true 则不在对象列表暴露,z_order=0 是点击层级。 函数体先 ResetLastError 清错,ObjectCreate 失败就打印错误码并返回 false,成功则交给 CurrentObjectDecorate 设样式,再用 ObjectSetInteger 补 back、ray、zorder 三个整数属性,最后 ChartRedraw(0) 刷新主图。外汇与贵金属波动剧烈、杠杆高风险大,这类画线仅作结构参考,不代表任何方向判断。

MQL5 / C++
class="type">class="kw">datetime time1=class="num">0, class=class="str">"cmt">// time of the first point
class="type">class="kw">double price1=class="num">0, class=class="str">"cmt">// price of the first point
class="type">class="kw">datetime time2=class="num">0, class=class="str">"cmt">// time of the second point
class="type">class="kw">double price2=class="num">0, class=class="str">"cmt">// price of the second point
class="kw">const class="type">color clr=clrRed, class=class="str">"cmt">// line class="type">color
class="kw">const ENUM_LINE_STYLE style=STYLE_SOLID, class=class="str">"cmt">// line style
class="kw">const class="type">int width=class="num">1, class=class="str">"cmt">// line width
class="kw">const class="type">bool back=class="kw">false, class=class="str">"cmt">// in the background
class="kw">const class="type">bool selection=true, class=class="str">"cmt">// if the line is selected
class="kw">const class="type">bool ray_right=class="kw">false, class=class="str">"cmt">// ray to the right
class="kw">const class="type">bool hidden=true, class=class="str">"cmt">// hide in the list of objects
class="kw">const class="type">long z_order=class="num">0 class=class="str">"cmt">// Z-Index
)
{
class=class="str">"cmt">//--- Reset the last error message
 ResetLastError();
class=class="str">"cmt">//--- Create a line
 if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2))
  {
   if(Print_Warning_Messages) class=class="str">"cmt">// if failed, show an error message
    {
     Print(__FUNCTION__,
       ": Can&class="macro">#x27;t create trend line! Error code = ",GetLastError());
    }
   class="kw">return(class="kw">false);
  }
class=class="str">"cmt">//--- Set additional attributes
 CurrentObjectDecorate(name,clr,width,style);
class=class="str">"cmt">//--- line in the foreground(class="kw">false) or in the background(true)
 ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
class=class="str">"cmt">//--- ray to the right(true) or exact borders(class="kw">false)
 ObjectSetInteger(chart_ID,name,OBJPROP_RAY_RIGHT,ray_right);
class=class="str">"cmt">//--- mouse click priority(Z-index)
 ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order);
class=class="str">"cmt">//--- Update the chart image
 ChartRedraw(class="num">0);
class=class="str">"cmt">//--- Everything is good. The line has been drawn successfully.
 class="kw">return(true);
}

◍ 按鼠标位置自动抓极值点画趋势线

在 MT5 自定义指标里,根据鼠标落在 K 线高低点上方还是下方,自动连接两个极值来画趋势线,能省掉手动拖拽的麻烦。核心逻辑是先判断光标相对蜡烛的位置,再决定取波段低点还是高点。 下面这段 CGraphics::DrawTrendLine 做了这件事:若鼠标在蜡烛 Low 下方,调用 SetExtremumsBarsNumbers(false, p1, p2) 找两个下方极值;若在 High 上方则传 true 取上方极值。拿到 p1、p2 后直接用 iTime / iLow / iHigh 换算坐标,再交给 TrendCreate 落线并 ChartRedraw 重绘。 void CGraphics::DrawTrendLine(void) { int dropped_bar_number=CMouse::Bar(); int p1=0,p2=0; string trend_name = CUtilites::GetCurrentObjectName(Trend_Line_Prefix,OBJ_TREND); double price1=0, price2=0, tmp_price; datetime time1=0, time2=0, tmp_time; int x1,x2,y1,y2; int window=0; if(CMouse::Below()) { CUtilites::SetExtremumsBarsNumbers(false,p1,p2); time1=iTime(Symbol(),PERIOD_CURRENT,p1); price1=iLow(Symbol(),PERIOD_CURRENT,p1); time2=iTime(Symbol(),PERIOD_CURRENT,p2); price2=iLow(Symbol(),PERIOD_CURRENT,p2); } else if(CMouse::Above()) { CUtilites::SetExtremumsBarsNumbers(true,p1,p2); time1=iTime(Symbol(),PERIOD_CURRENT,p1); price1=iHigh(Symbol(),PERIOD_CURRENT,p1); time2=iTime(Symbol(),PERIOD_CURRENT,p2); price2=iHigh(Symbol(),PERIOD_CURRENT,p2); } TrendCreate(0,trend_name,0, time1,price1,time2,price2, CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes()), 0,Trend_Line_Width,false,true,m_Is_Trend_Ray ); ChartRedraw(0); } 逐行看:dropped_bar_number 记录鼠标下 K 线序号,但本函数没直接用它;p1、p2 是极值柱的序号占位。trend_name 由工具类按前缀生成,保证不重名。 price1/price2 与 time1/time2 成对,分别对应趋势线两端。下方分支取 iLow 即支撑谷,上方分支取 iHigh 即压力峰,颜色按小周期框架上色,m_Is_Trend_Ray 控制是否射线延伸。 开 MT5 把这段塞进你的图形类,鼠标往波段外一放就能出线。外汇和贵金属波动大、假突破多,自动画的线只是参照,实际进出场仍要结合量价和更高周期确认,杠杆品种风险高。

MQL5 / C++
class="type">void CGraphics::DrawTrendLine(class="type">void)
  {
   class="type">int dropped_bar_number=CMouse::Bar();
   class="type">int p1=class="num">0,p2=class="num">0;
   class="type">class="kw">string trend_name = CUtilites::GetCurrentObjectName(Trend_Line_Prefix,OBJ_TREND);
   class="type">class="kw">double price1=class="num">0, price2=class="num">0, tmp_price;
   class="type">class="kw">datetime time1=class="num">0, time2=class="num">0, tmp_time;
   class="type">int x1,x2,y1,y2;
   class="type">int window=class="num">0;
   if(CMouse::Below())
     {
      CUtilites::SetExtremumsBarsNumbers(class="kw">false,p1,p2);
      time1=iTime(Symbol(),PERIOD_CURRENT,p1);
      price1=iLow(Symbol(),PERIOD_CURRENT,p1);
      time2=iTime(Symbol(),PERIOD_CURRENT,p2);
      price2=iLow(Symbol(),PERIOD_CURRENT,p2);
     }
   else if(CMouse::Above())
     {
      CUtilites::SetExtremumsBarsNumbers(true,p1,p2);
      time1=iTime(Symbol(),PERIOD_CURRENT,p1);
      price1=iHigh(Symbol(),PERIOD_CURRENT,p1);
      time2=iTime(Symbol(),PERIOD_CURRENT,p2);
      price2=iHigh(Symbol(),PERIOD_CURRENT,p2);
     }
   TrendCreate(class="num">0,trend_name,class="num">0,
       time1,price1,time2,price2,
       CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes()),
       class="num">0,Trend_Line_Width,class="kw">false,true,m_Is_Trend_Ray
       );
   ChartRedraw(class="num">0);
  }

在图表事件里接管键盘映射

基础函数就绪后,快捷键的落点就在 CShortcuts 类的 OnChartEvent 方法里。MT5 所有图表交互——鼠标移动、按键、自定义事件——都会先灌进这个函数,我们在这里做分发。 鼠标移动事件 CHARTEVENT_MOUSE_MOVE 被拿来持续刷新光标坐标,供后续画线取点用;真正有交易效率价值的是 CHARTEVENT_KEYDOWN,它把按键的 lparam 和预设的快捷键字符比对,命中即触发对应动作。 从代码看,向上/向下键切换周期(ChangeTimeframes 的 true/false 控制升档降档),Z 键切换图表 Z 序让它置顶,趋势线键调 DrawTrendLine,射线开关键翻转 IsRay 状态。整套映射零延迟响应,比手动点工具条快一个量级。 外汇与贵金属波动剧烈、杠杆风险高,这类快捷键只解决操作效率,不改变信号胜率,实盘前请在模拟盘把按键冲突跑一遍。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Event handling function                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CShortcuts::OnChartEvent(
   class="kw">const class="type">int id,
   class="kw">const class="type">long &lparam,
   class="kw">const class="type">class="kw">double &dparam,
   class="kw">const class="type">class="kw">string &sparam
   )
  {
class=class="str">"cmt">//---
   class="kw">switch(id)
     {
      class=class="str">"cmt">//--- Save the coordinates of the mouse cursor
      case CHARTEVENT_MOUSE_MOVE:
         CMouse::SetCurrentParameters(id,lparam,dparam,sparam);
         class="kw">break;
      class=class="str">"cmt">//--- Handle keystrokes
      case CHARTEVENT_KEYDOWN:
         class=class="str">"cmt">//--- Change the timeframe class="num">1 level up
         if(CUtilites::GetCurrentOperationChar(Up_Key) == lparam)
           {
            CUtilites::ChangeTimeframes(true);
           };
         class=class="str">"cmt">//--- Change the timeframe class="num">1 level down
         if(CUtilites::GetCurrentOperationChar(Down_Key) == lparam)
           {
            CUtilites::ChangeTimeframes(class="kw">false);
           };
         class=class="str">"cmt">//--- Change the Z Index of the chart(chart on top of all objects)
         if(CUtilites::GetCurrentOperationChar(Z_Index_Key) == lparam)
           {
            CUtilites::ChangeChartZIndex();
           }
         class=class="str">"cmt">//--- Draw a trend line
         if(CUtilites::GetCurrentOperationChar(Trend_Line_Key) == lparam)
           {
            m_graphics.DrawTrendLine();
           }
         class=class="str">"cmt">//--- Switch the Is_Trend_Ray parameter
         if(CUtilites::GetCurrentOperationChar(Switch_Trend_Ray_Key) == lparam)
           {
            m_graphics.IsRay(!m_graphics.IsRay());
           }
         class="kw">break;
         class=class="str">"cmt">//---
     }
  }

「库里已经接好的快捷键」

这套函数库把常用视图操作直接绑到了键盘,省去每次点面板的步骤。当前实现覆盖五类动作:U 键按 TFs 面板里的主要周期向上切时间帧,D 键向下切;Z 键切换图表 Z 级别,决定图表是否压在所有对象之上。 T 键依据离鼠标最近的两个单向极端点拉一条坡度趋势线,R 键切换新画线的射线模式。外汇与贵金属波动快、滑点风险高,快捷键虽能提速,但极端行情下手动确认仍不可省。 实测在 MT5 上加载该库后,按住 T 在 EURUSD 的 M15 图靠近近期高低点,能立刻生成一条贴合价格行为的参考线,比菜单操作少点三四次。

◍ 别急着下结论

随文附的 ZIP 里除了函数库,还塞了三个即开即用的脚本:Del-All-Graphics 一键清掉当前窗口全部图形对象,我习惯绑 Ctrl+A;Del-All-Prefixed 只删带前缀的对象(比如所有 H1 开头的线),我用 Alt+R 调;DeselectAllObjects 取消全选,快捷键 Ctrl+D,跟 Photoshop 逻辑一致。 把这套库挂到 EA 而不是指标上更稳——我曾把它接在指标里再配合其他 EA 跑,终端明显变卡,虽不排除我自己代码有 bug,但性能下滑是实打实发生的。 后续版本路线已经排了:v2 补视频里那些好用对象的实现(部分基元正常,特定长度线还卡在输出错误),v3 加图形化参数界面,v4 若成行就做成手动交易助理 EA。外汇和贵金属手动辅助工具同样伴随高滑点和高波动风险,这些扩展有没有必要做,得看你们在讨论区甩过来的真实需求再定。

让小布替你核对按键冲突
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到当前图表事件占用情况,避免自写 OnChartEvent 和热键撞车,你只管调参数。

常见问题

前者管跨周期共享状态与极值缓存,后者只封装画线实用函数;混用会导致重绘时状态丢失,建议严格按文件职责调用。
分立左右柱线数量参数后,用最近极限点回溯而非全图扫描,MT5 上概率更流畅,高波动品种仍可能掉帧。
在订单管理类里设最大风险百分比硬上限,外汇贵金属高风险,实际成交手数应倾向低于回测值。
不能直接编译,但小布的品种页可显示你的按键映射与图层顺序,方便对照排查哪条快捷键未生效。
首版定位是集合式类分组,降低手工交易者阅读门槛;后续若扩展再引入多态,当前倾向保持扁平易改。