English Русский 中文 Deutsch 日本語 Português
preview
Teoría de grafos: Aplicación del algoritmo de Dijkstra al trading

Teoría de grafos: Aplicación del algoritmo de Dijkstra al trading

MetaTrader 5Ejemplos |
49 0
Hlomohang John Borotho
Hlomohang John Borotho

Introducción

En este artículo, analizaremos la implementación del algoritmo de Dijkstra, un concepto fundamental de la teoría de grafos conocido por su eficacia a la hora de resolver problemas de camino más corto. Aunque tradicionalmente se ha aplicado a la optimización de rutas y redes, adaptaremos este algoritmo a los mercados financieros modelando los movimientos de precios como un grafo ponderado. En este caso, los nodos representan niveles de precios o intervalos de tiempo, mientras que las aristas reflejan el coste (o la probabilidad) de pasar de uno a otro.

Nuestro objetivo es aprovechar el método de Dijkstra para predecir el siguiente conjunto probable de datos de precios, determinando en la práctica la «ruta más corta» que el precio podría seguir desde su posición actual hasta un valor futuro. Al modelar la dinámica del mercado como un grafo, nuestro objetivo es identificar la trayectoria más probable y optimizar las decisiones de negociación basándonos en la mínima resistencia o el menor coste.

La teoría de grafos ofrece un potente marco para analizar estructuras de mercado complejas, y el algoritmo de Dijkstra proporciona una forma sistemática de explorarlas. Al interpretar las fluctuaciones de los precios como aristas ponderadas por factores como la volatilidad, podemos calcular la trayectoria óptima que minimiza el riesgo o maximiza la eficiencia.

La serie prevista de precios actúa, en esencia, como la distancia más corta entre el precio actual y los niveles futuros, lo que ofrece a los traders un método basado en datos para anticipar las tendencias. Este enfoque tiende un puente entre el trading algorítmico y las matemáticas computacionales, demostrando cómo los algoritmos clásicos de grafos pueden revelar oportunidades ocultas en los datos de series temporales financieras.


Los fundamentos de Dijkstra

Terminología Nuestra interpretación  
Grafo: Conjunto de nodos y aristas. Grafo: La  estructura del gráfico formada por máximos y mínimos de oscilación ; cada punto de oscilación se convierte en un nodo y cada recorrido del precio entre ellos, en una arista.
Peso: El coste de desplazarse de un nodo a otro. Peso: El coste (o esfuerzo) que supone que el precio se mueva entre dos puntos de oscilación. Esto podría ser la diferencia de precio absoluta.
Nodo de origen: El punto de partida del algoritmo. Nodo de origen: El punto de oscilación válido más reciente (el máximo o mínimo más reciente que el precio no ha vuelto a traspasar). Es nuestro punto de partida para calcular las rutas más cortas a partir de ahí.
Conjunto visitado: Nodos cuyo procesamiento ya hemos completado. Conjunto de puntos visitados: Todos los puntos de oscilación que el algoritmo ya ha evaluado y no volverá a visitar. En términos bursátiles, se trata de puntos de oscilación que el precio ya ha superado o hacia los que ya se ha negociado.
Tabla de distancias: Registra la distancia más corta a cada nodo. Tabla de distancias: Una asignación de cada nodo a su valor de «coste de acceso» más corto desde el nodo de origen. En el mundo del trading, te indica con qué facilidad (o a qué coste) puede moverse el precio desde el punto actual hasta cualquier otro punto de oscilación.

Proceso paso a paso:

1. Inicialización:

  • Establece la distancia al nodo de origen en 0.
  • Establece la distancia a todos los demás nodos en infinito.
  • Crea una cola de prioridad (o montón mínimo) para seleccionar siempre el nodo con la distancia conocida más corta.

2. Visita el nodo más cercano que aún no hayas visitado:

  • Empieza por el nodo de origen.
  • Para cada vecino, calcula:
    new_distance = distance_to_current + edge_weight
    Si este `new_distance` es menor que la distancia conocida anteriormente, actualízalo.

3. Marcar el nodo actual como visitado:

  • Una vez tramitado, no lo volvemos a revisar.

4. Repite:

  • Sigue visitando el siguiente nodo más cercano que aún no hayas visitado.
  • Repite el proceso hasta que se hayan visitado todos los nodos o se haya encontrado el camino más corto.
  • while unvisited nodes remain:
        select node with the smallest tentative distance
        for each neighbor:
            if new path to neighbor is shorter:
                update the shortest distance
        mark current node as visited


Primeros pasos

//+------------------------------------------------------------------+
//|                                               Dijkstars Algo.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict
#include <Trade/Trade.mqh>
CTrade trade;

Empezamos incluyendo el archivo `trade.mqh`, que nos permite acceder a la funcionalidad de negociación integrada de MQL5 a través de la clase `CTrade`. Esta clase ofrece métodos para abrir, modificar y cerrar operaciones mediante programación. Tras incluir el archivo, creamos una instancia de `CTrade` denominada `trade`, que utilizaremos a lo largo de todo el Asesor Experto para enviar comandos de negociación como `Buy()`, `Sell()` y `PositionOpen()`. Esta configuración es esencial para automatizar la ejecución de órdenes en el EA.

// Input Parameters
input int    TakeProfit   = 1000;     
input int   StopLoss = 385;
input double  In_Lot = 0.01;
input int    LeftBars    = 3;
input int    RightBars   = 3;
input int    MaxSwings   = 50;
input double Lots        = 0.1;
input double PointBuffer = 10;
input int    Slippage    = 5;
ENUM_TIMEFRAMES TimeFrame;

En esta sección, definimos los parámetros de entrada del Asesor Experto, lo que permite al operador personalizar los ajustes clave directamente desde la interfaz del EA. `TakeProfit` y `StopLoss` establecen el objetivo y el riesgo en puntos, mientras que `In_Lot` y `Lots` definen el tamaño de la operación. `LeftBars` y `RightBars` se utilizan para detectar máximos y mínimos de oscilación comparando las barras con las adyacentes. `MaxSwings` limita el número de puntos de swing que se registran, y `PointBuffer` añade una distancia adicional a SL/TP por seguridad. «Slippage» establece la desviación máxima permitida en el precio durante la ejecución de la orden, y «TimeFrame» especifica el periodo del gráfico que analizará el EA.

// Node Structure
struct SwingPoint {
   int index;
   datetime time;
   double price;
   bool isHigh;
   bool visited;
   double distance;
   bool   used;
   int previous;
};

Esta estructura define el modelo de un SwingPoint, que representa un nodo en el sistema de negociación basado en Dijkstra. Cada punto de oscilación contiene información importante:</i0>

  • Index es el número de barra en la que se ha detectado la oscilación.
  • Time es la marca de tiempo exacta de esa barra.
  • Price es el valor máximo o mínimo en ese punto.
  • IsHigh indica si se trata de un máximo (verdadero) o un mínimo (falso).
  • Visited ayuda a realizar un seguimiento de los nodos que ha procesado el algoritmo.
  • Distance almacena el coste calculado desde el nodo de origen en el algoritmo de búsqueda de rutas de Dijkstra.
  • Used indica si este punto de oscilación ya se ha utilizado en decisiones de inversión.
  • Previous mantiene un registro del nodo anterior en la cadena del camino más corto.
SwingPoint swingPoints[];

//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit() {
   Print("Dijkstra Swing EA initialized");
   return INIT_SUCCEEDED;
}

Aquí se declara una matriz dinámica `swingPoints[]` para almacenar todos los máximos y mínimos de oscilación detectados (como estructuras `SwingPoint`) en el gráfico. Esta matriz se rellenará y se utilizará a lo largo de todo el EA para representar los nodos en el gráfico de evolución del precio. En la función `OnInit()`, el EA simplemente muestra un mensaje en la terminal para confirmar que el «Dijkstra Swing EA» se ha inicializado correctamente y devuelve `INIT_SUCCEEDED` para indicar que el inicio se ha realizado sin problemas.

//+------------------------------------------------------------------+
//| Detect swing highs and lows                                      |
//+------------------------------------------------------------------+
void DetectSwings(int left, int right) {
   ArrayResize(swingPoints, 0);

   int totalBars = Bars(_Symbol, PERIOD_CURRENT) - right;
   for (int i = left; i < totalBars; i++) {
      bool isHigh = true, isLow = true;
      double high = High(i), low = Low(i);

      for (int j = 1; j <= left; j++) {
         if (High(i - j) >= high) isHigh = false;
         if (Low(i - j) <= low) isLow = false;
      }
      for (int j = 1; j <= right; j++) {
         if (High(i + j) >= high) isHigh = false;
         if (Low(i + j) <= low) isLow = false;
      }

      if (isHigh || isLow) {
         int idx = ArraySize(swingPoints);
         ArrayResize(swingPoints, idx + 1);
         swingPoints[idx].index = i;
         swingPoints[idx].time = Time(i);
         swingPoints[idx].price = isHigh ? high : low;
         swingPoints[idx].isHigh = isHigh;
         swingPoints[idx].visited = false;
         swingPoints[idx].distance = DBL_MAX;
         swingPoints[idx].previous = -1;

         if (idx >= MaxSwings) break;
      }
   }
}

Esta función, `DetectSwings()`, identifica los máximos y mínimos de oscilación en el gráfico de precios comparando cada vela con las barras adyacentes. Empieza borrando la matriz `SwingPoint` existente mediante `ArrayResize`, lo que garantiza una detección nueva cada vez que se invoca. Recorre cada barra del gráfico, empezando por el índice `left` hasta `totalBars - right`, y comprueba si la barra actual (i) cumple los requisitos para considerarse un máximo o un mínimo de oscilación.

Para determinar si una barra es un máximo de oscilación, se comprueba si su precio máximo es superior a los máximos de las barras anteriores (a la izquierda) y de las barras siguientes (a la derecha). Del mismo modo, un mínimo de oscilación se confirma si su precio mínimo es inferior tanto al mínimo anterior como al siguiente. Si se cumple cualquiera de las dos condiciones, la barra se considera un punto de oscilación válido. Esta comparación local garantiza que solo se registren como puntos de inflexión los picos y valles significativos en el precio.

Cuando se detecta una oscilación, se almacena en la matriz `SwingPoint[]` junto con todos los datos relevantes: su índice, la hora, el precio, si se trata de un máximo o un mínimo, y los valores predeterminados para el cálculo de la trayectoria (por ejemplo, `visited`, `distance` y `previous`). Esta estructura permite realizar análisis posteriores mediante el algoritmo de Dijkstra para evaluar las trayectorias probables entre puntos de oscilación. El bucle se detiene antes de tiempo si el número de oscilaciones detectadas alcanza el valor `MaxSwings`, lo que evita un uso excesivo de memoria o problemas de rendimiento.

//+------------------------------------------------------------------+
//| Apply Dijkstra's algorithm                                       |
//+------------------------------------------------------------------+
void ApplyDijkstra() {
   if (ArraySize(swingPoints) == 0) return;

   swingPoints[0].distance = 0;

   for (int i = 0; i < ArraySize(swingPoints); i++) {
      int u = -1;
      double minDist = DBL_MAX;

      for (int j = 0; j < ArraySize(swingPoints); j++) {
         if (!swingPoints[j].visited && swingPoints[j].distance < minDist) {
            minDist = swingPoints[j].distance;
            u = j;
         }
      }

      if (u == -1) break;

      swingPoints[u].visited = true;

      for (int v = 0; v < ArraySize(swingPoints); v++) {
         if (!swingPoints[v].visited) {
            double cost = MathAbs(swingPoints[u].price - swingPoints[v].price);
            if (swingPoints[u].distance + cost < swingPoints[v].distance) {
               swingPoints[v].distance = swingPoints[u].distance + cost;
               swingPoints[v].previous = u;
            }
         }
      }
   }
}

En esta función, implementamos el algoritmo de Dijkstra para calcular la ruta más corta desde el primer punto de oscilación hasta todos los demás puntos de oscilación, tratando el movimiento de los precios entre los máximos y mínimos de oscilación como un grafo ponderado. Primero comprobamos si hay algún punto de oscilación; si la matriz está vacía, la función se detiene inmediatamente. A continuación, establece la «distancia» del primer nodo (el pivote inicial) en «0», lo que indica que se trata del nodo de origen en el proceso de búsqueda de ruta.

El algoritmo entra en un bucle en el que, en cada iteración, selecciona el punto de inflexión no visitado con la `distancia` conocida más pequeña. Este nodo `(u)` se marca como visitado, y el algoritmo evalúa todos sus vecinos no visitados. Para cada uno de estos vecinos `(v)`, calcula el «coste» o peso que supone desplazarse del nodo `u` al `v`, basándose en la diferencia de precio absoluta entre ambos. Si el coste acumulado para llegar a `v` pasando por `u` es inferior a la distancia que tiene registrada actualmente, actualiza la `distancia` de `v` y registra `u` como su nodo `anterior`.

Este proceso continúa hasta que se hayan visitado todos los puntos de inflexión accesibles o hasta que ya no haya nodos sin visitar a los que se pueda acceder. Al final de la función, cada punto de inflexión contiene el coste acumulado más bajo desde el origen y un puntero al nodo anterior a lo largo de esa ruta óptima. Esta información permite al EA trazar la trayectoria más eficiente a través de la estructura reciente del mercado y determinar a qué puntos de oscilación es más probable que vuelva el precio a continuación, lo que constituye la base para la generación de señales de trading inteligentes.

//+------------------------------------------------------------------+
//| Visualize Swing Points and Connections                           |
//+------------------------------------------------------------------+
void VisualizeSwings() {
   for (int i = 0; i < ArraySize(swingPoints); i++) {
      string objName = "Swing_" + IntegerToString(i);
      ObjectDelete(0, objName);

      ObjectCreate(0, objName, OBJ_ARROW, 0, swingPoints[i].time, swingPoints[i].price);
      ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, swingPoints[i].isHigh ? 233 : 234);
      ObjectSetInteger(0, objName, OBJPROP_COLOR, swingPoints[i].isHigh ? clrRed : clrBlue);
   }

   for (int i = 1; i < ArraySize(swingPoints); i++) {
      int prev = swingPoints[i].previous;
      if (prev != -1) {
         string lineName = "Line_" + IntegerToString(i);
         ObjectDelete(0, lineName);

         ObjectCreate(0, lineName, OBJ_TREND, 0,
                      swingPoints[prev].time, swingPoints[prev].price,
                      swingPoints[i].time, swingPoints[i].price);
         ObjectSetInteger(0, lineName, OBJPROP_COLOR, clrGray);
         ObjectSetInteger(0, lineName, OBJPROP_WIDTH, 1);
      }
   }
}

La función `VisualizeSwings()` se encarga de trazar los puntos de oscilación detectados y las conexiones entre ellos directamente en el gráfico, lo que ayuda a los traders a confirmar visualmente la estructura y la lógica que utiliza el EA. En el primer bucle «for», se recorre la matriz «swingPoints[]» y se crean objetos de flecha para cada swing. Antes de crear un nuevo objeto, elimina cualquier objeto existente con el mismo nombre para evitar el desorden. A cada flecha se le asigna un símbolo específico: rojo para los máximos de oscilación `código de flecha 233` y azul para los mínimos de oscilación `código de flecha 234`, lo que permite distinguirlos a simple vista.

En el segundo bucle «for», la función traza líneas entre cada punto de oscilación y su nodo «previous» correspondiente (según lo determina el algoritmo de Dijkstra). Estas líneas representan las conexiones de ruta más corta que se utilizan para evaluar posibles trayectorias operativas. Una vez más, cualquier objeto de línea existente con el mismo nombre se elimina antes de dibujar uno nuevo. Las líneas se crean mediante `OBJ_TREND` y se dibujan en gris con un grosor estándar, lo que permite mantener una estructura visual clara y ordenada.

Esta visualización ayuda a validar la toma de decisiones del EA, ya que permite ver qué puntos de inflexión se identificaron, cómo están conectados y qué ruta se eligió basándose en el algoritmo de Dijkstra. Resulta especialmente útil durante las pruebas retrospectivas o el trading en vivo a la hora de comprobar que el EA analiza la estructura del mercado tal y como se pretende.

double High(int index){return (iHigh(_Symbol, _Period, index));}
double Low(int index){return (iLow(_Symbol, _Period, index));}
datetime Time(int index){return (iTime(_Symbol, _Period, index));}

Estas tres funciones auxiliares, `High()`, `Low()` y `Time()`, son simples envoltorios de las funciones integradas de MQL5 `iHigh()`, `iLow()` y `iTime()`. Permiten consultar fácilmente el precio máximo, el precio mínimo y la hora de apertura de una barra concreta (según el «índice» indicado) en el valor y el intervalo de tiempo actuales. Al utilizar estas funciones abreviadas, el código resulta más limpio y legible, sobre todo cuando se accede repetidamente a los datos de las barras durante la detección de oscilaciones o la visualización.

//+------------------------------------------------------------------+
//|                          Filter and mark                         |
//+------------------------------------------------------------------+
void FilterAndMarkValidSwings(SwingPoint &points[]) {
   int count = ArraySize(points);
   if(count < 2) return;

   for(int i = 0; i < count; i++) {
      if(points[i].used) continue;

      bool isValid = true;
      double swingPrice = points[i].price;
      int swingIndex = points[i].index;

      // Scan forward in time from the swing point
      for(int j = swingIndex - 1; j >= 0; j--) {
         double high = iHigh(_Symbol, TimeFrame, j);
         double low  = iLow(_Symbol, TimeFrame, j);

         // Invalidate swing high if price went higher later
         if(points[i].isHigh && high > swingPrice) {
            isValid = false;
            break;
         }

         // Invalidate swing low if price went lower later
         if(!points[i].isHigh && low < swingPrice) {
            isValid = false;
            break;
         }
      }

      if(isValid) {
         points[i].used = true;

         // Draw object on chart
         string objName = points[i].isHigh ? 
            StringFormat("SwingHigh_%d", TimeToString(iTime(_Symbol, TimeFrame, swingIndex))) :
            StringFormat("SwingLow_%d", TimeToString(iTime(_Symbol, TimeFrame, swingIndex)));

         color swingColor = points[i].isHigh ? clrRed : clrBlue;

         ObjectCreate(0, objName, OBJ_HLINE, 0, 0, swingPrice);
         ObjectSetInteger(0, objName, OBJPROP_COLOR, swingColor);
         ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DASH);
         ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);
      }
   }
}

La función `FilterAndMarkValidSwings()` refina la lista de puntos de oscilación identificando cuáles siguen siendo válidos y no han quedado invalidados por movimientos posteriores del precio. Toma una matriz de referencias de tipo `SwingPoint` y recorre sus elementos, omitiendo aquellos que ya estén marcados como `used`. Para cada oscilación candidata, se parte de la base de que el punto es válido y, a continuación, se lleva a cabo una comprobación de validación basada en la evolución histórica del precio para confirmar si este ha superado dicha oscilación tras su formación.

Para determinar la validez, la función recorre hacia atrás las barras anteriores a partir del índice del swing. En el caso de un máximo de oscilación, comprueba si alguna vela posterior ha registrado un máximo que lo haya superado; y en el caso de un mínimo de oscilación, comprueba si alguna vela ha registrado un mínimo inferior. Si se detecta una situación de este tipo, el punto de oscilación se considera inválido, ya que el precio ha «superado» efectivamente ese nivel de oscilación, por lo que no se marca ni se utiliza en cálculos posteriores. Si no se cumple dicha condición, el punto de inflexión es válido y se marca como «utilizado».

Por cada oscilación válida, la función traza una línea horizontal en el gráfico para marcarla visualmente. La línea aparece con un trazo discontinuo y en color rojo para los máximos de oscilación, o en azul para los mínimos de oscilación. El objeto recibe su nombre en función del tipo de oscilación y del momento de la barra en el que se detectó. Esta información visual ayuda a los traders a identificar de inmediato qué puntos de oscilación considera el EA que son sólidos y no han sido afectados por el precio, lo que facilita confiar en la lógica y depurarla durante el análisis o la negociación.

//+------------------------------------------------------------------+
//|                        Cleaning up old swings                    |
//+------------------------------------------------------------------+
void CleanOldSwingObjects(int keepBars = 100) {
   datetime oldestDate = iTime(_Symbol, TimeFrame, keepBars);
   int total = ObjectsTotal(0);

   for(int i = total - 1; i >= 0; i--) {
      string name = ObjectName(0, i);
      if(StringFind(name, "SwingHigh_") == 0 || StringFind(name, "SwingLow_") == 0) {
         datetime swingTime = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME);
         if(swingTime < oldestDate) {
            ObjectDelete(0, name);
         }
      }
   }
}

En este caso, la función se encarga de eliminar del gráfico los elementos visuales obsoletos relacionados con el swing para mantener la claridad y el rendimiento. Determina el umbral para los objetos «antiguos» mediante el parámetro `keepBars`, que recupera la marca de tiempo de la barra situada `keepBars` velas atrás. Cualquier objeto Swing creado antes de esta marca de tiempo se considera obsoleto. A continuación, la función recorre todos los objetos gráficos del gráfico en orden inverso y comprueba si sus nombres comienzan por «SwingHigh_» o «SwingLow_», lo que los identifica como marcadores de oscilación.

Para cada uno de estos objetos oscilantes, recupera la hora de creación y la compara con la marca de tiempo límite (oldestDate). Si el objeto es más antiguo, lo elimina del gráfico mediante `ObjectDelete()`. Esta rutina garantiza que el gráfico se mantenga despejado, ya que solo muestra los puntos de oscilación recientes y relevantes. Además, ayuda a evitar la pérdida de rendimiento con el paso del tiempo, sobre todo al ejecutar el EA con series históricas más largas o en mercados en tiempo real, donde se acumulan muchas oscilaciones.

//+------------------------------------------------------------------+
//| Generate Signal & Trade                                          |
//+------------------------------------------------------------------+
void GenerateSignalAndTrade() {
   if (ArraySize(swingPoints) < 2) return;

   int last = ArraySize(swingPoints) - 1;
   int prev = swingPoints[last].previous;
   if (prev == -1) return;

   double entry = swingPoints[last].price;
   double reference = swingPoints[prev].price;
   double sl, tp;
   bool isBuy = entry > reference, isSell = entry < reference;

   SetSLTP(entry, reference, isBuy, sl, tp);

   if (PositionSelect(_Symbol)) return;

   if (isBuy)
      ExecuteTrade(ORDER_TYPE_BUY);
   else if(isSell)
      ExecuteTrade(ORDER_TYPE_SELL); 
}

La función `GenerateSignalAndTrade()` se encarga de generar una señal de operación basándose en la dirección de la trayectoria más reciente encontrada por el algoritmo de Dijkstra a través de los puntos de oscilación. En primer lugar, se asegura de que haya al menos dos puntos de inflexión que comparar y de que el último punto de inflexión tenga un nodo «previous» válido. A continuación, extrae el precio del último movimiento oscilatorio y del movimiento oscilatorio anterior con el que está relacionado, utilizando la relación de precios entre ambos para determinar la dirección de la operación: si el precio más reciente es superior al anterior, indica una señal de compra; si es inferior, una señal de venta.

Una vez determinada la dirección, la función calcula los niveles de stop loss y take profit mediante la función `SetSLTP()`, que los calcula en función de la distancia entre los puntos de oscilación. Antes de abrir una operación, comprueba si ya hay una posición abierta en ese instrumento para evitar duplicidades. Por último, ejecuta la operación mediante la función `ExecuteTrade()`, pasando el tipo de orden adecuado. Esta lógica garantiza que las operaciones solo se ejecuten cuando se identifique una tendencia clara y respaldada estructuralmente entre puntos de oscilación válidos.

//+------------------------------------------------------------------+
//| Calculate SL and TP based on distance to previous node           |
//+------------------------------------------------------------------+
void SetSLTP(double entry, double ref, bool isBuy, double &sl, double &tp) {
   double distance = MathAbs(entry - ref) + PointBuffer * _Point;
   if (isBuy) {
      sl = entry - distance;
      tp = entry + distance;
   } else {
      sl = entry + distance;
      tp = entry - distance;
   }
}

La función `SetSLTP()` calcula los niveles de stop loss (sl) y take profit (tp) de una operación basándose en la distancia entre el precio de entrada actual y un precio de referencia (normalmente, el punto de oscilación anterior). En primer lugar, calcula la diferencia de precio absoluta entre estos dos puntos y añade un pequeño margen (en puntos) por seguridad. Si la operación es de compra, el stop loss se coloca por debajo del precio de entrada y el take profit por encima; en caso de venta, el stop loss se coloca por encima del precio de entrada y el take profit por debajo. Esto garantiza que el riesgo y la rentabilidad estén alineados simétricamente en torno a la estructura del swing, lo que ayuda al EA a seguir la evolución del precio con niveles de stop loss y take profit significativos y basados en dicha estructura.

//+------------------------------------------------------------------+
//| Execute trade with risk parameters                               |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE tradeType){

   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) :
                                                  SymbolInfoDouble(_Symbol, SYMBOL_BID);

   // Convert StopLoss and TakeProfit from pips to actual price distances
   double sl_distance = StopLoss * point;
   double tp_distance = TakeProfit * point;
   
   double sl = (tradeType == ORDER_TYPE_BUY) ? price - sl_distance :
                                               price + sl_distance;
   
   double tp = (tradeType == ORDER_TYPE_BUY) ? price + tp_distance :
                                               price - tp_distance;

   trade.PositionOpen(_Symbol, tradeType, In_Lot, price, sl, tp, NULL);
}

La función `ExecuteTrade()` se encarga de enviar una orden de negociación con parámetros de riesgo predefinidos. En primer lugar, se determina el precio de mercado actual: se utiliza el precio Ask para una orden de compra y el precio Bid para una orden de venta. A continuación, calcula los niveles de stop loss y take profit convirtiendo los valores introducidos (StopLoss y TakeProfit) de puntos a distancias de precio reales utilizando el tamaño del punto del símbolo. Dependiendo de si la operación es de compra o de venta, coloca el stop loss y el take profit de forma adecuada por encima o por debajo del precio de entrada.

Por último, utiliza el método `PositionOpen()` de la clase `CTrade` para ejecutar la operación con los parámetros calculados, incluyendo el tamaño del lote, la dirección, el precio de entrada, el SL, el TP y sin ningún comentario personalizado. Esto garantiza que las operaciones se realicen siguiendo un marco de riesgo coherente, independientemente de la tendencia del mercado.

//+------------------------------------------------------------------+
//| OnTick                                                           |
//+------------------------------------------------------------------+
void OnTick() {
   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(_Symbol, _Period, 0);

   if (currentBarTime != lastBarTime) {
      lastBarTime = currentBarTime;

      DetectSwings(LeftBars, RightBars);
      ApplyDijkstra();
      VisualizeSwings();
      GenerateSignalAndTrade();
      FilterAndMarkValidSwings(swingPoints);
      CleanOldSwingObjects();

   }
}

Por último, la función `OnTick()` es el bucle de ejecución principal del EA, que se activa con cada nuevo tick. Para evitar un procesamiento innecesario, utiliza una variable estática `lastBarTime` para detectar si se ha formado una nueva barra comparándola con la hora de apertura de la barra actual. Si se detecta una nueva barra, se actualiza `lastBarTime` y se ejecuta la lógica principal: detectar nuevos máximos y mínimos de oscilación (DetectSwings), aplicar el algoritmo de Dijkstra para encontrar la ruta más eficiente entre las oscilaciones (ApplyDijkstra), mostrar visualmente las oscilaciones y sus conexiones en el gráfico (VisualizeSwings), generar y ejecutar señales de trading basadas en la dirección de la ruta (GenerateSignalAndTrade), filtrar los puntos de oscilación invalidados (FilterAndMarkValidSwings) y, por último, limpiar los objetos de oscilación antiguos para mantener el gráfico despejado (CleanOldSwingObjects).

Esta estructura garantiza que el EA procese la estructura del mercado de forma inteligente.



Resultados del backtest

El backtest se evaluó en el marco temporal de 1 hora durante un periodo de prueba de dos meses (del 1 de mayo de 2025 al 20 de junio de 2025), con los siguientes parámetros de entrada:

  • TP en puntos = 1000
  • Stop loss = 385
  • Lotes de entrada = 0,01
  • Barras a la izquierda = 3
  • Barras a la derecha = 3
  • Oscilación máxima = 50
  • Búfer de puntos = 10,0
  • Slippage = 5

Conclusión

En resumen, hemos creado un Asesor Experto MQL5 totalmente funcional que interpreta la estructura de los mercados financieros utilizando el algoritmo de Dijkstra aplicado a los máximos y mínimos de oscilación como nodos del gráfico. El sistema detecta puntos de oscilación significativos en cada nueva barra, descarta los que ya han sido superados por el precio y trata las oscilaciones válidas como vértices en un algoritmo de búsqueda de rutas. A continuación, utiliza la distancia de precios como pesos de las aristas para calcular la ruta más eficiente a través de la estructura del mercado, determinando así la dirección más probable del movimiento de los precios.

A partir de este análisis, el EA genera señales de trading direccionales y ejecuta operaciones con niveles de stop loss y take profit calculados adecuadamente en función de la distancia entre los puntos de oscilación. Se trazan herramientas visuales, como flechas y líneas de tendencia, para reflejar tanto los puntos de oscilación detectados como las trayectorias calculadas, mientras que las rutinas de limpieza garantizan que el gráfico se mantenga claro y actualizado.

En conclusión, este EA va más allá del trading tradicional basado en indicadores al integrar un algoritmo basado en grafos en el análisis de la evolución de los precios, lo que permite tomar decisiones de trading más estructuradas y lógicas. Al ajustar las entradas de las operaciones a la geometría de las oscilaciones del mercado y garantizar que cada punto de referencia solo se utilice una vez, salvo que sea válido, el sistema imita la forma en que el precio fluye de manera natural a través de los niveles de soporte y resistencia. El diseño modular, que incluye funciones de detección, validación, búsqueda de rutas, ejecución y visualización, también facilita la mejora, la ampliación o la realización de pruebas retrospectivas de la estrategia. Este proyecto sienta las bases para un sistema de negociación inteligente y adaptativo que considera la evolución de los precios como una red navegable, combinando la teoría de las estructuras de datos con el comportamiento del mercado.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18760

Archivos adjuntos |
Dijkstras_Algo.mq5 (21.01 KB)
Simulación de mercado: Position View (IV) Simulación de mercado: Position View (IV)
Aquí comenzaremos a unir diversos componentes o aplicaciones que antes estaban completamente aisladas entre sí. Aunque Chart Trade, el Indicador de Mouse y el Asesor Experto ya mantenían cierta relación, todavía no había una forma de observar directamente en el gráfico las posiciones abiertas en el servidor de trading, muchas veces usando un sistema de órdenes cruzadas. A partir de este momento, esto empieza a ser posible, abriendo diversas puertas a nuevas ideas e implementaciones futuras. Aunque apenas estamos comenzando a poner estos componentes en funcionamiento, ya tendremos un rumbo que seguir.
Símbolos personalizados MQL5: Creamos un símbolo de barras 3D Símbolos personalizados MQL5: Creamos un símbolo de barras 3D
Este artículo ofrece una guía detallada para crear el innovador indicador 3DBarCustomSymbol.mq5, que genera símbolos personalizados en MetaTrader 5 que combinan precio, tiempo, volumen y volatilidad en una única representación tridimensional. Asimismo, analizaremos los fundamentos matemáticos, la arquitectura del sistema y los aspectos prácticos de su implementación y aplicación en estrategias de negociación.
Del básico al intermedio: Acceso aleatorio (I) Del básico al intermedio: Acceso aleatorio (I)
En este artículo tendremos nuestra primera experiencia con el acceso aleatorio al contenido de un archivo. Esto apunta tanto a la escritura como a la lectura de información y datos almacenados en un archivo. Sin embargo, como este tema es bastante extenso para explicarlo en un único artículo, aquí solo haremos una introducción a esta cuestión del acceso aleatorio.
Exploramos modelos de regresión para inferencia causal y operaciones bursátiles Exploramos modelos de regresión para inferencia causal y operaciones bursátiles
Este artículo explora la posibilidad de usar modelos de regresión en el trading algorítmico. Los modelos de regresión, a diferencia de la clasificación binaria, permiten crear estrategias de trading más flexibles mediante la evaluación cuantitativa de los cambios de precio previstos.