MQL5 标准交易类库入门:用内置类给 EA 开发省掉重复造轮子(基础篇)
(1/3)· 从零认识 Trade 类体系,把账户、订单、持仓的底层访问封装成几行调用
◍ 用标准库写 EA 省在哪
在 MT5 里手搓下单逻辑,光是持仓选择、市价单与挂单的差异就要写一堆容错,新手很容易在品种点值换算上翻车。MQL5 自带的标准交易类库(Trade 和 PositionInfo 等)把这些封装好了,直接调方法就能发单、改仓、查盈亏。 实际跑过的人知道,用 CTrade 类下一单黄金 XAUUSD,0.1 手市价买,核心就三行,比自己拼 MQL5 原生 OrderSend 请求结构少写二十行以上。外汇和贵金属杠杆高,滑点和点值波动大,封装层至少帮你挡掉部分参数拼错导致的异常下单。 类库不是银弹,它默认不替你做资金管理,也不管信号逻辑。你仍要在 OnTick 里自己判断入场,再调用类库方法执行。开 MT5 新建 EA 时勾选「使用标准库」,就能直接 #include <Trade/Trade.mqh> 验证这套路径。
「标准类库藏在 Include 目录里」
MT5 客户端装好后,MQL5 标准类库就静默躺在 MQL5\Include\ 目录下,不打开文件管理器基本不会注意到。里面按功能拆成 Arrays、ChartObjects、Charts、Files、Indicators、Strings、Trade 七个子分类,EA、指标、脚本都能直接 include 调用。 做自动交易最常用的是 Trade 类,它把下单、平仓、改仓这些动作封装成方法,省掉手写交易请求结构的麻烦。本文后续就只围绕内置交易类讲 EA 怎么写,策略逻辑很简单:条件触发后,对已有仓位做平仓或修改。 如果你已经懂类的继承和实例化,直接往下看就行。零基础的话,先去翻官方分步指南和面向对象编程那两篇,不然 Trade 类的成员函数会看得一头雾水。外汇和贵金属杠杆高,跑任何 EA 前都先在策略测试器用历史数据验证。
用标准类接管账户与订单信息读取
MT5 标准库把账户、品种、历史订单、挂单、成交和持仓分别封装成六个类,开发者不必重写底层调用,直接实例化就能拉数据。对外汇与贵金属交易者而言,这意味着 EA 里读取杠杆、点差、保证金和持仓状态可以压缩成几行代码,但外汇贵金属杠杆品种波动剧烈,实盘前务必在策略测试器里验证边界条件。 以 CAccountInfo 为例,先声明对象再调成员函数: CAccountInfo myaccount; double lev = myaccount.Leverage(); bool can_trade = myaccount.TradeAllowed(); 上面两行分别取当前账户杠杆和交易开关,若 TradeAllowed() 返回 false,EA 应直接跳过下单逻辑。MarginCheck() 接收品种、订单类型、手数和价格四个参数,返回开仓所需预付款,是手数风控的核心调用。 CSymbolInfo 解决品种属性读取。创建 mysymbol 后,用 Name() 设定品种,RefreshRates() 拉最新报价,返回 bool 告诉你成功与否;Spread() 给当前点差点数,StopsLevel() 返回止损最低距离点数,挂单或修改止损前不查它就可能报『离市价太近』错误。Digits() 和 Point() 决定价格规范化位数,黄金 XAUUSD 通常 Digits()=2,Point()=0.01。 历史与实时订单分在两个类:CHistoryOrderInfo 用 Ticket(ulong) 选订单号后读 TimeSetup()、State()、PriceOpen();COrderInfo 管挂单,多了 StopLoss()、TakeProfit() 和 StoreState()/CheckState() 这对,能缓存挂单快照再比对改动。CDealInfo 走成交记录,Entry() 区分 DEAL_ENTRY_IN/OUT;CPositionInfo 用 Select(品种) 后取 Time()、Type() 遍历敞口仓。 这套类不暴露内部实现,你只管传参取数。开 MT5 按 F4 建个空白 EA,把 myaccount、mysymbol 对象敲进去,Print 出 Leverage() 和 Spread(),比读文档更快建立手感。
◍ 用 CTrade 把下单逻辑跑通
CTrade 类把 MQL5 里零散的交易函数收拢成一个对象,省去自己拼交易请求的麻烦。先声明 CTrade mytrade;,再设幻数和允许偏离点数,后面所有下单、改单、平仓都走这个对象的方法。
mytrade.Buy(volume, symbol, price, sl, tp) 用当前 Ask 价建多仓,Sell() 对应 Bid 价建空仓;挂单侧有 BuyStop / SellStop / BuyLimit / SellLimit,默认 time 为 ORDER_TIME_GTC、expiration 为 0,和你心意一致时可不填。外汇与贵金属杠杆高,实盘前务必在策略测试器用极小手数验证滑点与离场逻辑。
出错时不要瞎猜。mytrade.ResultRetcode() 给返回码,mytrade.RequestSL() 这类请求函数能把发往服务器的参数打出来。比如收到 Invalid Stops,调 RequestSL() 一看就知道止损价算错在哪。
下方代码演示了账户对象取登录号、交易模式描述与杠杆,并判断账户是否允许 EA 交易——这些在用 CTrade 发单前就该先确认。
class=class="str">"cmt">//--- The AccountInfo Class Object CAccountInfo myaccount; class=class="str">"cmt">// returns account number, example class="num">7770 class="type">long accountno = myaccount.Login() class=class="str">"cmt">// returns Demo trading account, class=class="str">"cmt">// or Real trading account or Contest trading account class="type">class="kw">string acc_trading_mode = myaccount.TradeModeDescription(); class=class="str">"cmt">// returns leverage given to the active account class="type">long acc_leverage = myaccount.Leverage(); if (myaccount.TradeAllowed()) { class=class="str">"cmt">// trade is allowed } else { class=class="str">"cmt">// trade is not allowed } if (myaccount.TradeExpert()) { class=class="str">"cmt">// Expert Advisor trade is allowed } else { class=class="str">"cmt">// Expert Advisor trade is not allowed } class=class="str">"cmt">// returns account balance in the deposit currency class="type">class="kw">double acс_balance = myaccount.Balance(); class=class="str">"cmt">// returns account profit in deposit currency
「账户与品种对象的字段抓取实战」
在 MT5 里做风控前,先把账户和品种的真实状态读出来。下面这段用 CAccountInfo、CSymbolInfo、CHistoryOrderInfo 三个标准类,把浮动盈利、空闲保证金、点差、合约大小等字段一次性拉到本地变量,开 MT5 新建 EA 粘贴就能跑。 double acc_profit = myaccount.Profit(); 取当前账户浮动盈利;myaccount.FreeMargin() 返回可用保证金;myaccount.Currency() 拿到账户结算币种,比如 "USD"。用 myaccount.OrderProfitCheck(_Symbol, ORDER_TYPE_BUY, 1.0, 1.2950, 1.3235) 可以纯计算不下单地预估:EURUSD 在 1.2950 买、1.3235 卖、1 标准手的可能盈利,Print 出来即可核对。 CSymbolInfo 对象必须先 Name(_Symbol) 再 Refresh(),否则 Bid()/Ask() 可能读到旧值。代码里用 mysymbol.VolumeHigh() 取当日最高成交量、mysymbol.Spread() 取点差整数点数、mysymbol.StopsLevel() 取止损最小距离、mysymbol.FreezeLevel() 取冻结点数——这些在贵金属跳空时可能瞬间扩大,外汇和贵金属均属高风险品种,数值仅供参考。 品种选择状态也值得留意:mysymbol.Select(true) 把品种加入市场报价窗口,Select(false) 移除;TradeMode()==SYMBOL_TRADE_MODE_FULL 才代表无交易限制。历史订单用 HistorySelect(0, TimeCurrent()) 拉全量,HistoryOrdersTotal() 给总数,再按 ticket 循环可取每笔明细,用于复盘胜率。
class="type">class="kw">double acc_profit = myaccount.Profit(); class=class="str">"cmt">// returns free margin for active account class="type">class="kw">double acc_free_margin = myaccount.FreeMargin(); class="type">class="kw">double acc_currency = myaccount.Currency(); class="type">class="kw">double op_profit=myaccount.OrderProfitCheck(_Symbol,ORDER_TYPE_BUY,class="num">1.0,class="num">1.2950,class="num">1.3235); Print("The amount of Profit for deal buy EURUSD","at class="num">1.2950 and sell at class="num">1.3235 is: ",op_profit); class=class="str">"cmt">// depending on the type of position to open - in our case buy class="type">class="kw">double price=SymbolInfoDouble(_Symbol,SYMBOL_ASK); class="type">class="kw">double margin_req=myaccount.MarginCheck(_Symbol,ORDER_TYPE_BUY,LOT,price); class="type">class="kw">double acc_fm=myaccount.FreeMarginCheck(_Symbol,ORDER_TYPE_BUY,LOT,price); class="type">class="kw">double max_lot=myaccount.MaxLotCheck(_Symbol,ORDER_TYPE_BUY,price); class=class="str">"cmt">// the CSymbolInfo Class object CSymbolInfo mysymbol; class=class="str">"cmt">// set the symbol name for our CSymbolInfo class Object mysymbol.Name(_Symbol); mysymbol.Refresh(); class=class="str">"cmt">//--- Get the last price quote class="kw">using the CSymbolInfo class=class="str">"cmt">// class object function if (!mysymbol.RefreshRates()) { class=class="str">"cmt">// error getting latest price quotes } class=class="str">"cmt">// check if symbol data are synchronized with server if (!mysymbol.IsSynchronized()) { class=class="str">"cmt">// error! Symbol data aren&class="macro">#x27;t synchronized with server } class="type">long max_vol = mysymbol.VolumeHigh(); class="type">long min_vol = mysymbol.VolumeLow(); class="type">class="kw">datetime qtime = mysymbol.Time(); class="type">int spread = mysymbol.Spread(); class="type">int stp_level = mysymbol.StopsLevel(); class="type">int frz_level = mysymbol.FreezeLevel(); class="type">class="kw">double bid = mysymbol.Bid(); class="type">class="kw">double max_bid = mysymbol.BidHigh(); class="type">class="kw">double min_bid = mysymbol.BidLow(); class="type">class="kw">double ask = mysymbol.Ask(); class="type">class="kw">double max_ask = mysymbol.AskHigh(); class="type">class="kw">double min_ask = mysymbol.AskLow(); class=class="str">"cmt">// returns "USD" for USDJPY or USDCAD class="type">class="kw">string base_currency = mysymbol.CurrencyBase(); class="type">class="kw">double cont_size = mysymbol.ContractSize(); class="type">int s_digits = mysymbol.Digits(); class="type">class="kw">double s_point = mysymbol.Point(); class="type">class="kw">double min_lot = mysymbol.LotsMin(); class="type">class="kw">double max_lot = mysymbol.LotsMax(); class="type">class="kw">double lot_step = mysymbol.LotsStep(); class=class="str">"cmt">// A normalized current Ask price class="type">class="kw">double n_price = mysymbol.NormalizePrice(mysymbol.Ask()); if (mysymbol.Select()) { class=class="str">"cmt">//Symbol successfully selected } else { class=class="str">"cmt">// Symbol could not be selected } if (!mysymbol.Select()) { class=class="str">"cmt">//Symbol not selected, Select the symbol mysymbol.Select(true); } else { class=class="str">"cmt">// Symbol already selected, class=class="str">"cmt">// remove Symbol from market watch window mysymbol.Select(class="kw">false); } class="type">class="kw">double init_margin = mysymbol.MarginInitial(); if (mysymbol.TradeMode() == SYMBOL_TRADE_MODE_FULL) { class=class="str">"cmt">// Full trade allowed for this symbol, class=class="str">"cmt">// no trade restrictions } Print("The trade mode for this symbol is", mysymbol.TradeModeDescription()); class=class="str">"cmt">// The CHistoryOrderInfo Class object CHistoryOrderInfo myhistory; class=class="str">"cmt">//Select all history orders within a time period if (HistorySelect(class="num">0,TimeCurrent())) class=class="str">"cmt">// get all history orders { class=class="str">"cmt">// Get total orders in history class="type">int tot_hist_orders = HistoryOrdersTotal(); class="type">class="kw">ulong h_ticket; class=class="str">"cmt">// Order ticket for (class="type">int j=class="num">0; j<tot_hist_orders; j++) { h_ticket = HistoryOrderGetTicket(j); if (h_ticket>class="num">0) {
用标准类把历史订单和持仓扒干净
MQL5 里 COrderInfo、CDealInfo、CPositionInfo 三个类,是把经纪商返回的原始历史与实时仓位转成可读字段的最直接路径。先给 COrderInfo 喂一个 ticket,就能脱离数组下标去取订单属性。
myhistory.Ticket(h_ticket);
ulong o_ticket = myhistory.Ticket();
上面两行把类对象绑到具体订单号,TimeSetup() 拿到挂单时间,OrderType()==ORDER_TYPE_BUY 判断是市价买,State()==ORDER_STATE_REJECTED 则说明单子被拒、根本没进场。外汇和贵金属杠杆高, rejected 状态在滑点行情里很常见,看到就得查原因而不是当没发生。
批量扫历史时,用 HistorySelect(0, TimeCurrent()) 拉全量,再用 OrdersTotal() 拿总数循环。注意 myorder.Select(OrderGetTicket(j)) 返回真才能安全读字段——TimeDone() 成交时间、Magic() 魔术码、PositionId() 关联仓位、PriceOpen() 开价、StopLoss() / TakeProfit() 防护线,PriceCurrent() 实时价也都在这。
myorder.StoreState() 加 CheckState() 是轻量改动侦测:返回 true 表示订单状态或细节变了,适合做小布这类盯盘机器人的事件触发点。
成交明细走 CDealInfo:HistoryDealGetTicket(j) 取 deal ticket,DEAL_TYPE_BUY 配合 DEAL_ENTRY_IN 能区分是开仓买还是平仓回流;Volume() 和 Price() 就是真实成交量与成交价。
持仓侧 CPositionInfo 用 PositionsTotal() 跑循环,Select(PositionGetSymbol(j)) 或只盯当前图 Select(_Symbol) 都行,Time() 给出仓位建立时间。把这些类接起来,你在 MT5 里就能用几十行把账户全景打印出来验证。
myhistory.Ticket(h_ticket); class="type">class="kw">ulong o_ticket = myhistory.Ticket(); class="type">class="kw">datetime os_time = myhistory.TimeSetup(); if (myhistory.OrderType() == ORDER_TYPE_BUY) { class=class="str">"cmt">// This is a buy order } if(myhistory.State() == ORDER_STATE_REJECTED) { class=class="str">"cmt">// order was rejected, not placed. } class="type">class="kw">datetime ot_done = myhistory.TimeDone(); class="type">long o_magic = myhistory.Magic(); class="type">long o_posid = myhistory.PositionId(); class="type">class="kw">double o_price = myhistory.PriceOpen(); class="type">class="kw">string o_symbol = myhistory.Symbol(); class=class="str">"cmt">// The OrderInfo Class object COrderInfo myorder; class=class="str">"cmt">// Select all history orders within a time period if (HistorySelect(class="num">0,TimeCurrent())) class=class="str">"cmt">// get all history orders { class=class="str">"cmt">// get total orders class="type">int o_total = OrdersTotal(); for (class="type">int j=class="num">0; j<o_total; j++) { if (myorder.Select(OrderGetTicket(j))) { class=class="str">"cmt">// order has been selected and can now be manipulated. } class="type">class="kw">ulong o_ticket = myorder.Ticket(); class="type">class="kw">datetime o_setup = myorder.TimeSetup(); if (myorder.Type() == ORDER_TYPE_BUY_LIMIT) { class=class="str">"cmt">// This is a Buy Limit order, etc } if (myorder.State() == ORDER_STATE_STARTED) { class=class="str">"cmt">// order has been checked class=class="str">"cmt">// and may soon be treated by the broker } class="type">class="kw">datetime ot_done = myorder.TimeDone(); class="type">long o_magic = myorder.Magic(); class="type">long o_posid = myorder.PositionId(); class="type">class="kw">double o_price = myorder.PriceOpen(); class="type">class="kw">double s_loss = myorder.StopLoss(); class="type">class="kw">double t_profit = myorder.TakeProfit(); class="type">class="kw">double cur_price = myorder.PriceCurrent(); class="type">class="kw">string o_symbol = myorder.Symbol(); myorder.StoreState(); if (myorder.CheckState() == true) { class=class="str">"cmt">// Our order status or details have changed } class=class="str">"cmt">// The DealInfo Class object CDealInfo myinfo; if (HistorySelect(class="num">0,TimeCurrent())) { class=class="str">"cmt">// Get total deals in history class="type">int tot_deals = HistoryDealsTotal(); class="type">class="kw">ulong d_ticket; class=class="str">"cmt">// deal ticket for (class="type">int j=class="num">0; j<tot_deals; j++) { d_ticket = HistoryDealGetTicket(j); if (d_ticket>class="num">0) { mydeal.Ticket(d_ticket); class="type">class="kw">ulong deal_ticket = mydeal.Ticket(); class="type">long deal_order_no = mydeal.Order(); class="type">class="kw">datetime d_time = mydeal.Time(); if (mydeal.Type() == DEAL_TYPE_BUY) { class=class="str">"cmt">// This deal was executed as a buy deal type } if (mydeal.Entry() == DEAL_ENTRY_IN) { class=class="str">"cmt">// This was an IN entry deal } class="type">long d_magic = mydeal.Magic(); class="type">long d_post_id = mydeal.PositionId(); class="type">class="kw">double d_price = mydeal.Price(); class="type">class="kw">double d_vol = mydeal.Volume(); class="type">class="kw">string d_symbol = mydeal.Symbol(); class=class="str">"cmt">// The object of the CPositionInfo class CPositionInfo myposition; class="type">int pos_total = PositionsTotal(); for (class="type">int j=class="num">0; j<pos_total; j++) { if (myposition.Select(PositionGetSymbol(j))) { class=class="str">"cmt">// symbol successfully selected, we can now work class=class="str">"cmt">// on the current open position for this symbol } } if (myposition.Select(_Symbol)) { class=class="str">"cmt">// symbol successfully selected, we can now work class=class="str">"cmt">// on the current open position for this symbol } class="type">class="kw">datetime pos_time = myposition.Time();