构建自动运行的 EA(第 04 部分):手工触发器(I)·进阶篇
鼠标交互类的事件开关与析构清理
在 MT5 自定义指标或 EA 里接管鼠标行为,第一步是打开图表事件开关。构造函数中对当前图表 ID 调用 ChartSetInteger,把 CHART_EVENT_MOUSE_MOVE 与 CHART_EVENT_OBJECT_DELETE 均置为 true,这样鼠标移动和对象被删的事件才会推送到 DispatchMessage。 析构函数 ~C_Mouse 做反向操作:先把 CHART_EVENT_OBJECT_DELETE 设回 false,再用 ObjectDelete 按 def_MouseName 删掉那条水平线对象,避免残留图形卡在图表上。 GetStatus 用引用传出 m_Infos.Price 与 m_Infos.BtnStatus,外部只需声明 double 与 uint 变量接收,就能知道鼠标悬停价和按键状态,不用反复读全局。 DispatchMessage 里用 switch(id) 分流,当 id 等于 CHARTEVENT_OBJECT_DELETE 且 sparam 等于 def_MouseName 时,说明用户手删了那条线,立刻调 CreateLineH() 重建——这个判断少一个等号就会让线删了不再出现。
ChartSetInteger(m_Infos.Id, CHART_EVENT_MOUSE_MOVE, true); ChartSetInteger(m_Infos.Id, CHART_EVENT_OBJECT_DELETE, true); CreateLineH(); } ~C_Mouse() { ChartSetInteger(m_Infos.Id, CHART_EVENT_OBJECT_DELETE, false); ObjectDelete(m_Infos.Id, def_MouseName); } class="kw">const class="type">void GetStatus(class="type">class="kw">double &Price, class="type">uint &BtnStatus) class="kw">const { Price = m_Infos.Price; BtnStatus = m_Infos.BtnStatus; } class="type">void DispatchMessage(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="type">int w; class="type">class="kw">datetime dt; class="kw">switch (id) { case CHARTEVENT_OBJECT_DELETE: if (sparam == def_MouseName) CreateLineH();
◍ 鼠标跟价与手数参数的接驳写法
在 EA 的图表事件分支里,CHARTEVENT_MOUSE_MOVE 负责把光标位置转成可下单的价位。下面这段从事件回调中摘出,lparam/dparam 是鼠标像素坐标,经 ChartXYToTimePrice 换算出时间 w 与价格 dt、m_Infos.Price,再用 ObjectMove 把虚拟鼠标对象贴到 AdjustPrice 校准后的报价上。 case CHARTEVENT_MOUSE_MOVE: ChartXYToTimePrice(m_Infos.Id, (int)lparam, (int)dparam, w, dt, m_Infos.Price); ObjectMove(m_Infos.Id, def_MouseName, 0, 0, m_Infos.Price = AdjustPrice(m_Infos.Price)); m_Infos.BtnStatus = (uint)sparam; ChartRedraw(); break; 上面四行是逐行拆解:第一句把像素坐标转为时间价格结构;第二句移动名称为 def_MouseName 的图表对象到第 0 点、校准价;第三句记录鼠标按键状态到 BtnStatus;第四句强制重绘,避免报价线拖影。外汇与贵金属杠杆高,鼠标误触可能瞬间发单,实盘前务必在策略测试器用模拟盘验证。 外部参数部分直接决定仓位与风控:user01=1 为手数步进,user02=100 与 user03=75 是盈亏金额阈值(非点数),user04=true 切日内的日内模式,user05=clrBlack 控制鼠标对象颜色。Magic 号写死 987654321 便于订单归类。 #include <Generic Auto Trader\C_Orders.mqh> #include <Generic Auto Trader\C_Mouse.mqh> C_Orders *manager; C_Mouse *mouse; int OnInit() { manager = new C_Orders(def_MAGIC_NUMBER); mouse = new C_Mouse(user05); 这两段说明订单与鼠标两个类在 OnInit 里以 new 实例化,manager 接 Magic 号、mouse 接颜色输入。开 MT5 把这两头文件放进 Include\Generic Auto Trader\ 路径,编译后拖入图表即可看鼠标报线是否跟随。
case CHARTEVENT_MOUSE_MOVE: ChartXYToTimePrice(m_Infos.Id, (class="type">int)lparam, (class="type">int)dparam, w, dt, m_Infos.Price); ObjectMove(m_Infos.Id, def_MouseName, class="num">0, class="num">0, m_Infos.Price = AdjustPrice(m_Infos.Price)); m_Infos.BtnStatus = (class="type">uint)sparam; ChartRedraw(); class="kw">break; class="macro">#include <Generic Auto Trader\C_Orders.mqh> class="macro">#include <Generic Auto Trader\C_Mouse.mqh> C_Orders *manager; C_Mouse *mouse; class="kw">input class="type">int user01 = class="num">1; class=class="str">"cmt">//Lot increase class="kw">input class="type">class="kw">double user02 = class="num">100; class=class="str">"cmt">//Take Profit( FINANCEIRO ) class="kw">input class="type">class="kw">double user03 = class="num">75; class=class="str">"cmt">//Stop Loss( FINANCEIRO ) class="kw">input class="type">bool user04 = true; class=class="str">"cmt">//Day Trade ? class="kw">input class="type">color user05 = clrBlack;class=class="str">"cmt">//Color Mouse class="macro">#define def_MAGIC_NUMBER class="num">987654321 class="type">int OnInit() { manager = new C_Orders(def_MAGIC_NUMBER); mouse = new C_Mouse(user05);
「鼠标与快捷键双通道下单的事件路由」
这段逻辑把图表交互拆成两条触发路径:一条走鼠标对象的状态轮询,一条直接读终端键状态。两者都在 OnChartEvent 里完成,但前者依赖 mouse 指针封装,后者用 TerminalInfoInteger 硬查 CTRL/方向键,互不冲突。 先看鼠标分支。每次事件进来先 DispatchMessage 把系统消息喂给 mouse 对象,再 GetStatus 取回光标价和按键位。只有当 SHIFT 与 CTRL 的按下状态不相等(即只按了其中一个修饰键)时才进挂单判断;左键单击且静态变量 mem 为 0 时,以光标价为基准调 CreateOrder,方向由 SHIFT 决定买或卖,同时把 mem 锁成该价格防止同根 K 线重复触单。 键盘分支则更粗暴:只要 TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL) 为真,上方向键直接 ToMarket 市价买、下方向键市价卖,参数 user01~user04 分别是止损、止盈、手数、魔术码一类外部输入。注意原文第二版 OnChartEvent 里下行键那行少了右括号,编译会报错,需补成 (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN)) 才能过。 两个 OnChartEvent 重载在真实 EA 里不能同时存在,上面第一段是简化版、第二段是带键盘市价功能的扩展版。你复制时只留一个,否则 MT5 编译器会报函数重定义。外汇与贵金属杠杆高,这类一键下单逻辑回测时滑点可能吃掉 2~5 点,实盘前务必在策略测试器用 tick 级数据跑一遍。
class="kw">return INIT_SUCCEEDED; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { class="kw">delete mouse; class="kw">delete manager; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void 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="type">uint BtnStatus; class="type">class="kw">double Price; class="kw">static class="type">class="kw">double mem = class="num">0; (*mouse).DispatchMessage(id, lparam, dparam, sparam); (*mouse).GetStatus(Price, BtnStatus); if (def_SHIFT_Press(BtnStatus) != def_CTRL_Press(BtnStatus)) { if (def_BtnLeftClick(BtnStatus) && (mem == class="num">0)) (*manager).CreateOrder(def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, mem = Price, user03, user02, user01, user04); }else mem = class="num">0; } class="type">void 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="type">uint BtnStatus; class="type">class="kw">double Price; class="kw">static class="type">class="kw">double mem = class="num">0; (*mouse).DispatchMessage(id, lparam, dparam, sparam); (*mouse).GetStatus(Price, BtnStatus); if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL)) { if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP)) (*manager).ToMarket(ORDER_TYPE_BUY, user03, user02, user01, user04); if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN) (*manager).ToMarket(ORDER_TYPE_SELL, user03, user02, user01, user04); } if (def_SHIFT_Press(BtnStatus) != def_CTRL_Press(BtnStatus)) { if (def_BtnLeftClick(BtnStatus) && (mem == class="num">0)) (*manager).CreateOrder(def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, mem = Price, user03, user02, user01, user04); }else mem = class="num">0; }
把这条线请下神坛
前面拆的事件触发说到底还是半自动:挂单动作得交易者亲手在 MT5 里点下去,EA 本身不会自己往订单簿里塞单。一旦单子进了簿子,后续就脱离人手了——价格触到挂单位,持仓自动生成;再碰限价,持仓自动平掉。 但外汇和贵金属是高风险的,跳空常有。价格瞬间越过挂单位时,持仓可能卡在不利价位,一直扛到经纪商强平或日内休市才了结。所以手工触发不是免死金牌,仓位大小得自己压住。 当前附件 EA_Automatico_-_04.zip(4.83 KB)就是这套逻辑的原始代码,下一篇会改得更好上手。你可以先把它拖进 MT5 的 Experts 目录,开一个 XAUUSD 的 M1 图,手动挂一单看它怎么接力。