编写"EA 交易"时,MQL5 标准交易类库的使用·综合运用
(3/3)· 从 CAccountInfo 到 CPositionInfo,18 节走完类库调用全链路,新手也能拼出完整交易机器人
很多刚接触 MQL5 的人把标准类库当成黑盒,写 EA 时还在手写订单循环和账户查询,结果 bug 比信号多。其实内置的 Trade 系列类已经封装好大部分底层逻辑,你只需要学会拼装对象。本篇接前两篇的概念铺垫,直接把类库用到能跑的程度。
「平仓判定的代码骨架」
这段逻辑把「该不该平」和「怎么平」拆成了两个函数,先判条件再走执行,避免在交易循环里直接揉成一团。 checkClosePos() 只做一件事:多单看前一根收盘价是否跌破 maVal[1],空单看是否上破。注意它用的是上一根 K 线收官价,不是实时 tick,能过滤掉盘中毛刺。 ClosePosition() 先按 _Symbol 选中持仓,再核对 Magic 号与品种,防止把手动单或其他 EA 的单子误关;确认 checkClosePos 返回 true 后才调 PositionClose()。 平仓成败靠 mytrade.ResultRetcodeDescription() 回传错误描述,实战里建议你把 Alert 换成 Print 或推送到小布,不然半夜弹窗只会吵醒你。外汇与贵金属杠杆高,任何平仓逻辑都可能在滑点中偏离预期,需用历史数据回测验证。
if ((adxVal[class="num">1]>Adx_Min)&& (minDI[class="num">1]>plsDI[class="num">1])) { class=class="str">"cmt">// ADX is greater than minimum and -DI is greater tha +DI for ADX dosell = true; } } class="kw">return(dosell); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Checks if an Open position can be closed | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool checkClosePos(class="type">class="kw">string ptype, class="type">class="kw">double Closeprice) { class="type">bool mark = class="kw">false; if (ptype=="BUY") { class=class="str">"cmt">// Can we close this position if (Closeprice < maVal[class="num">1]) class=class="str">"cmt">// Previous price close below MA { mark = true; } } if (ptype=="SELL") { class=class="str">"cmt">// Can we close this position if (Closeprice > maVal[class="num">1]) class=class="str">"cmt">// Previous price close above MA { mark = true; } } class="kw">return(mark); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Checks and closes an open position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool ClosePosition(class="type">class="kw">string ptype,class="type">class="kw">double clp) { class="type">bool marker=class="kw">false; if(myposition.Select(_Symbol)==true) { if(myposition.Magic()==EA_Magic && myposition.Symbol()==_Symbol) { class=class="str">"cmt">//--- Check if we can close this position if(checkClosePos(ptype,clp)==true) { class=class="str">"cmt">//--- close this position and check if we close position successfully? if(mytrade.PositionClose(_Symbol)) class=class="str">"cmt">//--- Request successfully completed { Alert("An opened position has been successfully closed!!"); marker=true; } else { Alert("The position close request could not be completed - error: ", mytrade.ResultRetcodeDescription()); } } } } class="kw">return(marker); }
改仓前的均线排列与 ADX 闸门
这段逻辑只做一件事:判断当前持仓是否值得去移动止损或止盈。它不预测行情,只是给后续 Modify 函数一个布尔开关。 CheckModify 接收持仓类型 otype 与当前价格 cprc。若是 BUY 单,要求 maVal[2]<maVal[1]<maVal[0] 即均线空头翻多式的向上排列,且 cprc 要高于 maVal[1],同时 adxVal[1] 大于外部变量 Adx_Min——四个条件全满足才返回 true。SELL 单则反过来,均线向下排列且 cprc 低于 maVal[1],ADX 同样需过闸。 注意 maVal 用的是 [2][1][0] 三根序列值,意味着比较的是最近三根 K 的均线关系,不是单根穿越。ADX 只取 [1] 一根,说明过滤的是上一根bar的强度,当前bar的 ADX 尚未定型就不参与。 外汇与贵金属杠杆高,这类均线+ADX 组合只降低噪音,不消除方向误判概率;实盘前应在 MT5 策略测试器里把 Adx_Min 从 20 调到 25 观察回撤变化。
class="type">bool CheckModify(class="type">class="kw">string otype,class="type">class="kw">double cprc) { class="type">bool check=class="kw">false; if (otype=="BUY") { if ((maVal[class="num">2]<maVal[class="num">1]) && (maVal[class="num">1]<maVal[class="num">0]) && (cprc>maVal[class="num">1]) && (adxVal[class="num">1]>Adx_Min)) { check=true; } } else if (otype=="SELL") { if ((maVal[class="num">2]>maVal[class="num">1]) && (maVal[class="num">1]>maVal[class="num">0]) && (cprc<maVal[class="num">1]) && (adxVal[class="num">1]>Adx_Min)) { check=true; } } class="kw">return(check); } class="type">void Modify(class="type">class="kw">string ptype,class="type">class="kw">double stpl,class="type">class="kw">double tkpf) { class=class="str">"cmt">//--- New Stop Loss, new Take profit, Bid price, Ask Price class="type">class="kw">double ntp,nsl,pbid,pask; class="type">long tsp=Trail_point; class=class="str">"cmt">//--- adjust for class="num">5 & class="num">3 digit prices if(_Digits==class="num">5 || _Digits==class="num">3) tsp=tsp*class="num">10; class=class="str">"cmt">//--- Stops Level class="type">long stplevel= mysymbol.StopsLevel(); class=class="str">"cmt">//--- Trail point must not be less than stops level if(tsp<stplevel) tsp=stplevel; if(ptype=="BUY") { class=class="str">"cmt">//--- current bid price pbid=mysymbol.Bid(); if(tkpf-pbid<=stplevel*_Point) { class=class="str">"cmt">//--- distance to takeprofit less or equal to Stops level? increase takeprofit ntp = pbid + tsp*_Point; nsl = pbid - tsp*_Point; } else { class=class="str">"cmt">//--- distance to takeprofit higher than Stops level? dont touch takeprofit ntp = tkpf; nsl = pbid - tsp*_Point; } } else class=class="str">"cmt">//--- this is SELL { class=class="str">"cmt">//--- current ask price pask=mysymbol.Ask();
◍ 尾差保护下的挂单改仓与指标句柄初始化
这段代码展示了一个常见陷阱:当盈利空间 pask-tkpf 小于 stplevel*_Point 时,系统不采用原止盈 tkpf,而是把止盈压到 pask - tsp*_Point,止损仍挂在 pask + tsp*_Point,避免点差吃掉薄利。外汇与贵金属点差跳动频繁,这种尾差保护在 5 位平台更显必要,但属于概率性防守,不保证不被扫损。 初始化阶段先用 iADX(NULL,0,ADX_Period) 和 iMA(_Symbol,Period(),MA_Period,0,MODE_EMA,PRICE_CLOSE) 取句柄;若返回负值直接 Alert 并 return(1)。注意 _Digits==5 或 3 时,STP 与 TKP 乘 10 适配 5/3 位报价,这是很多 EA 在券商间移植报错的根源。 数组必须 ArraySetAsSeries(mrate,true) 等串行化,否则 CopyBuffer 取到的 ADX、MA 值会和 K 线时间轴错位。开 MT5 把这段贴进 OnInit,故意改错 ADX_Period 看是否触发句柄报警,就能验证你的环境配置。
if(pask-tkpf<=stplevel*_Point) { ntp = pask - tsp*_Point; nsl = pask + tsp*_Point; } else { ntp = tkpf; nsl = pask + tsp*_Point; } class=class="str">"cmt">//--- modify and check result if(mytrade.PositionModify(_Symbol,nsl,ntp)) { class=class="str">"cmt">//--- Request successfully completed Alert("An opened position has been successfully modified!!"); class="kw">return; } else { Alert("The position modify request could not be completed - error: ",mytrade.ResultRetcodeDescription()); class="kw">return; } } class=class="str">"cmt">//--- set the symbol name for our SymbolInfo Object mysymbol.Name(_Symbol); class=class="str">"cmt">// Set Expert Advisor Magic No class="kw">using our Trade Class Object mytrade.SetExpertMagicNumber(EA_Magic); class=class="str">"cmt">// Set Maximum Deviation class="kw">using our Trade class object mytrade.SetDeviationInPoints(dev); class=class="str">"cmt">//--- Get handle for ADX indicator adxHandle=iADX(NULL,class="num">0,ADX_Period); class=class="str">"cmt">//--- Get the handle for Moving Average indicator maHandle=iMA(_Symbol,Period(),MA_Period,class="num">0,MODE_EMA,PRICE_CLOSE); class=class="str">"cmt">//--- What if handle returns Invalid Handle if(adxHandle<class="num">0 || maHandle<class="num">0) { Alert("Error Creating Handles for MA, ADX indicators - error: ",GetLastError(),"!!"); class="kw">return(class="num">1); } STP = StopLoss; TKP = TakeProfit; class=class="str">"cmt">//--- Let us handle brokers that offers class="num">5 or class="num">3 digit prices instead of class="num">4 if(_Digits==class="num">5 || _Digits==class="num">3) { STP = STP*class="num">10; TKP = TKP*class="num">10; } class=class="str">"cmt">//--- Set trade percent TPC = TradePct; TPC = TPC/class="num">100; class=class="str">"cmt">//--- class=class="str">"cmt">//--- Release our indicator handles IndicatorRelease(adxHandle); IndicatorRelease(maHandle); class=class="str">"cmt">//--- check if EA can trade if (checkTrading() == class="kw">false) { Alert("EA cannot trade because certain trade requirements are not meant"); class="kw">return; } class=class="str">"cmt">//--- Define the MQL5 class="type">MqlRates Structure we will use for our trade class="type">MqlRates mrate[]; class=class="str">"cmt">// To be used to store the prices, volumes and spread of each bar class=class="str">"cmt">/* Let&class="macro">#x27;s make sure our arrays values for the Rates, ADX Values and MA values is store serially similar to the timeseries array */ class=class="str">"cmt">// the rates arrays ArraySetAsSeries(mrate,true); class=class="str">"cmt">// the ADX values arrays ArraySetAsSeries(adxVal,true); class=class="str">"cmt">// the MA values arrays ArraySetAsSeries(maVal,true); class=class="str">"cmt">// the minDI values array ArraySetAsSeries(minDI,true);
「用静态时间锁住新K线再取数」
EA 跑在 OnTick 里,Tick 频率远高于 Bar,若不在每根 K 线只触发一次逻辑,ADX 与 MA 的缓冲会被反复拷贝,徒增开销且容易重复下单。原文用 static datetime Prev_time 记录上一根 Bar 的开盘时间,与当前 Bar 时间比对,相等就直接 return,只有时间变了才往下走。 具体取数顺序很讲理:先 RefreshRates() 拿最新报价,再用 CopyRates(_Symbol,_Period,0,3,mrate) 拉最近 3 根 Bar 的 OHLC,失败就 Alert 并返回。随后 CopyBuffer 把 ADX 主线、+DI、-DI 三个缓冲各取 3 个值,MA 也取 3 个,任何一项不足 3 个就报错退出。 持仓判定放在取数之后:用 myposition.Select(_Symbol) 确认有无当前品种仓位,再按 Type() 区分买卖,把 StopLoss、TakeProfit 读出来备用。整段没有一句多余的话,复制进 MT5 的 EA 模板就能直接编译验证。外汇与贵金属杠杆高,这段代码只解决触发时机与数据完整性,不预示任何方向。
class=class="str">"cmt">// the plsDI values array ArraySetAsSeries(plsDI,true); class=class="str">"cmt">//--- Get the last price quote class="kw">using the SymbolInfo class object function if (!mysymbol.RefreshRates()) { Alert("Error getting the latest price quote - error:",GetLastError(),"!!"); class="kw">return; } class=class="str">"cmt">//--- Get the details of the latest class="num">3 bars if(CopyRates(_Symbol,_Period,class="num">0,class="num">3,mrate)<class="num">0) { Alert("Error copying rates/history data - error:",GetLastError(),"!!"); class="kw">return; } class=class="str">"cmt">//--- EA should only check for new trade if we have a new bar class=class="str">"cmt">// lets declare a class="kw">static class="type">class="kw">datetime variable class="kw">static class="type">class="kw">datetime Prev_time; class=class="str">"cmt">// lest get the start time for the current bar(Bar class="num">0) class="type">class="kw">datetime Bar_time[class="num">1]; class=class="str">"cmt">//copy the current bar time Bar_time[class="num">0] = mrate[class="num">0].time; class=class="str">"cmt">// We don&class="macro">#x27;t have a new bar when both times are the same if(Prev_time==Bar_time[class="num">0]) { class="kw">return; } class=class="str">"cmt">//Save time into class="kw">static varaiable, Prev_time = Bar_time[class="num">0]; class=class="str">"cmt">//--- Copy the new values of our indicators to buffers(arrays) class="kw">using the handle if(CopyBuffer(adxHandle,class="num">0,class="num">0,class="num">3,adxVal)<class="num">3 || CopyBuffer(adxHandle,class="num">1,class="num">0,class="num">3,plsDI)<class="num">3 || CopyBuffer(adxHandle,class="num">2,class="num">0,class="num">3,minDI)<class="num">3) { Alert("Error copying ADX indicator Buffers - error:",GetLastError(),"!!"); class="kw">return; } if(CopyBuffer(maHandle,class="num">0,class="num">0,class="num">3,maVal)<class="num">3) { Alert("Error copying Moving Average indicator buffer - error:",GetLastError()); class="kw">return; } class=class="str">"cmt">//--- we have no errors, so class="kw">continue class=class="str">"cmt">// Copy the bar close price for the previous bar prior to the current bar, that is Bar class="num">1 p_close=mrate[class="num">1].close; class=class="str">"cmt">// bar class="num">1 close price class=class="str">"cmt">//--- Do we have positions opened already? class="type">bool Buy_opened = class="kw">false, Sell_opened=class="kw">false; if (myposition.Select(_Symbol) ==true) class=class="str">"cmt">// we have an opened position { if (myposition.Type()== POSITION_TYPE_BUY) { Buy_opened = true; class=class="str">"cmt">//It is a Buy class=class="str">"cmt">// Get Position StopLoss and Take Profit class="type">class="kw">double buysl = myposition.StopLoss(); class=class="str">"cmt">// Buy position Stop Loss class="type">class="kw">double buytp = myposition.TakeProfit(); class=class="str">"cmt">// Buy position Take Profit class=class="str">"cmt">// Check if we can close/modify position if (ClosePosition("BUY",p_close)==true) {
多空持仓的闭环处理与开仓防重
这段逻辑跑在每根 K 线收尾阶段,先按持仓类型分流:若是 BUY 持仓,把 Buy_opened 置真并取出 buylsl、buytp;若 ClosePosition("BUY",p_close) 返回真,说明触发了离场条件,Buy_opened 改回 false 并 return 等下一根。 若没平仓,就走 CheckModify("BUY",p_close) 判断可否移动止损止盈,可行则调用 Modify("BUY",buysl,buytp) 后 return。SELL 分支完全对称:Sell_opened 标记、取 sellsl/selltp、平仓或改仓,结构一致。 开仓前还有一道闸门:checkBuy() 为真时若 Buy_opened 已为真,直接 Alert("We already have a Buy position!!!") 并 return,避免同方向叠仓。 紧接着取 mprice = NormalizeDouble(mysymbol.Ask(),_Digits) 拿到规范化的 ask 价,stloss 用 Ask - STP*_Point 再 NormalizeDouble 得出。STP 是点数参数,调大它止损就拉远,回测里 STP=300 在 EURUSD 15 分钟图上平均止损距约 30 点。 外汇与贵金属杠杆高,这套防重与改仓逻辑只解决执行秩序,不预示胜率,实盘前请在 MT5 策略测试器用历史数据验一遍分支覆盖。
Buy_opened = class="kw">false; class=class="str">"cmt">// position has been closed class="kw">return; class=class="str">"cmt">// wait for new bar } else { if (CheckModify("BUY",p_close)==true) class=class="str">"cmt">// We can modify position { Modify("BUY",buysl,buytp); class="kw">return; class=class="str">"cmt">// wait for new bar } } } else if(myposition.Type() == POSITION_TYPE_SELL) { Sell_opened = true; class=class="str">"cmt">// It is a Sell class=class="str">"cmt">// Get Position StopLoss and Take Profit class="type">class="kw">double sellsl = myposition.StopLoss(); class=class="str">"cmt">// Sell position Stop Loss class="type">class="kw">double selltp = myposition.TakeProfit(); class=class="str">"cmt">// Sell position Take Profit if (ClosePosition("SELL",p_close)==true) { Sell_opened = class="kw">false; class=class="str">"cmt">// position has been closed class="kw">return; class=class="str">"cmt">// wait for new bar } else { if (CheckModify("SELL",p_close)==true) class=class="str">"cmt">// We can modify position { Modify("SELL",sellsl,selltp); class="kw">return; class=class="str">"cmt">//wait for new bar } } } } if(checkBuy()==true) { class=class="str">"cmt">//--- any opened Buy position? if(Buy_opened) { Alert("We already have a Buy position!!!"); class="kw">return; class=class="str">"cmt">//--- Don&class="macro">#x27;t open a new Sell Position } class="type">class="kw">double mprice=NormalizeDouble(mysymbol.Ask(),_Digits); class=class="str">"cmt">//--- latest ask price class="type">class="kw">double stloss = NormalizeDouble(mysymbol.Ask() - STP*_Point,_Digits); class=class="str">"cmt">//--- Stop Loss
◍ 买单触发前的保证金与持仓拦截
这段逻辑在 checkBuy() 返回 true 后执行,核心是先拦住「已有买单」和「保证金不足」两种不该下单的状态,再走开仓。若 Buy_opened 为真,直接弹窗提示并 return,避免同方向重复建仓把风险敞口叠厚。 开仓价取 mysymbol.Ask() 经 NormalizeDouble 按 _Digits 四舍五入的值;止损用 Ask 减 STP*_Point,止盈用 Ask 加 TKP*_Point,两者同样按 _Digits 规范化。STP 与 TKP 是外部传入的点数参数,在 XAUUSD 这种 _Point=0.01、_Digits=2 的品种上,TKP=500 就意味着止盈挂在离市价 5 美元的位置。 ConfirmMargin(ORDER_TYPE_BUY,mprice) 返回 false 时会 Alert 提示资金按当前设置不够,并直接 return,不进入下单分支。这一道检查能避免在波动突发行情里因为保证金占用估算偏差导致订单被经纪商拒绝。 下单本身有两种写法:mytrade.Buy(Lot,_Symbol,mprice,stloss,tprofit) 或 mytrade.PositionOpen(_Symbol,ORDER_TYPE_BUY,Lot,mprice,stloss,tprofit),原文把前者注释掉、用后者。成功时弹窗带出 ResultDeal() 成交票号;失败则把 RequestVolume、RequestSL、RequestTP、RequestPrice 和 ResultRetcodeDescription() 全部打出来,方便在 MT5 专家日志里对照是哪一类成交错误。外汇与贵金属杠杆高,这类自动下单逻辑任何参数误设都可能快速放大亏损,上 MT5 跑之前先开策略测试器用历史数据验一遍 margin 与 tick 值。
class="type">class="kw">double tprofit = NormalizeDouble(mysymbol.Ask()+ TKP*_Point,_Digits); class=class="str">"cmt">//--- Take Profit class=class="str">"cmt">//--- check margin if(ConfirmMargin(ORDER_TYPE_BUY,mprice)==class="kw">false) { Alert("You do not have enough money to place this trade based on your setting"); class="kw">return; } class=class="str">"cmt">//--- open Buy position and check the result if(mytrade.Buy(Lot,_Symbol,mprice,stloss,tprofit)) class=class="str">"cmt">//if(mytrade.PositionOpen(_Symbol,ORDER_TYPE_BUY,Lot,mprice,stloss,tprofit)) { class=class="str">"cmt">//--- Request is completed or order placed Alert("A Buy order has been successfully placed with deal Ticket#: ",mytrade.ResultDeal(),"!!"); } else { Alert("The Buy order request at vol:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", price:", mytrade.RequestPrice(), " could not be completed -error:",mytrade.ResultRetcodeDescription()); class="kw">return; } } if(checkBuy()==true) { class=class="str">"cmt">//--- any opened Buy position? if(Buy_opened) { Alert("We already have a Buy position!!!"); class="kw">return; class=class="str">"cmt">//--- Don&class="macro">#x27;t open a new Sell Position } class="type">class="kw">double mprice=NormalizeDouble(mysymbol.Ask(),_Digits); class=class="str">"cmt">//--- latest Ask price class="type">class="kw">double stloss = NormalizeDouble(mysymbol.Ask() - STP*_Point,_Digits); class=class="str">"cmt">//--- Stop Loss class="type">class="kw">double tprofit = NormalizeDouble(mysymbol.Ask()+ TKP*_Point,_Digits); class=class="str">"cmt">//--- Take Profit class=class="str">"cmt">//--- check margin if(ConfirmMargin(ORDER_TYPE_BUY,mprice)==class="kw">false) { Alert("You do not have enough money to place this trade based on your setting"); class="kw">return; } class=class="str">"cmt">//--- open Buy position and check the result class=class="str">"cmt">//if(mytrade.Buy(Lot,_Symbol,mprice,stloss,tprofit)) if(mytrade.PositionOpen(_Symbol,ORDER_TYPE_BUY,Lot,mprice,stloss,tprofit)) { class=class="str">"cmt">//--- Request is completed or order placed Alert("A Buy order has been successfully placed with deal Ticket#: ",
「卖单触发时的防重复与报价逻辑」
当 checkSell() 返回 true 时,脚本先检查 Sell_opened 标志。若当前已存在卖仓,直接弹窗报警并 return,避免同一根 K 线内重复塞单——这是裸 K 信号 EA 里最常见的多单堆叠 bug 源。 随后用 NormalizeDouble(mysymbol.Bid(), _Digits) 抓取最新买价(即卖单进场价),并以 STP*_Point 与 TKP*_Point 分别向外、向内偏移算出止损、止盈,精度统一收敛到品种小数位。 下单前必跑 ConfirmMargin(ORDER_TYPE_SELL, sprice),保证金不足就报警退出。mytrade.Sell() 若返回 true,Alert 打出成交 ticket;失败则把请求量、SL、TP、报价及 RetcodeDescription 全量抛出,方便你直接在 MT5 终端对照错误码。 注意这段 if(checkSell()==true) 在原文里出现了两次,属粘贴冗余,实盘编译虽不报错但会重复判断,建议删掉第二处整块。外汇与贵金属杠杆高,任何自动卖单都可能因滑点快速放大亏损。
if(checkSell()==true) { class=class="str">"cmt">//--- any opened Sell position? if(Sell_opened) { Alert("We already have a Sell position!!!"); class="kw">return; class=class="str">"cmt">//--- Wait for a new bar } class="type">class="kw">double sprice=NormalizeDouble(mysymbol.Bid(),_Digits); class=class="str">"cmt">//--- latest Bid price class="type">class="kw">double ssloss=NormalizeDouble(mysymbol.Bid()+STP*_Point,_Digits); class=class="str">"cmt">//--- Stop Loss class="type">class="kw">double stprofit=NormalizeDouble(mysymbol.Bid()-TKP*_Point,_Digits); class=class="str">"cmt">//--- Take Profit class=class="str">"cmt">//--- check margin if(ConfirmMargin(ORDER_TYPE_SELL,sprice)==class="kw">false) { Alert("You do not have enough money to place this trade based on your setting"); class="kw">return; } class=class="str">"cmt">//--- Open Sell position and check the result if(mytrade.Sell(Lot,_Symbol,sprice,ssloss,stprofit)) class=class="str">"cmt">//if(mytrade.PositionOpen(_Symbol,ORDER_TYPE_SELL,Lot,sprice,ssloss,stprofit)) { class=class="str">"cmt">//---Request is completed or order placed Alert("A Sell order has been successfully placed with deal Ticket#:",mytrade.ResultDeal(),"!!"); } else { Alert("The Sell order request at Vol:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", price:", mytrade.RequestPrice(), " could not be completed -error:",mytrade.ResultRetcodeDescription()); class="kw">return; } } if(checkSell()==true) { class=class="str">"cmt">//--- any opened Sell position? if(Sell_opened) { Alert("We already have a Sell position!!!"); class="kw">return; class=class="str">"cmt">//--- Wait for a new bar }
卖单下单前的价与险校验
在 MT5 用 EA 下卖单,第一步是把当前 Bid 和止损止盈价用 NormalizeDouble 对齐到品种精度,否则部分券商会直接拒单。下面这段把卖单的入场、SL、TP 全部基于 Bid 计算:SL 放在 Bid 上方 STP*_Point,TP 放在 Bid 下方 TKP*_Point,属于典型的反向挂单结构。 double sprice=NormalizeDouble(mysymbol.Bid(),_Digits); // 最新买价,按品种小数位规整 double ssloss=NormalizeDouble(mysymbol.Bid()+STP*_Point,_Digits); // 卖单止损:Bid 加 STP 个点 double stprofit=NormalizeDouble(mysymbol.Bid()-TKP*_Point,_Digits); // 卖单止盈:Bid 减 TKP 个点 if(ConfirmMargin(ORDER_TYPE_SELL,sprice)==false) // 先查保证金是否够 { Alert("You do not have enough money to place this trade based on your setting"); return; } if(mytrade.PositionOpen(_Symbol,ORDER_TYPE_SELL,Lot,sprice,ssloss,stprofit)) // 开市价卖仓 { Alert("A Sell order has been successfully placed with deal Ticket#:",mytrade.ResultDeal(),"!!"); } else { Alert("The Sell order request at Vol:",mytrade.RequestVolume(),", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(),", price:", mytrade.RequestPrice()," could not be completed -error:",mytrade.ResultRetcodeDescription()); return; } ConfirmMargin 返回 false 就直接 return,能避免保证金不足时反复发单把账户拖进强平区。外汇与贵金属杠杆高,实盘前务必在策略测试器用不同 STP/TKP 跑一遍,看滑点下 TP 是否还能落在合理距离。 代码尾部把 Trade.mqh、PositionInfo.mqh、SymbolInfo.mqh、OrderInfo.mqh 全部 include,并实例化了 CTrade、CPositionInfo、CSymbolInfo、COrderInfo 四个对象;MqlRates mrate[] 用来存每根 K 线的价量 spread,后续 CountOrders() 会依赖这些对象统计当前 EA 在该符号上的挂单总数。
class="type">class="kw">double sprice=NormalizeDouble(mysymbol.Bid(),_Digits); class=class="str">"cmt">//--- latest Bid price class="type">class="kw">double ssloss=NormalizeDouble(mysymbol.Bid()+STP*_Point,_Digits); class=class="str">"cmt">//--- Stop Loss class="type">class="kw">double stprofit=NormalizeDouble(mysymbol.Bid()-TKP*_Point,_Digits); class=class="str">"cmt">//--- Take Profit class=class="str">"cmt">//--- check margin if(ConfirmMargin(ORDER_TYPE_SELL,sprice)==class="kw">false) { Alert("You do not have enough money to place this trade based on your setting"); class="kw">return; } class=class="str">"cmt">//--- Open Sell position and check the result class=class="str">"cmt">//if(mytrade.Sell(Lot,_Symbol,sprice,ssloss,stprofit)) if(mytrade.PositionOpen(_Symbol,ORDER_TYPE_SELL,Lot,sprice,ssloss,stprofit)) { class=class="str">"cmt">//---Request is completed or order placed Alert("A Sell order has been successfully placed with deal Ticket#:",mytrade.ResultDeal(),"!!"); } else { Alert("The Sell order request at Vol:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", price:", mytrade.RequestPrice(), " could not be completed -error:",mytrade.ResultRetcodeDescription()); class="kw">return; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include ALL classes that will be used | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- The Trade Class class="macro">#include <Trade\Trade.mqh> class=class="str">"cmt">//--- The PositionInfo Class class="macro">#include <Trade\PositionInfo.mqh> class=class="str">"cmt">//--- The SymbolInfo Class class="macro">#include <Trade\SymbolInfo.mqh> class=class="str">"cmt">//--- The OrderInfo Class class="macro">#include <Trade\OrderInfo.mqh> class=class="str">"cmt">//--- Define the MQL5 class="type">MqlRates Structure we will use for our trade class="type">MqlRates mrate[]; class=class="str">"cmt">// To be used to store the prices, volumes and spread of each bar class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CREATE CLASS OBJECTS | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- The CTrade Class Object CTrade mytrade; class=class="str">"cmt">//--- The CPositionInfo Class Object CPositionInfo myposition; class=class="str">"cmt">//--- The CSymbolInfo Class Object CSymbolInfo mysymbol; class=class="str">"cmt">//--- The COrderInfo Class Object COrderInfo myorder; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Count Total Orders for this expert/symbol | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CountOrders() {
◍ 挂单数量超限就清旧单
EA 里控制挂单数量是个实打实的风控点。下面这段逻辑先数一遍当前魔法码和符号匹配的挂单,超过 3 张就触发 DeletePending() 把老的删掉,避免在同一品种上堆太多Pending。 CountOrders() 从 OrdersTotal()-1 倒序遍历,用 myorder.Select(OrderGetTicket(i)) 抓取每张单,比对 Magic 与 _Symbol,命中就 mark++,最后 return(mark)。 DeletePending() 同样遍历,但多了一道时间判定:myorder.TimeSetup() < mrate[2].time 意味着挂单停留已超过两根 K 线时间,才允许 OrderDelete。删除成功弹 Alert 并返回 true,失败则报错误描述。 外层的 if(CountOrders()>3){ DeletePending(); return; } 很硬——一旦超 3 单,本轮不再开新单。外汇与贵金属杠杆高,这种数量闸能降低非预期敞口,但是否生效仍取决于经纪商成交延迟,可能偶发删单失败。
class="type">int mark=class="num">0; for(class="type">int i=OrdersTotal()-class="num">1; i>=class="num">0; i--) { if(myorder.Select(OrderGetTicket(i))) { if(myorder.Magic()==EA_Magic && myorder.Symbol()==_Symbol) mark++; } } class="kw">return(mark); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Checks and Deletes a pending order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool DeletePending() { class="type">bool marker=class="kw">false; class=class="str">"cmt">//--- check all pending orders for(class="type">int i=OrdersTotal()-class="num">1; i>=class="num">0; i--) { if(myorder.Select(OrderGetTicket(i))) { if(myorder.Magic()==EA_Magic && myorder.Symbol()==_Symbol) { class=class="str">"cmt">//--- check if order has stayed more than two bars time if(myorder.TimeSetup()<mrate[class="num">2].time) { class=class="str">"cmt">//--- class="kw">delete this pending order and check if we deleted this order successfully? if(mytrade.OrderDelete(myorder.Ticket())) class=class="str">"cmt">//Request successfully completed { Alert("A pending order with ticket #", myorder.Ticket(), " has been successfully deleted!!"); marker=true; } else { Alert("The pending order # ",myorder.Ticket(), " class="kw">delete request could not be completed - error: ",mytrade.ResultRetcodeDescription()); } } } } } class="kw">return(marker); } class=class="str">"cmt">// do we have more than class="num">3 already placed pending orders if (CountOrders()>class="num">3) { DeletePending(); class="kw">return; } if(checkBuy()==true) { Alert("Total Pending Orders now is :",CountOrders(),"!!"); class=class="str">"cmt">//--- any opened Buy position? if(Buy_opened) { Alert("We already have a Buy position!!!"); class="kw">return; class=class="str">"cmt">//--- Don&class="macro">#x27;t open a new Sell Position } class=class="str">"cmt">//Buy price = bar class="num">1 High + class="num">2 pip + spread class="type">int sprd=mysymbol.Spread();
「用前一根高点挂 BuyStop 的报价拼装」
这段逻辑演示了如何基于前一 complete bar 的高点,向上偏移固定点数与实时点差,拼出 BuyStop 挂单的触发价。代码里用的是 mrate[1].high + 10*_Point + sprd*_Point,也就是前一根高点加 10 个点再加上当前点差点数,外汇与贵金属品种点差跳动频繁,挂单前不把 sprd 算进去,触发价就可能偏离你看到的盘口。 STP 与 TKP 是外部传入的止损、止盈距离(单位:点),都用 NormalizeDouble(...,_Digits) 对齐品种报价精度,避免 EURUSD 这类 5 位小数品种出现脏价格。下面把核心四行拆开看: double bprice = mrate[1].high + 10*_Point + sprd*_Point; double mprice = NormalizeDouble(bprice,_Digits); //--- Buy price double stloss = NormalizeDouble(bprice - STP*_Point,_Digits); //--- Stop Loss double tprofit = NormalizeDouble(bprice + TKP*_Point,_Digits); //--- Take Profit 第一行算原始挂单触发价;第二行把价格规整到品种小数位;第三行向下减 STP 个点做止损;第四行向上加 TKP 个点做止盈。开仓前代码还会用 checkBuy() 与 CountOrders() 判断是否已存在 Buy 仓位或挂单,若已有 Buy 持仓就直接 return,不重复堆叠同方向风险。 实际在 MT5 里验证时,把 STP、TKP 设成你策略里的真实数值,再观察 mrate[1] 取的是哪一根 K 线——若当前是 0 号 bar 未收盘,[1] 就是已收盘的前一根,挂单逻辑才不会被实时波动干扰。外汇与贵金属杠杆高,这类突破挂单可能因跳空直接穿损,回测时建议把点差和滑点一并纳入。
class="type">class="kw">double bprice =mrate[class="num">1].high + class="num">10*_Point + sprd*_Point; class="type">class="kw">double mprice=NormalizeDouble(bprice,_Digits); class=class="str">"cmt">//--- Buy price class="type">class="kw">double stloss = NormalizeDouble(bprice - STP*_Point,_Digits); class=class="str">"cmt">//--- Stop Loss class="type">class="kw">double tprofit = NormalizeDouble(bprice+ TKP*_Point,_Digits); class=class="str">"cmt">//--- Take Profit class=class="str">"cmt">//--- open BuyStop order if(mytrade.BuyStop(Lot,mprice,_Symbol,stloss,tprofit)) class=class="str">"cmt">//if(mytrade.OrderOpen(_Symbol,ORDER_TYPE_BUY_STOP,Lot,class="num">0.0,bprice,stloss,tprofit,ORDER_TIME_GTC,class="num">0)) { class=class="str">"cmt">//--- Request is completed or order placed Alert("A BuyStop order has been successfully placed with Ticket#:",mytrade.ResultOrder(),"!!"); class="kw">return; } else { Alert("The BuyStop order request at vol:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", price:", mytrade.RequestPrice(), " could not be completed -error:",mytrade.ResultRetcodeDescription()); class="kw">return; } } if(checkBuy()==true) { Alert("Total Pending Orders now is :",CountOrders(),"!!"); class=class="str">"cmt">//--- any opened Buy position? if(Buy_opened) { Alert("We already have a Buy position!!!"); class="kw">return; class=class="str">"cmt">//--- Don&class="macro">#x27;t open a new Sell Position } class=class="str">"cmt">//Buy price = bar class="num">1 High + class="num">2 pip + spread class="type">int sprd=mysymbol.Spread(); class="type">class="kw">double bprice =mrate[class="num">1].high + class="num">10*_Point + sprd*_Point; class="type">class="kw">double mprice=NormalizeDouble(bprice,_Digits); class=class="str">"cmt">//--- Buy price class="type">class="kw">double stloss = NormalizeDouble(bprice - STP*_Point,_Digits); class=class="str">"cmt">//--- Stop Loss class="type">class="kw">double tprofit = NormalizeDouble(bprice+ TKP*_Point,_Digits); class=class="str">"cmt">//--- Take Profit class=class="str">"cmt">//--- open BuyStop order class=class="str">"cmt">//if(mytrade.BuyStop(Lot,mprice,_Symbol,stloss,tprofit)) if(mytrade.OrderOpen(_Symbol,ORDER_TYPE_BUY_STOP,Lot,class="num">0.0,bprice,stloss,tprofit,ORDER_TIME_GTC,class="num">0)) { class=class="str">"cmt">//--- Request is completed or order placed Alert("A BuyStop order has been successfully placed with Ticket#:",mytrade.ResultOrder(),"!!"); class="kw">return; }
挂单失败时的报错回传路径
这段逻辑处理的是 SellStop 挂单被拒后的反馈。当 mytrade.SellStop 返回 false,程序不静默退出,而是把本次请求的体积、止损、止盈、挂单价格以及 RetcodeDescription 全量弹窗,方便你直接在 MT5 终端定位是参数非法还是经纪商拒绝。 注意原文里 BuyStop 与 SellStop 的 else 分支结构对称:先 Alert 拼出 RequestVolume()、RequestSL()、RequestTP()、RequestPrice(),最后补一句 ResultRetcodeDescription()。如果你在复盘时发现挂单莫名没成,却没收到这类提示,八成是自己在调用前把 return 提前吃了。 外汇与贵金属挂单受点差和报价精度影响,_Point 与 _Digits 不匹配时请求会被拒,属高频坑。把下面这段报错分支直接拷进你的 EA 测试,跑一轮模拟单就能验证弹窗字段是否齐全。
else { Alert("The BuyStop order request at vol:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", price:", mytrade.RequestPrice(), " could not be completed -error:",mytrade.ResultRetcodeDescription()); class="kw">return; } } if(checkSell()==true) { Alert("Total Pending Orders now is :",CountOrders(),"!!"); class=class="str">"cmt">//--- any opened Sell position? if(Sell_opened) { Alert("We already have a Sell position!!!"); class="kw">return; class=class="str">"cmt">//--- Wait for a new bar } class=class="str">"cmt">//--- Sell price = bar class="num">1 Low - class="num">2 pip class="type">class="kw">double sprice=mrate[class="num">1].low-class="num">10*_Point; class="type">class="kw">double slprice=NormalizeDouble(sprice,_Digits); class=class="str">"cmt">//--- Sell price class="type">class="kw">double ssloss=NormalizeDouble(sprice+STP*_Point,_Digits); class=class="str">"cmt">//--- Stop Loss class="type">class="kw">double stprofit=NormalizeDouble(sprice-TKP*_Point,_Digits); class=class="str">"cmt">//--- Take Profit class=class="str">"cmt">//--- Open SellStop Order if(mytrade.SellStop(Lot,slprice,_Symbol,ssloss,stprofit)) class=class="str">"cmt">//if(mytrade.OrderOpen(_Symbol,ORDER_TYPE_SELL_STOP,Lot,class="num">0.0,slprice,ssloss,stprofit,ORDER_TIME_GTC,class="num">0)) { class=class="str">"cmt">//--- Request is completed or order placed Alert("A SellStop order has been successfully placed with Ticket#:",mytrade.ResultOrder(),"!!"); class="kw">return; } else { Alert("The SellStop order request at Vol:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", price:", mytrade.RequestPrice(), " could not be completed -error:",mytrade.ResultRetcodeDescription()); class="kw">return; } }
◍ 挂单止损止盈与历史订单分类的统计落点
在 SellStop 挂单逻辑里,止损与止盈先用 NormalizeDouble 对齐报价精度,避免跨品种小数位错配。止损价 = 挂单价 + STP*_Point,止盈价 = 挂单价 - TKP*_Point,方向与市场反向挂单一致。 double ssloss=NormalizeDouble(sprice+STP*_Point,_Digits); //--- Stop Loss double stprofit=NormalizeDouble(sprice-TKP*_Point,_Digits); //--- Take Profit //--- Open SellStop Order //if(mytrade.SellStop(Lot,slprice,_Symbol,ssloss,stprofit)) if(mytrade.OrderOpen(_Symbol,ORDER_TYPE_SELL_STOP,Lot,0.0,slprice,ssloss,stprofit,ORDER_TIME_GTC,0)) { //--- Request is completed or order placed Alert("A SellStop order has been successfully placed with Ticket#:",mytrade.ResultOrder(),"!!"); return; } else { Alert("The SellStop order request at Vol:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", price:", mytrade.RequestPrice(), " could not be completed -error:",mytrade.ResultRetcodeDescription()); return; } } 上面这段里 OrderOpen 用 ORDER_TYPE_SELL_STOP 显式传参,比旧版 SellStop 封装更直观,失败时能直接把 RequestVolume、RequestSL、RequestTP 和错误描述打出来,方便在 MT5 终端里当场核对参数。 统计侧在 OnStart 里先声明 buystop / sellstop / buylimit / selllimit 等计数器,再用 HistorySelect(0,TimeCurrent()) 拉取全部历史订单。外汇与贵金属挂单受滑点和流动性影响,历史回看只能说明过去分布,实盘仍属高风险,别把计数结果当胜率保证。 #include <Trade\HistoryOrderInfo.mqh> CHistoryOrderInfo myhistory; void OnStart() { int buystop=0; int sellstop=0; int buylimit=0; int selllimit=0; int buystoplimit=0; int sellstoplimit=0; int buy=0; int sell=0; int s_started=0; int s_placed=0; int s_cancelled=0; int s_partial=0; int s_filled=0; int s_rejected=0; int s_expired=0; ulong o_ticket; if(HistorySelect(0,TimeCurrent())) { // Get total orders in history 把 HistoryOrderInfo 类 include 进来后,逐个枚举订单状态计数,能算出某段时间内 SellStop 成交占比或过期占比,这数据在调挂单距离参数时比肉眼翻日志靠谱。
class="type">class="kw">double ssloss=NormalizeDouble(sprice+STP*_Point,_Digits); class=class="str">"cmt">//--- Stop Loss class="type">class="kw">double stprofit=NormalizeDouble(sprice-TKP*_Point,_Digits); class=class="str">"cmt">//--- Take Profit class=class="str">"cmt">//--- Open SellStop Order class=class="str">"cmt">//if(mytrade.SellStop(Lot,slprice,_Symbol,ssloss,stprofit)) if(mytrade.OrderOpen(_Symbol,ORDER_TYPE_SELL_STOP,Lot,class="num">0.0,slprice,ssloss,stprofit,ORDER_TIME_GTC,class="num">0)) { class=class="str">"cmt">//--- Request is completed or order placed Alert("A SellStop order has been successfully placed with Ticket#:",mytrade.ResultOrder(),"!!"); class="kw">return; } else { Alert("The SellStop order request at Vol:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", price:", mytrade.RequestPrice(), " could not be completed -error:",mytrade.ResultRetcodeDescription()); class="kw">return; } } class="macro">#include <Trade\HistoryOrderInfo.mqh> CHistoryOrderInfo myhistory; class="type">void OnStart() { class="type">int buystop=class="num">0; class="type">int sellstop=class="num">0; class="type">int buylimit=class="num">0; class="type">int selllimit=class="num">0; class="type">int buystoplimit=class="num">0; class="type">int sellstoplimit=class="num">0; class="type">int buy=class="num">0; class="type">int sell=class="num">0; class="type">int s_started=class="num">0; class="type">int s_placed=class="num">0; class="type">int s_cancelled=class="num">0; class="type">int s_partial=class="num">0; class="type">int s_filled=class="num">0; class="type">int s_rejected=class="num">0; class="type">int s_expired=class="num">0; class="type">class="kw">ulong o_ticket; if(HistorySelect(class="num">0,TimeCurrent())) { class=class="str">"cmt">// Get total orders in history
「倒序遍历历史订单并分类计数」
在 MT5 的 EA 或脚本里,历史订单池用 HistoryOrdersTotal() 取总数,但索引是从 1 到总数、且需倒序 j-- 才不会漏掉即时插入的记录。下面这段循环就是标准做法:先拿 ticket,再绑给自定义封装类 myhistory 去读字段。 循环体里把每张订单的建仓时间、开仓价、品种、类型、魔术码、成交时间、初始手数全打印出来,方便在终端核对策略到底挂了什么单。注意 TimeToString(myhistory.TimeSetup()) 输出的是挂单设置时间,不是成交时间,两者在限价单上可能差几个小时。 真正有用量化价值的是后半段的分类统计:按 Type() 区分 buy_stop / sell_stop / buy / sell / 各 limit 变体,再按 State() 看 started、placed、cancelled、partial、filled 各有多少。跑一遍你就会发现,比如一周内 ORDER_STATE_CANCELED 占比若超过 40%,说明挂单被扫掉的概率偏高,外汇和贵金属这种高波动品种尤其要警惕滑点风险。 别把正态当圣经 历史订单的 cancelled 与 filled 比例不是稳态分布,重大数据行情前 placed 单会堆积,事件后 cancelled 可能瞬间拉高,直接用均值去调参会误判。
for(class="type">int j=HistoryOrdersTotal(); j>class="num">0; j--) { class=class="str">"cmt">// select order by ticket o_ticket=HistoryOrderGetTicket(j); if(o_ticket>class="num">0) { class=class="str">"cmt">// Set order Ticket to work with myhistory.Ticket(o_ticket); Print("Order index ",j," Order Ticket is: ",myhistory.Ticket()," !"); Print("Order index ",j," Order Setup Time is: ",TimeToString(myhistory.TimeSetup())," !"); Print("Order index ",j," Order Open Price is: ",myhistory.PriceOpen()," !"); Print("Order index ",j," Order Symbol is: ",myhistory.Symbol() ," !"); Print("Order index ",j," Order Type is: ", myhistory.Type() ," !"); Print("Order index ",j," Order Type Description is: ",myhistory.TypeDescription()," !"); Print("Order index ",j," Order Magic is: ",myhistory.Magic()," !"); Print("Order index ",j," Order Time Done is: ",myhistory.TimeDone()," !"); Print("Order index ",j," Order Initial Volume is: ",myhistory.VolumeInitial()," !"); class=class="str">"cmt">// class=class="str">"cmt">// if(myhistory.Type() == ORDER_TYPE_BUY_STOP) buystop++; if(myhistory.Type() == ORDER_TYPE_SELL_STOP) sellstop++; if(myhistory.Type() == ORDER_TYPE_BUY) buy++; if(myhistory.Type() == ORDER_TYPE_SELL) sell++; if(myhistory.Type() == ORDER_TYPE_BUY_LIMIT) buylimit++; if(myhistory.Type() == ORDER_TYPE_SELL_LIMIT) selllimit++; if(myhistory.Type() == ORDER_TYPE_BUY_STOP_LIMIT) buystoplimit++; if(myhistory.Type() == ORDER_TYPE_SELL_STOP_LIMIT) sellstoplimit++; if(myhistory.State() == ORDER_STATE_STARTED) s_started++; if(myhistory.State() == ORDER_STATE_PLACED) s_placed++; if(myhistory.State() == ORDER_STATE_CANCELED) s_cancelled++; if(myhistory.State() == ORDER_STATE_PARTIAL) s_partial++; if(myhistory.State() == ORDER_STATE_FILLED) s_filled++; }
把历史订单状态打印出来核对
遍历完历史订单后,光靠计数器累加还不够,得把分类结果直接打到 MT5 Experts 日志里,才能确认挂单与成交的分布是否和经纪商后台对得上。下面这段收尾逻辑把买单、卖单、各类挂单以及订单生命周期状态逐一输出,外汇与贵金属账户的高频重挂单场景下,rejected 与 expired 的数量异常往往能暴露服务器延迟或有效期设置问题。 核心打印块先用 Print 输出基础分类:Buy Stop、Sell Stop、市价买、市价卖,以及 HistoryOrdersTotal() 返回的历史订单总数。随后分三组汇总——按订单类型列全 8 种挂单子类,再按状态列 started/placed/cancelled/partial/filled/rejected/expired。注意 HistoryOrdersTotal() 在 HistorySelect(0, TimeCurrent()) 成功后才有效,否则返回 0 会让你误以为没单。 脚本底部顺手把 CDealInfo 类包含进来并建了 mydeal 对象,虽然后面 OnStart 里只声明了 deal 相关局部变量尚未展开,但已经为下一步逐笔成交拆解留好接口。开 MT5 把这段贴进脚本跑一次,重点看 rejected 和 expired 是不是非零——若是,调挂单有效期或换连接节点可能比改策略更紧迫。
if(myhistory.State() == ORDER_STATE_REJECTED) s_rejected++; if(myhistory.State() == ORDER_STATE_EXPIRED) s_expired++; } } } class=class="str">"cmt">// Print summary Print("Buy Stop Pending Orders : ",buystop); Print("Sell Stop Pending Orders: ",sellstop); Print("Buy Orders : ",buy); Print("Sell Orders: ",sell); Print("Total Orders in History is :",HistoryOrdersTotal()," !"); Print("Orders type summary"); Print("Market Buy Orders: ",buy); Print("Market Sell Orders: ",sell); Print("Pending Buy Stop: ",buystop); Print("Pending Sell Stop: ",sellstop); Print("Pending Buy Limit: ",buylimit); Print("Pending Sell Limit: ",selllimit); Print("Pending Buy Stop Limit: ",buystoplimit); Print("Pending Sell Stop Limit: ",sellstoplimit); Print("Total orders:",HistoryOrdersTotal()," !"); Print("Orders state summary"); Print("Checked, but not yet accepted by broker: ",s_started); Print("Accepted: ",s_placed); Print("Canceled by client: ",s_cancelled); Print("Partially executed: ",s_partial); Print("Fully executed: ",s_filled); Print("Rejected: ",s_rejected); Print("Expired: ",s_expired); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include ALL classes that will be used | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- The CDealInfo Class class="macro">#include <Trade\DealInfo.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create class object | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- The CDealInfo Class Object CDealInfo mydeal; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- Get all deals in History and get their details class="type">int buy=class="num">0; class="type">int sell=class="num">0; class="type">int deal_in=class="num">0; class="type">int deal_out=class="num">0; class="type">class="kw">ulong d_ticket; class=class="str">"cmt">// Get all history records if (HistorySelect(class="num">0,TimeCurrent())) { class=class="str">"cmt">// Get total deals in history
◍ 倒序遍历历史成交并统计进出场分类
在 MT5 里拉取历史成交,不能从 0 正向扫,因为 HistoryDealsTotal() 返回的是当前历史池里的成交总数,索引从 1 到总数,倒序 j-- 才能稳定拿到每张 ticket 而不会漏掉运行期新增的记录。 下面这段逻辑先用 HistoryDealGetTicket(j) 按索引取 ticket,再塞进 CDeal 对象 mydeal,随后把成交时间、价格、品种、魔术码、成交量、进场类型描述、盈亏逐行 Print 出来,方便在专家日志里直接核对每一笔。 统计部分用 Entry() 区分 DEAL_ENTRY_IN 与 DEAL_ENTRY_OUT,用 Type() 区分 DEAL_TYPE_BUY 与 DEAL_TYPE_SELL,分别累加 deal_in / deal_out / buy / sell。跑完循环后 Print 总成交数与四类计数,你能在日志里立刻看到比如「Total Deals in History is : 237」这类真实数据,用以核对策略实际触发的买卖频次。 开 MT5 把代码贴进 EA 的 OnDeinit 或脚本里跑一次,重点看 deal_in 和 deal_out 是否相等——若不等,说明有挂单成交未被历史池完整收录,或当前账户存在部分平仓的拆单成交。
for (class="type">int j=HistoryDealsTotal(); j>class="num">0; j--) { class=class="str">"cmt">// select deals by ticket if (d_ticket = HistoryDealGetTicket(j)) { class=class="str">"cmt">// Set Deal Ticket to work with mydeal.Ticket(d_ticket); Print("Deal index ", j ," Deal Ticket is: ", mydeal.Ticket() ," !"); Print("Deal index ", j ," Deal Execution Time is: ", TimeToString(mydeal.Time()) ," !"); Print("Deal index ", j ," Deal Price is: ", mydeal.Price() ," !"); Print("Deal index ", j ," Deal Symbol is: ", mydeal.Symbol() ," !"); Print("Deal index ", j ," Deal Type Description is: ", mydeal.TypeDescription() ," !"); Print("Deal index ", j ," Deal Magic is: ", mydeal.Magic() ," !"); Print("Deal index ", j ," Deal Time is: ", mydeal.Time() ," !"); Print("Deal index ", j ," Deal Initial Volume is: ", mydeal.Volume() ," !"); Print("Deal index ", j ," Deal Entry Type Description is: ", mydeal.EntryDescription() ," !"); Print("Deal index ", j ," Deal Profit is: ", mydeal.Profit() ," !"); class=class="str">"cmt">// if (mydeal.Entry() == DEAL_ENTRY_IN) deal_in++; if (mydeal.Entry() == DEAL_ENTRY_OUT) deal_out++; if (mydeal.Type() == DEAL_TYPE_BUY) buy++; if (mydeal.Type() == DEAL_TYPE_SELL) sell++; } } class=class="str">"cmt">// Print Summary Print("Total Deals in History is :", HistoryDealsTotal(), " !"); Print("Total Deal Entry IN is : ", deal_in); Print("Total Deal Entry OUT is: ", deal_out); Print("Total Buy Deal is : ", buy); Print("Total Sell Deal is: ", sell);
「标准类库函数该怎么挑着用」
前面几节把 CTrade 里的仓位修改、挂单下达与删除、预付款校验,以及订单和交易详情抓取都跑了一遍。实测里用到的函数只是标准类库的一小部分,随附的 mql5_standardclass_ea.mq5(20.08 KB)和 trade_classes_test.mq5(20.12 KB)可作为对照样本直接丢进 MT5 编译。 策略不同,实际调用的接口数量可能比示例多、也可能少。比如做纯市价网格的 EA 大概率用不到挂单类函数,而做突破策略的则离不开 BuyStop/SellStop 下达与撤销。 开 MT5 把这几个示例的 ZIP 解压,重点翻 CTrade 的成员函数声明,按你自己策略列一张“必用/备用”清单,比通读文档更高效。外汇与贵金属杠杆高,回测通过不代表实盘无风险,上真实账户前先用策略测试器跑够样本。
PositionType 方法里的拼写坑
MQL5 的 CPositionInfo 类里,取持仓方向的方法是 PositionType(),不是某些示例代码里误写的 PositionTipe() 或 PositionTyep()。2019 年 6 月社区里就有人贴出修正版,原示例代码因拼写错字会导致编译失败或返回无效枚举。
在 EA 里调用时,先 myposition.Select(symbol) 选中标的,再读 myposition.PositionType(),返回 POSITION_TYPE_BUY 或 POSITION_TYPE_SELL。若拼写错,MT5 编译器报 'PositionTipe' is not a member of CPositionInfo,盯盘逻辑直接断在加载阶段。
外汇与贵金属杠杆高,持仓方向判断错可能让反向平仓脚本误杀单子。开 MT5 新建脚本,粘贴下方片段,把 Symbol 换成你盯的 XAUUSD,编译跑一遍验证返回枚举值。
CPositionInfo myposition; if(myposition.Select(Symbol())) { class="type">ENUM_POSITION_TYPE type = myposition.PositionType(); if(type == POSITION_TYPE_BUY) Print("多仓"); else if(type == POSITION_TYPE_SELL) Print("空仓"); }
◍ 一点提醒
前面几篇把通道绘制、判别分析、GARCH 条件方差、时序特性类都拆过了,落到 MT5 上最实在的一步:把示例代码里的中文或西里尔注释换成你能秒懂的短语,否则回看时容易漏掉极值判定逻辑。 外汇和贵金属杠杆高、滑点随机,任何通道或预后模型都只是概率参考;下次重读时,先确认 myposition.PositionType() 返回的持仓方向有没有和通道突破信号对齐,再决定跟不跟。 文章里那些登录框、信号复制的推广块和正文技术无关,盯盘时直接忽略,省下的注意力留给参数微调。