开发回放系统(第 34 部分):订单系统 (三)·进阶篇
📘

开发回放系统(第 34 部分):订单系统 (三)·进阶篇

第 2/2 篇

◍ 键盘与鼠标驱动的下单事件分发

在 MT5 图表事件回调里,用 CHARTEVENT_KEYDOWN 捕获 Ctrl+方向键能直接触发市价单:Ctrl+Up 走 ToMarket(ORDER_TYPE_BUY),Ctrl+Down 走 ToMarket(ORDER_TYPE_SELL),这套映射省去了点面板的时间。 鼠标逻辑更细。CHARTEVENT_MOUSE_MOVE 中,Shift 按下标记 bBuy、Ctrl 按下标记 bSell;两者互斥时才画三条水平线(价格线、止盈线、止损线),并用 EventChartCustom 隐藏系统光标。 止盈止损的偏移由 FinanceToPoints 按杠杆换算点数:买方向加正值,卖方向乘 -1。左键单击且 price 为 0 时,以鼠标价 CreateOrder,随后若 bBuy==bSell 且已创建对象,则删线、复位 price、恢复光标。外汇与贵金属杠杆波动大,这类一键下单在高波动时段可能滑点放大,实盘前务必在策略测试器用历史 tick 验证。

MQL5 / C++
def_AcessMouse.DispatchMessage(id, lparam, dparam, sparam);
case CHARTEVENT_KEYDOWN:
    if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
    {
        if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  ToMarket(ORDER_TYPE_BUY);
        if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))ToMarket(ORDER_TYPE_SELL);
    }
    break;
case CHARTEVENT_MOUSE_MOVE:
    bBuy = def_AcessMouse.CheckClick(C_Mouse::eSHIFT_Press);
    bSell = def_AcessMouse.CheckClick(C_Mouse::eCTRL_Press);
    if (bBuy != bSell)
    {
        if (!m_Objects.bCreate)
        {
            def_AcessTerminal.CreateObjectGraphics(def_LINE_PRICE, OBJ_HLINE, m_Objects.corPrice, class="num">0);
            def_AcessTerminal.CreateObjectGraphics(def_LINE_STOP, OBJ_HLINE, m_Objects.corStop, class="num">0);
            def_AcessTerminal.CreateObjectGraphics(def_LINE_TAKE, OBJ_HLINE, m_Objects.corTake, class="num">0);
            EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_HideMouse, class="num">0, class="num">0, "");
            m_Objects.bCreate = true;
        }
        ObjectMove(def_InfoTerminal.ID, def_LINE_PRICE, class="num">0, class="num">0, def_InfoMouse.Position.Price);
        ObjectMove(def_InfoTerminal.ID, def_LINE_TAKE, class="num">0, class="num">0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceTake, m_Infos.Leverage) * (bBuy ? class="num">1 : -class="num">1)));
        ObjectMove(def_InfoTerminal.ID, def_LINE_STOP, class="num">0, class="num">0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceStop, m_Infos.Leverage) * (bSell ? class="num">1 : -class="num">1)));
        if ((def_AcessMouse.CheckClick(C_Mouse::eClickLeft)) && (price == class="num">0)) CreateOrder((bBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), price = def_InfoMouse.Position.Price);
    }else if (m_Objects.bCreate)
    {
        EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_ShowMouse, class="num">0, class="num">0, "");
        ObjectsDeleteAll(def_InfoTerminal.ID, def_Prefix);
        m_Objects.bCreate = class="kw">false;
        price = class="num">0;
    }

鼠标移动事件里的显隐切换逻辑

在 MT5 自定义指标或 EA 里接管图表鼠标消息,核心入口是 DispatchMessage 回调。上面这段把外部鼠标管理对象 def_AcessMouse 先转发一遍消息,再用 switch 按事件 id 分流,CHARTEVENT_MOUSE_MOVE 就是鼠标移动分支。 当 bBuy 与 bSell 状态不一致时,若对象尚未创建(m_Objects.bCreate 为 false),代码通过 EventChartCustom 向终端 ID 发 ev_HideMouse 事件,两个参数都是 0、字符串为空。这个调用不画东西,只通知图表层把鼠标隐藏。 反之若 m_Objects.bCreate 已为 true,则发 ev_ShowMouse 事件把鼠标恢复。外汇与贵金属杠杆高,这类 GUI 交互改动只影响显示,不改变任何订单逻辑,实盘前请在策略测试器里跑一遍确认无报错。 直接把这段结构拷进你的鼠标类,改 def_InfoTerminal.ID 为本终端 ID,就能在 MT5 里验证显隐触发时机。

MQL5 / C++
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=class="str">"cmt">// ...
    def_AcessMouse.DispatchMessage(id, lparam, dparam, sparam);
    class="kw">switch (id)
    {
class=class="str">"cmt">// ...
      case CHARTEVENT_MOUSE_MOVE:
class=class="str">"cmt">// ...
        if (bBuy != bSell)
        {
          if (!m_Objects.bCreate)
          {
class=class="str">"cmt">// ...
            EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_HideMouse, class="num">0, class="num">0, "");
class=class="str">"cmt">// ...
          }
class=class="str">"cmt">// ...
        }else if (m_Objects.bCreate)
        {
          EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_ShowMouse, class="num">0, class="num">0, "");
class=class="str">"cmt">// ...
        }
        break;
      }
    }
  }

「EA 用户参数与周级别交易窗口的接法」

这组 EA 的交互配置分两块。第一块决定下单方式:user10 是杠杆倍数,外汇里常填 100,代表用最小手数乘该值去算保证金占比;若填错,限价线会偏到意料之外的位置。user11、user12 不是点数而是金融价格绝对值,代码内部再做换算,user13 只是多空开关(true 偏多)。 第二块是时间控制,按周日到周六设字符串窗口,例如周一 '09:05 - 17:35'、周二 '10:05 - 16:50'。订单的修改、终止交给 MT5 平台,EA 只管在窗口内发单或挂单。 OnInit 里用 SUNDAY 到 SATURDAY 的循环把整周时间表读进管理器,而非按单日硬编。外汇市场近乎 24x5 连续运行,若按日历日切 23:59:59 会遇到跨天断点;周视角下 EA 可连跑 52 周不掉向。 经纪商交易规则差异大,user10~user13 的机制请在策略测试器先用模拟盘验一遍,再上真实账户。外汇与贵金属杠杆交易风险高,参数错了可能让止损位完全失效。

MQL5 / C++
class="kw">input group "Mouse";
class="kw">input class="type">class="kw">color    user00 = clrBlack;        class=class="str">"cmt">//Price Line
class="kw">input class="type">class="kw">color    user01 = clrPaleGreen;   class=class="str">"cmt">//Positive Study
class="kw">input class="type">class="kw">color    user02 = clrLightCoral;  class=class="str">"cmt">//Negative Study
class="kw">input group "Trade";
class="kw">input class="type">uint      user10 = class="num">1;              class=class="str">"cmt">//Leverage
class="kw">input class="type">class="kw">double    user11 = class="num">100;           class=class="str">"cmt">//Take Profit( Finance )
class="kw">input class="type">class="kw">double    user12 = class="num">75;            class=class="str">"cmt">//Stop Loss( Finance )
class="kw">input class="type">bool      user13 = true;          class=class="str">"cmt">//Is Day Trade
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input group "Control of Time"
class="kw">input class="type">class="kw">string    user20  = "class="num">00:class="num">00 - class="num">00:class="num">00";      class=class="str">"cmt">//Sunday
class="kw">input class="type">class="kw">string    user21  = "class="num">09:class="num">05 - class="num">17:class="num">35";      class=class="str">"cmt">//Monday
class="kw">input class="type">class="kw">string    user22  = "class="num">10:class="num">05 - class="num">16:class="num">50";      class=class="str">"cmt">//Tuesday
class="kw">input class="type">class="kw">string    user23  = "class="num">09:class="num">45 - class="num">13:class="num">38";      class=class="str">"cmt">//Wednesday
class="kw">input class="type">class="kw">string    user24  = "class="num">11:class="num">07 - class="num">15:class="num">00";      class=class="str">"cmt">//Thursday
class="kw">input class="type">class="kw">string    user25  = "class="num">12:class="num">55 - class="num">18:class="num">25";      class=class="str">"cmt">//Friday
class="kw">input class="type">class="kw">string    user26  = "class="num">00:class="num">00 - class="num">00:class="num">00";      class=class="str">"cmt">//Saturday
class="type">int OnInit()
{
        class="type">class="kw">string szInfo;
        
        terminal = new C_Terminal();
        study    = new C_Study(terminal, user00, user01, user02);
        manager  = new C_Manager(terminal, study, user00, user02, user01, def_MagicNumber, user12, user11, user10, user13);
        
        if (_LastError != ERR_SUCCESS) class="kw">return INIT_FAILED;
        
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)
        {

◍ 按交易日切换面板信息的分支写法

这段 switch 逻辑把当前星期几(c0)映射到你预先定义的 user20~user26 字符串,再交给 SetInfoCtrl 刷新终端面板。周日到周六一一对应,缺一天都会掉进默认分支导致显示空白,所以七个 case 必须齐。 MarketBookAdd 在初始化末尾订阅了指定品种的深度数据,紧接着手动触发一次 OnBookEvent 让盘口立刻绘制,不必等下一笔报价。EventSetMillisecondTimer(500) 把定时器精度压到 500 毫秒,意味着面板刷新频率上限是每秒 2 次,想更跟手可以把参数改小,但 CPU 占用会线性上升。 直接把上面代码贴进 EA 的 OnInit 尾部就能跑,先确认 user20~user26 已在别处赋值,否则编译通过也只显示空串。外汇与贵金属杠杆高,面板只是辅助观测,信号本身不承诺方向。

MQL5 / C++
class="kw">switch (c0)
{
   case SUNDAY    : szInfo = user20; break;
   case MONDAY    : szInfo = user21; break;
   case TUESDAY   : szInfo = user22; break;
   case WEDNESDAY : szInfo = user23; break;
   case THURSDAY  : szInfo = user24; break;
   case FRIDAY    : szInfo = user25; break;
   case SATURDAY  : szInfo = user26; break;
}
(*manager).SetInfoCtrl(c0, szInfo);
}
   MarketBookAdd(def_InfoTerminal.szSymbol);
   OnBookEvent(def_InfoTerminal.szSymbol);
   EventSetMillisecondTimer(class="num">500);
   class="kw">return INIT_SUCCEEDED;
}

把回放系统跑起来再下判断

这一阶段的回放系统已经打包进四个压缩包,体积分别是 BOLSA 1358.24 KB、FOREX 3743.96 KB、FUTUROS 11397.51 KB 和 Market_Replay 130.63 KB,比前一篇多出可运行的完整代码。但有个怪现象:系统在某段没动过的逻辑里突然报错,原因暂时没摸透,下一篇再拆。 之前提过,对象选取改成了单击而非平台默认的双击。与其看文字描述,不如直接在自己 MT5 里加载跑一遍——外汇和贵金属波动剧烈、杠杆高风险大,实盘前先用回放验证交互是否符合预期。 代码先放着不贴,等定位到那处无端崩溃的调用链再一并放出。你现在能做的,就是下载附件、挂上图表,看它怎么转。

常见问题

在鼠标移动事件里判断光标坐标是否落在图标热区,命中就切面板显隐状态,移出则隐藏,避免遮挡盘口。
把星期选择做成输入参数数组,每根新 K 线读当前星期值,不在允许列表就跳过下单分支。
小布可读取你的回放配置,按交易日分支自动标注当前面板应显示的内容,省去手动核对。
常见错是跨零点时区没校准,导致分支偏移一天;用经纪商时间而非本地时间做判断即可规避。
先用历史数据离线回放走完至少一个月交易日,确认事件分发与显隐无卡顿,再上模拟盘观察。