//+------------------------------------------------------------------+ //| TradeManager.mqh v2.2 | //| Optimized Trade Management Module | //| Fixed: Compatibility with ERMT 6.8 | //+------------------------------------------------------------------+ #ifndef TRADE_MANAGER_MQH #define TRADE_MANAGER_MQH #include #include #include #include "DataTypes.mqh" #include "Utilities.mqh" #include "RiskManager.mqh" #include "TechnicalAnalysis.mqh" #include "ExternalTradeManager.mqh" //+------------------------------------------------------------------+ //| Trade Manager Class | //+------------------------------------------------------------------+ class CTradeManager : public CObject { private: // Dependencies CUtilities* m_utils; CRiskManager* m_risk; CTechnicalAnalysis* m_tech; CExternalTradeManager* m_external; // Trade execution CTrade m_trade; // Settings int m_magic_number; string m_comment; double m_slippage; ENUM_POSITION_SIZING m_sizing_method; double m_fixed_lot_size; double m_atr_multiplier_sl; double m_atr_multiplier_tp; // Trailing stop settings ENUM_TRAILING_METHOD m_trailing_method; double m_trail_start; double m_trail_step; double m_trail_distance; bool m_breakeven_enabled; double m_breakeven_trigger; double m_breakeven_offset; // Trade tracking - using arrays of structures TradeInfo m_managed_trades[]; int m_trade_count; // Performance tracking PerformanceMetrics m_performance; double m_peak_balance; datetime m_last_update; // Trade history TradeInfo m_history[]; int m_history_count; // Helper methods bool UpdateTradeInfo(int index); void RecordTrade(const TradeInfo &trade); void UpdatePerformanceMetrics(); bool ExecuteMarketOrder(MqlTradeRequest &request, MqlTradeResult &result); double CalculateStopLoss(ENUM_ORDER_TYPE type, double entry, double atr); double CalculateTakeProfit(ENUM_ORDER_TYPE type, double entry, double sl); int FindTradeIndex(ulong ticket); public: // Constructor/Destructor CTradeManager(); ~CTradeManager(); // Initialization bool Initialize(CUtilities* utils, CRiskManager* risk, CTechnicalAnalysis* tech, CExternalTradeManager* external, int magic); // Configuration void SetSizingMethod(ENUM_POSITION_SIZING method) { m_sizing_method = method; } void SetFixedLotSize(double size) { m_fixed_lot_size = size; } void SetATRMultipliers(double sl_mult, double tp_mult); void SetTrailingMethod(ENUM_TRAILING_METHOD method) { m_trailing_method = method; } void SetTrailingParams(double start, double step, double distance); void SetBreakevenEnabled(bool enabled) { m_breakeven_enabled = enabled; } void SetBreakevenParams(double trigger, double offset); void SetSlippage(double slippage) { m_slippage = slippage; } void SetComment(string comment) { m_comment = comment; } // Trade execution ulong OpenTrade(MqlTradeRequest &request); bool CloseTrade(ulong ticket, string reason = ""); bool PartialClose(ulong ticket, double volume, string reason = ""); bool ModifyTrade(ulong ticket, double sl, double tp); // Position management bool ApplyTrailingStop(ulong ticket); bool CheckBreakeven(ulong ticket); bool MoveToBreakeven(ulong ticket); bool ScaleOut(ulong ticket, double percent, string reason); bool ScaleIn(ulong ticket, double additional_lots); // Trade information int GetManagedTrades(TradeInfo &trades[]); int GetOpenTradeCount() const { return m_trade_count; } bool GetTradeInfo(ulong ticket, TradeInfo &trade); double GetTotalExposure(); double GetTotalProfit(); // Performance metrics void GetPerformanceMetrics(PerformanceMetrics &metrics); double GetWinRate() const { return m_performance.win_rate; } double GetProfitFactor() const { return m_performance.profit_factor; } double GetAverageWin() const { return m_performance.average_win; } double GetAverageLoss() const { return m_performance.average_loss; } double GetMaxDrawdown() const { return m_performance.max_drawdown; } // Trade monitoring void UpdateAllTrades(); void CheckStopLevels(); void CheckTakeProfitLevels(); // Utility void CleanupHistory(); void ExportHistory(string filename); void ResetPerformance(); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTradeManager::CTradeManager() { m_utils = NULL; m_risk = NULL; m_tech = NULL; m_external = NULL; m_magic_number = 0; m_comment = ""; m_slippage = 3; m_sizing_method = SIZING_FIXED_PERCENT; m_fixed_lot_size = 0.1; m_atr_multiplier_sl = 2.0; m_atr_multiplier_tp = 3.0; m_trailing_method = TRAIL_NONE; m_trail_start = 30; m_trail_step = 10; m_trail_distance = 20; m_breakeven_enabled = false; m_breakeven_trigger = 20; m_breakeven_offset = 2; m_trade_count = 0; ArrayResize(m_managed_trades, 10); m_history_count = 0; ArrayResize(m_history, 100); m_peak_balance = 0; m_last_update = 0; // Initialize performance metrics ZeroMemory(m_performance); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CTradeManager::~CTradeManager() { ArrayFree(m_managed_trades); ArrayFree(m_history); } //+------------------------------------------------------------------+ //| Initialize | //+------------------------------------------------------------------+ bool CTradeManager::Initialize(CUtilities* utils, CRiskManager* risk, CTechnicalAnalysis* tech, CExternalTradeManager* external, int magic) { if(utils == NULL || risk == NULL || tech == NULL) return false; m_utils = utils; m_risk = risk; m_tech = tech; m_external = external; m_magic_number = magic; // Set trade parameters m_trade.SetExpertMagicNumber(m_magic_number); m_trade.SetDeviationInPoints((ulong)m_slippage); m_trade.SetTypeFilling(ORDER_FILLING_RETURN); m_trade.SetAsyncMode(false); // Initialize performance tracking m_peak_balance = AccountInfoDouble(ACCOUNT_BALANCE); m_last_update = TimeCurrent(); // Load existing positions UpdateAllTrades(); m_utils.Log("Trade Manager initialized successfully", LOG_INFO); m_utils.Log(StringFormat("Managing positions with magic: %d", m_magic_number), LOG_INFO); return true; } //+------------------------------------------------------------------+ //| Set ATR Multipliers | //+------------------------------------------------------------------+ void CTradeManager::SetATRMultipliers(double sl_mult, double tp_mult) { m_atr_multiplier_sl = sl_mult; m_atr_multiplier_tp = tp_mult; m_utils.Log(StringFormat("ATR multipliers set: SL=%.1f, TP=%.1f", sl_mult, tp_mult), LOG_DEBUG); } //+------------------------------------------------------------------+ //| Set Trailing Parameters | //+------------------------------------------------------------------+ void CTradeManager::SetTrailingParams(double start, double step, double distance) { m_trail_start = start; m_trail_step = step; m_trail_distance = distance; m_utils.Log(StringFormat("Trailing params: Start=%.0f, Step=%.0f, Distance=%.0f", start, step, distance), LOG_DEBUG); } //+------------------------------------------------------------------+ //| Set Breakeven Parameters | //+------------------------------------------------------------------+ void CTradeManager::SetBreakevenParams(double trigger, double offset) { m_breakeven_trigger = trigger; m_breakeven_offset = offset; } //+------------------------------------------------------------------+ //| Open Trade | //+------------------------------------------------------------------+ ulong CTradeManager::OpenTrade(MqlTradeRequest &request) { MqlTradeResult result; MqlTradeCheckResult check_result; ZeroMemory(result); ZeroMemory(check_result); // Ensure proper filling type request.type_filling = ORDER_FILLING_RETURN; request.deviation = (ulong)m_slippage; // Set magic and comment if not already set if(request.magic == 0) request.magic = m_magic_number; if(request.comment == "") request.comment = m_comment; // Validate stops if(!m_trade.OrderCheck(request, check_result)) { m_utils.Log(StringFormat("Order check failed: %s", check_result.comment), LOG_ERROR); return 0; } // Execute order if(!ExecuteMarketOrder(request, result)) { return 0; } // Create trade info record if(result.order > 0) { // Wait for position to appear Sleep(100); // Find the position for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(PositionGetInteger(POSITION_MAGIC) == m_magic_number && PositionGetInteger(POSITION_IDENTIFIER) == result.order) { // Create new trade record if(m_trade_count >= ArraySize(m_managed_trades)) { ArrayResize(m_managed_trades, m_trade_count + 10); } TradeInfo trade; trade.ticket = ticket; trade.symbol = request.symbol; trade.type = request.type; trade.volume = request.volume; trade.entry_price = result.price; trade.current_price = result.price; trade.sl = request.sl; trade.tp = request.tp; trade.open_time = TimeCurrent(); trade.last_update = TimeCurrent(); trade.status = TRADE_STATUS_ACTIVE; trade.comment = request.comment; trade.magic = m_magic_number; trade.is_external = false; trade.management_mode = MODE_FULL_CONTROL; trade.profit = 0; trade.swap = 0; trade.commission = 0; trade.close_requested = false; trade.close_reason = ""; trade.partial_close_volume = 0; trade.partial_close_reason = ""; // Calculate risk double stop_distance = MathAbs(result.price - request.sl); trade.risk_amount = m_risk.CalculateTradeRisk(request.volume, stop_distance, request.symbol); trade.risk_percent = (trade.risk_amount / AccountInfoDouble(ACCOUNT_BALANCE)) * 100; trade.current_rr = 0; m_managed_trades[m_trade_count] = trade; m_trade_count++; m_utils.LogTrade(trade); return ticket; } } } return result.order; } //+------------------------------------------------------------------+ //| Execute Market Order | //+------------------------------------------------------------------+ bool CTradeManager::ExecuteMarketOrder(MqlTradeRequest &request, MqlTradeResult &result) { // Try to execute order if(!m_trade.OrderSend(request, result)) { m_utils.Log(StringFormat("OrderSend failed: %d - %s", m_trade.ResultRetcode(), m_trade.ResultComment()), LOG_ERROR); return false; } // Check result if(result.retcode != TRADE_RETCODE_DONE) { m_utils.Log(StringFormat("Order failed: Retcode %d - %s", result.retcode, result.comment), LOG_ERROR); return false; } m_utils.Log(StringFormat("Order executed: #%d at %.5f", result.order, result.price), LOG_INFO); return true; } //+------------------------------------------------------------------+ //| Find Trade Index | //+------------------------------------------------------------------+ int CTradeManager::FindTradeIndex(ulong ticket) { for(int i = 0; i < m_trade_count; i++) { if(m_managed_trades[i].ticket == ticket) return i; } return -1; } //+------------------------------------------------------------------+ //| Close Trade | //+------------------------------------------------------------------+ bool CTradeManager::CloseTrade(ulong ticket, string reason) { if(!PositionSelectByTicket(ticket)) { m_utils.Log(StringFormat("Position %d not found", ticket), LOG_WARNING); return false; } // Close position if(!m_trade.PositionClose(ticket, (ulong)m_slippage)) { m_utils.Log(StringFormat("Failed to close position %d: %s", ticket, m_trade.ResultComment()), LOG_ERROR); return false; } // Update trade record int index = FindTradeIndex(ticket); if(index >= 0) { m_managed_trades[index].status = TRADE_STATUS_CLOSED; m_managed_trades[index].close_reason = reason; m_managed_trades[index].last_update = TimeCurrent(); // Record in history RecordTrade(m_managed_trades[index]); // Remove from active trades by shifting array for(int j = index; j < m_trade_count - 1; j++) { m_managed_trades[j] = m_managed_trades[j + 1]; } m_trade_count--; } m_utils.Log(StringFormat("Position %d closed: %s", ticket, reason), LOG_INFO); // Update performance UpdatePerformanceMetrics(); return true; } //+------------------------------------------------------------------+ //| Partial Close | //+------------------------------------------------------------------+ bool CTradeManager::PartialClose(ulong ticket, double volume, string reason) { if(!PositionSelectByTicket(ticket)) { m_utils.Log(StringFormat("Position %d not found", ticket), LOG_WARNING); return false; } double current_volume = PositionGetDouble(POSITION_VOLUME); if(volume >= current_volume) { // Close entire position return CloseTrade(ticket, reason); } // Partial close if(!m_trade.PositionClosePartial(ticket, volume, (ulong)m_slippage)) { m_utils.Log(StringFormat("Failed to partial close %d: %s", ticket, m_trade.ResultComment()), LOG_ERROR); return false; } // Update trade record int index = FindTradeIndex(ticket); if(index >= 0) { m_managed_trades[index].volume = current_volume - volume; m_managed_trades[index].partial_close_volume = 0; m_managed_trades[index].partial_close_reason = ""; m_managed_trades[index].last_update = TimeCurrent(); } m_utils.Log(StringFormat("Partial close %d: %.2f lots (%s)", ticket, volume, reason), LOG_INFO); return true; } //+------------------------------------------------------------------+ //| Modify Trade | //+------------------------------------------------------------------+ bool CTradeManager::ModifyTrade(ulong ticket, double sl, double tp) { if(!PositionSelectByTicket(ticket)) { m_utils.Log(StringFormat("Position %d not found", ticket), LOG_WARNING); return false; } // Validate stops string symbol = PositionGetString(POSITION_SYMBOL); ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE); double current_price = PositionGetDouble(POSITION_PRICE_CURRENT); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); int stops_level = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL); double min_stop = stops_level * point; // Validate stop loss if(sl > 0) { if(type == ORDER_TYPE_BUY) { if(sl > current_price - min_stop) { sl = current_price - min_stop; } } else { if(sl < current_price + min_stop) { sl = current_price + min_stop; } } } // Validate take profit if(tp > 0) { if(type == ORDER_TYPE_BUY) { if(tp < current_price + min_stop) { tp = current_price + min_stop; } } else { if(tp > current_price - min_stop) { tp = current_price - min_stop; } } } // Modify position if(!m_trade.PositionModify(ticket, sl, tp)) { m_utils.Log(StringFormat("Failed to modify position %d: %s", ticket, m_trade.ResultComment()), LOG_ERROR); return false; } // Update trade record int index = FindTradeIndex(ticket); if(index >= 0) { m_managed_trades[index].sl = sl; m_managed_trades[index].tp = tp; m_managed_trades[index].last_update = TimeCurrent(); // Recalculate risk double stop_distance = MathAbs(m_managed_trades[index].entry_price - sl); m_managed_trades[index].risk_amount = m_risk.CalculateTradeRisk( m_managed_trades[index].volume, stop_distance, symbol); m_managed_trades[index].risk_percent = (m_managed_trades[index].risk_amount / AccountInfoDouble(ACCOUNT_BALANCE)) * 100; } m_utils.Log(StringFormat("Position %d modified: SL=%.5f, TP=%.5f", ticket, sl, tp), LOG_DEBUG); return true; } //+------------------------------------------------------------------+ //| Apply Trailing Stop | //+------------------------------------------------------------------+ bool CTradeManager::ApplyTrailingStop(ulong ticket) { if(m_trailing_method == TRAIL_NONE) return false; if(!PositionSelectByTicket(ticket)) return false; string symbol = PositionGetString(POSITION_SYMBOL); ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE); double current_price = PositionGetDouble(POSITION_PRICE_CURRENT); double current_sl = PositionGetDouble(POSITION_SL); double entry_price = PositionGetDouble(POSITION_PRICE_OPEN); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double new_sl = 0; switch(m_trailing_method) { case TRAIL_FIXED_POINTS: { double trail_distance = m_trail_distance * point; if(type == ORDER_TYPE_BUY) { new_sl = current_price - trail_distance; if(current_sl == 0 || new_sl > current_sl) { return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP)); } } else { new_sl = current_price + trail_distance; if(current_sl == 0 || new_sl < current_sl) { return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP)); } } } break; case TRAIL_PERCENT: { double trail_distance = current_price * m_trail_distance / 100; if(type == ORDER_TYPE_BUY) { new_sl = current_price - trail_distance; if(current_sl == 0 || new_sl > current_sl) { return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP)); } } else { new_sl = current_price + trail_distance; if(current_sl == 0 || new_sl < current_sl) { return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP)); } } } break; case TRAIL_ATR: { double atr = m_tech.GetATR(); double trail_distance = atr * (m_trail_distance / 10); // Normalize if(type == ORDER_TYPE_BUY) { new_sl = current_price - trail_distance; if(current_sl == 0 || new_sl > current_sl) { return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP)); } } else { new_sl = current_price + trail_distance; if(current_sl == 0 || new_sl < current_sl) { return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP)); } } } break; case TRAIL_PARABOLIC_SAR: { int sar_handle = iSAR(symbol, PERIOD_CURRENT, 0.02, 0.2); if(sar_handle != INVALID_HANDLE) { double sar_buffer[1]; if(CopyBuffer(sar_handle, 0, 0, 1, sar_buffer) > 0) { new_sl = sar_buffer[0]; IndicatorRelease(sar_handle); if(type == ORDER_TYPE_BUY && (current_sl == 0 || new_sl > current_sl)) { return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP)); } else if(type == ORDER_TYPE_SELL && (current_sl == 0 || new_sl < current_sl)) { return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP)); } } else { IndicatorRelease(sar_handle); } } } break; case TRAIL_MOVING_AVERAGE: { double ma = m_tech.GetMA(); if(type == ORDER_TYPE_BUY && (current_sl == 0 || ma > current_sl)) { return ModifyTrade(ticket, ma, PositionGetDouble(POSITION_TP)); } else if(type == ORDER_TYPE_SELL && (current_sl == 0 || ma < current_sl)) { return ModifyTrade(ticket, ma, PositionGetDouble(POSITION_TP)); } } break; case TRAIL_STEPPED: { double profit_points = (current_price - entry_price) / point; if(type == ORDER_TYPE_SELL) profit_points = (entry_price - current_price) / point; if(profit_points >= m_trail_start) { int steps = (int)(profit_points / m_trail_step); double new_trail = steps * m_trail_step * point; if(type == ORDER_TYPE_BUY) { new_sl = entry_price + new_trail - m_trail_distance * point; if(current_sl == 0 || new_sl > current_sl) { return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP)); } } else { new_sl = entry_price - new_trail + m_trail_distance * point; if(current_sl == 0 || new_sl < current_sl) { return ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP)); } } } } break; } return false; } //+------------------------------------------------------------------+ //| Check Breakeven | //+------------------------------------------------------------------+ bool CTradeManager::CheckBreakeven(ulong ticket) { if(!m_breakeven_enabled) return false; if(!PositionSelectByTicket(ticket)) return false; double entry_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_price = PositionGetDouble(POSITION_PRICE_CURRENT); double current_sl = PositionGetDouble(POSITION_SL); ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE); string symbol = PositionGetString(POSITION_SYMBOL); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double profit_points = 0; if(type == ORDER_TYPE_BUY) { profit_points = (current_price - entry_price) / point; if(profit_points >= m_breakeven_trigger) { double new_sl = entry_price + m_breakeven_offset * point; if(current_sl < new_sl) { return MoveToBreakeven(ticket); } } } else { profit_points = (entry_price - current_price) / point; if(profit_points >= m_breakeven_trigger) { double new_sl = entry_price - m_breakeven_offset * point; if(current_sl == 0 || current_sl > new_sl) { return MoveToBreakeven(ticket); } } } return false; } //+------------------------------------------------------------------+ //| Move to Breakeven | //+------------------------------------------------------------------+ bool CTradeManager::MoveToBreakeven(ulong ticket) { if(!PositionSelectByTicket(ticket)) return false; double entry_price = PositionGetDouble(POSITION_PRICE_OPEN); ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE); string symbol = PositionGetString(POSITION_SYMBOL); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double new_sl = 0; if(type == ORDER_TYPE_BUY) { new_sl = entry_price + m_breakeven_offset * point; } else { new_sl = entry_price - m_breakeven_offset * point; } bool result = ModifyTrade(ticket, new_sl, PositionGetDouble(POSITION_TP)); if(result) { m_utils.Log(StringFormat("Position %d moved to breakeven at %.5f", ticket, new_sl), LOG_INFO); } return result; } //+------------------------------------------------------------------+ //| Get Managed Trades | //+------------------------------------------------------------------+ int CTradeManager::GetManagedTrades(TradeInfo &trades[]) { UpdateAllTrades(); ArrayResize(trades, m_trade_count); for(int i = 0; i < m_trade_count; i++) { trades[i] = m_managed_trades[i]; } return m_trade_count; } //+------------------------------------------------------------------+ //| Update All Trades | //+------------------------------------------------------------------+ void CTradeManager::UpdateAllTrades() { // Update existing trades for(int i = m_trade_count - 1; i >= 0; i--) { if(!UpdateTradeInfo(i)) { // Trade closed RecordTrade(m_managed_trades[i]); // Shift array for(int j = i; j < m_trade_count - 1; j++) { m_managed_trades[j] = m_managed_trades[j + 1]; } m_trade_count--; } } // Check for new positions for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetInteger(POSITION_MAGIC) != m_magic_number) continue; // Check if already tracked if(FindTradeIndex(ticket) >= 0) continue; // Add new trade if(m_trade_count >= ArraySize(m_managed_trades)) { ArrayResize(m_managed_trades, m_trade_count + 10); } TradeInfo trade; trade.ticket = ticket; trade.symbol = PositionGetString(POSITION_SYMBOL); trade.type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE); trade.volume = PositionGetDouble(POSITION_VOLUME); trade.entry_price = PositionGetDouble(POSITION_PRICE_OPEN); trade.current_price = PositionGetDouble(POSITION_PRICE_CURRENT); trade.sl = PositionGetDouble(POSITION_SL); trade.tp = PositionGetDouble(POSITION_TP); trade.profit = PositionGetDouble(POSITION_PROFIT); trade.swap = PositionGetDouble(POSITION_SWAP); trade.commission = 0; // Not available directly trade.open_time = (datetime)PositionGetInteger(POSITION_TIME); trade.last_update = TimeCurrent(); trade.status = TRADE_STATUS_ACTIVE; trade.comment = PositionGetString(POSITION_COMMENT); trade.magic = m_magic_number; trade.is_external = false; trade.management_mode = MODE_FULL_CONTROL; trade.close_requested = false; trade.close_reason = ""; trade.partial_close_volume = 0; trade.partial_close_reason = ""; trade.risk_amount = 0; trade.risk_percent = 0; trade.current_rr = 0; m_managed_trades[m_trade_count] = trade; m_trade_count++; m_utils.Log(StringFormat("New position detected: %d", ticket), LOG_INFO); } } //+------------------------------------------------------------------+ //| Update Trade Info | //+------------------------------------------------------------------+ bool CTradeManager::UpdateTradeInfo(int index) { if(index < 0 || index >= m_trade_count) return false; ulong ticket = m_managed_trades[index].ticket; if(!PositionSelectByTicket(ticket)) { // Position closed m_managed_trades[index].status = TRADE_STATUS_CLOSED; m_managed_trades[index].last_update = TimeCurrent(); return false; } // Update current values m_managed_trades[index].current_price = PositionGetDouble(POSITION_PRICE_CURRENT); m_managed_trades[index].profit = PositionGetDouble(POSITION_PROFIT); m_managed_trades[index].swap = PositionGetDouble(POSITION_SWAP); m_managed_trades[index].sl = PositionGetDouble(POSITION_SL); m_managed_trades[index].tp = PositionGetDouble(POSITION_TP); m_managed_trades[index].volume = PositionGetDouble(POSITION_VOLUME); m_managed_trades[index].last_update = TimeCurrent(); // Update risk metrics if(m_managed_trades[index].sl > 0) { double stop_distance = MathAbs(m_managed_trades[index].entry_price - m_managed_trades[index].sl); m_managed_trades[index].risk_amount = m_risk.CalculateTradeRisk( m_managed_trades[index].volume, stop_distance, m_managed_trades[index].symbol); m_managed_trades[index].risk_percent = (m_managed_trades[index].risk_amount / AccountInfoDouble(ACCOUNT_BALANCE)) * 100; if(m_managed_trades[index].tp > 0) { m_managed_trades[index].current_rr = m_utils.CalculateRiskReward( m_managed_trades[index].entry_price, m_managed_trades[index].sl, m_managed_trades[index].tp, m_managed_trades[index].type); } } return true; } //+------------------------------------------------------------------+ //| Record Trade in History | //+------------------------------------------------------------------+ void CTradeManager::RecordTrade(const TradeInfo &trade) { if(m_history_count >= ArraySize(m_history)) { ArrayResize(m_history, m_history_count + 100); } m_history[m_history_count] = trade; m_history_count++; // Update performance metrics UpdatePerformanceMetrics(); } //+------------------------------------------------------------------+ //| Update Performance Metrics | //+------------------------------------------------------------------+ void CTradeManager::UpdatePerformanceMetrics() { // Reset metrics ZeroMemory(m_performance); double total_wins = 0; double total_losses = 0; int win_count = 0; int loss_count = 0; // Analyze history for(int i = 0; i < m_history_count; i++) { m_performance.total_trades++; if(m_history[i].profit > 0) { m_performance.winning_trades++; total_wins += m_history[i].profit; win_count++; } else if(m_history[i].profit < 0) { m_performance.losing_trades++; total_losses += MathAbs(m_history[i].profit); loss_count++; } m_performance.total_profit += m_history[i].profit; } // Calculate metrics if(m_performance.total_trades > 0) { m_performance.win_rate = (double)m_performance.winning_trades / m_performance.total_trades * 100; } if(win_count > 0) { m_performance.average_win = total_wins / win_count; } if(loss_count > 0) { m_performance.average_loss = total_losses / loss_count; } if(total_losses > 0) { m_performance.profit_factor = total_wins / total_losses; } // Calculate drawdown double current_balance = AccountInfoDouble(ACCOUNT_BALANCE); if(current_balance > m_peak_balance) { m_peak_balance = current_balance; } m_performance.current_drawdown = ((m_peak_balance - current_balance) / m_peak_balance) * 100; m_performance.max_drawdown = MathMax(m_performance.max_drawdown, m_performance.current_drawdown); // Expected value if(m_performance.total_trades > 0) { m_performance.expected_value = m_performance.total_profit / m_performance.total_trades; } // Sharpe ratio (simplified) if(m_performance.average_loss > 0) { double avg_return = m_performance.expected_value; double std_dev = MathSqrt(MathPow(m_performance.average_win - avg_return, 2) * m_performance.win_rate / 100 + MathPow(m_performance.average_loss + avg_return, 2) * (1 - m_performance.win_rate / 100)); if(std_dev > 0) { m_performance.sharpe_ratio = avg_return / std_dev; } } // Recovery factor if(m_performance.max_drawdown > 0) { m_performance.recovery_factor = m_performance.total_profit / m_performance.max_drawdown; } m_performance.last_update = TimeCurrent(); // Update risk manager with performance data m_risk.UpdatePerformanceMetrics(m_performance.win_rate, m_performance.average_win, m_performance.average_loss); } //+------------------------------------------------------------------+ //| Get Performance Metrics | //+------------------------------------------------------------------+ void CTradeManager::GetPerformanceMetrics(PerformanceMetrics &metrics) { UpdatePerformanceMetrics(); metrics = m_performance; } //+------------------------------------------------------------------+ //| Get Total Exposure | //+------------------------------------------------------------------+ double CTradeManager::GetTotalExposure() { double total = 0; for(int i = 0; i < m_trade_count; i++) { total += m_managed_trades[i].volume; } return total; } //+------------------------------------------------------------------+ //| Get Total Profit | //+------------------------------------------------------------------+ double CTradeManager::GetTotalProfit() { double total = 0; for(int i = 0; i < m_trade_count; i++) { total += m_managed_trades[i].profit; } return total; } //+------------------------------------------------------------------+ //| Get Trade Info | //+------------------------------------------------------------------+ bool CTradeManager::GetTradeInfo(ulong ticket, TradeInfo &trade) { int index = FindTradeIndex(ticket); if(index >= 0) { trade = m_managed_trades[index]; return true; } return false; } //+------------------------------------------------------------------+ //| Cleanup History | //+------------------------------------------------------------------+ void CTradeManager::CleanupHistory() { // Keep only last 1000 trades in memory if(m_history_count > 1000) { int to_remove = m_history_count - 1000; // Shift remaining records for(int i = 0; i < 1000; i++) { m_history[i] = m_history[i + to_remove]; } m_history_count = 1000; m_utils.Log("Trade history cleaned up", LOG_DEBUG); } } //+------------------------------------------------------------------+ //| Export History | //+------------------------------------------------------------------+ void CTradeManager::ExportHistory(string filename) { if(filename == "") { filename = StringFormat("TradeHistory_%s.csv", TimeToString(TimeCurrent(), TIME_DATE)); } TradeInfo history_array[]; ArrayResize(history_array, m_history_count); for(int i = 0; i < m_history_count; i++) { history_array[i] = m_history[i]; } m_utils.SaveTradeHistory(history_array, m_history_count); } //+------------------------------------------------------------------+ //| Reset Performance | //+------------------------------------------------------------------+ void CTradeManager::ResetPerformance() { ZeroMemory(m_performance); m_peak_balance = AccountInfoDouble(ACCOUNT_BALANCE); m_last_update = TimeCurrent(); // Clear history m_history_count = 0; m_utils.Log("Performance metrics reset", LOG_INFO); } //+------------------------------------------------------------------+ //| Calculate Stop Loss | //+------------------------------------------------------------------+ double CTradeManager::CalculateStopLoss(ENUM_ORDER_TYPE type, double entry, double atr) { double sl = 0; double stop_distance = atr * m_atr_multiplier_sl; if(type == ORDER_TYPE_BUY) { sl = entry - stop_distance; } else { sl = entry + stop_distance; } return m_utils.NormalizePrice(sl, _Symbol); } //+------------------------------------------------------------------+ //| Calculate Take Profit | //+------------------------------------------------------------------+ double CTradeManager::CalculateTakeProfit(ENUM_ORDER_TYPE type, double entry, double sl) { if(sl == 0) return 0; double risk = MathAbs(entry - sl); double reward = risk * m_atr_multiplier_tp / m_atr_multiplier_sl; double tp = 0; if(type == ORDER_TYPE_BUY) { tp = entry + reward; } else { tp = entry - reward; } return m_utils.NormalizePrice(tp, _Symbol); } //+------------------------------------------------------------------+ //| Scale Out Position | //+------------------------------------------------------------------+ bool CTradeManager::ScaleOut(ulong ticket, double percent, string reason) { if(!PositionSelectByTicket(ticket)) return false; double current_volume = PositionGetDouble(POSITION_VOLUME); double close_volume = current_volume * percent / 100; return PartialClose(ticket, close_volume, reason); } //+------------------------------------------------------------------+ //| Scale In Position | //+------------------------------------------------------------------+ bool CTradeManager::ScaleIn(ulong ticket, double additional_lots) { if(!PositionSelectByTicket(ticket)) return false; // Create new position in same direction MqlTradeRequest request = {}; request.symbol = PositionGetString(POSITION_SYMBOL); request.type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE); request.volume = additional_lots; request.magic = m_magic_number; request.comment = "Scale In"; // Use same stops as original request.sl = PositionGetDouble(POSITION_SL); request.tp = PositionGetDouble(POSITION_TP); ulong new_ticket = OpenTrade(request); if(new_ticket > 0) { m_utils.Log(StringFormat("Scaled in position %d with %.2f lots", ticket, additional_lots), LOG_INFO); return true; } return false; } //+------------------------------------------------------------------+ //| Check Stop Levels | //+------------------------------------------------------------------+ void CTradeManager::CheckStopLevels() { for(int i = 0; i < m_trade_count; i++) { // Check if stop loss needs adjustment if(m_managed_trades[i].sl == 0) { // Apply default stop double atr = m_tech.GetATR(); double sl = CalculateStopLoss(m_managed_trades[i].type, m_managed_trades[i].entry_price, atr); ModifyTrade(m_managed_trades[i].ticket, sl, m_managed_trades[i].tp); } } } //+------------------------------------------------------------------+ //| Check Take Profit Levels | //+------------------------------------------------------------------+ void CTradeManager::CheckTakeProfitLevels() { for(int i = 0; i < m_trade_count; i++) { // Check if take profit needs adjustment if(m_managed_trades[i].tp == 0 && m_managed_trades[i].sl != 0) { // Apply default TP based on SL double tp = CalculateTakeProfit(m_managed_trades[i].type, m_managed_trades[i].entry_price, m_managed_trades[i].sl); ModifyTrade(m_managed_trades[i].ticket, m_managed_trades[i].sl, tp); } } } #endif // TRADE_MANAGER_MQH