一张图表上的多个指标(第 05 部分):将 MetaTrader 5 转变为 RAD 系统(I)·综合运用
📘

一张图表上的多个指标(第 05 部分):将 MetaTrader 5 转变为 RAD 系统(I)·综合运用

第 3/3 篇

面板对象与订单参数的双向同步

这段代码暴露了 MT5 自定义交易面板里一个常被忽略的细节:图形对象的属性读写和 EA 内核参数必须走同一套映射,否则点一下输入框,底层仓位计算还是老的。 Resize 函数用 OBJPROP_XDISTANCE 把所有面板子对象按基准 x 加偏移重排,循环上限是 m_CountObject。实测当对象数达到 def_MaxObject(代码里用作上限常量)时,新增计数会被掐断,界面不会再吃新控件。 UpdateInfos 从三个编辑框文本反解整数:杠杆合约数、止盈金额、止损金额,类型强转 (int) 后喂给 NanoEA.Initilize。注意 bSwap 参数会在日交易开关状态间翻转,但仅当外部传 true 才切,默认 false 保持原状。 InitilizeChartTradeDispatchMessage 都先判 m_CountObject < eEDIT_STOP 就直接 return——意味着面板没完全构建完之前,任何外部消息都不会改参数,这个守卫能避免半初始化状态下的脏写。

MQL5 / C++
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 可视化回放看清每一次点击对应的字符串。

MQL5 / C++
            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 最碍事,自己改掉它。

常见问题

通常是面板对象与订单参数未做双向绑定。检查点击事件的回调里是否同步更新了输入价格和成交量变量,否则只画了线不下单。
可以,前提是给对象绑定了 OBJPROP_ONDELETE 事件并调用撤仓函数。没写这段逻辑的话,删线只是清图表,仓位还在。
小布盯盘会扫描当前品种图表对象与账户订单,不一致时直接标红提示,你不用自己比对面板和终端。
可能只给第一条线挂了点击下单逻辑。每个对象都要独立注册事件处理器,漏写的线只是装饰。
容易神化拖拽下单,忽略对象事件与订单状态不同步的延迟。先在小品种上验证双向同步再上实盘,外汇贵金属波动大风险高。