从头开始开发智能交易系统(第 30 部分):CHART TRADE 当作指标?·进阶篇
📊

从头开始开发智能交易系统(第 30 部分):CHART TRADE 当作指标?·进阶篇

(2/3)· 当 EA 只留订单系统,市价党如何不丢图表交易?把 Chart Trade 重做成指标就对了

含代码示例实战向 第 2/3 篇
若你习惯按市价键入场,又用交叉订单系统,原生快速交易按钮会直接消失。把 Chart Trade 从 EA 里砍掉后,它还能以指标身份回到图表,不抢 EA 执行线程。

◍ 用全局变量搭起指标与EA的内部对话

想让 Chart Trade 指标真正能发单、平仓、回报结果,关键是在 MT5 进程内搭一套客户端-服务器式的数据通道。这里不靠 DLL,而是直接借用 MT5 的全局变量——它们最好导航、易维护,也方便按资产隔离。 具体分工上,把挂载在图表上的 EA 设为服务器更实用:EA 常驻,Chart Trade 只在需要时浮现。EA 负责建 5 个全局变量里的 4 个(杠杆、止盈、止损、当日结果),剩下 1 个由 Chart Trade 建,用来传“按了哪个按钮”的状态。 每种资产用 5 个变量,变量名拼了 _Symbol 前缀,所以同一图表能同时跑多品种。EA 建了杠杆变量后,指标初始化时若读不到它直接 INIT_FAILED;EA 撤掉该变量,指标也跟着被删。检查点有两个:指标初始化时,以及图表有任何事件时。 别把 OnTime 塞进指标里。所有指标共享同一条线程,一个指标里的定时事件可能拖慢其它指标。真担心性能损耗再考虑挪进去,但得清楚后果。 平仓回报这块有个细节:EA 每次平仓建一个 _Result 变量,Chart Trade 观测到同名变量立刻取值并删除,避免 EA 又发新更新把上一条冲掉。取值在删除前完成,所以不会丢。 绿色代码对应指标形态,它把按钮状态写进全局变量。点平仓就写 0x10;市价单则组合 0x04/0x08(买卖)加 0x01/0x02(日内或短线)。EA 靠这个值决定动作。 这个系统是自我覆盖的:卖出标志写出后、EA 未响应前又点买入,EA 最终只认买入;有持仓时点平仓再按取消,持仓也会被平掉。外汇和贵金属波动大、杠杆高,这种竞态在手动快点的场景里容易误触发,实盘前务必在策略测试器里复现。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define def_GlobalVariableLeverage(_Symbol + "_Leverage")
class="macro">#define def_GlobalVariableTake(_Symbol + "_Take")
class="macro">#define def_GlobalVariableStop(_Symbol + "_Stop")
class="macro">#define def_GlobalVariableResult(_Symbol + "_Result")
class="macro">#define def_GlobalVariableButton(_Symbol + "_ButtonState")
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define def_ButtonDTSelect               0x01
class="macro">#define def_ButtonSWSelect               0x02
class="macro">#define def_ButtonBuyMarket              0x04
class="macro">#define def_ButtonSellMarket             0x08
class="macro">#define def_ButtonClosePosition          0x10
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define def_SHORTNAME "CHART TRADE"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
{
      class="type">long lparam = class="num">0;
      class="type">class="kw">double dparam = class="num">0.0;
      class="type">class="kw">string sparam = "";
      
      IndicatorSetString(INDICATOR_SHORTNAME, def_SHORTNAME);
      if(!GlobalVariableGet(def_GlobalVariableLeverage, dparam)) class="kw">return INIT_FAILED;
      Terminal.Init();

「图表交互事件的派发与下单链路」

这段逻辑展示了 MT5 指标/EA 里如何把图表上的按钮点击转成实盘下单指令。核心在 OnChartEvent 收到 CHARTEVENT_OBJECT_CLICK 后,先过滤掉非自身前缀的对象名(def_HeaderMSG),避免误触其他图形元素。 过滤后截取第 9 个字符之后的子串并转大写,再判断是否是买/卖按钮。若 sparam 等于 szMsgIDE[eBTN_BUY] 或 szMsgIDE[eBTN_SELL],就调用 TradeView.ExecuteOrderInMarket,传入杠杆、止盈、止损、买卖方向和是否日内交易等参数。 初始化阶段也有坑:Chart.InitilizeChartTrade 用 dparam * Terminal.GetVolumeMinimal() 计算手数,止盈止损取自全局变量。若 GlobalVariableCheck(def_GlobalVariableLeverage) 不成立,直接 OnDeinit(REASON_INITFAILED),并打印『Unable to use Chart Trade…』提示 EA 不在该品种图表上。 外汇与贵金属杠杆交易风险极高,这段代码只解决『按钮到订单』的通道,不保证任何胜率;开 MT5 把 def_HeaderMSG 和 szMsgIDE 数组打印出来,就能确认你图表上的对象名是否对得上。

MQL5 / C++
Chart.AddThese("IDE(,,class="num">170, class="num">215)");
Chart.InitilizeChartTrade(dparam * Terminal.GetVolumeMinimal(), GlobalVariableGet(def_GlobalVariableTake), GlobalVariableGet(def_GlobalVariableStop), true);
OnChartEvent(CHARTEVENT_OBJECT_ENDEDIT, lparam, dparam, sparam);
class="kw">return INIT_SUCCEEDED;
}
class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam)
{
   if (!GlobalVariableCheck(def_GlobalVariableLeverage)) OnDeinit(REASON_INITFAILED);
   Chart.DispatchMessage(id, lparam, dparam, sparam);
}
class="type">void OnDeinit(const class="type">int reason)
{
   if (reason == REASON_INITFAILED)
   {
      Print("Unable to use Chart Trade. The EA is not on the chart of this asset...");
      if (!ChartIndicatorDelete(class="num">0, class="num">0, def_SHORTNAME))
         Print("Unable to class="kw">delete Chart Trade from the chart.");
   }
}
class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">class="kw">double &price[])
{
   class="type">class="kw">double value;
   
   if (GlobalVariableGet(def_GlobalVariableResult, value))
   {
      GlobalVariableDel(def_GlobalVariableResult);
      Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, class="num">0, value, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
   }

   class="kw">return rates_total;
}
class=class="str">"cmt">// ... Previous code ...
case CHARTEVENT_OBJECT_CLICK:
   if (StringSubstr(sparam, class="num">0, StringLen(def_HeaderMSG)) != def_HeaderMSG)
   {
      Resize(-class="num">1);
      class="kw">return;
   }
   sparam = StringSubstr(sparam, class="num">9, StringLen(sparam));
   StringToUpper(sparam);
class="macro">#ifdef def_INTEGRATION_CHART_TRADER
   if ((sparam == szMsgIDE[eBTN_SELL]) || (sparam == szMsgIDE[eBTN_BUY]))
      TradeView.ExecuteOrderInMarket(m_BaseFinance.Leverange, m_BaseFinance.FinanceTake, m_BaseFinance.FinanceStop, sparam == szMsgIDE[eBTN_BUY], m_BaseFinance.IsDayTrade);
   if (sparam == szMsgIDE[eBTN_CANCEL])
   {
      TradeView.CloseAllsPosition();

按钮事件里的联合体打包技巧

在 MT5 面板里处理 BUY / SELL / CANCEL 按钮点击时,这段非 MQL5 编译分支用了一个 union 把 double 和 ulong 叠在同一块内存上,靠 GlobalVariableSet 把指令甩给 EA 主循环。 具体看,u_local.c 先清零;若 sparam 匹配买按钮,就按日内或波段标记(def_ButtonDTSelect / def_ButtonSWSelect)加上市价买常量;卖按钮同理换常量;取消按钮则写 def_ButtonClosePosition 并顺手把取消键的 OBJPROP_STATE 置 false,避免界面卡在按下态。 最后一行 if (u_local.Value > 0) GlobalVariableSet(def_GlobalVariableButton, u_local.Value); 是真正触发点——只有打包出非零值才写全局变量,EA 侧读这个双精度数再拆回 ulong 就能知道用户点了什么。外汇与贵金属杠杆高,这类面板误操作可能瞬间触发实盘下单,建议在策略测试器里先跑通事件链路再接真账户。 下面把核心片段逐行拆一下,方便你直接抄进自己的面板类: union u00 { double Value; ulong c; } u_local; — 声明联合体,Value 和 c 共用 8 字节。 u_local.c = 0; — 指令码先清白。 if (sparam == szMsgIDE[eBTN_BUY]) u_local.c = (m_BaseFinance.IsDayTrade ? def_ButtonDTSelect : def_ButtonSWSelect) + def_ButtonBuyMarket; — 买按钮按交易周期拼出指令。 else if (sparam == szMsgIDE[eBTN_SELL]) u_local.c = ... + def_ButtonSellMarket; — 卖按钮同理。 else if (sparam == szMsgIDE[eBTN_CANCEL]) { u_local.c = def_ButtonClosePosition; ObjectSetInteger(... OBJPROP_STATE, false); } — 取消平仓并复位按钮。 if (u_local.Value > 0) GlobalVariableSet(def_GlobalVariableButton, u_local.Value); — 非零才广播,主 EA 轮询取值。

MQL5 / C++
union u00
{
	class="type">class="kw">double Value;
	class="type">class="kw">ulong c;
}u_local;
		u_local.c = class="num">0;
		if (sparam == szMsgIDE[eBTN_BUY]) u_local.c = (m_BaseFinance.IsDayTrade ? def_ButtonDTSelect : def_ButtonSWSelect) + def_ButtonBuyMarket; else
		if (sparam == szMsgIDE[eBTN_SELL]) u_local.c = (m_BaseFinance.IsDayTrade ? def_ButtonDTSelect : def_ButtonSWSelect) + def_ButtonSellMarket; else
		if (sparam == szMsgIDE[eBTN_CANCEL])
		{
			u_local.c = def_ButtonClosePosition;
			ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false);
		}
				if (u_local.Value > class="num">0) GlobalVariableSet(def_GlobalVariableButton, u_local.Value);

◍ 让 EA 接住 Chart Trade 的按钮信号

改 EA 接 Chart Trade 的消息,核心就一件事:在 EA 里建一套临时全局变量当信道,Chart Trade 点按钮写变量,EA 读变量执行。记住,EA 必须比 Chart Trade 先加载,否则指标初始化会失败;变量初值由 EA 给定,哪怕之前有历史交易,累积值也会重新灌进 Chart Trade。 变量要用 GlobalVariableTemp 声明成临时型。平台崩了或 EA 卸载,这些变量自动消失,不会在数据转储里留垃圾。但即便如此,OnDeinit 里仍要强制删一遍,确保 Chart Trade 脱离图表后不残留。 响应函数声明为 inline,编译器就地展开,跑得最快。放哪触发是个真问题:塞 OnTick 里,低波动品种跳价稀,点按钮可能半天才响应;塞 OnTime 里按频率跑才稳。OnTime 默认 1 秒一次,调用 EventSetMillisecondTimer(500) 可压到 500 毫秒,绝大多数场景够用。OnInit 加这行,OnDeinit 里关掉。 C_IndicatorTradeView 类补一行抓全局变量值的代码,Chart Trade 上的挂单参数就能进订单系统;所有挂单默认跟 EA 时间,想改直接图表上拖,不用走 Chart Trade。语音通知加进 C_Router,Chart Trade 手动开平也会响。外汇和贵金属杠杆高、滑点猛,这套通信若时序错乱可能漏单,实盘前务必在 MT5 策略测试器跑通再上。

MQL5 / C++
class="kw">input class="type">int       user20       = class="num">1;        class=class="str">"cmt">//Leverage
class="kw">input class="type">class="kw">double     user21       = class="num">100;      class=class="str">"cmt">//Take Profit
class="kw">input class="type">class="kw">double     user22       = class="num">81.74;    class=class="str">"cmt">//Stop Loss 
class="kw">input class="type">bool       EA_user23    = true;     class=class="str">"cmt">//Day Trade ?
class="type">int OnInit()
{
        if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        {
            Sound.PlayAlert(C_Sounds::TRADE_ALLOWED);
            class="kw">return INIT_FAILED;
        }
        
        Terminal.Init();
class="macro">#ifdef def_INTEGRATION_TAPE_READING
        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
        GlobalVariableTemp(def_GlobalVariableLeverage);

「按钮触发与全局变量清理的收尾逻辑」

EA 在初始化末尾用 GlobalVariableTemp 先占位三个临时变量(Take、Stop、Result),再用 GlobalVariableSet 写入真实参数:杠杆 = user20 乘终端最小成交量,止盈止损分别取 user21、user22,最后把 TradeView.GetFinanceRoof() 的返回值塞进 Result 变量。 非图表交易集成模式下,OnDeinit 会逐个 GlobalVariableDel 清掉 Leverage、Take、Stop、Result、Button 五个全局变量,避免残留数据干扰下一次加载;若开了 TAPE_READING 宏则额外 EventKillTimer 停掉定时器。 ChartTrade_ClickButton 用 union 把 double 和 ulong 复用同一块内存,读取 Button 变量后判断低位标志:等于 def_ButtonClosePosition 就平掉所有仓位,否则按 BuyMarket / DTSelect 位域调用 ExecuteOrderInMarket 下市价单,下单后重新 Initilize 刷新状态。 直接拷这段到 MT5 的 EA 源码里,把 user20~user22 换成你的手数系数与点数,编译后点面板按钮即可验证下单与全局变量读写是否如预期;外汇与贵金属杠杆品种波动剧烈,实盘前务必先用策略测试器跑一遍。

MQL5 / C++
GlobalVariableTemp(def_GlobalVariableTake);
GlobalVariableTemp(def_GlobalVariableStop);
GlobalVariableTemp(def_GlobalVariableResult);
GlobalVariableSet(def_GlobalVariableLeverage, user20 * Terminal.GetVolumeMinimal());
GlobalVariableSet(def_GlobalVariableTake, user21);
GlobalVariableSet(def_GlobalVariableStop, user22);
TradeView.Initilize();
GlobalVariableSet(def_GlobalVariableResult, TradeView.GetFinanceRoof());
class="macro">#endif

   class="kw">return INIT_SUCCEEDED;
}
class="type">void OnDeinit(const class="type">int reason)
{
   Mouse.Destroy();
   TradeView.Finish();
class="macro">#ifndef def_INTEGRATION_CHART_TRADER
   GlobalVariableDel(def_GlobalVariableLeverage);
   GlobalVariableDel(def_GlobalVariableTake);
   GlobalVariableDel(def_GlobalVariableStop);
   GlobalVariableDel(def_GlobalVariableResult);
   GlobalVariableDel(def_GlobalVariableButton);
class="macro">#endif
class="macro">#ifdef def_INTEGRATION_TAPE_READING
   EventKillTimer();
class="macro">#endif
}
class="kw">inline class="type">void ChartTrade_ClickButton(class="type">void)
{
   union u00
   {
      class="type">class="kw">double Value;
      class="type">class="kw">ulong c;
   }u_local;
   
   if (GlobalVariableGet(def_GlobalVariableButton, u_local.Value))
   {
      GlobalVariableDel(def_GlobalVariableButton);
      
      if (u_local.c == def_ButtonClosePosition) TradeView.CloseAllsPosition(); else
         TradeView.ExecuteOrderInMarket(GlobalVariableGet(def_GlobalVariableLeverage), GlobalVariableGet(def_GlobalVariableTake), GlobalVariableGet(def_GlobalVariableStop), ((u_local.c & def_ButtonBuyMarket) == def_ButtonBuyMarket), ((u_local.c & def_ButtonDTSelect) == def_ButtonDTSelect));
      TradeView.Initilize();
   }
}
class="macro">#else
   GlobalVariableTemp(def_GlobalVariableLeverage);
   GlobalVariableTemp(def_GlobalVariableTake);
   GlobalVariableTemp(def_GlobalVariableStop);
   GlobalVariableTemp(def_GlobalVariableResult);
   GlobalVariableSet(def_GlobalVariableLeverage, user20 * Terminal.GetVolumeMinimal());
   GlobalVariableSet(def_GlobalVariableTake, user21);
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 Chart Trade 指标加载后的盘口状态,把重复劳动交给小布,你专注决策。

常见问题

EA 应只负责交易系统本身,不属于系统的部分从代码剥离可提升性能与可靠性,后续篇章会解释具体原因。
脚本在切换图表时间帧时会关闭,需手动重跑;指标则常驻且不阻塞 EA 线程。
MetaTrader 5 未开放部分 Chart Trade 信息访问权限,指标版更简化,但市价单操作依然有效。
小布盯盘的 AIGC 看盘页已内置常见指标诊断,Chart Trade 指标挂上图表后,对应品种页可观察到其运行状态与盘口反馈。
回归 Chart Trade 并以指标形式挂载到图表,即可在交叉订单系统里继续用市价单面板。