Русский Português
preview
Asesor de autoaprendizaje con red neuronal basada en matriz de estados

Asesor de autoaprendizaje con red neuronal basada en matriz de estados

MetaTrader 5Sistemas comerciales |
45 10
Yevgeniy Koshtenko
Yevgeniy Koshtenko

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:

  1. 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.
  2. Conteo de transiciones: para cada par de estados consecutivos (anterior → actual), incrementamos el contador correspondiente en la matriz transitionCounts.
  3. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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).
  5. 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.
  6. 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

  1. Koshtenko, Y. (2025). Modelo de pronóstico matricial en cadenas de Márkov. 
  2. ALGLIB - Biblioteca de análisis numérico: https://www.alglib.net/
  3. Documentación de MQL5: https://www.mql5.com/es/docs
  4. Sewell, M. (2011). Characterization of Financial Time Series. UCL Research Note, 11(01).
  5. 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

Archivos adjuntos |
Matrix_MLP_EA.mq5 (35.48 KB)
Victor Golovkov
Victor Golovkov | 22 may 2025 en 10:28

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.

Aliaksandr Kazunka
Aliaksandr Kazunka | 22 may 2025 en 15:44

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 !!!!)))))

Aliaksandr Kazunka
Aliaksandr Kazunka | 22 may 2025 en 15:46
Añadido rsi, macd, fibo, volumen si alguien está interesado
Aleksey Vyazmikin
Aleksey Vyazmikin | 22 may 2025 en 16:07
sportoman #:
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?

Aliaksandr Kazunka
Aliaksandr Kazunka | 22 may 2025 en 17:05
Aleksey Vyazmikin #:

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?

Locurioso es que el Expert Advisor no funciona en el tester. No entiendo qué y cómo lo ha probado el autor. Lo he puesto en demo para todos los pares, ya veré qué y cómo

Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
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 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
Esta discusión trata sobre el TradeManagementPanel actualizado en nuestro asesor experto New_Admin_Panel. La actualización mejora el panel mediante el uso de clases integradas para ofrecer una interfaz de gestión de operaciones fácil de usar. Incluye botones para abrir posiciones y controles para gestionar las operaciones existentes y las órdenes pendientes. Una característica clave es la gestión de riesgos integrada, que permite establecer los valores de stop loss y take profit directamente en la interfaz. Esta actualización mejora la organización del código para programas grandes y simplifica el acceso a las herramientas de gestión de pedidos, que a menudo son complejas en la terminal.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Redes neuronales en el trading: Generalización de series temporales sin vinculación a datos (Módulos básicos del modelo) Redes neuronales en el trading: Generalización de series temporales sin vinculación a datos (Módulos básicos del modelo)
Seguimos familiarizándonos con el framework Mamba4Cast. Hoy profundizaremos en la implementación práctica de los enfoques propuestos. Mamba4Cast no ha sido diseñado para un largo periodo de calentamiento en cada nueva serie temporal, sino para un funcionamiento inmediato. Gracias al concepto de pronóstico Zero-Shot, el modelo es capaz de generar inmediatamente pronósticos de alta calidad sobre datos reales sin entrenamiento adicional ni ajuste de hiperparámetros.