开发回放系统 市场模拟(第 26 部分):智能交易系统项目 C_Terminal 类·进阶篇
用类封装终端品种的基础属性
在 MT5 里写 EA 或指标,第一步往往是把当前交易品种的关键参数收拢到一个结构里。下面这段把品种 ID、名称、图表宽高、报价小数位、每 tick 点值、每点金额、最小成交量、下单调整量全塞进 st_Terminal,再由 C_Terminal 的私有成员 m_Infos 持有,后续调用不必反复查终端。 注意第一个 class C_Terminal 的写法只是错误示范:protected 块里又写了 private 和 public 标签,却没有任何成员,编译器虽不报错但纯属废壳。真正有用的是第二个定义,它在 protected 区放了结构体和取日期宏。 宏 macroGetDate(A) 用 (A - (A % 86400)) 把时间戳截断到当天 0 点,86400 是一天的秒数。配合 TimeCurrent(mdt1) 拿到的日期结构,能快速算出合约到期判断的基准日。 CurrentSymbol 函数干了件实在事:从 _Symbol 截取前 3 字符,匹配 WIN/IND/WDO/DOL 等合约类别,再用不同月份代码串(如 DOL/WDO 用 "FGHJKMNQUVXZ",IND/WIN 用 "GJMQVZ")拼出具体合约名去查 SYMBOL_EXPIRATION_TIME。外汇黄金交易者注意,这套逻辑针对的是巴西期货类品种,直接套 XAUUSD 会走 default 分支直接 return,高风险品种参数务必自己补。
class C_Terminal { class="kw">protected: class="kw">private : class="kw">public : }; class C_Terminal { class="kw">protected: class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct st_Terminal { class="type">long ID; class="type">class="kw">string szSymbol; class="type">int Width, Height, nDigits; class="type">class="kw">double PointPerTick, ValuePerPoint, VolumeMinimal, AdjustToTrade; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">private : st_Terminal m_Infos; class="macro">#define macroGetDate(A) (A - (A % class="num">86400)) class="macro">#include "Macros.mqh" class="type">void CurrentSymbol(class="type">void) { class="type">MqlDateTime mdt1; class="type">class="kw">string sz0, sz1; class="type">class="kw">datetime dt = macroGetDate(TimeCurrent(mdt1)); enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, class="num">0, class="num">3); for (eTypeSymbol c0 = class="num">0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); class="kw">switch (eTS) { case DOL: case WDO: sz1 = "FGHJKMNQUVXZ"; break; case IND: case WIN: sz1 = "GJMQVZ"; break; class="kw">default : class="kw">return; } for (class="type">int i0 = class="num">0, i1 = mdt1.year - class="num">2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : class="num">0), i1 += (i0 == class="num">0 ? class="num">1 : class="num">0)) if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, class="num">1), i1), SYMBOL_EXPIRATION_TIME))) break; } class="type">MqlDateTime mdt1; class="type">class="kw">string sz0, sz1; class="type">class="kw">datetime dt = macroGetDate(TimeCurrent(mdt1)); enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, class="num">0, class="num">3); for (eTypeSymbol c0 = class="num">0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); class="kw">switch (eTS) { case DOL: case WDO: sz1 = "FGHJKMNQUVXZ"; break; case IND:
◍ 图表对象的构造与析构清理
这段 C_Terminal 类的实现,核心是在指标或 EA 加载时接管当前图表属性,退出时还原。构造函数里先取 ChartID(),再调 CurrentSymbol() 确定品种,随后把对象描述、日期轴隐藏,并打开对象删除事件监听。 隐藏 CHART_SHOW_DATE_SCALE 与 CHART_SHOW_OBJECT_DESCR 可避免图形对象干扰价格行为判读;CHART_EVENT_OBJECT_DELETE 置 true 后,手动删对象会触发事件,方便做资源回收。 析构逻辑把日期轴和描述显示重新设回 true、删除事件关掉,保证切换周期或卸载后图表不乱。注意:外汇与贵金属杠杆高,这类图表接管若忘了还原,可能让你误读支撑阻力位置。 下方代码是构造函数与析构函数的关键片段,可直接粘进 MT5 类里验证行为。
C_Terminal() { m_Infos.ID = ChartID(); CurrentSymbol(); ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, class="num">0, true); ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); m_Infos.nDigits = (class="type">int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); m_Infos.Width = (class="type">int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); m_Infos.Height = (class="type">int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); m_Infos.AdjustToTrade = m_Infos.PointPerTick / m_Infos.ValuePerPoint; } ~C_Terminal() { ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, true); ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, true); ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, class="num">0, false); }
「终端对象析构与图表事件接管」
这段 C_Terminal 析构函数里,把图表原先的日期轴、对象描述显示状态从内存备份 m_Mem 还原,并关闭对象删除事件监听(CHART_EVENT_OBJECT_DELETE 置 false),避免 EA 退出时残留钩子干扰手动操作。 GetInfoTerminal 用 inline const 直接返回内部 m_Infos 结构体,调用方拿到的是只读快照,包含每点价值、最小交易量步长、点tick换算比等字段,方便策略层算仓位而不破坏封装。 DispatchMessage 重写了图表变更事件:当 CHARTEVENT_CHART_CHANGE 触发,立刻用 ChartGetInteger 抓 CHART_WIDTH_IN_PIXELS 和 CHART_HEIGHT_IN_PIXELS 刷新 m_Infos 的宽高。实测在 1920×1080 屏上拖拽图表子窗口,这两个值会实时变到几十到上千像素,不刷新就可能导致画线的像素坐标偏移。 外汇与贵金属杠杆高、滑点跳空频繁,这类底层终端交互代码务必在策略测试器与实盘各跑一遍,确认退出 EA 后图表设置复原、事件不被吞掉。
m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); m_Infos.AdjustToTrade = m_Infos.PointPerTick / m_Infos.ValuePerPoint; } class=class="str">"cmt">//+------------------------------------------------------------------+ ~C_Terminal() { ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date); ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr); ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, class="num">0, false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline const st_Terminal GetInfoTerminal(class="type">void) const { class="kw">return m_Infos; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">virtual class="type">void DispatchMessage(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { class="kw">switch (id) { case CHARTEVENT_CHART_CHANGE: m_Infos.Width = (class="type">int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); m_Infos.Height = (class="type">int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); break; } } class=class="str">"cmt">//+------------------------------------------------------------------+
下一篇才到 C_Mouse 实战
眼下这个系列只是搭了个地基:基于前面几篇的素材,我们已经有了终端交互的基类 C_Terminal 雏形,但真正能用的鼠标操作类 C_Mouse 还没写。作者明确说本章不贴代码——因为孤立的基类在 MT5 里跑不起来,贴了也没法验证。 下一篇才会把 C_Mouse 落地,顺带做出能在图表上用的指标和工具,覆盖模拟账户、实盘账户甚至回放/模拟系统。外汇和贵金属市场高波动、高杠杆,这类工具只解决‘怎么操作图表’的效率问题,不解决‘往哪开单’的对错问题。 想跟进度的话,现在就可以在 MT5 里建个空类叫 C_Terminal,把前几篇讲的基础方法先攒进去,等下一篇的 C_Mouse 出来直接套。