//+------------------------------------------------------------------+ //| PartialCloseManager.mqh | //| Universal Buffer Reader EA v2.0 | //+------------------------------------------------------------------+ #property copyright "Copyright 2025" #property link "" #property version "1.00" #property strict #include //+------------------------------------------------------------------+ //| CPartialCloseManager - Manages multiple TPs and partial closes | //+------------------------------------------------------------------+ class CPartialCloseManager { private: bool m_enable_partial_close; bool m_use_equal_division; double m_partial_close_percentages[]; int m_magic_number; string m_symbol; CTrade *m_trade; // TP tracking to prevent duplicate closes struct TPTracking { ulong ticket; int last_closed_tp; datetime last_check_time; }; TPTracking m_tp_tracking[]; int m_max_tracking_records; // Logging bool m_enable_debug; public: //+------------------------------------------------------------------+ //| TP lot allocation structure | //+------------------------------------------------------------------+ struct TPLotAllocation { double lots; double percentage; }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPartialCloseManager() { m_enable_partial_close = true; m_use_equal_division = true; ArrayResize(m_partial_close_percentages, 0); m_magic_number = 0; m_symbol = ""; m_enable_debug = false; m_max_tracking_records = 100; ArrayResize(m_tp_tracking, 0); m_trade = new CTrade(); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CPartialCloseManager() { if(m_trade != NULL) { delete m_trade; m_trade = NULL; } ArrayResize(m_partial_close_percentages, 0); } //+------------------------------------------------------------------+ //| Set parameters | //+------------------------------------------------------------------+ void SetParameters( bool enable_partial_close, bool use_equal_division, double &partial_close_percentages[], int magic_number, string symbol, bool enable_debug = false ) { m_enable_partial_close = enable_partial_close; m_use_equal_division = use_equal_division; // Validate and copy percentages int pct_count = ArraySize(partial_close_percentages); ArrayResize(m_partial_close_percentages, pct_count); double total_pct = 0; for(int i = 0; i < pct_count; i++) { if(partial_close_percentages[i] <= 0) { Print("[WARNING] Invalid partial close percentage at index ", i, ": ", partial_close_percentages[i], ". Using 0%"); m_partial_close_percentages[i] = 0; } else if(partial_close_percentages[i] > 100) { Print("[WARNING] Partial close percentage > 100% at index ", i, ": ", partial_close_percentages[i], ". Using 100%"); m_partial_close_percentages[i] = 100; } else { m_partial_close_percentages[i] = partial_close_percentages[i]; } total_pct += m_partial_close_percentages[i]; } if(!m_use_equal_division && MathAbs(total_pct - 100.0) > 0.01) { Print("[WARNING] Partial close percentages don't sum to 100%: ", total_pct, "%"); } m_magic_number = magic_number; m_symbol = symbol; m_enable_debug = enable_debug; m_trade.SetExpertMagicNumber(m_magic_number); if(m_enable_debug) { Print("[PartialCloseManager] Parameters set:"); Print(" Enable partial close: ", m_enable_partial_close); Print(" Use equal division: ", m_use_equal_division); Print(" Partial close percentages: ", pct_count, " levels"); if(!m_use_equal_division) { for(int i = 0; i < pct_count; i++) { Print(" TP", (i + 1), ": ", m_partial_close_percentages[i], "%"); } } } } //+------------------------------------------------------------------+ //| Set debug mode | //+------------------------------------------------------------------+ void SetDebugMode(bool enable_debug) { m_enable_debug = enable_debug; } //+------------------------------------------------------------------+ //| Check if any TP has been reached and execute partial close | //+------------------------------------------------------------------+ void CheckAndExecutePartialCloses() { if(!m_enable_partial_close) { if(m_enable_debug) { Print("[PartialCloseManager] Partial close disabled"); } return; } // Iterate through all positions for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!PositionSelectByTicket(PositionGetTicket(i))) continue; if(PositionGetString(POSITION_SYMBOL) != m_symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != m_magic_number) continue; ulong ticket = PositionGetInteger(POSITION_TICKET); double total_lots = PositionGetDouble(POSITION_VOLUME); if(m_enable_debug) { Print("[PartialCloseManager] Checking position #", ticket, " (", total_lots, " lots)"); } // Get TP prices from comment double tp_prices[]; int tp_count = 0; if(!GetTPPricesFromComment(ticket, tp_prices, tp_count)) { if(m_enable_debug) { Print("[PartialCloseManager] No TPs found in comment for ticket #", ticket); } continue; } if(m_enable_debug) { Print("[PartialCloseManager] Found ", tp_count, " TP levels for ticket #", ticket); } // Get last closed TP for this ticket int last_closed_tp = GetLastClosedTP(ticket); // Check each TP level for(int tp_index = last_closed_tp; tp_index < tp_count; tp_index++) { if(IsTPReached(ticket, tp_index, tp_prices)) { // Calculate close lots TPLotAllocation allocation = CalculateTPLotAllocation(total_lots, tp_index, tp_count); if(m_enable_debug) { Print("[PartialCloseManager] TP", (tp_index + 1), " reached for ticket #", ticket); Print(" Close lots: ", allocation.lots, " (", allocation.percentage, "%)"); } // Execute partial close if(allocation.lots > 0) { if(ExecutePartialClose(ticket, allocation.lots, tp_index)) { // Update tracking UpdateTPTracking(ticket, tp_index + 1); } } } } } } //+------------------------------------------------------------------+ //| Calculate lot size for each TP level | //+------------------------------------------------------------------+ TPLotAllocation CalculateTPLotAllocation(double total_lots, int tp_index, int total_tp_count) { TPLotAllocation result; result.lots = 0; result.percentage = 0; if(m_use_equal_division) { result.lots = CalculateEqualDivisionLots(total_lots, tp_index, total_tp_count); result.percentage = 100.0 / total_tp_count; } else { result.lots = CalculateCustomPercentageLots(total_lots, tp_index); if(tp_index < ArraySize(m_partial_close_percentages)) { result.percentage = m_partial_close_percentages[tp_index]; } } return result; } private: //+------------------------------------------------------------------+ //| Get TP prices from position comment | //+------------------------------------------------------------------+ bool GetTPPricesFromComment(ulong ticket, double &tp_prices[], int &tp_count) { if(!PositionSelectByTicket(ticket)) return false; string comment = PositionGetString(POSITION_COMMENT); tp_count = 0; ArrayResize(tp_prices, 0); // Parse comment for TP values // Format: "UnivBufEA_24680_H1;TP1=1.2530;TP2=1.2560;TP3=1.2600;..." int tp_index = 1; while(true) { string search_str = ";TP" + IntegerToString(tp_index) + "="; int pos = StringFind(comment, search_str); if(pos == -1) break; // Extract TP value int start_pos = pos + StringLen(search_str); int end_pos = StringFind(comment, ";", start_pos); if(end_pos == -1) end_pos = StringLen(comment); string tp_str = StringSubstr(comment, start_pos, end_pos - start_pos); double tp_value = StringToDouble(tp_str); ArrayResize(tp_prices, tp_count + 1); tp_prices[tp_count] = tp_value; tp_count++; tp_index++; } return (tp_count > 0); } //+------------------------------------------------------------------+ //| Check if specific TP level has been reached | //+------------------------------------------------------------------+ bool IsTPReached(ulong ticket, int tp_index, double &tp_prices[]) { if(!PositionSelectByTicket(ticket)) return false; if(tp_index >= ArraySize(tp_prices)) return false; double tp_price = tp_prices[tp_index]; if(tp_price == 0) return false; ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double current_price = (pos_type == POSITION_TYPE_BUY) ? SymbolInfoDouble(m_symbol, SYMBOL_BID) : SymbolInfoDouble(m_symbol, SYMBOL_ASK); // Check if TP has been reached if(pos_type == POSITION_TYPE_BUY) { return (current_price >= tp_price); } else { return (current_price <= tp_price); } } //+------------------------------------------------------------------+ //| Execute partial close for specific TP level | //+------------------------------------------------------------------+ bool ExecutePartialClose(ulong ticket, double close_lots, int tp_index) { if(!PositionSelectByTicket(ticket)) { Print("[ERROR] Failed to select position #", ticket); return false; } double current_lots = PositionGetDouble(POSITION_VOLUME); if(close_lots <= 0) { Print("[ERROR] Invalid close lots: ", close_lots, " for ticket #", ticket); return false; } if(close_lots > current_lots) { Print("[WARNING] Close lots (", close_lots, ") > current lots (", current_lots, ") for ticket #", ticket, ". Adjusting to current lots."); close_lots = current_lots; } ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); bool result = false; if(pos_type == POSITION_TYPE_BUY) { result = m_trade.Sell(close_lots, m_symbol, 0, 0, 0, "Partial Close TP" + IntegerToString(tp_index + 1)); } else { result = m_trade.Buy(close_lots, m_symbol, 0, 0, 0, "Partial Close TP" + IntegerToString(tp_index + 1)); } if(result) { Print("[PartialCloseManager] Partial close executed: ", close_lots, " lots at TP", (tp_index + 1), " for ticket #", ticket); UpdateCommentAfterPartialClose(ticket, tp_index + 1); } else { Print("[ERROR] Failed to execute partial close for ticket #", ticket, ". Error: ", m_trade.ResultRetcodeDescription()); } return result; } //+------------------------------------------------------------------+ //| Update position comment after partial close | //+------------------------------------------------------------------+ bool UpdateCommentAfterPartialClose(ulong ticket, int tp_index) { if(!PositionSelectByTicket(ticket)) return false; string comment = PositionGetString(POSITION_COMMENT); // Update BE flag if TP1 was closed if(tp_index == 1) { // Replace BE=0 with BE=1 string be_old = ";BE=0"; string be_new = ";BE=1"; comment = StringReplace(comment, be_old, be_new); } // Update TS flag if all TPs closed // Check if this was the last TP double tp_prices[]; int tp_count = 0; if(GetTPPricesFromComment(ticket, tp_prices, tp_count)) { if(tp_index >= tp_count) { // All TPs closed, activate trailing string ts_old = ";TS=0"; string ts_new = ";TS=ACTIVE"; comment = StringReplace(comment, ts_old, ts_new); } } // Update comment if(m_trade.PositionModify(ticket, PositionGetDouble(POSITION_SL), PositionGetDouble(POSITION_TP))) { if(m_enable_debug) { Print("[PartialCloseManager] Comment updated for ticket #", ticket, ": ", comment); } return true; } else { Print("[ERROR] Failed to update comment for ticket #", ticket); return false; } } //+------------------------------------------------------------------+ //| Get last closed TP for ticket | //+------------------------------------------------------------------+ int GetLastClosedTP(ulong ticket) { for(int i = 0; i < ArraySize(m_tp_tracking); i++) { if(m_tp_tracking[i].ticket == ticket) { return m_tp_tracking[i].last_closed_tp; } } return 0; } //+------------------------------------------------------------------+ //| Update TP tracking | //+------------------------------------------------------------------+ void UpdateTPTracking(ulong ticket, int tp_level) { // Check if ticket already exists in tracking for(int i = 0; i < ArraySize(m_tp_tracking); i++) { if(m_tp_tracking[i].ticket == ticket) { m_tp_tracking[i].last_closed_tp = tp_level; m_tp_tracking[i].last_check_time = TimeCurrent(); return; } } // Add new tracking record int size = ArraySize(m_tp_tracking); if(size >= m_max_tracking_records) { // Remove oldest record ArrayCopy(m_tp_tracking, m_tp_tracking, 0, 1, size - 1); size = m_max_tracking_records - 1; } ArrayResize(m_tp_tracking, size + 1); m_tp_tracking[size].ticket = ticket; m_tp_tracking[size].last_closed_tp = tp_level; m_tp_tracking[size].last_check_time = TimeCurrent(); if(m_enable_debug) { Print("[PartialCloseManager] Added tracking for ticket #", ticket, ", TP level: ", tp_level); } } //+------------------------------------------------------------------+ //| Clear TP tracking for ticket | //+------------------------------------------------------------------+ void ClearTPTracking(ulong ticket) { for(int i = 0; i < ArraySize(m_tp_tracking); i++) { if(m_tp_tracking[i].ticket == ticket) { ArrayCopy(m_tp_tracking, m_tp_tracking, i, i + 1, ArraySize(m_tp_tracking) - i - 1); ArrayResize(m_tp_tracking, ArraySize(m_tp_tracking) - 1); return; } } } //+------------------------------------------------------------------+ //| Calculate equal division lots | //+------------------------------------------------------------------+ double CalculateEqualDivisionLots(double total_lots, int tp_index, int total_tp_count) { if(total_tp_count == 0) return 0; double equal_lots = total_lots / total_tp_count; // Last TP gets remaining lots if(tp_index == total_tp_count - 1) { return total_lots - (equal_lots * (total_tp_count - 1)); } return equal_lots; } //+------------------------------------------------------------------+ //| Calculate custom percentage lots | //+------------------------------------------------------------------+ double CalculateCustomPercentageLots(double total_lots, int tp_index) { if(tp_index >= ArraySize(m_partial_close_percentages)) return 0; double percentage = m_partial_close_percentages[tp_index]; return total_lots * (percentage / 100.0); } }; //+------------------------------------------------------------------+