跨平台智能交易程序: 重用来自 MQL5 标准库的控件(基础篇)
📘

跨平台智能交易程序: 重用来自 MQL5 标准库的控件(基础篇)

第 1/3 篇

把 MQL5 标准库控件搬进跨平台 EA

在 MT5 里写带界面的智能交易程序,最省事的办法是直接复用标准库里的控件类,而不是从零造按钮、编辑框和图表对象。标准库自 MT5 发布起就随终端附带,路径在 MQL5/Include/Controls,老项目里大量 EA 和指标都依赖这套基类。 根据终端公开统计,这篇关于控件重用的技术文发布于 2016 年 9 月 20 日,截至近年仍被查看过 4412 次,说明跨版本移植界面的需求一直存在。外汇与贵金属交易本身杠杆高、滑点风险大,自动化界面只是辅助决策,不能替你消掉仓位风险。 实际动手时,先在你的 EA 源文件里 include 对应头文件,继承 CButton 或 CEdit 做子类,就能在 OnInit 里挂到图表。下面这段是标准库里简化出的按钮创建骨架,跑通它你就有了跨平台 UI 的入口。

MQL5 / C++
class="macro">#include <Controls/Button.mqh>
CButton btn;
class="type">int OnInit()
{
   btn.Create(class="num">0, "MyBtn", class="num">0, class="num">10, class="num">10, class="num">80, class="num">25);
   btn.Text("Run");
   btn.Show();
   class="kw">return(INIT_SUCCEEDED);
}

「这套交易类库的骨架先看哪几块」

想用 MQL5 搭自己的盯盘与下单框架,先认清楚标准库里这几个核心类的关系,能省掉大量重复造轮子的时间。 CSymbolInfo 负责单品种的属性读取,CSymbolManager 管品种池的增删与刷新,CAccountInfo 拿账户净值与杠杆,CExpertTrade 和 CTradeManager 则分别承接策略信号与订单执行。 实际开 MT5 按 F4 进标准库目录,对照这几个类名建个空 EA 把头文件 include 进来,编译不报错就说明骨架通了。外汇与贵金属杠杆高,仅把类库跑通不等于策略可靠,后续参数仍要小仓位验证。

◍ 让 MQL4 也能用上 MQL5 控件

MQL5 标准库里不少控件在 MQL4 跨平台 EA 中很实用,但直接引用会撞上 MQL4 编译器不兼容的问题,导致头文件根本编不过。 处理这种割裂通常两条路:一是只留两边都能用的控件从零重写;二是把 MQL5 的类拷出来改一改,让头文件能被 MQL4 编译器吞下去。本文走的是第二条。 拷贝改写的做法会让 MQL4 侧实现比较松散,代价是要重写相当多代码。不过有个实打实的好处:等 MQL5 官方类更新到新版本,你比对 diff 跟进比全盘重写要省力得多。 外汇与贵金属交易杠杆高、滑点随机,跨平台代码若没在 MT4/MT5 双终端实编验证,别直接挂真仓。

跨版本复用 MQL5 标准库类的目录摆法

要把 MQL5 标准库里的重用类(比如 CSymbolInfo)拿到 MQL4 环境里用,不能直接引原始路径,得先拷进 MQL4 数据文件夹的 include 目录再做桥接。否则两套终端编译时一个找得到、一个找不着,EA 根本跨不了平台。 最省事的做法是在 Include 下建一个 Base\Lib 放主头文件,再在 MQL4 下建一个自定义目录(例如 MQLx-Reuse\Lib)存从 MQL5 拷过来、可能改过的头文件。MQL5 侧因为标准库就在原生 include 里,主头文件直接指原始位置即可,不需要再套一层 Lib。 目录形态大致是:Include 下有 MQLx-Reuse(含 Base\Lib 与 MQL4\Lib),MQL5 文件夹则不设 Lib,主头文件直连 <Trade\SymbolInfo.mqh>。实测用 CSymbolInfo 时,MQL4 版通过 ..\..\MQL4\Lib\SymbolInfo.mqh 回退到拷贝件,MQL5 版走原生 Trade 路径,两边都能编过。 这并非强制规范,但专门留一个复用目录,后续维护跨平台 EA 时翻代码会轻松很多,也避免把改过的标准库类散落得到处都是。

MQL5 / C++
class="macro">#include <Trade\SymbolInfo.mqh>
class="macro">#ifdef __MQL5__
  class="macro">#include <Trade\SymbolInfo.mqh>
class="macro">#else
  class="macro">#include "..\..\MQL4\Lib\SymbolInfo.mqh"
class="macro">#endif

「把 MQL5 的 CSymbolInfo 塞进 MQL4 编译器」

直接用 MQL4 编译器去编 MQL5 标准库里的 CSymbolInfo 头文件,基本会报一堆错,根子出在 SymbolInfoDouble 和 SymbolInfoInteger 这两个函数在 MQL4 里并不全兼容,调用集中暴露在类的 Refresh() 方法内。 最快的落地办法不是重写三件套,而是把 MQL5 的 CSymbolInfo 文件拷出来,只把不兼容的行注释掉,MQL5 版指向原标准库位置,MQL4 版指向这份改过的副本,基础头文件只做版本路由不写类定义。 SYMBOL_TRADE_TICK_VALUE_PROFIT 和 SYMBOL_TRADE_TICK_VALUE_LOSS 这类标识符 MQL4 目前不支持,先注释掉;ENUM_SYMBOL_CALC_MODE、ENUM_SYMBOL_SWAP_MODE 在 MQL4 没有,得在头文件里手动声明枚举才能过编。 注释掉那两行 tick value profit/loss 后,Refresh() 在 MQL4 下可能直接返回 false,这是可复现的坑,开 MT4 编一下就能验证。拿不准某个方法两版是否都支持时,分别翻两版语言文档比对最稳。

MQL5 / C++
class="type">bool CSymbolInfo::Refresh(class="type">void)
  {
   class="type">long tmp=class="num">0;
class=class="str">"cmt">//---
   if(!SymbolInfoDouble(m_name,SYMBOL_POINT,m_point))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE,m_tick_value))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT,m_tick_value_profit))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_LOSS,m_tick_value_loss))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_SIZE,m_tick_size))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_CONTRACT_SIZE,m_contract_size))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MIN,m_lots_min))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MAX,m_lots_max))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_STEP,m_lots_step))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_LIMIT,m_lots_limit))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_LONG,m_swap_long))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_SHORT,m_swap_short))
      class="kw">return(false);
   if(!SymbolInfoInteger(m_name,SYMBOL_DIGITS,tmp))
      class="kw">return(false);
   m_digits=(class="type">int)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_ORDER_MODE,tmp))
      class="kw">return(false);
   m_order_mode=(class="type">int)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_EXEMODE,tmp))
      class="kw">return(false);
   m_trade_execution=(ENUM_SYMBOL_TRADE_EXECUTION)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_CALC_MODE,tmp))
      class="kw">return(false);
   m_trade_calcmode=(ENUM_SYMBOL_CALC_MODE)tmp;
   if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_MODE,tmp))

◍ Refresh 里那些决定下单成败的合约参数

CSymbolInfo 的 Refresh 方法负责把交易品种的动态属性拉进内存,任何一项 SymbolInfoDouble 调用失败都会直接 return(false),意味着后续基于该对象的下单计算全部失效。 它先取 SYMBOL_POINT 和 SYMBOL_TRADE_TICK_VALUE,这两个值决定了点值和报价精度;注释掉的那两行 TICK_VALUE_PROFIT / LOSS 说明标准库默认不区分多空tick价值,实盘里金银价差扩大时可能带来微小偏差。 紧接着是 TICK_SIZE、CONTRACT_SIZE、VOLUME_MIN / MAX / STEP——合约大小和最小手数步进直接制约你的仓位粒度。比如伦敦金 CONTRACT_SIZE 通常是 100 盎司,VOLUME_STEP 多为 0.01,刷新失败就会沿用旧值,导致 OrderSend 返回无效交易量错误。 开 MT5 把这段贴进 EA 的 Refresh 调用后,打印 m_lots_step 和 m_contract_size,对照品种规格窗口核对,能提前排掉一半以上的手数类报错。外汇和贵金属杠杆高,参数误读会放大穿仓概率,核对完再上模拟盘。

MQL5 / C++
class="type">bool CSymbolInfo::Refresh(class="type">void)
  {
   class="type">long tmp=class="num">0;
class=class="str">"cmt">//---
   if(!SymbolInfoDouble(m_name,SYMBOL_POINT,m_point))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE,m_tick_value))
      class="kw">return(false);
class=class="str">"cmt">//if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT,m_tick_value_profit))
class=class="str">"cmt">//class="kw">return(false);
class=class="str">"cmt">//if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_LOSS,m_tick_value_loss))
class=class="str">"cmt">//class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_SIZE,m_tick_size))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_CONTRACT_SIZE,m_contract_size))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MIN,m_lots_min))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MAX,m_lots_max))
      class="kw">return(false);
   if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_STEP,m_lots_step))
      class="kw">return(false);

常见问题

把标准库类按原层级复制到工程目录,保持 Include 路径相对引用,避免改文件名导致依赖断裂。
检查老平台是否缺对应行情函数,用条件编译补一层兼容封装,只暴露必要接口给原逻辑。
小布可扫描你的工程依赖,标出不适配平台的类与函数,并给出最小改动建议,省去手动排查。
重点看点差、最小止损距离、合约大小与交易模式,这几个没同步刷新,发单会被服务器拒。
先理清控件基类、容器管理和事件路由三块,其余 UI 与数据类都是挂在这套骨架上扩展的。