//+------------------------------------------------------------------+ //| Universal_Buffer_Reader_EA.mq4 | //| Copyright 2024, Your Name/Company | //| http://yourwebsite.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Your Name/Company" #property link "http://yourwebsite.com" #property version "1.84" // FINAL: Rewrote Trailing Stop with unified, safe ratchet logic #property strict #include // For ErrorDescription() // --- Trade Mode Enum --- enum TradeMode { MODE_INDICATOR, MODE_MANUAL }; // --- New Input Parameters --- extern TradeMode Trade_Mode = MODE_INDICATOR; // Trade Mode Selector extern bool UsePercentBalanceLot = true; // Use custom % of Balance based on TP extern double PercentOfBalanceForProfit = 1.0; // Profit target as % of balance extern double DailyProfitTargetPercent = 2.0; // Daily profit target as % of balance at 01:00 // --- Original Input Parameters --- extern string General_Settings = "--- General Settings ---"; extern double LotSize = 0.03; // Initial Lot Size extern int Slippage = 3; // Slippage in Pips extern int MagicNumber = 24680; extern bool ClearPersistentLossOnStart = false; // !! SET TO TRUE FOR BACKTESTING TO RESET LOSS !! extern bool TakeScreenshotOnOpen = true; extern bool EnableDebugPrints = true; extern bool ExitOnOppositeSignal = false; // --- Recovery & Safety Settings --- extern string Recovery_Settings_Header = "--- Recovery & Safety Settings ---"; extern bool EnableRecoveryMode = true; // Enable/Disable recovery mode completely extern double HighLossThresholdPercent = 30.0; // Trigger conservative mode if loss > this % of balance. (0=Off) extern double HighLossRecoveryDampener = 3.0; // Divide recovery lot by this factor in conservative mode. Must be > 1. extern int ConsecutiveLossesToReset = 3; // Reset accumulated loss after X losses in a row. (0=Off) // --- Time Filtering Settings --- extern string Time_Filter_Settings_Header = "--- Time Filtering Settings ---"; extern bool EnableTimeFiltering = false; // Enable/Disable time filtering extern string Day_Filter_Header = "--- Day of Week Filter ---"; extern bool TradeMondayEnabled = true; // Allow trading on Monday extern bool TradeTuesdayEnabled = true; // Allow trading on Tuesday extern bool TradeWednesdayEnabled = true; // Allow trading on Wednesday extern bool TradeThursdayEnabled = true; // Allow trading on Thursday extern bool TradeFridayEnabled = true; // Allow trading on Friday extern bool TradeSaturdayEnabled = false; // Allow trading on Saturday extern bool TradeSundayEnabled = false; // Allow trading on Sunday extern string Session_Filter_Header = "--- Trading Session Filter ---"; extern bool TradeAsianSessionEnabled = true; // Allow trading during Asian session (00:00-08:00 GMT) extern bool TradeEuropeSessionEnabled = true; // Allow trading during Europe session (07:00-16:00 GMT) extern bool TradeAmericaSessionEnabled = true; // Allow trading during America session (13:00-22:00 GMT) // --- Trailing Stop Settings --- extern string Trailing_Stop_Settings_Header = "--- Trailing Stop Settings ---"; extern bool UseTrailingStop = false; // Enable/Disable the trailing stop feature extern int TrailingStopPips = 300; // Trailing distance in Pips (e.g., 300 on XAUUSD = $3 move) // --- Indicator Settings --- extern string Indicator_Settings = "--- Indicator Settings ---"; extern string IndicatorFileName = "My_Indicator"; extern string Indicator_Params_Help = "Manual settings on chart indicator."; // --- Signal Buffers --- extern int BuySignalBuffer = 0; extern int SellSignalBuffer = 1; extern string Signal_Value_Type_Help = "Buy/Sell buffers contain PRICE levels."; // --- SL/TP Mode Settings --- extern string SLTP_Settings_Header = "--- SL/TP Mode Settings ---"; extern int SLTP_Mode = 0; // --- Take Profit Settings --- extern string TP_Settings_Header = "--- Take Profit Settings ---"; extern int MinimumTakeProfitPips = 300; // Enforce a minimum TP distance in Pips (0=disabled) // --- ATR Settings (Mode 0) --- extern string Settings_Mode_0 = "--- (Mode 0) ATR Settings ---"; extern int AtrPeriod = 14; extern double StopLossAtrMultiplier = 1.5; extern double TakeProfitAtrMultiplier= 3.0; // --- Indicator SL/TP Buffers (Mode 1) --- extern string Settings_Mode_1 = "--- (Mode 1) Indicator SL/TP ---"; extern int StopLossBuffer = 2; extern int TakeProfitBuffer = 3; extern string Mode_1_Value_Type_Help = "SL/TP buffers contain PRICE levels."; // ... (Global Variables, OnInit, OnDeinit, CreateTradeUI etc. are unchanged from v1.82) ... // --- Global Variables --- int slippagePoints; double pointValue; double pipValue; int digits; string eaCommentPrefix; datetime lastTradeSignalBarTime = 0; long chartID; double minLotSize; double maxLotSize; double lotStep; double accumulatedLoss = 0; int lastProcessedOrderTicket = -1; int consecutiveLosses = 0; string gvAccumLossName; string gvConsecLossName; double dailyProfitStartBalance = 0; datetime lastDailyResetTime = 0; double dailyProfitAccumulated = 0; double manualTP = 0; double manualSL = 0; bool manualTradeButtonPressed = false; //+------------------------------------------------------------------+ //| Get Pip Value (Correctly handles 2/3 and 4/5 digit brokers) | //+------------------------------------------------------------------+ double GetPipValue() { if(digits == 3 || digits == 5) return (10 * pointValue); else return pointValue; } //+------------------------------------------------------------------+ //| Check if current day of week is allowed for trading | //+------------------------------------------------------------------+ bool IsDayOfWeekAllowed() { if(!EnableTimeFiltering) return true; int dayOfWeek = DayOfWeek(); switch(dayOfWeek) { case 1: return TradeMondayEnabled; // Monday case 2: return TradeTuesdayEnabled; // Tuesday case 3: return TradeWednesdayEnabled; // Wednesday case 4: return TradeThursdayEnabled; // Thursday case 5: return TradeFridayEnabled; // Friday case 6: return TradeSaturdayEnabled; // Saturday case 0: return TradeSundayEnabled; // Sunday default: return false; } } //+------------------------------------------------------------------+ //| Check if current time is within allowed trading sessions | //+------------------------------------------------------------------+ bool IsSessionTimeAllowed() { if(!EnableTimeFiltering) return true; // Get current GMT time datetime gmtTime = TimeGMT(); int hour = TimeHour(gmtTime); // Asian Session: 00:00 - 08:00 GMT bool isAsianSession = (hour >= 0 && hour < 8); // Europe Session: 07:00 - 16:00 GMT bool isEuropeSession = (hour >= 7 && hour < 16); // America Session: 13:00 - 22:00 GMT bool isAmericaSession = (hour >= 13 && hour < 22); // Check if current time falls within any enabled session if(isAsianSession && TradeAsianSessionEnabled) return true; if(isEuropeSession && TradeEuropeSessionEnabled) return true; if(isAmericaSession && TradeAmericaSessionEnabled) return true; // If no session is active or enabled, return false return false; } //+------------------------------------------------------------------+ //| Check if trading is allowed based on time filters | //+------------------------------------------------------------------+ bool IsTimeFilterAllowed() { if(!EnableTimeFiltering) return true; bool dayAllowed = IsDayOfWeekAllowed(); bool sessionAllowed = IsSessionTimeAllowed(); if(EnableDebugPrints && (!dayAllowed || !sessionAllowed)) { Print("Time Filter Check: Day allowed=", dayAllowed, ", Session allowed=", sessionAllowed); } return (dayAllowed && sessionAllowed); } //+------------------------------------------------------------------+ //| Helper function to update and persist loss | //+------------------------------------------------------------------+ void UpdatePersistentLoss(double newLoss) { newLoss = MathMax(0, newLoss); if (accumulatedLoss != newLoss) { if(EnableDebugPrints) Print("Updating accumulated loss from ", DoubleToString(accumulatedLoss, 2), " to ", DoubleToString(newLoss, 2)); accumulatedLoss = newLoss; GlobalVariableSet(gvAccumLossName, accumulatedLoss); } } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { chartID = ChartID(); digits = (int)MarketInfo(Symbol(), MODE_DIGITS); pointValue = MarketInfo(Symbol(), MODE_POINT); pipValue = GetPipValue(); minLotSize = MarketInfo(Symbol(), MODE_MINLOT); maxLotSize = MarketInfo(Symbol(), MODE_MAXLOT); lotStep = MarketInfo(Symbol(), MODE_LOTSTEP); string tfString; switch(Period()) { case 1: tfString = "M1"; break; case 5: tfString = "M5"; break; case 15: tfString = "M15"; break; case 30: tfString = "M30"; break; case 60: tfString = "H1"; break; case 240: tfString = "H4"; break; case 1440: tfString = "D1"; break; case 10080: tfString = "W1"; break; case 43200: tfString = "MN"; break; default: tfString = StringFormat("%d", Period()); } eaCommentPrefix = "UnivBufEA_" + IntegerToString(MagicNumber) + "_" + tfString; lastTradeSignalBarTime = 0; if (digits == 3 || digits == 5) slippagePoints = Slippage * 10; else slippagePoints = Slippage; gvAccumLossName = "UnivBufEA_" + Symbol() + "_" + IntegerToString(MagicNumber) + "_AccumLoss"; gvConsecLossName = "UnivBufEA_" + Symbol() + "_" + IntegerToString(MagicNumber) + "_ConsecLoss"; if(ClearPersistentLossOnStart) { if(GlobalVariableCheck(gvAccumLossName)) GlobalVariableDel(gvAccumLossName); if(GlobalVariableCheck(gvConsecLossName)) GlobalVariableDel(gvConsecLossName); Print("Persistent variables have been cleared as per user setting."); } if(GlobalVariableCheck(gvAccumLossName)) { accumulatedLoss = GlobalVariableGet(gvAccumLossName); } else { accumulatedLoss = 0; GlobalVariableSet(gvAccumLossName, 0); } Print("Loaded persistent accumulated loss: ", DoubleToString(accumulatedLoss, 2)); if(GlobalVariableCheck(gvConsecLossName)) { consecutiveLosses = (int)GlobalVariableGet(gvConsecLossName); } else { consecutiveLosses = 0; GlobalVariableSet(gvConsecLossName, 0); } Print("Loaded persistent consecutive losses: ", consecutiveLosses); datetime now = TimeCurrent(); datetime midnight = now - (now % 86400); datetime resetTime = midnight + 3600; if (now >= resetTime && (lastDailyResetTime < resetTime || (TimeDayOfYear(now) != TimeDayOfYear(lastDailyResetTime)))) { dailyProfitStartBalance = AccountBalance(); dailyProfitAccumulated = 0; lastDailyResetTime = now; Print("Daily Profit Tracking Reset at 01:00. Start Balance: ", DoubleToString(dailyProfitStartBalance, 2)); } ObjectCreate(chartID, "AccumLossLabel", OBJ_LABEL, 0, 0, 0); ObjectSetString(chartID, "AccumLossLabel", OBJPROP_TEXT, "Accum. Loss: Loading..."); ObjectSetInteger(chartID, "AccumLossLabel", OBJPROP_CORNER, CORNER_LEFT_LOWER); ObjectSetInteger(chartID, "AccumLossLabel", OBJPROP_XDISTANCE, 10); ObjectSetInteger(chartID, "AccumLossLabel", OBJPROP_YDISTANCE, 40); ObjectSetInteger(chartID, "AccumLossLabel", OBJPROP_FONTSIZE, 10); ObjectCreate(chartID, "AccumLossPercentLabel", OBJ_LABEL, 0, 0, 0); ObjectSetString(chartID, "AccumLossPercentLabel", OBJPROP_TEXT, "Loss % of Bal: Loading..."); ObjectSetInteger(chartID, "AccumLossPercentLabel", OBJPROP_CORNER, CORNER_LEFT_LOWER); ObjectSetInteger(chartID, "AccumLossPercentLabel", OBJPROP_XDISTANCE, 10); ObjectSetInteger(chartID, "AccumLossPercentLabel", OBJPROP_YDISTANCE, 20); ObjectSetInteger(chartID, "AccumLossPercentLabel", OBJPROP_FONTSIZE, 10); CreateTradeUI(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(!IsTesting()) { GlobalVariableSet(gvAccumLossName, accumulatedLoss); GlobalVariableSet(gvConsecLossName, consecutiveLosses); Print("EA deinitializing (Reason: ", reason, "). Saved persistent state."); } ObjectDelete(chartID, "ManualTPLabel"); ObjectDelete(chartID, "ManualTPEdit"); ObjectDelete(chartID, "ManualSLLabel"); ObjectDelete(chartID, "ManualSLEdit"); ObjectDelete(chartID, "ManualTradeButton"); ObjectDelete(chartID, "AccumLossLabel"); ObjectDelete(chartID, "AccumLossPercentLabel"); } //+------------------------------------------------------------------+ //| Create Trade UI Elements | //+------------------------------------------------------------------+ void CreateTradeUI() { ObjectDelete(chartID, "ManualTPLabel"); ObjectDelete(chartID, "ManualTPEdit"); ObjectDelete(chartID, "ManualSLLabel"); ObjectDelete(chartID, "ManualSLEdit"); ObjectDelete(chartID, "ManualTradeButton"); if(Trade_Mode == MODE_MANUAL) { ObjectCreate(chartID, "ManualTPLabel", OBJ_LABEL, 0, 0, 0); ObjectSetString(chartID, "ManualTPLabel", OBJPROP_TEXT, "Take Profit:"); ObjectSetInteger(chartID, "ManualTPLabel", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(chartID, "ManualTPLabel", OBJPROP_XDISTANCE, 180); ObjectSetInteger(chartID, "ManualTPLabel", OBJPROP_YDISTANCE, 50); ObjectCreate(chartID, "ManualTPEdit", OBJ_EDIT, 0, 0, 0); ObjectSetInteger(chartID, "ManualTPEdit", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(chartID, "ManualTPEdit", OBJPROP_XDISTANCE, 110); ObjectSetInteger(chartID, "ManualTPEdit", OBJPROP_YDISTANCE, 50); ObjectSetInteger(chartID, "ManualTPEdit", OBJPROP_XSIZE, 60); ObjectCreate(chartID, "ManualSLLabel", OBJ_LABEL, 0, 0, 0); ObjectSetString(chartID, "ManualSLLabel", OBJPROP_TEXT, "Stop Loss:"); ObjectSetInteger(chartID, "ManualSLLabel", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(chartID, "ManualSLLabel", OBJPROP_XDISTANCE, 180); ObjectSetInteger(chartID, "ManualSLLabel", OBJPROP_YDISTANCE, 70); ObjectCreate(chartID, "ManualSLEdit", OBJ_EDIT, 0, 0, 0); ObjectSetInteger(chartID, "ManualSLEdit", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(chartID, "ManualSLEdit", OBJPROP_XDISTANCE, 110); ObjectSetInteger(chartID, "ManualSLEdit", OBJPROP_YDISTANCE, 70); ObjectSetInteger(chartID, "ManualSLEdit", OBJPROP_XSIZE, 60); ObjectCreate(chartID, "ManualTradeButton", OBJ_BUTTON, 0, 0, 0); ObjectSetString(chartID, "ManualTradeButton", OBJPROP_TEXT, "Open Market Order"); ObjectSetInteger(chartID, "ManualTradeButton", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(chartID, "ManualTradeButton", OBJPROP_XDISTANCE, 110); ObjectSetInteger(chartID, "ManualTradeButton", OBJPROP_YDISTANCE, 95); ObjectSetInteger(chartID, "ManualTradeButton", OBJPROP_XSIZE, 130); ObjectSetInteger(chartID, "ManualTradeButton", OBJPROP_YSIZE, 25); } } //+------------------------------------------------------------------+ //| Custom Trade Allowed Function | //+------------------------------------------------------------------+ bool IsDailyTradeAllowed() { if (DailyProfitTargetPercent <= 0) return true; double targetProfit = dailyProfitStartBalance * DailyProfitTargetPercent / 100.0; if (dailyProfitAccumulated >= targetProfit) { if (EnableDebugPrints) Print("Daily profit target reached. Target: ", DoubleToString(targetProfit, 2), " Accumulated: ", DoubleToString(dailyProfitAccumulated, 2)); return false; } return true; } //+------------------------------------------------------------------+ //| OnTick function (main loop) | //+------------------------------------------------------------------+ void OnTick() { // UI and State Updates double lossPercentOfBalance = 0; if(AccountBalance() > 0) lossPercentOfBalance = (accumulatedLoss / AccountBalance()) * 100.0; string lossText = "Accum. Loss: " + DoubleToString(accumulatedLoss, 2); string percentText = "Loss % of Bal: " + DoubleToString(lossPercentOfBalance, 2) + "%"; color labelColor = clrWhite; if(HighLossThresholdPercent > 0 && lossPercentOfBalance >= HighLossThresholdPercent) labelColor = clrOrangeRed; ObjectSetString(chartID, "AccumLossLabel", OBJPROP_TEXT, lossText); ObjectSetInteger(chartID, "AccumLossLabel", OBJPROP_COLOR, labelColor); ObjectSetString(chartID, "AccumLossPercentLabel", OBJPROP_TEXT, percentText); ObjectSetInteger(chartID, "AccumLossPercentLabel", OBJPROP_COLOR, labelColor); // Trailing Stop Management if(UseTrailingStop) { ManageTrailingStop(); } // Bar and Time Management static datetime lastBarTime = 0; datetime currentBarTime = Time[0]; bool isNewBar = false; if (currentBarTime != lastBarTime) { isNewBar = true; lastBarTime = currentBarTime; if (EnableDebugPrints) Print("========== New Bar Detected: ", TimeToString(currentBarTime), " =========="); } // Daily profit tracking update { datetime now = TimeCurrent(); datetime midnight = now - (now % 86400); datetime resetTime = midnight + 3600; if (now >= resetTime && (lastDailyResetTime < resetTime || (TimeDayOfYear(now) != TimeDayOfYear(lastDailyResetTime)))) { dailyProfitStartBalance = AccountBalance(); dailyProfitAccumulated = 0; lastDailyResetTime = now; if (EnableDebugPrints) Print("Daily Profit Tracking Reset at 01:00. New Balance: ", DoubleToString(dailyProfitStartBalance, 2)); } dailyProfitAccumulated = 0; for (int i = OrdersHistoryTotal() - 1; i >= 0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderCloseTime() > 0 && OrderCloseTime() >= lastDailyResetTime) { dailyProfitAccumulated += OrderProfit() + OrderCommission() + OrderSwap(); } } } // Manual Trade Processing if(Trade_Mode == MODE_MANUAL && manualTradeButtonPressed) { manualTradeButtonPressed = false; string tpStr = ObjectGetString(chartID, "ManualTPEdit", OBJPROP_TEXT); string slStr = ObjectGetString(chartID, "ManualSLEdit", OBJPROP_TEXT); if(StringLen(tpStr) > 0 && StringLen(slStr) > 0) { manualTP = StrToDouble(tpStr); manualSL = StrToDouble(slStr); RefreshRates(); if(manualTP > Bid && manualSL < Bid) { if (!ExecuteTrade(OP_BUY, LotSize, Bid, manualSL, manualTP)) Print("Manual BUY trade execution failed."); } else if(manualTP < Bid && manualSL > Bid) { if (!ExecuteTrade(OP_SELL, LotSize, Bid, manualSL, manualTP)) Print("Manual SELL trade execution failed."); } else { Print("Invalid SL/TP values for a market order."); } } else { Print("Please fill both Stop Loss and Take Profit values for manual trade."); } } // Indicator Trade Processing if(Trade_Mode == MODE_INDICATOR && isNewBar) { datetime signalBarTime = Time[1]; if (!IsDailyTradeAllowed() || !IsTimeFilterAllowed() || signalBarTime == 0) return; double buySignalValue = GetIndicatorValue(BuySignalBuffer, 1); double sellSignalValue = GetIndicatorValue(SellSignalBuffer, 1); bool buyCondition = (buySignalValue != EMPTY_VALUE && buySignalValue != 0); bool sellCondition = (sellSignalValue != EMPTY_VALUE && sellSignalValue != 0); if (ExitOnOppositeSignal) { if (buyCondition) CloseExistingOppositeTrade(OP_SELL); else if (sellCondition) CloseExistingOppositeTrade(OP_BUY); } if ((buyCondition || sellCondition) && (signalBarTime <= lastTradeSignalBarTime)) return; if (!IsTradeOpen(Symbol(), MagicNumber)) { for (int i = OrdersHistoryTotal() - 1; i >= 0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderCloseTime() > 0) { if (OrderTicket() != lastProcessedOrderTicket) { double lastProfit = OrderProfit() + OrderCommission() + OrderSwap(); double currentLoss = accumulatedLoss; if (lastProfit < 0) { currentLoss += MathAbs(lastProfit); consecutiveLosses++; if(EnableDebugPrints) Print("Consecutive loss count increased to: ", consecutiveLosses); } else { currentLoss -= lastProfit; if (consecutiveLosses > 0) { if(EnableDebugPrints) Print("Winning trade. Resetting consecutive loss count from ", consecutiveLosses, " to 0."); consecutiveLosses = 0; } } UpdatePersistentLoss(currentLoss); GlobalVariableSet(gvConsecLossName, consecutiveLosses); if(ConsecutiveLossesToReset > 0 && consecutiveLosses >= ConsecutiveLossesToReset) { Print("CONSECUTIVE LOSS RESET: Reached ", consecutiveLosses, " losses in a row. Clearing accumulated loss."); UpdatePersistentLoss(0); consecutiveLosses = 0; GlobalVariableSet(gvConsecLossName, consecutiveLosses); } lastProcessedOrderTicket = OrderTicket(); } break; } } double stopLossPrice = 0, takeProfitPrice = 0, atrValue = 0; if(SLTP_Mode == 0) atrValue = iATR(Symbol(), Period(), AtrPeriod, 1); RefreshRates(); if (buyCondition) { double openPrice = Bid; if (SLTP_Mode == 0) { if(atrValue > 0 && StopLossAtrMultiplier > 0) stopLossPrice = NormalizeDouble(openPrice - atrValue * StopLossAtrMultiplier, digits); } else { double rawSL = GetIndicatorValue(StopLossBuffer, 1); if(rawSL != EMPTY_VALUE && rawSL != 0) stopLossPrice = NormalizeDouble(rawSL, digits); } if (SLTP_Mode == 0) { if(atrValue > 0 && TakeProfitAtrMultiplier > 0) takeProfitPrice = NormalizeDouble(openPrice + atrValue * TakeProfitAtrMultiplier, digits); } else { double rawTP = GetIndicatorValue(TakeProfitBuffer, 1); if(rawTP != EMPTY_VALUE && rawTP != 0) takeProfitPrice = NormalizeDouble(rawTP, digits); } if (MinimumTakeProfitPips > 0 && takeProfitPrice > 0) { double minTpLevel = NormalizeDouble(openPrice + MinimumTakeProfitPips * pipValue, digits); if(EnableDebugPrints) Print("Min TP Check (BUY): Open=", openPrice, ", Initial TP=", takeProfitPrice, ", Min TP Level=", minTpLevel); if (takeProfitPrice < minTpLevel) { if(EnableDebugPrints) Print("Minimum TP logic TRIGGERED for BUY. Overriding initial TP."); takeProfitPrice = minTpLevel; } } double baseLot = LotSize; if(UsePercentBalanceLot && takeProfitPrice > 0) { double tpPoints = (takeProfitPrice - openPrice) / pointValue; if(tpPoints > 0) baseLot = (AccountBalance() * (PercentOfBalanceForProfit / 100.0)) / (tpPoints * MarketInfo(Symbol(), MODE_TICKVALUE)); } double recoveryLots = 0; if (EnableRecoveryMode && accumulatedLoss > 0 && takeProfitPrice > 0) { double tpPoints = (takeProfitPrice - openPrice) / pointValue; if (tpPoints > 0) { double profitPerLot = tpPoints * MarketInfo(Symbol(), MODE_TICKVALUE); if (profitPerLot > 0) { recoveryLots = accumulatedLoss / profitPerLot; double highLossTriggerAmount = AccountBalance() * (HighLossThresholdPercent / 100.0); if (HighLossThresholdPercent > 0 && accumulatedLoss > highLossTriggerAmount && HighLossRecoveryDampener > 1.0) recoveryLots /= HighLossRecoveryDampener; } } } double currentLotSize = baseLot + recoveryLots; currentLotSize = NormalizeDouble(currentLotSize, 2); currentLotSize = MathMax(currentLotSize, minLotSize); currentLotSize = MathMin(currentLotSize, maxLotSize); ExecuteTrade(OP_BUY, currentLotSize, openPrice, stopLossPrice, takeProfitPrice); } else if (sellCondition) { double openPrice = Bid; if (SLTP_Mode == 0) { if(atrValue > 0 && StopLossAtrMultiplier > 0) stopLossPrice = NormalizeDouble(openPrice + atrValue * StopLossAtrMultiplier, digits); } else { double rawSL = GetIndicatorValue(StopLossBuffer, 1); if(rawSL != EMPTY_VALUE && rawSL != 0) stopLossPrice = NormalizeDouble(rawSL, digits); } if (SLTP_Mode == 0) { if(atrValue > 0 && TakeProfitAtrMultiplier > 0) takeProfitPrice = NormalizeDouble(openPrice - atrValue * TakeProfitAtrMultiplier, digits); } else { double rawTP = GetIndicatorValue(TakeProfitBuffer, 1); if(rawTP != EMPTY_VALUE && rawTP != 0) takeProfitPrice = NormalizeDouble(rawTP, digits); } if (MinimumTakeProfitPips > 0 && takeProfitPrice > 0) { double minTpLevel = NormalizeDouble(openPrice - MinimumTakeProfitPips * pipValue, digits); if(EnableDebugPrints) Print("Min TP Check (SELL): Open=", openPrice, ", Initial TP=", takeProfitPrice, ", Min TP Level=", minTpLevel); if (takeProfitPrice > minTpLevel) { if(EnableDebugPrints) Print("Minimum TP logic TRIGGERED for SELL. Overriding initial TP."); takeProfitPrice = minTpLevel; } } double baseLot = LotSize; if(UsePercentBalanceLot && takeProfitPrice > 0) { double tpPoints = (openPrice - takeProfitPrice) / pointValue; if(tpPoints > 0) baseLot = (AccountBalance() * (PercentOfBalanceForProfit / 100.0)) / (tpPoints * MarketInfo(Symbol(), MODE_TICKVALUE)); } double recoveryLots = 0; if (EnableRecoveryMode && accumulatedLoss > 0 && takeProfitPrice > 0) { double tpPoints = (openPrice - takeProfitPrice) / pointValue; if (tpPoints > 0) { double profitPerLot = tpPoints * MarketInfo(Symbol(), MODE_TICKVALUE); if (profitPerLot > 0) { recoveryLots = accumulatedLoss / profitPerLot; double highLossTriggerAmount = AccountBalance() * (HighLossThresholdPercent / 100.0); if (HighLossThresholdPercent > 0 && accumulatedLoss > highLossTriggerAmount && HighLossRecoveryDampener > 1.0) recoveryLots /= HighLossRecoveryDampener; } } } double spread = MarketInfo(Symbol(), MODE_SPREAD) * pointValue; if (takeProfitPrice > 0) takeProfitPrice += spread; if (stopLossPrice > 0) stopLossPrice += spread; double currentLotSize = baseLot + recoveryLots; currentLotSize = NormalizeDouble(currentLotSize, 2); currentLotSize = MathMax(currentLotSize, minLotSize); currentLotSize = MathMin(currentLotSize, maxLotSize); ExecuteTrade(OP_SELL, currentLotSize, openPrice, stopLossPrice, takeProfitPrice); } } } } //+------------------------------------------------------------------+ //| Manage Trailing Stop (REWRITTEN with safe, unified logic) | //+------------------------------------------------------------------+ void ManageTrailingStop() { if(TrailingStopPips <= 0) return; for (int i = OrdersTotal() - 1; i >= 0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) { // --- Get all necessary order info --- string comment = OrderComment(); int ticket = OrderTicket(); int orderType = OrderType(); double openPrice = OrderOpenPrice(); double currentSL = OrderStopLoss(); // --- Determine if the order is a trailing candidate --- bool is_active_trail = (StringFind(comment, ";TS=ACTIVE") != -1); int vtp_pos = StringFind(comment, ";VTP="); bool is_pre_trail = (vtp_pos != -1); // If it's not a trailing trade at all, skip it. if (!is_active_trail && !is_pre_trail) continue; // --- Check if the order is ready to be trailed --- bool ready_to_trail = false; if (is_active_trail) { ready_to_trail = true; // Already active, so it's always ready. } else // It's a pre-trail order, check for activation. { double virtual_tp = StrToDouble(StringSubstr(comment, vtp_pos + 5)); if (virtual_tp > 0) { RefreshRates(); double activation_price; if (orderType == OP_BUY) { activation_price = NormalizeDouble(virtual_tp + TrailingStopPips * pipValue, digits); if(EnableDebugPrints) Print("TS Pre-Trail (BUY): Ticket #", ticket, ", VTP=", virtual_tp, ", Watching Activation Price=", activation_price); if(Bid >= activation_price) ready_to_trail = true; } else // OP_SELL { activation_price = NormalizeDouble(virtual_tp - TrailingStopPips * pipValue, digits); if(EnableDebugPrints) Print("TS Pre-Trail (SELL): Ticket #", ticket, ", VTP=", virtual_tp, ", Watching Activation Price=", activation_price); if(Bid <= activation_price) ready_to_trail = true; } } } // --- If not ready, skip to the next order --- if (!ready_to_trail) continue; // --- If we get here, the order MUST be trailed. Calculate and apply the new SL. --- if(EnableDebugPrints && !is_active_trail) Print("TS: ACTIVATION PRICE HIT on Ticket #", ticket, "! Attempting to start trail..."); RefreshRates(); double potentialNewSL; if (orderType == OP_BUY) potentialNewSL = NormalizeDouble(Bid - TrailingStopPips * pipValue, digits); else potentialNewSL = NormalizeDouble(Bid + TrailingStopPips * pipValue, digits); // The Unbreakable Ratchet Rule: New SL must be an improvement over the current one. if ((orderType == OP_BUY && potentialNewSL > currentSL) || (orderType == OP_SELL && potentialNewSL < currentSL)) { string new_comment = comment; // If it was a pre-trail order, update its comment to make it active. if (is_pre_trail) { string base_comment = StringSubstr(comment, 0, vtp_pos); new_comment = base_comment + ";TS=ACTIVE"; } if(OrderModify(ticket, openPrice, potentialNewSL, 0, new_comment, clrNONE)) { Print("TS SUCCESS: Stop loss for #", ticket, " set/trailed to ", potentialNewSL); if(is_pre_trail) Print("TS INFO: Order #", ticket, " is now in active trail mode."); } else { Print("TS ERROR: Failed to modify stop loss for #", ticket, ". Error: ", ErrorDescription(GetLastError())); } } } } } //+------------------------------------------------------------------+ //| Execute Trade (Using Comment for Virtual TP) | //+------------------------------------------------------------------+ bool ExecuteTrade(int orderType, double lots, double openPrice, double slPrice, double tpPrice) { RefreshRates(); openPrice = Bid; // Use Bid price for all orders double tpForServer = tpPrice; string orderComment = StringSubstr(eaCommentPrefix + "_" + IndicatorFileName, 0, 15); if(UseTrailingStop && tpPrice > 0) { tpForServer = 0; orderComment += ";VTP=" + DoubleToString(tpPrice, digits); if(EnableDebugPrints) Print("Trailing Stop active. Embedding Virtual TP in comment."); } orderComment = StringSubstr(orderComment, 0, 31); double stopLevelPoints = MarketInfo(Symbol(), MODE_STOPLEVEL) * pointValue; if(orderType == OP_BUY) { if(slPrice != 0 && openPrice - slPrice < stopLevelPoints) slPrice = NormalizeDouble(openPrice - stopLevelPoints, digits); if(tpForServer != 0 && tpForServer - openPrice < stopLevelPoints) tpForServer = NormalizeDouble(openPrice + stopLevelPoints, digits); } else // OP_SELL { if(slPrice != 0 && slPrice - openPrice < stopLevelPoints) slPrice = NormalizeDouble(openPrice + stopLevelPoints, digits); if(tpForServer != 0 && openPrice - tpForServer < stopLevelPoints) tpForServer = NormalizeDouble(openPrice - stopLevelPoints, digits); } if (EnableDebugPrints) Print("DEBUG ExecuteTrade: Type=", orderType, " Lots=", lots, " Price=", openPrice, " SL=", slPrice, " TP(ToServer)=", tpForServer, " Comment='", orderComment,"'"); int ticket = OrderSend(Symbol(), orderType, lots, openPrice, slippagePoints, slPrice, tpForServer, orderComment, MagicNumber, 0, (orderType == OP_BUY ? clrGreen : clrRed)); if (ticket < 0) { Print("OrderSend failed: ", ErrorDescription(GetLastError()), " (Code: ", GetLastError(), ")"); return (false); } Print("OrderSend successful. Ticket: ", ticket); if(TakeScreenshotOnOpen) { string timeStr = MyStringReplace(MyStringReplace(TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS), ":", "_"),".","_"); string filename = Symbol() + "_" + EnumToString((ENUM_TIMEFRAMES)Period()) + "_T" + IntegerToString(ticket) + "_" + timeStr + ".gif"; if(!ChartScreenShot(chartID, filename, (int)ChartGetInteger(chartID, CHART_WIDTH_IN_PIXELS), (int)ChartGetInteger(chartID, CHART_HEIGHT_IN_PIXELS), ALIGN_RIGHT)) Print("Failed to save screenshot. Error: ", ErrorDescription(GetLastError())); } lastTradeSignalBarTime = Time[1]; return (true); } //+------------------------------------------------------------------+ //| ChartEvent handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK && Trade_Mode == MODE_MANUAL && sparam == "ManualTradeButton") { manualTradeButtonPressed = true; if(EnableDebugPrints) Print("Manual trade button clicked"); } } //+------------------------------------------------------------------+ //| Get Indicator Value | //+------------------------------------------------------------------+ double GetIndicatorValue(int bufferIndex, int barShift) { if(IndicatorFileName == "" || IndicatorFileName == "My_Indicator" || bufferIndex < 0) return EMPTY_VALUE; double value = iCustom(Symbol(), Period(), IndicatorFileName, bufferIndex, barShift); if (value == EMPTY_VALUE || (value == 0 && GetLastError() != ERR_NO_ERROR)) { if (EnableDebugPrints) Print("DEBUG GetIndicatorValue: iCustom error for '", IndicatorFileName, "', Buffer ", bufferIndex, ". Error: ", ErrorDescription(GetLastError())); return EMPTY_VALUE; } return value; } //+------------------------------------------------------------------+ //| Check if a trade is already open | //+------------------------------------------------------------------+ bool IsTradeOpen(string symbol, int magic) { for (int i = OrdersTotal() - 1; i >= 0; i--) if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == symbol && OrderMagicNumber() == magic) return (true); return (false); } //+------------------------------------------------------------------+ //| Close Existing Opposite Trade | //+------------------------------------------------------------------+ bool CloseExistingOppositeTrade(int oppositeOrderType) { bool closedAny = false; RefreshRates(); for (int i = OrdersTotal() - 1; i >= 0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == oppositeOrderType) { if (OrderClose(OrderTicket(), OrderLots(), (oppositeOrderType == OP_BUY) ? Bid : Ask, slippagePoints, clrYellow)) { Print("Closed opposite trade Ticket#", OrderTicket(), " due to new signal."); closedAny = true; } else Print("Failed to close opposite trade Ticket#", OrderTicket(), ". Error: ", GetLastError()); } } return closedAny; } //+------------------------------------------------------------------+ //| Custom String Replace Function | //+------------------------------------------------------------------+ string MyStringReplace(string text, string replace_what, string replace_with) { string temp = text; int pos = StringFind(temp, replace_what, 0); while(pos >= 0) { temp = StringSubstr(temp, 0, pos) + replace_with + StringSubstr(temp, pos + StringLen(replace_what)); pos = StringFind(temp, replace_what, pos + StringLen(replace_with)); } return temp; }