//+------------------------------------------------------------------+ //| Universal Buffer Reader EA v2.0 | //| Combined Single File Version | //+------------------------------------------------------------------+ #property copyright "Copyright 2025" #property link "" #property version "2.0" #property strict #include #include #include //+------------------------------------------------------------------+ //| CLASS: CLoggingManager | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CLoggingManager - Smart logging with error deduplication | //+------------------------------------------------------------------+ class CLoggingManager { private: bool m_enable_debug; string m_session_id; // Error tracking for deduplication struct ErrorRecord { int error_code; string error_message; int count; datetime first_seen; datetime last_seen; }; ErrorRecord m_error_records[]; int m_max_error_records; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLoggingManager() { m_enable_debug = false; m_session_id = ""; m_max_error_records = 50; ArrayResize(m_error_records, 0); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CLoggingManager() { ArrayResize(m_error_records, 0); } //+------------------------------------------------------------------+ //| Set parameters | //+------------------------------------------------------------------+ void SetParameters(bool enable_debug) { m_enable_debug = enable_debug; m_session_id = GenerateSessionID(); ClearErrorRecords(); } //+------------------------------------------------------------------+ //| Log info message | //+------------------------------------------------------------------+ void LogInfo(string message) { Print("[INFO] ", message); } //+------------------------------------------------------------------+ //| Log warning message | //+------------------------------------------------------------------+ void LogWarning(string message) { Print("[WARNING] ", message); } //+------------------------------------------------------------------+ //| Get error description | //+------------------------------------------------------------------+ string ErrorDescription(int error_code) { switch(error_code) { case 0: return "No error"; case 1: return "No error, but result is unknown"; case 2: return "Common error"; case 3: return "Invalid parameters"; case 4: return "Trade server is busy"; case 5: return "Old version of the client terminal"; case 6: return "No connection with trade server"; case 7: return "Not enough rights"; case 8: return "Too frequent requests"; case 9: return "Malfunctional trade operation"; case 64: return "Account disabled"; case 65: return "Invalid account"; case 128: return "Trade timeout"; case 129: return "Invalid price"; case 130: return "Invalid stops"; case 131: return "Invalid volume"; case 132: return "Market is closed"; case 133: return "Trade is disabled"; case 134: return "Not enough money"; case 135: return "Price changed"; case 136: return "No prices"; case 137: return "Broker is busy"; case 138: return "New prices (requote)"; case 139: return "Order locked"; case 140: return "Long positions only allowed"; case 141: return "Too many requests"; case 145: return "Modification denied because order is too close to market"; case 146: return "Trade context is busy"; case 147: return "Expirations are denied by broker"; case 148: return "Too many orders"; case 149: return "Hedge is prohibited"; case 150: return "Prohibited by FIFO rules"; case 4000: return "No error"; case 4001: return "Wrong function pointer"; case 4002: return "Array index is out of range"; case 4003: return "No memory for function call stack"; case 4004: return "Recursive stack overflow"; case 4005: return "Not enough stack for parameter"; case 4006: return "No memory for parameter string"; case 4007: return "No memory for temp string"; case 4008: return "Not initialized string"; case 4009: return "Not initialized arraystring"; case 4010: return "No memory for arraystring"; case 4011: return "Too long string"; case 4012: return "Remainder from zero divide"; case 4013: return "Zero divide"; case 4014: return "Unknown command"; case 4015: return "Wrong jump (never generated error)"; case 4016: return "Not initialized array"; case 4017: return "DLL calls are not allowed"; case 4018: return "Cannot load library"; case 4019: return "Cannot call function"; case 4020: return "External function calls are not allowed"; case 4021: return "Not enough memory for temp string"; case 4022: return "System is busy (never generated error)"; case 4023: return "Internal error"; case 4024: return "Out of memory"; case 4025: return "Invalid pointer"; case 4026: return "Too long string (up to 256 characters)"; case 4027: return "Structures or classes containing objects are not allowed"; case 4028: return "Not enough memory for string"; case 4029: return "Not enough memory for arraystring"; case 4030: return "Not enough memory for array"; case 4031: return "Unknown object type"; case 4032: return "Invalid object type"; case 4033: return "Object is not initialized"; case 4034: return "Cannot apply delete operation"; case 4035: return "Too many objects"; case 4036: return "Cannot create object"; case 4037: return "Invalid object pointer"; case 4038: return "Too many array dimensions"; case 4039: return "Access to arrayindex is out of range"; case 4040: return "Custom indicator error"; case 4041: return "Incorrect series array using"; case 4042: return "Custom indicator error"; case 4043: return "Arrays are incompatible"; case 4044: return "Series array cannot be used as timeseries"; case 4045: return "Custom indicator error"; case 4046: return "Internal error"; case 4047: return "Custom indicator error"; case 4048: return "Internal error"; case 4049: return "String error"; case 4050: return "String error"; case 4051: return "String error"; case 4052: return "String error"; case 4053: return "String error"; case 4054: return "Too long string"; case 4055: return "String error"; case 4056: return "String error"; case 4057: return "String error"; case 4058: return "String error"; case 4059: return "String error"; case 4060: return "String error"; case 4061: return "Array error"; case 4062: return "Array error"; case 4063: return "Array error"; case 4064: return "Array error"; case 4065: return "Array error"; case 4066: return "Array error"; case 4067: return "Array error"; case 4068: return "Array error"; case 4069: return "String error"; case 4070: return "String error"; case 4071: return "String error"; case 4072: return "String error"; case 4073: return "String error"; case 4074: return "String error"; case 4075: return "String error"; case 4076: return "String error"; case 4077: return "String error"; case 4078: return "String error"; case 4079: return "String error"; case 4080: return "String error"; case 4081: return "Too many array dimensions"; case 4082: return "Too many array dimensions"; case 4083: return "Too many array dimensions"; case 4084: return "Too many array dimensions"; case 4085: return "Array error"; case 4086: return "Array error"; case 4087: return "Array error"; case 4088: return "Array error"; case 4089: return "Array error"; case 4090: return "Array error"; case 4091: return "Array error"; case 4092: return "Array error"; case 4093: return "Array error"; case 4094: return "Array error"; case 4095: return "Array error"; case 4096: return "Array error"; case 4097: return "Array error"; case 4098: return "Array error"; case 4099: return "Array error"; case 4100: return "Array error"; case 4101: return "Array error"; case 4102: return "Array error"; case 4103: return "Array error"; case 4104: return "Array error"; case 4105: return "Array error"; case 4106: return "Array error"; case 4107: return "Array error"; case 4108: return "Array error"; case 4109: return "Array error"; case 4110: return "Array error"; case 4111: return "Array error"; case 4112: return "Array error"; case 4113: return "Array error"; case 4114: return "Array error"; case 4115: return "Array error"; case 4116: return "Array error"; case 4117: return "Array error"; case 4118: return "Array error"; case 4119: return "Array error"; case 4200: return "Object is not exist"; case 4201: return "Unknown object property"; case 4202: return "Object is not exist"; case 4203: return "Unknown object type"; case 4204: return "No object name"; case 4205: return "Object coordinates error"; case 4206: return "No specified subwindow"; case 4207: return "Some object error"; default: return "Unknown error code: " + IntegerToString(error_code); } } //+------------------------------------------------------------------+ //| Log error with deduplication | //+------------------------------------------------------------------+ void LogError(int error_code, string context) { string error_message = ErrorDescription(error_code); // Check if this error has been logged before if(IsErrorDuplicate(error_code, error_message)) { // Update existing record for(int i = 0; i < ArraySize(m_error_records); i++) { if(m_error_records[i].error_code == error_code && m_error_records[i].error_message == error_message) { m_error_records[i].count++; m_error_records[i].last_seen = TimeCurrent(); // Log with repetition count Print("[ERROR] ", FormatErrorMessage(error_code, context, m_error_records[i].count)); return; } } } else { // New error - add to tracking AddErrorRecord(error_code, error_message); Print("[ERROR] ", FormatErrorMessage(error_code, context, 1)); } } //+------------------------------------------------------------------+ //| Log debug message (only if enabled) | //+------------------------------------------------------------------+ void LogDebug(string message) { if(m_enable_debug) { Print("[DEBUG] ", message); } } //+------------------------------------------------------------------+ //| Clear error records | //+------------------------------------------------------------------+ void ClearErrorRecords() { ArrayResize(m_error_records, 0); } private: //+------------------------------------------------------------------+ //| Check if error has been logged before | //+------------------------------------------------------------------+ bool IsErrorDuplicate(int error_code, string error_message) { for(int i = 0; i < ArraySize(m_error_records); i++) { if(m_error_records[i].error_code == error_code && m_error_records[i].error_message == error_message) { return true; } } return false; } //+------------------------------------------------------------------+ //| Add error to tracking | //+------------------------------------------------------------------+ void AddErrorRecord(int error_code, string error_message) { int size = ArraySize(m_error_records); // Limit number of tracked errors if(size >= m_max_error_records) { // Remove oldest error by shifting elements for(int i = 0; i < size - 1; i++) { m_error_records[i] = m_error_records[i + 1]; } size = m_max_error_records - 1; } ArrayResize(m_error_records, size + 1); m_error_records[size].error_code = error_code; m_error_records[size].error_message = error_message; m_error_records[size].count = 1; m_error_records[size].first_seen = TimeCurrent(); m_error_records[size].last_seen = TimeCurrent(); } //+------------------------------------------------------------------+ //| Format error message for display | //+------------------------------------------------------------------+ string FormatErrorMessage(int error_code, string context, int count) { string error_msg = ErrorDescription(error_code); string result = error_msg + " (Code: " + IntegerToString(error_code) + ")"; if(count > 1) { result += " [REPEATED " + IntegerToString(count) + "x]"; } result += "\nContext: " + context; return result; } //+------------------------------------------------------------------+ //| Generate session ID | //+------------------------------------------------------------------+ string GenerateSessionID() { datetime now = TimeCurrent(); return TimeToString(now, TIME_DATE|TIME_MINUTES|TIME_SECONDS); } }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CLASS: CSignalDetector | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CSignalDetector - Reads indicator buffers and detects signals | //+------------------------------------------------------------------+ class CSignalDetector { private: string m_indicator_name; int m_indicator_handle; // Signal buffers int m_buy_signal_buffer; int m_sell_signal_buffer; // SL/TP buffers (separate for BUY/SELL) int m_buy_sl_buffer; int m_buy_tp_buffers[]; int m_sell_sl_buffer; int m_sell_tp_buffers[]; // ATR settings int m_sltp_mode; int m_atr_period; int m_atr_handle; double m_sl_atr_multiplier; double m_tp_atr_multiplier; int m_min_tp_pips; // Symbol info string m_symbol; double m_pip_value; int m_digits; // Logging bool m_enable_debug; public: //+------------------------------------------------------------------+ //| Signal data structure with multiple TPs | //+------------------------------------------------------------------+ struct SignalData { bool has_signal; bool is_buy; double signal_price; double sl_price; double tp_prices[]; int tp_count; bool used_atr_fallback; }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSignalDetector() { m_indicator_name = ""; m_indicator_handle = INVALID_HANDLE; m_buy_signal_buffer = 0; m_sell_signal_buffer = 1; m_buy_sl_buffer = 2; m_sell_sl_buffer = 6; m_sltp_mode = 0; m_atr_period = 14; m_atr_handle = INVALID_HANDLE; m_sl_atr_multiplier = 1.5; m_tp_atr_multiplier = 3.0; m_min_tp_pips = 300; m_symbol = ""; m_pip_value = 0; m_digits = 0; m_enable_debug = false; ArrayResize(m_buy_tp_buffers, 0); ArrayResize(m_sell_tp_buffers, 0); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CSignalDetector() { Deinitialize(); } //+------------------------------------------------------------------+ //| Initialize indicator handles | //+------------------------------------------------------------------+ bool Initialize() { Print("[SignalDetector] Initializing..."); // Initialize custom indicator handle if(m_indicator_name != "" && m_indicator_name != "My_Indicator") { m_indicator_handle = iCustom(m_symbol, PERIOD_CURRENT, m_indicator_name); if(m_indicator_handle == INVALID_HANDLE) { int error = GetLastError(); Print("[ERROR] Failed to create indicator handle for: ", m_indicator_name, " Error code: ", error); return false; } Print("[SignalDetector] Custom indicator handle created: ", m_indicator_name); } else { Print("[SignalDetector] Using ATR mode only (no custom indicator)"); } // Initialize ATR handle m_atr_handle = iATR(m_symbol, PERIOD_CURRENT, m_atr_period); if(m_atr_handle == INVALID_HANDLE) { int error = GetLastError(); Print("[ERROR] Failed to create ATR handle. Error code: ", error); return false; } Print("[SignalDetector] ATR handle created (Period: ", m_atr_period, ")"); Print("[SignalDetector] Initialization complete"); return true; } //+------------------------------------------------------------------+ //| Deinitialize indicator handles | //+------------------------------------------------------------------+ void Deinitialize() { Print("[SignalDetector] Deinitializing..."); if(m_indicator_handle != INVALID_HANDLE) { IndicatorRelease(m_indicator_handle); m_indicator_handle = INVALID_HANDLE; Print("[SignalDetector] Custom indicator handle released"); } if(m_atr_handle != INVALID_HANDLE) { IndicatorRelease(m_atr_handle); m_atr_handle = INVALID_HANDLE; Print("[SignalDetector] ATR handle released"); } ArrayResize(m_buy_tp_buffers, 0); ArrayResize(m_sell_tp_buffers, 0); Print("[SignalDetector] Deinitialization complete"); } //+------------------------------------------------------------------+ //| Set parameters | //+------------------------------------------------------------------+ void SetParameters( string indicator_name, int buy_signal_buffer, int sell_signal_buffer, int buy_sl_buffer, int sell_sl_buffer, int sltp_mode, int atr_period, double sl_atr_multiplier, double tp_atr_multiplier, int min_tp_pips, double pip_value, int digits, string symbol, bool enable_debug = false ) { m_indicator_name = indicator_name; m_buy_signal_buffer = buy_signal_buffer; m_sell_signal_buffer = sell_signal_buffer; m_buy_sl_buffer = buy_sl_buffer; m_sell_sl_buffer = sell_sl_buffer; m_sltp_mode = sltp_mode; m_atr_period = atr_period; m_sl_atr_multiplier = sl_atr_multiplier; m_tp_atr_multiplier = tp_atr_multiplier; m_min_tp_pips = min_tp_pips; m_pip_value = pip_value; m_digits = digits; m_symbol = symbol; m_enable_debug = enable_debug; } //+------------------------------------------------------------------+ //| Set debug mode | //+------------------------------------------------------------------+ void SetDebugMode(bool enable_debug) { m_enable_debug = enable_debug; } //+------------------------------------------------------------------+ //| Set TP buffers (can have multiple) | //+------------------------------------------------------------------+ void SetBuyTPBuffers(int &buffers[]) { ArrayCopy(m_buy_tp_buffers, buffers); } //+------------------------------------------------------------------+ //| Set TP buffers (can have multiple) | //+------------------------------------------------------------------+ void SetSellTPBuffers(int &buffers[]) { ArrayCopy(m_sell_tp_buffers, buffers); } //+------------------------------------------------------------------+ //| Detect signal from indicator buffers | //+------------------------------------------------------------------+ SignalData DetectSignal(int bar_shift = 1) { SignalData result; result.has_signal = false; result.is_buy = false; result.signal_price = 0; result.sl_price = 0; result.tp_count = 0; result.used_atr_fallback = false; ArrayResize(result.tp_prices, 0); if(m_enable_debug) { Print("[SignalDetector] Detecting signal at bar shift: ", bar_shift); } // Read buy/sell signal buffers double buy_signal = GetIndicatorBufferValue(m_buy_signal_buffer, bar_shift); double sell_signal = GetIndicatorBufferValue(m_sell_signal_buffer, bar_shift); // Determine signal type bool has_buy = (buy_signal != EMPTY_VALUE && buy_signal != 0); bool has_sell = (sell_signal != EMPTY_VALUE && sell_signal != 0); if(!has_buy && !has_sell) { if(m_enable_debug) { Print("[SignalDetector] No signal detected"); } return result; } // Validate: Both buy and sell signals should not be present if(has_buy && has_sell) { Print("[WARNING] Both BUY and SELL signals detected. Using BUY signal."); has_sell = false; } result.has_signal = true; result.is_buy = has_buy; result.signal_price = has_buy ? buy_signal : sell_signal; if(m_enable_debug) { Print("[SignalDetector] ", (has_buy ? "BUY" : "SELL"), " signal detected at: ", DoubleToString(result.signal_price, m_digits)); } // Calculate SL/TP based on mode if(m_sltp_mode == 1) { if(m_enable_debug) { Print("[SignalDetector] Using Mode 1: Indicator SL/TP buffers"); } // Try indicator buffers first double sl_buffer = has_buy ? GetIndicatorBufferValue(m_buy_sl_buffer, bar_shift) : GetIndicatorBufferValue(m_sell_sl_buffer, bar_shift); if(IsValidPrice(sl_buffer)) { result.sl_price = sl_buffer; if(m_enable_debug) { Print("[SignalDetector] SL from indicator: ", DoubleToString(result.sl_price, m_digits)); } } else { if(m_enable_debug) { Print("[SignalDetector] SL buffer empty, will use ATR fallback"); } } // Read TP buffers int tp_count; if(has_buy) tp_count = ArraySize(m_buy_tp_buffers); else tp_count = ArraySize(m_sell_tp_buffers); if(tp_count > 0) { ArrayResize(result.tp_prices, tp_count); result.tp_count = 0; for(int i = 0; i < tp_count; i++) { int buffer_index; if(has_buy) buffer_index = m_buy_tp_buffers[i]; else buffer_index = m_sell_tp_buffers[i]; double tp_buffer = GetIndicatorBufferValue(buffer_index, bar_shift); if(IsValidPrice(tp_buffer)) { result.tp_prices[result.tp_count] = tp_buffer; result.tp_count++; if(m_enable_debug) { Print("[SignalDetector] TP", (i + 1), " from indicator:", DoubleToString(tp_buffer, m_digits)); } } } } } else { if(m_enable_debug) { Print("[SignalDetector] Using Mode 0: ATR-based SL/TP"); } } // Fall back to ATR if SL/TP not set if(result.sl_price == 0 || result.tp_count == 0) { if(m_enable_debug) { Print("[SignalDetector] SL or TP not set from indicator, using ATR fallback"); } double atr = GetATRValue(bar_shift); if(atr > 0) { double open_price = SymbolInfoDouble(m_symbol, SYMBOL_BID); result.sl_price = CalculateATRSL(has_buy, atr, open_price); CalculateATRTPs(has_buy, atr, open_price, result.tp_prices, result.tp_count); result.used_atr_fallback = true; if(m_enable_debug) { Print("[SignalDetector] ATR value: ", DoubleToString(atr, m_digits)); Print("[SignalDetector] SL from ATR: ", DoubleToString(result.sl_price, m_digits)); for(int i = 0; i < result.tp_count; i++) { Print("[SignalDetector] TP", (i + 1), " from ATR: ", DoubleToString(result.tp_prices[i], m_digits)); } } } else { Print("[ERROR] ATR value is 0, cannot calculate SL/TP"); } } // Apply minimum TP ApplyMinimumTP(has_buy, result.tp_prices, result.tp_count, result.signal_price); if(m_enable_debug) { Print("[SignalDetector] Signal detection complete. SL: ", DoubleToString(result.sl_price, m_digits), ", TPs: ", result.tp_count, ", ATR fallback: ", (result.used_atr_fallback ? "Yes" : "No")); } return result; } //+------------------------------------------------------------------+ //| Get ATR value | //+------------------------------------------------------------------+ double GetATRValue(int bar_shift = 1) { if(m_atr_handle == INVALID_HANDLE) { Print("[ERROR] ATR handle is invalid"); return 0; } double atr[]; ArraySetAsSeries(atr, true); int copied = CopyBuffer(m_atr_handle, 0, bar_shift, 1, atr); if(copied <= 0) { int error = GetLastError(); Print("[ERROR] Failed to copy ATR buffer. Error code: ", error); return 0; } if(atr[0] <= 0) { if(m_enable_debug) { Print("[SignalDetector] ATR value is 0 or negative: ", atr[0]); } return 0; } return atr[0]; } private: //+------------------------------------------------------------------+ //| Get indicator buffer value | //+------------------------------------------------------------------+ double GetIndicatorBufferValue(int buffer_index, int bar_shift) { if(m_indicator_handle == INVALID_HANDLE) { if(m_enable_debug) { Print("[SignalDetector] Indicator handle is invalid"); } return EMPTY_VALUE; } if(buffer_index < 0) { Print("[ERROR] Invalid buffer index: ", buffer_index); return EMPTY_VALUE; } double buffer[]; ArraySetAsSeries(buffer, true); int copied = CopyBuffer(m_indicator_handle, buffer_index, bar_shift, 1, buffer); if(copied <= 0) { if(m_enable_debug) { int error = GetLastError(); Print("[SignalDetector] Failed to copy buffer ", buffer_index, ". Error code: ", error); } return EMPTY_VALUE; } return buffer[0]; } //+------------------------------------------------------------------+ //| Calculate ATR-based SL | //+------------------------------------------------------------------+ double CalculateATRSL(bool is_buy, double atr_value, double open_price) { if(atr_value <= 0) return 0; if(is_buy) { return NormalizeDouble(open_price - atr_value * m_sl_atr_multiplier, m_digits); } else { return NormalizeDouble(open_price + atr_value * m_sl_atr_multiplier, m_digits); } } //+------------------------------------------------------------------+ //| Calculate ATR-based TPs | //+------------------------------------------------------------------+ void CalculateATRTPs(bool is_buy, double atr_value, double open_price, double &tp_prices[], int &tp_count) { if(atr_value <= 0) return; // Default to 3 TPs if using ATR int num_tps = 3; ArrayResize(tp_prices, num_tps); tp_count = num_tps; for(int i = 0; i < num_tps; i++) { double multiplier = m_tp_atr_multiplier * (i + 1); if(is_buy) { tp_prices[i] = NormalizeDouble(open_price + atr_value * multiplier, m_digits); } else { tp_prices[i] = NormalizeDouble(open_price - atr_value * multiplier, m_digits); } } } //+------------------------------------------------------------------+ //| Apply minimum TP to all TPs | //+------------------------------------------------------------------+ void ApplyMinimumTP(bool is_buy, double &tp_prices[], int tp_count, double open_price) { if(m_min_tp_pips <= 0) { if(m_enable_debug) { Print("[SignalDetector] Minimum TP disabled (0 pips)"); } return; } if(tp_count == 0) { if(m_enable_debug) { Print("[SignalDetector] No TPs to apply minimum TP"); } return; } int adjusted_count = 0; for(int i = 0; i < tp_count; i++) { if(is_buy) { double min_tp = NormalizeDouble(open_price + m_min_tp_pips * m_pip_value, m_digits); if(tp_prices[i] < min_tp) { if(m_enable_debug) { Print("[SignalDetector] TP", (i + 1), " adjusted to minimum: ", DoubleToString(tp_prices[i], m_digits), " -> ", DoubleToString(min_tp, m_digits)); } tp_prices[i] = min_tp; adjusted_count++; } } else { double min_tp = NormalizeDouble(open_price - m_min_tp_pips * m_pip_value, m_digits); if(tp_prices[i] > min_tp) { if(m_enable_debug) { Print("[SignalDetector] TP", (i + 1), " adjusted to minimum: ", DoubleToString(tp_prices[i], m_digits), " -> ", DoubleToString(min_tp, m_digits)); } tp_prices[i] = min_tp; adjusted_count++; } } } if(m_enable_debug && adjusted_count > 0) { Print("[SignalDetector] Minimum TP applied to ", adjusted_count, " TP(s)"); } } //+------------------------------------------------------------------+ //| Check if price is valid | //+------------------------------------------------------------------+ bool IsValidPrice(double price) { return (price != EMPTY_VALUE && price != 0 && price > 0); } }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CLASS: CTimeFilter | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CTimeFilter - Validates day of week and trading session filters | //+------------------------------------------------------------------+ class CTimeFilter { private: bool m_enabled; bool m_days_enabled[7]; // 0=Sunday, 1=Monday, ..., 6=Saturday bool m_asian_enabled; bool m_europe_enabled; bool m_america_enabled; // Logging bool m_enable_debug; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTimeFilter() { m_enabled = false; m_enable_debug = false; // Default: Mon-Fri enabled for(int i = 0; i < 7; i++) { m_days_enabled[i] = (i >= 1 && i <= 5); } m_asian_enabled = true; m_europe_enabled = true; m_america_enabled = true; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CTimeFilter() { } //+------------------------------------------------------------------+ //| Set parameters | //+------------------------------------------------------------------+ void SetParameters( bool enabled, bool mon, bool tue, bool wed, bool thu, bool fri, bool sat, bool sun, bool asian, bool europe, bool america, bool enable_debug = false ) { m_enabled = enabled; m_days_enabled[1] = mon; // Monday m_days_enabled[2] = tue; // Tuesday m_days_enabled[3] = wed; // Wednesday m_days_enabled[4] = thu; // Thursday m_days_enabled[5] = fri; // Friday m_days_enabled[6] = sat; // Saturday m_days_enabled[0] = sun; // Sunday m_asian_enabled = asian; m_europe_enabled = europe; m_america_enabled = america; m_enable_debug = enable_debug; if(m_enable_debug) { Print("[TimeFilter] Parameters set - Enabled: ", m_enabled); Print("[TimeFilter] Days: Mon=", mon, " Tue=", tue, " Wed=", wed, " Thu=", thu, " Fri=", fri, " Sat=", sat, " Sun=", sun); Print("[TimeFilter] Sessions: Asian=", asian, " Europe=", europe, " America=", america); } } //+------------------------------------------------------------------+ //| Set debug mode | //+------------------------------------------------------------------+ void SetDebugMode(bool enable_debug) { m_enable_debug = enable_debug; } //+------------------------------------------------------------------+ //| Check if current time is allowed for trading | //+------------------------------------------------------------------+ bool IsTimeAllowed() { if(!m_enabled) { if(m_enable_debug) { Print("[TimeFilter] Time filtering disabled - Trading allowed"); } return true; } bool day_allowed = IsDayOfWeekAllowed(); bool session_allowed = IsSessionTimeAllowed(); bool result = (day_allowed && session_allowed); if(m_enable_debug) { Print("[TimeFilter] Time check - Day allowed: ", day_allowed, ", Session allowed: ", session_allowed, ", Result: ", (result ? "ALLOWED" : "BLOCKED")); } return result; } //+------------------------------------------------------------------+ //| Get current day of week name | //+------------------------------------------------------------------+ string GetCurrentDayOfWeek() { MqlDateTime dt; TimeToStruct(TimeGMT(), dt); string day_names[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; return day_names[dt.day_of_week]; } //+------------------------------------------------------------------+ //| Get current session name | //+------------------------------------------------------------------+ string GetCurrentSession() { MqlDateTime dt; TimeToStruct(TimeGMT(), dt); int hour = dt.hour; if(IsAsianSession(hour)) return "Asian"; if(IsEuropeSession(hour)) return "Europe"; if(IsAmericaSession(hour)) return "America"; return "Off-hours"; } //+------------------------------------------------------------------+ //| Get current GMT time as string | //+------------------------------------------------------------------+ string GetCurrentGMTTime() { MqlDateTime dt; TimeToStruct(TimeGMT(), dt); return StringFormat("%02d:%02d:%02d GMT", dt.hour, dt.min, dt.sec); } private: //+------------------------------------------------------------------+ //| Check if current day of week is allowed | //+------------------------------------------------------------------+ bool IsDayOfWeekAllowed() { MqlDateTime dt; TimeToStruct(TimeGMT(), dt); string day_names[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; bool allowed = m_days_enabled[dt.day_of_week]; if(m_enable_debug) { Print("[TimeFilter] Day of week: ", day_names[dt.day_of_week], " (", dt.day_of_week, ") - ", (allowed ? "ALLOWED" : "BLOCKED")); } return allowed; } //+------------------------------------------------------------------+ //| Check if current session is allowed | //+------------------------------------------------------------------+ bool IsSessionTimeAllowed() { MqlDateTime dt; TimeToStruct(TimeGMT(), dt); int hour = dt.hour; bool in_asian = IsAsianSession(hour); bool in_europe = IsEuropeSession(hour); bool in_america = IsAmericaSession(hour); bool allowed = false; string active_session = "None"; if(in_asian && m_asian_enabled) { allowed = true; active_session = "Asian"; } else if(in_europe && m_europe_enabled) { allowed = true; active_session = "Europe"; } else if(in_america && m_america_enabled) { allowed = true; active_session = "America"; } if(m_enable_debug) { Print("[TimeFilter] Current time: ", dt.hour, ":00 GMT"); Print("[TimeFilter] In Asian session (00-08): ", in_asian, " - Enabled: ", m_asian_enabled); Print("[TimeFilter] In Europe session (07-16): ", in_europe, " - Enabled: ", m_europe_enabled); Print("[TimeFilter] In America session (13-22): ", in_america, " - Enabled: ", m_america_enabled); Print("[TimeFilter] Active session: ", active_session, " - ", (allowed ? "ALLOWED" : "BLOCKED")); } return allowed; } //+------------------------------------------------------------------+ //| Check if Asian session (00:00 - 08:00 GMT) | //+------------------------------------------------------------------+ bool IsAsianSession(int hour) { return (hour >= 0 && hour < 8); } //+------------------------------------------------------------------+ //| Check if Europe session (07:00 - 16:00 GMT) | //+------------------------------------------------------------------+ bool IsEuropeSession(int hour) { return (hour >= 7 && hour < 16); } //+------------------------------------------------------------------+ //| Check if America session (13:00 - 22:00 GMT) | //+------------------------------------------------------------------+ bool IsAmericaSession(int hour) { return (hour >= 13 && hour < 22); } }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CLASS: CMoneyManager | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CMoneyManager - Calculates lot sizes and manages daily profit | //+------------------------------------------------------------------+ class CMoneyManager { private: double m_base_lot_size; bool m_use_percent_balance; double m_percent_of_balance_for_profit; double m_daily_profit_target_percent; double m_min_lot; double m_max_lot; double m_lot_step; double m_point_value; double m_tick_value; // State double m_daily_profit_accumulated; double m_daily_start_balance; // Logging bool m_enable_debug; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CMoneyManager() { m_base_lot_size = 0.03; m_use_percent_balance = true; m_percent_of_balance_for_profit = 1.0; m_daily_profit_target_percent = 2.0; m_min_lot = 0.01; m_max_lot = 100.0; m_lot_step = 0.01; m_point_value = 0; m_tick_value = 0; m_daily_profit_accumulated = 0; m_daily_start_balance = 0; m_enable_debug = false; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CMoneyManager() { } //+------------------------------------------------------------------+ //| Set parameters | //+------------------------------------------------------------------+ void SetParameters( double base_lot_size, bool use_percent_balance, double percent_of_balance_for_profit, double daily_profit_target_percent, double min_lot, double max_lot, double lot_step, double point_value, double tick_value, bool enable_debug = false ) { // Validate parameters if(base_lot_size <= 0) { Print("[ERROR] Invalid base lot size: ", base_lot_size, ". Using default 0.01"); m_base_lot_size = 0.01; } else { m_base_lot_size = base_lot_size; } if(percent_of_balance_for_profit <= 0) { Print("[ERROR] Invalid percent of balance for profit: ", percent_of_balance_for_profit, ". Using default 1.0"); m_percent_of_balance_for_profit = 1.0; } else { m_percent_of_balance_for_profit = percent_of_balance_for_profit; } if(daily_profit_target_percent < 0) { Print("[ERROR] Invalid daily profit target percent: ", daily_profit_target_percent, ". Using default 2.0"); m_daily_profit_target_percent = 2.0; } else { m_daily_profit_target_percent = daily_profit_target_percent; } if(min_lot <= 0) { Print("[ERROR] Invalid min lot: ", min_lot, ". Using default 0.01"); m_min_lot = 0.01; } else { m_min_lot = min_lot; } if(max_lot <= 0 || max_lot < min_lot) { Print("[ERROR] Invalid max lot: ", max_lot, ". Using default 100.0"); m_max_lot = 100.0; } else { m_max_lot = max_lot; } if(lot_step <= 0) { Print("[ERROR] Invalid lot step: ", lot_step, ". Using default 0.01"); m_lot_step = 0.01; } else { m_lot_step = lot_step; } if(point_value <= 0) { Print("[ERROR] Invalid point value: ", point_value); } m_point_value = point_value; if(tick_value <= 0) { Print("[ERROR] Invalid tick value: ", tick_value); } m_tick_value = tick_value; m_use_percent_balance = use_percent_balance; m_enable_debug = enable_debug; if(m_enable_debug) { Print("[MoneyManager] Parameters set:"); Print(" Base lot size: ", m_base_lot_size); Print(" Use % balance: ", m_use_percent_balance); Print(" % of balance for profit: ", m_percent_of_balance_for_profit); Print(" Daily profit target %: ", m_daily_profit_target_percent); Print(" Min lot: ", m_min_lot, ", Max lot: ", m_max_lot, ", Lot step: ", m_lot_step); Print(" Point value: ", m_point_value, ", Tick value: ", m_tick_value); } } //+------------------------------------------------------------------+ //| Set debug mode | //+------------------------------------------------------------------+ void SetDebugMode(bool enable_debug) { m_enable_debug = enable_debug; } //+------------------------------------------------------------------+ //| Calculate lot size (pure function) | //+------------------------------------------------------------------+ double CalculateLotSize( bool is_buy, double open_price, double tp_price, double account_balance ) { if(m_enable_debug) { Print("[MoneyManager] Calculating lot size..."); Print(" Direction: ", (is_buy ? "BUY" : "SELL")); Print(" Open price: ", open_price); Print(" TP price: ", tp_price); Print(" Account balance: ", account_balance); } // Validate inputs if(open_price <= 0) { Print("[ERROR] Invalid open price: ", open_price); return m_base_lot_size; } if(tp_price <= 0) { Print("[ERROR] Invalid TP price: ", tp_price); return m_base_lot_size; } if(account_balance <= 0) { Print("[ERROR] Invalid account balance: ", account_balance); return m_base_lot_size; } // Calculate TP points double tp_points = 0; if(is_buy) { tp_points = (tp_price - open_price) / m_point_value; } else { tp_points = (open_price - tp_price) / m_point_value; } if(m_enable_debug) { Print(" TP points: ", tp_points); } if(tp_points <= 0) { Print("[WARNING] TP points <= 0. Using base lot size: ", m_base_lot_size); return m_base_lot_size; } // Calculate base lot double base_lot = m_base_lot_size; if(m_use_percent_balance) { base_lot = CalculateBaseLot(tp_points, account_balance); if(m_enable_debug) { Print(" Base lot from % balance: ", base_lot); } } else { if(m_enable_debug) { Print(" Using fixed base lot: ", base_lot); } } // Normalize and return double normalized_lot = NormalizeLotSize(base_lot); if(m_enable_debug) { Print(" Normalized lot: ", normalized_lot); Print("[MoneyManager] Lot size calculation complete: ", normalized_lot); } return normalized_lot; } //+------------------------------------------------------------------+ //| Reset daily profit tracking | //+------------------------------------------------------------------+ void ResetDailyProfit(double current_balance) { if(current_balance <= 0) { Print("[ERROR] Invalid current balance for daily profit reset: ", current_balance); return; } m_daily_start_balance = current_balance; m_daily_profit_accumulated = 0; if(m_enable_debug) { Print("[MoneyManager] Daily profit tracking reset"); Print(" Start balance: ", m_daily_start_balance); Print(" Target profit: ", GetDailyProfitTarget()); } } //+------------------------------------------------------------------+ //| Check if daily profit target reached | //+------------------------------------------------------------------+ bool IsDailyProfitTargetReached() { if(m_daily_profit_target_percent <= 0) { if(m_enable_debug) { Print("[MoneyManager] Daily profit target disabled (0%)"); } return false; } double target_profit = GetDailyProfitTarget(); bool reached = (m_daily_profit_accumulated >= target_profit); if(m_enable_debug) { Print("[MoneyManager] Daily profit check:"); Print(" Accumulated: ", m_daily_profit_accumulated); Print(" Target: ", target_profit); Print(" Reached: ", (reached ? "YES" : "NO")); } return reached; } //+------------------------------------------------------------------+ //| Get daily profit target | //+------------------------------------------------------------------+ double GetDailyProfitTarget() { if(m_daily_start_balance <= 0) return 0; return m_daily_start_balance * m_daily_profit_target_percent / 100.0; } //+------------------------------------------------------------------+ //| Get daily profit accumulated | //+------------------------------------------------------------------+ double GetDailyProfitAccumulated() { return m_daily_profit_accumulated; } //+------------------------------------------------------------------+ //| Get daily profit percentage | //+------------------------------------------------------------------+ double GetDailyProfitPercent() { if(m_daily_start_balance <= 0) return 0; return (m_daily_profit_accumulated / m_daily_start_balance) * 100.0; } //+------------------------------------------------------------------+ //| Set daily profit accumulated (for tracking) | //+------------------------------------------------------------------+ void SetDailyProfitAccumulated(double value) { m_daily_profit_accumulated = value; if(m_enable_debug) { Print("[MoneyManager] Daily profit accumulated set to: ", value); } } private: //+------------------------------------------------------------------+ //| Calculate base lot based on % balance | //+------------------------------------------------------------------+ double CalculateBaseLot(double tp_points, double account_balance) { if(tp_points <= 0) { Print("[ERROR] Invalid TP points for base lot calculation: ", tp_points); return m_base_lot_size; } if(m_tick_value <= 0) { Print("[ERROR] Invalid tick value for base lot calculation: ", m_tick_value); return m_base_lot_size; } if(account_balance <= 0) { Print("[ERROR] Invalid account balance for base lot calculation: ", account_balance); return m_base_lot_size; } double target_profit = account_balance * (m_percent_of_balance_for_profit / 100.0); double profit_per_lot = tp_points * m_tick_value; if(m_enable_debug) { Print("[MoneyManager] Base lot calculation:"); Print(" Target profit: ", target_profit, " (", m_percent_of_balance_for_profit, "% of balance)"); Print(" Profit per lot: ", profit_per_lot); } if(profit_per_lot <= 0) { Print("[ERROR] Invalid profit per lot: ", profit_per_lot); return m_base_lot_size; } double lot = target_profit / profit_per_lot; if(m_enable_debug) { Print(" Calculated lot: ", lot); } return lot; } //+------------------------------------------------------------------+ //| Normalize lot size to broker requirements | //+------------------------------------------------------------------+ double NormalizeLotSize(double lot) { if(m_enable_debug) { Print("[MoneyManager] Normalizing lot size: ", lot); } // Round to lot step double rounded_lot = MathFloor(lot / m_lot_step) * m_lot_step; if(m_enable_debug && rounded_lot != lot) { Print(" Rounded to lot step: ", rounded_lot, " (step: ", m_lot_step, ")"); } // Ensure within min/max double min_adjusted = MathMax(rounded_lot, m_min_lot); double max_adjusted = MathMin(min_adjusted, m_max_lot); if(m_enable_debug) { if(min_adjusted != rounded_lot) { Print(" Adjusted to min lot: ", min_adjusted, " (min: ", m_min_lot, ")"); } if(max_adjusted != min_adjusted) { Print(" Adjusted to max lot: ", max_adjusted, " (max: ", m_max_lot, ")"); } } double normalized = NormalizeDouble(max_adjusted, 2); if(m_enable_debug) { Print(" Final normalized lot: ", normalized); } return normalized; } }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CLASS: CRiskManager | //+------------------------------------------------------------------+ #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; } }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CLASS: CPartialCloseManager | //+------------------------------------------------------------------+ #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); } }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CLASS: CTradeExecutor | //+------------------------------------------------------------------+ #include #include //+------------------------------------------------------------------+ //| CTradeExecutor - Executes trades and manages orders | //+------------------------------------------------------------------+ class CTradeExecutor { private: int m_slippage_points; int m_magic_number; string m_symbol; int m_digits; bool m_take_screenshot; long m_chart_id; string m_indicator_name; CTrade *m_trade; CPositionInfo *m_position_info; // Logging bool m_enable_debug; public: //+------------------------------------------------------------------+ //| Trade result structure | //+------------------------------------------------------------------+ struct TradeResult { bool success; ulong ticket; string error_message; }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTradeExecutor() { m_slippage_points = 30; m_magic_number = 24680; m_symbol = ""; m_digits = 0; m_take_screenshot = true; m_chart_id = 0; m_indicator_name = ""; m_enable_debug = false; m_trade = new CTrade(); m_position_info = new CPositionInfo(); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CTradeExecutor() { if(m_trade != NULL) { delete m_trade; m_trade = NULL; } if(m_position_info != NULL) { delete m_position_info; m_position_info = NULL; } } //+------------------------------------------------------------------+ //| Set parameters | //+------------------------------------------------------------------+ void SetParameters( int slippage_points, int magic_number, string symbol, int digits, bool take_screenshot, long chart_id, string indicator_name, bool enable_debug = false ) { // Validate parameters if(slippage_points < 0) { Print("[ERROR] Invalid slippage points: ", slippage_points, ". Using default 30"); m_slippage_points = 30; } else { m_slippage_points = slippage_points; } if(magic_number <= 0) { Print("[ERROR] Invalid magic number: ", magic_number, ". Using default 24680"); m_magic_number = 24680; } else { m_magic_number = magic_number; } if(symbol == "") { Print("[ERROR] Invalid symbol: empty string"); } m_symbol = symbol; if(digits <= 0) { Print("[ERROR] Invalid digits: ", digits); } m_digits = digits; m_take_screenshot = take_screenshot; m_chart_id = chart_id; m_indicator_name = indicator_name; m_enable_debug = enable_debug; m_trade.SetExpertMagicNumber(m_magic_number); m_trade.SetDeviationInPoints(m_slippage_points); if(m_enable_debug) { Print("[TradeExecutor] Parameters set:"); Print(" Slippage: ", m_slippage_points, " points"); Print(" Magic number: ", m_magic_number); Print(" Symbol: ", m_symbol); Print(" Digits: ", m_digits); Print(" Take screenshot: ", m_take_screenshot); Print(" Chart ID: ", m_chart_id); Print(" Indicator: ", m_indicator_name); } } //+------------------------------------------------------------------+ //| Set debug mode | //+------------------------------------------------------------------+ void SetDebugMode(bool enable_debug) { m_enable_debug = enable_debug; } //+------------------------------------------------------------------+ //| Open new trade with multiple TPs | //+------------------------------------------------------------------+ TradeResult ExecuteTrade( bool is_buy, double lots, double open_price, double sl_price, double &tp_prices[], int tp_count ) { TradeResult result; result.success = false; result.ticket = 0; result.error_message = ""; if(m_enable_debug) { Print("[TradeExecutor] Executing trade..."); Print(" Direction: ", (is_buy ? "BUY" : "SELL")); Print(" Lots: ", lots); Print(" SL: ", sl_price); Print(" TP count: ", tp_count); } // Validate inputs if(lots <= 0) { result.error_message = "Invalid lots: " + DoubleToString(lots, 2); Print("[ERROR] ", result.error_message); return result; } if(m_symbol == "") { result.error_message = "Symbol not set"; Print("[ERROR] ", result.error_message); return result; } // Build order comment with multiple TPs string comment = BuildOrderComment(tp_prices, tp_count); if(m_enable_debug) { Print(" Comment: ", comment); } // Get execution price double execution_price = GetExecutionPrice(is_buy); if(m_enable_debug) { Print(" Execution price: ", execution_price); } // Execute order bool order_result = false; if(is_buy) { order_result = m_trade.Buy(lots, m_symbol, execution_price, sl_price, 0, comment); } else { order_result = m_trade.Sell(lots, m_symbol, execution_price, sl_price, 0, comment); } if(order_result) { result.success = true; result.ticket = m_trade.ResultOrder(); Print("[TradeExecutor] Order opened successfully. Ticket: ", result.ticket); // Take screenshot if(m_take_screenshot) { TakeScreenshot(result.ticket); } } else { int error_code = (int)m_trade.ResultRetcode(); result.error_message = "OrderSend failed: " + m_trade.ResultRetcodeDescription() + " (Code: " + IntegerToString(error_code) + ")"; Print("[ERROR] ", result.error_message); } return result; } //+------------------------------------------------------------------+ //| Partial close | //+------------------------------------------------------------------+ TradeResult ExecutePartialClose( ulong ticket, double close_lots, int tp_index ) { TradeResult result; result.success = false; result.ticket = 0; result.error_message = ""; if(m_enable_debug) { Print("[TradeExecutor] Executing partial close..."); Print(" Ticket: ", ticket); Print(" Close lots: ", close_lots); Print(" TP index: ", tp_index); } // Validate inputs if(ticket == 0) { result.error_message = "Invalid ticket: 0"; Print("[ERROR] ", result.error_message); return result; } if(close_lots <= 0) { result.error_message = "Invalid close lots: " + DoubleToString(close_lots, 2); Print("[ERROR] ", result.error_message); return result; } if(!PositionSelectByTicket(ticket)) { result.error_message = "Failed to select position #" + IntegerToString(ticket); Print("[ERROR] ", result.error_message); return result; } double current_lots = PositionGetDouble(POSITION_VOLUME); 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 close_result = false; if(pos_type == POSITION_TYPE_BUY) { close_result = m_trade.Sell(close_lots, m_symbol, 0, 0, 0, "Partial Close TP" + IntegerToString(tp_index)); } else { close_result = m_trade.Buy(close_lots, m_symbol, 0, 0, 0, "Partial Close TP" + IntegerToString(tp_index)); } if(close_result) { result.success = true; result.ticket = m_trade.ResultOrder(); Print("[TradeExecutor] Partial close executed: ", close_lots, " lots at TP", tp_index, " for ticket #", ticket); } else { int error_code = (int)m_trade.ResultRetcode(); result.error_message = "Partial close failed: " + m_trade.ResultRetcodeDescription() + " (Code: " + IntegerToString(error_code) + ")"; Print("[ERROR] ", result.error_message); } return result; } //+------------------------------------------------------------------+ //| Check if trade is open | //+------------------------------------------------------------------+ bool IsTradeOpen() { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(m_position_info.SelectByIndex(i)) { if(m_position_info.Symbol() == m_symbol && m_position_info.Magic() == m_magic_number) { return true; } } } return false; } //+------------------------------------------------------------------+ //| Close opposite trade | //+------------------------------------------------------------------+ bool CloseOppositeTrade(bool is_buy) { if(m_enable_debug) { Print("[TradeExecutor] Closing opposite trades for ", (is_buy ? "BUY" : "SELL"), " signal"); } bool closed_any = false; ENUM_POSITION_TYPE opposite_type = is_buy ? POSITION_TYPE_SELL : POSITION_TYPE_BUY; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(m_position_info.SelectByIndex(i)) { if(m_position_info.Symbol() == m_symbol && m_position_info.Magic() == m_magic_number && m_position_info.PositionType() == opposite_type) { ulong ticket = m_position_info.Ticket(); double lots = m_position_info.Volume(); if(m_enable_debug) { Print("[TradeExecutor] Found opposite trade #", ticket, " (", lots, " lots)"); } if(m_trade.PositionClose(ticket)) { Print("[TradeExecutor] Closed opposite trade Ticket#", ticket, " due to new signal."); closed_any = true; } else { int error_code = (int)m_trade.ResultRetcode(); Print("[ERROR] Failed to close opposite trade Ticket#", ticket, ". Error: ", m_trade.ResultRetcodeDescription(), " (", error_code, ")"); } } } } if(m_enable_debug && !closed_any) { Print("[TradeExecutor] No opposite trades found to close"); } return closed_any; } private: //+------------------------------------------------------------------+ //| Build order comment with multiple TPs | //+------------------------------------------------------------------+ string BuildOrderComment(double &tp_prices[], int tp_count) { string comment = "UnivBufEA_" + IntegerToString(m_magic_number); // Add TPs to comment for(int i = 0; i < tp_count; i++) { comment += ";TP" + IntegerToString(i + 1) + "=" + DoubleToString(tp_prices[i], m_digits); } // Add breakeven and trailing flags comment += ";BE=0;TS=0"; // Truncate to max length (31 chars for some brokers) if(StringLen(comment) > 31) { comment = StringSubstr(comment, 0, 31); } return comment; } //+------------------------------------------------------------------+ //| Take screenshot | //+------------------------------------------------------------------+ bool TakeScreenshot(ulong ticket) { if(!m_take_screenshot) { if(m_enable_debug) { Print("[TradeExecutor] Screenshot disabled"); } return false; } if(m_chart_id == 0) { Print("[ERROR] Chart ID is 0, cannot take screenshot"); return false; } string time_str = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS); StringReplace(time_str, ":", "_"); StringReplace(time_str, ".", "_"); string filename = m_symbol + "_T" + IntegerToString(ticket) + "_" + time_str + ".gif"; int width = (int)ChartGetInteger(m_chart_id, CHART_WIDTH_IN_PIXELS); int height = (int)ChartGetInteger(m_chart_id, CHART_HEIGHT_IN_PIXELS); if(width <= 0 || height <= 0) { Print("[ERROR] Invalid chart dimensions: ", width, "x", height); return false; } if(m_enable_debug) { Print("[TradeExecutor] Taking screenshot: ", filename, " (", width, "x", height, ")"); } if(ChartScreenShot(m_chart_id, filename, width, height, ALIGN_RIGHT)) { Print("[TradeExecutor] Screenshot saved: ", filename); return true; } int error = GetLastError(); Print("[ERROR] Failed to save screenshot. Error code: ", error); return false; } //+------------------------------------------------------------------+ //| Get execution price | //+------------------------------------------------------------------+ double GetExecutionPrice(bool is_buy) { if(is_buy) { return SymbolInfoDouble(m_symbol, SYMBOL_ASK); } else { return SymbolInfoDouble(m_symbol, SYMBOL_BID); } } }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CLASS: CStateManager | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CStateManager - Manages persistent state using Global Variables | //+------------------------------------------------------------------+ class CStateManager { private: string m_symbol; int m_magic_number; string m_prefix; string m_gv_accum_loss; string m_gv_consec_loss; // Logging bool m_enable_debug; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CStateManager() { m_symbol = ""; m_magic_number = 0; m_prefix = "UnivBufEA"; m_enable_debug = false; m_gv_accum_loss = ""; m_gv_consec_loss = ""; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CStateManager() { } //+------------------------------------------------------------------+ //| Set parameters | //+------------------------------------------------------------------+ void SetParameters(string symbol, int magic_number, bool enable_debug = false) { // Validate parameters if(symbol == "") { Print("[ERROR] Invalid symbol: empty string"); } m_symbol = symbol; if(magic_number <= 0) { Print("[ERROR] Invalid magic number: ", magic_number, ". Using default 24680"); m_magic_number = 24680; } else { m_magic_number = magic_number; } m_enable_debug = enable_debug; // Build Global Variable names m_gv_accum_loss = BuildGVName("_AccumLoss"); m_gv_consec_loss = BuildGVName("_ConsecLoss"); if(m_enable_debug) { Print("[StateManager] Parameters set:"); Print(" Symbol: ", m_symbol); Print(" Magic number: ", m_magic_number); Print(" GV Accum Loss: ", m_gv_accum_loss); Print(" GV Consec Loss: ", m_gv_consec_loss); } } //+------------------------------------------------------------------+ //| Set debug mode | //+------------------------------------------------------------------+ void SetDebugMode(bool enable_debug) { m_enable_debug = enable_debug; } //+------------------------------------------------------------------+ //| Load state from Global Variables | //+------------------------------------------------------------------+ bool LoadState(double &accumulated_loss, int &consecutive_losses) { if(m_enable_debug) { Print("[StateManager] Loading state..."); } accumulated_loss = 0; consecutive_losses = 0; // Load accumulated loss if(GlobalVariableCheck(m_gv_accum_loss)) { accumulated_loss = GlobalVariableGet(m_gv_accum_loss); // Validate accumulated loss if(accumulated_loss < 0) { Print("[WARNING] Invalid accumulated loss found: ", accumulated_loss, ". Resetting to 0"); accumulated_loss = 0; GlobalVariableSet(m_gv_accum_loss, 0); } Print("[StateManager] Loaded accumulated loss: ", DoubleToString(accumulated_loss, 2)); } else { if(!GlobalVariableSet(m_gv_accum_loss, 0)) { Print("[ERROR] Failed to initialize accumulated loss Global Variable"); return false; } Print("[StateManager] Initialized accumulated loss: 0"); } // Load consecutive losses if(GlobalVariableCheck(m_gv_consec_loss)) { consecutive_losses = (int)GlobalVariableGet(m_gv_consec_loss); // Validate consecutive losses if(consecutive_losses < 0) { Print("[WARNING] Invalid consecutive losses found: ", consecutive_losses, ". Resetting to 0"); consecutive_losses = 0; GlobalVariableSet(m_gv_consec_loss, 0); } Print("[StateManager] Loaded consecutive losses: ", consecutive_losses); } else { if(!GlobalVariableSet(m_gv_consec_loss, 0)) { Print("[ERROR] Failed to initialize consecutive losses Global Variable"); return false; } Print("[StateManager] Initialized consecutive losses: 0"); } if(m_enable_debug) { Print("[StateManager] State loaded successfully"); } return true; } //+------------------------------------------------------------------+ //| Save state to Global Variables | //+------------------------------------------------------------------+ bool SaveState(double accumulated_loss, int consecutive_losses) { if(m_enable_debug) { Print("[StateManager] Saving state..."); Print(" Accumulated loss: ", DoubleToString(accumulated_loss, 2)); Print(" Consecutive losses: ", consecutive_losses); } // Validate inputs if(accumulated_loss < 0) { Print("[ERROR] Invalid accumulated loss to save: ", accumulated_loss); return false; } if(consecutive_losses < 0) { Print("[ERROR] Invalid consecutive losses to save: ", consecutive_losses); return false; } bool result = true; // Save accumulated loss if(!GlobalVariableSet(m_gv_accum_loss, accumulated_loss)) { int error = GetLastError(); Print("[ERROR] Failed to save accumulated loss. Error code: ", error); result = false; } // Save consecutive losses if(!GlobalVariableSet(m_gv_consec_loss, consecutive_losses)) { int error = GetLastError(); Print("[ERROR] Failed to save consecutive losses. Error code: ", error); result = false; } if(result && m_enable_debug) { Print("[StateManager] State saved successfully"); } return result; } //+------------------------------------------------------------------+ //| Clear state (for backtesting) | //+------------------------------------------------------------------+ bool ClearState() { if(m_enable_debug) { Print("[StateManager] Clearing state..."); } bool result = true; // Delete accumulated loss if(GlobalVariableCheck(m_gv_accum_loss)) { if(!GlobalVariableDel(m_gv_accum_loss)) { int error = GetLastError(); Print("[ERROR] Failed to delete accumulated loss. Error code: ", error); result = false; } else { Print("[StateManager] Deleted accumulated loss Global Variable"); } } else { Print("[StateManager] Accumulated loss Global Variable not found (already cleared)"); } // Delete consecutive losses if(GlobalVariableCheck(m_gv_consec_loss)) { if(!GlobalVariableDel(m_gv_consec_loss)) { int error = GetLastError(); Print("[ERROR] Failed to delete consecutive losses. Error code: ", error); result = false; } else { Print("[StateManager] Deleted consecutive losses Global Variable"); } } else { Print("[StateManager] Consecutive losses Global Variable not found (already cleared)"); } if(result) { Print("[StateManager] State cleared successfully"); } return result; } //+------------------------------------------------------------------+ //| Get Global Variable names | //+------------------------------------------------------------------+ void GetGVNames(string &accum_loss_name, string &consec_loss_name) { accum_loss_name = m_gv_accum_loss; consec_loss_name = m_gv_consec_loss; } //+------------------------------------------------------------------+ //| Check if state exists | //+------------------------------------------------------------------+ bool StateExists() { bool accum_exists = GlobalVariableCheck(m_gv_accum_loss); bool consec_exists = GlobalVariableCheck(m_gv_consec_loss); return (accum_exists || consec_exists); } private: //+------------------------------------------------------------------+ //| Build Global Variable name | //+------------------------------------------------------------------+ string BuildGVName(string suffix) { return m_prefix + "_" + m_symbol + "_" + IntegerToString(m_magic_number) + suffix; } }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CLASS: CUIManager | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| CUIManager - Manages chart labels and manual mode UI | //+------------------------------------------------------------------+ class CUIManager { private: long m_chart_id; bool m_manual_mode; int m_magic_number; string m_symbol; double m_accumulated_loss; double m_loss_percent; double m_high_loss_threshold; // Logging bool m_enable_debug; public: //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CUIManager() { m_chart_id = 0; m_manual_mode = false; m_magic_number = 0; m_symbol = ""; m_accumulated_loss = 0; m_loss_percent = 0; m_high_loss_threshold = 0; m_enable_debug = false; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ ~CUIManager() { DeleteUI(); } //+------------------------------------------------------------------+ //| Set parameters | //+------------------------------------------------------------------+ void SetParameters( long chart_id, bool manual_mode, int magic_number, string symbol, double high_loss_threshold, bool enable_debug = false ) { // Validate parameters if(chart_id == 0) { Print("[ERROR] Invalid chart ID: 0"); } m_chart_id = chart_id; if(magic_number <= 0) { Print("[ERROR] Invalid magic number: ", magic_number, ". Using default 24680"); m_magic_number = 24680; } else { m_magic_number = magic_number; } if(symbol == "") { Print("[ERROR] Invalid symbol: empty string"); } m_symbol = symbol; if(high_loss_threshold < 0) { Print("[ERROR] Invalid high loss threshold: ", high_loss_threshold, ". Using 0"); m_high_loss_threshold = 0; } else { m_high_loss_threshold = high_loss_threshold; } m_manual_mode = manual_mode; m_enable_debug = enable_debug; if(m_enable_debug) { Print("[UIManager] Parameters set:"); Print(" Chart ID: ", m_chart_id); Print(" Manual mode: ", m_manual_mode); Print(" Magic number: ", m_magic_number); Print(" Symbol: ", m_symbol); Print(" High loss threshold: ", m_high_loss_threshold, "%"); } } //+------------------------------------------------------------------+ //| Set debug mode | //+------------------------------------------------------------------+ void SetDebugMode(bool enable_debug) { m_enable_debug = enable_debug; } //+------------------------------------------------------------------+ //| Create UI elements | //+------------------------------------------------------------------+ void CreateUI() { if(m_enable_debug) { Print("[UIManager] Creating UI elements..."); } if(m_chart_id == 0) { Print("[ERROR] Cannot create UI: Chart ID is 0"); return; } CreateLossLabels(); if(m_manual_mode) { CreateManualModeControls(); } if(m_enable_debug) { Print("[UIManager] UI elements created successfully"); } } //+------------------------------------------------------------------+ //| Delete UI elements | //+------------------------------------------------------------------+ void DeleteUI() { if(m_enable_debug) { Print("[UIManager] Deleting UI elements..."); } int deleted_count = 0; if(ObjectDelete(m_chart_id, "AccumLossLabel")) deleted_count++; if(ObjectDelete(m_chart_id, "AccumLossPercentLabel")) deleted_count++; if(m_manual_mode) { if(ObjectDelete(m_chart_id, "ManualTPLabel")) deleted_count++; if(ObjectDelete(m_chart_id, "ManualTPEdit")) deleted_count++; if(ObjectDelete(m_chart_id, "ManualSLLabel")) deleted_count++; if(ObjectDelete(m_chart_id, "ManualSLEdit")) deleted_count++; if(ObjectDelete(m_chart_id, "ManualTradeButton")) deleted_count++; } if(m_enable_debug) { Print("[UIManager] Deleted ", deleted_count, " UI elements"); } } //+------------------------------------------------------------------+ //| Update loss display | //+------------------------------------------------------------------+ void UpdateLossDisplay(double accumulated_loss, double loss_percent) { m_accumulated_loss = accumulated_loss; m_loss_percent = loss_percent; // Validate inputs if(accumulated_loss < 0) { Print("[WARNING] Invalid accumulated loss: ", accumulated_loss, ". Using 0"); accumulated_loss = 0; } if(loss_percent < 0) { Print("[WARNING] Invalid loss percent: ", loss_percent, "%. Using 0%"); loss_percent = 0; } string loss_text = "Accum. Loss: " + DoubleToString(accumulated_loss, 2); string percent_text = "Loss % of Bal: " + DoubleToString(loss_percent, 2) + "%"; color label_color = clrWhite; bool is_high_loss = false; if(m_high_loss_threshold > 0 && loss_percent >= m_high_loss_threshold) { label_color = clrOrangeRed; is_high_loss = true; } // Update labels ObjectSetString(m_chart_id, "AccumLossLabel", OBJPROP_TEXT, loss_text); ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_COLOR, label_color); ObjectSetString(m_chart_id, "AccumLossPercentLabel", OBJPROP_TEXT, percent_text); ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_COLOR, label_color); if(m_enable_debug) { Print("[UIManager] Loss display updated: ", loss_text, ", ", percent_text, (is_high_loss ? " [HIGH LOSS WARNING]" : "")); } } //+------------------------------------------------------------------+ //| Update manual mode UI | //+------------------------------------------------------------------+ void UpdateManualModeUI() { // Update any dynamic manual mode elements if needed } //+------------------------------------------------------------------+ //| Get manual TP from UI | //+------------------------------------------------------------------+ double GetManualTP() { if(!m_manual_mode) { if(m_enable_debug) { Print("[UIManager] Manual mode not enabled, returning TP = 0"); } return 0; } string tp_str = ObjectGetString(m_chart_id, "ManualTPEdit", OBJPROP_TEXT); double tp_value = StringToDouble(tp_str); if(m_enable_debug) { Print("[UIManager] Manual TP: ", tp_value); } return tp_value; } //+------------------------------------------------------------------+ //| Get manual SL from UI | //+------------------------------------------------------------------+ double GetManualSL() { if(!m_manual_mode) { if(m_enable_debug) { Print("[UIManager] Manual mode not enabled, returning SL = 0"); } return 0; } string sl_str = ObjectGetString(m_chart_id, "ManualSLEdit", OBJPROP_TEXT); double sl_value = StringToDouble(sl_str); if(m_enable_debug) { Print("[UIManager] Manual SL: ", sl_value); } return sl_value; } //+------------------------------------------------------------------+ //| Check if manual trade button was clicked | //+------------------------------------------------------------------+ bool IsManualTradeButtonClicked() { if(!m_manual_mode) return false; // This will be checked in OnChartEvent return false; } private: //+------------------------------------------------------------------+ //| Create loss display labels | //+------------------------------------------------------------------+ void CreateLossLabels() { if(m_enable_debug) { Print("[UIManager] Creating loss display labels..."); } // Accumulated Loss Label if(!ObjectCreate(m_chart_id, "AccumLossLabel", OBJ_LABEL, 0, 0, 0)) { Print("[ERROR] Failed to create AccumLossLabel. Error: ", GetLastError()); } else { ObjectSetString(m_chart_id, "AccumLossLabel", OBJPROP_TEXT, "Accum. Loss: Loading..."); ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_CORNER, CORNER_LEFT_LOWER); ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_XDISTANCE, 10); ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_YDISTANCE, 40); ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_FONTSIZE, 10); ObjectSetInteger(m_chart_id, "AccumLossLabel", OBJPROP_COLOR, clrWhite); } // Loss Percent Label if(!ObjectCreate(m_chart_id, "AccumLossPercentLabel", OBJ_LABEL, 0, 0, 0)) { Print("[ERROR] Failed to create AccumLossPercentLabel. Error: ", GetLastError()); } else { ObjectSetString(m_chart_id, "AccumLossPercentLabel", OBJPROP_TEXT, "Loss % of Bal: Loading..."); ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_CORNER, CORNER_LEFT_LOWER); ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_XDISTANCE, 10); ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_YDISTANCE, 20); ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_FONTSIZE, 10); ObjectSetInteger(m_chart_id, "AccumLossPercentLabel", OBJPROP_COLOR, clrWhite); } if(m_enable_debug) { Print("[UIManager] Loss display labels created"); } } //+------------------------------------------------------------------+ //| Create manual mode controls | //+------------------------------------------------------------------+ void CreateManualModeControls() { if(m_enable_debug) { Print("[UIManager] Creating manual mode controls..."); } int created_count = 0; // Manual TP Label if(ObjectCreate(m_chart_id, "ManualTPLabel", OBJ_LABEL, 0, 0, 0)) { ObjectSetString(m_chart_id, "ManualTPLabel", OBJPROP_TEXT, "Take Profit:"); ObjectSetInteger(m_chart_id, "ManualTPLabel", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(m_chart_id, "ManualTPLabel", OBJPROP_XDISTANCE, 180); ObjectSetInteger(m_chart_id, "ManualTPLabel", OBJPROP_YDISTANCE, 50); created_count++; } else { Print("[ERROR] Failed to create ManualTPLabel. Error: ", GetLastError()); } // Manual TP Edit if(ObjectCreate(m_chart_id, "ManualTPEdit", OBJ_EDIT, 0, 0, 0)) { ObjectSetInteger(m_chart_id, "ManualTPEdit", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(m_chart_id, "ManualTPEdit", OBJPROP_XDISTANCE, 110); ObjectSetInteger(m_chart_id, "ManualTPEdit", OBJPROP_YDISTANCE, 50); ObjectSetInteger(m_chart_id, "ManualTPEdit", OBJPROP_XSIZE, 60); created_count++; } else { Print("[ERROR] Failed to create ManualTPEdit. Error: ", GetLastError()); } // Manual SL Label if(ObjectCreate(m_chart_id, "ManualSLLabel", OBJ_LABEL, 0, 0, 0)) { ObjectSetString(m_chart_id, "ManualSLLabel", OBJPROP_TEXT, "Stop Loss:"); ObjectSetInteger(m_chart_id, "ManualSLLabel", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(m_chart_id, "ManualSLLabel", OBJPROP_XDISTANCE, 180); ObjectSetInteger(m_chart_id, "ManualSLLabel", OBJPROP_YDISTANCE, 70); created_count++; } else { Print("[ERROR] Failed to create ManualSLLabel. Error: ", GetLastError()); } // Manual SL Edit if(ObjectCreate(m_chart_id, "ManualSLEdit", OBJ_EDIT, 0, 0, 0)) { ObjectSetInteger(m_chart_id, "ManualSLEdit", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(m_chart_id, "ManualSLEdit", OBJPROP_XDISTANCE, 110); ObjectSetInteger(m_chart_id, "ManualSLEdit", OBJPROP_YDISTANCE, 70); ObjectSetInteger(m_chart_id, "ManualSLEdit", OBJPROP_XSIZE, 60); created_count++; } else { Print("[ERROR] Failed to create ManualSLEdit. Error: ", GetLastError()); } // Manual Trade Button if(ObjectCreate(m_chart_id, "ManualTradeButton", OBJ_BUTTON, 0, 0, 0)) { ObjectSetString(m_chart_id, "ManualTradeButton", OBJPROP_TEXT, "Open Market Order"); ObjectSetInteger(m_chart_id, "ManualTradeButton", OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(m_chart_id, "ManualTradeButton", OBJPROP_XDISTANCE, 110); ObjectSetInteger(m_chart_id, "ManualTradeButton", OBJPROP_YDISTANCE, 95); ObjectSetInteger(m_chart_id, "ManualTradeButton", OBJPROP_XSIZE, 130); ObjectSetInteger(m_chart_id, "ManualTradeButton", OBJPROP_YSIZE, 25); created_count++; } else { Print("[ERROR] Failed to create ManualTradeButton. Error: ", GetLastError()); } if(m_enable_debug) { Print("[UIManager] Created ", created_count, " manual mode controls"); } } }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| MAIN EA CODE | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Trade Mode Enum | //+------------------------------------------------------------------+ enum ENUM_TRADE_MODE { MODE_INDICATOR, MODE_MANUAL }; //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ // --- General Settings --- input string General_Settings = "--- General Settings ---"; input ENUM_TRADE_MODE Trade_Mode = MODE_INDICATOR; input double LotSize = 0.03; input int Slippage = 3; input int MagicNumber = 24680; input bool TakeScreenshotOnOpen = true; input bool EnableDebugPrints = true; input bool ExitOnOppositeSignal = false; // --- Money Management Settings --- input string Money_Management_Settings = "--- Money Management Settings ---"; input bool UsePercentBalanceLot = true; input double PercentOfBalanceForProfit = 1.0; input double DailyProfitTargetPercent = 2.0; // --- Time Filtering Settings --- input string Time_Filter_Settings = "--- Time Filtering Settings ---"; input bool EnableTimeFiltering = false; input string Day_Filter_Header = "--- Day of Week Filter ---"; input bool TradeMondayEnabled = true; input bool TradeTuesdayEnabled = true; input bool TradeWednesdayEnabled = true; input bool TradeThursdayEnabled = true; input bool TradeFridayEnabled = true; input bool TradeSaturdayEnabled = false; input bool TradeSundayEnabled = false; input string Session_Filter_Header = "--- Trading Session Filter ---"; input bool TradeAsianSessionEnabled = true; input bool TradeEuropeSessionEnabled = true; input bool TradeAmericaSessionEnabled = true; // --- Risk Management Settings --- input string Risk_Management_Settings = "--- Risk Management Settings ---"; input bool UseBreakeven = true; input int BreakevenPips = 30; input bool UseTrailingStop = false; input int TrailingStopPips = 300; input int MinimumTakeProfitPips = 300; // --- Indicator Settings --- input string Indicator_Settings = "--- Indicator Settings ---"; input string IndicatorFileName = "My_Indicator"; input int BuySignalBuffer = 0; input int SellSignalBuffer = 1; // --- SL/TP Mode Settings --- input string SLTP_Settings = "--- SL/TP Mode Settings ---"; input int SLTP_Mode = 0; // 0=ATR, 1=Indicator // --- ATR Settings (Mode 0) --- input string ATR_Settings = "--- (Mode 0) ATR Settings ---"; input int AtrPeriod = 14; input double StopLossAtrMultiplier = 1.5; input double TakeProfitAtrMultiplier = 3.0; // --- Indicator SL/TP Buffers (Mode 1) --- input string Indicator_SLTP_Settings = "--- (Mode 1) Indicator SL/TP ---"; input int BuyStopLossBuffer = 2; input int BuyTP1Buffer = 3; input int BuyTP2Buffer = 4; input int BuyTP3Buffer = 5; input int SellStopLossBuffer = 6; input int SellTP1Buffer = 7; input int SellTP2Buffer = 8; input int SellTP3Buffer = 9; // --- Partial Close Settings --- input string Partial_Close_Settings = "--- Partial Close Settings ---"; input bool EnablePartialClose = true; input bool UseEqualDivision = true; input double TP1_ClosePercent = 50.0; input double TP2_ClosePercent = 30.0; input double TP3_ClosePercent = 20.0; //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ // Component instances CSignalDetector *g_signal_detector; CTimeFilter *g_time_filter; CMoneyManager *g_money_manager; CRiskManager *g_risk_manager; CPartialCloseManager *g_partial_close_manager; CTradeExecutor *g_trade_executor; CStateManager *g_state_manager; CLoggingManager *g_logging_manager; CUIManager *g_ui_manager; // Symbol info string g_symbol; int g_digits; double g_point_value; double g_pip_value; double m_tick_value; double m_stop_level_points; // Lot info double g_min_lot; double g_max_lot; double g_lot_step; // Time tracking datetime g_last_bar_time; datetime g_last_daily_reset_time; // State double g_accumulated_loss; int g_consecutive_losses; double g_daily_profit_accumulated; double g_daily_start_balance; // Chart long g_chart_id; // Manual mode bool g_manual_trade_button_pressed; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Initialize logging first g_logging_manager = new CLoggingManager(); g_logging_manager.SetParameters(EnableDebugPrints); g_logging_manager.LogInfo("========== EA Initializing =========="); // Get symbol info g_symbol = _Symbol; g_digits = (int)SymbolInfoInteger(g_symbol, SYMBOL_DIGITS); g_point_value = SymbolInfoDouble(g_symbol, SYMBOL_POINT); g_pip_value = (g_digits == 3 || g_digits == 5) ? g_point_value * 10 : g_point_value; m_tick_value = SymbolInfoDouble(g_symbol, SYMBOL_TRADE_TICK_VALUE); m_stop_level_points = (double)SymbolInfoInteger(g_symbol, SYMBOL_TRADE_STOPS_LEVEL); // Get lot info g_min_lot = SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_MIN); g_max_lot = SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_MAX); g_lot_step = SymbolInfoDouble(g_symbol, SYMBOL_VOLUME_STEP); g_chart_id = ChartID(); // Initialize state manager g_state_manager = new CStateManager(); g_state_manager.SetParameters(g_symbol, MagicNumber, EnableDebugPrints); g_state_manager.LoadState(g_accumulated_loss, g_consecutive_losses); // Initialize time filter g_time_filter = new CTimeFilter(); g_time_filter.SetParameters( EnableTimeFiltering, TradeMondayEnabled, TradeTuesdayEnabled, TradeWednesdayEnabled, TradeThursdayEnabled, TradeFridayEnabled, TradeSaturdayEnabled, TradeSundayEnabled, TradeAsianSessionEnabled, TradeEuropeSessionEnabled, TradeAmericaSessionEnabled, EnableDebugPrints ); // Initialize money manager g_money_manager = new CMoneyManager(); g_money_manager.SetParameters( LotSize, UsePercentBalanceLot, PercentOfBalanceForProfit, DailyProfitTargetPercent, g_min_lot, g_max_lot, g_lot_step, g_point_value, m_tick_value, EnableDebugPrints ); // Initialize signal detector g_signal_detector = new CSignalDetector(); g_signal_detector.SetParameters( IndicatorFileName, BuySignalBuffer, SellSignalBuffer, BuyStopLossBuffer, SellStopLossBuffer, SLTP_Mode, AtrPeriod, StopLossAtrMultiplier, TakeProfitAtrMultiplier, MinimumTakeProfitPips, g_pip_value, g_digits, g_symbol, EnableDebugPrints ); // Set TP buffers int buy_tp_buffers[] = {BuyTP1Buffer, BuyTP2Buffer, BuyTP3Buffer}; int sell_tp_buffers[] = {SellTP1Buffer, SellTP2Buffer, SellTP3Buffer}; g_signal_detector.SetBuyTPBuffers(buy_tp_buffers); g_signal_detector.SetSellTPBuffers(sell_tp_buffers); if(!g_signal_detector.Initialize()) { g_logging_manager.LogError(GetLastError(), "OnInit - SignalDetector initialization"); return INIT_FAILED; } // Initialize risk manager g_risk_manager = new CRiskManager(); g_risk_manager.SetParameters( UseTrailingStop, TrailingStopPips, UseBreakeven, BreakevenPips, g_pip_value, g_digits, m_stop_level_points, MagicNumber, g_symbol, EnableDebugPrints ); g_risk_manager.EnablePartialClose(EnablePartialClose); // Initialize partial close manager g_partial_close_manager = new CPartialCloseManager(); double partial_close_percentages[] = {TP1_ClosePercent, TP2_ClosePercent, TP3_ClosePercent}; g_partial_close_manager.SetParameters( EnablePartialClose, UseEqualDivision, partial_close_percentages, MagicNumber, g_symbol, EnableDebugPrints ); // Initialize trade executor g_trade_executor = new CTradeExecutor(); g_trade_executor.SetParameters( Slippage, MagicNumber, g_symbol, g_digits, TakeScreenshotOnOpen, g_chart_id, IndicatorFileName, EnableDebugPrints ); // Initialize UI manager g_ui_manager = new CUIManager(); g_ui_manager.SetParameters( g_chart_id, (Trade_Mode == MODE_MANUAL), MagicNumber, g_symbol, 0, // High loss threshold (not used in v2.0) EnableDebugPrints ); g_ui_manager.CreateUI(); // Initialize time tracking g_last_bar_time = 0; g_last_daily_reset_time = 0; g_manual_trade_button_pressed = false; // Reset daily profit if needed ResetDailyProfitIfNeeded(); g_logging_manager.LogInfo("========== EA Initialized Successfully =========="); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { g_logging_manager.LogInfo("========== EA Deinitializing =========="); // Save state if(g_state_manager != NULL) { g_state_manager.SaveState(g_accumulated_loss, g_consecutive_losses); } // Delete UI if(g_ui_manager != NULL) { g_ui_manager.DeleteUI(); } // Delete components if(g_signal_detector != NULL) { delete g_signal_detector; g_signal_detector = NULL; } if(g_time_filter != NULL) { delete g_time_filter; g_time_filter = NULL; } if(g_money_manager != NULL) { delete g_money_manager; g_money_manager = NULL; } if(g_risk_manager != NULL) { delete g_risk_manager; g_risk_manager = NULL; } if(g_partial_close_manager != NULL) { delete g_partial_close_manager; g_partial_close_manager = NULL; } if(g_trade_executor != NULL) { delete g_trade_executor; g_trade_executor = NULL; } if(g_state_manager != NULL) { delete g_state_manager; g_state_manager = NULL; } if(g_ui_manager != NULL) { delete g_ui_manager; g_ui_manager = NULL; } if(g_logging_manager != NULL) { delete g_logging_manager; g_logging_manager = NULL; } g_logging_manager.LogInfo("========== EA Deinitialized =========="); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Update UI UpdateUI(); // Manage risk (breakeven, trailing) g_risk_manager.ManageRiskManagement(); // Check partial closes g_partial_close_manager.CheckAndExecutePartialCloses(); // Check new bar if(!IsNewBar()) return; g_logging_manager.LogInfo("========== New Bar Detected =========="); // Update daily profit tracking UpdateDailyProfitTracking(); // Process based on trade mode if(Trade_Mode == MODE_MANUAL) { ProcessManualTrade(); } else if(Trade_Mode == MODE_INDICATOR) { ProcessIndicatorTrade(); } } //+------------------------------------------------------------------+ //| Chart event function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK && Trade_Mode == MODE_MANUAL && sparam == "ManualTradeButton") { g_manual_trade_button_pressed = true; g_logging_manager.LogDebug("Manual trade button clicked"); } } //+------------------------------------------------------------------+ //| Check if new bar | //+------------------------------------------------------------------+ bool IsNewBar() { datetime current_bar_time = iTime(g_symbol, PERIOD_CURRENT, 0); if(current_bar_time != g_last_bar_time) { g_last_bar_time = current_bar_time; return true; } return false; } //+------------------------------------------------------------------+ //| Update UI | //+------------------------------------------------------------------+ void UpdateUI() { double account_balance = AccountInfoDouble(ACCOUNT_BALANCE); double loss_percent = 0; if(account_balance > 0) { loss_percent = (g_accumulated_loss / account_balance) * 100.0; } g_ui_manager.UpdateLossDisplay(g_accumulated_loss, loss_percent); } //+------------------------------------------------------------------+ //| Reset daily profit if needed | //+------------------------------------------------------------------+ void ResetDailyProfitIfNeeded() { datetime now = TimeCurrent(); datetime midnight = now - (now % 86400); datetime reset_time = midnight + 3600; // 01:00 GMT if(now >= reset_time && (g_last_daily_reset_time < reset_time || TimeToString(now, TIME_DATE) != TimeToString(g_last_daily_reset_time, TIME_DATE))) { double current_balance = AccountInfoDouble(ACCOUNT_BALANCE); g_money_manager.ResetDailyProfit(current_balance); g_last_daily_reset_time = now; g_logging_manager.LogInfo("Daily Profit Tracking Reset at 01:00. Start Balance: " + DoubleToString(current_balance, 2)); } } //+------------------------------------------------------------------+ //| Update daily profit tracking | //+------------------------------------------------------------------+ void UpdateDailyProfitTracking() { ResetDailyProfitIfNeeded(); // Calculate daily profit from history double daily_profit = 0; for(int i = HistoryDealsTotal() - 1; i >= 0; i--) { if(HistoryDealSelect(i)) { string symbol; long magic, entry_type, deal_time; double profit, commission, swap; if(HistoryDealGetString(i, DEAL_SYMBOL, symbol) && HistoryDealGetInteger(i, DEAL_MAGIC, magic) && HistoryDealGetInteger(i, DEAL_ENTRY, entry_type) && HistoryDealGetInteger(i, DEAL_TIME, deal_time) && HistoryDealGetDouble(i, DEAL_PROFIT, profit) && HistoryDealGetDouble(i, DEAL_COMMISSION, commission) && HistoryDealGetDouble(i, DEAL_SWAP, swap)) { if(symbol == g_symbol && magic == MagicNumber && entry_type == DEAL_ENTRY_OUT && deal_time > g_last_daily_reset_time) { daily_profit += profit + commission + swap; } } } } g_money_manager.SetDailyProfitAccumulated(daily_profit); } //+------------------------------------------------------------------+ //| Process manual trade | //+------------------------------------------------------------------+ void ProcessManualTrade() { if(!g_manual_trade_button_pressed) return; g_manual_trade_button_pressed = false; double manual_tp = g_ui_manager.GetManualTP(); double manual_sl = g_ui_manager.GetManualSL(); if(manual_tp == 0 || manual_sl == 0) { g_logging_manager.LogWarning("Please fill both Stop Loss and Take Profit values for manual trade."); return; } double bid = SymbolInfoDouble(g_symbol, SYMBOL_BID); double ask = SymbolInfoDouble(g_symbol, SYMBOL_ASK); bool is_buy = (manual_tp > bid && manual_sl < bid); bool is_sell = (manual_tp < bid && manual_sl > bid); if(!is_buy && !is_sell) { g_logging_manager.LogWarning("Invalid SL/TP values for a market order."); return; } // Calculate lot size double lot_size = g_money_manager.CalculateLotSize(is_buy, is_buy ? ask : bid, manual_tp, AccountInfoDouble(ACCOUNT_BALANCE)); // Validate SL/TP double tp_prices[] = {manual_tp}; CRiskManager::ValidatedSLTP validated = g_risk_manager.ValidateSLTP(is_buy, is_buy ? ask : bid, manual_sl, tp_prices, 1); if(!validated.is_valid) { g_logging_manager.LogWarning("SL/TP validation failed: " + validated.error_message); return; } // Execute trade CTradeExecutor::TradeResult result = g_trade_executor.ExecuteTrade( is_buy, lot_size, is_buy ? ask : bid, validated.sl_price, validated.tp_prices, validated.tp_count ); if(!result.success) { g_logging_manager.LogError(GetLastError(), "ProcessManualTrade - ExecuteTrade"); } } //+------------------------------------------------------------------+ //| Process indicator trade | //+------------------------------------------------------------------+ void ProcessIndicatorTrade() { // Check daily profit target if(g_money_manager.IsDailyProfitTargetReached()) { g_logging_manager.LogInfo("Daily profit target reached. Skipping trade."); return; } // Check time filter if(!g_time_filter.IsTimeAllowed()) { g_logging_manager.LogDebug("Time filter not allowed. Skipping trade."); return; } // Check if trade is already open if(g_trade_executor.IsTradeOpen()) { g_logging_manager.LogDebug("Trade already open. Skipping new signal."); return; } // Detect signal CSignalDetector::SignalData signal = g_signal_detector.DetectSignal(1); if(!signal.has_signal) { g_logging_manager.LogDebug("No signal detected."); return; } g_logging_manager.LogInfo(signal.is_buy ? "BUY signal detected" : "SELL signal detected"); g_logging_manager.LogInfo("Signal Price: " + DoubleToString(signal.signal_price, g_digits)); g_logging_manager.LogInfo("SL: " + DoubleToString(signal.sl_price, g_digits)); for(int i = 0; i < signal.tp_count; i++) { g_logging_manager.LogInfo("TP" + IntegerToString(i + 1) + ": " + DoubleToString(signal.tp_prices[i], g_digits)); } if(signal.used_atr_fallback) { g_logging_manager.LogInfo("ATR fallback used for SL/TP"); } // Exit opposite trade if enabled if(ExitOnOppositeSignal) { g_trade_executor.CloseOppositeTrade(signal.is_buy); } // Calculate lot size double open_price = SymbolInfoDouble(g_symbol, SYMBOL_BID); double lot_size = g_money_manager.CalculateLotSize( signal.is_buy, open_price, signal.tp_prices[0], // Use first TP for lot calculation AccountInfoDouble(ACCOUNT_BALANCE) ); g_logging_manager.LogInfo("Lot size: " + DoubleToString(lot_size, 2)); // Validate SL/TP CRiskManager::ValidatedSLTP validated = g_risk_manager.ValidateSLTP( signal.is_buy, open_price, signal.sl_price, signal.tp_prices, signal.tp_count ); if(!validated.is_valid) { g_logging_manager.LogWarning("SL/TP validation failed: " + validated.error_message); return; } // Execute trade CTradeExecutor::TradeResult result = g_trade_executor.ExecuteTrade( signal.is_buy, lot_size, open_price, validated.sl_price, validated.tp_prices, validated.tp_count ); if(!result.success) { g_logging_manager.LogError(GetLastError(), "ProcessIndicatorTrade - ExecuteTrade"); } } //+------------------------------------------------------------------+