开发回放系统(第29部分):EA 交易项目C_Mouse类(三)·综合运用
📘

开发回放系统(第29部分):EA 交易项目C_Mouse类(三)·综合运用

第 3/3 篇

「用鼠标事件驱动重绘订单簿面板」

下面这段 EA 骨架把深度图渲染完全挂到图表鼠标消息和 500 毫秒定时器上,而不是每笔 tick 去刷屏。OnChartEvent 里收到 CHARTEVENT_MOUSE_MOVE 就调 Draw() 并 ChartRedraw(),意味着你移动鼠标才会重画价格线,CPU 占用倾向更低。 OnInit 中 EventSetMillisecondTimer(500) 设定了半秒级轮询,OnTimer 里只做 (*extra).Update(),即每 500ms 刷新一次附属 study 图层。对比 OnTick() 留空,说明设计者有意避开 tick 高频回调,外汇与贵金属品种在剧烈波动时这种写法可能减少卡顿。 OnBookEvent 根据当前品种是否等于 def_SymbolReplay 决定 book 数组长度:回放品种只开 1 个元素,实盘则走 MarketBookGet 取真实深度。注意 mouse 与 extra 两个指针在 OnDeinit 中必须 delete,否则 MT5 终端退出时倾向报内存泄漏。 代码里 input group "Mouse" 暴露了三根颜色线:user00 黑线做价格基准、user01 浅绿表正向 study、user02 珊瑚红表负向 study,复制时若改了头文件路径 <Market Replay\System EA\Auxiliar\...> 需同步调整 include 才能编译过。

MQL5 / C++
class="kw">switch (id)
  {
    case CHARTEVENT_MOUSE_MOVE:
      Draw();
      class="kw">break;
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Market Replay\System EA\Auxiliar\C_Mouse.mqh>
class="macro">#include <Market Replay\System EA\Auxiliar\Study\C_Studys.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input group "Mouse";
class="kw">input class="type">color   user00 = clrBlack;      class=class="str">"cmt">//Price Line
class="kw">input class="type">color   user01 = clrPaleGreen;  class=class="str">"cmt">//Positive Study
class="kw">input class="type">color   user02 = clrLightCoral; class=class="str">"cmt">//Negative Study
class=class="str">"cmt">//+------------------------------------------------------------------+
C_Mouse *mouse = NULL;
C_Studys *extra = NULL;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
  mouse = new C_Mouse(user00, user01, user02);
  extra = new C_Studys(mouse, user01, user02);

  OnBookEvent(_Symbol);
  EventSetMillisecondTimer(class="num">500);
  class="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
{
  MarketBookRelease(_Symbol);
  EventKillTimer();

  class="kw">delete extra;
  class="kw">delete mouse;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick() {}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
{
  (*extra).Update();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnBookEvent(class="kw">const class="type">class="kw">string &symbol)
{
MqlBookInfo book[];

if (mouse.GetInfoTerminal().szSymbol == def_SymbolReplay) ArrayResize(book, class="num">1, class="num">0); else
{
    if (symbol != (*mouse).GetInfoTerminal().szSymbol) class="kw">return;
    MarketBookGet((*mouse).GetInfoTerminal().szSymbol, book);
  }
  (*extra).Update(book);
}
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)
{
  (*mouse).DispatchMessage(id, lparam, dparam, sparam);
  (*extra).DispatchMessage(id, lparam, dparam, sparam);

  ChartRedraw();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
C_Studys(C_Mouse *arg, class="type">color corP, class="type">color corN)
  {
class="macro">#define def_FontName "Lucida Console"
class="macro">#define def_FontSize class="num">10
    class="type">int x;
    mouse = arg;
    ZeroMemory(m_Info);

收盘态下的状态赋值与按钮绘制

这段逻辑跑在日线收盘判定之后,先把面板状态切到 eCloseMarket,再取 D1 收盘价填进结构体。取价时用了一个三元判断:当品种是回放符号,或当前日期与 D1 最新 K 线日期不一致时取 0 号柱,否则取 1 号柱——这能避免在非换日时刻误读昨收。 corP 与 corN 直接透传进 m_Info,随后用 TextSetFont 设字体,高度为 -10 * def_FontSize、字重 FW_NORMAL。接着连续建三个按钮:Closed Market 用淡蓝底色,后两个 99.99% 用透明底,位置靠 x+5 错开。 最后 Draw() 一次性刷新,并用 #undef 撤掉 def_FontSize 与 def_FontName 宏,防止后续编译命名污染。开 MT5 把这段塞进你的 CPanel::Update 尾段,改 def_ExpansionBtn2/3 的文本就能快速验证面板布局。

MQL5 / C++
   m_Info.Status = eCloseMarket;
   m_Info.Rate.close = iClose(def_InfoTerminal.szSymbol, PERIOD_D1, ((def_InfoTerminal.szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(def_InfoTerminal.szSymbol, PERIOD_D1, class="num">0))) ? class="num">0 : class="num">1));
   m_Info.corP = corP;
   m_Info.corN = corN;
   TextSetFont(def_FontName, -class="num">10 * def_FontSize, FW_NORMAL);
   CreateBTNInfo("Closed Market", class="num">2, def_ExpansionBtn1, clrPaleTurquoise, def_FontName, def_FontSize);
   x = CreateBTNInfo("class="num">99.99%", class="num">2, def_ExpansionBtn2, clrNONE, def_FontName, def_FontSize);
   CreateBTNInfo("class="num">99.99%", x + class="num">5, def_ExpansionBtn3, clrNONE, def_FontName, def_FontSize);
   Draw();
class="macro">#undef def_FontSize
class="macro">#undef def_FontName
   }

◍ 收束

把附加的 Market_Replay 压缩包(56.76 KB)解进 MT5 的 MQL5/Files 目录,先在重放模式跑通,再切到 DEMO 账户做实盘环境验证,比只看代码更能摸清撮合与滑点的边界。 主类逻辑作者明确未改一行,意味着你拿到的回放框架是可溯源的;外汇与贵金属品种在高杠杆下波动剧烈,重放结论只代表历史序列概率,不预示未来。 ZIP 里 FUTUROS(11.4 MB)、FOREX(3.7 MB)、BOLSA(1.3 MB)三套样本覆盖不同市场节奏,建议按品种分开回测,避免混仓干扰信号。

常见问题

把鼠标按下与移动事件映射到独立的重绘标记位,仅在事件触发帧调用重绘,避免每帧全量刷新,可显著降低 CPU 占用。
需在收盘态的逻辑分支里显式把按钮的 enabled 标记赋为 false,并调用一次状态重绘,否则旧状态会残留。
可以,小布能扫描你的面板逻辑并提示未处理的市场状态分支,帮你快速定位收盘态下按钮未置灰的问题。
记录鼠标按下时的锚点坐标,移动时以差值更新面板左上角,别直接用绝对光标位置覆盖原坐标。
把事件分发、状态赋值、绘制拆成三个内部方法,主循环只调收束后的接口,结构会清晰很多。