开发回放系统  市场模拟(第 28 部分):智能交易系统项目  C_Mouse 类 (II)·进阶篇
📘

开发回放系统 市场模拟(第 28 部分):智能交易系统项目 C_Mouse 类 (II)·进阶篇

第 2/2 篇

鼠标事件里把水平线钉在报价上

这段逻辑挂在图表的 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 分支在起作用。外汇与贵金属波动大、滑点频繁,这类贴价线只作辅助定位,实际下单仍需人工确认。

MQL5 / C++
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 里拖一下就能看到线随鼠标走。

MQL5 / C++
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(),是因为鼠标对象可能改了图形对象坐标或状态,不重绘的话屏幕不会刷新,你看到的光标或辅助线会卡住。

MQL5 / C++
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">//+------------------------------------------------------------------+
逐行看:第1行定义事件回调函数,四个参数分别是事件ID、长整型参数、双精度参数、字符串参数;第3行通过指针解引用调用鼠标对象的 DispatchMessage,把全部参数透传进去;第4行强制图表重绘;最后一行是 MQL5 常用的分隔注释,无实际逻辑。 开 MT5 新建 EA 或指标,粘入这段并定义一个 CMouse* mouse 指针,就能在图表上跑通最基础的交互响应链。外汇与贵金属杠杆高,实盘前务必在策略测试器用历史数据验证事件逻辑。

MQL5 / C++
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。赶工的时候,很多人会随手写错,导致条件判断悄悄失灵。 所以别觉得某些老派写法简单就懒得学。它们不显眼,却实实在在让程序更好读、开发更快,也少让你对着一段「明明该触发却没动」的代码发呆。外汇和贵金属杠杆高、滑点频,回测过的逻辑更得经得起细看。

常见问题

在鼠标移动事件里读取当前 Bid/Ask,用 OBJPROP_PRICE 重写水平线价格并立即 ChartRedraw,线就会钉在报价上。
在 C_Mouse 的 OnEvent 里判断按键与坐标命中区域,触发下单函数前先校验品种与手数,再调用交易接口发送市价单。
小布可以加载你的品种回放页,模拟鼠标事件流并标出重绘卡顿或事件漏接的位置,你照着改就行。
转交事件后必须显式调用 ChartRedraw 强制重绘,否则 MT5 不会自动更新对象图层,表现就是拖不动。
在事件循环里反复 new 局部对象、不用引用传参、漏掉坐标到价格的精度转换,都会让回放卡顿,建议先固化基础写法。