一张图表上的多个指标(第 05 部分):将 MetaTrader 5 转变为 RAD 系统(I)·综合运用
面板对象与订单参数的双向同步
这段代码暴露了 MT5 自定义交易面板里一个常被忽略的细节:图形对象的属性读写和 EA 内核参数必须走同一套映射,否则点一下输入框,底层仓位计算还是老的。
Resize 函数用 OBJPROP_XDISTANCE 把所有面板子对象按基准 x 加偏移重排,循环上限是 m_CountObject。实测当对象数达到 def_MaxObject(代码里用作上限常量)时,新增计数会被掐断,界面不会再吃新控件。
UpdateInfos 从三个编辑框文本反解整数:杠杆合约数、止盈金额、止损金额,类型强转 (int) 后喂给 NanoEA.Initilize。注意 bSwap 参数会在日交易开关状态间翻转,但仅当外部传 true 才切,默认 false 保持原状。
InitilizeChartTrade 和 DispatchMessage 都先判 m_CountObject < eEDIT_STOP 就直接 return——意味着面板没完全构建完之前,任何外部消息都不会改参数,这个守卫能避免半初始化状态下的脏写。
if (m_szLine == "border_type" ) macro_SetInteger(OBJPROP_BORDER_TYPE, StringToInteger(m_szValue)); } m_CountObject += (b0 ? class="num">0 : (m_CountObject < def_MaxObject ? class="num">1 : class="num">0)); class="kw">return true; class="macro">#undef macro_SetString class="macro">#undef macro_SetInteger } class="type">void Resize(class="type">int x) { for (class="type">int c0 = class="num">0; c0 < m_CountObject; c0++) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX); }; class="type">void UpdateInfos(class="type">bool bSwap = false) { class="type">int nContract, FinanceTake, FinanceStop; nContract = (class="type">int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_LEVERAGE].szName, OBJPROP_TEXT)); FinanceTake = (class="type">int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_TAKE].szName, OBJPROP_TEXT)); FinanceStop = (class="type">int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_STOP].szName, OBJPROP_TEXT)); m_IsDayTrade = (bSwap ? (m_IsDayTrade ? false : true) : m_IsDayTrade); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eCHECK_DAYTRADE].szName, OBJPROP_STATE, m_IsDayTrade); NanoEA.Initilize(nContract, FinanceTake, FinanceStop, clrNONE, clrNONE, clrNONE, m_IsDayTrade); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void InitilizeChartTrade(class="type">int nContracts, class="type">int FinanceTake, class="type">int FinanceStop, class="type">color cp, class="type">color ct, class="type">color cs, class="type">bool b1) { NanoEA.Initilize(nContracts, FinanceTake, FinanceStop, cp, ct, cs, b1); if (m_CountObject < eEDIT_STOP) class="kw">return; ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_LEVERAGE].szName, OBJPROP_TEXT, IntegerToString(nContracts)); ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_TAKE].szName, OBJPROP_TEXT, IntegerToString(FinanceTake)); ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_STOP].szName, OBJPROP_TEXT, IntegerToString(FinanceStop)); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eCHECK_DAYTRADE].szName, OBJPROP_STATE, m_IsDayTrade = b1); } class="type">void DispatchMessage(class="type">int iMsg, class="type">class="kw">string szArg, class="type">class="kw">double dValue = class="num">0.0) { if (m_CountObject < eEDIT_STOP) class="kw">return; class="kw">switch (iMsg) { case CHARTEVENT_CHART_CHANGE: if (szArg == szMsgIDE[eRESULT]) {
「图表对象点击如何驱动下单与撤仓」
这段逻辑处理的是面板和图表之间的事件桥接:用户点按钮,EA 读字符串、判类型,再调交易函数。外汇与贵金属杠杆高,任何一键下单都可能在几秒内扩大浮亏,实盘前务必在策略测试器里用 tick 数据跑通。 当触发 CHARTEVENT_OBJECT_CLICK 时,先过滤前缀不是 def_HeaderMSG 的对象直接 return,避免误触。随后截掉前 9 个字符、转大写,若字符串等于买/卖按钮标识就调 NanoEA.OrderMarket(),布尔值决定买卖方向;等于取消按钮则平掉持仓并把按钮状态复位为 false。 若点到日内交易勾选框,则传 true 刷新信息;对象编辑结束(ENDEDIT)也会无声调一次 UpdateInfos()。OnTick 里只做一件事:把当前持仓检查结果推给结果文本对象,负值显珊瑚红、非负显浅绿,保留两位小数。 别把按钮事件当保险丝 面板按钮不会替你确认仓位大小,NanoEA.OrderMarket 内部若没做手数校验,连点两次可能直接双倍敞口。建议复制下面代码后在 OrderMarket 前加 Print 断点,用 MT5 可视化回放看清每一次点击对应的字符串。
ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < class="num">0 ? clrLightCoral : clrLightGreen)); ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, class="num">2)); } class="kw">break; case CHARTEVENT_OBJECT_CLICK: if (StringSubstr(szArg, class="num">0, StringLen(def_HeaderMSG)) != def_HeaderMSG) class="kw">return; szArg = StringSubstr(szArg, class="num">9, StringLen(szArg)); StringToUpper(szArg); if ((szArg == szMsgIDE[eBTN_SELL]) || (szArg == szMsgIDE[eBTN_BUY])) NanoEA.OrderMarket(szArg == szMsgIDE[eBTN_BUY]); if (szArg == szMsgIDE[eBTN_CANCEL]) { NanoEA.ClosePosition(); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false); } if (szArg == szMsgIDE[eCHECK_DAYTRADE]) UpdateInfos(true); class="kw">break; case CHARTEVENT_OBJECT_ENDEDIT: UpdateInfos(); class="kw">break; } } class="type">void OnTick() { SubWin.DispatchMessage(CHARTEVENT_CHART_CHANGE, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT], NanoEA.CheckPosition()); }
◍ 把工具请下神坛
把 MT5 原本不是为 RAD 设计的底层框架,硬改成一个能拖控件、跑逻辑的可视化系统,这件事本身证明平台边界是人划的,不是铁律。前面两篇里那套从轨道线到多子窗口 EA 的链路,已经在 EA_1.04.zip(约 3.28 MB)里可跑,你下载丢进终端就能看到 GUI 与交易逻辑同框。 外汇和贵金属这类高波动品种,拿这套思路做原型可以,但任何信号都只是概率倾向,真金白银前先在策略测试器里把点差和滑点调成实盘量级。 作者放话后续还会往 EA 里塞新模块,社区里也有人靠他的图形库补齐了官方缺失的 GUI 支持。你要做的不是等更新,是开 MT5 把现有 zip 拆了,看哪段 OnChartEvent 最碍事,自己改掉它。