开发回放系统 市场模拟(第 28 部分):智能交易系统项目 C_Mouse 类 (II)·进阶篇
鼠标事件里把水平线钉在报价上
这段逻辑挂在图表的 CHARTEVENT_MOUSE_MOVE 分支里,核心是把一条名为 def_NameObjectLineH 的水平线实时跟住光标对应的报价。ChartXYToTimePrice 先把鼠标像素坐标 (lparam,dparam) 换算成时间 m_Info.Data.Position.dt 和价格 m_Info.Data.Position.Price,随后 ObjectMove 把水平线挪到 AdjustPrice 处理后的价位——AdjustPrice 通常做点差对齐,避免画线落在不可成交的价位。 如果当前处于非 eStudyNull 的研究状态,垂直参考线 def_NameObjectLineV 也会被同步移到光标的 dt 处,方便 later 做时空定位。ButtonStatus 直接记下 sparam(鼠标键码),中键点击且水平线颜色不是 clrNONE 时触发 CreateStudy(),左键在 eStudyCreate 状态下则关掉 CHART_MOUSE_SCROLL 并落下定线,把状态切到 eStudyExecute。 另一个容易踩的点是 CHARTEVENT_OBJECT_DELETE:当被删的对象名正好是 def_NameObjectLineH,代码会调 CreateLineH() 重建,等于禁止用户手删这条基准线。自定义事件 ev_HideMouse / ev_ShowMouse 则只改 OBJPROP_COLOR 为 clrNONE 或恢复原色,用来在不需要时隐藏水平线而不销毁对象。 开 MT5 把这段塞进你的 EA 的 OnChartEvent,把 def_NameObjectLineH 换成自己面板里的线名,移动鼠标就能看到线贴着报价跑;若发现线被手删后自动复活,就是这个 DELETE 分支在起作用。外汇与贵金属波动大、滑点频繁,这类贴价线只作辅助定位,实际下单仍需人工确认。
ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, class="num">0, class="num">0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price)); if (m_Info.Study != eStudyNull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, class="num">0, m_Info.Data.Position.dt, class="num">0); m_Info.Data.ButtonStatus = (class="type">uint) sparam; if (CheckClick(eClickMiddle) && ((class="type">class="kw">color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) { ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, class="num">0, m_Info.Data.Position.dt, memPrice = m_Info.Data.Position.Price); m_Info.Study = eStudyExecute; } if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); class="kw">break; case CHARTEVENT_OBJECT_DELETE: if (sparam == def_NameObjectLineH) CreateLineH(); class="kw">break; case (CHARTEVENT_CUSTOM + ev_HideMouse): ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE); class="kw">break; case (CHARTEVENT_CUSTOM + ev_ShowMouse): ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH); class="kw">break; case CHARTEVENT_MOUSE_MOVE: ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X = (class="type">int)lparam, m_Info.Data.Position.Y = (class="type">int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, class="num">0, class="num">0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price)); if (m_Info.Study != eStudyNull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, class="num">0, m_Info.Data.Position.dt, class="num">0); m_Info.Data.ButtonStatus = (class="type">uint) sparam; if (CheckClick(eClickMiddle) && ((class="type">class="kw">color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) { ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
「鼠标驱动的下单与画线逻辑」
这段 EA 骨架把图表鼠标事件直接映射成交易辅助动作:中键点击且水平线未隐藏时触发 CreateStudy(),左键在创建态下锁定价格并切到执行态,鼠标移动时通过 ChartXYToTimePrice 把像素坐标换算成时间价格并实时移动水平线。 核心交互落在 CHARTEVENT_MOUSE_MOVE 分支:ObjectMove 对水平线用 AdjustPrice 对齐报价点差,垂直线仅在已有研究态时才跟随光标时间轴移动,避免无谓重绘。 输入参数用三组颜色区分价格线(黑)、正向研究(深绿)、负向研究(栗色),版本标记 1.28,说明这是一套可跑在模拟回放与真实账户上的通用鼠标下单框架,外汇与贵金属品种下使用仍属高风险,滑点与重报价可能让实际成交偏离光标价位。 想验证就新建 EA,把 C_Mouse 类挂上,中键画水平线、左键定入场价,MT5 里拖一下就能看到线随鼠标走。
ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, class="num">0, m_Info.Data.Position.dt, memPrice = m_Info.Data.Position.Price); m_Info.Study = eStudyExecute; } if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); class="kw">break; case CHARTEVENT_MOUSE_MOVE: ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X = (class="type">int)lparam, m_Info.Data.Position.Y = (class="type">int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, class="num">0, class="num">0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price)); if (m_Info.Study != eStudyNull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, class="num">0, m_Info.Data.Position.dt, class="num">0); m_Info.Data.ButtonStatus = (class="type">uint) sparam; if (CheckClick(eClickMiddle) && (((class="type">class="kw">color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR)) != clrNONE)) CreateStudy(); if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) { ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, class="num">0, m_Info.Data.Position.dt, memPrice = m_Info.Data.Position.Price); m_Info.Study = eStudyExecute; } if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); class="kw">break; class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property description "Generic EA for use on Demo account, replay system/simulator and Real account." class="macro">#class="kw">property description "This system has means of sending orders using the mouse and keyboard combination." class="macro">#class="kw">property description "For more information see the article about the system." class="macro">#class="kw">property version "class="num">1.28" class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Market Replay\System EA\Auxiliar\C_Mouse.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ input group "Mouse"; input class="type">class="kw">color user00 = clrBlack; class=class="str">"cmt">//Price Line input class="type">class="kw">color user01 = clrDarkGreen; class=class="str">"cmt">//Positive Study input class="type">class="kw">color user02 = clrMaroon; class=class="str">"cmt">//Negative Study class=class="str">"cmt">//+------------------------------------------------------------------+ C_Mouse *mouse; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { mouse = new C_Mouse(user00, user01, user02); class="kw">return INIT_SUCCEEDED; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class="kw">delete mouse; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() {}
◍ 把鼠标事件转交给对象后立刻重绘
在 MT5 的图表脚本里,OnChartEvent 是接收鼠标、键盘等交互事件的入口。上面这段代码把事件直接派发给我们自己封装的鼠标对象,由它内部决定怎么响应。 派发之后马上调用 ChartRedraw(),是因为鼠标对象可能改了图形对象坐标或状态,不重绘的话屏幕不会刷新,你看到的光标或辅助线会卡住。
class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { (*mouse).DispatchMessage(id, lparam, dparam, sparam); ChartRedraw(); } class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { (*mouse).DispatchMessage(id, lparam, dparam, sparam); ChartRedraw(); } class=class="str">"cmt">//+------------------------------------------------------------------+
别小看那些基础语法把式
随包附带的两个类 C_Terminal 和 C_Mouse 都依赖 Defines.mqh,把鼠标和终端交互从主逻辑里抽出来,代码可读性明显提升。这种写法跟开篇说的「全部统一格式」并不完全一致,但你要是愿意,完全可以在自己的 EA 里照抄这套。 我早年在 C/C++ 里也这么干过,靠把逻辑拆成小块来吃透语法。一开始看着绕,用顺了就知道,在满是布尔和位运算的大项目里,它能省掉大量回头查错的时间。 举个最容易被坑的例子:按位与(&)和布尔与(&&)长得像,作用却不同。前者逐位算,后者整体求值返回 true/false。赶工的时候,很多人会随手写错,导致条件判断悄悄失灵。 所以别觉得某些老派写法简单就懒得学。它们不显眼,却实实在在让程序更好读、开发更快,也少让你对着一段「明明该触发却没动」的代码发呆。外汇和贵金属杠杆高、滑点频,回测过的逻辑更得经得起细看。