mql5/Experts/Advisors/DualEA/Include/CErrorRecovery.mqh
2026-04-14 20:08:20 -04:00

628 lines
20 KiB
MQL5

//+------------------------------------------------------------------+
//| 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