开发回放系统(第 34 部分):订单系统 (三)·进阶篇
◍ 键盘与鼠标驱动的下单事件分发
在 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 验证。
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 里验证显隐触发时机。
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 的机制请在策略测试器先用模拟盘验一遍,再上真实账户。外汇与贵金属杠杆交易风险高,参数错了可能让止损位完全失效。
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 已在别处赋值,否则编译通过也只显示空串。外汇与贵金属杠杆高,面板只是辅助观测,信号本身不承诺方向。
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 里加载跑一遍——外汇和贵金属波动剧烈、杠杆高风险大,实盘前先用回放验证交互是否符合预期。 代码先放着不贴,等定位到那处无端崩溃的调用链再一并放出。你现在能做的,就是下载附件、挂上图表,看它怎么转。