从头开始开发智能交易系统(第 12 部分):时序与交易(I)·综合运用
(3/3)· 传统看盘需双屏盯盘且易走神漏单,本篇用类改造把完整合约成交直接叠到交易图
◍ 连接状态机与Tick回灌的初始化链路
这段逻辑把一个离线合成的自定义品种接入终端的过程拆成了 0→1→2→3 四级状态。状态 0 是尚未建窗,Init() 里若检测不到子窗口就现场 CreateCustomSymbol 再 CreateChart,并把 m_ConnectionStatus 置 0;状态 1 要求 SymbolIsSynchronized 返回真,否则直接 return 不往下走,过了才置 2;状态 2 才记下本地时间并倒推 60 分钟乘以 1000 毫秒作为 m_MemTickTime 回溯起点,随后切到 3 表示就绪。 Init 的形参 iScale 默认 2,写进 OBJPROP_CHART_SCALE 前做了截断:大于 5 按 5、小于 0 按 0。这意味着你即便传了 9,图表缩放也只到档位 5,传 -3 则落到 0,避免越界把离线图渲染崩掉。 Update 开头就卡 m_ConnectionStatus < 3 直接 return,没连好不碰数据。CopyTicks 用 m_MemTickTime 做起点拉 def_SizeBuff 条 COPY_TICKS_ALL,回来先跳过 time_msc 等于起点的旧 tick,再逐条过滤 volume == 0 的脏数据,并按 TICK_FLAG_BUY / TICK_FLAG_SELL 给 iflag 加一减一。外汇与贵金属杠杆高,这类自建tick流若不同步真实服务器时间,回测信号可能严重偏移,上 MT5 跑前先确认 TerminalInfoInteger(TERMINAL_CONNECTED) 非 0。
if (!TerminalInfoInteger(TERMINAL_CONNECTED)) class="kw">return; else m_ConnectionStatus = class="num">1; case class="num">1: if (!SymbolIsSynchronized(Terminal.GetFullSymbol())) class="kw">return; else m_ConnectionStatus = class="num">2; case class="num">2: m_LastTime = TimeLocal(); m_MemTickTime = macroMinusMinutes(class="num">60, m_LastTime) * class="num">1000; m_ConnectionStatus = class="num">3; class="kw">default: class="kw">break; } class="type">void Init(class="kw">const class="type">int iScale = class="num">2) { if (!ExistSubWin()) { CreateCustomSymbol(); CreateChart(); m_ConnectionStatus = class="num">0; } ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_CHART_SCALE, (iScale > class="num">5 ? class="num">5 : (iScale < class="num">0 ? class="num">0 : iScale))); } class="kw">inline class="type">void Update(class="type">void) { class="type">MqlTick Tick[]; class="type">MqlRates Rates[def_SizeBuff]; class="type">int i0, p1, p2 = class="num">0; class="type">int iflag; if (m_ConnectionStatus < class="num">3) class="kw">return; if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > class="num">0) { for (p1 = class="num">0, p2 = class="num">0; (p1 < i0) && (Tick[p1].time_msc == m_MemTickTime); p1++); for (class="type">int c0 = p1, c1 = class="num">0; c0 < i0; c0++) { if (Tick[c0].volume == class="num">0) class="kw">continue; iflag = class="num">0; iflag += ((Tick[c0].flags & TICK_FLAG_BUY) == TICK_FLAG_BUY ? class="num">1 : class="num">0); iflag -= ((Tick[c0].flags & TICK_FLAG_SELL) == TICK_FLAG_SELL ? class="num">1 : class="num">0);
用逐笔报价拼出自定义分钟K线
这段逻辑干的事,是把真实逐笔 Tick 塞进一个自定义品种的合成 K 线数组里。当 iflag 为 0 时直接 continue 跳过,意味着该笔报价不参与合成,可能是过滤掉非活跃时段或异常标记。 高价取 ask、低价取 bid、开盘与基准取 last,这是典型的买卖盘双边合成法,比单纯用 trade 价格更贴近盘口。 收盘价的算法值得盯一眼:在 last 基础上,加上成交量(封顶 200)乘以品种系数再乘 iflag。若为 WDO 品种系数取 0.02,其它取 1.0,等于把微小合约的波动按比例压缩,避免合成 K 线被点数放大失真。 每合成一根就把 m_LastTime 加 60 秒,说明目标周期是 1 分钟。最后用 CustomRatesUpdate 把 p2 根合成数据推送到 m_szCustomSymbol,并在收尾把上一笔 Tick 的毫秒时间存进 m_MemTickTime,作为下次续拼的断点。
if (iflag == class="num">0) class="kw">continue; Rates[c1].high = Tick[c0].ask; Rates[c1].low = Tick[c0].bid; Rates[c1].open = Tick[c0].last; Rates[c1].close = Tick[c0].last + ((Tick[c0].volume > class="num">200 ? class="num">200 : Tick[c0].volume) * (Terminal.GetTypeSymbol() == C_Terminal::WDO ? class="num">0.02 : class="num">1.0) * iflag); Rates[c1].time = m_LastTime; p2++; c1++; m_LastTime += class="num">60; } CustomRatesUpdate(m_szCustomSymbol, Rates, p2); m_MemTickTime = Tick[i0 - class="num">1].time_msc; } } Rates[c1].high = Tick[c0].ask; Rates[c1].low = Tick[c0].bid; Rates[c1].open = Tick[c0].last; Rates[c1].close = Tick[c0].last + ((Tick[c0].volume > class="num">200 ? class="num">200 : Tick[c0].volume) * (Terminal.GetTypeSymbol() == C_Terminal::WDO ? class="num">0.02 : class="num">1.0) * iflag); Rates[c1].time = m_LastTime;
「一点提醒」
这套「时序与交易」看盘框架已经能在 EA 里跑起来,用图形替代密密麻麻的数字矩阵,分析效率和 MT5 自带替代系统基本持平。附件里的 EA_-_Times_e_Trade.zip 约 5.98 MB,直接拖进 MT5 的 MQL5/Experts 目录就能编译验证。 下一篇准备补进当前缺失的几类行情信息,并往智能交易代码里加新元素;外汇与贵金属波动剧烈、杠杆风险高,任何图形化辅助都只是降低读盘负担,不预示方向。 真要落地,先开 MT5 把 ZIP 里的 EA 加载到图表,确认你自己的环境里渲染速度和原文描述一致,再等后续章节补完逻辑。