# Trading Operations Reference ## Table of Contents - [Order Management - MQL4](#order-management---mql4) - [Order Management - MQL5](#order-management---mql5) - [Risk Management](#risk-management) - [Trailing Stop Implementations](#trailing-stop-implementations) - [Trade Return Codes (MQL5)](#trade-return-codes-mql5) - [Runtime Persistence Across TF Changes (MT5)](#runtime-persistence-across-timeframe-changes-mt5) - [Trailing Stop Implementations](#trailing-stop-implementations) --- ## Order Management - MQL4 ### OrderSend with Error Handling and Retry ```mql4 int SendOrderReliable(string symbol, int cmd, double lots, double price, int slippage, double sl, double tp, string comment, int magic, int maxRetries = 5) { int ticket = -1; int digits = (int)MarketInfo(symbol, MODE_DIGITS); double point = MarketInfo(symbol, MODE_POINT); double stopLevel = MarketInfo(symbol, MODE_STOPLEVEL) * point; price = NormalizeDouble(price, digits); sl = NormalizeDouble(sl, digits); tp = NormalizeDouble(tp, digits); // Validate stop level distances if(sl != 0) { if(cmd == OP_BUY && (price - sl) < stopLevel) { Print("SL too close. Min distance: ", stopLevel / point, " points"); return(-1); } if(cmd == OP_SELL && (sl - price) < stopLevel) { Print("SL too close. Min distance: ", stopLevel / point, " points"); return(-1); } } for(int attempt = 0; attempt < maxRetries; attempt++) { if(attempt > 0) { int sleepMs = 1000 * (int)MathPow(2, attempt - 1); // 1s, 2s, 4s, 8s Sleep(sleepMs); RefreshRates(); // Update price for market orders if(cmd == OP_BUY) price = MarketInfo(symbol, MODE_ASK); if(cmd == OP_SELL) price = MarketInfo(symbol, MODE_BID); price = NormalizeDouble(price, digits); } ticket = OrderSend(symbol, cmd, lots, price, slippage, sl, tp, comment, magic, 0, clrNONE); if(ticket >= 0) return(ticket); int err = GetLastError(); Print("OrderSend attempt ", attempt + 1, " failed. Error: ", err, " - ", ErrorDescription(err)); // Retryable errors if(err == ERR_REQUOTE || err == ERR_PRICE_CHANGED || err == ERR_OFF_QUOTES || err == ERR_SERVER_BUSY || err == ERR_BROKER_BUSY || err == ERR_TRADE_CONTEXT_BUSY || err == ERR_TOO_MANY_REQUESTS || err == ERR_TRADE_TIMEOUT || err == ERR_NO_CONNECTION) { continue; } // Non-retryable errors - exit immediately break; } return(-1); } ``` ### Order Modification Pattern ```mql4 bool ModifyOrderReliable(int ticket, double sl, double tp) { if(!OrderSelect(ticket, SELECT_BY_TICKET)) { Print("Order ", ticket, " not found"); return(false); } int digits = (int)MarketInfo(OrderSymbol(), MODE_DIGITS); sl = NormalizeDouble(sl, digits); tp = NormalizeDouble(tp, digits); // Skip if nothing changed if(MathAbs(OrderStopLoss() - sl) < Point / 2.0 && MathAbs(OrderTakeProfit() - tp) < Point / 2.0) return(true); bool result = OrderModify(ticket, OrderOpenPrice(), sl, tp, OrderExpiration(), clrNONE); if(!result) { int err = GetLastError(); Print("OrderModify failed for ticket ", ticket, ". Error: ", err, " - ", ErrorDescription(err)); } return(result); } ``` ### Order Loop Patterns **CRITICAL: Always use reverse loops when closing/deleting orders.** Closing an order shifts indices, so forward loops skip orders. #### Close All Orders for a Symbol ```mql4 void CloseAllOrders(string symbol, int magic) { for(int i = OrdersTotal() - 1; i >= 0; i--) { if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if(OrderSymbol() != symbol) continue; if(OrderMagicNumber() != magic) continue; bool result = false; if(OrderType() == OP_BUY) result = OrderClose(OrderTicket(), OrderLots(), MarketInfo(symbol, MODE_BID), 3, clrNONE); else if(OrderType() == OP_SELL) result = OrderClose(OrderTicket(), OrderLots(), MarketInfo(symbol, MODE_ASK), 3, clrNONE); else result = OrderDelete(OrderTicket()); // Pending orders if(!result) Print("Failed to close order ", OrderTicket(), ". Error: ", GetLastError()); } } ``` #### Count Orders by Type and Magic Number ```mql4 int CountOrders(string symbol, int magic, int orderType = -1) { int count = 0; for(int i = OrdersTotal() - 1; i >= 0; i--) { if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if(OrderSymbol() != symbol) continue; if(OrderMagicNumber() != magic) continue; if(orderType >= 0 && OrderType() != orderType) continue; count++; } return(count); } ``` #### Find Most Recent Order ```mql4 int FindLatestOrder(string symbol, int magic) { int latestTicket = -1; datetime latestTime = 0; for(int i = OrdersTotal() - 1; i >= 0; i--) { if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if(OrderSymbol() != symbol) continue; if(OrderMagicNumber() != magic) continue; if(OrderOpenTime() > latestTime) { latestTime = OrderOpenTime(); latestTicket = OrderTicket(); } } return(latestTicket); } ``` --- ## Order Management - MQL5 ### CTrade Setup ```mql5 #include CTrade trade; int OnInit() { trade.SetExpertMagicNumber(MagicNumber); trade.SetDeviationInPoints(Slippage); SetFillingPolicy(trade, _Symbol); return(INIT_SUCCEEDED); } ``` ### Filling Policy Detection Brokers support different order filling modes. Setting the wrong filling type causes order rejection. ```mql5 void SetFillingPolicy(CTrade &trade, string symbol) { long filling = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); if((filling & SYMBOL_FILLING_FOK) != 0) trade.SetTypeFilling(ORDER_FILLING_FOK); else if((filling & SYMBOL_FILLING_IOC) != 0) trade.SetTypeFilling(ORDER_FILLING_IOC); else trade.SetTypeFilling(ORDER_FILLING_RETURN); } ``` **Filling mode meanings:** - **FOK (Fill or Kill):** Entire order must be filled at the requested price or better, otherwise it is cancelled entirely. - **IOC (Immediate or Cancel):** Fill as much as possible immediately, cancel the remainder. - **RETURN:** Partial fills allowed; unfilled portion remains as a live order. Common on exchange-traded instruments. ### Trade with Retry Logic (MQL5) ```mql5 class CTradeManager { private: CTrade m_trade; int m_maxRetries; int m_magic; bool IsRetryable(uint retcode) { switch(retcode) { case TRADE_RETCODE_REQUOTE: // 10004 case TRADE_RETCODE_PRICE_CHANGED: // 10020 case TRADE_RETCODE_PRICE_OFF: // 10021 case TRADE_RETCODE_CONNECTION: // 10031 case TRADE_RETCODE_TIMEOUT: // 10012 case TRADE_RETCODE_TOO_MANY_REQUESTS:// 10024 return(true); default: return(false); } } public: CTradeManager(int magic, int slippage, int maxRetries = 5) { m_magic = magic; m_maxRetries = maxRetries; m_trade.SetExpertMagicNumber(magic); m_trade.SetDeviationInPoints(slippage); } bool Init(string symbol) { SetFillingPolicy(m_trade, symbol); return(true); } bool Buy(string symbol, double lots, double sl = 0, double tp = 0, string comment = "") { for(int attempt = 0; attempt < m_maxRetries; attempt++) { if(attempt > 0) Sleep(1000 * (int)MathPow(2, attempt - 1)); double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); if(m_trade.Buy(lots, symbol, ask, sl, tp, comment)) { uint retcode = m_trade.ResultRetcode(); if(retcode == TRADE_RETCODE_DONE || retcode == TRADE_RETCODE_PLACED) return(true); if(!IsRetryable(retcode)) { Print("Buy failed (non-retryable): ", retcode, " - ", m_trade.ResultRetcodeDescription()); return(false); } Print("Buy attempt ", attempt + 1, " retryable error: ", retcode); } else { uint retcode = m_trade.ResultRetcode(); if(!IsRetryable(retcode)) { Print("Buy failed (non-retryable): ", retcode, " - ", m_trade.ResultRetcodeDescription()); return(false); } Print("Buy attempt ", attempt + 1, " retryable error: ", retcode); } } Print("Buy failed after ", m_maxRetries, " attempts"); return(false); } bool Sell(string symbol, double lots, double sl = 0, double tp = 0, string comment = "") { for(int attempt = 0; attempt < m_maxRetries; attempt++) { if(attempt > 0) Sleep(1000 * (int)MathPow(2, attempt - 1)); double bid = SymbolInfoDouble(symbol, SYMBOL_BID); if(m_trade.Sell(lots, symbol, bid, sl, tp, comment)) { uint retcode = m_trade.ResultRetcode(); if(retcode == TRADE_RETCODE_DONE || retcode == TRADE_RETCODE_PLACED) return(true); if(!IsRetryable(retcode)) { Print("Sell failed (non-retryable): ", retcode, " - ", m_trade.ResultRetcodeDescription()); return(false); } Print("Sell attempt ", attempt + 1, " retryable error: ", retcode); } else { uint retcode = m_trade.ResultRetcode(); if(!IsRetryable(retcode)) { Print("Sell failed (non-retryable): ", retcode, " - ", m_trade.ResultRetcodeDescription()); return(false); } Print("Sell attempt ", attempt + 1, " retryable error: ", retcode); } } Print("Sell failed after ", m_maxRetries, " attempts"); return(false); } CTrade* GetTrade() { return(&m_trade); } }; ``` ### Position Loop Patterns (MQL5) #### Iterate All Positions with Filtering ```mql5 void ProcessPositions(string symbol, int magic) { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != magic) continue; double volume = PositionGetDouble(POSITION_VOLUME); double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double profit = PositionGetDouble(POSITION_PROFIT); long type = PositionGetInteger(POSITION_TYPE); // Process position... } } ``` #### Close All Positions for a Symbol ```mql5 void CloseAllPositions(string symbol, int magic) { CTrade trade; trade.SetExpertMagicNumber(magic); SetFillingPolicy(trade, symbol); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != magic) continue; if(!trade.PositionClose(ticket)) Print("Failed to close position ", ticket, ": ", trade.ResultRetcodeDescription()); } } ``` #### Count Positions by Type ```mql5 int CountPositions(string symbol, int magic, ENUM_POSITION_TYPE posType = -1) { int count = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != magic) continue; if(posType >= 0 && (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != posType) continue; count++; } return(count); } ``` --- ## Risk Management ### Position Sizing Formulas #### Fixed Lot ```mql5 double FixedLot(double lotSize, string symbol) { double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); lotSize = MathFloor(lotSize / lotStep) * lotStep; lotSize = MathMax(minLot, MathMin(maxLot, lotSize)); return(NormalizeDouble(lotSize, 2)); } ``` Simplest approach but ignores account growth and does not adapt to varying stop loss distances. #### Percentage Risk (Recommended Default) The core formula: ``` lots = (AccountBalance * RiskPercent / 100) / (SL_Points * TickValue / TickSize) ``` **MQL5 Implementation:** ```mql5 double CalcLotSize(string symbol, double riskPercent, double slPoints) { if(slPoints <= 0) { Print("CalcLotSize: SL distance must be > 0"); return(0); } double balance = AccountInfoDouble(ACCOUNT_BALANCE); double tickVal = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if(tickVal == 0 || tickSize == 0) { Print("CalcLotSize: Invalid tick info for ", symbol); return(minLot); } double riskMoney = balance * riskPercent / 100.0; double slValue = slPoints * point * (tickVal / tickSize); double lots = riskMoney / slValue; // Round down to lot step lots = MathFloor(lots / lotStep) * lotStep; // Clamp lots = MathMax(minLot, MathMin(maxLot, lots)); // Verify margin is sufficient double margin = 0; if(!OrderCalcMargin(ORDER_TYPE_BUY, symbol, lots, SymbolInfoDouble(symbol, SYMBOL_ASK), margin)) { Print("CalcLotSize: OrderCalcMargin failed"); return(minLot); } if(margin > AccountInfoDouble(ACCOUNT_MARGIN_FREE)) { Print("CalcLotSize: Insufficient free margin. Required: ", margin); // Reduce lots to fit available margin double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE) * 0.95; // 5% safety buffer lots = lots * (freeMargin / margin); lots = MathFloor(lots / lotStep) * lotStep; lots = MathMax(minLot, lots); } return(NormalizeDouble(lots, 2)); } ``` **MQL4 Implementation:** ```mql4 double CalcLotSize_MQL4(string symbol, double riskPercent, double slPoints) { if(slPoints <= 0) { Print("CalcLotSize: SL distance must be > 0"); return(0); } double balance = AccountBalance(); double tickVal = MarketInfo(symbol, MODE_TICKVALUE); double tickSize = MarketInfo(symbol, MODE_TICKSIZE); double minLot = MarketInfo(symbol, MODE_MINLOT); double maxLot = MarketInfo(symbol, MODE_MAXLOT); double lotStep = MarketInfo(symbol, MODE_LOTSTEP); double point = MarketInfo(symbol, MODE_POINT); if(tickVal == 0 || tickSize == 0) { Print("CalcLotSize: Invalid tick info for ", symbol); return(minLot); } double riskMoney = balance * riskPercent / 100.0; double slValue = slPoints * point * (tickVal / tickSize); double lots = riskMoney / slValue; lots = MathFloor(lots / lotStep) * lotStep; lots = MathMax(minLot, MathMin(maxLot, lots)); // Verify margin double marginRequired = AccountFreeMarginCheck(symbol, OP_BUY, lots); if(marginRequired <= 0) { Print("CalcLotSize: Insufficient margin"); return(minLot); } return(NormalizeDouble(lots, 2)); } ``` #### Kelly Criterion ``` kelly_fraction = (winRate * avgWin - (1 - winRate) * avgLoss) / avgWin ``` ```mql5 double KellyLotSize(string symbol, double winRate, double avgWin, double avgLoss, double slPoints, double kellyFraction = 0.25) { // Full Kelly is too aggressive; use quarter-Kelly as maximum double kelly = (winRate * avgWin - (1.0 - winRate) * avgLoss) / avgWin; if(kelly <= 0) { Print("Kelly criterion negative - system has no edge"); return(0); } double riskPercent = kelly * kellyFraction * 100.0; riskPercent = MathMin(riskPercent, 5.0); // Hard cap at 5% return(CalcLotSize(symbol, riskPercent, slPoints)); } ``` **WARNING:** Full Kelly sizing leads to extreme drawdowns in practice. Always use quarter-Kelly (kellyFraction = 0.25) or less. Even quarter-Kelly can produce 40-50% drawdowns. Most professional traders use eighth-Kelly or fixed fractional risk of 1-2%. ### Multi-Market Lot Calculation Different instruments have different contract sizes and tick values: | Market | Typical Contract Size | Notes | |---|---|---| | Forex | 100,000 units (1 standard lot) | Tick value varies by pair denomination | | Indices (CFD) | Variable (often 1 or 10 per point) | Contract size varies by broker and index | | Gold (XAUUSD) | 100 troy ounces (1 lot) | Large tick value per lot | | Oil (crude) | 1,000 barrels (1 lot) | Varies by broker | | Crypto | Variable | Bitcoin often 1 BTC per lot | **Always use SymbolInfo functions to get accurate values per instrument:** ```mql5 void PrintSymbolTradeInfo(string symbol) { double contractSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); PrintFormat("%s: Contract=%.2f TickVal=%.5f TickSize=%.5f Point=%.5f Digits=%d", symbol, contractSize, tickValue, tickSize, point, digits); PrintFormat(" MinLot=%.2f MaxLot=%.2f LotStep=%.2f", minLot, maxLot, lotStep); } ``` **Key rule:** Never hardcode tick values or contract sizes. The `CalcLotSize` function above handles all markets correctly because it reads the values dynamically from the symbol properties. ### Drawdown Control ```mql5 class CDrawdownManager { private: double m_startBalance; double m_dailyStartBalance; double m_maxDailyLossPercent; // e.g., 3.0 double m_maxTotalDDPercent; // e.g., 10.0 datetime m_currentDay; bool m_dailyLocked; bool m_totalLocked; public: CDrawdownManager(double maxDailyLoss = 3.0, double maxTotalDD = 10.0) { m_maxDailyLossPercent = maxDailyLoss; m_maxTotalDDPercent = maxTotalDD; m_startBalance = AccountInfoDouble(ACCOUNT_BALANCE); m_dailyStartBalance = m_startBalance; m_currentDay = iTime(_Symbol, PERIOD_D1, 0); m_dailyLocked = false; m_totalLocked = false; } void OnNewDay() { datetime today = iTime(_Symbol, PERIOD_D1, 0); if(today != m_currentDay) { m_currentDay = today; m_dailyStartBalance = AccountInfoDouble(ACCOUNT_BALANCE); m_dailyLocked = false; // Total lock is NOT reset on new day } } bool CanTrade() { OnNewDay(); double equity = AccountInfoDouble(ACCOUNT_EQUITY); // Daily drawdown check double dailyLoss = (m_dailyStartBalance - equity) / m_dailyStartBalance * 100.0; if(dailyLoss >= m_maxDailyLossPercent) { if(!m_dailyLocked) { Print("Daily drawdown limit reached: ", DoubleToString(dailyLoss, 2), "%"); m_dailyLocked = true; } return(false); } // Total drawdown check double totalDD = (m_startBalance - equity) / m_startBalance * 100.0; if(totalDD >= m_maxTotalDDPercent) { if(!m_totalLocked) { Print("Total drawdown limit reached: ", DoubleToString(totalDD, 2), "%"); m_totalLocked = true; } return(false); } return(true); } void Reset() { m_startBalance = AccountInfoDouble(ACCOUNT_BALANCE); m_dailyStartBalance = m_startBalance; m_dailyLocked = false; m_totalLocked = false; } double GetDailyDD() { double equity = AccountInfoDouble(ACCOUNT_EQUITY); return((m_dailyStartBalance - equity) / m_dailyStartBalance * 100.0); } double GetTotalDD() { double equity = AccountInfoDouble(ACCOUNT_EQUITY); return((m_startBalance - equity) / m_startBalance * 100.0); } }; ``` ### Margin Verification Before Trade ```mql5 bool HasSufficientMargin(string symbol, ENUM_ORDER_TYPE orderType, double lots, double safetyFactor = 0.9) { double price = (orderType == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); double margin = 0; if(!OrderCalcMargin(orderType, symbol, lots, price, margin)) { Print("OrderCalcMargin failed: ", GetLastError()); return(false); } double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); if(margin > freeMargin * safetyFactor) { PrintFormat("Insufficient margin. Required: %.2f, Available: %.2f (safety: %.0f%%)", margin, freeMargin, safetyFactor * 100); return(false); } return(true); } ``` **MQL4 equivalent:** ```mql4 bool HasSufficientMargin_MQL4(string symbol, int orderType, double lots) { double freeMargin = AccountFreeMarginCheck(symbol, orderType, lots); if(freeMargin <= 0 || GetLastError() == ERR_NOT_ENOUGH_MONEY) { Print("Insufficient margin for ", lots, " lots on ", symbol); return(false); } return(true); } ``` **Common mistakes:** - Setting `g_symbol` after `LoadRuntimeValues()` → GV keys become `SmartEntry____field` (empty symbol) - No first-run check → defaults of 0 cause validation to fail or EA to not trade - Using `FILE_WRITE | FILE_COMMON` without `FILE_REWRITE` → file locked on repeated saves - Saving inside `OnDeinit` but `OnInit` runs before `OnDeinit` finishes (TF change race condition) --- ## Trailing Stop Implementations ### CTrailingManager (MQL5) ```mql5 #include #include #include class CTrailingManager { private: CTrade m_trade; int m_magic; bool ModifySL(ulong ticket, double newSL) { if(!PositionSelectByTicket(ticket)) return(false); double currentSL = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); string symbol = PositionGetString(POSITION_SYMBOL); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); newSL = NormalizeDouble(newSL, digits); // Skip if SL hasn't changed meaningfully if(MathAbs(currentSL - newSL) < SymbolInfoDouble(symbol, SYMBOL_POINT)) return(true); return(m_trade.PositionModify(ticket, newSL, tp)); } public: CTrailingManager(int magic, int slippage = 10) { m_magic = magic; m_trade.SetExpertMagicNumber(magic); m_trade.SetDeviationInPoints(slippage); } //--- Fixed Trailing Stop --- // trailPoints: distance in points to trail behind current price // activationPoints: minimum profit in points before trailing activates (0 = immediate) void TrailFixed(string symbol, double trailPoints, double activationPoints = 0) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double stopLevel = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * point; double trailDist = MathMax(trailPoints * point, stopLevel); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != m_magic) continue; double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentSL = PositionGetDouble(POSITION_SL); long type = PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) { double bid = SymbolInfoDouble(symbol, SYMBOL_BID); double profitDist = bid - openPrice; if(activationPoints > 0 && profitDist < activationPoints * point) continue; double newSL = bid - trailDist; if(newSL > currentSL || currentSL == 0) ModifySL(ticket, newSL); } else if(type == POSITION_TYPE_SELL) { double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double profitDist = openPrice - ask; if(activationPoints > 0 && profitDist < activationPoints * point) continue; double newSL = ask + trailDist; if(newSL < currentSL || currentSL == 0) ModifySL(ticket, newSL); } } } //--- ATR-Based Trailing Stop --- // atrHandle: handle from iATR() // multiplier: ATR multiplier for trail distance (e.g., 2.0) void TrailATR(string symbol, int atrHandle, double multiplier = 2.0) { double atrBuffer[]; ArraySetAsSeries(atrBuffer, true); if(CopyBuffer(atrHandle, 0, 1, 1, atrBuffer) != 1) { Print("TrailATR: Failed to copy ATR buffer"); return; } double atrValue = atrBuffer[0]; double trailDist = atrValue * multiplier; double stopLevel = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * SymbolInfoDouble(symbol, SYMBOL_POINT); trailDist = MathMax(trailDist, stopLevel); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != m_magic) continue; double currentSL = PositionGetDouble(POSITION_SL); long type = PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) { double bid = SymbolInfoDouble(symbol, SYMBOL_BID); double newSL = bid - trailDist; if(newSL > currentSL || currentSL == 0) ModifySL(ticket, newSL); } else if(type == POSITION_TYPE_SELL) { double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double newSL = ask + trailDist; if(newSL < currentSL || currentSL == 0) ModifySL(ticket, newSL); } } } //--- Parabolic SAR Trailing --- // sarHandle: handle from iSAR() // Uses bar[1] (last completed bar) for confirmed SAR value void TrailParabolicSAR(string symbol, int sarHandle) { double sarBuffer[]; ArraySetAsSeries(sarBuffer, true); // Use bar index 1 (last closed bar) for confirmed value if(CopyBuffer(sarHandle, 0, 1, 1, sarBuffer) != 1) { Print("TrailSAR: Failed to copy SAR buffer"); return; } double sarValue = sarBuffer[0]; double stopLevel = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * SymbolInfoDouble(symbol, SYMBOL_POINT); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != m_magic) continue; double currentSL = PositionGetDouble(POSITION_SL); double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); long type = PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) { double bid = SymbolInfoDouble(symbol, SYMBOL_BID); // SAR must be below price for buy (confirming uptrend) if(sarValue >= bid) continue; // Ensure minimum stop level distance if((bid - sarValue) < stopLevel) continue; // Only move SL forward if(sarValue > currentSL || currentSL == 0) ModifySL(ticket, sarValue); } else if(type == POSITION_TYPE_SELL) { double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); // SAR must be above price for sell (confirming downtrend) if(sarValue <= ask) continue; // Ensure minimum stop level distance if((sarValue - ask) < stopLevel) continue; // Only move SL closer to entry (lower for sells) if(sarValue < currentSL || currentSL == 0) ModifySL(ticket, sarValue); } } } //--- Break-Even Logic --- // activationPoints: profit in points before moving to break-even // lockInPoints: points above entry to lock in (0 = exact entry, positive = small profit) void ManageBreakEven(string symbol, double activationPoints, double lockInPoints = 0) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != m_magic) continue; double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentSL = PositionGetDouble(POSITION_SL); long type = PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) { double bid = SymbolInfoDouble(symbol, SYMBOL_BID); double beSL = openPrice + lockInPoints * point; // Check if profit threshold reached and SL not already at or above BE if((bid - openPrice) >= activationPoints * point) { if(currentSL < beSL) ModifySL(ticket, beSL); } } else if(type == POSITION_TYPE_SELL) { double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double beSL = openPrice - lockInPoints * point; if((openPrice - ask) >= activationPoints * point) { if(currentSL > beSL || currentSL == 0) ModifySL(ticket, beSL); } } } } }; ``` **Usage example:** ```mql5 // In global scope CTrailingManager *trailMgr; int atrHandle; int sarHandle; int OnInit() { trailMgr = new CTrailingManager(MagicNumber, Slippage); atrHandle = iATR(_Symbol, PERIOD_CURRENT, 14); sarHandle = iSAR(_Symbol, PERIOD_CURRENT, 0.02, 0.2); return(INIT_SUCCEEDED); } void OnTick() { // Choose one trailing method: // trailMgr.TrailFixed(_Symbol, 50, 30); // trailMgr.TrailATR(_Symbol, atrHandle, 2.0); // trailMgr.TrailParabolicSAR(_Symbol, sarHandle); // Break-even can be combined with any trailing method trailMgr.ManageBreakEven(_Symbol, 30, 5); // Activate at 30pts profit, lock 5pts } void OnDeinit(const int reason) { delete trailMgr; IndicatorRelease(atrHandle); IndicatorRelease(sarHandle); } ``` ### Fixed Trailing Stop (MQL4 Version) ```mql4 void TrailFixedMQL4(string symbol, int magic, double trailPoints) { double point = MarketInfo(symbol, MODE_POINT); int digits = (int)MarketInfo(symbol, MODE_DIGITS); double stopLevel = MarketInfo(symbol, MODE_STOPLEVEL) * point; double trailDist = MathMax(trailPoints * point, stopLevel); for(int i = OrdersTotal() - 1; i >= 0; i--) { if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if(OrderSymbol() != symbol) continue; if(OrderMagicNumber() != magic) continue; if(OrderType() == OP_BUY) { double bid = MarketInfo(symbol, MODE_BID); double newSL = NormalizeDouble(bid - trailDist, digits); if(newSL > OrderStopLoss() || OrderStopLoss() == 0) OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE); } else if(OrderType() == OP_SELL) { double ask = MarketInfo(symbol, MODE_ASK); double newSL = NormalizeDouble(ask + trailDist, digits); if(newSL < OrderStopLoss() || OrderStopLoss() == 0) OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE); } } } ``` --- ## Trade Return Codes (MQL5) | Constant | Value | Meaning | |---|---|---| | `TRADE_RETCODE_REQUOTE` | 10004 | Requote - price changed during processing | | `TRADE_RETCODE_REJECT` | 10006 | Request rejected by server | | `TRADE_RETCODE_CANCEL` | 10007 | Request cancelled by trader | | `TRADE_RETCODE_PLACED` | 10008 | Order placed (pending) | | `TRADE_RETCODE_DONE` | 10009 | Request completed successfully | | `TRADE_RETCODE_DONE_PARTIAL` | 10010 | Request partially completed | | `TRADE_RETCODE_TIMEOUT` | 10012 | Request timed out | | `TRADE_RETCODE_INVALID` | 10013 | Invalid request parameters | | `TRADE_RETCODE_INVALID_VOLUME` | 10014 | Invalid volume (lot size) | | `TRADE_RETCODE_INVALID_PRICE` | 10015 | Invalid price | | `TRADE_RETCODE_INVALID_STOPS` | 10016 | Invalid SL/TP levels | | `TRADE_RETCODE_TRADE_DISABLED` | 10017 | Trading disabled for this symbol | | `TRADE_RETCODE_MARKET_CLOSED` | 10018 | Market is closed | | `TRADE_RETCODE_NO_MONEY` | 10019 | Insufficient funds | | `TRADE_RETCODE_PRICE_CHANGED` | 10020 | Price changed since request | | `TRADE_RETCODE_PRICE_OFF` | 10021 | No quotes available | | `TRADE_RETCODE_INVALID_EXPIRATION` | 10022 | Invalid order expiration | | `TRADE_RETCODE_ORDER_CHANGED` | 10023 | Order state changed | | `TRADE_RETCODE_TOO_MANY_REQUESTS` | 10024 | Too many requests | | `TRADE_RETCODE_CONNECTION` | 10031 | No connection to trade server | **Handling guidance:** - **Success:** 10008 and 10009 indicate the trade was accepted. Always check for both. - **Retryable:** 10004, 10012, 10020, 10021, 10024, 10031 can be retried with backoff. - **Fix and retry:** 10014 (adjust volume), 10015 (refresh price), 10016 (adjust stops). - **Fatal:** 10006, 10013, 10017, 10018, 10019 usually require user intervention or logic changes. ### Interpreting Trade Results ```mql5 void LogTradeResult(CTrade &trade) { uint retcode = trade.ResultRetcode(); switch(retcode) { case TRADE_RETCODE_DONE: case TRADE_RETCODE_PLACED: PrintFormat("Trade OK: Order #%d, Deal #%d, Volume=%.2f, Price=%.5f", trade.ResultOrder(), trade.ResultDeal(), trade.ResultVolume(), trade.ResultPrice()); break; case TRADE_RETCODE_DONE_PARTIAL: PrintFormat("Partial fill: Volume=%.2f of requested. Order #%d", trade.ResultVolume(), trade.ResultOrder()); break; default: PrintFormat("Trade FAILED: retcode=%u (%s), Comment: %s", retcode, trade.ResultRetcodeDescription(), trade.ResultComment()); break; } } ```