//+------------------------------------------------------------------+ //| CErrorRecovery.mqh - Centralized Error Recovery System | //| Provides consistent error handling, retry logic, and recovery | //+------------------------------------------------------------------+ #ifndef CERRORRECOVERY_MQH #define CERRORRECOVERY_MQH #include "CArchitectureConfig.mqh" // Error severity levels enum ENUM_ERROR_SEVERITY { ERR_SEVERITY_LOW = 0, // Log only, continue ERR_SEVERITY_MEDIUM = 1, // Degrade gracefully ERR_SEVERITY_HIGH = 2, // Block operations temporarily ERR_SEVERITY_CRITICAL = 3 // Shutdown required }; // Error categories for classification enum ENUM_ERROR_CATEGORY { ERR_CAT_FILE_IO = 0, ERR_CAT_NETWORK = 1, ERR_CAT_MEMORY = 2, ERR_CAT_TRADE = 3, ERR_CAT_SYSTEM = 4, ERR_CAT_UNKNOWN = 5 }; // Recovery action types enum ENUM_RECOVERY_ACTION { RECOVERY_NONE = 0, // No action, just log RECOVERY_RETRY = 1, // Retry operation RECOVERY_FALLBACK = 2, // Use fallback mode RECOVERY_RESET = 3, // Reset and reinitialize RECOVERY_SHUTDOWN = 4 // Graceful shutdown }; //+------------------------------------------------------------------+ //| Error Record Structure | //+------------------------------------------------------------------+ struct SErrorRecord { int error_code; ENUM_ERROR_CATEGORY category; ENUM_ERROR_SEVERITY severity; string context; string message; datetime timestamp; int retry_count; bool recovered; SErrorRecord() { error_code = 0; category = ERR_CAT_UNKNOWN; severity = ERR_SEVERITY_LOW; context = ""; message = ""; timestamp = 0; retry_count = 0; recovered = false; } }; //+------------------------------------------------------------------+ //| Error Recovery Manager | //+------------------------------------------------------------------+ class CErrorRecovery { private: static CErrorRecovery* s_instance; // Error history for analysis SErrorRecord m_error_history[]; int m_history_count; int m_max_history; // Retry configuration int m_max_retries; int m_retry_delay_ms; // Circuit breaker state int m_consecutive_errors; datetime m_circuit_breaker_until; int m_circuit_breaker_threshold; int m_circuit_breaker_cooldown_sec; // Category-specific thresholds int m_category_errors[]; datetime m_category_last_error[]; // Statistics int m_total_errors; int m_recovered_errors; int m_failed_errors; // Logging bool m_verbose; string m_log_prefix; public: CErrorRecovery() { m_history_count = 0; m_max_history = 100; ArrayResize(m_error_history, m_max_history); m_max_retries = 3; m_retry_delay_ms = 100; m_consecutive_errors = 0; m_circuit_breaker_until = 0; m_circuit_breaker_threshold = 5; m_circuit_breaker_cooldown_sec = 60; ArrayResize(m_category_errors, 6); ArrayResize(m_category_last_error, 6); for(int i = 0; i < 6; i++) { m_category_errors[i] = 0; m_category_last_error[i] = 0; } m_total_errors = 0; m_recovered_errors = 0; m_failed_errors = 0; m_verbose = false; m_log_prefix = "[ErrorRecovery]"; } ~CErrorRecovery() { // Cleanup } static CErrorRecovery* Instance() { if(s_instance == NULL) s_instance = new CErrorRecovery(); return s_instance; } static void Destroy() { if(s_instance != NULL) { delete s_instance; s_instance = NULL; } } //+------------------------------------------------------------------+ //| Configure recovery behavior | //+------------------------------------------------------------------+ void SetMaxRetries(int max_retries) { m_max_retries = max_retries; } void SetRetryDelay(int delay_ms) { m_retry_delay_ms = delay_ms; } void SetCircuitBreakerThreshold(int threshold) { m_circuit_breaker_threshold = threshold; } void SetCircuitBreakerCooldown(int cooldown_sec) { m_circuit_breaker_cooldown_sec = cooldown_sec; } void SetVerbose(bool verbose) { m_verbose = verbose; } //+------------------------------------------------------------------+ //| Main error handling entry point | //+------------------------------------------------------------------+ bool HandleError(int error_code, const string context, const string message) { // Clear error to prevent contamination ResetLastError(); // Check if in circuit breaker if(IsCircuitBreakerActive()) { Log("Circuit breaker active, operation blocked", ERR_SEVERITY_HIGH); return false; } // Classify error ENUM_ERROR_CATEGORY category = ClassifyError(error_code); ENUM_ERROR_SEVERITY severity = DetermineSeverity(error_code, category); // Create error record SErrorRecord record; record.error_code = error_code; record.category = category; record.severity = severity; record.context = context; record.message = message; record.timestamp = TimeCurrent(); record.retry_count = 0; record.recovered = false; // Add to history AddToHistory(record); // Update statistics m_total_errors++; m_consecutive_errors++; m_category_errors[category]++; m_category_last_error[category] = TimeCurrent(); // Check circuit breaker if(m_consecutive_errors >= m_circuit_breaker_threshold) { ActivateCircuitBreaker(); } // Determine recovery action ENUM_RECOVERY_ACTION action = DetermineRecoveryAction(error_code, category, severity); // Execute recovery bool recovered = ExecuteRecovery(action, record); if(recovered) { m_recovered_errors++; m_consecutive_errors = 0; record.recovered = true; UpdateHistoryRecord(record); Log("Error recovered: " + message, ERR_SEVERITY_LOW); } else { m_failed_errors++; Log("Error recovery failed: " + message, severity); } return recovered; } //+------------------------------------------------------------------+ //| Classify error code to category | //+------------------------------------------------------------------+ ENUM_ERROR_CATEGORY ClassifyError(int error_code) { switch(error_code) { // File I/O errors case 5001: case 5002: case 5003: case 5004: case 5005: case 5006: case 5007: case 5008: case 5010: case 5011: case 5012: case 5013: case 5014: case 5015: case 5016: case 5017: case 5020: return ERR_CAT_FILE_IO; // Network/connection errors case 5200: case 5201: case 5202: case 5203: case 6750: case 6751: case 6752: return ERR_CAT_NETWORK; // Memory errors case 4001: case 4002: case 4003: return ERR_CAT_MEMORY; // Trading errors case 10004: case 10006: case 10007: case 10009: case 10010: case 10013: case 10014: case 10015: case 10016: case 10021: case 10022: case 10023: case 10024: case 10025: case 10026: case 10027: return ERR_CAT_TRADE; // System errors case 1: case 2: case 3: case 4: return ERR_CAT_SYSTEM; default: return ERR_CAT_UNKNOWN; } } //+------------------------------------------------------------------+ //| Determine error severity | //+------------------------------------------------------------------+ ENUM_ERROR_SEVERITY DetermineSeverity(int error_code, ENUM_ERROR_CATEGORY category) { // Critical errors switch(error_code) { case 4001: // ERR_WRONG_FUNCTION_POINTER case 4002: // ERR_ARRAY_INDEX_OUT_OF_RANGE case 2: // ERR_COMMON_ERROR case 10026: // ERR_TRADE_MODIFY_DENIED return ERR_SEVERITY_CRITICAL; // High severity case 10007: // ERR_TRADE_NOT_ALLOWED case 10014: // ERR_TRADE_INVALID_STOPS case 5001: // ERR_FILE_TOO_LARGE case 5200: // ERR_INTERNET_CONNECTION_CLOSED return ERR_SEVERITY_HIGH; // Medium severity case 10004: // ERR_REQUOTE case 10006: // ERR_NO_PRICES case 5004: // ERR_FILE_NOT_OPEN case 6750: // ERR_SOCKET_INIT_FAILED return ERR_SEVERITY_MEDIUM; // Low severity (transient) case 10009: // ERR_TRADE_ERROR case 10010: // ERR_TRADE_TIMEOUT case 5003: // ERR_FILE_INVALID_HANDLE return ERR_SEVERITY_LOW; } // Category-based fallback if(category == ERR_CAT_MEMORY) return ERR_SEVERITY_HIGH; if(category == ERR_CAT_SYSTEM) return ERR_SEVERITY_HIGH; return ERR_SEVERITY_MEDIUM; } //+------------------------------------------------------------------+ //| Determine recovery action | //+------------------------------------------------------------------+ ENUM_RECOVERY_ACTION DetermineRecoveryAction(int error_code, ENUM_ERROR_CATEGORY category, ENUM_ERROR_SEVERITY severity) { // Never retry critical errors if(severity == ERR_SEVERITY_CRITICAL) return RECOVERY_SHUTDOWN; // Network errors: retry with fallback if(category == ERR_CAT_NETWORK) return RECOVERY_RETRY; // File I/O errors: retry then fallback if(category == ERR_CAT_FILE_IO) return RECOVERY_RETRY; // Trading errors: specific handling if(category == ERR_CAT_TRADE) { switch(error_code) { case 10004: // Requote - retry immediately case 10006: // No prices - retry later return RECOVERY_RETRY; case 10007: // Not allowed - check permissions then fallback return RECOVERY_FALLBACK; default: return RECOVERY_NONE; } } // Default: no recovery action return RECOVERY_NONE; } //+------------------------------------------------------------------+ //| Execute recovery action | //+------------------------------------------------------------------+ bool ExecuteRecovery(ENUM_RECOVERY_ACTION action, SErrorRecord &record) { switch(action) { case RECOVERY_NONE: return true; // Nothing to do case RECOVERY_RETRY: return ExecuteRetry(record); case RECOVERY_FALLBACK: return ExecuteFallback(record); case RECOVERY_RESET: return ExecuteReset(record); case RECOVERY_SHUTDOWN: ExecuteShutdown(record); return false; // Cannot recover from shutdown default: return false; } } //+------------------------------------------------------------------+ //| Retry logic with exponential backoff | //+------------------------------------------------------------------+ bool ExecuteRetry(SErrorRecord &record) { if(record.retry_count >= m_max_retries) { Log("Max retries exceeded for error " + IntegerToString(record.error_code), ERR_SEVERITY_MEDIUM); return false; } record.retry_count++; // Calculate delay with exponential backoff int delay = m_retry_delay_ms * (1 << (record.retry_count - 1)); if(delay > 5000) delay = 5000; // Cap at 5 seconds Log("Retry " + IntegerToString(record.retry_count) + "/" + IntegerToString(m_max_retries) + " after " + IntegerToString(delay) + "ms for " + record.context, ERR_SEVERITY_LOW); // Use Sleep only in non-tick contexts // In OnTick, this should be handled asynchronously // For now, we return false to indicate retry needed return false; // Caller must handle actual retry } //+------------------------------------------------------------------+ //| Fallback mode execution | //+------------------------------------------------------------------+ bool ExecuteFallback(SErrorRecord &record) { Log("Activating fallback mode for " + record.context, ERR_SEVERITY_MEDIUM); // Fallback behaviors by category switch(record.category) { case ERR_CAT_FILE_IO: // Switch to memory buffer mode return true; case ERR_CAT_NETWORK: // Use cached data return true; case ERR_CAT_TRADE: // Switch to shadow/simulation mode return true; default: return false; } } //+------------------------------------------------------------------+ //| Reset and reinitialize | //+------------------------------------------------------------------+ bool ExecuteReset(SErrorRecord &record) { Log("Executing system reset for " + record.context, ERR_SEVERITY_HIGH); // Clear error state ResetLastError(); // Reset counters m_consecutive_errors = 0; return true; } //+------------------------------------------------------------------+ //| Graceful shutdown | //+------------------------------------------------------------------+ void ExecuteShutdown(SErrorRecord &record) { Log("CRITICAL: Executing graceful shutdown - " + record.message, ERR_SEVERITY_CRITICAL); // Log final statistics string stats = StringFormat("Total errors: %d, Recovered: %d, Failed: %d", m_total_errors, m_recovered_errors, m_failed_errors); Log(stats, ERR_SEVERITY_CRITICAL); // In real implementation, this would trigger EA shutdown // ExpertRemove(); // Uncomment when ready for production } //+------------------------------------------------------------------+ //| Circuit breaker management | //+------------------------------------------------------------------+ bool IsCircuitBreakerActive() { if(m_circuit_breaker_until == 0) return false; if(TimeCurrent() < m_circuit_breaker_until) { int remaining = (int)(m_circuit_breaker_until - TimeCurrent()); if(m_verbose && remaining % 10 == 0) // Log every 10 seconds Log("Circuit breaker active: " + IntegerToString(remaining) + "s remaining", ERR_SEVERITY_MEDIUM); return true; } // Circuit breaker expired m_circuit_breaker_until = 0; m_consecutive_errors = 0; Log("Circuit breaker deactivated", ERR_SEVERITY_LOW); return false; } void ActivateCircuitBreaker() { m_circuit_breaker_until = TimeCurrent() + m_circuit_breaker_cooldown_sec; Log("Circuit breaker ACTIVATED for " + IntegerToString(m_circuit_breaker_cooldown_sec) + "s", ERR_SEVERITY_HIGH); } void DeactivateCircuitBreaker() { m_circuit_breaker_until = 0; m_consecutive_errors = 0; Log("Circuit breaker manually deactivated", ERR_SEVERITY_LOW); } //+------------------------------------------------------------------+ //| History management | //+------------------------------------------------------------------+ void AddToHistory(SErrorRecord &record) { // Shift existing records for(int i = m_history_count - 1; i > 0; i--) { m_error_history[i] = m_error_history[i - 1]; } // Add new record at start m_error_history[0] = record; if(m_history_count < m_max_history) m_history_count++; } void UpdateHistoryRecord(SErrorRecord &record) { // Find and update record for(int i = 0; i < m_history_count; i++) { if(m_error_history[i].timestamp == record.timestamp && m_error_history[i].error_code == record.error_code) { m_error_history[i] = record; break; } } } //+------------------------------------------------------------------+ //| Statistics and reporting | //+------------------------------------------------------------------+ string GetErrorStats() { string stats = StringFormat( "=== Error Recovery Statistics ===\n" + "Total Errors: %d\n" + "Recovered: %d (%.1f%%)\n" + "Failed: %d (%.1f%%)\n" + "Consecutive Errors: %d\n" + "Circuit Breaker: %s\n" + "History: %d/%d records", m_total_errors, m_recovered_errors, (m_total_errors > 0 ? 100.0 * m_recovered_errors / m_total_errors : 0), m_failed_errors, (m_total_errors > 0 ? 100.0 * m_failed_errors / m_total_errors : 0), m_consecutive_errors, (IsCircuitBreakerActive() ? "ACTIVE" : "inactive"), m_history_count, m_max_history ); return stats; } string GetRecentErrors(int count = 10) { string report = "=== Recent Errors ===\n"; int show = MathMin(count, m_history_count); for(int i = 0; i < show; i++) { SErrorRecord &rec = m_error_history[i]; string status = rec.recovered ? "[RECOVERED]" : "[FAILED]"; report += StringFormat("%s %s: %d - %s (%s retries)\n", status, TimeToString(rec.timestamp, TIME_MINUTES), rec.error_code, rec.message, IntegerToString(rec.retry_count) ); } return report; } //+------------------------------------------------------------------+ //| Utility functions | //+------------------------------------------------------------------+ void Log(const string message, ENUM_ERROR_SEVERITY severity) { string prefix = m_log_prefix; switch(severity) { case ERR_SEVERITY_CRITICAL: prefix += " [CRITICAL]"; break; case ERR_SEVERITY_HIGH: prefix += " [HIGH]"; break; case ERR_SEVERITY_MEDIUM: prefix += " [MEDIUM]"; break; case ERR_SEVERITY_LOW: prefix += " [LOW]"; break; } Print(prefix + " " + message); } void ResetLastError() { // Clear any pending error int _ = GetLastError(); // Read and clear } // Getters int GetTotalErrors() const { return m_total_errors; } int GetRecoveredErrors() const { return m_recovered_errors; } int GetFailedErrors() const { return m_failed_errors; } int GetConsecutiveErrors() const { return m_consecutive_errors; } bool IsHealthy() const { return m_consecutive_errors < 3; } }; // Static instance initialization CErrorRecovery* CErrorRecovery::s_instance = NULL; // Global macro for easy error handling #define HANDLE_ERROR(code, context, message) \ CErrorRecovery::Instance().HandleError(code, context, message) #define CHECK_ERROR(context) \ do { int _err = GetLastError(); if(_err != 0) { \ CErrorRecovery::Instance().HandleError(_err, context, ""); \ ResetLastError(); \ } } while(0) #define CLEAR_ERROR() ResetLastError() #endif // CERRORRECOVERY_MQH