//+------------------------------------------------------------------+ //| TrailingStop.mqh - Trailing stop and break-even management | //| Modifies SL based on profit targets | //+------------------------------------------------------------------+ #ifndef __TRAILINGSTOP_MQH__ #define __TRAILINGSTOP_MQH__ #include "Config.mqh" #include "Logger.mqh" #include "MarketData.mqh" #include "Utilities.mqh" #include "TradeManager.mqh" class CTrailingStop { private: CMarketData *mp_market_data; CLogger *mp_logger; CTradeManager *mp_trade_manager; public: // Constructor CTrailingStop(CMarketData *market_data, CLogger *logger, CTradeManager *trade_manager) { mp_market_data = market_data; mp_logger = logger; mp_trade_manager = trade_manager; } // Update trailing stop for all positions void UpdateAllPositions() { if(!g_use_trailing_stop && !g_use_break_even) return; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetString(POSITION_SYMBOL) == mp_market_data.GetSymbol() && PositionGetInteger(POSITION_MAGIC) == g_magic_number) { UpdatePosition(PositionGetTicket(i)); } } } } // Update trailing stop for a single position void UpdatePosition(ulong ticket) { if(ticket == 0) return; if(!PositionSelectByTicket(ticket)) return; ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double current_sl = PositionGetDouble(POSITION_SL); double current_tp = PositionGetDouble(POSITION_TP); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double profit = PositionGetDouble(POSITION_PROFIT); double new_sl = current_sl; double new_tp = current_tp; bool need_modify = false; // Apply break-even logic first if(g_use_break_even) { if(ApplyBreakEven(pos_type, open_price, current_sl, profit, new_sl)) { need_modify = true; } } // Apply trailing stop logic if(g_use_trailing_stop) { if(ApplyTrailingStop(pos_type, current_sl, new_sl)) { need_modify = true; } } // Only modify if SL changed if(need_modify && new_sl != current_sl) { mp_trade_manager.ModifyPosition(ticket, new_sl, new_tp); } } private: // Apply break-even logic bool ApplyBreakEven(ENUM_POSITION_TYPE pos_type, double open_price, double current_sl, double profit, double &new_sl) { double break_even_trigger = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), g_break_even_profit); double break_even_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), g_break_even_sl); // Break-even only if profit threshold is reached if(profit < break_even_trigger) return false; // For BUY: move SL to BE (open price + distance) if(pos_type == POSITION_TYPE_BUY) { double be_sl = open_price + break_even_distance; be_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), be_sl); if(be_sl > current_sl) { new_sl = be_sl; if(mp_logger && g_debug_mode) mp_logger.Info(StringFormat("Break-even applied: BUY SL moved to %.5f", be_sl)); return true; } } // For SELL: move SL to BE (open price - distance) else if(pos_type == POSITION_TYPE_SELL) { double be_sl = open_price - break_even_distance; be_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), be_sl); if(be_sl < current_sl) { new_sl = be_sl; if(mp_logger && g_debug_mode) mp_logger.Info(StringFormat("Break-even applied: SELL SL moved to %.5f", be_sl)); return true; } } return false; } // Apply trailing stop logic bool ApplyTrailingStop(ENUM_POSITION_TYPE pos_type, double current_sl, double &new_sl) { double bid = mp_market_data.GetBid(); double ask = mp_market_data.GetAsk(); double trail_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), g_trailing_stop_points); // For BUY: trailing stop follows price from below if(pos_type == POSITION_TYPE_BUY) { double candidate_sl = bid - trail_distance; candidate_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), candidate_sl); // Only move SL up (never down) if(candidate_sl > current_sl) { new_sl = candidate_sl; if(mp_logger && g_debug_mode) mp_logger.Info(StringFormat("Trailing stop applied: BUY SL moved to %.5f", candidate_sl)); return true; } } // For SELL: trailing stop follows price from above else if(pos_type == POSITION_TYPE_SELL) { double candidate_sl = ask + trail_distance; candidate_sl = CUtilities::NormalizePrice(mp_market_data.GetSymbol(), candidate_sl); // Only move SL down (never up) if(candidate_sl < current_sl) { new_sl = candidate_sl; if(mp_logger && g_debug_mode) mp_logger.Info(StringFormat("Trailing stop applied: SELL SL moved to %.5f", candidate_sl)); return true; } } return false; } }; #endif //__TRAILINGSTOP_MQH__