首页 / 交易学院 / 小布知识库 / MQL5交易操作极简实战指南
MQL5交易操作极简实战指南
🤖

MQL5交易操作极简实战指南

用标准库CTrade类轻松实现买卖挂单与持仓管理

MQL5开发 难度 · 入门 2026-03-10 7 分钟阅读
#MQL5#CTrade#交易操作#EA开发

为什么MQL5交易操作比想象中简单

几乎所有交易者进入市场都是为了盈利,但也有人享受交易过程本身。自动化交易系统开发同样能带来乐趣。创建交易机器人像读悬疑小说一样有趣。开发算法时我们必须处理大量技术问题,核心可归纳为三个:交易什么、何时交易、如何交易。前两个涉及品种选择与信号规则,第三个才是编程语言层面的买卖执行。本文聚焦第三问:如何利用MQL5实现交易操作。

MQL5是专为算法交易设计的语言,拥有丰富的订单、持仓和交易请求函数。开发人员无需从零封装底层协议,只需构造请求并发送。真正难点仅在于正确填写请求结构并处理返回结果,标准库已将复杂度封装完毕。

MQL5交易函数与请求结构基础

MQL5中所有交易操作最终都通过OrderSend()或OrderSendAsync()完成。前者同步等待服务器返回,后者异步发送后继续执行。两者都接收MqlTradeRequest结构,该结构包含十余个字段,但并非全部必填,必填项取决于交易操作类型。若填错枚举或留空必填字段,请求会直接报错而不发送。

标准订单类型对应ENUM_ORDER_TYPE枚举:市价BUY/SELL;挂单BUY_STOP/SELL_STOP突破市价一定距离;BUY_LIMIT/SELL_LIMIT回归市价一定距离;以及BUY_STOP_LIMIT/SELL_STOP_LIMIT组合。理解这些类型是编写EA的第一步。

✦ 异步模式适用场景
OrderSendAsync适合高频或需并行处理的其他逻辑,但需自行通过交易事件确认成交;新手建议先用OrderSend同步模式调试。

用CAccountInfo检查交易账户属性

EA启动后首先要确认运行账户环境。CAccountInfo类封装了登录号、账户类型、是否允许交易、是否允许EA、最大挂单数、公司服务器名、结余利润等。实盘上误跑测试策略风险极大,因此可在OnInit中检测ACCOUNT_TRADE_MODE_REAL并终止。

下方代码展示如何包含AccountInfo.mqh并声明对象,在初始化时打印关键信息,若实盘则弹窗禁用。将此类片段加入自己的EA,便于在日志中快速核查账户状态。

#include <Trade\AccountInfo.mqh>

int OnInit()
  {
   CAccountInfo account;
   long login=account.Login();
   Print("Login=",login);
   ENUM_ACCOUNT_TRADE_MODE account_type=account.TradeMode();
   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!");
      return(-1);
     }
   Print("Account type: ",EnumToString(account_type));
   if(account.TradeAllowed()) Print("Trading on this account is allowed");
   else Print("Trading on this account is forbidden: you may have entered using the Investor password");
   if(account.TradeExpert()) Print("Automated trading on this account is allowed");
   else Print("Automated trading using Expert Advisors and scripts on this account is forbidden");
   int orders_limit=account.LimitOrders();
   if(orders_limit!=0)Print("Maximum permissible amount of active pending orders: ",orders_limit);
   Print(account.Company(),": server ",account.Server());
   Print("Balance=",account.Balance(),"  Profit=",account.Profit(),"   Equity=",account.Equity());
   return(0);
  }

用CSymbolInfo获取品种交易参数

下单前必须知道品种的点值、小数位、止损冻结水平、执行模式、合约大小、最小最大手数等。CSymbolInfo类提供Name、RefreshRates、Bid、Ask、StopsLevel、FreezeLevel、Digits、Point、Spread、TradeMode、TradeExecution、ContractSize、LotsMin、LotsMax等方法。多货币EA中务必显式设置Name(_Symbol)或具体符号。

以下代码在OnInit中打印EURUSD类属性。注意StopsLevel和FreezeLevel以点为单位的限制,若挂单或止损距离小于该值会被拒单,这是回测常见的隐形坑。

#include<Trade\SymbolInfo.mqh>

int OnInit()
  {
   CSymbolInfo symbol_info;
   symbol_info.Name(_Symbol);
   symbol_info.RefreshRates();
   Print(symbol_info.Name()," (",symbol_info.Description(),")",
         "  Bid=",symbol_info.Bid(),"   Ask=",symbol_info.Ask());
   Print("StopsLevel=",symbol_info.StopsLevel()," pips, FreezeLevel=",
         symbol_info.FreezeLevel()," pips");
   Print("Digits=",symbol_info.Digits(),
         ", Point=",DoubleToString(symbol_info.Point(),symbol_info.Digits()));
   Print("SpreadFloat=",symbol_info.SpreadFloat(),", Spread(current)=",
         symbol_info.Spread()," pips");
   Print("Limitations for trade operations: ",EnumToString(symbol_info.TradeMode()));
   Print("Trades execution mode: ",EnumToString(symbol_info.TradeExecution()));
   Print("Contract price calculation: ",EnumToString(symbol_info.TradeCalcMode()));
   Print("Standard contract size: ",symbol_info.ContractSize());
   Print("Volume info: LotsMin=",symbol_info.LotsMin(),"  LotsMax=",symbol_info.LotsMax(),
         "  LotsStep=",symbol_info.LotsStep());
   return(0);
  }

CTrade类:一行代码完成交易操作

标准库Trade.mqh中的CTrade类将OrderSend细节隐藏。使用前在全局声明CTrade trade,在OnInit中设置魔术数字、滑点、填充模式、日志等级和同步异步标志。之后Buy、Sell、BuyLimit、SellLimit、BuyStop、SellStop、PositionOpen、PositionClose、PositionModify、OrderModify、OrderDelete等方法均可直接调用。

#include<Trade\Trade.mqh>
CTrade  trade;
int OnInit()
  {
   int MagicNumber=123456;
   trade.SetExpertMagicNumber(MagicNumber);
   int deviation=10;
   trade.SetDeviationInPoints(deviation);
   trade.SetTypeFilling(ORDER_FILLING_RETURN);
   trade.LogLevel(1);
   trade.SetAsyncMode(true);
   return(0);
  }
⚠ 填充模式必须服务器支持
ORDER_FILLING_RETURN等模式需经纪商支持,若不支持会返回无效填充错误,建议用SymbolInfoInteger(SYMBOL_FILLING_MODE)查询后动态设置。

市价买入卖出与多品种处理

最简单的是trade.Buy(0.1)在当前图表品种市价买0.1手。若不指定品种,CTrade默认用图表品种,对单品种策略方便;多货币策略必须明式传参如trade.Buy(0.1,"GBPUSD")。还可带开仓价、SL、TP、备注的完整版本,此时需自行用NormalizeDouble按小数位规范化价格。

if(!trade.Buy(0.1))
     {
      Print("Buy() method failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("Buy() method executed successfully. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }
if(!trade.Buy(0.1,"GBPUSD"))
     {
      Print("Buy() method failed. Return code=",trade.ResultRetcode(),
            ". Code description: ",trade.ResultRetcodeDescription());
     }
   else
     {
      Print("Buy() method executed successfully. Return code=",trade.ResultRetcode(),
            " (",trade.ResultRetcodeDescription(),")");
     }
double volume=0.1;
string symbol="GBPUSD";
int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
double bid=SymbolInfoDouble(symbol,SYMBOL_BID);
double SL=NormalizeDouble(bid-1000*point,digits);
double TP=NormalizeDouble(bid+1000*point,digits);
double open_price=SymbolInfoDouble(symbol,SYMBOL_ASK);
string comment=StringFormat("Buy %s %G lots at %s, SL=%s TP=%s",symbol,volume,DoubleToString(open_price,digits),DoubleToString(SL,digits),DoubleToString(TP,digits));
if(!trade.Buy(volume,symbol,open_price,SL,TP,comment)) Print("failed");

限价单与止损单的下达逻辑

BuyLimit价格须低于市价,用于支撑反弹策略;SellLimit高于市价。BuyStop高于市价突破追入;SellStop低于市价。精简版只传量和价,详细版可带SL、TP、有效期、备注。CTrade会自动校验SL/TP相对于开仓价的方向,错误时在日志提示,回测中极易发现。

string symbol="GBPUSD";
int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
double price=NormalizeDouble(1000*point,digits);
if(!trade.BuyLimit(0.1,price)) Print("BuyLimit failed");
double volume=0.1; string symbol="GBPUSD";
int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
double price=NormalizeDouble(1000*point,digits);
double SL=NormalizeDouble(price-300*point,digits);
double TP=NormalizeDouble(price+500*point,digits);
datetime expiration=TimeTradeServer()+PeriodSeconds(PERIOD_D1);
if(!trade.BuyLimit(volume,price,symbol,SL,TP,ORDER_TIME_GTC,expiration,"Buy Limit")) Print("failed");
string symbol="USDJPY";
int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
double price=NormalizeDouble(1000*point,digits);
if(!trade.BuyStop(0.1,price)) Print("BuyStop failed");

持仓开仓修改与挂单管理

除Buy/Sell外可用PositionOpen开仓并指定类型;平仓用PositionClose(_Symbol);修改持仓仅能改SL/TP,用PositionModify。挂单修改用OrderModify需传原ticket和正确新价;删除用OrderDelete(ticket)。这些ticket可从持仓或订单信息类获取。

int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
double price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
double SL=NormalizeDouble(price-1000*point,digits);
double TP=NormalizeDouble(price+1000*point,digits);
if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,0.1,price,SL,TP,"Buy")) Print("failed");
if(!trade.PositionClose(_Symbol)) Print("Close failed");
int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
double price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
double SL=NormalizeDouble(price-1000*point,digits);
double TP=NormalizeDouble(price+1000*point,digits);
if(!trade.PositionModify(_Symbol,SL,TP)) Print("Modify failed");
ulong ticket=1234556; string symbol="EURUSD";
int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
double price=SymbolInfoDouble(symbol,SYMBOL_ASK);
double SL=NormalizeDouble(price-1000*point,digits);
double TP=NormalizeDouble(price+1000*point,digits);
datetime expiration=TimeTradeServer()+PeriodSeconds(PERIOD_D1);
if(!trade.OrderModify(ticket,price,SL,TP,ORDER_TIME_GTC,expiration)) Print("OrderModify failed");
ulong ticket=1234556;
if(!trade.OrderDelete(ticket)) Print("OrderDelete failed");

辅助信息类与下一步学习路径

除前述三类,标准库还有COrderInfo、CHistoryOrderInfo、CPositionInfo、CDealInfo、CTerminalInfo,分别操作活动订单、历史订单、持仓、成交和终端数据。熟练后可将主要精力放在策略逻辑。接收信号方面,新手应从移动平均线交叉等指标文章入手,参考官方指标章节。

✦ 回测历史损坏错误
有用户反馈回测NZDUSD时日志报历史损坏停止,这通常是本地历史数据缺失或不完整,非代码问题。可通过重新下载该品种历史或换用完整数据源解决。

MQL5语言为算法交易提供无限可能,标准库交易类能节省大量底层时间。复杂事情简单化,是新人快速建立信心的钥匙。不久你会发现,设计机器人比追踪趋势简单得多。

常见问题

全局声明CTrade trade,OnInit中设置魔术数字等参数,然后在OnTick调用trade.Buy(0.1)即可在当前品种市价买0.1手;多品种用trade.Buy(0.1,"GBPUSD")。
这是本地品种历史数据不完整导致,不是EA代码错误。在MT5上方菜单栏选中对应品种右键下载完整历史,或重新同步数据后再跑测试。
BUY_LIMIT价格必须低于当前ASK市价,用于回调买入。用SymbolInfoDouble取点值和小数位,NormalizeDouble规范化后传给trade.BuyLimit(量,价格)。
包含AccountInfo.mqh,声明CAccountInfo对象,在OnInit用account.TradeMode()判断若为ACCOUNT_TRADE_MODE_REAL则return(-1)终止,防止实盘误跑。
用trade.PositionModify(_Symbol,SL,TP)仅能改SL和TP,价格需按品种小数位NormalizeDouble,且距离不能小于StopsLevel冻结水平。