开发回放系统(第30部分):EA交易项目C_Mouse类(四)·进阶篇
(2/3)· 在已测试代码中塞未验证函数常致不可预测失败,本篇给另一条并行路径
◍ 用指针绕开继承的并行测试思路
第二条路径里我们不去碰继承,而是直接在 EA 里用指针把各个类系统挂起来。主类系统(鼠标下单核心)标黄,上一篇文章里扩展出来的分析类标绿,橙色则是另一套可并行跑的分析系统。因为没走继承,EA 里要手写更多声明,但好处是能同时跑多份并行代码,哪一段在实测里出故障,直接删掉指针释放就行,不会牵一发动全身。 橙色和绿色这两套分析类本身都可能写成多态,这样能在并行开发阶段把系统的更多切面测出来。多态的具体用法留到后面再拆,现在铺开讲容易把人绕晕,尤其是刚接触这种架构的交易者。 下面这段 EA 代码就是第二条路径的完整骨架。注意 OnInit 里用 new 给三个指针分配内存,OnDeinit 里必须按相反顺序 delete,否则可能漏释放。OnTimer 每 500 毫秒触发一次,只调了 extra1 的 Update,而盘口事件 OnBookEvent 里也喂给 extra1,extra2 在当前版本还没接活。
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.30" class="macro">#class="kw">property icon "../../Images/Icons/Replay - EA.ico" class="macro">#class="kw">property link "[MQL5官方文档] 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_StudyS1.mqh> class="macro">#include <Market Replay\System EA\Auxiliar\Study\C_StudyS2.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_StudyS1 *extra1 = NULL; C_StudyS2 *extra2 = NULL; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { mouse = new C_Mouse(user00, user01, user02); extra1 = new C_StudyS1(mouse, user01, user02); extra2 = new C_StudyS2(mouse, user01, user02); MarketBookAdd((*mouse).GetInfoTerminal().szSymbol); OnBookEvent((*mouse).GetInfoTerminal().szSymbol); 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((*mouse).GetInfoTerminal().szSymbol); EventKillTimer(); class="kw">delete extra1; class="kw">delete extra2; class="kw">delete mouse; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() {} class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTimer() { (*extra1).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); } (*extra1).Update(book); }
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.30" class="macro">#class="kw">property icon "../../Images/Icons/Replay - EA.ico" class="macro">#class="kw">property link "[MQL5官方文档] 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_StudyS1.mqh> class="macro">#include <Market Replay\System EA\Auxiliar\Study\C_StudyS2.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_StudyS1 *extra1 = NULL; C_StudyS2 *extra2 = NULL; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { mouse = new C_Mouse(user00, user01, user02); extra1 = new C_StudyS1(mouse, user01, user02); extra2 = new C_StudyS2(mouse, user01, user02); MarketBookAdd((*mouse).GetInfoTerminal().szSymbol); OnBookEvent((*mouse).GetInfoTerminal().szSymbol); 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((*mouse).GetInfoTerminal().szSymbol); EventKillTimer(); class="kw">delete extra1; class="kw">delete extra2; class="kw">delete mouse; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() {} class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTimer() { (*extra1).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); } (*extra1).Update(book); }
「把鼠标事件分流给多个绘制对象」
在 MT5 自定义指标或 EA 里接管 OnChartEvent,是做交互式图形的前提。下面这段把图表事件统一派发给三个对象:mouse、extra1、extra2,各自内部再决定怎么响应。 代码里用指针解引用 (*mouse).DispatchMessage(...) 把 id、lparam、dparam、sparam 原样传进去,意味着鼠标点击、移动、键盘输入都会同时触达这三个实例。extra1 和 extra2 是扩展层,可挂不同的悬浮标签或辅助线,不干扰主鼠标逻辑。 每次派发完立刻调 ChartRedraw(),保证多个对象的状态变更在同一帧重绘,避免界面撕裂。实盘加载这类指标时,若对象超过 3 个且都接事件,CPU 占用可能随鼠标移动陡增,建议在 OnChartEvent 里加 id==CHARTEVENT_MOUSE_MOVE 的节流判断。外汇与贵金属图表事件密集,高频重绘有卡顿风险,参数需自测。
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); (*extra1).DispatchMessage(id, lparam, dparam, sparam); (*extra2).DispatchMessage(id, lparam, dparam, sparam); ChartRedraw(); }
拆开 C_StudyS2 的斐波那契分析骨架
C_StudyS2 类不是从零搭系统,而是复用 C_Mouse 已经验证过的鼠标交互与对象管理逻辑,再叠加一套基于 OBJ_FIBO 的分析呈现。头文件用相对路径 include(如 "..\C_Mouse.mqh"),项目整体迁移目录时不用改引用,结构维护成本低。 类里先用宏定义对象前缀与别名:def_ExpansionPrefix 拼出 "Expansion2_",再派生按钮与斐波那契对象名;def_InfoTerminal、def_InfoMousePos 直接映射到鼠标类的实时数据,写代码时少敲很多字。私有结构 st00 通过 m_Info 全局变量存分析状态——是否执行、是否清除、记忆价格与时间、正负偏差颜色。 真正画分析分三段:第一段监听到用户开始分析事件后,删掉 C_Mouse 创建的临时对象,但只删“研究不需要看见”的,避免误伤图表关键元素;此时旧分析对象也被清掉,方便按 OBJ_FIBOTIMES、OBJ_FIBOFAN、OBJ_FIBOARC、OBJ_FIBOCHANNEL、OBJ_EXPANSION 等变体重建。第二段创建 OBJ_FIBO 并设属性,级别用静态定义,颜色与线形可改,目的是一眼读懂。第三段上图表绘制,并备好后续函数要用的变量。 显示文本的小数位数跟着品种跑:有的品种报价值要 4 位,有的 5 位,股票可能只 2 位。代码用 StringFormat 配合 <%.2f> / <%.4f> 在运行时拼格式,差值(分析起点价与鼠标当前价偏移)按对应位数转字符串,不会写死。 柱数统计也值得注意:用平台函数数分析区间的 bar,若在空白区会返回 -1,单 bar 返回 0;代码里把 -1 剔掉,让数值恒等于“含起点柱”的实际根数。偏差百分比另算一并显示。外汇与贵金属波动大、杠杆高,这类自定义分析对象仅辅助判读,信号失效概率不低,上 MT5 跑前先开模拟盘验证。
<span class="preprocessor">class="macro">#class="kw">property </span><span class="macro">copyright</span> <span class="class="type">class="kw">string">"Daniel Jose"</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"..\C_Mouse.mqh"</span> <span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"..\..\..\Service Graphics\Support\Interprocess.mqh"</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#define </span>def_ExpansionPrefix <span class="class="type">class="kw">string">"Expansion2_"</span> <span class="preprocessor">class="macro">#define </span>def_ExpansionBtn1 def_ExpansionPrefix + <span class="class="type">class="kw">string">"B1"</span> <span class="preprocessor">class="macro">#define </span>def_ExpansionFibo def_ExpansionPrefix + <span class="class="type">class="kw">string">"FB"</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#define </span>def_InfoTerminal(*mouse).GetInfoTerminal() <span class="preprocessor">class="macro">#define </span>def_InfoMousePos(*mouse).GetInfoMouse().Position <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class</span> C_StudyS2 { <span class="keyword">class="kw">protected</span>: <span class="keyword">class="kw">private</span> : <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> C_Mouse *mouse; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="kw">struct</span> st00 { <span class="keyword">class="type">bool</span> ExecStudy, ClearStudy; <span class="keyword">class="type">class="kw">double</span> MemPrice; <span class="keyword">class="type">class="kw">datetime</span> MemDT; <span class="keyword">class="type">color</span> corP, corN; }m_Info; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#define </span>def_FontName <span class="class="type">class="kw">string">"Lucida Console"</span> <span class="preprocessor">class="macro">#define </span>def_FontSize <span class="number">class="num">10</span>
◍ 用 Fibo 数组和按钮对象搭信息面板
在 MT5 指标里做交互式提示,先得量出文字占位。GetDimensionText 用 TextSetFont 设字体(字号取负向 -10*def_FontSize 表示像素单位),TextGetSize 拿宽高后各加 5 像素留白,避免文字贴边。 CreateBTNInfo 负责生成 OBJ_BUTTON:通过 ObjectSetInteger 把状态锁为 true、边框与文字都设 clrBlack,背景交 backColor 参数控制;字体名与字号沿用全局 def_FontName / def_FontSize,锚点固定 CORNER_LEFT_UPPER 并按 x 给水平偏移。 CreateStudy 才是主装配:FiboLevels 写死 0 / 0.236 / 0.382 / 0.50 / 0.618 / 1 / 1.618 / 2 共 8 档,先删掉旧扩展斐波那契与三个 MOUSE_ 前缀对象,再建 OBJ_FIBO 并关掉向左射线。OBJPROP_LEVELS 直接用 ArraySize(FiboLevels) 动态填层数,外汇与贵金属波动剧烈,这类画线仅作参考、实际破位概率随流动性变化。 循环从 c0=0 跑到数组长度,逐档写层级样式——这一段若接你自己的面板逻辑,改 FiboLevels 数组就能换一套扩展位。
class="type">void GetDimensionText(class="kw">const class="type">class="kw">string szArg, class="type">int &w, class="type">int &h) { TextSetFont(def_FontName, -class="num">10 * def_FontSize, FW_NORMAL); TextGetSize(szArg, w, h); h += class="num">5; w += class="num">5; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CreateBTNInfo(class="type">int x, class="type">int w, class="type">int h, class="type">class="kw">string szName, class="type">color backColor) { (*mouse).CreateObjectGraphics(szName, OBJ_BUTTON, clrNONE); ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_STATE, true); ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BORDER_COLOR, clrBlack); ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_COLOR, clrBlack); ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BGCOLOR, backColor); ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_FONT, def_FontName); ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_FONTSIZE, def_FontSize); ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XDISTANCE, x); } class="macro">#undef def_FontSize class="macro">#undef def_FontName class="type">void CreateStudy(class="type">void) { class="kw">const class="type">class="kw">double FiboLevels[] = {class="num">0, class="num">0.236, class="num">0.382, class="num">0.50, class="num">0.618, class="num">1, class="num">1.618, class="num">2}; ENUM_LINE_STYLE ls; class="type">color cor; ObjectDelete(def_InfoTerminal.ID, def_ExpansionFibo); ObjectDelete(def_InfoTerminal.ID, "MOUSE_TB"); ObjectDelete(def_InfoTerminal.ID, "MOUSE_TI"); ObjectDelete(def_InfoTerminal.ID, "MOUSE_TT"); (*mouse).CreateObjectGraphics(def_ExpansionFibo, OBJ_FIBO, clrNONE); ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_HIDDEN, class="kw">false); ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_RAY_LEFT, class="kw">false); ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_LEVELS, ArraySize(FiboLevels)); for (class="type">int c0 = class="num">0, c1 = ArraySize(FiboLevels); c0 < c1; c0++) {
「斐波那契扩展线的样式与交互收尾」
上面这段把扩展斐波那契的各档位线型与配色做了区分:0、1、2 档用实线,其余用点划线,0.5 档单独点线。switch 里把线型映射到颜色——点线蓝紫、点划紫、默认靛蓝,视觉上能把关键回调位和延伸位拉开层次。 循环里用 ObjectSetInteger 逐个写 OBJPROP_LEVELSTYLE、OBJPROP_LEVELCOLOR、OBJPROP_LEVELWIDTH(固定 1),再把档位值乘 100 后 NormalizeDouble 到两位小数写进 OBJPROP_LEVELTEXT,图表上直接显示百分比而非小数。 对象收尾靠析构里的 ObjectsDeleteAll 按前缀 def_ExpansionPrefix 清空,避免重复画线堆积。键盘事件里若按下 ESC 且已执行分析,就把 ClearStudy 置真,鼠标移动事件留作后续触发刷新——外汇与贵金属波动剧烈,这类对象若不清会干扰后续手动划线,实盘前务必在 MT5 策略测试器或图表手动验证一遍生命周期。
ls = ((FiboLevels[c0] == class="num">0) || (FiboLevels[c0] == class="num">1) || (FiboLevels[c0] == class="num">2) ? STYLE_SOLID : STYLE_DASHDOT); ls = (FiboLevels[c0] == class="num">0.5 ? STYLE_DOT : ls); class="kw">switch (ls) { case STYLE_DOT : cor = clrBlueViolet; break; case STYLE_DASHDOT: cor = clrViolet; break; class="kw">default : cor = clrIndigo; } ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_LEVELSTYLE, c0, ls); ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_LEVELCOLOR, c0, cor); ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_LEVELWIDTH, c0, class="num">1); ObjectSetString(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_LEVELTEXT, c0, (class="type">class="kw">string)NormalizeDouble(FiboLevels[c0] * class="num">100, class="num">2)); } ObjectSetDouble(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_PRICE, class="num">1, m_Info.MemPrice = def_InfoMousePos.Price); ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionFibo, OBJPROP_TIME, class="num">1, m_Info.MemDT = def_InfoMousePos.dt); CreateBTNInfo(def_InfoMousePos.X, class="num">50, class="num">18, def_ExpansionBtn1, clrNONE); m_Info.ExecStudy = true; m_Info.ClearStudy = class="kw">false; } C_StudyS2(C_Mouse *arg, class="type">color corP, class="type">color corN) { mouse = arg; ZeroMemory(m_Info); m_Info.corP = corP; m_Info.corN = corN; } class=class="str">"cmt">//+------------------------------------------------------------------+ ~C_StudyS2() { ObjectsDeleteAll(def_InfoTerminal.ID, def_ExpansionPrefix); } class="kw">virtual 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="type">class="kw">double v1, v2; class="type">int w, h; class="type">class="kw">string sz1; class="kw">switch (id) { case CHARTEVENT_KEYDOWN: if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE) && (m_Info.ExecStudy)) m_Info.ClearStudy = true; break; case CHARTEVENT_MOUSE_MOVE: