MQL5 Cookbook: 处理自定义图表事件·综合运用
「平仓分支里的事件复位与重建」
EA 在收到 CHARTEVENT_CUSTOM+4 自定义事件后进入平仓分支,先尝试用 PositionClose(_Symbol) 平掉当前品种持仓。若返回失败且输入参数 InpIsLogging 为真,会向终端打印失败日志,方便在 MT5 Experts 标签里直接抓异常。 平仓成功后并不立即结束流程,而是先 Sleep(InpTradePause) 挂起一段毫秒级停顿,避免同根 K 线上连续发单被经纪商拒绝。随后调用 ResetEvent() 释放旧的事件对象指针,再 new 一个 COrderEvent 实例承接下一步发单事件。 重建事件对象后,代码用 CheckPointer 确认是 POINTER_DYNAMIC 动态分配,然后把魔数塞进 lparam、把 ResultDeal 成交单号转 double 塞进 dparam,向 Generate(6,data) 推送 CHARTEVENT_CUSTOM+6。若生成失败会 PrintFormat 报出具体事件 ID,这种写法让你在回测或实盘里能精确追踪每次平仓后的链路断点。 外汇与贵金属杠杆高,这类自定义事件链任一步返回假值都可能让仓位悬空,建议把 InpTradePause 设为 100–500 毫秒并在策略测试器跑至少 3 个月 tick 数据验证。
class=class="str">"cmt">//--- class="num">4) check close signal case CHARTEVENT_CUSTOM+class="num">4: { if(!this.m_trade.PositionClose(_Symbol)) { class=class="str">"cmt">//--- to log? if(InpIsLogging) Print("Failed to close the position: "+_Symbol); } else { class=class="str">"cmt">//--- pause Sleep(InpTradePause); class=class="str">"cmt">//--- reset the event object this.ResetEvent(); class=class="str">"cmt">//--- create an order event object this.m_ptr_event=new COrderEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(class="type">long)this.m_magic; data.dparam=(class="type">class="kw">double)this.m_trade.ResultDeal(); class=class="str">"cmt">//--- generate CHARTEVENT_CUSTOM+class="num">6 event class="type">class="kw">ushort curr_id=class="num">6; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } class=class="str">"cmt">//--- class="kw">break; }
图表事件里抓开仓与平仓信号
EA 通过 CHARTEVENT_CUSTOM+5 与 +6 两个自定义图表事件,把『开仓』和『平仓』动作从主逻辑解耦到事件回调里。这样你在 MT5 图表上点一下按钮,或者外部脚本发一个自定义事件,EA 就能立刻响应,不用每帧去轮询持仓。 下面这段只截了开仓分支(case CHARTEVENT_CUSTOM+5)的核心处理:先从事件参数里取出 deal 票据号,再调 HistorySelect 把最近 1 小时(PeriodSeconds(PERIOD_H1))的历史拉进缓存,然后用 HistoryDealSelect 定位这笔成交。 [CODE] //--- 5) position opening case CHARTEVENT_CUSTOM+5: { ulong ticket=(ulong)_data.dparam; ulong deal=(ulong)_data.dparam; //--- datetime now=TimeCurrent(); //--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); //--- if(deal_entry==DEAL_ENTRY_IN) { //--- to log? if(InpIsLogging) { Print("\nNew position for: "+_Symbol); PrintFormat("Volume: %0.2f",deal_vol); } } } //--- break; } [/CODE] 逐行看:ticket 和 deal 都从 _data.dparam 强转成 ulong,实际项目里建议区分开(这里原文复用同一参数);now 用 TimeCurrent 取服务器时间。HistorySelect 的窗口是 now-3600 秒到 now,只查近 1 小时成交,能压低历史遍历开销。 HistoryDealGetDouble(deal,DEAL_VOLUME) 拿到的成交量是 double,DEAL_ENTRY 枚举判断是不是 DEAL_ENTRY_IN(入场成交)。只有入场且 InpIsLogging 开关打开,才向日志打 Symbol 和两位小数音量。外汇和贵金属杠杆高,这种事件驱动开仓若误触发可能瞬间建重仓,建议在 HistorySelect 窗口和 InpIsLogging 之外再加一层手数上限校验。 把 +6 的平仓分支照这个骨架补上,用 DEAL_ENTRY_OUT 判断出场,就能在 MT5 里跑一套完整的自定义事件下单流程。
class=class="str">"cmt">//--- class="num">5) position opening case CHARTEVENT_CUSTOM+class="num">5: { class="type">ulong ticket=(class="type">ulong)_data.dparam; class="type">ulong deal=(class="type">ulong)_data.dparam; class=class="str">"cmt">//--- class="type">class="kw">datetime now=TimeCurrent(); class=class="str">"cmt">//--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { class="type">class="kw">double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); class=class="str">"cmt">//--- if(deal_entry==DEAL_ENTRY_IN) { class=class="str">"cmt">//--- to log? if(InpIsLogging) { Print("\nNew position for: "+_Symbol); PrintFormat("Volume: %class="num">0.2f",deal_vol); } } } class=class="str">"cmt">//--- class="kw">break; }
◍ 平仓事件与停止交易的回调处理
EA 在图表事件中捕获成交回执时,先通过 _data.dparam 拿到 deal ticket,再用 HistorySelect 限定最近 1 小时(PeriodSeconds(PERIOD_H1))窗口拉取历史,避免全量扫描拖慢主线程。
若 HistoryDealSelect(deal) 命中且 DEAL_ENTRY 为 DEAL_ENTRY_OUT,说明这是平仓成交。此时读取 DEAL_VOLUME 可拿到平仓手数,结合 InpIsLogging 开关决定是否向专家日志打印品种与体积——实盘里建议只在对账阶段开这个开关,常态关掉能少写 30%~50% 的日志 IO。
另一种自定义事件 CHARTEVENT_CUSTOM+7 用于外部脚本下发“停止交易”指令:把传入的 dparam 当作 stop_time,并将成员 m_is_trade 置 false。这样主循环下一轮轮询读到该标志就会跳过下单逻辑,实现不带平仓动作的软熔断。外汇与贵金属波动剧烈,这种熔断只能阻断新单,已有持仓风险依旧在场内。
class="type">ulong deal=(class="type">ulong)_data.dparam; class=class="str">"cmt">//--- class="type">class="kw">datetime now=TimeCurrent(); class=class="str">"cmt">//--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { class="type">class="kw">double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); class=class="str">"cmt">//--- if(deal_entry==DEAL_ENTRY_OUT) { class=class="str">"cmt">//--- to log? if(InpIsLogging) { Print("\nClosed position for: "+_Symbol); PrintFormat("Volume: %class="num">0.2f",deal_vol); } } } class=class="str">"cmt">//--- class="kw">break; class=class="str">"cmt">//--- class="num">7) stop trading case CHARTEVENT_CUSTOM+class="num">7: { class="type">class="kw">datetime stop_time=(class="type">class="kw">datetime)_data.dparam; class=class="str">"cmt">//--- this.m_is_trade=class="kw">false; class=class="str">"cmt">//--- to log? if(InpIsLogging)
「用自定义事件掐断与恢复EA交易」
EA在图表事件里监听 CHARTEVENT_CUSTOM+7 与 +8,分别负责停止和恢复自动交易。这类设计适合人工在盘中对冲或规避重大数据行情时手动干预,外汇与贵金属杠杆高,突发波动可能瞬间放大亏损,手动暂停机制不是装饰。 当收到停止指令,代码把 _data.dparam 转型为停止时刻的 datetime,置 m_is_trade=false 阻断后续下单,并在开启日志时打印精确时间戳。恢复分支逻辑对称:把交易开关重新置 true,同样记录恢复时间点到日志。
| 下面这段是事件处理核心,注意 PrintFormat 里 TimeToString 用了 TIME_DATE | TIME_MINUTES | TIME_SECONDS 三标志位,日志会精确到秒,方便你事后对账哪一根 K 线期间 EA 是被冻住的。 |
|---|
PrintFormat("Expert trading is stopped at: %s", TimeToString(stop_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS)); class=class="str">"cmt">//--- class="kw">break; } class=class="str">"cmt">//--- class="num">8) resume trading case CHARTEVENT_CUSTOM+class="num">8: { class="type">class="kw">datetime resume_time=(class="type">class="kw">datetime)_data.dparam; this.m_is_trade=true; class=class="str">"cmt">//--- to log? if(InpIsLogging) PrintFormat("Expert trading is resumed at: %s", TimeToString(resume_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS)); class=class="str">"cmt">//--- class="kw">break; }
把自定义事件接进你的项目目录
MQL5 的自定义事件机制已经能跑通一套脱离定时器轮询的事件驱动模型,核心在于用 EventChartCustom 往图表抛 ID、在 OnChartEvent 里按 id 分流处理。想验证这套思路,直接把源码解压丢进 MQL5\Projects\ChartUserEvent 即可编译,原文附带的 ChartUserEvent_EN.zip 体积 85.61 KB,含完整可运行例程。 语言层面仍有缺口:眼下没有类模板,也没有指向函数的指针,所以严格意义上的委托还写不出来。若未来版本补上这两样,才可能用原生语法把事件绑到任意对象方法上,届时自定义事件的写法会瘦一大圈。 外汇与贵金属杠杆高、滑点跳空频繁,事件回调里的下单逻辑务必先在策略测试器跑历史,再上模拟盘。把例程跑通后,你可以试着把其中一个 case 改成自己的信号触发,看图表是不是按预期响应。