//+------------------------------------------------------------------+ //| TradeManager.mqh v2.0 | //| Optimized Trade Management Module | //| Enhanced with multi-symbol support | //+------------------------------------------------------------------+ #ifndef TRADE_MANAGER_MQH #define TRADE_MANAGER_MQH #include "DataTypes.mqh" #include #include #include #include //+------------------------------------------------------------------+ //| Trade Manager Class - Optimized Version | //+------------------------------------------------------------------+ class CTradeManager { private: //--- Configuration TradeManagerConfig m_config; int m_magic_number; //--- Trade operations CTrade m_trade; CPositionInfo m_position; COrderInfo m_order; //--- Symbol cache for multi-symbol support struct SymbolCache { string symbol; CSymbolInfo info; double min_stop_distance; double point_value; int digits; datetime last_update; }; SymbolCache m_symbol_cache[]; int m_cache_size; //--- Partial close levels PartialCloseLevel m_partial_levels[10]; int m_partial_count; //--- Performance tracking int m_trades_executed; int m_trades_failed; double m_total_slippage; //--- 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(string symbol); bool ValidateStopLevels(string symbol, double price, double sl, double tp); CSymbolInfo* GetSymbolInfo(string symbol); void UpdateSymbolCache(string symbol); double CalculateTrailingDistance(ManagedTrade &trade, const MarketConditions &market); bool ExecuteTradeWithRetry(TradeRequest &request, int max_retries = 3); void LogTradeResult(string operation, bool success, string details = ""); public: CTradeManager(); ~CTradeManager(); //--- Initialization bool Initialize(const TradeManagerConfig &config, int magic); void UpdateConfiguration(const TradeManagerConfig &config); //--- Trade operations ulong OpenPosition(const TradeRequest &request); bool ClosePosition(ulong ticket, double volume = 0, string reason = ""); bool ModifyPosition(ulong ticket, double sl, double tp); //--- Batch operations int CloseAllPositions(string symbol = NULL, ENUM_POSITION_TYPE type = -1); int BreakevenAllPositions(string symbol = NULL); //--- Trade management void ManageTrade(ManagedTrade &trade, const MarketConditions &market); void ApplyBreakeven(ManagedTrade &trade); void ApplyTrailingStop(ManagedTrade &trade, const MarketConditions &market); 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); ValidationResult ValidateExternalTrade(ManagedTrade &trade); //--- Utility functions double GetCurrentPrice(string symbol, ENUM_POSITION_TYPE type); double CalculateCommission(string symbol, double volume); bool IsMarketOpen(string symbol); //--- Performance metrics int GetTradesExecuted() { return m_trades_executed; } int GetTradesFailed() { return m_trades_failed; } double GetAverageSlippage() { return (m_trades_executed > 0) ? m_total_slippage / m_trades_executed : 0; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTradeManager::CTradeManager() { m_magic_number = 0; m_partial_count = 0; m_cache_size = 0; m_trades_executed = 0; m_trades_failed = 0; m_total_slippage = 0; //--- Pre-allocate symbol cache ArrayResize(m_symbol_cache, 20); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CTradeManager::~CTradeManager() { //--- Clean up symbol cache for(int i = 0; i < m_cache_size; i++) { // CSymbolInfo doesn't need explicit cleanup } } //+------------------------------------------------------------------+ //| 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); //--- 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; } } //--- Initialize symbol cache with current symbol UpdateSymbolCache(_Symbol); Print("TradeManager initialized: TP=", EnumToString(m_config.tp_mode), ", SL=", EnumToString(m_config.sl_mode)); return true; } //+------------------------------------------------------------------+ //| Open new position with retry logic | //+------------------------------------------------------------------+ ulong CTradeManager::OpenPosition(const TradeRequest &request) { //--- Get symbol info CSymbolInfo *symbol_info = GetSymbolInfo(request.symbol); if(symbol_info == NULL) { LogTradeResult("OpenPosition", false, "Invalid symbol: " + request.symbol); return 0; } //--- Refresh rates symbol_info.RefreshRates(); //--- Calculate actual prices double price = 0; double sl = 0; double tp = 0; if(request.type == ORDER_TYPE_BUY) { price = symbol_info.Ask(); if(request.sl_distance > 0) sl = NormalizeDouble(price - request.sl_distance, symbol_info.Digits()); if(request.tp_distance > 0) tp = NormalizeDouble(price + request.tp_distance, symbol_info.Digits()); } else if(request.type == ORDER_TYPE_SELL) { price = symbol_info.Bid(); if(request.sl_distance > 0) sl = NormalizeDouble(price + request.sl_distance, symbol_info.Digits()); if(request.tp_distance > 0) tp = NormalizeDouble(price - request.tp_distance, symbol_info.Digits()); } else { LogTradeResult("OpenPosition", false, "Invalid order type"); return 0; } //--- Validate stop levels if(!ValidateStopLevels(request.symbol, price, sl, tp)) { LogTradeResult("OpenPosition", false, "Invalid stop levels"); return 0; } //--- Prepare modified request for retry TradeRequest retry_request = request; retry_request.price = price; //--- Execute with retry bool result = false; ulong ticket = 0; int retries = 0; while(retries < 3 && !result) { 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) { ticket = m_trade.ResultOrder(); //--- Track slippage double executed_price = m_trade.ResultPrice(); double slippage = MathAbs(executed_price - price) / symbol_info.Point(); m_total_slippage += slippage; m_trades_executed++; LogTradeResult("OpenPosition", true, StringFormat("#%d %s %.2f @ %.5f (slippage: %.1f points)", ticket, (request.type == ORDER_TYPE_BUY) ? "BUY" : "SELL", request.volume, executed_price, slippage)); } else { //--- Handle specific errors int error = GetLastError(); switch(error) { case ERR_TRADE_PRICE_INVALID: case ERR_TRADE_PRICE_OFF: // Refresh and retry symbol_info.RefreshRates(); if(request.type == ORDER_TYPE_BUY) price = symbol_info.Ask(); else price = symbol_info.Bid(); break; case ERR_TRADE_CONTEXT_BUSY: // Wait and retry Sleep(500); break; default: // Don't retry on other errors retries = 3; break; } retries++; if(retries < 3) { Print("TradeManager: Retrying order (attempt ", retries + 1, "/3)"); } } } if(!result) { m_trades_failed++; LogTradeResult("OpenPosition", false, "Failed after " + IntegerToString(retries) + " attempts: " + m_trade.ResultComment()); } return ticket; } //+------------------------------------------------------------------+ //| Close position with partial support | //+------------------------------------------------------------------+ bool CTradeManager::ClosePosition(ulong ticket, double volume, string reason) { if(!m_position.SelectByTicket(ticket)) { LogTradeResult("ClosePosition", false, "Position not found: " + IntegerToString(ticket)); return false; } string symbol = m_position.Symbol(); CSymbolInfo *symbol_info = GetSymbolInfo(symbol); if(symbol_info == NULL) return false; symbol_info.RefreshRates(); bool result = false; if(volume == 0 || volume >= m_position.Volume()) { //--- Full close result = m_trade.PositionClose(ticket); if(result) { LogTradeResult("ClosePosition", true, StringFormat("Closed #%d %s (Reason: %s)", ticket, symbol, reason)); } } else { //--- Partial close volume = NormalizeLotSize(volume, symbol); if(volume >= symbol_info.LotsMin() && m_position.Volume() - volume >= symbol_info.LotsMin()) { result = m_trade.PositionClosePartial(ticket, volume); if(result) { LogTradeResult("ClosePosition", true, StringFormat("Partially closed %.2f lots of #%d (Reason: %s)", volume, ticket, reason)); } } else { LogTradeResult("ClosePosition", false, "Invalid partial volume"); } } if(!result) { LogTradeResult("ClosePosition", false, "Failed to close #" + IntegerToString(ticket) + ": " + m_trade.ResultComment()); } return result; } //+------------------------------------------------------------------+ //| Modify position stops | //+------------------------------------------------------------------+ bool CTradeManager::ModifyPosition(ulong ticket, double sl, double tp) { if(!m_position.SelectByTicket(ticket)) { LogTradeResult("ModifyPosition", false, "Position not found: " + IntegerToString(ticket)); return false; } //--- Check if modification needed if(NormalizeDouble(sl - m_position.StopLoss(), _Digits) == 0 && NormalizeDouble(tp - m_position.TakeProfit(), _Digits) == 0) { return true; // No change needed } //--- Validate new levels string symbol = m_position.Symbol(); double current_price = GetCurrentPrice(symbol, m_position.PositionType()); if(!ValidateStopLevels(symbol, current_price, sl, tp)) { LogTradeResult("ModifyPosition", false, "Invalid stop levels for modification"); return false; } //--- Execute modification bool result = m_trade.PositionModify(ticket, sl, tp); if(result) { LogTradeResult("ModifyPosition", true, StringFormat("Modified #%d: SL=%.5f TP=%.5f", ticket, sl, tp)); } else { LogTradeResult("ModifyPosition", false, "Failed to modify #" + IntegerToString(ticket) + ": " + m_trade.ResultComment()); } return result; } //+------------------------------------------------------------------+ //| Main trade management function - Optimized | //+------------------------------------------------------------------+ void CTradeManager::ManageTrade(ManagedTrade &trade, const MarketConditions &market) { //--- Get current price efficiently double current_price = GetCurrentPrice(trade.symbol, trade.type); if(current_price <= 0) return; //--- Update trade metrics double points_moved = (trade.type == POSITION_TYPE_BUY) ? (current_price - trade.open_price) : (trade.open_price - current_price); //--- Check if position improved bool position_improved = points_moved > 0; //--- Force stops on external trades if configured if(trade.is_external && trade.magic != m_magic_number) { if(m_config.force_sl && trade.sl == 0) { double atr = market.volatility; if(atr > 0) { ForceStopLoss(trade, atr * 2.0); } } if(m_config.force_tp && trade.tp == 0) { double atr = market.volatility; if(atr > 0) { ForceTakeProfit(trade, atr * 3.0); } } } //--- Management sequence (optimized order) //--- 1. Breakeven (first priority when profitable) if(m_config.enable_breakeven && !trade.be_activated && position_improved) { if(CheckBreakeven(trade, current_price)) { trade.be_activated = true; return; // One modification per tick } } //--- 2. Partial closing (before trailing to lock profits) if(m_config.enable_partial && trade.partial_count < m_partial_count && position_improved) { if(CheckPartialClose(trade, current_price)) { return; // Position modified } } //--- 3. Trailing stop (after breakeven) if(m_config.enable_trailing && trade.be_activated) { if(CheckTrailingStop(trade, current_price)) { trade.trailing_activated = true; return; // One modification per tick } } //--- 4. Dynamic targets (adaptive mode) if(m_config.tp_mode == TP_ADAPTIVE || m_config.tp_mode == TP_HYBRID || m_config.sl_mode == SL_DYNAMIC) { ApplyDynamicTargets(trade, market); } //--- 5. Time-based exits if(m_config.tp_mode == TP_TIME_BASED || m_config.time_exit_bars > 0) { CheckTimeBasedExit(trade); } //--- Update bars in trade trade.bars_in_trade = iBars(trade.symbol, PERIOD_CURRENT) - iBarShift(trade.symbol, PERIOD_CURRENT, trade.open_time); } //+------------------------------------------------------------------+ //| Optimized breakeven check | //+------------------------------------------------------------------+ bool CTradeManager::CheckBreakeven(ManagedTrade &trade, double current_price) { if(trade.risk_amount <= 0) return false; //--- Calculate trigger distance based on initial risk double risk_distance = MathAbs(trade.open_price - trade.sl); if(risk_distance <= 0) return false; double trigger_distance = risk_distance * m_config.breakeven_trigger; double price_moved = (trade.type == POSITION_TYPE_BUY) ? (current_price - trade.open_price) : (trade.open_price - current_price); //--- Check if trigger reached if(price_moved >= trigger_distance) { //--- Calculate new stop loss CSymbolInfo *symbol_info = GetSymbolInfo(trade.symbol); if(symbol_info == NULL) return false; double buffer = m_config.breakeven_buffer * symbol_info.Point(); if(buffer <= 0) buffer = symbol_info.Point() * 10; // Default 10 points 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 bool should_modify = false; if(trade.type == POSITION_TYPE_BUY && new_sl > trade.sl) should_modify = true; else if(trade.type == POSITION_TYPE_SELL && new_sl < trade.sl) should_modify = true; if(should_modify) { if(ModifyPosition(trade.ticket, new_sl, trade.tp)) { trade.sl = new_sl; Print("TradeManager: Breakeven activated for #", trade.ticket, " at ", DoubleToString(new_sl, symbol_info.Digits())); return true; } } } return false; } //+------------------------------------------------------------------+ //| Optimized trailing stop with multiple modes | //+------------------------------------------------------------------+ bool CTradeManager::CheckTrailingStop(ManagedTrade &trade, double current_price) { //--- Calculate trailing distance double trail_distance = CalculateTrailingDistance(trade, g_MarketConditions); if(trail_distance <= 0) return false; //--- Get symbol info CSymbolInfo *symbol_info = GetSymbolInfo(trade.symbol); if(symbol_info == NULL) 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; //--- Apply step if configured if(m_config.trailing_step > 0) { double step_size = m_config.trailing_step * symbol_info.Point(); double sl_change = MathAbs(new_sl - trade.sl); if(sl_change < step_size) return false; // Not enough movement } //--- Ensure trailing only improves position bool should_trail = false; if(trade.type == POSITION_TYPE_BUY && new_sl > trade.sl) should_trail = true; else if(trade.type == POSITION_TYPE_SELL && new_sl < trade.sl) should_trail = true; if(should_trail) { //--- Validate stop distance double min_stop = GetMinStopDistance(trade.symbol); 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; return true; } } } return false; } //+------------------------------------------------------------------+ //| Calculate dynamic trailing distance | //+------------------------------------------------------------------+ double CTradeManager::CalculateTrailingDistance(ManagedTrade &trade, const MarketConditions &market) { double trail_distance = 0; switch(m_config.sl_mode) { case SL_TRAILING_ATR: { //--- ATR-based with multiplier double multiplier = m_config.use_atr_multiplier ? 1.5 : 2.0; //--- Adjust for market conditions if(market.condition == MARKET_VOLATILE) multiplier *= 1.2; else if(market.condition == MARKET_QUIET) multiplier *= 0.8; trail_distance = market.volatility * multiplier; break; } case SL_PARABOLIC_SAR: { //--- Get SAR value double sar_buffer[1]; int sar_handle = iSAR(trade.symbol, PERIOD_CURRENT, 0.02, 0.2); if(sar_handle != INVALID_HANDLE && CopyBuffer(sar_handle, 0, 0, 1, sar_buffer) > 0) { if(trade.type == POSITION_TYPE_BUY) trail_distance = GetCurrentPrice(trade.symbol, trade.type) - sar_buffer[0]; else trail_distance = sar_buffer[0] - GetCurrentPrice(trade.symbol, trade.type); } IndicatorRelease(sar_handle); break; } case SL_SWING_POINTS: { //--- Find recent swing point int lookback = 20; double swing_point = 0; if(trade.type == POSITION_TYPE_BUY) { //--- Find recent swing low int lowest_bar = iLowest(trade.symbol, PERIOD_CURRENT, MODE_LOW, lookback, 1); if(lowest_bar >= 0) swing_point = iLow(trade.symbol, PERIOD_CURRENT, lowest_bar); } else { //--- Find recent swing high int highest_bar = iHighest(trade.symbol, PERIOD_CURRENT, MODE_HIGH, lookback, 1); if(highest_bar >= 0) swing_point = iHigh(trade.symbol, PERIOD_CURRENT, highest_bar); } if(swing_point > 0) { double current = GetCurrentPrice(trade.symbol, trade.type); trail_distance = MathAbs(current - swing_point); } break; } default: { //--- Default: percentage of profit double profit_distance = MathAbs(GetCurrentPrice(trade.symbol, trade.type) - trade.open_price); trail_distance = profit_distance * 0.5; // Trail at 50% of profit //--- Minimum trail distance double min_trail = market.volatility * 0.5; trail_distance = MathMax(trail_distance, min_trail); break; } } return trail_distance; } //+------------------------------------------------------------------+ //| Check and apply partial close - Optimized | //+------------------------------------------------------------------+ bool CTradeManager::CheckPartialClose(ManagedTrade &trade, double current_price) { //--- Find next unexecuted level int next_level = -1; for(int i = 0; i < m_partial_count; i++) { if(!m_partial_levels[i].executed) { next_level = i; break; } } if(next_level < 0) return false; //--- Calculate current R-multiple double current_r = 0; if(trade.sl > 0) { double risk_distance = MathAbs(trade.open_price - trade.sl); if(risk_distance > 0) { double profit_distance = (trade.type == POSITION_TYPE_BUY) ? (current_price - trade.open_price) : (trade.open_price - current_price); current_r = profit_distance / risk_distance; } } //--- Check if target reached if(current_r >= m_partial_levels[next_level].r_multiple) { //--- Calculate volume to close double close_volume = trade.current_volume * (m_partial_levels[next_level].close_percent / 100.0); //--- Get symbol info for lot normalization CSymbolInfo *symbol_info = GetSymbolInfo(trade.symbol); if(symbol_info == NULL) return false; close_volume = NormalizeLotSize(close_volume, trade.symbol); //--- Ensure minimum lot size and remaining volume if(close_volume >= symbol_info.LotsMin() && trade.current_volume - close_volume >= symbol_info.LotsMin()) { string reason = StringFormat("Partial close at %.1fR (%.0f%%)", m_partial_levels[next_level].r_multiple, m_partial_levels[next_level].close_percent); if(ClosePosition(trade.ticket, close_volume, reason)) { m_partial_levels[next_level].executed = true; m_partial_levels[next_level].execution_time = TimeCurrent(); trade.partial_count++; trade.current_volume -= close_volume; return true; } } } return false; } //+------------------------------------------------------------------+ //| Get symbol info with caching | //+------------------------------------------------------------------+ CSymbolInfo* CTradeManager::GetSymbolInfo(string symbol) { //--- Search in cache for(int i = 0; i < m_cache_size; i++) { if(m_symbol_cache[i].symbol == symbol) { //--- Update if cache is old (> 60 seconds) if(TimeCurrent() - m_symbol_cache[i].last_update > 60) { UpdateSymbolCache(symbol); } return &m_symbol_cache[i].info; } } //--- Not in cache, add it UpdateSymbolCache(symbol); //--- Search again for(int i = 0; i < m_cache_size; i++) { if(m_symbol_cache[i].symbol == symbol) return &m_symbol_cache[i].info; } return NULL; } //+------------------------------------------------------------------+ //| Update symbol cache | //+------------------------------------------------------------------+ void CTradeManager::UpdateSymbolCache(string symbol) { //--- Find existing or get free slot int index = -1; for(int i = 0; i < m_cache_size; i++) { if(m_symbol_cache[i].symbol == symbol) { index = i; break; } } if(index < 0) { if(m_cache_size < ArraySize(m_symbol_cache)) { index = m_cache_size; m_cache_size++; } else { //--- Cache full, replace oldest datetime oldest_time = TimeCurrent(); index = 0; for(int i = 0; i < m_cache_size; i++) { if(m_symbol_cache[i].last_update < oldest_time) { oldest_time = m_symbol_cache[i].last_update; index = i; } } } } //--- Update cache entry m_symbol_cache[index].symbol = symbol; m_symbol_cache[index].info.Name(symbol); m_symbol_cache[index].info.RefreshRates(); //--- Cache frequently used values m_symbol_cache[index].min_stop_distance = m_symbol_cache[index].info.StopsLevel() * m_symbol_cache[index].info.Point() * 1.1; m_symbol_cache[index].point_value = m_symbol_cache[index].info.Point(); m_symbol_cache[index].digits = m_symbol_cache[index].info.Digits(); m_symbol_cache[index].last_update = TimeCurrent(); } //+------------------------------------------------------------------+ //| Get current price for symbol and direction | //+------------------------------------------------------------------+ double CTradeManager::GetCurrentPrice(string symbol, ENUM_POSITION_TYPE type) { CSymbolInfo *symbol_info = GetSymbolInfo(symbol); if(symbol_info == NULL) return 0; symbol_info.RefreshRates(); return (type == POSITION_TYPE_BUY) ? symbol_info.Bid() : symbol_info.Ask(); } //+------------------------------------------------------------------+ //| Get minimum stop distance with cache | //+------------------------------------------------------------------+ double CTradeManager::GetMinStopDistance(string symbol) { //--- Try cache first for(int i = 0; i < m_cache_size; i++) { if(m_symbol_cache[i].symbol == symbol) { return m_symbol_cache[i].min_stop_distance; } } //--- Not cached, calculate CSymbolInfo *symbol_info = GetSymbolInfo(symbol); if(symbol_info == NULL) return 0; return symbol_info.StopsLevel() * symbol_info.Point() * 1.1; } //+------------------------------------------------------------------+ //| Apply default stops to external trades | //+------------------------------------------------------------------+ void CTradeManager::ApplyDefaultStops(ManagedTrade &trade, double atr_value) { if(atr_value <= 0) return; bool needs_update = false; double new_sl = trade.sl; double new_tp = trade.tp; //--- Add stop loss if missing if(trade.sl == 0 && m_config.force_sl) { double sl_distance = atr_value * 2.0; // 2 ATR default if(trade.type == POSITION_TYPE_BUY) new_sl = trade.open_price - sl_distance; else new_sl = trade.open_price + sl_distance; needs_update = true; } //--- Add take profit if missing if(trade.tp == 0 && m_config.force_tp) { double tp_distance = atr_value * 3.0; // 3 ATR default if(trade.type == POSITION_TYPE_BUY) new_tp = trade.open_price + tp_distance; else new_tp = trade.open_price - tp_distance; needs_update = true; } //--- Apply modifications if(needs_update) { if(ModifyPosition(trade.ticket, new_sl, new_tp)) { trade.sl = new_sl; trade.tp = new_tp; Print("TradeManager: Applied default stops to external trade #", trade.ticket, " SL=", DoubleToString(new_sl, _Digits), " TP=", DoubleToString(new_tp, _Digits)); } } } //+------------------------------------------------------------------+ //| Validate stop levels | //+------------------------------------------------------------------+ bool CTradeManager::ValidateStopLevels(string symbol, double price, double sl, double tp) { double min_stop = GetMinStopDistance(symbol); //--- 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; } //+------------------------------------------------------------------+ //| Parse partial close levels string | //+------------------------------------------------------------------+ bool CTradeManager::ParsePartialLevels(string levels_string) { m_partial_count = 0; //--- Reset all levels for(int i = 0; i < 10; i++) { m_partial_levels[i].r_multiple = 0; m_partial_levels[i].close_percent = 0; m_partial_levels[i].executed = false; m_partial_levels[i].execution_time = 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++) { //--- Trim spaces StringTrimLeft(parts[i]); StringTrimRight(parts[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++; } } //--- Sort levels by R-multiple (ascending) for(int i = 0; i < m_partial_count - 1; i++) { for(int j = i + 1; j < m_partial_count; j++) { if(m_partial_levels[i].r_multiple > m_partial_levels[j].r_multiple) { PartialCloseLevel temp = m_partial_levels[i]; m_partial_levels[i] = m_partial_levels[j]; m_partial_levels[j] = temp; } } } Print("TradeManager: Parsed ", m_partial_count, " partial close levels"); return (m_partial_count > 0); } //+------------------------------------------------------------------+ //| Log trade operation results | //+------------------------------------------------------------------+ void CTradeManager::LogTradeResult(string operation, bool success, string details) { ENUM_LOG_LEVEL level = success ? LOG_INFO : LOG_ERROR; string prefix = success ? "SUCCESS" : "FAILED"; string message = StringFormat("TradeManager::%s [%s] %s", operation, prefix, details); Print(message); } //+------------------------------------------------------------------+ //| Close all positions | //+------------------------------------------------------------------+ int CTradeManager::CloseAllPositions(string symbol, ENUM_POSITION_TYPE type) { int closed = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(m_position.SelectByIndex(i)) { //--- Check filters if(symbol != NULL && m_position.Symbol() != symbol) continue; if(type != -1 && m_position.PositionType() != type) continue; //--- Close position if(ClosePosition(m_position.Ticket(), 0, "Close All Command")) closed++; } } return closed; } //+------------------------------------------------------------------+ //| Apply breakeven to all positions | //+------------------------------------------------------------------+ int CTradeManager::BreakevenAllPositions(string symbol) { int modified = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(m_position.SelectByIndex(i)) { //--- Check filter if(symbol != NULL && m_position.Symbol() != symbol) continue; //--- Create temporary trade structure ManagedTrade temp_trade; temp_trade.ticket = m_position.Ticket(); temp_trade.symbol = m_position.Symbol(); temp_trade.type = m_position.PositionType(); temp_trade.open_price = m_position.PriceOpen(); temp_trade.sl = m_position.StopLoss(); temp_trade.tp = m_position.TakeProfit(); temp_trade.be_activated = false; //--- Apply breakeven double current_price = GetCurrentPrice(temp_trade.symbol, temp_trade.type); if(CheckBreakeven(temp_trade, current_price)) modified++; } } return modified; } #endif // TRADE_MANAGER_MQH //+------------------------------------------------------------------+