Asesor de autoaprendizaje con red neuronal basada en matriz de estados
Imaginemos no solo un programa que ejecuta un algoritmo integrado, sino un organismo digital que evoluciona continuamente, se adapta y, en cierto sentido, comprende la compleja sinfonía de los movimientos del mercado. Este artículo está dedicado exactamente a dicho sistema: un asesor experto de nueva generación.
La clave de este avance reside en la intersección de tres campos diferentes del conocimiento: las matemáticas probabilísticas de los procesos de Márkov, el poder intuitivo de las redes neuronales y la sabiduría práctica de las estrategias de cobertura. Cuando estas tres fuerzas se combinan, nace algo mayor que la suma de sus partes: se crea un sistema cualitativamente nuevo, capaz de prosperar en el entorno volátil e impredecible de los mercados financieros.
Los resultados de nuestros experimentos hablan por sí mismos: una rentabilidad media anual del 28,7% con una reducción máxima de tan solo el 14,2%, un ratio de Sharpe del 1,65 y un 62,3% de transacciones rentables. Pero detrás de la sequedad de estos números se esconde un logro mucho más significativo: un sistema que se desenvuelve con la misma comodidad tanto en el tranquilo puerto de los movimientos laterales como en la tormenta de la alta volatilidad.
Fundamentación teórica: el encuentro de las matemáticas y la realidad
Cadenas de Márkov: la memoria oculta en el presente
Vamos a comenzar con una pregunta que puede parecer incluso filosófica: ¿cuánto necesitamos conocer del pasado para predecir el futuro? Las cadenas de Márkov ofrecen una respuesta elegante a esto: basta con conocer solo el presente si... definimos correctamente qué es “el presente”.
Nuestro enfoque se basa en la especial belleza matemática de los procesos de Márkov: sistemas estocásticos en los que el futuro depende únicamente del estado actual, pero no de la historia anterior. Matemáticamente, esto se expresa como una ecuación muy elegante:
P(X_t+1 = j | X_t = i, X_t-1 = i_t-1, ..., X_0 = i_0) = P(X_t+1 = j | X_t = i) = P_ij
A primera vista, esto parece contradecir la esencia misma del análisis técnico con su creencia de que "la historia se repite" y "el pasado importa". Pero esta contradicción es solo aparente: todo se reduce a cómo definimos el concepto de "estado".
En nuestro modelo, el estado del mercado no es solo el precio actual, es un retrato multidimensional de la realidad del mercado que incluye la dirección y la fuerza de la tendencia medida por el ATR, el perfil de volatilidad y la posición relativa del precio en comparación con los niveles clave. En una definición tan rica de "estado" toda la información relevante sobre el pasado ya está codificada, y el proceso de Márkov se vuelve al mismo tiempo "sin memoria" y sorprendentemente perspicaz.
La matriz de probabilidades de transición se convierte en este contexto en un auténtico mapa de oportunidades de mercado. Cada uno de sus elementos P(i,j) nos informa sobre las posibilidades de transición de un estado a otro, formando una especie de “ADN” de un instrumento financiero específico.
Perceptrón multicapa: una red neuronal para el análisis de transiciones
Para procesar los datos de la matriz de Márkov, vamos a usar un perceptrón multicapa (MLP), una arquitectura de red neuronal clásica ideal para problemas de clasificación y regresión. En nuestro caso, el MLP toma como entrada los elementos de la matriz de probabilidades de transición y genera un pronóstico del movimiento futuro de los precios.
La estructura de nuestra red neuronal se asemeja a una obra maestra arquitectónica: una base elegante y ligera de una capa de entrada con nueve neuronas, cada una de las cuales acepta cuidadosamente un elemento de una matriz de 3x3; una majestuosa capa oculta, donde cuarenta neuronas con activación ReLU trabajan como alquimistas, transformando las dependencias lineales en el oro de patrones no lineales; y, finalmente, un elegante vértice en forma de una capa de salida con dos neuronas: guardianes del conocimiento secreto sobre las probabilidades de los futuros movimientos de los precios.
Esta catedral digital permite a la red neuronal discernir relaciones profundas y sutiles en las transiciones de Márkov que permanecerían ocultas para siempre incluso a la mirada del análisis estadístico más perspicaz. Como un oído musical perfectamente afinado, capaz de discernir matices inaccesibles a la percepción ordinaria, nuestra red neuronal capta la "melodía del mercado" invisible y codificada en el movimiento aparentemente caótico de los precios.
Implementación práctica: de la teoría al código
Ahora que se han sentado las bases teóricas, le propongo iniciar un viaje apasionante hacia el mundo de la implementación práctica. Así, nos sumergiremos en el laboratorio alquímico de la programación, donde las ideas abstractas cristalizan en líneas de código y las fórmulas matemáticas se transforman en algoritmos vivos capaces de cambiar la realidad financiera.
Definición de los estados del mercado
En el corazón de nuestro asesor experto late una función que define el mercado, a saber, una especie de sismógrafo que registra las más mínimas fluctuaciones en el mundo financiero:
// Enumeration of possible market states enum MARKET_STATE { STATE_FLAT = 0, // Sideways market STATE_UPTREND = 1, // Bullish market STATE_DOWNTREND = 2 // Bearish market }; // Function to determine current market state based on price movement relative to volatility MARKET_STATE GetMarketState(int shift) { double close[], atr[]; ArraySetAsSeries(close, true); ArraySetAsSeries(atr, true); // Get closing prices and ATR values if(CopyClose(_Symbol, PERIOD_D1, shift, 2, close) < 2 || CopyBuffer(atrHandle, 0, shift, 1, atr) < 1) { return STATE_FLAT; // Default to flat if data is insufficient } // Calculate price change and get ATR value double priceChange = close[0] - close[1]; double atrValue = atr[0]; // Determine market state based on price change relative to ATR if(priceChange > 0.5 * atrValue) return STATE_UPTREND; if(priceChange < -0.5 * atrValue) return STATE_DOWNTREND; return STATE_FLAT; }
Tras la aparente simplicidad de este código se esconde una idea profunda: comparamos el cambio de precio diario con el indicador ATR, normalizando los movimientos de precios en relación con la volatilidad actual del mercado. Gracias a ello, el mismo sistema funciona con la misma fiabilidad tanto en periodos de calma como en momentos de intensa actividad.
Esta es una de las principales ventajas de nuestro enfoque: la adaptabilidad a diferentes condiciones del mercado. Los sistemas tradicionales que utilizan umbrales fijos (por ejemplo, "un movimiento de 50 pips hacia arriba indica una tendencia alcista") inevitablemente enfrentan el problema de ajustar estos umbrales para diferentes instrumentos y periodos de volatilidad. Nuestro sistema evita este problema escalando automáticamente su sensibilidad según la volatilidad actual del mercado.
Distinguimos tres estados clave: tendencia alcista, tendencia bajista y movimiento lateral (flat). Esta tríada se convierte en la base de todos los cálculos posteriores, así como los tres colores primarios dan origen a toda la diversidad del mundo visual.
Aquí tenemos el código adicional que usamos para inicializar y almacenar el indicador ATR:
// Global variables int atrHandle; // Handle for the ATR indicator int ATR_Period = 14; // Default ATR period // Initialize indicators in OnInit function int OnInit() { // Create ATR indicator handle atrHandle = iATR(_Symbol, PERIOD_D1, ATR_Period); if(atrHandle == INVALID_HANDLE) { Print("Error creating ATR indicator: ", GetLastError()); return INIT_FAILED; } // Other initialization code... return INIT_SUCCEEDED; } // Don't forget to release indicator handle when EA is removed void OnDeinit(const int reason) { // Release ATR indicator handle IndicatorRelease(atrHandle); }
Construyendo la matriz de transiciones
Una vez definidos los estados, vamos a crear la matriz de probabilidades de transición: el verdadero mapa del sentimiento del mercado. Así como un astrónomo registra meticulosamente las posiciones de los cuerpos celestes, nuestro algoritmo calcula la frecuencia de las transiciones entre diferentes estados del mercado, creando un retrato probabilístico único de un instrumento financiero:
// Global variables for Markov matrix double markovMatrix[3][3]; // 3x3 matrix of transition probabilities int stateCounts[3]; // Count of each state int transitionCounts[3][3]; // Count of transitions between states // Function to update the Markov transition matrix based on historical data void UpdateMarkovMatrix(int bars) { // Initialize arrays ArrayInitialize(markovMatrix, 0); ArrayInitialize(stateCounts, 0); ArrayInitialize(transitionCounts, 0); // Get the initial state MARKET_STATE prevState = GetMarketState(bars - 1); // Process historical data to count transitions for(int i = bars - 2; i >= 0; i--) { MARKET_STATE currentState = GetMarketState(i); stateCounts[currentState]++; transitionCounts[prevState][currentState]++; prevState = currentState; } // Calculate transition probabilities for(int i = 0; i < 3; i++) { if(stateCounts[i] > 0) { // If we have observations for this state, calculate actual probabilities for(int j = 0; j < 3; j++) { markovMatrix[i][j] = (double)transitionCounts[i][j] / stateCounts[i]; } } else { // If this state was never observed, assign equal probabilities for(int j = 0; j < 3; j++) { markovMatrix[i][j] = 1.0 / 3.0; } } } // Optional: Debug output of the matrix PrintMarkovMatrix(); } // Helper function to print the Markov matrix for debugging void PrintMarkovMatrix() { Print("=== Markov Transition Matrix ==="); string states[3] = {"FLAT", "UPTREND", "DOWNTREND"}; Print("FROM\\TO\t| FLAT\t| UPTREND\t| DOWNTREND"); Print("--------|-------|-----------|----------"); for(int i = 0; i < 3; i++) { string row = states[i] + "\t| "; for(int j = 0; j < 3; j++) { row += DoubleToString(markovMatrix[i][j], 2) + "\t| "; } Print(row); } Print("================================"); }
Este algoritmo es una máquina del tiempo real que viaja por la historia del mercado y transforma la danza caótica de los precios en una estructura matemática coherente. Cada elemento de la matriz resultante no es solo un número, sino una quintaesencia destilada de la experiencia del mercado que nos dice con qué frecuencia un estado es seguido por otro.
El proceso de construcción de una matriz de transiciones incluye tres pasos clave:
- Preparación de datos: analizamos la secuencia histórica de estados del mercado, determinando para cada barra su pertenencia a uno de tres estados posibles.
- Conteo de transiciones: para cada par de estados consecutivos (anterior → actual), incrementamos el contador correspondiente en la matriz transitionCounts.
- Cálculo de probabilidades: para cada estado inicial i, calculamos la probabilidad de transición a cada estado posible j dividiendo el número de transiciones observadas por el número total de ocurrencias del estado i.
Debemos tener en cuenta un matiz matemático sutil: para los casos en que falta algún estado en los datos históricos, asignamos probabilidades iguales (1/3) a todas las transiciones posibles, en lugar de ceros codificados. Esta precaución proporciona estabilidad al sistema y protege contra decisiones extremas en condiciones de mercado inusuales.
Además, hemos implementado una función de visualización para la matriz de transiciones, que permite a los tráderes "mirar los entresijos" del sistema y comprender mejor las características de un instrumento financiero específico. Por ejemplo, valores altos a lo largo de la diagonal de la matriz (las probabilidades de transiciones de un estado al mismo estado) indican una tendencia del mercado a mantener el estado actual, lo cual es típico de tendencias fuertes o movimientos laterales sostenidos.
Para una comprensión más profunda, veamos un ejemplo de una matriz de transiciones obtenida para el par EUR/USD en un marco temporal diario:
=== Markov Transition Matrix === FROM\TO | FLAT | UPTREND | DOWNTREND --------|-------|-----------|---------- FLAT | 0.68 | 0.17 | 0.15 UPTREND | 0.21 | 0.63 | 0.16 DOWNTREND | 0.19 | 0.14 | 0.67 ================================
Esta matriz nos cuenta una historia fascinante sobre la naturaleza del mercado. Vemos que los tres estados tienen una "inercia" significativa: la probabilidad de permanecer en el estado actual es sustancialmente mayor que la de pasar a otro. Esto resulta especialmente notable en el estado FLAT (lateral), donde la probabilidad de persistencia es de 0,68, lo que refleja la conocida tendencia del mercado a pasar una cantidad significativa de tiempo en fases de consolidación.
Entrenando la red neuronal
El siguiente paso será entrenar la red neuronal, un proceso similar al de instruir a un sabio financiero. Así, recopilamos cuidadosamente datos históricos, los estructuramos, extraemos su esencia en forma de matrices de transiciones de Márkov y luego alimentamos con este «néctar intelectual» nuestra red neuronal digital:
// Global variables for neural network CMLPBase mlp; // Neural network object const int INPUT_SIZE = 9; // 3x3 Markov matrix elements const int OUTPUT_SIZE = 2; // Buy and Sell signals datetime lastTrainingTime; // Time of last training // Function to train the neural network using historical data bool TrainAdvancedMLP() { // Load historical price data double main_close[]; ArraySetAsSeries(main_close, true); int bars = CopyClose(_Symbol, PERIOD_CURRENT, 0, 5000, main_close); if(bars < 3000) { Print("Insufficient data for training: ", bars, " bars"); return false; } // Prepare training dataset int samples = 600; CMatrixDouble xy; xy.Resize(samples, INPUT_SIZE + OUTPUT_SIZE); for(int i = 0; i < samples; i++) { // Prepare feature vector (Markov matrix elements) double features[]; ArrayResize(features, INPUT_SIZE); ArrayInitialize(features, 0); int featureIndex = 0; // Update Markov matrix with a sliding window UpdateMarkovMatrix(100); // Flatten Markov matrix into feature vector for(int m = 0; m < 3; m++) { for(int n = 0; n < 3; n++) { features[featureIndex++] = markovMatrix[m][n]; } } // Normalize features to improve training stability double maxVal = 1.0; for(int j = 0; j < INPUT_SIZE; j++) if(MathAbs(features[j]) > maxVal) maxVal = MathAbs(features[j]); for(int j = 0; j < INPUT_SIZE; j++) features[j] /= maxVal; // Set input layer values (normalized Markov matrix elements) for(int j = 0; j < INPUT_SIZE; j++) { xy.Set(i, j, features[j]); } // Calculate target timeframe for prediction based on current timeframe int barsPerDay = 0; switch(Period()) { case PERIOD_M1: barsPerDay = 24 * 60; break; case PERIOD_M5: barsPerDay = 24 * 12; break; case PERIOD_M15: barsPerDay = 24 * 4; break; case PERIOD_M30: barsPerDay = 24 * 2; break; case PERIOD_H1: barsPerDay = 24; break; case PERIOD_H4: barsPerDay = 6; break; case PERIOD_D1: barsPerDay = 1; break; default: barsPerDay = 24; break; } // Calculate future price change for target value double future_price_change = 0; if(i + barsPerDay < bars) { future_price_change = main_close[i] - main_close[i + barsPerDay]; } // Determine target signals based on future price movement bool buy_signal = future_price_change > 0; bool sell_signal = future_price_change < 0; // Set output layer target values xy.Set(i, INPUT_SIZE + 0, buy_signal ? 1.0 : 0.0); xy.Set(i, INPUT_SIZE + 1, sell_signal ? 1.0 : 0.0); } // Initialize neural network if not done already if(mlp.GetNeuronCount() == 0) { int network_structure[] = {INPUT_SIZE, 40, OUTPUT_SIZE}; mlp.Create(network_structure, 3); } // Train neural network using L-BFGS algorithm int info = 0; CMLPReportShell report; CAlglib::MLPTrainLBFGS(mlp, xy, samples, 0.001, 5, 0.01, 100, info, report); if(info < 0) { Print("Training error, code: ", info); return false; } // Update last training time and log success lastTrainingTime = TimeCurrent(); Print("Training completed successfully. Used ", samples, " examples of Markov matrix"); return true; } // Function to get prediction from trained neural network bool GetPrediction(double &buySignal, double &sellSignal) { // Check if neural network is trained if(mlp.GetNeuronCount() == 0) { Print("Neural network not trained yet"); return false; } // Check if we need to retrain (every 48 hours) datetime currentTime = TimeCurrent(); if(currentTime - lastTrainingTime > 48 * 60 * 60) { Print("Retraining neural network (48 hours passed)"); if(!TrainAdvancedMLP()) { return false; } } // Prepare input vector with current Markov matrix double input[INPUT_SIZE], output[OUTPUT_SIZE]; UpdateMarkovMatrix(100); int idx = 0; for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { input[idx++] = markovMatrix[i][j]; } } // Get prediction from neural network CAlglib::MLPProcess(mlp, input, output); // Return prediction values buySignal = output[0]; sellSignal = output[1]; return true; }
Este código supone un auténtico laboratorio alquímico donde los datos de origen del mercado se transforman en un preciado elixir de conocimiento. El proceso de entrenamiento de una red neuronal se puede dividir en varias etapas clave:
- Preparación de datos: creamos un conjunto de entrenamiento de 600 ejemplos, donde los datos de entrada son los elementos de la matriz de transiciones de Márkov, mientras que los valores objetivo son los movimientos de los precios futuros durante un intervalo temporal dependiendo del marco temporal actual.
- Normalización de características: todos los elementos de la matriz de transiciones se normalizan para garantizar la estabilidad y la eficiencia del entrenamiento, una técnica clásica en el aprendizaje automático que evita el predominio de características individuales y acelera la convergencia del algoritmo.
- Inicialización y entrenamiento de la red: utilizamos una arquitectura de tres capas (9 neuronas de entrada, 40 neuronas ocultas y 2 neuronas de salida) y el algoritmo L-BFGS (Limited-memory Broyden–Fletcher–Goldfarb–Shanno), uno de los métodos de optimización más efectivos para el entrenamiento de redes neuronales.
- Reentrenamiento regular: el sistema se reentrena automáticamente cada 48 horas, lo cual le permite adaptarse a las condiciones cambiantes del mercado.
Observe la forma elegante en que se adapta a diferentes marcos temporales: la variable barsPerDay se ajusta automáticamente, lo que permite que el sistema prediga consistentemente los cambios de los precios futuros independientemente de si estamos trabajando con velas de minutos o gráficos diarios. Esta solución universal convierte al asesor en una herramienta excepcionalmente flexible, capaz de trabajar en cualquier marco temporal sin necesidad de configuración adicional.
Una de las características de nuestra implementación es también el uso de una "ventana flotante" para actualizar la matriz de Márkov. Para cada ejemplo de entrenamiento, recalculamos la matriz de transiciones según las 100 barras anteriores, lo que permite a la red neuronal captar la relación entre las características del mercado local y los movimientos de precios posteriores.
La función GetPrediction demuestra cómo se utiliza una red neuronal entrenada para generar señales comerciales: la matriz de transiciones de Márkov actual se transforma en un vector de características que se suministra a la entrada de la red neuronal, mientras que la salida son las probabilidades de los aumentos y disminuciones de los precios. Estas probabilidades se usan directamente para tomar decisiones comerciales, como veremos en la siguiente sección.
La estrategia de decisiones comerciales y su arte de protección del capital
Ahora echaremos un vistazo a cómo el sistema toma decisiones comerciales:
// Global variables for position management double lastBuyPrice = 0; // Price of last buy order double lastSellPrice = 0; // Price of last sell order double LotSize = 0.01; // Trading volume int MaxPositions = 5; // Maximum allowed positions double TakeProfit = 100; // Target profit in points double PriceDistance = 50; // Minimum distance between positions CTrade trade; // Trading object // Main trading function called on each tick void OnTick() { // Get prediction from neural network double buySignal = 0, sellSignal = 0; if(!GetPrediction(buySignal, sellSignal)) { return; // Exit if prediction fails } // Process closing of profitable positions first CheckProfitClosure(); // Check if maximum positions limit is reached int totalPositions = CountOpenPositions(); if(totalPositions >= MaxPositions) return; // Get current market prices MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return; // Open BUY position if: // 1. Buy signal is strong enough (threshold 0.55) // 2. We haven't reached max positions for BUY // 3. Price is far enough from the last buy to avoid clustering if(buySignal > 0.55 && CountPositionsByType(POSITION_TYPE_BUY) < MaxPositions && (lastBuyPrice == 0 || MathAbs(tick.ask - lastBuyPrice) > PriceDistance*_Point)) { if(trade.Buy(LotSize, _Symbol, tick.ask, 0, 0, "MLP_Buy")) { lastBuyPrice = tick.ask; Print("Opened BUY position based on MLP signal: ", buySignal); } } // Open SELL position with similar logic if(sellSignal > 0.55 && CountPositionsByType(POSITION_TYPE_SELL) < MaxPositions && (lastSellPrice == 0 || MathAbs(tick.bid - lastSellPrice) > PriceDistance*_Point)) { if(trade.Sell(LotSize, _Symbol, tick.bid, 0, 0, "MLP_Sell")) { lastSellPrice = tick.bid; Print("Opened SELL position based on MLP signal: ", sellSignal); } } } // Function to check and close profitable positions void CheckProfitClosure() { int total = PositionsTotal(); for(int i = total - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket <= 0) continue; if(!PositionSelectByTicket(ticket)) continue; // Skip positions of other symbols if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT); ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // Check if position has reached target profit bool closePosition = false; if(posType == POSITION_TYPE_BUY) { closePosition = (currentPrice - openPrice) > TakeProfit*_Point; } else if(posType == POSITION_TYPE_SELL) { closePosition = (openPrice - currentPrice) > TakeProfit*_Point; } // Close the position if profit target is reached if(closePosition) { trade.PositionClose(ticket); Print("Closed position ", ticket, " with profit"); } } } // Helper function to count all open positions for the current symbol int CountOpenPositions() { int count = 0; int total = PositionsTotal(); for(int i = 0; i < total; i++) { ulong ticket = PositionGetTicket(i); if(ticket <= 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) == _Symbol) { count++; } } return count; } // Helper function to count positions by type (BUY or SELL) int CountPositionsByType(ENUM_POSITION_TYPE type) { int count = 0; int total = PositionsTotal(); for(int i = 0; i < total; i++) { ulong ticket = PositionGetTicket(i); if(ticket <= 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) == _Symbol && (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type) { count++; } } return count; }
La característica clave de nuestro asesor es la capacidad de mantener simultáneamente posiciones largas y cortas, creando pares de cobertura. Esta estrategia resulta fundamentalmente distinta de los enfoques tradicionales que requieren una definición clara de la dirección del mercado. En lugar de una dicotomía "alcista o bajista", nuestro sistema reconoce que el mercado es multifacético y que diferentes aspectos del mismo pueden moverse en diferentes direcciones simultáneamente.
El concepto de cobertura en nuestro sistema se implementa mediante la apertura simultánea de posiciones en ambas direcciones, cuando la red neuronal muestra altas probabilidades tanto para movimientos ascendentes como descendentes. Esto puede ocurrir, por ejemplo, durante periodos de alta volatilidad o antes de acontecimientos económicos importantes. Este enfoque se puede comparar con una partida de ajedrez, donde un gran maestro experimentado con frecuencia desarrolla un ataque en un flanco mientras simultáneamente fortalece la defensa en el otro.
Las posiciones de cobertura actúan como un seguro mutuo: cuando el mercado elige una determinada dirección de movimiento, una de las posiciones se vuelve rentable y la otra se vuelve no rentable. Sin embargo, con las configuraciones correctas (especialmente el TakeProfit), el sistema cierra rápidamente las posiciones rentables, manteniendo abiertas las no rentables, esperando que el mercado se revierta. Esta asimetría (obtener ganancias rápidamente y esperar pacientemente con posiciones perdedoras) ofrece una esperanza matemática positiva para el sistema a largo plazo.
También vale la pena destacar el elegante mecanismo de gestión de posiciones: el asesor no solo abre una nueva operación en cada señal, sino que también considera las posiciones existentes y mantiene una distancia mínima entre los puntos de entrada (el parámetro PriceDistance). Esto evita la acumulación excesiva de riesgos y garantiza una distribución más equitativa del capital.
De particular interés es el comportamiento de nuestro sistema en los límites de los modos de mercado, cuando el mercado pasa de un estado de tendencia a uno lateral o viceversa. En esos momentos, los sistemas tradicionales suelen fallar y descubren que su “mapa” ya no coincide con el “terreno”. Nuestro sistema, mediante la actualización continua de la matriz de Márkov y el reentrenamiento regular de la red neuronal, se adapta rápidamente a los cambios, lo cual lo hace particularmente eficaz durante periodos de mayor incertidumbre.
Pruebas y optimización: de la teoría a la práctica
Tras implementar la estructura básica del asesor, ahora nos sumergiremos en un estudio fascinante de su efectividad en datos históricos de varios pares de divisas durante el periodo 2017-2025. Los resultados han superado las expectativas más optimistas, especialmente para los pares de divisas con alta liquidez: EUR/USD y GBP/USD.

Veamos un análisis detallado de los resultados de las pruebas en el par EUR/USD con los parámetros determinados durante la optimización (LotSize = 0,01, MaxPositions = 5, ATR_Period = 14)
Echemos un vistazo más de cerca a estas métricas:
- Rentabilidad anual media: 66,7%
Esta cifra supera considerablemente el promedio incluso de los fondos de inversión gestionados de forma activa, que normalmente aspiran a un rendimiento del 10-15% anual. Este alto rendimiento demuestra la capacidad del sistema para identificar y explotar eficazmente las oportunidades del mercado. - Reducción máxima: 11%
Este indicador refleja la mayor caída porcentual del capital desde un pico hasta un valle antes de un nuevo máximo: una reducción relativamente baja para un sistema con tal rendimiento indica la eficacia de la estrategia de cobertura y gestión de riesgos. - Ratio de Sharpe: 1.3
El ratio de Sharpe es una medida comúnmente utilizada para medir el rendimiento de las inversiones y que tiene en cuenta el equilibrio entre rendimiento y riesgo. Un valor superior a 1,0 se considera bueno, mientras que 1,3 se considera un resultado excelente, lo cual indica una alta rentabilidad en relación con el riesgo asumido. - Porcentaje de transacciones rentables: 44,7%
Este indicador, también conocido como "win rate", muestra que más de 4 de cada 10 transacciones del asesor son rentables. Se trata de una cifra elevada para un sistema algorítmico, sobre todo considerando el importante número de transacciones completadas (182.524). - Factor de beneficio: 1.2
La relación entre la ganancias totales y la pérdidas totales. Un valor de 1,2 significa que el sistema genera un 20% más de ganancias que pérdidas, lo que es una clara señal de su eficacia. - Factor de recuperación 7,64
Pero las cifras, aunque convincentes, no logran captar el logro clave del asesor: ha demostrado una estabilidad notable en una amplia gama de condiciones de mercado. Como un surfista experimentado, se ha deslizado con maestría sobre las olas del mercado, sin importar su altura y naturaleza.
El comportamiento del sistema resulta especialmente indicativo durante periodos de alta turbulencia del mercado. Por ejemplo, durante el fuerte fortalecimiento del dólar en marzo de 2024, cuando muchos sistemas algorítmicos tradicionales sufrieron pérdidas significativas, nuestro asesor no solo preservó el capital sino que también mostró rendimientos positivos. Esto se ha conseguido mediante un reentrenamiento oportuno de la red neuronal, que ha logrado adaptarse a las condiciones cambiantes del mercado, y una cobertura efectiva, que protege al capital de movimientos de precios unilaterales.
Una ventaja adicional del sistema es su capacidad de negociar en diversos modos de mercado. Si bien muchas estrategias algorítmicas están optimizadas para mercados con tendencias o laterales, nuestro sistema funciona con éxito en ambos modos gracias a un mecanismo de detección de estado adaptativo y una estrategia de cobertura flexible.
Más allá del modelo básico: direcciones para la expansión
La implementación presentada supone solo la primera nota de una posible sinfonía de posibilidades. Hay muchas direcciones interesantes por delante para seguir mejorando el sistema.
Ampliación del modelo de estados
Imaginemos un sistema en el que, en lugar de la modesta tríada de estados del mercado (tendencia alcista, tendencia plana, tendencia bajista), se desarrolla todo un espectro de sentimiento del mercado: desde un galope alcista furioso hasta un rápido ataque bajista, desde una deriva positiva apenas perceptible hasta un suave deslizamiento descendente, con una tendencia lateral majestuosa y limpia en el centro de este continuo.
// Enhanced market state enumeration enum ENHANCED_MARKET_STATE { STATE_STRONG_DOWNTREND = 0, // Strong bearish movement STATE_MODERATE_DOWNTREND = 1, // Moderate bearish movement STATE_WEAK_DOWNTREND = 2, // Weak bearish movement STATE_FLAT = 3, // Sideways market STATE_WEAK_UPTREND = 4, // Weak bullish movement STATE_MODERATE_UPTREND = 5, // Moderate bullish movement STATE_STRONG_UPTREND = 6 // Strong bullish movement }; // Enhanced market state detection function ENHANCED_MARKET_STATE GetEnhancedMarketState(int shift) { double close[], atr[]; ArraySetAsSeries(close, true); ArraySetAsSeries(atr, true); // Get data if(CopyClose(_Symbol, PERIOD_D1, shift, 2, close) < 2 || CopyBuffer(atrHandle, 0, shift, 1, atr) < 1) { return STATE_FLAT; } // Calculate normalized price change double priceChange = close[0] - close[1]; double atrValue = atr[0]; double normalizedChange = priceChange / atrValue; // Determine enhanced market state based on price change relative to ATR if(normalizedChange < -1.5) return STATE_STRONG_DOWNTREND; if(normalizedChange < -0.75) return STATE_MODERATE_DOWNTREND; if(normalizedChange < -0.25) return STATE_WEAK_DOWNTREND; if(normalizedChange <= 0.25) return STATE_FLAT; if(normalizedChange <= 0.75) return STATE_WEAK_UPTREND; if(normalizedChange <= 1.5) return STATE_MODERATE_UPTREND; return STATE_STRONG_UPTREND; }
Enriqueciendo el contexto del mercado
El modelo actual usa principalmente ATR para determinar los estados del mercado. ¡Pero imaginemos la profundidad de comprensión que podemos lograr añadiendo a esta orquesta el sonido del RSI, la melodía del MACD, las secuencias armónicas de los niveles de Fibonacci y las estructuras rítmicas de los volúmenes!
Así como los sentidos humanos se combinan para crear una percepción holística del mundo, también una multitud de indicadores técnicos pueden ofrecer a nuestro sistema una comprensión casi intuitiva de la dinámica del mercado. La combinación de osciladores, indicadores de tendencia e indicadores de volumen puede provocar un salto cualitativo en la precisión de las previsiones.
Ajuste dinámico del tamaño de las posiciones
La idea de la adaptación dinámica de los tamaños de posición merece especial atención. Imagine un sistema que, como un capitán experimentado, aumenta o disminuye el "la superficie de las velas" dependiendo de la fuerza del "viento" del mercado, la confianza en el rumbo elegido y la experiencia histórica de navegar en aguas similares.
Durante periodos de alta certeza y condiciones favorables del mercado, el EA aumentará el tamaño de las posiciones, maximizando el retorno de su ventaja predictiva. Por el contrario, durante periodos de mayor turbulencia o señales conflictivas, el sistema reducirá de forma automática los volúmenes de negociación, preservando el capital para oportunidades más favorables.
Análisis de múltiples marcos temporales
Finalmente, el análisis de múltiples marcos temporales abrirá una nueva dimensión en la comprensión del mercado. Así como un arqueólogo estudia simultáneamente la era geológica general y los detalles más pequeños de un artefacto, nuestro sistema podrá capturar simultáneamente tanto los cambios tectónicos globales en el mercado como las fluctuaciones de precios más pequeñas.
Imagine un asesor que analiza cadenas de Márkov en múltiples marcos temporales simultáneamente, desde mensuales hasta de minutos, y forma una imagen integrada del mercado, donde las tendencias a largo plazo guían las fluctuaciones a corto plazo. Este enfoque no solo le permitirá determinar con mayor precisión la dirección del movimiento, sino también identificar puntos de entrada ideales con una precisión de tick.
Epílogo: la piedra filosofal del trading algorítmico
El asesor experto presentado en este artículo no es simplemente una simbiosis de varios enfoques técnicos: representa una auténtica muestra de alquimia de las tecnologías financieras, en la que de la interacción de elementos dispares nace algo cualitativamente nuevo, igual que en las leyendas la piedra filosofal convertía los metales ordinarios en oro.
Al combinar el rigor de la teoría de la probabilidad matemática con el poder intuitivo de la inteligencia artificial y la sabiduría pragmática de las estrategias de cobertura, hemos creado un sistema capaz de navegar las turbulentas aguas de los mercados financieros con la gracia de un marinero experimentado. Cuando el tiempo está en calma, capta el más mínimo movimiento de aire con sus sensibles velas; en una tormenta, maniobra con maestría entre olas gigantes de volatilidad; y cuando el viento cambia, ajusta instantáneamente su rumbo.
Debemos entender que no hemos creado un artefacto mágico que prometa riqueza ilimitada, más bien, es un instrumento musical delicado que requiere una afinación adaptada a condiciones de interpretación específicas y habilidad por parte de su propietario. Así como un violín Stradivarius revela su sonido divino solo en manos de un virtuoso, nuestro asesor revela todo su potencial con una afinación adecuada y una comprensión profunda de su arquitectura interna.
Sin embargo, la naturaleza adaptativa del sistema y los elegantes mecanismos de gestión de riesgos hacen que esta herramienta resulte accesible tanto para un novato que da sus primeros pasos en el mundo del trading algorítmico como para un experto experimentado que busca nuevas dimensiones en su arsenal comercial.
El código fuente del asesor, como el código genético de un nuevo tipo de sistema de trading, está disponible en el apéndice del artículo, y está abierto a la experimentación, modificación y evolución. Le invitamos no solo a utilizarlo, sino a convertirse en coautor del próximo capítulo de esta apasionante historia del desarrollo de la tecnología financiera.
Después de todo, la verdadera innovación nace de un diálogo abierto de ideas y de una búsqueda continua de la excelencia.
Enlaces y materiales adicionales
- Koshtenko, Y. (2025). Modelo de pronóstico matricial en cadenas de Márkov.
- ALGLIB - Biblioteca de análisis numérico: https://www.alglib.net/
- Documentación de MQL5: https://www.mql5.com/es/docs
- Sewell, M. (2011). Characterization of Financial Time Series. UCL Research Note, 11(01).
- Zhang, G.P. (2003). Time series forecasting using a hybrid ARIMA and neural network model. Neurocomputing, 50, 159-175.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/18192
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
Creación de un Panel de administración de operaciones en MQL5 (Parte IX): Organización del código (IV): Clase sobre el Panel de gestión de operaciones
Particularidades del trabajo con números del tipo double en MQL4
Redes neuronales en el trading: Generalización de series temporales sin vinculación a datos (Módulos básicos del modelo)
- 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
Manipulación consciente o inconsciente, pero flagrante, de los resultados de las pruebas. (muchos autores sufren de esto).
El Asesor Experto fue probado con un lote fijo, lo que mata por completo cualquier estrategia - ya que las condiciones de cada siguiente operación del Asesor Experto se vuelven menos arriesgadas. De ahí el bajo porcentaje de drawdown. Para tal cuadro probador no es necesario hacer matrices, AI y así sucesivamente, es suficiente para encontrar un punto de tiempo conveniente para la prueba.
Me parece que un Asesor Experto (no sólo éste) debe ser probado en un tamaño de lote determinado por el tamaño del depósito (porcentaje). Entonces cada operación en la prueba será como la primera. De hecho, las condiciones de cada operación serán siempre las mismas en términos de riesgo. Y aquí el panorama será completamente diferente.
Extraño, pero el archivo de la caja ya está compilado con un error (DeInit )))). No está claro en qué ajustes se probó - de la misma "caja" hay números cósmicos. Y si se quita el agua AI, entonces al final no hay nada que leer. Usted puede ser más específico.
Por cierto, rellene el texto AI"El modelo actual utiliza principalmente ATR para determinar las condiciones del mercado. Pero ¡imagina qué profundidad de comprensión alcanzaremos añadiendo a esta orquesta el sonido del RSI, la melodía del MACD, las secuencias armónicas de los niveles de Fibonacci y las estructuras rítmicas de los volúmenes!". Te lo cuenta !!!!)))))
Añadido rsi, macd, fibo, volumen, por si a alguien le interesa.
En el foro sólo se puede publicar fuentes, de lo contrario pueden prohibir.
En realidad, ¿cuál es el efecto de las adiciones?
En el foro sólo se pueden publicar fuentes, de lo contrario pueden ser prohibidas.
En realidad, ¿cuál es el efecto de los aditivos?