从头开始开发智能交易系统(第 29 部分):谈话平台·进阶篇
🗣️

从头开始开发智能交易系统(第 29 部分):谈话平台·进阶篇

(2/3)· 删除 Chart Trade 只是开始,让 EA 开口说话前先搞懂哪些模块能安全裁掉

进阶 第 2/3 篇
很多交易者给 EA 加了一堆图形下单控件,却从没想过它们正在拖慢执行。把 Chart Trade 这类冗余模块拆掉,往往比加新功能更能提升稳定性。本篇接着上一部分,聊聊怎么在不破坏订单系统的前提下做减法。

给 EA 加一道就绪闸门

在动手删 Chart Trade 相关逻辑前,先得把 EA 的启动稳定性补上。MT5 的 OnTick 事件可能在 EA 还没初始化完就砸过来,复杂系统里这会引发莫名其妙的异常。 我们在 C_IndicatorTradeView 类里塞了一个私有结构 st01,用两个布尔量标记「有无持仓」和「系统是否就绪」。初始化函数里先把它们置 false,等订单、持仓枚举和图表设置都跑完,才把 SystemInitilized 翻成 true。 下面这段是核心:若系统未就绪,SecureChannelPosition 直接返回 0,跳过后续计算。以前没这道检查,怪事频发;现在把事件拦截在门槛外,漏掉异常的概率明显下降。 持仓过滤还能省点算力。多资产 EA 里每种品种都可能带仓位,加了「订单总数或持仓总数变了才重扫」的判断后,平时不进循环,只在真正需要时跑。单次省的时间不长,但极端行情下 MT5 tick 密集时,累计开销的差别可能很可观。外汇与贵金属杠杆高,这类底层防护不到位容易放大滑点风险。

MQL5 / C++
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 笔微迷你仓的情况。

MQL5 / C++
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 策略测试器里跑对照。

MQL5 / C++
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 区切换这两个宏,重新编译看是否报“未定义标识符”即可。 外汇与贵金属品种波动大、滑点随机,宏隔离只解决代码结构问题,不降低实盘风险,参数切换后务必先在策略测试器跑历史数据。

MQL5 / C++
class="macro">#ifdef def_SECURE_CHANNEL
      TradeView.SecureChannelPosition();
class="macro">#endif
class="macro">#ifdef def_INTEGRATION_WITH_EA
      TimesAndTrade.Update();
class="macro">#endif
把语音播报交给小布盯盘
这些诊断和小布盯盘的 AIGC 已内置,打开对应品种页即可看到 EA 运行状态与异常提醒,你专注决策就好。

常见问题

Chart Trade 依赖简单订单系统,与当前 EA 的订单管理重复。用编译定义控制比隐藏更安全,改一行就能恢复,不会留下运行负担。
数据改为在 EA 初始化消息框内定义,并在 C_IndicatorTradeView.mqh 的 DispatchMessage 等处直接访问,无需再经图表控件中转。
语音和互动反馈会降低交易的枯燥感,可能诱发过度交易。外汇贵金属本身高风险,这类修改不应被当作娱乐化入口。
可以,小布盯盘的品种页能解析 EA 推送的状态与消息框内容,用 AIGC 做口语化播报,不必自己写语音模块。
那些行原用于挂单指标显示,若保留 Chart Trade 逻辑又删了控件,可能导致指标空引用。按本篇方式隔离定义即可避免。