从头开始开发智能交易系统(第 29 部分):谈话平台·进阶篇
(2/3)· 删除 Chart Trade 只是开始,让 EA 开口说话前先搞懂哪些模块能安全裁掉
给 EA 加一道就绪闸门
在动手删 Chart Trade 相关逻辑前,先得把 EA 的启动稳定性补上。MT5 的 OnTick 事件可能在 EA 还没初始化完就砸过来,复杂系统里这会引发莫名其妙的异常。 我们在 C_IndicatorTradeView 类里塞了一个私有结构 st01,用两个布尔量标记「有无持仓」和「系统是否就绪」。初始化函数里先把它们置 false,等订单、持仓枚举和图表设置都跑完,才把 SystemInitilized 翻成 true。 下面这段是核心:若系统未就绪,SecureChannelPosition 直接返回 0,跳过后续计算。以前没这道检查,怪事频发;现在把事件拦截在门槛外,漏掉异常的概率明显下降。 持仓过滤还能省点算力。多资产 EA 里每种品种都可能带仓位,加了「订单总数或持仓总数变了才重扫」的判断后,平时不进循环,只在真正需要时跑。单次省的时间不长,但极端行情下 MT5 tick 密集时,累计开销的差别可能很可观。外汇与贵金属杠杆高,这类底层防护不到位容易放大滑点风险。
class="kw">struct st01 { class="type">bool ExistOpenPosition, SystemInitilized; }m_InfoSystem; class="type">void Initilize(class="type">void) { class="kw">static class="type">int ot = class="num">0, pt = class="num">0; m_InfoSystem.ExistOpenPosition = false; m_InfoSystem.SystemInitilized = false; ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false); ChartSetInteger(Terminal.Get_ID(), CHART_DRAG_TRADE_LEVELS, false); if ((ot != OrdersTotal()) || (pt != PositionsTotal())) { ObjectsDeleteAll(Terminal.Get_ID(), def_NameObjectsTrade); ChartRedraw(); for (class="type">int c0 = ot = OrdersTotal(); c0 >= class="num">0; c0--) IndicatorAdd(OrderGetTicket(c0)); for (class="type">int c0 = pt = PositionsTotal(); c0 >= class="num">0; c0--) IndicatorAdd(PositionGetTicket(c0)); } m_InfoSystem.SystemInitilized = true; } class="kw">inline class="type">class="kw">double SecureChannelPosition(class="type">void) { class="kw">static class="type">int nPositions = class="num">0; class="type">class="kw">double Res = class="num">0; class="type">class="kw">ulong ticket; class="type">int iPos = PositionsTotal(); if (!m_InfoSystem.SystemInitilized) class="kw">return class="num">0; }
◍ 持仓扫描与浮盈累加的实现细节
这段逻辑嵌在指标计算函数里,负责在每根 K 线刷新时重新确认当前品种是否还有未平仓位。它先判断遍历位置是否变动,或系统标记里是否曾存在持仓;只要任一条件成立,就先把 ExistOpenPosition 置 false,避免上一次的残留状态干扰本次统计。 随后从 iPos-1 倒序扫到 0,用 PositionGetSymbol(i0) 比对 Terminal.GetSymbol()。命中本品种时,ExistOpenPosition 翻回 true,并抓取 ticket、成交量、浮动盈亏和开仓价。若遍历位置不等于总持仓数,还会调用 IndicatorAdd 把该持仓挂到指标对象上,方便在副图直接画线。 SetTextValue 这一行把单笔 POSITION_VOLUME 写进 IT_RESULT,同时把 POSITION_PROFIT 累加到 Res,再附上 POSITION_PRICE_OPEN。最终函数 return Res,也就是当前品种全部浮仓的净盈亏之和——你可以在 MT5 策略测试器里接一段 Print(Res) 验证,正常行情下该值随持仓增减每秒波动,无持仓时稳定为 0.0。 倒序遍历而非正序,是因为 PositionGetTicket 在部分版本里删除持仓后索引会前移,正序容易漏单;若你改成正序扫描,回测中可能出现少算 1~2 笔微迷你仓的情况。
if ((iPos != nPositions) || (m_InfoSystem.ExistOpenPosition)) { m_InfoSystem.ExistOpenPosition = false; for (class="type">int i0 = iPos - class="num">1; i0 >= class="num">0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol()) { m_InfoSystem.ExistOpenPosition = true; ticket = PositionGetInteger(POSITION_TICKET); if (iPos != nPositions) IndicatorAdd(ticket); SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), Res += PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN)); } nPositions = iPos; } class="kw">return Res;
「把图表交易从 EA 里剥掉」
改完 C_IndicatorTradeView 类之后,EA 本体就可以动手剔除 Chart Trade 了。第一步是在 OnInit 里把集成分支换掉:原先 #ifdef def_INTEGRATION_CHART_TRADER 下那一整段绿色初始化(清模板、挂图表交易对象、调 OnTrade)被替换成仅一行 TradeView.Initilize()。表面看只是可执行文件变小,实际 EA 不再订阅 OnTrade 事件。
删掉 OnTrade 不是漏写,交易事件改由 OnTradeTransaction 承接。在 MT5 环境里 OnTrade 常被短时间内重复触发——实测某些成交回报会在 1 秒内连喊多次,是实打实的性能噩梦;而 OnTradeTransaction 由系统按事务粒度派发,竞争更少、处理更稳。
OnChartEvent 里也要同步:原绿色 Chart.DispatchMessage 调用整段移除,只留 TradeView.DispatchMessage 透传。这样 EA 专心做信号处理与订单管理,图表交易若以后要保留,挪进独立指标更干净,且仍能发单。
外汇与贵金属杠杆高,回测剔除事件前后的 CPU 占用与成交漏单率,是验证这套改法是否靠谱的唯一标准,请直接在 MT5 策略测试器里跑对照。
class="type">int OnInit() { Terminal.Init(); class="macro">#ifdef def_INTEGRATION_WITH_EA WallPaper.Init(user10, user12, user11); VolumeAtPrice.Init(user32, user33, user30, user31); TimesAndTrade.Init(user41); EventSetTimer(class="num">1); class="macro">#endif Mouse.Init(user50, user51, user52); class="macro">#ifdef def_INTEGRATION_CHART_TRADER class="kw">static class="type">class="kw">string memSzUser01 = ""; if (memSzUser01 != user01) { Chart.ClearTemplateChart(); Chart.AddThese(memSzUser01 = user01); } Chart.InitilizeChartTrade(EA_user20 * Terminal.GetVolumeMinimal(), EA_user21, EA_user22, EA_user23); TradeView.Initilize(); OnTrade(); class="macro">#else TradeView.Initilize(); class="macro">#endif class="kw">return INIT_SUCCEEDED; } 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); class="macro">#ifdef def_INTEGRATION_WITH_EA class="kw">switch (id) { case CHARTEVENT_CHART_CHANGE: Terminal.Resize(); WallPaper.Resize(); TimesAndTrade.Resize(); class="kw">break; } VolumeAtPrice.DispatchMessage(id, sparam); class="macro">#endif class="macro">#ifdef def_INTEGRATION_CHART_TRADER Chart.DispatchMessage(id, lparam, dparam, sparam); class="macro">#endif TradeView.DispatchMessage(id, lparam, dparam, sparam); } class="type">void OnTick() { class="macro">#ifdef def_INTEGRATION_CHART_TRADER Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, class="num">0, TradeView.SecureChannelPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]); class="macro">#else
用宏开关隔离 EA 与通道仓位
在 MQL5 的 OnInit 或主流程收尾处,常见做法是靠预处理宏把不同部署形态的代码隔开。上面这段就是典型:当定义了 def_SECURE_CHANNEL 宏,才调用 TradeView.SecureChannelPosition() 去锁定通道仓位;只有定义了 def_INTEGRATION_WITH_EA,才跑 TimesAndTrade.Update() 做与 EA 的联动刷新。 这种写法让同一份源码既能编译成独立指标,也能挂进 EA 做信号源,不用改逻辑。你在 MT5 里想验证,只需在 metaeditor 顶部 #define 区切换这两个宏,重新编译看是否报“未定义标识符”即可。 外汇与贵金属品种波动大、滑点随机,宏隔离只解决代码结构问题,不降低实盘风险,参数切换后务必先在策略测试器跑历史数据。
class="macro">#ifdef def_SECURE_CHANNEL TradeView.SecureChannelPosition(); class="macro">#endif class="macro">#ifdef def_INTEGRATION_WITH_EA TimesAndTrade.Update(); class="macro">#endif