Creación de un sistema personalizado de detección de regímenes de mercado en MQL5 (Parte 1): Indicador
- Introducción
- Comprensión de los regímenes de mercado
- Construyendo la base estadística
- Implementación del detector de régimen de mercado
- Creación de un indicador personalizado para la visualización del régimen
- Conclusión
Introducción
Los mercados financieros se encuentran en un estado constante de cambio, transitando períodos de fuertes tendencias, consolidación lateral y volatilidad caótica. Para los traders algorítmicos, esto representa un desafío importante: una estrategia que funciona excepcionalmente bien en mercados con tendencias a menudo fracasa miserablemente en condiciones de rango, mientras que los enfoques diseñados para baja volatilidad pueden hacer estallar las cuentas cuando la volatilidad aumenta. A pesar de esta realidad, la mayoría de los sistemas comerciales se construyen con el supuesto implícito de que el comportamiento del mercado se mantiene constante a lo largo del tiempo.
Esta desconexión fundamental entre la realidad del mercado y el diseño del sistema comercial conduce al patrón demasiado familiar de degradación del rendimiento de la estrategia. Un sistema funciona brillantemente durante las pruebas retrospectivas y la implementación inicial, solo para fallar cuando las condiciones del mercado inevitablemente cambian. El comerciante se enfrenta entonces a una difícil elección: abandonar la estrategia y empezar de nuevo, o soportar caídas mientras espera que las condiciones del mercado vuelvan a favorecer su enfoque.
¿Y si hubiera una manera mejor? ¿Qué pasaría si su sistema de trading pudiera identificar objetivamente el régimen actual del mercado y adaptar su estrategia en consecuencia? Esto es precisamente lo que construiremos en este artículo: un sistema integral de detección de regímenes de mercado en MQL5 que puede clasificar las condiciones del mercado en regímenes distintos y proporcionar un marco para estrategias comerciales adaptativas.
Al final de esta serie de artículos, tendrá una implementación completa de un Sistema de detección de régimen de mercado que incluye:- Una base estadística sólida para la clasificación objetiva del mercado.
- Una clase de detector de régimen de mercado personalizada que identifica condiciones de mercado de tendencia, rango y volatilidad.
- Un indicador personalizado que visualiza los cambios de régimen directamente en sus gráficos.
- Un Asesor Experto adaptativo que selecciona automáticamente estrategias apropiadas según el régimen actual (Parte 2).
- Ejemplos prácticos de cómo implementar y optimizar el sistema para sus necesidades comerciales específicas (Parte 2).
Ya sea que sea un comerciante algorítmico experimentado que busca mejorar sus sistemas existentes o un recién llegado que busca construir estrategias más sólidas desde el principio, este sistema de detección de régimen de mercado le proporcionará herramientas poderosas para navegar por el panorama en constante cambio de los mercados financieros.
Comprensión de los regímenes de mercado
Antes de profundizar en los detalles de implementación, es fundamental comprender qué son los regímenes de mercado y por qué son importantes para los comerciantes. Los mercados no se comportan de manera uniforme a lo largo del tiempo; más bien, transitan entre distintos estados de comportamiento o "regímenes". Estos regímenes influyen significativamente en la fluctuación de los precios y, en consecuencia, en el rendimiento de las estrategias de trading.
¿Qué son los regímenes de mercado?
Los regímenes de mercado son patrones distintos de comportamiento del mercado caracterizados por propiedades estadísticas específicas de los movimientos de precios. Si bien existen varias formas de clasificar los regímenes de mercado, nos centraremos en tres tipos principales que son los más relevantes para el desarrollo de estrategias comerciales:- Regímenes de tendencia: Los mercados exhiben un fuerte movimiento direccional con una reversión a la media mínima. El precio tiende a realizar movimientos consistentes en una dirección con retrocesos superficiales. Estadísticamente, los mercados en tendencia muestran una autocorrelación positiva en los retornos, lo que significa que es probable que los movimientos de precios en una dirección sean seguidos por movimientos en la misma dirección.
- Regímenes de alcance: Los mercados oscilan entre niveles de soporte y resistencia con fuertes tendencias de reversión a la media. El precio tiende a rebotar entre límites definidos en lugar de dispararse en cualquier dirección. Estadísticamente, los mercados con rangos muestran una autocorrelación negativa en los retornos, lo que significa que es probable que los movimientos ascendentes sean seguidos por movimientos descendentes y viceversa.
- Regímenes volátiles: Los mercados experimentan movimientos de precios grandes y erráticos con una dirección poco clara. Estos regímenes suelen ocurrir durante períodos de incertidumbre, acontecimientos noticiosos o tensión en el mercado. Estadísticamente, los regímenes volátiles muestran una alta desviación estándar en los retornos con patrones de autocorrelación impredecibles.
Comprender en qué régimen se encuentra actualmente el mercado proporciona un contexto crucial para las decisiones comerciales. Una estrategia optimizada para mercados con tendencias probablemente tendrá un desempeño deficiente en condiciones de rango, mientras que las estrategias de reversión a la media diseñadas para mercados con tendencias pueden ser desastrosas durante tendencias fuertes.
¿Por qué los indicadores tradicionales fallan?
La mayoría de los indicadores técnicos fueron diseñados para identificar patrones o condiciones de precios específicos en lugar de clasificar regímenes de mercado. Por ejemplo:- Los promedios móviles y el MACD pueden ayudar a identificar tendencias, pero no distinguen entre regímenes de tendencia y volátiles.
- Los osciladores RSI y estocásticos funcionan bien en mercados con movimientos en rango, pero generan señales falsas en condiciones de tendencia.
- Las Bandas de Bollinger se adaptan a la volatilidad pero no identifican explícitamente las transiciones de régimen.
Fundamentos estadísticos de la detección de regímenes
Para construir un sistema eficaz de detección de regímenes, necesitamos aprovechar medidas estadísticas que puedan clasificar objetivamente el comportamiento del mercado. Los conceptos estadísticos clave que utilizaremos incluyen:- Autocorrelación: Mide la correlación entre una serie temporal y una versión rezagada de la misma. La autocorrelación positiva indica un comportamiento de tendencia, mientras que la autocorrelación negativa sugiere un comportamiento de reversión a la media (rango).
- Volatilidad: Mide la dispersión de los rendimientos, normalmente utilizando la desviación estándar. Los aumentos repentinos de la volatilidad a menudo indican cambios de régimen.
- Fuerza de la tendencia: Se puede cuantificar utilizando varios métodos, incluido el valor absoluto de autocorrelación, la pendiente de la regresión lineal o indicadores especializados como ADX.
Al combinar estas medidas estadísticas, podemos crear un marco sólido para clasificar los regímenes de mercado de manera objetiva. En la siguiente sección, implementaremos estos conceptos en el código MQL5 para construir nuestro sistema de detección de régimen de mercado.
Construyendo la base estadística
En esta sección, implementaremos los componentes estadísticos principales necesarios para nuestro sistema de detección de régimen de mercado. Crearemos una clase CStatistics robusta que manejará todos los cálculos matemáticos necesarios para la clasificación del régimen.
La clase CStatistics
La base de nuestro sistema de detección de regímenes es una poderosa clase de estadísticas que puede realizar varios cálculos sobre datos de precios. Examinemos los componentes clave de esta clase:
//+------------------------------------------------------------------+ //| Class for statistical calculations | //+------------------------------------------------------------------+ class CStatistics { private: double m_data[]; // Data array for calculations int m_dataSize; // Size of the data array bool m_isSorted; // Flag indicating if data is sorted double m_sortedData[]; // Sorted copy of data for percentile calculations public: // Constructor and destructor CStatistics(); ~CStatistics(); // Data management methods bool SetData(const double &data[], int size); bool AddData(double value); void Clear(); // Basic statistical methods double Mean() const; double StandardDeviation() const; double Variance() const; // Range and extremes double Min() const; double Max() const; double Range() const; // Time series specific methods double Autocorrelation(int lag) const; double TrendStrength() const; double MeanReversionStrength() const; // Percentile calculations double Percentile(double percentile); double Median(); };
Esta clase proporciona un conjunto completo de funciones estadísticas que nos permitirán analizar datos de precios y determinar el régimen actual del mercado. Veamos algunos de los métodos clave en detalle.
Constructor y Destructor
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CStatistics::CStatistics() { m_dataSize = 0; m_isSorted = false; ArrayResize(m_data, 0); ArrayResize(m_sortedData, 0); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CStatistics::~CStatistics() { Clear(); }
El constructor y el destructor ayudan a inicializar y desinicializar la clase. El constructor inicializa las variables miembro y los arrays, mientras que el destructor garantiza una limpieza adecuada mediante la llamada al método Clear(). Este patrón de inicialización y limpieza adecuados es esencial en MQL5 para evitar pérdidas de memoria y garantizar un funcionamiento confiable.
Métodos de gestión de datos
A continuación, implementemos los métodos de gestión de datos que nos permiten configurar, agregar y borrar datos:
bool CStatistics::SetData(const double &data[], int size) { if(size <= 0) return false; m_dataSize = size; ArrayResize(m_data, size); for(int i = 0; i < size; i++) m_data[i] = data[i]; m_isSorted = false; return true; } bool CStatistics::AddData(double value) { m_dataSize++; ArrayResize(m_data, m_dataSize); m_data[m_dataSize - 1] = value; m_isSorted = false; return true; } void CStatistics::Clear() { m_dataSize = 0; ArrayResize(m_data, 0); ArrayResize(m_sortedData, 0); m_isSorted = false; }
El método SetData() nos permite reemplazar todo el conjunto de datos con una nueva matriz, lo cual es útil al procesar datos de precios históricos. El método AddData() agrega un único valor a los datos existentes, lo que resulta útil para actualizaciones incrementales a medida que hay nuevos datos de precios disponibles. El método Clear() restablece el objeto a su estado inicial, liberando cualquier memoria asignada.
Observe cómo establecemos m_isSorted = false siempre que los datos cambian. Esta bandera nos ayuda a optimizar el rendimiento al ordenar los datos solo cuando es necesario para los cálculos de percentiles.
Métodos estadísticos básicos
Ahora, implementemos los métodos estadísticos básicos para calcular la media, la desviación estándar y la varianza:
double CStatistics::Mean() const { if(m_dataSize <= 0) return 0.0; double sum = 0.0; for(int i = 0; i < m_dataSize; i++) sum += m_data[i]; return sum / m_dataSize; } double CStatistics::StandardDeviation() const { if(m_dataSize <= 1) return 0.0; double mean = Mean(); double sum = 0.0; for(int i = 0; i < m_dataSize; i++) sum += MathPow(m_data[i] - mean, 2); return MathSqrt(sum / (m_dataSize - 1)); } double CStatistics::Variance() const { if(m_dataSize <= 1) return 0.0; double stdDev = StandardDeviation(); return stdDev * stdDev; }
Estos métodos implementan fórmulas estadísticas estándar. El método Mean() calcula el promedio de todos los puntos de datos. El método StandardDeviation() mide la dispersión de los puntos de datos alrededor de la media, lo que es crucial para identificar regímenes de mercado volátiles. El método Variance() devuelve el cuadrado de la desviación estándar, proporcionando otra medida de dispersión de datos.
Observe cómo manejamos casos extremos, como conjuntos de datos vacíos o puntos de datos individuales, devolviendo cero. Este enfoque de programación defensiva evita errores cuando se trabaja con datos insuficientes.Métodos de rango y extremos
//+------------------------------------------------------------------+ //| Calculate minimum value in the data | //+------------------------------------------------------------------+ double CStatistics::Min() const { if(m_dataSize <= 0) return 0.0; double min = m_data[0]; for(int i = 1; i < m_dataSize; i++) if(m_data[i] < min) min = m_data[i]; return min; } //+------------------------------------------------------------------+ //| Calculate maximum value in the data | //+------------------------------------------------------------------+ double CStatistics::Max() const { if(m_dataSize <= 0) return 0.0; double max = m_data[0]; for(int i = 1; i < m_dataSize; i++) if(m_data[i] > max) max = m_data[i]; return max; } //+------------------------------------------------------------------+ //| Calculate range (max - min) of the data | //+------------------------------------------------------------------+ double CStatistics::Range() const { return Max() - Min(); }
Estos métodos proporcionan información adicional sobre la distribución de datos. Los métodos Min() y Max() encuentran los valores más pequeños y más grandes en el conjunto de datos, mientras que el método Range() calcula la diferencia entre ellos. Estas medidas pueden ser útiles para identificar límites de precios en mercados con cambios de precios.
Métodos específicos de series de tiempo
Ahora, implementemos los métodos específicos de series de tiempo que son cruciales para la detección del régimen:
double CStatistics::Autocorrelation(int lag) const { if(m_dataSize <= lag || lag <= 0) return 0.0; double mean = Mean(); double numerator = 0.0; double denominator = 0.0; for(int i = 0; i < m_dataSize - lag; i++) { numerator += (m_data[i] - mean) * (m_data[i + lag] - mean); } for(int i = 0; i < m_dataSize; i++) { denominator += MathPow(m_data[i] - mean, 2); } if(denominator == 0.0) return 0.0; return numerator / denominator; } double CStatistics::TrendStrength() const { // Use lag-1 autocorrelation as a measure of trend strength double ac1 = Autocorrelation(1); // Positive autocorrelation indicates trending behavior return ac1; } double CStatistics::MeanReversionStrength() const { // Negative autocorrelation indicates mean-reverting behavior double ac1 = Autocorrelation(1); // Return the negative of autocorrelation, so positive values // indicate stronger mean reversion return -ac1; }
El método Autocorrelation() calcula la correlación entre la serie de datos y una versión rezagada de sí misma. Esta es una medida poderosa para distinguir entre mercados en tendencia y mercados en rango. La autocorrelación positiva (valores mayores que cero) indica un comportamiento de tendencia, mientras que la autocorrelación negativa (valores menores que cero) sugiere un comportamiento de reversión a la media o de rango.
El método TrendStrength() utiliza la autocorrelación de retardo 1 como medida directa de la fuerza de la tendencia. Los valores positivos más altos indican tendencias más fuertes. El método MeanReversionStrength() devuelve el negativo de la autocorrelación, por lo que los valores positivos indican tendencias de reversión a la media más fuertes.
Estos métodos forman la columna vertebral estadística de nuestro sistema de detección de regímenes y proporcionan medidas objetivas del comportamiento del mercado que utilizaremos para clasificar los regímenes.
Cálculos de percentiles
Por último, implementemos métodos para calcular percentiles y la media:
double CStatistics::Percentile(double percentile) { if(m_dataSize <= 0 || percentile < 0.0 || percentile > 100.0) return 0.0; // Sort data if needed if(!m_isSorted) { ArrayResize(m_sortedData, m_dataSize); for(int i = 0; i < m_dataSize; i++) m_sortedData[i] = m_data[i]; ArraySort(m_sortedData); m_isSorted = true; } // Calculate position double position = (percentile / 100.0) * (m_dataSize - 1); int lowerIndex = (int)MathFloor(position); int upperIndex = (int)MathCeil(position); // Handle edge cases if(lowerIndex == upperIndex) return m_sortedData[lowerIndex]; // Interpolate double fraction = position - lowerIndex; return m_sortedData[lowerIndex] + fraction * (m_sortedData[upperIndex] - m_sortedData[lowerIndex]); } double CStatistics::Median() { return Percentile(50.0); }
El método Percentile() calcula el valor por debajo del cual cae un porcentaje determinado de observaciones. Primero ordena los datos (si aún no están ordenados) y luego utiliza la interpolación lineal para encontrar el valor percentil preciso. El método Median() es una función de conveniencia que devuelve el percentil 50, que representa el valor medio del conjunto de datos.
Tenga en cuenta la optimización con el indicador m_isSorted, que garantiza que solo ordenemos los datos una vez, incluso si calculamos múltiples percentiles. Este es un ejemplo de cómo una implementación cuidadosa puede mejorar el rendimiento en el código MQL5.
Con nuestra clase de CStatistics completada, ahora tenemos un poderoso conjunto de herramientas para analizar datos de precios y detectar regímenes de mercado. En la siguiente sección, nos basaremos en esta base para crear la clase Detector de régimen de mercado.
Implementación del detector de régimen de mercado
Ahora que tenemos nuestra base estadística en su lugar, podemos construir el componente central de nuestro sistema: el detector de régimen de mercado. Esta clase utilizará las medidas estadísticas que hemos implementado para clasificar las condiciones del mercado en regímenes específicos.
Enumeración del régimen de mercado
Primero, definamos los tipos de régimen de mercado que nuestro sistema identificará. Crearemos un archivo separado llamado MarketRegimeEnum.mqh para garantizar que la definición de enumeración esté disponible para todos los componentes de nuestro sistema:
// Define market regime types enum ENUM_MARKET_REGIME { REGIME_TRENDING_UP = 0, // Trending up regime REGIME_TRENDING_DOWN = 1, // Trending down regime REGIME_RANGING = 2, // Ranging/sideways regime REGIME_VOLATILE = 3, // Volatile/chaotic regime REGIME_UNDEFINED = 4 // Undefined regime (default) };
Esta enumeración define los cinco posibles regímenes de mercado que nuestro sistema puede detectar. Utilizaremos estos valores a lo largo de nuestra implementación para representar el estado actual del mercado.
La clase CMarketRegimeDetector
La clase Detector de régimen de mercado combina nuestras herramientas estadísticas con la lógica de clasificación de regímenes. Examinemos su estructura:
class CMarketRegimeDetector { private: // Configuration int m_lookbackPeriod; // Period for calculations int m_smoothingPeriod; // Period for smoothing regime transitions double m_trendThreshold; // Threshold for trend detection double m_volatilityThreshold; // Threshold for volatility detection // Data buffers double m_priceData[]; // Price data buffer double m_returns[]; // Returns data buffer double m_volatility[]; // Volatility buffer double m_trendStrength[]; // Trend strength buffer double m_regimeBuffer[]; // Regime classification buffer // Statistics objects CStatistics m_priceStats; // Statistics for price data CStatistics m_returnsStats; // Statistics for returns data CStatistics m_volatilityStats; // Statistics for volatility data // Current state ENUM_MARKET_REGIME m_currentRegime; // Current detected regime // Helper methods void CalculateReturns(); void CalculateVolatility(); void CalculateTrendStrength(); ENUM_MARKET_REGIME DetermineRegime(); public: // Constructor and destructor CMarketRegimeDetector(int lookbackPeriod = 100, int smoothingPeriod = 10); ~CMarketRegimeDetector(); // Configuration methods void SetLookbackPeriod(int period); void SetSmoothingPeriod(int period); void SetTrendThreshold(double threshold); void SetVolatilityThreshold(double threshold); // Processing methods bool Initialize(); bool ProcessData(const double &price[], int size); // Access methods ENUM_MARKET_REGIME GetCurrentRegime() const { return m_currentRegime; } string GetRegimeDescription() const; double GetTrendStrength() const; double GetVolatility() const; // Buffer access for indicators bool GetRegimeBuffer(double &buffer[]) const; bool GetTrendStrengthBuffer(double &buffer[]) const; bool GetVolatilityBuffer(double &buffer[]) const; };
Esta clase encapsula toda la funcionalidad necesaria para detectar regímenes de mercado. Implementemos cada método en detalle.
Constructor y Destructor
Primero, implementemos el constructor y el destructor:
CMarketRegimeDetector::CMarketRegimeDetector(int lookbackPeriod, int smoothingPeriod) { // Set default parameters m_lookbackPeriod = (lookbackPeriod > 20) ? lookbackPeriod : 100; m_smoothingPeriod = (smoothingPeriod > 0) ? smoothingPeriod : 10; m_trendThreshold = 0.2; m_volatilityThreshold = 1.5; // Initialize current regime m_currentRegime = REGIME_UNDEFINED; // Initialize buffers ArrayResize(m_priceData, m_lookbackPeriod); ArrayResize(m_returns, m_lookbackPeriod - 1); ArrayResize(m_volatility, m_lookbackPeriod - 1); ArrayResize(m_trendStrength, m_lookbackPeriod - 1); ArrayResize(m_regimeBuffer, m_lookbackPeriod); // Initialize buffers with zeros ArrayInitialize(m_priceData, 0.0); ArrayInitialize(m_returns, 0.0); ArrayInitialize(m_volatility, 0.0); ArrayInitialize(m_trendStrength, 0.0); ArrayInitialize(m_regimeBuffer, (double)REGIME_UNDEFINED); } CMarketRegimeDetector::~CMarketRegimeDetector() { // Free memory (not strictly necessary in MQL5, but good practice) ArrayFree(m_priceData); ArrayFree(m_returns); ArrayFree(m_volatility); ArrayFree(m_trendStrength); ArrayFree(m_regimeBuffer); }
El constructor inicializa todas las variables miembro y matrices con valores predeterminados. Incluye validación de parámetros para garantizar que el período de retrospección sea de al menos 20 barras (para significancia estadística) y que el período de suavizado sea positivo. El destructor libera la memoria asignada para las matrices, lo que es una buena práctica incluso aunque MQL5 tenga recolección de basura automática.
Métodos de configuración
A continuación, implementemos los métodos de configuración que permiten a los usuarios personalizar el comportamiento del detector:
void CMarketRegimeDetector::SetLookbackPeriod(int period) { if(period <= 20) return; m_lookbackPeriod = period; // Resize buffers ArrayResize(m_priceData, m_lookbackPeriod); ArrayResize(m_returns, m_lookbackPeriod - 1); ArrayResize(m_volatility, m_lookbackPeriod - 1); ArrayResize(m_trendStrength, m_lookbackPeriod - 1); ArrayResize(m_regimeBuffer, m_lookbackPeriod); // Re-initialize Initialize(); } void CMarketRegimeDetector::SetSmoothingPeriod(int period) { if(period <= 0) return; m_smoothingPeriod = period; } void CMarketRegimeDetector::SetTrendThreshold(double threshold) { if(threshold <= 0.0) return; m_trendThreshold = threshold; } void CMarketRegimeDetector::SetVolatilityThreshold(double threshold) { if(threshold <= 0.0) return; m_volatilityThreshold = threshold; }
Estos métodos permiten a los usuarios personalizar los parámetros del detector para adaptarlos a sus instrumentos comerciales y marcos temporales específicos. El método SetLookbackPeriod() es particularmente importante ya que redimensiona todos los buffers internos para que coincidan con el nuevo período. Los otros métodos simplemente actualizan los parámetros correspondientes después de validar los valores de entrada.
Métodos de inicialización y procesamiento
Ahora, implementemos los métodos de inicialización y procesamiento de datos:
bool CMarketRegimeDetector::Initialize() { // Initialize buffers with zeros ArrayInitialize(m_priceData, 0.0); ArrayInitialize(m_returns, 0.0); ArrayInitialize(m_volatility, 0.0); ArrayInitialize(m_trendStrength, 0.0); ArrayInitialize(m_regimeBuffer, (double)REGIME_UNDEFINED); // Reset current regime m_currentRegime = REGIME_UNDEFINED; return true; } bool CMarketRegimeDetector::ProcessData(const double &price[], int size) { if(size < m_lookbackPeriod) return false; // Copy the most recent price data for(int i = 0; i < m_lookbackPeriod; i++) m_priceData[i] = price[size - m_lookbackPeriod + i]; // Calculate returns, volatility, and trend strength CalculateReturns(); CalculateVolatility(); CalculateTrendStrength(); // Determine the current market regime m_currentRegime = DetermineRegime(); // Update regime buffer for indicator display for(int i = 0; i < m_lookbackPeriod - 1; i++) m_regimeBuffer[i] = m_regimeBuffer[i + 1]; m_regimeBuffer[m_lookbackPeriod - 1] = (double)m_currentRegime; return true; }
El método Initialize() restablece todos los buffers y el régimen actual a sus valores predeterminados. El método ProcessData() es el corazón del detector, procesa nuevos datos de precios y actualiza la clasificación del régimen. Primero copia los datos de precios más recientes, luego calcula los rendimientos, la volatilidad y la fuerza de la tendencia y, finalmente, determina el régimen actual del mercado. También actualiza el buffer de régimen para la visualización del indicador, cambiando los valores para dejar espacio para el nuevo régimen.
Métodos de cálculo
Implementemos los métodos de cálculo que calculan las medidas estadísticas utilizadas para la detección del régimen:
void CMarketRegimeDetector::CalculateReturns() { for(int i = 0; i < m_lookbackPeriod - 1; i++) { // Calculate percentage returns if(m_priceData[i] != 0.0) m_returns[i] = (m_priceData[i + 1] - m_priceData[i]) / m_priceData[i] * 100.0; else m_returns[i] = 0.0; } // Update returns statistics m_returnsStats.SetData(m_returns, m_lookbackPeriod - 1); } void CMarketRegimeDetector::CalculateVolatility() { // Use a rolling window for volatility calculation int windowSize = MathMin(20, m_lookbackPeriod - 1); for(int i = 0; i < m_lookbackPeriod - 1; i++) { if(i < windowSize - 1) { m_volatility[i] = 0.0; continue; } double sum = 0.0; double mean = 0.0; // Calculate mean for(int j = 0; j < windowSize; j++) mean += m_returns[i - j]; mean /= windowSize; // Calculate standard deviation for(int j = 0; j < windowSize; j++) sum += MathPow(m_returns[i - j] - mean, 2); m_volatility[i] = MathSqrt(sum / (windowSize - 1)); } // Update volatility statistics m_volatilityStats.SetData(m_volatility, m_lookbackPeriod - 1); } void CMarketRegimeDetector::CalculateTrendStrength() { // Use a rolling window for trend strength calculation int windowSize = MathMin(50, m_lookbackPeriod - 1); for(int i = 0; i < m_lookbackPeriod - 1; i++) { if(i < windowSize - 1) { m_trendStrength[i] = 0.0; continue; } double window[]; ArrayResize(window, windowSize); // Copy data to window for(int j = 0; j < windowSize; j++) window[j] = m_returns[i - j]; // Create temporary statistics object CStatistics tempStats; tempStats.SetData(window, windowSize); // Calculate trend strength using autocorrelation m_trendStrength[i] = tempStats.TrendStrength(); } // Update price statistics m_priceStats.SetData(m_priceData, m_lookbackPeriod); }Estos métodos calculan las medidas estadísticas clave utilizadas para la detección del régimen:
- CalculateReturns() calcula retornos porcentuales a partir de los datos de precios, que son más adecuados para el análisis estadístico que los precios brutos.
- CalculateVolatility() utiliza un enfoque de ventana móvil para calcular la desviación estándar de los rendimientos en cada punto del tiempo, proporcionando una medida de la volatilidad del mercado.
- CalculateTrendStrength() también utiliza un enfoque de ventana móvil, pero crea un objeto CStatistics temporal para cada ventana y utiliza su método TrendStrength() para calcular la fuerza de la tendencia basada en la autocorrelación.
Estos cálculos de ventana móvil proporcionan una evaluación más precisa y ágil de las condiciones del mercado que el uso de todo el período retrospectivo para cada cálculo.
Clasificación del régimen
El corazón de nuestro sistema es el método DetermineRegime(), que clasifica el estado actual del mercado basándose en medidas estadísticas:
ENUM_MARKET_REGIME CMarketRegimeDetector::DetermineRegime()
{
// Get the latest values
double latestTrendStrength = m_trendStrength[m_lookbackPeriod - 2];
double latestVolatility = m_volatility[m_lookbackPeriod - 2];
// Get the average volatility for comparison
double avgVolatility = 0.0;
int count = 0;
for(int i = m_lookbackPeriod - 22; i < m_lookbackPeriod - 2; i++)
{
if(i >= 0)
{
avgVolatility += m_volatility[i];
count++;
}
}
if(count > 0)
avgVolatility /= count;
else
avgVolatility = latestVolatility;
// Determine price direction
double priceChange = m_priceData[m_lookbackPeriod - 1] - m_priceData[m_lookbackPeriod - m_smoothingPeriod - 1];
// Classify the regime
if(latestVolatility > avgVolatility * m_volatilityThreshold)
{
// Highly volatile market
return REGIME_VOLATILE;
}
else if(MathAbs(latestTrendStrength) > m_trendThreshold)
{
// Trending market
if(priceChange > 0)
return REGIME_TRENDING_UP;
else
return REGIME_TRENDING_DOWN;
}
else
{
// Ranging market
return REGIME_RANGING;
}
}
Este método implementa un enfoque de clasificación jerárquica: - En primer lugar, verifica si el mercado es altamente volátil comparando la volatilidad más reciente con la volatilidad promedio de las últimas 20 barras. Si la volatilidad supera el umbral, el mercado se clasifica como volátil.
- Si el mercado no es volátil, verifica si hay una tendencia significativa comparando la fuerza absoluta de la tendencia con el umbral de tendencia. Si se detecta una tendencia, se determina la dirección (hacia arriba o hacia abajo) en función del cambio de precio durante el período de suavizado.
- Si no se detecta ni volatilidad ni tendencia, el mercado se clasifica como en rango.
Este enfoque jerárquico garantiza que la volatilidad tenga prioridad sobre la detección de tendencias, ya que las estrategias de seguimiento de tendencias son particularmente vulnerables en mercados volátiles.
Métodos de acceso
Por último, implementemos los métodos de acceso que proporcionan información sobre el régimen actual del mercado:
string CMarketRegimeDetector::GetRegimeDescription() const { switch(m_currentRegime) { case REGIME_TRENDING_UP: return "Trending Up"; case REGIME_TRENDING_DOWN: return "Trending Down"; case REGIME_RANGING: return "Ranging"; case REGIME_VOLATILE: return "Volatile"; default: return "Undefined"; } } double CMarketRegimeDetector::GetTrendStrength() const { if(m_lookbackPeriod <= 2) return 0.0; return m_trendStrength[m_lookbackPeriod - 2]; } double CMarketRegimeDetector::GetVolatility() const { if(m_lookbackPeriod <= 2) return 0.0; return m_volatility[m_lookbackPeriod - 2]; } bool CMarketRegimeDetector::GetRegimeBuffer(double &buffer[]) const { if(ArraySize(buffer) < m_lookbackPeriod) ArrayResize(buffer, m_lookbackPeriod); for(int i = 0; i < m_lookbackPeriod; i++) buffer[i] = m_regimeBuffer[i]; return true; } bool CMarketRegimeDetector::GetTrendStrengthBuffer(double &buffer[]) const { int size = m_lookbackPeriod - 1; if(ArraySize(buffer) < size) ArrayResize(buffer, size); for(int i = 0; i < size; i++) buffer[i] = m_trendStrength[i]; return true; } bool CMarketRegimeDetector::GetVolatilityBuffer(double &buffer[]) const { int size = m_lookbackPeriod - 1; if(ArraySize(buffer) < size) ArrayResize(buffer, size); for(int i = 0; i < size; i++) buffer[i] = m_volatility[i]; return true; }Estos métodos permiten acceder al régimen actual y sus características:
- GetRegimeDescription() devuelve una descripción legible por humanos del régimen actual.
- GetTrendStrength() y GetVolatility() devuelven los últimos valores de fuerza de tendencia y volatilidad.
- GetRegimeBuffer(), GetTrendStrengthBuffer() y GetVolatilityBuffer() copian los buffers internos a matrices externas, lo que resulta útil para la visualización de indicadores.
Con nuestra clase CMarketRegimeDetector completa, ahora tenemos una herramienta poderosa para detectar regímenes de mercado. En la siguiente sección, crearemos un indicador personalizado que visualice estos regímenes directamente en el gráfico de precios.
Creación de un indicador personalizado para la visualización del régimen
Ahora que tenemos nuestra clase detector de régimen de mercado, creemos un indicador personalizado que visualice los regímenes detectados directamente en el gráfico de precios. Esto proporcionará a los operadores una forma intuitiva de ver los cambios de régimen y adaptar sus estrategias en consecuencia.
El indicador de régimen de mercado
Nuestro indicador personalizado mostrará el régimen actual del mercado, la fuerza de la tendencia y la volatilidad directamente en el gráfico. Aquí está la implementación:#property indicator_chart_window #property indicator_buffers 3 #property indicator_plots 3 // Include the Market Regime Detector #include "MarketRegimeEnum.mqh" #include "MarketRegimeDetector.mqh" // Indicator input parameters input int LookbackPeriod = 100; // Lookback period for calculations input int SmoothingPeriod = 10; // Smoothing period for regime transitions input double TrendThreshold = 0.2; // Threshold for trend detection (0.1-0.5) input double VolatilityThreshold = 1.5; // Threshold for volatility detection (1.0-3.0) // Indicator buffers double RegimeBuffer[]; // Buffer for regime classification double TrendStrengthBuffer[]; // Buffer for trend strength double VolatilityBuffer[]; // Buffer for volatility // Global variables CMarketRegimeDetector *Detector = NULL;El indicador utiliza tres buffers para almacenar y mostrar diferentes aspectos de los regímenes de mercado:
- RegimeBuffer: Almacena la representación numérica del régimen actual.
- TrendStrengthBuffer: Almacena los valores de fuerza de la tendencia.
- VolatilityBuffer: Almacena los valores de volatilidad.
Inicialización del indicador
La función OnInit() configura los buffers del indicador y crea el detector de régimen de mercado:
int OnInit() { // Set indicator buffers SetIndexBuffer(0, RegimeBuffer, INDICATOR_DATA); SetIndexBuffer(1, TrendStrengthBuffer, INDICATOR_DATA); SetIndexBuffer(2, VolatilityBuffer, INDICATOR_DATA); // Set indicator labels PlotIndexSetString(0, PLOT_LABEL, "Market Regime"); PlotIndexSetString(1, PLOT_LABEL, "Trend Strength"); PlotIndexSetString(2, PLOT_LABEL, "Volatility"); // Set indicator styles PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_LINE); PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_LINE); PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_LINE); // Set line colors PlotIndexSetInteger(1, PLOT_LINE_COLOR, clrBlue); PlotIndexSetInteger(2, PLOT_LINE_COLOR, clrRed); // Set line styles PlotIndexSetInteger(1, PLOT_LINE_STYLE, STYLE_SOLID); PlotIndexSetInteger(2, PLOT_LINE_STYLE, STYLE_SOLID); // Set line widths PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 1); PlotIndexSetInteger(2, PLOT_LINE_WIDTH, 1); // Create and initialize the Market Regime Detector Detector = new CMarketRegimeDetector(LookbackPeriod, SmoothingPeriod); if(Detector == NULL) { Print("Failed to create Market Regime Detector"); return INIT_FAILED; } // Configure the detector Detector.SetTrendThreshold(TrendThreshold); Detector.SetVolatilityThreshold(VolatilityThreshold); Detector.Initialize(); // Set indicator name IndicatorSetString(INDICATOR_SHORTNAME, "Market Regime Detector"); return INIT_SUCCEEDED; }Esta función realiza varias tareas importantes:
- Vincula los buffers indicadores a las matrices correspondientes.
- Establece las propiedades visuales del indicador (etiquetas, estilos, colores).
- Crea y configura el detector de régimen de mercado con los parámetros especificados por el usuario.
El uso de SetIndexBuffer() y varias funciones PlotIndexSetXXX() es una práctica estándar en el desarrollo de indicadores MQL5. Estas funciones configuran cómo se mostrará el indicador en el gráfico.
Cálculo del indicador
La función OnCalculate() procesa los datos de precios y actualiza los buffers del indicador:
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { // Check if there's enough data if(rates_total < LookbackPeriod) return 0; // Process data with the detector if(!Detector.ProcessData(close, rates_total)) { Print("Failed to process data with Market Regime Detector"); return 0; } // Get the regime buffer Detector.GetRegimeBuffer(RegimeBuffer); // Get the trend strength buffer Detector.GetTrendStrengthBuffer(TrendStrengthBuffer); // Get the volatility buffer Detector.GetVolatilityBuffer(VolatilityBuffer); // Display current regime in the chart corner string regimeText = "Current Market Regime: " + Detector.GetRegimeDescription(); string trendText = "Trend Strength: " + DoubleToString(Detector.GetTrendStrength(), 4); string volatilityText = "Volatility: " + DoubleToString(Detector.GetVolatility(), 4); Comment(regimeText + "\n" + trendText + "\n" + volatilityText); // Return the number of calculated bars return rates_total; }Esta función:
- Comprueba si hay suficientes datos para el cálculo.
- Procesa los datos de precios con el detector de régimen de mercado
- Recupera el régimen, la fuerza de la tendencia y los amortiguadores de volatilidad.
- Muestra la información del régimen actual en la esquina del gráfico.
- Devuelve el número de barras calculadas
La plataforma llama a la función OnCalculate() siempre que haya nuevos datos de precios disponibles o cuando se desplaza el gráfico. Es responsable de actualizar los buffers del indicador, que luego se muestran en el gráfico.
Limpieza de indicadores
La función OnDeinit() garantiza una limpieza adecuada cuando se elimina el indicador:
void OnDeinit(const int reason) { // Clean up if(Detector != NULL) { delete Detector; Detector = NULL; } // Clear the comment Comment(""); }
Esta función elimina el objeto Detector de régimen de mercado para evitar pérdidas de memoria y borra todos los comentarios del gráfico. Una limpieza adecuada es esencial en la programación MQL5 para garantizar que los recursos se liberen cuando ya no sean necesarios.
Interpretación del indicador
Al utilizar el indicador de régimen de mercado, los operadores deben prestar atención a lo siguiente:- Línea de régimen: Esta línea representa el régimen actual del mercado. Los valores numéricos corresponden a diferentes regímenes:
- 0: Tendencia al alza
- 1: Tendencia a la baja
- 2: Rango
- 3: Volatilidad
- 4: Indefinido
- Línea de fuerza de tendencia: Esta línea azul muestra la fuerza de la tendencia. Los valores positivos más altos indican tendencias alcistas más fuertes, mientras que los valores negativos más bajos indican tendencias bajistas más fuertes. Los valores cercanos a cero sugieren una tendencia débil o nula.
- Línea de volatilidad: Esta línea roja muestra el nivel de volatilidad actual. Los picos en esta línea a menudo preceden cambios de régimen y pueden indicar posibles oportunidades o riesgos comerciales.
- Comentario del gráfico: El indicador muestra el régimen actual, la fuerza de la tendencia y los valores de volatilidad en la esquina superior izquierda del gráfico para una fácil referencia.
Al monitorear estos elementos, los operadores pueden identificar rápidamente el régimen actual del mercado y ajustar sus estrategias en consecuencia. Por ejemplo, se deben emplear estrategias de seguimiento de tendencias durante regímenes de tendencias, mientras que las estrategias de reversión a la media son más apropiadas durante regímenes de rango. Durante regímenes volátiles, los traders podrían considerar reducir el tamaño de sus posiciones o mantenerse fuera del mercado por completo.


Aquí es donde podemos ver claramente que el mercado actual está oscilando, como también podemos observar claramente en el gráfico.
Conclusión
A lo largo de este artículo, nos hemos embarcado en un viaje para resolver uno de los problemas más desafiantes en el trading algorítmico: la adaptación a las condiciones cambiantes del mercado. Comenzamos reconociendo que los mercados no se comportan de manera uniforme a lo largo del tiempo, sino que transitan entre distintos estados de comportamiento o "regímenes". Esta perspectiva nos llevó a desarrollar un Sistema Integral de Detección de Regímenes de Mercado que puede identificar estas transiciones. En la siguiente sección, veremos cómo adaptar las estrategias de trading según nuestra detección de los regímenes.
El viaje del problema a la solución
Cuando comenzamos, identificamos una brecha crítica en la mayoría de los sistemas comerciales: la incapacidad de clasificar objetivamente las condiciones del mercado y adaptarnos a ellas. Los indicadores y estrategias tradicionales suelen estar optimizados para condiciones específicas del mercado, lo que genera un rendimiento inconsistente a medida que los mercados evolucionan. Este es el problema al que se enfrentan los traders a diario: las estrategias que funcionan brillantemente en un entorno de mercado pueden fallar espectacularmente en otro.
Nuestra solución a este problema fue construir un sistema de detección del régimen de mercado sólido desde cero. Comenzamos con una base estadística sólida, implementando medidas clave como la autocorrelación y la volatilidad que pueden clasificar objetivamente el comportamiento del mercado. Luego desarrollamos una clase integral de Detector de régimen de mercado que utiliza estas medidas estadísticas para identificar condiciones de mercado con tendencias, rangos y volatilidad.
Finalmente, para que este sistema sea práctico y accesible, creamos un indicador personalizado que visualiza los cambios de régimen directamente en el gráfico de precios, brindando a los operadores retroalimentación visual inmediata sobre las condiciones actuales del mercado. Luego demostramos cómo construir un Asesor Experto adaptativo que selecciona y aplica automáticamente diferentes estrategias comerciales según el régimen detectado.
Ahora, en la siguiente parte del artículo, exploraremos consideraciones prácticas para implementar y optimizar el sistema, incluida la optimización de parámetros, el manejo de la transición de régimen y la integración con los sistemas comerciales existentes. Estos conocimientos prácticos le ayudarán a implementar el Sistema de detección del régimen de mercado de forma eficaz en sus propias operaciones de forma automática. Mientras tanto, juega con el enfoque manual del indicador por ahora.
Descripción general del archivo
Aquí hay un resumen de todos los archivos creados en este artículo: | Nombre del archivo | Descripción |
|---|---|
| MarketRegimeEnum.mqh | Define los tipos de enumeración del régimen de mercado utilizados en todo el sistema. |
| CStatistics.mqh | Clase de cálculos estadísticos para la detección del régimen de mercado. |
| MarketRegimeDetector.mqh | Implementación de la detección del régimen central del mercado. |
| MarketRegimeIndicator.mq5 | Indicador personalizado para visualizar regímenes en gráficos. |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17737
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Utilizando redes neuronales en MetaTrader
Trading por pares: Trading algorítmico con optimización automática en la diferencia de puntuación Z
Particularidades del trabajo con números del tipo double en MQL4
Redes neuronales en el trading: Actor—Director—Crítico (Final)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Tu código no compila.... falta IsStrongSignal(valor) ...
¿A qué archivo se refiere?
El indicador de régimen de mercado tiene 24 errores y 1 advertencia cuando intento compilar.:
'MarketRegimeIndicator.mq5' 1
file 'C:\Users\rauma\AppData\Roaming\MetaQuotes\Terminal\10CE948A1DFC9A8C27E56E827008EBD4\MQL5\IncludeMarketRegimeEnum.mqh' not found MarketRegimeIndicator.mq5 14 11
archivo 'C:\sers\rauma\AppData\Roaming\MetaQuotes\Terminal\10CE948A1DFC9A8C27E56E827008EBD4\MQL5\IncludeMarketRegimeDetector.mqh' no encontrado MarketRegimeIndicator.mq5 15 11
CMarketRegimeDetector' - token inesperado, probablemente falta el tipo MarketRegimeIndicator.mq5 29 1
'*' - punto y coma esperado MarketRegimeIndicator.mq5 29 23
Detector" - identificador no declarado MarketRegimeIndicator.mq5 64 5
CMarketRegimeDetector" - declaración sin tipo MarketRegimeIndicator.mq5 64 20
CMarketRegimeDetector' - tipo de clase esperado MarketRegimeIndicator.mq5 64 20
función no definida MarketRegimeIndicator.mq5 64 20
new' - expresión de tipo 'void' es ilegal MarketRegimeIndicator.mq5 64 16
'=' - uso de operación ilegal MarketRegimeIndicator.mq5 64 14
Detector' - identificador no declarado MarketRegimeIndicator.mq5 65 8
'==' - uso de operación ilegal MarketRegimeIndicator.mq5 65 17
Detector" - identificador no declarado MarketRegimeIndicator.mq5 72 5
Detector" - identificador no declarado MarketRegimeIndicator.mq5 73 5
Detector" - identificador no declarado MarketRegimeIndicator.mq5 74 5
Detector" - identificador no declarado MarketRegimeIndicator.mq5 101 9
';' - token inesperado MarketRegimeIndicator.mq5 103 68
'(' - paréntesis izquierdo desequilibrado MarketRegimeIndicator.mq5 101 7
declaración controlada vacía encontrada MarketRegimeIndicator.mq5 103 68
Detector' - identificador no declarado MarketRegimeIndicator.mq5 133 8
'!=' - uso de operación ilegal MarketRegimeIndicator.mq5 133 17
Detector' - identificador no declarado MarketRegimeIndicator.mq5 135 16
Detector' - puntero de objeto esperado MarketRegimeIndicator.mq5 135 16
Detector' - identificador no declarado MarketRegimeIndicator.mq5 136 9
'=' - uso de operación ilegal MarketRegimeIndicator.mq5 136 18
24 errores, 1 advertencias 25 2
El indicador de régimen de mercado tiene 24 errores y 1 advertencia cuando intento compilar.:
'MarketRegimeIndicator.mq5' 1
archivo 'C:\sers\rauma\AppData\Roaming\MetaQuotes\Terminal\10CE948A1DFC9A8C27E56E827008EBD4\MQL5\IncludeMarketRegimeEnum.mqh' no encontrado MarketRegimeIndicator.mq5 14 11
archivo 'C:\sers\rauma\AppData\Roaming\MetaQuotes\Terminal\10CE948A1DFC9A8C27E56E827008EBD4\MQL5\IncludeMarketRegimeDetector.mqh' no encontrado MarketRegimeIndicator.mq5 15 11
El indicador busca estos archivos en la carpeta C:\Users\rauma\AppData\Roaming\MetaQuotes\Terminal\10CE948A1DFC9A8C27E56E827008EBD4\MQL5\Include\
¿A qué archivo se refiere?
en la línea 472
Supongo que se refiere a
'IsStrongSignal' - identificador no declarado MarketRegimeDetector.mqh 472 16