//+------------------------------------------------------------------+ //| RiskManager.mqh | //| Universal Buffer Reader EA v2.0 | //+------------------------------------------------------------------+ #property copyright "Copyright 2025" #property link "" #property version "1.00" #property strict #include //+------------------------------------------------------------------+ //| CRiskManager - Manages breakeven and trailing stop | //+------------------------------------------------------------------+ class CRiskManager { private: bool m_use_trailing_stop; int m_trailing_stop_pips; bool m_use_breakeven; int m_breakeven_pips; double m_pip_value; int m_digits; double m_stop_level_points; int m_magic_number; string m_symbol; CTrade *m_trade; // Logging bool m_enable_debug; // Partial close tracking bool m_partial_close_enabled; public: //+------------------------------------------------------------------+ //| Validated SL/TP structure | //+------------------------------------------------------------------+ struct ValidatedSLTP { double sl_price; double tp_prices[]; int tp_count; bool is_valid; string error_message; }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CRiskManager() { m_use_trailing_stop = false; m_trailing_stop_pips = 300; m_use_breakeven = true; m_breakeven_pips = 30; m_pip_value = 0; m_digits = 0; m_stop_level_points = 0; m_magic_number = 0; m_symbol = ""; m_enable_debug = false; m_partial_close_enabled = false; m_trade = new CTrade(); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CRiskManager() { if(m_trade != NULL) { delete m_trade; m_trade = NULL; } } //+------------------------------------------------------------------+ //| Set parameters | //+------------------------------------------------------------------+ void SetParameters( bool use_trailing_stop, int trailing_stop_pips, bool use_breakeven, int breakeven_pips, double pip_value, int digits, double stop_level_points, int magic_number, string symbol, bool enable_debug = false ) { // Validate parameters if(trailing_stop_pips <= 0) { Print("[ERROR] Invalid trailing stop pips: ", trailing_stop_pips, ". Using default 300"); m_trailing_stop_pips = 300; } else { m_trailing_stop_pips = trailing_stop_pips; } if(breakeven_pips <= 0) { Print("[ERROR] Invalid breakeven pips: ", breakeven_pips, ". Using default 30"); m_breakeven_pips = 30; } else { m_breakeven_pips = breakeven_pips; } if(pip_value <= 0) { Print("[ERROR] Invalid pip value: ", pip_value); } m_pip_value = pip_value; if(digits <= 0) { Print("[ERROR] Invalid digits: ", digits); } m_digits = digits; if(stop_level_points < 0) { Print("[ERROR] Invalid stop level points: ", stop_level_points); } m_stop_level_points = stop_level_points; m_use_trailing_stop = use_trailing_stop; m_use_breakeven = use_breakeven; m_magic_number = magic_number; m_symbol = symbol; m_enable_debug = enable_debug; m_trade.SetExpertMagicNumber(m_magic_number); if(m_enable_debug) { Print("[RiskManager] Parameters set:"); Print(" Use trailing stop: ", m_use_trailing_stop, " (", m_trailing_stop_pips, " pips)"); Print(" Use breakeven: ", m_use_breakeven, " (", m_breakeven_pips, " pips)"); Print(" Pip value: ", m_pip_value, ", Digits: ", m_digits); Print(" Stop level: ", m_stop_level_points, " points"); } } //+------------------------------------------------------------------+ //| Set debug mode | //+------------------------------------------------------------------+ void SetDebugMode(bool enable_debug) { m_enable_debug = enable_debug; } //+------------------------------------------------------------------+ //| Enable partial close (for TP-based trailing) | //+------------------------------------------------------------------+ void EnablePartialClose(bool enable) { m_partial_close_enabled = enable; if(m_enable_debug) { Print("[RiskManager] Partial close ", (enable ? "enabled" : "disabled")); } } //+------------------------------------------------------------------+ //| Manage breakeven and trailing stop | //+------------------------------------------------------------------+ void ManageRiskManagement() { // 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 open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_sl = PositionGetDouble(POSITION_SL); ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); bool is_buy = (pos_type == POSITION_TYPE_BUY); double current_price = is_buy ? SymbolInfoDouble(m_symbol, SYMBOL_BID) : SymbolInfoDouble(m_symbol, SYMBOL_ASK); if(m_enable_debug) { Print("[RiskManager] Checking position #", ticket); Print(" Type: ", (is_buy ? "BUY" : "SELL")); Print(" Open: ", open_price, ", Current: ", current_price, ", SL: ", current_sl); } // Check breakeven if(m_use_breakeven && current_sl != open_price) { if(ShouldMoveToBreakeven(ticket, open_price, current_price)) { TryMoveToBreakeven(ticket, open_price); } } // Check TP-based trailing if(m_partial_close_enabled) // Only if partial close is enabled { ManageTPBasedTrailing(ticket, is_buy, open_price, current_price, current_sl); } // Check standard trailing if(m_use_trailing_stop) { ManageStandardTrailing(ticket, is_buy, open_price, current_price, current_sl); } } } //+------------------------------------------------------------------+ //| Validate SL/TP to meet broker requirements | //+------------------------------------------------------------------+ ValidatedSLTP ValidateSLTP( bool is_buy, double open_price, double sl_price, double &tp_prices[], int tp_count ) { ValidatedSLTP result; result.sl_price = sl_price; result.tp_count = tp_count; result.is_valid = true; result.error_message = ""; ArrayResize(result.tp_prices, tp_count); ArrayCopy(result.tp_prices, tp_prices); // Validate SL if(sl_price != 0) { result.sl_price = AdjustToStopLevel(is_buy, sl_price, open_price); } // Validate TPs for(int i = 0; i < tp_count; i++) { if(result.tp_prices[i] != 0) { result.tp_prices[i] = AdjustToStopLevel(is_buy, result.tp_prices[i], open_price); } } return result; } private: //+------------------------------------------------------------------+ //| Check if SL should move to breakeven | //+------------------------------------------------------------------+ bool ShouldMoveToBreakeven(ulong ticket, double open_price, double current_price) { double profit_pips = 0; // Get position type if(!PositionSelectByTicket(ticket)) return false; ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); if(pos_type == POSITION_TYPE_BUY) { profit_pips = (current_price - open_price) / m_pip_value; } else { profit_pips = (open_price - current_price) / m_pip_value; } return (profit_pips >= m_breakeven_pips); } //+------------------------------------------------------------------+ //| Try to move SL to breakeven | //+------------------------------------------------------------------+ bool TryMoveToBreakeven(ulong ticket, double open_price) { if(!PositionSelectByTicket(ticket)) return false; double current_sl = PositionGetDouble(POSITION_SL); if(current_sl == open_price) return true; // Already at breakeven string comment = PositionGetString(POSITION_COMMENT); if(m_trade.PositionModify(ticket, open_price, PositionGetDouble(POSITION_TP))) { Print("[RiskManager] Breakeven set for ticket #", ticket, " at ", open_price); return true; } else { Print("[ERROR] Failed to set breakeven for ticket #", ticket, ". Error: ", m_trade.ResultRetcodeDescription()); return false; } } //+------------------------------------------------------------------+ //| Manage TP-based trailing stop | //+------------------------------------------------------------------+ void ManageTPBasedTrailing(ulong ticket, bool is_buy, double open_price, double current_price, double current_sl) { // Get TP prices from comment double tp_prices[]; int tp_count = 0; if(!GetTPPricesFromComment(ticket, tp_prices, tp_count)) return; if(tp_count == 0) return; // Check which TP level has been reached int reached_tp_level = GetReachedTPLevel(is_buy, current_price, tp_prices, tp_count); if(reached_tp_level == 0) return; // No TP reached yet if(m_enable_debug) { Print("[RiskManager] TP", reached_tp_level, " reached for ticket #", ticket); } // Determine new SL based on TP level double new_sl = 0; if(reached_tp_level == 1) { // TP1 reached: Move SL to breakeven new_sl = open_price; } else if(reached_tp_level == 2) { // TP2 reached: Move SL to TP1 new_sl = tp_prices[0]; } else if(reached_tp_level >= 3) { // TP3 reached: Move SL to TP2 new_sl = tp_prices[1]; } // Check if SL should be moved if(new_sl > 0) { bool should_move = false; if(is_buy) { should_move = (new_sl > current_sl); } else { should_move = (new_sl < current_sl); } if(should_move) { TryMoveSL(ticket, new_sl, "TP" + IntegerToString(reached_tp_level)); } } } //+------------------------------------------------------------------+ //| Manage standard trailing stop | //+------------------------------------------------------------------+ void ManageStandardTrailing(ulong ticket, bool is_buy, double open_price, double current_price, double current_sl) { // Calculate new SL based on trailing distance double new_sl = CalculateNewSL(is_buy, current_price); if(new_sl == 0) return; // Ratchet rule: SL must only move in profit direction bool should_move = false; if(is_buy) { should_move = (new_sl > current_sl); } else { should_move = (new_sl < current_sl); } if(should_move) { if(m_enable_debug) { Print("[RiskManager] Trailing SL for ticket #", ticket); Print(" Current SL: ", current_sl, ", New SL: ", new_sl); } TryMoveSL(ticket, new_sl, "TRAIL"); } } //+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+ //| Get reached TP level | //+------------------------------------------------------------------+ int GetReachedTPLevel(bool is_buy, double current_price, double &tp_prices[], int tp_count) { for(int i = 0; i < tp_count; i++) { if(tp_prices[i] == 0) continue; if(is_buy) { if(current_price >= tp_prices[i]) return (i + 1); } else { if(current_price <= tp_prices[i]) return (i + 1); } } return 0; } //+------------------------------------------------------------------+ //| Calculate new SL for trailing | //+------------------------------------------------------------------+ double CalculateNewSL(bool is_buy, double current_price) { if(m_trailing_stop_pips <= 0) return 0; double trailing_distance = m_trailing_stop_pips * m_pip_value; if(is_buy) { return NormalizeDouble(current_price - trailing_distance, m_digits); } else { return NormalizeDouble(current_price + trailing_distance, m_digits); } } //+------------------------------------------------------------------+ //| Try to move SL | //+------------------------------------------------------------------+ bool TryMoveSL(ulong ticket, double new_sl, string reason) { if(!PositionSelectByTicket(ticket)) return false; double current_tp = PositionGetDouble(POSITION_TP); if(m_trade.PositionModify(ticket, new_sl, current_tp)) { Print("[RiskManager] SL moved for ticket #", ticket, " to ", new_sl, " (", reason, ")"); return true; } else { Print("[ERROR] Failed to move SL for ticket #", ticket, ". Error: ", m_trade.ResultRetcodeDescription()); return false; } } //+------------------------------------------------------------------+ //| Adjust price to meet stop level requirements | //+------------------------------------------------------------------+ double AdjustToStopLevel(bool is_buy, double price, double open_price) { double distance = 0; if(is_buy) { if(price < open_price) // SL { distance = open_price - price; } else // TP { distance = price - open_price; } } else { if(price > open_price) // SL { distance = price - open_price; } else // TP { distance = open_price - price; } } if(distance < m_stop_level_points) { distance = m_stop_level_points; if(is_buy) { if(price < open_price) // SL { price = NormalizeDouble(open_price - distance, m_digits); } else // TP { price = NormalizeDouble(open_price + distance, m_digits); } } else { if(price > open_price) // SL { price = NormalizeDouble(open_price + distance, m_digits); } else // TP { price = NormalizeDouble(open_price - distance, m_digits); } } } return price; } }; //+------------------------------------------------------------------+