//+------------------------------------------------------------------+ //| StopLossManagement.mqh | //| Jay Davis | //| 512jay.github.io | //+------------------------------------------------------------------+ #property copyright "Jay Davis" #property link "512jay.github.io" // v1 bool Debug_StopLossManagment = false; input group "" input group "|----------Stop Loss Management----------|" input bool useVirtualStops = true; // Use virtual stops input int MinimumPointsForStopLossMove = 1; // Minimum number of points to move any Stoploss input int SendNoticeTimer = 60; // Minutes between stoploss notices input group "||--- Average True Range Settings" input bool useATRStopLoss = true; // Use ATR for stop loss? input double atrMultiplier = 1; // ATR Multiplier input ENUM_TIMEFRAMES TimeframeOfATR = PERIOD_CURRENT; // Timeframe for ATR calculations input int PeriodsInATR = 14; // periods in calculation input group "||--- Percentage Stop Loss Settings" input bool usePercentageStopLoss = false; // Use a percentage stop loss? input double BuyStopLossPercentage = 0.1; // BUY Stop-Loss Percentage, non zero, non negative; input double SellStopLossPercentage = 0.1; // SELL Stop-Loss Percentage, non zero, non negative; input group "||--- Idle Stoploss movement" input bool BuyMoveStopLossIfIdle = false; // Move BUY Stop-Loss if Idle? input int BuyIdleMinutesRequiringAMove = 25; // Number of Idle minutes requiring a BUY Stop-Loss move input int BuyIdleMovements = 5; // Number of possible BUY idle movments input double BuyPercentageToMoveStopLossIfIdle = 0.01; // % to move BUY Stop-Loss when idle input bool SellMoveStopLossIfIdle = false; // Move SELL Stop-Loss if Idle? input int SellIdleMinutesRequiringAMove = 15; // Number of Idle minutes requiring a SELL Stop-Loss move input double SellPercentageToMoveStopLossIfIdle = 0.01; // % to move SELL Stop-Loss when idle input int SellIdleMovements = 5; // Number of possible SELL idle movments datetime MostRecentNoticeTime = 0; // Global variables double trailingDistance = 0; //+------------------------------------------------------------------+ //| Sends the current stops to WhatsApp by SendNoticeTimer | //+------------------------------------------------------------------+ void SendModifyNotice() { if(TimeCurrent() - (SendNoticeTimer * 60) > MostRecentNoticeTime) { int total = ArraySize(Virt); for(int i = 0; i < total; i++) if(Virt[i].active == true) NoticeMessage("ITSL", Virt[i], 3); MostRecentNoticeTime = TimeCurrent(); } } //+------------------------------------------------------------------+ //| Gets and returns the ATR value | //| Returns ATR value | //+------------------------------------------------------------------+ double AverageTrueRange(string name, ENUM_TIMEFRAMES period, int atr_periods) { int handle = iATR(name, period, atr_periods); double atr[]; //--- if the handle is not created if(handle == INVALID_HANDLE) { //--- tell about the failure and output the error code PrintFormat("Failed to create handle of the iATR indicator for the symbol %s/%s, error code %d", name, EnumToString(period), GetLastError()); //--- the indicator is stopped early return(INIT_FAILED); } CopyBuffer(handle, 0, 0, 1, atr); trailingDistance = NormalizeDouble(atr[0] * atrMultiplier, (int)SymbolInfoInteger(name, SYMBOL_DIGITS)); return trailingDistance; } //+------------------------------------------------------------------+ //| Returns the amount for the given percentage. | //+------------------------------------------------------------------+ double GetPercentage(string name, ENUM_ORDER_TYPE dir, double percentage) { double bid, ask, amount = 0.0; if(dir == ORDER_TYPE_BUY) { bid = SymbolInfoDouble(name, SYMBOL_BID); amount = bid * percentage / 100; } if(dir == ORDER_TYPE_SELL) { ask = SymbolInfoDouble(name, SYMBOL_ASK); amount = ask * percentage / 100; } trailingDistance = NormalizeDouble(amount, (int)SymbolInfoInteger(name, SYMBOL_DIGITS)); return trailingDistance; } //+------------------------------------------------------------------+ //| Returns the stoploss based on input values | //+------------------------------------------------------------------+ double GetStopLoss(string name, ENUM_ORDER_TYPE dir, bool checkIdle, bool print = false) { double bid = 0, ask = 0, percentage_amount = 0, atr_amount = 0, stopLoss = 0; if(dir == ORDER_TYPE_BUY) { if(useATRStopLoss && !checkIdle) { atr_amount = AverageTrueRange(name, TimeframeOfATR, PeriodsInATR); stopLoss = atr_amount; //if(percentage_amount != 0) // stopLoss = fmin(percentage_amount, atr_amount); } if(usePercentageStopLoss && !checkIdle) { percentage_amount = GetPercentage(name, dir, BuyStopLossPercentage); stopLoss = percentage_amount; } if(checkIdle && BuyMoveStopLossIfIdle) { percentage_amount = GetPercentage(name, dir, BuyPercentageToMoveStopLossIfIdle); stopLoss = percentage_amount; } bid = SymbolInfoDouble(Sym, SYMBOL_BID); if(print) Print("Bid ", bid, ", stoploss ", stopLoss, " bid - sl ", bid - stopLoss); stopLoss = bid - stopLoss;//trailingDistance; } else if(dir == ORDER_TYPE_SELL) { if(useATRStopLoss && !checkIdle) { atr_amount = AverageTrueRange(name, TimeframeOfATR, PeriodsInATR); stopLoss = atr_amount; //if(percentage_amount != 0) // stopLoss = fmin(percentage_amount, atr_amount); } if(usePercentageStopLoss && !checkIdle) { percentage_amount = GetPercentage(name, dir, SellStopLossPercentage); stopLoss = percentage_amount; } if(SellMoveStopLossIfIdle && checkIdle) { percentage_amount = GetPercentage(name, dir, SellStopLossPercentage); stopLoss = percentage_amount; } ask = SymbolInfoDouble(Sym, SYMBOL_ASK); if(print) Print("Ask ", ask, ", stoploss ", stopLoss, " ask + sl ", ask + stopLoss); stopLoss = ask + stopLoss;//trailingDistance; } if(stopLoss == ask || stopLoss == bid) return (0); else return NormalizeDouble(stopLoss, (int)SymbolInfoInteger(name, SYMBOL_DIGITS)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ModifyStopLoss() { SendModifyNotice(); if(BuyMoveStopLossIfIdle || SellMoveStopLossIfIdle) ModifyStopLossByIdleTime(); if(usePercentageStopLoss || useATRStopLoss) ModifyStops(); } //+------------------------------------------------------------------+ //| Returns the current stoploss and creates one if it does not exist| //+------------------------------------------------------------------+ double CurrentServerStopLoss() { double currentStopLoss = PositionGetDouble(POSITION_SL); if(currentStopLoss == 0 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { currentStopLoss = GetStopLoss(Sym, ORDER_TYPE_BUY, false); if(!trade.PositionModify(Sym, currentStopLoss, 0)) Print(" Buy Modify fails, bid: ", SymbolInfoDouble(Sym, SYMBOL_BID), " current stop loss: ", currentStopLoss); } if(currentStopLoss == 0 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { currentStopLoss = GetStopLoss(Sym, ORDER_TYPE_SELL, false);; if(!trade.PositionModify(Sym, currentStopLoss, 0)) Print(" Sell Modify fails, ask: ", SymbolInfoDouble(Sym, SYMBOL_ASK), " current stop loss: ", currentStopLoss); } return currentStopLoss; } //+------------------------------------------------------------------+ //| Returns the current stoploss and creates one if it does not exist| //+------------------------------------------------------------------+ double CurrentVirtualStopLoss(int i) { if(Virt[i].request.sl == 0) { if(Virt[i].request.type == ORDER_TYPE_BUY) Virt[i].request.sl = GetStopLoss(Sym, ORDER_TYPE_BUY, false); if(Virt[i].request.type == ORDER_TYPE_SELL) Virt[i].request.sl = GetStopLoss(Sym, ORDER_TYPE_SELL, false); } return Virt[i].request.sl; } //+------------------------------------------------------------------+ //| Returns the current buy stop loss value with trailing | //+------------------------------------------------------------------+ double TrailingBuyStopLoss() { double bid = SymbolInfoDouble(Sym, SYMBOL_BID); return bid - trailingDistance; } //+------------------------------------------------------------------+ //| Returns the current sell stop loss value with trailing | //+------------------------------------------------------------------+ double TrailingSellStopLoss() { double ask = SymbolInfoDouble(Sym, SYMBOL_ASK); return ask + trailingDistance; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ModifyServerSideStops() { double buyStopLoss = 0; // = GetStopLoss(Sym, Buy, false); double sellStopLoss = 0; // = GetStopLoss(Sym, Sell, false); for(int i = PositionsTotal() - 1; i >= 0; i--) if(Sym == PositionGetSymbol(i)) { if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) if(TrailingBuyStopLoss() - CurrentServerStopLoss() > MinMovement) if(!trade.PositionModify(Sym, TrailingBuyStopLoss(), 0)) Print("[FAIL]*** Failed BUY Stop Loss at ", TrailingBuyStopLoss(), " Current was ", CurrentServerStopLoss()); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) if(CurrentServerStopLoss() - TrailingSellStopLoss() > MinMovement) if(!trade.PositionModify(Sym, TrailingSellStopLoss(), 0)) Print("[FAIL]*** Failed SELL Stop Loss at ", TrailingSellStopLoss(), " Current was ", CurrentServerStopLoss()); } } //+------------------------------------------------------------------+ //| Modifies virtual stop losses | //+------------------------------------------------------------------+ void ModifyVirtualStops() { //Print(__FUNCTION__); double newStopLoss; for(int i = ArraySize(Virt) - 1; i >= 0; i--) if(Virt[i].active) { double oldStopLoss = Virt[i].request.sl; if(oldStopLoss == 0.0) oldStopLoss = GetStopLoss(Virt[i].request.symbol, Virt[i].request.type, false); newStopLoss = GetStopLoss(Virt[i].request.symbol, Virt[i].request.type, false); if(Virt[i].request.type == ORDER_TYPE_BUY) if(newStopLoss - oldStopLoss > MinMovement) { Print("Buy trailing stoploss ", TrailingBuyStopLoss(), " - current virtual stoploss ", CurrentVirtualStopLoss(i), " is greater than min movement ", MinMovement); Virt[i].request.sl = newStopLoss; InsertResumeItem(Virt[i], DatabaseName); DisplayLines(Virt[i]); if(Virt[i].request.sl > Virt[i].request.price) ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrNavy); if(!MQLInfoInteger(MQL_TESTER)) { double dist = Virt[i].request.sl - oldStopLoss; dist /= SymbolInfoDouble(Virt[i].request.symbol, SYMBOL_POINT); int distance = (int) dist; string s = "s"; if(distance == 1) s = ""; string stoplossMessage = MQLInfoString(MQL_PROGRAM_NAME) + " " + Virt[i].request.symbol + " stoploss: " + (string)oldStopLoss + " moved " + (string)distance + " point" + s + " to: " + (string)Virt[i].request.sl; if(Debug_StopLossManagment) { SendToWhatsAppAPI("[Debug]::" + stoplossMessage); Print(stoplossMessage); } //SendModifyNotice(Virt[i].request.type, Virt[i].request.sl, Virt[i].request.volume, i); //Print(Virt[i].request.symbol, " stoploss: ", oldStopLoss, " moved ", distance, " points", s, " to: ", Virt[i].request.sl); } HLineMove(0, "StopLoss", Virt[i].request.sl); } if(Virt[i].request.type == ORDER_TYPE_SELL) if(CurrentVirtualStopLoss(i) - newStopLoss > MinMovement) { Print("Sell current virtual stoploss ", CurrentVirtualStopLoss(i), " - trailing stoploss ", TrailingSellStopLoss(), " is greater than min movement ", MinMovement); Virt[i].request.sl = newStopLoss; InsertResumeItem(Virt[i], DatabaseName); DisplayLines(Virt[i]); if(Virt[i].request.sl < Virt[i].request.price) ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrNavy); if(!MQLInfoInteger(MQL_TESTER)) { double dist = Virt[i].request.sl - oldStopLoss; dist /= SymbolInfoDouble(Virt[i].request.symbol, SYMBOL_POINT); int distance = (int) dist; string s = "s"; if(distance == 1 || distance == -1) s = ""; string stoplossMessage = MQLInfoString(MQL_PROGRAM_NAME) + " " + Virt[i].request.symbol + " stoploss: " + (string)oldStopLoss + " moved " + (string)distance + " point" + s + " to: " + (string)Virt[i].request.sl; if(Debug_StopLossManagment) { SendToWhatsAppAPI("[Debug]::" + stoplossMessage); Print(stoplossMessage); } //SendModifyNotice(Virt[i].request.type, Virt[i].request.sl, Virt[i].request.volume, i); //Print(Virt[i].request.symbol, " stoploss: ", oldStopLoss, " moved ", distance, " point", s, " to: ", Virt[i].request.sl); } HLineMove(0, "StopLoss", Virt[i].request.sl); } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ModifyStops() { if(!useVirtualStops) ModifyServerSideStops(); ModifyVirtualStops(); } //+------------------------------------------------------------------+ //| Checks and modifies stop losses that are too long idle | //+------------------------------------------------------------------+ void ModifyStopLossByIdleTime() { datetime idleTime = TimeCurrent() - TimeStopLossWasLastModified; double idleTimeInMinutes = (long) idleTime / 60.0; // 1 min = 60 seconds; bool aModificationHappened = false; string type = " BUY "; if((idleTimeInMinutes > BuyIdleMinutesRequiringAMove && IdleMovements < BuyIdleMovements && BuyMoveStopLossIfIdle) || (idleTimeInMinutes > SellIdleMinutesRequiringAMove && IdleMovements < SellIdleMovements && SellMoveStopLossIfIdle)) { for(int i = PositionsTotal() - 1; i >= 0; i--) if(Sym == PositionGetSymbol(i)) { // Get the current stop loss double currentStopLoss = PositionGetDouble(POSITION_SL); // If current stop loss = 0 skip if(currentStopLoss == 0) continue; ulong ticket = PositionGetInteger(POSITION_TICKET); //double currentStopLoss = PositionGetDouble(POSITION_SL); // Get the direction if(BuyMoveStopLossIfIdle) if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) if(GetStopLoss(Sym, ORDER_TYPE_BUY, true) - currentStopLoss > MinMovement) if(trade.PositionModify(ticket, GetStopLoss(Sym, ORDER_TYPE_BUY, true), 0)) aModificationHappened = true; else Print("[FAIL]*** Failed BUY Stop Loss at ", GetStopLoss(Sym, ORDER_TYPE_BUY, true), " Current was ", currentStopLoss); if(SellMoveStopLossIfIdle) if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) if(currentStopLoss - GetStopLoss(Sym, ORDER_TYPE_SELL, true) > MinMovement) if(trade.PositionModify(ticket, GetStopLoss(Sym, ORDER_TYPE_SELL, true), 0)) { type = " SELL "; aModificationHappened = true; } else Print("[FAIL]*** Failed SELL Stop Loss at ", GetStopLoss(Sym, ORDER_TYPE_SELL, true), " Current was ", currentStopLoss); } for(int i = ArraySize(Virt) - 1; i >= 0; i--) if(Virt[i].active) { // Get the current stop loss double currentStopLoss = Virt[i].request.sl; // Get the current stop loss ulong ticket = Virt[i].request.order; // Get the direction if(BuyMoveStopLossIfIdle) if(Virt[i].request.type == ORDER_TYPE_BUY) if(GetStopLoss(Sym, ORDER_TYPE_BUY, true) - currentStopLoss > MinMovement) { Virt[i].request.sl = GetStopLoss(Sym, ORDER_TYPE_BUY, true); aModificationHappened = true; } if(SellMoveStopLossIfIdle) if(Virt[i].request.type == ORDER_TYPE_SELL) if(currentStopLoss - GetStopLoss(Sym, ORDER_TYPE_SELL, true) > MinMovement) { Virt[i].request.sl = GetStopLoss(Sym, ORDER_TYPE_SELL, true); aModificationHappened = true; } } } if(aModificationHappened) { if(!MQLInfoInteger(MQL_TESTER)) Print("[MOVED", type, "STOPLOSS BY IDLE TIME] movement #", IdleMovements); IdleMovements++; TimeStopLossWasLastModified = TimeCurrent(); } } //+------------------------------------------------------------------+ ////+------------------------------------------------------------------+ ////| Modifies the stoploss if needed | ////+------------------------------------------------------------------+ //void ModifyStopLossByPercentage() // { // double buyStopLoss = GetStopLoss(Sym, Buy, false); // double sellStopLoss = GetStopLoss(Sym, Sell, false); // // for(int i = PositionsTotal() - 1; i >= 0; i--) // if(Sym == PositionGetSymbol(i)) // { // // Get the current stop loss // double currentStopLoss = PositionGetDouble(POSITION_SL); // // If current stop loss = 0 skip // if(currentStopLoss == 0) // continue; // // ulong ticket = PositionGetInteger(POSITION_TICKET); // //double currentStopLoss = PositionGetDouble(POSITION_SL); // // Get the direction // if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY // && currentStopLoss < buyStopLoss) // if(buyStopLoss - currentStopLoss > MinMovement) // if(!trade.PositionModify(ticket, buyStopLoss, 0)) // Print("[FAIL]*** Failed BUY Stop Loss at ", buyStopLoss, // " Current was ", currentStopLoss); // if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL // && currentStopLoss > sellStopLoss) // if(currentStopLoss - sellStopLoss > MinMovement) // if(!trade.PositionModify(ticket, sellStopLoss, 0)) // Print("[FAIL]*** Failed SELL Stop Loss at ", sellStopLoss, // " Current was ", currentStopLoss); // } // // for(int i = ArraySize(Virt) - 1; i >= 0; i--) // if(Virt[i].active) // { // double currentStopLoss = Virt[i].request.sl; // // Get the current stop loss // ulong ticket = Virt[i].request.order; // //double currentStopLoss = PositionGetDouble(POSITION_SL); // // Get the direction // if(Virt[i].request.type == ORDER_TYPE_BUY // && currentStopLoss < buyStopLoss) // if(buyStopLoss - currentStopLoss > MinMovement) // Virt[i].request.sl = buyStopLoss; // if(Virt[i].request.type == ORDER_TYPE_SELL // && currentStopLoss > sellStopLoss) // if(currentStopLoss - sellStopLoss > MinMovement) // Virt[i].request.sl = sellStopLoss; // } // }