MQL5 交易工具包(第 2 部分):扩展和实现仓位管理 EX5 库·进阶篇
(2/3)· 从单库导入到多库共存,再用图形面板把仓位函数跑进真实 EA,避开路径与命名坑
「追踪止损价的合法性校验与下单重试」
多头仓位里,追踪止损价按 positionPriceCurrent - trailingStopLoss * symbolPoint 推算;若算出来比开仓价 entryPrice 还低,或比当前挂着的 SL 还低,函数直接 return(false) 退出——这能避免把止损挪到亏损更深处。
空头反过来:slPrice = 当前价 + 追踪距离×点值,一旦高于开仓价或高于原 SL 同样判无效。外汇和贵金属点值随品种跳动,这种边界判断能少踩经纪商 reject 的坑,但杠杆品种本身高风险,校验过了也不代表行情不扫损。
下单前用 Print 把 ticket、volume、开仓价、旧 SL→新 SL、TP、magic 全打进日志,便于回看某次移动是哪个 EA 实例触发的。
真正发单包在 for(loop=0; loop<=100; loop++) 里,最多重试 101 次才罢休;每次先 ZeroMemory 清结构体、ResetLastError 清错误码,再 OrderSend。实战中遇到报价延迟,前几次失败很正常,靠这个循环把成功率拉起来。
trailingStopLoss = symbolStopLevel; class=class="str">"cmt">//-- Set it to the symbol stop level by class="kw">default class=class="str">"cmt">//-- Calculate and store the trailing stop loss price if(positionType == POSITION_TYPE_BUY) { slPrice = positionPriceCurrent - (trailingStopLoss * symbolPoint); class=class="str">"cmt">//-- Check if the proposed slPrice for the trailing stop loss is valid if(slPrice < entryPrice || slPrice < currentPositionSlPrice) { class="kw">return(false); class=class="str">"cmt">//-- Exit the function, proposed trailing stop loss price is invalid } } else class=class="str">"cmt">//-- SELL POSITION { slPrice = positionPriceCurrent + (trailingStopLoss * symbolPoint); class=class="str">"cmt">//-- Check if the proposed slPrice for the trailing stop loss is valid if(slPrice > entryPrice || slPrice > currentPositionSlPrice) { class="kw">return(false); class=class="str">"cmt">//-- Exit the function, proposed trailing stop loss price is invalid } } class=class="str">"cmt">//-- Print position properties before setting the trailing stop loss class="type">class="kw">string positionProperties = "--> " + positionSymbol + " " + EnumToString(positionType) + " Trailing Stop Loss Modification Details" + " <--\r\n"; positionProperties += "------------------------------------------------------------\r\n"; positionProperties += "Ticket: " + (class="type">class="kw">string)positionTicket + "\r\n"; positionProperties += "Volume: " + StringFormat("%G", volume) + "\r\n"; positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + "\r\n"; positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + " -> New Trailing SL: " + (class="type">class="kw">string)slPrice + "\r\n"; positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "\r\n"; positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n"; positionProperties += "Magic Number: " + (class="type">class="kw">string)PositionGetInteger(POSITION_MAGIC) + "\r\n"; positionProperties += "---"; Print(positionProperties); class=class="str">"cmt">//-- reset the the tradeRequest and tradeResult values by zeroing them ZeroMemory(tradeRequest); ZeroMemory(tradeResult); class=class="str">"cmt">//-- initialize the parameters to set the sltp tradeRequest.action = TRADE_ACTION_SLTP; class=class="str">"cmt">//-- Trade operation type for setting sl and tp tradeRequest.position = positionTicket; tradeRequest.symbol = positionSymbol; tradeRequest.sl = slPrice; tradeRequest.tp = currentPositionTpPrice; ResetLastError(); class=class="str">"cmt">//--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function for(class="type">int loop = class="num">0; loop <= class="num">100; loop++) class=class="str">"cmt">//-- try modifying the sl and tp class="num">101 times untill the request is successful { class=class="str">"cmt">//--- send order to the trade server if(OrderSend(tradeRequest, tradeResult)) { class=class="str">"cmt">//-- Confirm order execution
◍ 追踪止损回执与仓位校验的落地写法
移动止损真正难的地方不在算价格,而在把订单回执和仓位状态啃干净。下面这段逻辑先判断交易回执码:10008 代表订单已在交易服务器排队成交,10009 代表已成交,两者都算成功分支,直接打印仓位号、品种和买卖类型后退出函数。 失败分支里调用了 ErrorAdvisor 做错误归类,若返回 false 或脚本被强制停止(IsStopped),就打印错误并 return false。注意这里 retcode 与 GetLastError 是两套信息:前者是交易服务器给的业务码,后者是终端本地错误码,排查滑点类问题两个都要看。 SetTrailingStopLoss 入口先卡两道闸:TradingIsAllowed 确认 EA 自动交易没被禁,trailingStopLoss 参数不为 0 才往下走。随后用 PositionSelectByTicket 按 ticket 精确选中持仓,选不中立刻打印错误并退出——这一步漏掉 ResetLastError 的话,旧错误码会污染本次诊断。 选中仓位后把品种、开仓价、成交量读进局部变量,slPrice 先置 0 等待后续计算。外汇与贵金属波动剧烈,追踪止损触发存在滑点未成交可能,实盘前建议先在 MT5 策略测试器用历史数据跑一遍回执分支。
if(tradeResult.retcode == class="num">10008 || tradeResult.retcode == class="num">10009) { PrintFormat("Successfully set the Trailing SL for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); PrintFormat("retcode=%u runtime_code=%u", tradeResult.retcode, GetLastError()); Print("_______________________________________________________________________________________\r\n\r\n"); class="kw">return(true); class=class="str">"cmt">//-- exit function break; class=class="str">"cmt">//--- success - order placed ok. exit for loop } } else class=class="str">"cmt">//-- Order request failed { class=class="str">"cmt">//-- order not sent or critical error found if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped()) { PrintFormat("ERROR setting the Trailing SL for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); Print("_______________________________________________________________________________________\r\n\r\n"); class="kw">return(false); class=class="str">"cmt">//-- exit function break; class=class="str">"cmt">//-- exit for loop } } } class="kw">return(false); } class="type">bool SetTrailingStopLoss(class="type">ulong positionTicket, class="type">int trailingStopLoss) class="kw">export { class=class="str">"cmt">//-- first check if the EA is allowed to trade and the trailing stop loss parameter is more than zero if(!TradingIsAllowed() || trailingStopLoss == class="num">0) { class="kw">return(false); class=class="str">"cmt">//--- algo trading is disabled or trailing stop loss is invalid, exit function } class=class="str">"cmt">//--- Confirm and select the position using the provided positionTicket ResetLastError(); class=class="str">"cmt">//--- Reset error cache incase of ticket selection errors if(!PositionSelectByTicket(positionTicket)) { class=class="str">"cmt">//---Position selection failed Print("\r\n_______________________________________________________________________________________"); Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError()); class="kw">return(false); class=class="str">"cmt">//-- Exit the function } class=class="str">"cmt">//-- create variable to store the calculated trailing sl prices to send to the trade server class="type">class="kw">double slPrice = class="num">0.0; class=class="str">"cmt">//--- Position ticket selected, save the position properties class="type">class="kw">string positionSymbol = PositionGetString(POSITION_SYMBOL); class="type">class="kw">double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN); class="type">class="kw">double volume = PositionGetDouble(POSITION_VOLUME);
正文
<span class="keyword">double</span> currentPositionSlPrice = <span class="functions">PositionGetDouble</span>(<span class="macro">POSITION_SL</span>); <span class="keyword">double</span> currentPositionTpPrice = <span class="functions">PositionGetDouble</span>(<span class="macro">POSITION_TP</span>); <span class="macro">ENUM_POSITION_TYPE</span> positionType = (<span class="macro">ENUM_POSITION_TYPE</span>)<span class="functions">PositionGetInteger</span>(<span class="macro">POSITION_TYPE</span>); <span class="comment">//-- Get some information about the positions symbol</span> <span class="keyword">int</span> symbolDigits = (<span class="keyword">int</span>)<span class="functions">SymbolInfoInteger</span>(positionSymbol, <span class="macro">SYMBOL_DIGITS</span>); <span class="comment">//-- Number of symbol decimal places</span> <span class="keyword">int</span> symbolStopLevel = (<span class="keyword">int</span>)<span class="functions">SymbolInfoInteger</span>(positionSymbol, <span class="macro">SYMBOL_TRADE_STOPS_LEVEL</span>); <span class="keyword">double</span> symbolPoint = <span class="functions">SymbolInfoDouble</span>(positionSymbol, <span class="macro">SYMBOL_POINT</span>); <span class="keyword">double</span> positionPriceCurrent = <span class="functions">PositionGetDouble</span>(<span class="macro">POSITION_PRICE_CURRENT</span>); <span class="keyword">int</span> sp
「给持仓挂移动止损的实战发送逻辑」
在 MT5 里修改已有持仓的止损止盈,靠的是 TRADE_ACTION_SLTP 这类交易动作,而不是下新单。上面这段把 tradeRequest 的 action 设成该常量,再填 position、symbol、sl、tp,就能对准指定 ticket 的仓位改线。 发送前用 ZeroMemory 把请求和结果结构体清零,ResetLastError 清掉错误缓存,否则后续拿到的 runtime error code 可能是上一轮的脏值。改线这种操作对 broker 返回节奏敏感,所以代码里套了 for(loop=0; loop<=100) 的 101 次重试。 OrderSend 成功且 retcode 是 10008(已成交)或 10009(已受理)才视为改线成功,直接 return(true) 退出;失败则交给 ErrorAdvisor 判断是否可重试,遇到不可恢复错误或 IsStopped() 就 return(false)。外汇和贵金属杠杆高,SL 被快速扫掉或 reject 的概率不低,跑之前建议在策略测试器用真实点差验证重试次数够不够。
positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n"; positionProperties += "Magic Number: " + (class="type">class="kw">string)PositionGetInteger(POSITION_MAGIC) + "\r\n"; positionProperties += "---"; Print(positionProperties); class=class="str">"cmt">//-- reset the the tradeRequest and tradeResult values by zeroing them ZeroMemory(tradeRequest); ZeroMemory(tradeResult); class=class="str">"cmt">//-- initialize the parameters to set the sltp tradeRequest.action = TRADE_ACTION_SLTP; class=class="str">"cmt">//-- Trade operation type for setting sl and tp tradeRequest.position = positionTicket; tradeRequest.symbol = positionSymbol; tradeRequest.sl = slPrice; tradeRequest.tp = currentPositionTpPrice; ResetLastError(); class=class="str">"cmt">//--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function for(class="type">int loop = class="num">0; loop <= class="num">100; loop++) class=class="str">"cmt">//-- try modifying the sl and tp class="num">101 times untill the request is successful { class=class="str">"cmt">//--- send order to the trade server if(OrderSend(tradeRequest, tradeResult)) { class=class="str">"cmt">//-- Confirm order execution if(tradeResult.retcode == class="num">10008 || tradeResult.retcode == class="num">10009) { PrintFormat("Successfully set the Trailing SL for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); PrintFormat("retcode=%u runtime_code=%u", tradeResult.retcode, GetLastError()); Print("_______________________________________________________________________________________\r\n\r\n"); class="kw">return(true); class=class="str">"cmt">//-- exit function break; class=class="str">"cmt">//--- success - order placed ok. exit for loop } } else class=class="str">"cmt">//-- Order request failed { class=class="str">"cmt">//-- order not sent or critical error found if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped()) { PrintFormat("ERROR setting the Trailing SL for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType)); Print("_______________________________________________________________________________________\r\n\r\n"); class="kw">return(false); class=class="str">"cmt">//-- exit function break; class=class="str">"cmt">//-- exit for loop } } } class="kw">return(false); }
◍ 一键清仓的 CloseAllPositions 怎么写才不卡死
在 MT5 里做批量平仓,最怕两件事:一是EA自动化被关了还在发单,二是服务器没确认平仓就反复回调陷入死循环。CloseAllPositions() 用两个默认参数(symbol=ALL_SYMBOLS、magicNumber=0)来框定范围,只清指定品种和幻数的仓,其余一律跳过。 函数开头先调 TradingIsAllowed() 做一次闸门检查,禁止交易直接 return false,不浪费一次网络请求。随后用 PositionsTotal() 取未平总数,for 循环里逐个读 ticket、symbol、magic,不符合条件的 continue,符合的丢给 ClosePositionByTicket() 发平仓指令。 发完指令不等于真的平了。原文用 while 循环配合 SymbolPositionsTotal() 复查,只要目标仓位数大于0就再次回调自身,每次 Sleep(100) 给交易服务器喘息,并用 breakerBreaker 计数器封顶101次防无限循环。遇到关键错误、脚本被停或超次数就 break。 最后再查一次 SymbolPositionsTotal() 是否为0,是则 returnThis=true,函数返回真,否则返假。外汇和贵金属杠杆高,批量平仓可能在滑点扩大时部分失败,跑之前务必在策略测试器用历史数据验证断路器逻辑。
class="type">bool CloseAllPositions(class="type">class="kw">string symbol = ALL_SYMBOLS, class="type">ulong magicNumber = class="num">0) class="kw">export { class=class="str">"cmt">//-- Functions body goes here } if(!TradingIsAllowed()) { class="kw">return(false); class=class="str">"cmt">//--- algo trading is disabled, exit function } class="type">bool returnThis = false; class="type">int totalOpenPositions = PositionsTotal(); for(class="type">int x = class="num">0; x < totalOpenPositions; x++) { class=class="str">"cmt">//--- Get position properties class="type">ulong positionTicket = PositionGetTicket(x); class=class="str">"cmt">//-- Get ticket to select the position class="type">class="kw">string selectedSymbol = PositionGetString(POSITION_SYMBOL); class="type">ulong positionMagicNo = PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">//-- Filter positions by symbol and magic number if( (symbol != ALL_SYMBOLS && symbol != selectedSymbol) || (magicNumber != class="num">0 && positionMagicNo != magicNumber) ) { class="kw">continue; } class=class="str">"cmt">//-- Close the position ClosePositionByTicket(positionTicket); } class="type">int breakerBreaker = class="num">0; class=class="str">"cmt">//-- Variable that safeguards and makes sure we are not locked in an infinite loop while(SymbolPositionsTotal(symbol, magicNumber) > class="num">0) { breakerBreaker++; CloseAllPositions(symbol, magicNumber); class=class="str">"cmt">//-- We still have some open positions, do a function callback Sleep(class="num">100); class=class="str">"cmt">//-- Micro sleep to pace the execution and give some time to the trade server class=class="str">"cmt">//-- Check for critical errors so that we exit the loop if we run into trouble if(!ErrorAdvisor(__FUNCTION__, symbol, GetLastError()) || IsStopped() || breakerBreaker > class="num">101) { break; } } if(SymbolPositionsTotal(symbol, magicNumber) == class="num">0) { returnThis = true; class=class="str">"cmt">//-- Save this status for the function class="kw">return value } class="kw">return(returnThis); class="type">bool CloseAllPositions(class="type">class="kw">string symbol = ALL_SYMBOLS, class="type">ulong magicNumber = class="num">0) class="kw">export { class=class="str">"cmt">//-- first check if the EA is allowed to trade if(!TradingIsAllowed()) { class="kw">return(false); class=class="str">"cmt">//--- algo trading is disabled, exit function } class="type">bool returnThis = false;
按品种与魔术码精准平掉持仓
这段逻辑干的事很直接:先扫一遍当前账户所有持仓,只挑出指定品种和魔术码的那部分,逐个发送平仓指令。过滤条件写在 if 里——symbol 不等于 ALL_SYMBOLS 且不等于当前持仓品种,或者 magicNumber 非零且不等于持仓魔术码,就 continue 跳过,不碰无关仓位。 平仓不是发一次指令就完事。代码里用 while 循环复查 SymbolPositionsTotal(symbol, magicNumber),只要目标持仓数还大于 0,就再回调 CloseAllPositions 并 Sleep(100) 给交易服务器喘息。breakerBreaker 计数到 101 就强制 break,避免服务器抽风时陷入死循环。 最后用一次 SymbolPositionsTotal == 0 做终确认,全平掉才把 returnThis 置为 true 返回。外汇和贵金属杠杆高,批量平仓可能因点差跳空或拒绝而部分失败,这种二次复查机制能显著降低漏平概率,建议你在 MT5 策略测试器里用 EURUSD 最小手数跑一遍验证。
class=class="str">"cmt">//-- Scan for symbol and magic number specific positions and close them class="type">int totalOpenPositions = PositionsTotal(); for(class="type">int x = class="num">0; x < totalOpenPositions; x++) { class=class="str">"cmt">//--- Get position properties class="type">ulong positionTicket = PositionGetTicket(x); class=class="str">"cmt">//-- Get ticket to select the position class="type">class="kw">string selectedSymbol = PositionGetString(POSITION_SYMBOL); class="type">ulong positionMagicNo = PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">//-- Filter positions by symbol and magic number if( (symbol != ALL_SYMBOLS && symbol != selectedSymbol) || (magicNumber != class="num">0 && positionMagicNo != magicNumber) ) { class="kw">continue; } class=class="str">"cmt">//-- Close the position ClosePositionByTicket(positionTicket); } class=class="str">"cmt">//-- Confirm that we have closed all the positions being targeted class="type">int breakerBreaker = class="num">0; class=class="str">"cmt">//-- Variable that safeguards and makes sure we are not locked in an infinite loop while(SymbolPositionsTotal(symbol, magicNumber) > class="num">0) { breakerBreaker++; CloseAllPositions(symbol, magicNumber); class=class="str">"cmt">//-- We still have some open positions, do a function callback Sleep(class="num">100); class=class="str">"cmt">//-- Micro sleep to pace the execution and give some time to the trade server class=class="str">"cmt">//-- Check for critical errors so that we exit the loop if we run into trouble if(!ErrorAdvisor(__FUNCTION__, symbol, GetLastError()) || IsStopped() || breakerBreaker > class="num">101) { break; } } class=class="str">"cmt">//-- Final confirmations that all targeted positions have been closed if(SymbolPositionsTotal(symbol, magicNumber) == class="num">0) { returnThis = true; class=class="str">"cmt">//-- Save this status for the function class="kw">return value } class="kw">return(returnThis); }