MQL5 Cookbook: 处理典型图表事件·进阶篇
📘

MQL5 Cookbook: 处理典型图表事件·进阶篇

第 2/3 篇

◍ EA 如何接管按键、鼠标与图表事件

MT5 的 EA 不是只能盯 K 线,它能直接吃进终端的各类交互事件。按键分支里,lparam 捕获的是按键代码:上箭头是 38、下箭头是 40,分别对应 TryToBuy() 和 TryToSell(),手数止损止盈走 InpLot、InpStopLoss 这类外部输入。 鼠标移动事件默认不触发,得在初始化里用 ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, is_mouse) 开权限。光标 X/Y 落在 lparam 和 dparam,利用 InpChartShiftSize 控制零柱线到右框的偏移百分比,滑过边界弹买卖确认窗——这套在 EventProcessor2 里可复现。 图形对象的新建、改属性、删除都各有一套开关:CHART_EVENT_OBJECT_CREATE / OBJECT_DELETE 等。删对象有个坑,属性在被删后读不到,想判断类型得在删除前自行缓存。开发者若肯加图表历史快照会更省事。 点击类事件最容易误触发。在对象上点一下,往往同时抛出「对象点击」和「图表点击」两个事件,导致买箭头被重复画。用全局变量 gLastTime 卡 250ms 间隔忽略二次调用,EventProcessor7 就是这么收口的。 文本框编辑事件(CHARTEVENT_OBJECT_ENDEDIT)适合做指令入口:输入 buy/sell 不区分大小写,EA 下单并把框染蓝或红。图表修改事件则能锁设置,有人改栅格或背景色时 EA 直接回写旧值。外汇与贵金属波动剧烈,这类半自动交互仍属高风险操作,参数请先在模拟盘验证。

MQL5 / C++
class=class="str">"cmt">//--- class="num">1
        case CHARTEVENT_KEYDOWN:
          {
           class=class="str">"cmt">//--- "up" arrow
           if(lparam==class="num">38)
              TryToBuy();
           class=class="str">"cmt">//--- "down" arrow
           else if(lparam==class="num">40)
              TryToSell();
           comment+="class="num">1) keystroke";
           class=class="str">"cmt">//---
           break;
          }
class=class="str">"cmt">//--- mouse move
class="type">bool is_mouse=false;
if(InpIsEventMouseMove)
   is_mouse=true;
ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,is_mouse);
class=class="str">"cmt">//--- class="num">2
        case CHARTEVENT_MOUSE_MOVE:
          {
           comment+="class="num">2) mouse";
           class=class="str">"cmt">//--- if a mouse event is processed

鼠标越界弹窗下单与对象创建事件

在 MT5 图表脚本里,可以通过捕捉鼠标移动事件来实现半自动下单触发。下面这段逻辑先判断开关 InpIsEventMouseMove,开启后调用 ChartShiftSet(true) 把图表平移打开,再用 ChartShiftSizeSet 按百分比设定右侧留白区。 核心在于算出卖单边界的像素坐标:chart_shift_x = 图表总宽 - 总宽 * InpChartShiftSize / 100。当鼠标 X 从左侧跨过这条边界(lparam 大于边界且上一帧 last_mouse_x 小于边界),就弹 MessageBox 问「Yes 买 / No 卖」,分别进 TryToBuy / TryToSell。外汇与贵金属波动剧烈,这种点击触发不带任何风控,实盘误触可能造成直接敞口,只建议在模拟盘验证。 另一个常被忽略的开关是 CHART_EVENT_OBJECT_CREATE。把 InpIsEventObjectCreate 置真后,ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true) 会让脚本收到画对象事件;在 case CHARTEVENT_OBJECT_CREATE 里可用 ObjectsTotal(0,0,OBJ_HLINE) 统计当前水平线数量,方便你做「画一根线就记录一次」的辅助逻辑。 别把弹窗当信号源:它只认边界穿越这一动作,不分辨趋势和流动性。真要接小布盯盘的结论,得把 TryToBuy/Sell 里换成你的置信度过滤。

MQL5 / C++
if(InpIsEventMouseMove)
  {
   class="type">long class="kw">static last_mouse_x;
   class=class="str">"cmt">//--- enable shift
   if(ChartShiftSet(true))
     class=class="str">"cmt">//--- set shift size 
     if(ChartShiftSizeSet(InpChartShiftSize))
       {
        class=class="str">"cmt">//--- chart width
        class="type">int chart_width=ChartWidthInPixels();
        class=class="str">"cmt">//--- calculate X coordinate of shift border
        class="type">int chart_shift_x=(class="type">int)(chart_width-chart_width*InpChartShiftSize/class="num">100.);
        class=class="str">"cmt">//--- border crossing condition
        if(lparam>chart_shift_x && last_mouse_x<chart_shift_x)
          {
           class="type">int res=MessageBox("Yes: buy / No: sell","Trade operation",MB_YESNO);
           class=class="str">"cmt">//--- buy
           if(res==IDYES)
             TryToBuy();
           class=class="str">"cmt">//--- sell
           else if(res==IDNO)
             TryToSell();
          }
        class=class="str">"cmt">//--- store mouse X coordinate
        last_mouse_x=lparam;
       }
  }
class=class="str">"cmt">//---
break;
}
class=class="str">"cmt">//--- object create
class="type">bool is_obj_create=false;
if(InpIsEventObjectCreate)
   is_obj_create=true;
ChartSetInteger(class="num">0,CHART_EVENT_OBJECT_CREATE,is_obj_create);
class=class="str">"cmt">//--- class="num">3
   case CHARTEVENT_OBJECT_CREATE:
     {
      comment+="class="num">3) create graphical object";
      class=class="str">"cmt">//--- if graphical object creation event is processed
      if(InpIsEventObjectCreate)
        {
         class=class="str">"cmt">//--- capture creation of horizontal line
         class="type">int all_hor_lines=ObjectsTotal(class="num">0,class="num">0,OBJ_HLINE);

「单线模式下取可视区间高低中三价」

当水平线总数被限定为 1 条时,脚本会直接进入单线分支,把传入的参数名作为主线对象名,并基于当前图表可视 K 线重算锚定价格。这里用 ChartVisibleBars() 拿到可视柱数,再减 1 作为 CopyHigh / CopyLow 的复制数量,意味着只处理视窗内完整可见的行情,不碰右侧未收线部分。 复制高点与低点都做了返回值校验:若 copied 不等于 visible_bars_num-1,就打印失败并 return,避免后续用空数组算出离谱价位。这一道防御在 MT5 切换周期或图表未完全渲染时尤其关键,否则 ArrayMaximum 可能落在无效数据上。 真正定价的三行是核心:ch_high_pr 取 highs 最大值并按 _Digits 规范化,ch_low_pr 取 lows 最小值规范化,ch_mid_pr 则是二者均值。若主线对象已存在就用 ObjectMove 挪到 high 价;失败同样打印并返回。随后另起一个名为 Hor_line_min 的字符串准备挂 low 线——外汇与贵金属波动大,这类自动重锚线只作参考,实盘仍需警惕滑点风险。

MQL5 / C++
if(all_hor_lines==class="num">1)
  {
   class="type">class="kw">string hor_line_name1=sparam;
   class=class="str">"cmt">//--- calculate levels
   class="type">int visible_bars_num=ChartVisibleBars();
   class=class="str">"cmt">//--- arrays for high and low prices
   class="type">class="kw">double highs[],lows[];
   class=class="str">"cmt">//---
   class="type">int copied=CopyHigh(_Symbol,_Period,class="num">0,visible_bars_num-class="num">1,highs);
   if(copied!=visible_bars_num-class="num">1)
     {
      Print("Failed to copy highs!");
      class="kw">return;
     }
   copied=CopyLow(_Symbol,_Period,class="num">0,visible_bars_num-class="num">1,lows);
   if(copied!=visible_bars_num-class="num">1)
     {
      Print("Failed to copy lows!");
      class="kw">return;
     }
   class=class="str">"cmt">//--- high and low prices
   class="type">class="kw">double ch_high_pr,ch_low_pr,ch_mid_pr;
   class=class="str">"cmt">//---
   ch_high_pr=NormalizeDouble(highs[ArrayMaximum(highs)],_Digits);
   ch_low_pr=NormalizeDouble(lows[ArrayMinimum(lows)],_Digits);
   ch_mid_pr=NormalizeDouble((ch_high_pr+ch_low_pr)/class="num">2.,_Digits);
   class=class="str">"cmt">//--- place created line on high
   if(ObjectFind(class="num">0,hor_line_name1)>-class="num">1)
    if(!ObjectMove(class="num">0,hor_line_name1,class="num">0,class="num">0,ch_high_pr))
     {
      Print("Failed to move!");
      class="kw">return;
     }
   class=class="str">"cmt">//--- create line on low
   class="type">class="kw">string hor_line_name2="Hor_line_min";

◍ 用事件同步图表对象的颜色

在 MT5 的图表事件处理里,CHARTEVENT_OBJECT_CHANGE 会在你通过属性框改了某个对象后触发。下面这段逻辑做的是:抓住当前被改的对象名,把它的最新颜色同步给图上其余所有对象,适合做一套联动的高亮水平线组。 先拿 sparam 拿到改动对象的名字,用 ObjectFind 确认它确实在图上(返回值大于 -1 才继续)。随后用 ObjectGetInteger 取 OBJPROP_COLOR,再用 ObjectsTotal 数出当前图表总对象数。 接着一个 for 循环从 0 扫到 all_other_objects-1,用 ObjectName 拿每个对象的名字;只要名字和当前改的不是同一个,就 ObjectSetInteger 把颜色刷成一样的。任意一次设置失败会 Print 报错并 return,外汇和贵金属图表对象多的时候这种失败概率会随对象数上升,属正常现象。 别把联动当默认 MT5 不会自动帮你同步对象属性,上面这套得自己写进 OnChartEvent;不改代码,你拖一根线改色,别的线依旧各走各的。

MQL5 / C++
case CHARTEVENT_OBJECT_CHANGE:
  {
   comment+="class="num">4) change object properties via properties dialog";
   class=class="str">"cmt">//---
   class="type">class="kw">string curr_obj_name=sparam;
   class=class="str">"cmt">//--- find the changed object
   if(ObjectFind(class="num">0,curr_obj_name)>-class="num">1)
     {
      class=class="str">"cmt">//--- get object class="type">color
      class="type">color curr_obj_color=(class="type">color)ObjectGetInteger(class="num">0,curr_obj_name,OBJPROP_COLOR);
      class=class="str">"cmt">//--- total number of objects on chart
      class="type">int all_other_objects=ObjectsTotal(class="num">0);
      class=class="str">"cmt">//--- find other objects
      for(class="type">int obj_idx=class="num">0;obj_idx<all_other_objects;obj_idx++)
        {
         class="type">class="kw">string other_obj_name=ObjectName(class="num">0,obj_idx);
         if(StringCompare(curr_obj_name,other_obj_name)!=class="num">0)
         if(!ObjectSetInteger(class="num">0,other_obj_name,OBJPROP_COLOR,curr_obj_color))
           {
            Print("Failed to change the object class="type">color!");
            class="kw">return;
           }
         }
     }
   break;
  }

图表事件里的对象清理与鼠标落点建标

在图表事件处理里,删除对象的开关由输入参数 InpIsEventObjectDelete 控制。代码通过 ChartSetInteger(0,CHART_EVENT_OBJECT_DELETE,is_obj_delete) 把开关写到当前图表,true 时图表才会派发对象删除事件,避免误删手动画的线。 一段 EURUSD.e 的 W1 日志很说明问题:移除前垂直线数量是 4,移除后变成 0,说明事件触发后清理逻辑确实把 4 条线全摘掉了。外汇和贵金属品种波动大,这种批量清理若开关误开,可能把你自己留的分析线一起删掉,高风险操作前先确认参数。 鼠标点击分支里,CHARTEVENT_CLICK 会把 lparam、dparam 当作像素坐标。用 ChartXYToTimePrice 转成时间 obj_time 和价格 obj_price,再 ObjectCreate 画 OBJ_ARROW_BUY 箭头,名称按 buy_sign_ 加静态计数器递增,点一次多一个买标。 别把坐标转换当理所当然。ChartXYToTimePrice 失败时就别建对象,否则箭头会落到 (0,0) 附近变成垃圾图形;实盘前在 MT5 用 EURUSD 的 M1 连点几下,看箭头是否精准落在你点的 K 线和价位上。

MQL5 / C++
            class=class="str">"cmt">//--- redraw chart
            ChartRedraw();
           }
         class=class="str">"cmt">//---
         break;
class=class="str">"cmt">//--- object class="kw">delete
   class="type">bool is_obj_delete=false;
   if(InpIsEventObjectDelete)
      is_obj_delete=true;
   ChartSetInteger(class="num">0,CHART_EVENT_OBJECT_DELETE,is_obj_delete);
class=class="str">"cmt">//--- class="num">6
         case CHARTEVENT_CLICK:
           {
            comment+="class="num">6) mouse click on chart";
            class=class="str">"cmt">//--- object counter 
            class="kw">static class="type">uint sign_obj_cnt;
            class="type">class="kw">string buy_sign_name="buy_sign_"+IntegerToString(sign_obj_cnt+class="num">1);
            class=class="str">"cmt">//--- coordinates 
            class="type">int mouse_x=(class="type">int)lparam;
            class="type">int mouse_y=(class="type">int)dparam;
            class=class="str">"cmt">//--- time and price
            class="type">class="kw">datetime obj_time;
            class="type">class="kw">double obj_price;
            class="type">int sub_window;
            class=class="str">"cmt">//--- convert the X and Y coordinates to the time and price values
            if(ChartXYToTimePrice(class="num">0,mouse_x,mouse_y,sub_window,obj_time,obj_price))
              {
               class=class="str">"cmt">//--- create object
               if(!ObjectCreate(class="num">0,buy_sign_name,OBJ_ARROW_BUY,class="num">0,obj_time,obj_price))
                 {
                  Print("Failed to create buy sign!");
                  class="kw">return;
                 }
               class=class="str">"cmt">//--- redraw chart
               ChartRedraw();
               class=class="str">"cmt">//--- increase object counter
               sign_obj_cnt++;
              }
            class=class="str">"cmt">//---
            break;
           }
class=class="str">"cmt">//--- class="num">7
         case CHARTEVENT_OBJECT_CLICK:
           {

「用鼠标点击在图上落卖出箭头」

图表事件里捕捉到鼠标点击图形对象时,先把被点对象名取出来尝试删除,成功就重绘并准备画反向信号。下面这段逻辑把点击坐标转成时间价格再建卖出箭头,是 MT5 里手动标信号最常抄的写法。 代码里用 ObjectDelete(0,sign_name) 删掉原买入箭头,ChartRedraw() 立即刷新。接着用静态计数器拼出 sell_sign_ 加序号的名字,避免重名覆盖。 鼠标 X/Y 来自 lparam、dparam 强转 int,交给 ChartXYToTimePrice() 换算成 obj_time 与 obj_price;换算成功才 ObjectCreate 画 OBJ_ARROW_SELL。外汇和贵金属波动快,手动标点仅作参考,实际下单仍属高风险行为,信号箭头不代表行情必然反转。 另有一段用 GetTickCount 算间隔:若距上次超过 250 毫秒才处理点击,能过滤掉连点造成的重复触发。拖拽事件 CHARTEVENT_OBJECT_DRAG 中若拖的是两条时间限制线,就进后续分支——这部分留给你在 MT5 里挂 EA 跑一遍看 sparam 返回什么。

MQL5 / C++
comment+="class="num">7) mouse click on graphical object";
class=class="str">"cmt">//---
class="type">class="kw">string sign_name=sparam;
class=class="str">"cmt">//--- class="kw">delete buy arrow
if(ObjectDelete(class="num">0,sign_name))
  {
   class=class="str">"cmt">//--- redraw chart
   ChartRedraw();
   class=class="str">"cmt">//---
   class="kw">static class="type">uint sign_obj_cnt;
   class="type">class="kw">string sell_sign_name="sell_sign_"+IntegerToString(sign_obj_cnt+class="num">1);
   class=class="str">"cmt">//--- coordinates 
   class="type">int mouse_x=(class="type">int)lparam;
   class="type">int mouse_y=(class="type">int)dparam;
   class=class="str">"cmt">//--- time and price
   class="type">class="kw">datetime obj_time;
   class="type">class="kw">double obj_price;
   class="type">int sub_window;
   class=class="str">"cmt">//--- convert the X and Y coordinates to the time and price values
   if(ChartXYToTimePrice(class="num">0,mouse_x,mouse_y,sub_window,obj_time,obj_price))
     {
      class=class="str">"cmt">//--- create object
      if(!ObjectCreate(class="num">0,sell_sign_name,OBJ_ARROW_SELL,class="num">0,obj_time,obj_price))
        {
         Print("Failed to create sell sign!");
         class="kw">return;
        }
      class=class="str">"cmt">//--- redraw chart
      ChartRedraw();
      class=class="str">"cmt">//--- increase object counter
      sign_obj_cnt++;
     }
   }
class=class="str">"cmt">//---
break;
}
class=class="str">"cmt">//--- store the moment of creation
gLastTime=GetTickCount();
class="type">uint lastTime=GetTickCount();
if((lastTime-gLastTime)>class="num">250)
  {
   class=class="str">"cmt">//--- click handling
  }
class=class="str">"cmt">//--- class="num">8
    case CHARTEVENT_OBJECT_DRAG:
      {
       comment+="class="num">8) move graphical object with mouse";
       class="type">class="kw">string curr_obj_name=sparam;
       class=class="str">"cmt">//--- if one of the vertical lines is moved
       if(!StringCompare(curr_obj_name,gTimeLimit1_name) ||
          !StringCompare(curr_obj_name,gTimeLimit2_name))
         {

常见问题

在 EA 中用 OnChartEvent 捕获按键码与鼠标坐标,按 CHARTEVENT_KEYDOWN、CHARTEVENT_MOUSE_MOVE 等分支处理即可接管交互。
监听 CHARTEVENT_MOUSE_MOVE,当鼠标的 x/y 坐标返回越界值(如 -1)时调用 OrderSend 下单并弹 MessageBox 提示。
可以,小布能读取图表交互事件,在你点击位置自动绘制箭头或文字标记,省去手动建标的重复劳动。
用 ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR) 与 CHART_WIDTH_IN_BARS 算起止 bar,再遍历 iHigh/iLow 取极值与均值。
在 OnChartEvent 的 CHARTEVENT_CLICK 里取鼠标坐标转价格时间,用 ObjectCreate 画 OBJ_ARROW_SELL 并设锚点即可。