MQL5 中的交易操作 - 很简单(基础篇)
📘

MQL5 中的交易操作 - 很简单(基础篇)

第 1/3 篇

◍ 用 MQL5 把买卖动作自动化

做算法交易,绕不开三个硬问题:交易什么、何时交易、如何用代码下单。前两个属于策略层,第三个才是 MQL5 真正要接住的活——把明确的买卖意图翻译成平台能执行的指令。 在 MetaTrader 5 环境里,交易操作本身并不玄学。官方示例文档显示,一篇基础讲解帖在 2013 年 10 月 24 日发布后,累计获得 13474 次查看与 39 条讨论,说明手动转自动的需求一直很实。 本文聚焦第三问:怎样用 MQL5 语言完成下单、平仓这类基础动作。品种选择和入场规则先放一边,先把‘怎么让机器人替你点鼠标’跑通,后面接策略才有着力点。外汇与贵金属杠杆高,自动化放大效率的同时也放大风险,任何下单逻辑都先在策略测试器里验证。

「MQL5 怎么把下单变成几行代码」

做算法交易,最耗时的从来不是想逻辑,而是把想法接进券商通道。MQL5 把订单、持仓、交易请求都封装成现成函数,开发者省掉大量底层通信和回报解析的活。 核心就两个发送函数:OrderSend() 是同步发单,代码会卡住等服务器回执;OrderSendAsync() 是异步发单,丢出去就继续跑,回执靠事件回调拿。实战里高频或挂多品种时,异步更不容易卡主线程。 除了发单,MQL5 还能拉交易品种的成交一览表、翻交易历史、捕获成交事件,也能画自定义指标和界面。外汇和贵金属杠杆高、滑点跳空频繁,异步发单配合事件处理,能降低错单概率,但断网或流动性枯竭时仍可能失败,需自行做超时与重检。

下单动作其实就这几类

在 MT5 的 EA 里,真正绕不开的交易动作只有四组:市价直接 BUY/SELL、按条件挂单、改单或撤单、以及平仓/加仓/减仓/反手。它们全部走 OrderSend() 或它的异步版 OrderSendAsync(),底层靠 MqlTradeRequest 结构把请求描述清楚。 挂单类型严格对应 ENUM_ORDER_TYPE 枚举:BUY STOP 与 SELL STOP 是突破低于市价的指定位才触发;BUY LIMIT 与 SELL LIMIT 则是回到高于市价的指定位才成交;BUY STOP LIMIT / SELL STOP LIMIT 先触价再转成对应的 LIMIT 挂单。外汇与贵金属杠杆高,挂单滑点和拒单概率都不容忽视,回测之外必须在实盘点差环境验证。 改挂单、删挂单、调持仓,用的还是同一套 OrderSend 逻辑,难点只在把 MqlTradeRequest 字段填对和处理返回结果。开 MT5 按 F4 进 MetaEditor,新建 EA 把请求结构打印出来,比看文档直观得多。

◍ 用 CAccountInfo 给 EA 加一道实盘防火墙

写训练型 EA 时,第一道关卡应该是确认它跑在什么账户上。MT5 标准库里的 CAccountInfo 类能直接拉取账号、账户模式、交易权限等关键字段,比手动调 AccountInfoInteger 更省事。 下面这段初始化逻辑,会在 OnInit 里先取 login,再判断 TradeMode。一旦识别到 ACCOUNT_TRADE_MODE_REAL,立刻弹窗并 return(-1) 终止 EA——这对防止训练代码误上实盘很有用,外汇和贵金属实盘波动剧烈,误触发下单可能造成真实亏损。 除实盘拦截外,代码还顺手打印了 TradeAllowed、TradeExpert、LimitOrders 以及 Balance/Profit/Equity。把这段塞进你的 EA,跑起来后翻日志就能快速确认环境是否合规,不用等到报错才发现问题。 别把正态当圣经:账户若用只读的投资人密码登录,TradeAllowed 会返回 false,这时候 EA 不会下单但也不会崩,日志里那句‘Investor password’才是真信号。

MQL5 / C++
class="macro">#include <Trade\AccountInfo.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA交易初始化函数                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 处理帐户的对象
   CAccountInfo account;
class=class="str">"cmt">//--- 接收EA交易系统所运行帐户的账号
   class="type">long login=account.Login();
   Print("Login=",login);
class=class="str">"cmt">//--- 声明帐户类型
   ENUM_ACCOUNT_TRADE_MODE account_type=account.TradeMode();
class=class="str">"cmt">//--- 如果是实盘账户,立即停止EA交易!
   if(account_type==ACCOUNT_TRADE_MODE_REAL)
     {
       MessageBox("Trading on a real account is forbidden, disabling","The Expert Advisor has been launched on a real account!");
       class="kw">return(-class="num">1);
     }
class=class="str">"cmt">//--- 显示帐户类型
   Print("Account type: ",EnumToString(account_type));
class=class="str">"cmt">//--- 声明我们可以在此帐户上进行交易
   if(account.TradeAllowed())
     Print("Trading on this account is allowed");
   else
     Print("Trading on this account is forbidden: you may have entered class="kw">using the Investor password");
class=class="str">"cmt">//--- 声明我们可以在此帐户上用EA交易
   if(account.TradeExpert())
     Print("Automated trading on this account is allowed");
   else
     Print("Automated trading class="kw">using Expert Advisors and scripts on this account is forbidden");
class=class="str">"cmt">//--- 声明已经设置了允许的订单数量
   class="type">int orders_limit=account.LimitOrders();
   if(orders_limit!=class="num">0)Print("Maximum permissible amount of active pending orders: ",orders_limit);
class=class="str">"cmt">//--- 显示公司和服务器名称
   Print(account.Company(),": server ",account.Server());
class=class="str">"cmt">//--- 最后显示帐户的结余和当前利润
   Print("Balance=",account.Balance(),"  Profit=",account.Profit(),"  Equity=",account.Equity());
   Print(__FUNCTION__,"  completed"); class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA交易去初始化函数                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EAtick函数                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
  }

「用 CSymbolInfo 把品种参数一次性读出来」

在 MT5 里下单前,最容易被忽略的一步是确认当前品种的交易约束。CSymbolInfo 这个类把合约大小、点差、止损冻结水平、最小/最大手数等属性封装成方法,初始化时调一次就能拿到全部关键设置。 下面这段 OnInit 里的代码在 EURUSD 上跑通后,终端会打印出类似 Bid=1.08542 Ask=1.08544、Spread=2 pips、LotsMin=0.01 LotsMax=500 的数据,具体数值随经纪商和行情实时变化。 别把正态当圣经 这些属性不是写死的。SpreadFloat 返回 true 说明点差是浮动的,亚盘和重大数据发布时 Spread 可能从 2 跳到 20 以上,挂单距离必须动态读取 StopsLevel 和 FreezeLevel,硬凑固定点数容易直接报『无效止损』。 外汇和贵金属杠杆高、波动剧烈,参数读错可能导致订单被拒或异常平仓。把这段代码贴进 EA 初始化,你就能在日志里直观核对品种规则,再决定后续开仓逻辑怎么写。

MQL5 / C++
class="macro">#include<Trade\SymbolInfo.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA初始化函数                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 接收交易品种设置参数的对象
   CSymbolInfo symbol_info;
class=class="str">"cmt">//--- 为合适的交易品种设置名称
   symbol_info.Name(_Symbol);
class=class="str">"cmt">//--- 接受当前价格相关信息并显示
   symbol_info.RefreshRates();
   Print(symbol_info.Name()," (",symbol_info.Description(),")",
         "  Bid=",symbol_info.Bid(),"  Ask=",symbol_info.Ask());
class=class="str">"cmt">//--- 接收交易操作的最小冻结水平
   Print("StopsLevel=",symbol_info.StopsLevel()," pips, FreezeLevel=",
         symbol_info.FreezeLevel()," pips");
class=class="str">"cmt">//--- 接收小数位数和点值
   Print("Digits=",symbol_info.Digits(),
         ", Point=",DoubleToString(symbol_info.Point(),symbol_info.Digits()));
class=class="str">"cmt">//--- 点差信息
   Print("SpreadFloat=",symbol_info.SpreadFloat(),", Spread(current)=",
         symbol_info.Spread()," pips");
class=class="str">"cmt">//--- 请求限价订单的执行类型
   Print("Limitations for trade operations: ",EnumToString(symbol_info.TradeMode()),
         " (",symbol_info.TradeModeDescription(),")");
class=class="str">"cmt">//--- 声明交易执行模式
   Print("Trades execution mode: ",EnumToString(symbol_info.TradeExecution()),
         " (",symbol_info.TradeExecutionDescription(),")");
class=class="str">"cmt">//--- 声明合约价格计算方法
   Print("Contract price calculation: ",EnumToString(symbol_info.TradeCalcMode()),
         " (",symbol_info.TradeCalcModeDescription(),")");
class=class="str">"cmt">//--- 合约大小
   Print("Standard contract size: ",symbol_info.ContractSize(),
         " (",symbol_info.CurrencyBase(),")");
class=class="str">"cmt">//--- 交易操作的最小和最大交易量
   Print("Volume info: LotsMin=",symbol_info.LotsMin(),"  LotsMax=",symbol_info.LotsMax(),
         "  LotsStep=",symbol_info.LotsStep());
class=class="str">"cmt">//--- 
   Print(__FUNCTION__,"  completed");
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA交易去初始化函数                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//---
  
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA的tick函数                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
  
}

常见问题

先取品种参数与账户状态,再调买入函数并填手数、止损止盈,最后用返回值确认成交,核心就是初始化、发单、校验三类动作。
多半是没读实时账户净值与可用保证金,用CAccountInfo在发单前加一道防火墙判断,可拦掉资金不足的单。
小布可读取你的策略说明与品种页,提示是否缺失账户校验与参数预读,并把重复诊断交给它看盘口。
要在每次下单前刷新一次,品种参数会随行情变,不刷新可能用旧点差算错止损位置。
把品种参数用CSymbolInfo一次性读出存对象,账户检查封装成函数,主逻辑就只剩发单几行,清晰且易改。