//+------------------------------------------------------------------+ //| TradeManager.mqh | //| Trade Management Module | //+------------------------------------------------------------------+ #ifndef TRADE_MANAGER_MQH #define TRADE_MANAGER_MQH #include "DataTypes.mqh" #include #include //+------------------------------------------------------------------+ //| Trade Manager Class | //+------------------------------------------------------------------+ class CTradeManager { private: //--- Configuration TradeManagerConfig m_config; int m_magic_number; //--- Trade operations CTrade m_trade; CSymbolInfo m_symbol; //--- Partial close levels PartialCloseLevel m_partial_levels[10]; int m_partial_count; //--- Helper methods bool ParsePartialLevels(string levels_string); bool CheckBreakeven(ManagedTrade &trade, double current_price); bool CheckTrailingStop(ManagedTrade &trade, double current_price); bool CheckPartialClose(ManagedTrade &trade, double current_price); bool CheckTimeBasedExit(ManagedTrade &trade); double CalculateDynamicTP(ManagedTrade &trade, const MarketConditions &market); double CalculateDynamicSL(ManagedTrade &trade, const MarketConditions &market); double GetMinStopDistance(); bool ValidateStopLevels(double price, double sl, double tp); public: CTradeManager(); ~CTradeManager(); //--- Initialization bool Initialize(const TradeManagerConfig &config, int magic); //--- Trade operations ulong OpenPosition(const TradeRequest &request); bool ClosePosition(ulong ticket, double volume = 0); bool ModifyPosition(ulong ticket, double sl, double tp); //--- Trade management void ManageTrade(ManagedTrade &trade, const MarketConditions &market); void ApplyBreakeven(ManagedTrade &trade); void ApplyTrailingStop(ManagedTrade &trade); void ApplyPartialClose(ManagedTrade &trade); void ApplyDynamicTargets(ManagedTrade &trade, const MarketConditions &market); //--- External trade management void ApplyDefaultStops(ManagedTrade &trade, double atr_value); bool ForceStopLoss(ManagedTrade &trade, double stop_distance); bool ForceTakeProfit(ManagedTrade &trade, double tp_distance); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTradeManager::CTradeManager() { m_magic_number = 0; m_partial_count = 0; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CTradeManager::~CTradeManager() { } //+------------------------------------------------------------------+ //| Initialize trade manager | //+------------------------------------------------------------------+ bool CTradeManager::Initialize(const TradeManagerConfig &config, int magic) { m_config = config; m_magic_number = magic; //--- Initialize trade object m_trade.SetExpertMagicNumber(magic); m_trade.SetDeviationInPoints(10); m_trade.SetTypeFilling(ORDER_FILLING_RETURN); m_trade.SetAsyncMode(false); //--- Initialize symbol if(!m_symbol.Name(_Symbol)) { Print("TradeManager: Failed to initialize symbol"); return false; } //--- Parse partial close levels if(m_config.enable_partial) { if(!ParsePartialLevels(m_config.partial_levels)) { Print("TradeManager: Failed to parse partial levels"); m_config.enable_partial = false; } } Print("TradeManager initialized: TP=", EnumToString(m_config.tp_mode), ", SL=", EnumToString(m_config.sl_mode)); return true; } //+------------------------------------------------------------------+ //| Open new position | //+------------------------------------------------------------------+ ulong CTradeManager::OpenPosition(const TradeRequest &request) { //--- Refresh symbol data m_symbol.RefreshRates(); //--- Calculate actual prices double price = 0; double sl = 0; double tp = 0; if(request.type == ORDER_TYPE_BUY) { price = m_symbol.Ask(); if(request.sl_distance > 0) sl = NormalizeDouble(price - request.sl_distance, m_symbol.Digits()); if(request.tp_distance > 0) tp = NormalizeDouble(price + request.tp_distance, m_symbol.Digits()); } else if(request.type == ORDER_TYPE_SELL) { price = m_symbol.Bid(); if(request.sl_distance > 0) sl = NormalizeDouble(price + request.sl_distance, m_symbol.Digits()); if(request.tp_distance > 0) tp = NormalizeDouble(price - request.tp_distance, m_symbol.Digits()); } else { Print("TradeManager: Invalid order type"); return 0; } //--- Validate stop levels if(!ValidateStopLevels(price, sl, tp)) { Print("TradeManager: Invalid stop levels"); return 0; } //--- Open position bool result = false; if(request.type == ORDER_TYPE_BUY) result = m_trade.Buy(request.volume, request.symbol, price, sl, tp, request.comment); else result = m_trade.Sell(request.volume, request.symbol, price, sl, tp, request.comment); if(result) { ulong ticket = m_trade.ResultOrder(); Print("TradeManager: Opened ", (request.type == ORDER_TYPE_BUY) ? "BUY" : "SELL", " position #", ticket, " at ", price); return ticket; } else { Print("TradeManager: Failed to open position - ", m_trade.ResultComment()); return 0; } } //+------------------------------------------------------------------+ //| Close position | //+------------------------------------------------------------------+ bool CTradeManager::ClosePosition(ulong ticket, double volume) { if(volume == 0) { //--- Full close if(m_trade.PositionClose(ticket)) { Print("TradeManager: Closed position #", ticket); return true; } } else { //--- Partial close if(m_trade.PositionClosePartial(ticket, volume)) { Print("TradeManager: Partially closed ", volume, " lots of position #", ticket); return true; } } Print("TradeManager: Failed to close position #", ticket, " - ", m_trade.ResultComment()); return false; } //+------------------------------------------------------------------+ //| Modify position stops | //+------------------------------------------------------------------+ bool CTradeManager::ModifyPosition(ulong ticket, double sl, double tp) { if(m_trade.PositionModify(ticket, sl, tp)) { Print("TradeManager: Modified position #", ticket, " SL=", sl, " TP=", tp); return true; } Print("TradeManager: Failed to modify position #", ticket, " - ", m_trade.ResultComment()); return false; } //+------------------------------------------------------------------+ //| Main trade management function | //+------------------------------------------------------------------+ void CTradeManager::ManageTrade(ManagedTrade &trade, const MarketConditions &market) { //--- Get current price m_symbol.RefreshRates(); double current_price = (trade.type == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask(); //--- Force stops on external trades if configured if(trade.magic != m_magic_number) { if(m_config.force_sl && trade.sl == 0) { double atr = market.volatility; ForceStopLoss(trade, atr * 2.0); } if(m_config.force_tp && trade.tp == 0) { double atr = market.volatility; ForceTakeProfit(trade, atr * 3.0); } } //--- Apply management strategies if(m_config.enable_breakeven && !trade.be_activated) { CheckBreakeven(trade, current_price); } if(m_config.enable_trailing && trade.be_activated) { CheckTrailingStop(trade, current_price); } if(m_config.enable_partial && trade.partial_count < m_partial_count) { CheckPartialClose(trade, current_price); } //--- Apply dynamic targets based on TP mode if(m_config.tp_mode == TP_ADAPTIVE || m_config.tp_mode == TP_HYBRID) { ApplyDynamicTargets(trade, market); } //--- Check time-based exits if(m_config.tp_mode == TP_TIME_BASED) { CheckTimeBasedExit(trade); } //--- Update bars in trade trade.bars_in_trade = iBars(_Symbol, PERIOD_CURRENT) - iBarShift(_Symbol, PERIOD_CURRENT, trade.open_time); } //+------------------------------------------------------------------+ //| Check and apply breakeven | //+------------------------------------------------------------------+ bool CTradeManager::CheckBreakeven(ManagedTrade &trade, double current_price) { if(trade.risk_amount <= 0) return false; //--- Calculate breakeven trigger price double trigger_distance = MathAbs(trade.open_price - trade.sl) * m_config.breakeven_trigger; double trigger_price; if(trade.type == POSITION_TYPE_BUY) trigger_price = trade.open_price + trigger_distance; else trigger_price = trade.open_price - trigger_distance; //--- Check if price reached trigger bool triggered = false; if(trade.type == POSITION_TYPE_BUY && current_price >= trigger_price) triggered = true; else if(trade.type == POSITION_TYPE_SELL && current_price <= trigger_price) triggered = true; if(triggered) { //--- Calculate new stop loss (breakeven + buffer) double buffer = m_symbol.Point() * 10; // 10 point buffer double new_sl; if(trade.type == POSITION_TYPE_BUY) new_sl = trade.open_price + buffer; else new_sl = trade.open_price - buffer; //--- Ensure new SL is better than current if((trade.type == POSITION_TYPE_BUY && new_sl > trade.sl) || (trade.type == POSITION_TYPE_SELL && new_sl < trade.sl)) { if(ModifyPosition(trade.ticket, new_sl, trade.tp)) { trade.sl = new_sl; trade.be_activated = true; Print("TradeManager: Breakeven activated for #", trade.ticket); return true; } } } return false; } //+------------------------------------------------------------------+ //| Check and apply trailing stop | //+------------------------------------------------------------------+ bool CTradeManager::CheckTrailingStop(ManagedTrade &trade, double current_price) { //--- Calculate trailing distance based on SL mode double trail_distance = 0; switch(m_config.sl_mode) { case SL_TRAILING_ATR: trail_distance = iATR(_Symbol, PERIOD_CURRENT, 14) * 1.5; break; case SL_PARABOLIC_SAR: { double sar_buffer[1]; int sar_handle = iSAR(_Symbol, PERIOD_CURRENT, 0.02, 0.2); if(CopyBuffer(sar_handle, 0, 0, 1, sar_buffer) > 0) { if(trade.type == POSITION_TYPE_BUY) trail_distance = current_price - sar_buffer[0]; else trail_distance = sar_buffer[0] - current_price; } IndicatorRelease(sar_handle); break; } default: trail_distance = MathAbs(trade.open_price - trade.sl) * 0.5; break; } if(trail_distance <= 0) return false; //--- Calculate new stop loss double new_sl; if(trade.type == POSITION_TYPE_BUY) new_sl = current_price - trail_distance; else new_sl = current_price + trail_distance; //--- Ensure new SL is better than current if((trade.type == POSITION_TYPE_BUY && new_sl > trade.sl) || (trade.type == POSITION_TYPE_SELL && new_sl < trade.sl)) { //--- Ensure minimum stop distance double min_stop = GetMinStopDistance(); double actual_distance = MathAbs(current_price - new_sl); if(actual_distance >= min_stop) { if(ModifyPosition(trade.ticket, new_sl, trade.tp)) { trade.sl = new_sl; trade.trailing_activated = true; return true; } } } return false; } //+------------------------------------------------------------------+ //| Check and apply partial close | //+------------------------------------------------------------------+ bool CTradeManager::CheckPartialClose(ManagedTrade &trade, double current_price) { //--- Check each partial level for(int i = 0; i < m_partial_count; i++) { if(m_partial_levels[i].executed) continue; //--- Calculate target price double target_distance = MathAbs(trade.open_price - trade.sl) * m_partial_levels[i].r_multiple; double target_price; if(trade.type == POSITION_TYPE_BUY) target_price = trade.open_price + target_distance; else target_price = trade.open_price - target_distance; //--- Check if target reached bool reached = false; if(trade.type == POSITION_TYPE_BUY && current_price >= target_price) reached = true; else if(trade.type == POSITION_TYPE_SELL && current_price <= target_price) reached = true; if(reached) { //--- Calculate volume to close double close_volume = trade.current_volume * (m_partial_levels[i].close_percent / 100.0); close_volume = NormalizeDouble(close_volume, 2); //--- Ensure minimum lot size double min_lot = m_symbol.LotsMin(); if(close_volume >= min_lot && trade.current_volume - close_volume >= min_lot) { if(ClosePosition(trade.ticket, close_volume)) { m_partial_levels[i].executed = true; trade.partial_count++; trade.current_volume -= close_volume; Print("TradeManager: Partial close ", close_volume, " lots at ", m_partial_levels[i].r_multiple, "R for #", trade.ticket); return true; } } } } return false; } //+------------------------------------------------------------------+ //| Apply dynamic targets based on market conditions | //+------------------------------------------------------------------+ void CTradeManager::ApplyDynamicTargets(ManagedTrade &trade, const MarketConditions &market) { bool need_modify = false; double new_sl = trade.sl; double new_tp = trade.tp; //--- Dynamic stop loss if(m_config.sl_mode == SL_DYNAMIC) { new_sl = CalculateDynamicSL(trade, market); if(new_sl != trade.sl) need_modify = true; } //--- Dynamic take profit if(m_config.tp_mode == TP_ADAPTIVE || m_config.tp_mode == TP_VOLATILITY) { new_tp = CalculateDynamicTP(trade, market); if(new_tp != trade.tp) need_modify = true; } //--- Apply modifications if needed if(need_modify && ValidateStopLevels(trade.open_price, new_sl, new_tp)) { if(ModifyPosition(trade.ticket, new_sl, new_tp)) { trade.sl = new_sl; trade.tp = new_tp; } } } //+------------------------------------------------------------------+ //| Calculate dynamic take profit | //+------------------------------------------------------------------+ double CTradeManager::CalculateDynamicTP(ManagedTrade &trade, const MarketConditions &market) { double tp_distance = 0; switch(m_config.tp_mode) { case TP_VOLATILITY: { //--- ATR-based dynamic TP double atr_multiplier = 3.0; if(market.condition == MARKET_VOLATILE) atr_multiplier = 4.0; // Wider TP in volatile markets else if(market.condition == MARKET_QUIET) atr_multiplier = 2.0; // Tighter TP in quiet markets tp_distance = market.volatility * atr_multiplier; break; } case TP_ADAPTIVE: { //--- Adaptive TP based on multiple factors double base_distance = MathAbs(trade.open_price - trade.sl) * 2.0; //--- Adjust for market conditions if(market.condition == MARKET_TRENDING_UP && trade.type == POSITION_TYPE_BUY) base_distance *= 1.5; // Extend TP in trending market else if(market.condition == MARKET_TRENDING_DOWN && trade.type == POSITION_TYPE_SELL) base_distance *= 1.5; else if(market.condition == MARKET_RANGING) base_distance *= 0.8; // Reduce TP in ranging market //--- Adjust for momentum if(market.momentum > 70 && trade.type == POSITION_TYPE_BUY) base_distance *= 1.2; else if(market.momentum < 30 && trade.type == POSITION_TYPE_SELL) base_distance *= 1.2; tp_distance = base_distance; break; } default: return trade.tp; // No change } //--- Calculate new TP double new_tp; if(trade.type == POSITION_TYPE_BUY) new_tp = trade.open_price + tp_distance; else new_tp = trade.open_price - tp_distance; return NormalizeDouble(new_tp, m_symbol.Digits()); } //+------------------------------------------------------------------+ //| Calculate dynamic stop loss | //+------------------------------------------------------------------+ double CTradeManager::CalculateDynamicSL(ManagedTrade &trade, const MarketConditions &market) { //--- Find nearest support/resistance double nearest_level = 0; double min_distance = DBL_MAX; for(int i = 0; i < market.sr_count; i++) { double distance = MathAbs(trade.open_price - market.sr_levels[i].price); //--- For buy trades, look for support below if(trade.type == POSITION_TYPE_BUY && market.sr_levels[i].price < trade.open_price && market.sr_levels[i].type == "support") { if(distance < min_distance) { min_distance = distance; nearest_level = market.sr_levels[i].price; } } //--- For sell trades, look for resistance above else if(trade.type == POSITION_TYPE_SELL && market.sr_levels[i].price > trade.open_price && market.sr_levels[i].type == "resistance") { if(distance < min_distance) { min_distance = distance; nearest_level = market.sr_levels[i].price; } } } //--- If found suitable level if(nearest_level > 0) { double buffer = market.volatility * 0.2; // 20% ATR buffer if(trade.type == POSITION_TYPE_BUY) return nearest_level - buffer; else return nearest_level + buffer; } return trade.sl; // No change } //+------------------------------------------------------------------+ //| Check time-based exit | //+------------------------------------------------------------------+ bool CTradeManager::CheckTimeBasedExit(ManagedTrade &trade) { //--- Time-based exit rules int bars_limit = 100; // Exit after 100 bars int time_limit = 86400; // Exit after 24 hours //--- Check bars in trade if(trade.bars_in_trade > bars_limit) { if(ClosePosition(trade.ticket)) { Print("TradeManager: Time exit (bars) for #", trade.ticket); return true; } } //--- Check time in trade if(TimeCurrent() - trade.open_time > time_limit) { if(ClosePosition(trade.ticket)) { Print("TradeManager: Time exit (hours) for #", trade.ticket); return true; } } return false; } //+------------------------------------------------------------------+ //| Force stop loss on external trade | //+------------------------------------------------------------------+ bool CTradeManager::ForceStopLoss(ManagedTrade &trade, double stop_distance) { if(stop_distance <= 0) return false; double new_sl; m_symbol.RefreshRates(); if(trade.type == POSITION_TYPE_BUY) new_sl = m_symbol.Bid() - stop_distance; else new_sl = m_symbol.Ask() + stop_distance; //--- Ensure minimum stop distance double min_stop = GetMinStopDistance(); double current_price = (trade.type == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask(); double actual_distance = MathAbs(current_price - new_sl); if(actual_distance < min_stop) { if(trade.type == POSITION_TYPE_BUY) new_sl = current_price - min_stop; else new_sl = current_price + min_stop; } if(ModifyPosition(trade.ticket, new_sl, trade.tp)) { trade.sl = new_sl; Print("TradeManager: Forced SL on external trade #", trade.ticket); return true; } return false; } //+------------------------------------------------------------------+ //| Force take profit on external trade | //+------------------------------------------------------------------+ bool CTradeManager::ForceTakeProfit(ManagedTrade &trade, double tp_distance) { if(tp_distance <= 0) return false; double new_tp; if(trade.type == POSITION_TYPE_BUY) new_tp = trade.open_price + tp_distance; else new_tp = trade.open_price - tp_distance; if(ModifyPosition(trade.ticket, trade.sl, new_tp)) { trade.tp = new_tp; Print("TradeManager: Forced TP on external trade #", trade.ticket); return true; } return false; } //+------------------------------------------------------------------+ //| Parse partial close levels string | //+------------------------------------------------------------------+ bool CTradeManager::ParsePartialLevels(string levels_string) { m_partial_count = 0; //--- Split by comma string parts[]; int count = StringSplit(levels_string, ',', parts); if(count == 0) return false; for(int i = 0; i < count && i < 10; i++) { //--- Split each part by colon (R:Percent) string level_parts[]; if(StringSplit(parts[i], ':', level_parts) == 2) { m_partial_levels[m_partial_count].r_multiple = StringToDouble(level_parts[0]); m_partial_levels[m_partial_count].close_percent = StringToDouble(level_parts[1]); m_partial_levels[m_partial_count].executed = false; m_partial_count++; } } Print("TradeManager: Parsed ", m_partial_count, " partial close levels"); return (m_partial_count > 0); } //+------------------------------------------------------------------+ //| Get minimum stop distance | //+------------------------------------------------------------------+ double CTradeManager::GetMinStopDistance() { int stops_level = (int)m_symbol.StopsLevel(); double min_stop = stops_level * m_symbol.Point(); //--- Add buffer return min_stop * 1.1; } //+------------------------------------------------------------------+ //| Validate stop levels | //+------------------------------------------------------------------+ bool CTradeManager::ValidateStopLevels(double price, double sl, double tp) { double min_stop = GetMinStopDistance(); //--- Check stop loss if(sl > 0) { double sl_distance = MathAbs(price - sl); if(sl_distance < min_stop) { Print("TradeManager: Stop loss too close - ", sl_distance, " < ", min_stop); return false; } } //--- Check take profit if(tp > 0) { double tp_distance = MathAbs(price - tp); if(tp_distance < min_stop) { Print("TradeManager: Take profit too close - ", tp_distance, " < ", min_stop); return false; } } return true; } #endif // TRADE_MANAGER_MQH //+------------------------------------------------------------------+