MQL5图表事件处理实战全解
从按键到拖拽,手把手写交互式EA
什么是 ChartEvent 事件
在 MetaTrader 5 的 MQL5 编程模型中,事件驱动编程(EOP)是核心范式之一。ChartEvent 即图表事件,指当用户或算法在图表窗口中进行交互操作时,终端向运行中的 EA 或指标发出的通知。根据官方文档,图表事件在以下情况出现:在激活图表窗口按下键盘按键;创建图形对象;删除图形对象;在图形对象上点击;用鼠标拖拽图形对象;在 LabelEdit 等文本输入对象的编辑框中完成文本编辑;以及鼠标移动、图表属性修改等。这类事件让程序能够感知‘人’或‘其他程序’对图表的改动,是半自动交易系统(人工辅助下单、图形化参数调整)的基石。
MQL5 通过 ENUM_CHART_EVENT 枚举对事件分类,除标准事件外,还保留了一个用户自定义事件范围(65535 个标识符),由 EventChartCustom() 生成。本文聚焦 10 种典型标准事件,不展开自定义事件。理解事件分类有助于在 OnChartEvent 中用 switch 精准分流。
事件处理器与生成器函数
所有 ChartEvent 都由特殊函数 OnChartEvent() 统一处理,类似 OnInit 处理初始化、OnTrade 处理成交。其函数签名包含四个常量参数:id 为事件标识符(如 CHARTEVENT_KEYDOWN);lparam 为 long 类型附加信息(按键码、X坐标);dparam 为 double 类型(Y坐标);sparam 为 string 类型(对象名、编辑文本)。每次事件触发,终端自动填充这些参数并调用该函数。
void OnChartEvent(const int id, // event identifier
const long& lparam, // parameter of the event of type long
const double& dparam, // parameter of the event of type double
const string& sparam // parameter of the event of type string
)
与之相对,EventChartCustom() 是事件生成器,可向指定图表(含当前)发送自定义事件,参数包括 chart_id、custom_event_id 及 lparam/dparam/sparam。虽然本文未使用,但它是多图表通信的手段。OnChartEvent 与 EventChartCustom 共同体现了事件驱动的解耦优势:发送者不需知道接收者逻辑。
bool EventChartCustom(long chart_id, // receiving chart identifier
ushort custom_event_id, // event identifier
long lparam, // the long parameter
double dparam, // the double parameter
string sparam // the string parameter
)
标准事件处理模板与授权机制
MQL5 定义了 10 种典型图表事件。其中鼠标移动、对象创建、对象删除三类,需预先通过 ChartSetInteger() 开启图表对应属性(CHART_EVENT_MOUSE_MOVE 等),否则不会触发。其余事件默认开启。一个基础模板是在 OnChartEvent 中用 switch(id) 分支,将最近事件写入 Comment,便于调试。
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
string comment="Last event: ";
switch(id)
{
case CHARTEVENT_KEYDOWN: comment+="1) keystroke"; break;
case CHARTEVENT_MOUSE_MOVE: comment+="2) mouse"; break;
case CHARTEVENT_OBJECT_CREATE: comment+="3) create graphical object"; break;
case CHARTEVENT_OBJECT_CHANGE: comment+="4) change object properties"; break;
case CHARTEVENT_OBJECT_DELETE: comment+="5) delete graphical object"; break;
case CHARTEVENT_CLICK: comment+="6) mouse click on chart"; break;
case CHARTEVENT_OBJECT_CLICK: comment+="7) mouse click on graphical object"; break;
case CHARTEVENT_OBJECT_DRAG: comment+="8) move graphical object"; break;
case CHARTEVENT_OBJECT_ENDEDIT: comment+="9) finish editing text"; break;
case CHARTEVENT_CHART_CHANGE: comment+="10) modify chart"; break;
}
Comment(comment);
}
按键与鼠标事件实战
按键事件(CHARTEVENT_KEYDOWN)中,lparam 捕获虚拟键码。例如上箭头 38、下箭头 40,可分别触发 TryToBuy()/TryToSell()。这种方式让交易者用键盘快捷下单,适合 scalper。
case CHARTEVENT_KEYDOWN: if(lparam==38) TryToBuy(); else if(lparam==40) TryToSell(); comment+="1) keystroke"; break;
鼠标移动事件需授权。可设输入参数 InpIsEventMouseMove 动态开关。例中计算图表右移偏移边界,当光标自左越界时弹 MessageBox 询问买卖。lparam/dparam 为 X/Y 像素坐标,需用 ChartWidthInPixels() 等转换。
bool is_mouse=false; if(InpIsEventMouseMove) is_mouse=true; ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,is_mouse);
case CHARTEVENT_MOUSE_MOVE:
if(InpIsEventMouseMove)
{
long static last_mouse_x;
if(ChartShiftSet(true))
if(ChartShiftSizeSet(InpChartShiftSize))
{
int chart_width=ChartWidthInPixels();
int chart_shift_x=(int)(chart_width-chart_width*InpChartShiftSize/100.);
if(lparam>chart_shift_x && last_mouse_x<chart_shift_x)
{
int res=MessageBox("Yes: buy / No: sell","Trade operation",MB_YESNO);
if(res==IDYES) TryToBuy();
else if(res==IDNO) TryToSell();
}
last_mouse_x=lparam;
}
}
break;
图形对象创建、修改与删除
对象创建(CHARTEVENT_OBJECT_CREATE)事件在图表出现新对象时触发,sparam 为对象名。例程中画一条水平线,EA 自动将其移到最高价,并生成最低价线和中线。注意需授权 CHART_EVENT_OBJECT_CREATE。
bool is_obj_create=false; if(InpIsEventObjectCreate) is_obj_create=true; ChartSetInteger(0,CHART_EVENT_OBJECT_CREATE,is_obj_create);
case CHARTEVENT_OBJECT_CREATE:
if(InpIsEventObjectCreate)
{
int all_hor_lines=ObjectsTotal(0,0,OBJ_HLINE);
if(all_hor_lines==1)
{
string hor_line_name1=sparam;
int visible_bars_num=ChartVisibleBars();
double highs[],lows[];
CopyHigh(_Symbol,_Period,0,visible_bars_num-1,highs);
CopyLow(_Symbol,_Period,0,visible_bars_num-1,lows);
double ch_high_pr=NormalizeDouble(highs[ArrayMaximum(highs)],_Digits);
double ch_low_pr=NormalizeDouble(lows[ArrayMinimum(lows)],_Digits);
double ch_mid_pr=NormalizeDouble((ch_high_pr+ch_low_pr)/2.,_Digits);
if(ObjectFind(0,hor_line_name1)>-1)
ObjectMove(0,hor_line_name1,0,0,ch_high_pr);
string hor_line_name2="Hor_line_min";
ObjectCreate(0,hor_line_name2,OBJ_HLINE,0,0,ch_low_pr);
}
}
break;
对象属性修改(CHARTEVENT_OBJECT_CHANGE)可用于同步多条线颜色:改一条,全部跟随。对象删除(CHARTEVENT_OBJECT_DELETE)中,对象被删后属性不可访问,若要记类型必须提前缓存。例程演示删一条垂直线即清掉所有垂线。
case CHARTEVENT_OBJECT_CHANGE:
string curr_obj_name=sparam;
if(ObjectFind(0,curr_obj_name)>-1)
{
color curr_obj_color=(color)ObjectGetInteger(0,curr_obj_name,OBJPROP_COLOR);
int all_other_objects=ObjectsTotal(0);
for(int obj_idx=0;obj_idx<all_other_objects;obj_idx++)
{
string other_obj_name=ObjectName(0,obj_idx);
if(StringCompare(curr_obj_name,other_obj_name)!=0)
ObjectSetInteger(0,other_obj_name,OBJPROP_COLOR,curr_obj_color);
}
ChartRedraw();
}
break;
点击、拖拽与文本编辑事件
鼠标点击图表(CHARTEVENT_CLICK)用 ChartXYToTimePrice() 将像素坐标转为时间/价格,从而放置箭头对象。点击对象(CHARTEVENT_OBJECT_CLICK)的 sparam 是对象名,可改写对象类型。但实测点击对象会连带触发图表点击,需用全局时间变量 gLastTime 与 GetTickCount() 忽略 250ms 内重复。
case CHARTEVENT_CLICK:
int mouse_x=(int)lparam; int mouse_y=(int)dparam;
datetime obj_time; double obj_price; int sub_window;
if(ChartXYToTimePrice(0,mouse_x,mouse_y,sub_window,obj_time,obj_price))
{
string buy_sign_name="buy_sign_"+IntegerToString(sign_obj_cnt+1);
ObjectCreate(0,buy_sign_name,OBJ_ARROW_BUY,0,obj_time,obj_price);
ChartRedraw(); sign_obj_cnt++;
}
break;
uint lastTime=GetTickCount();
if((lastTime-gLastTime)>250)
{
//--- click handling
}
拖拽事件(CHARTEVENT_OBJECT_DRAG)适合动态区间:移动垂线,矩形暗化区自动跟随。文本编辑结束(CHARTEVENT_OBJECT_ENDEDIT)可做成指令框,输入 Buy/Sell 不区分大小写下单,并变色反馈。
case CHARTEVENT_OBJECT_ENDEDIT:
string curr_obj_name=sparam;
if(!StringCompare(curr_obj_name,gEdit_name))
{
string obj_text=NULL;
if(ObjectGetString(0,curr_obj_name,OBJPROP_TEXT,0,obj_text))
{
if(!StringCompare(obj_text,"Buy",false)) { TryToBuy(); ObjectSetInteger(0,gEdit_name,OBJPROP_COLOR,clrBlue); }
else if(!StringCompare(obj_text,"Sell",false)) { TryToSell(); ObjectSetInteger(0,gEdit_name,OBJPROP_COLOR,clrRed); }
ChartRedraw();
}
}
break;
图表修改事件与防篡改
最后,CHARTEVENT_CHART_CHANGE 在图表尺寸或设置变化时触发。可用来锁定属性:若高度宽度未变,则强制恢复网格、显示模式、背景色等输入值,实现简易保护。注意尺寸变化时应跳过,避免死循环。
case CHARTEVENT_CHART_CHANGE:
int curr_ch_height=ChartHeightInPixelsGet();
int curr_ch_width=ChartWidthInPixels();
if(gChartHeight==curr_ch_height && gChartWidth==curr_ch_width)
{
ChartShowGridSet(InpToShowGrid);
ChartModeSet(InpMode);
ChartBackColorSet(InpBackColor);
}
else { gChartHeight=curr_ch_height; gChartWidth=curr_ch_width; }
break;
总结与多图表同步思路
本文覆盖了 MQL5 全部 10 种标准图表事件及处理模式。它们让 EA 从纯自动变为可人机协作。社区中有人问多图表同步:可用 EventChartCustom 向其他 chart_id 发事件,或全局变量+OnTimer 轮询(有延迟)。更优是用自定义图表事件解耦。学习时建议从 EventProcessor1~10 示例逐步修改,理解参数映射与授权开关。