MQL5交易操作极简实战指南
用标准库CTrade类轻松实现买卖挂单与持仓管理
为什么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的第一步。
用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);
}
市价买入卖出与多品种处理
最简单的是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,分别操作活动订单、历史订单、持仓、成交和终端数据。熟练后可将主要精力放在策略逻辑。接收信号方面,新手应从移动平均线交叉等指标文章入手,参考官方指标章节。
MQL5语言为算法交易提供无限可能,标准库交易类能节省大量底层时间。复杂事情简单化,是新人快速建立信心的钥匙。不久你会发现,设计机器人比追踪趋势简单得多。