English Русский Português
preview
Operaciones de arbitraje en Forex: Panel de evaluación de correlaciones

Operaciones de arbitraje en Forex: Panel de evaluación de correlaciones

MetaTrader 5Sistemas comerciales |
211 10
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Introducción

Mi camino en el trading algorítmico de divisas ha sido largo: años de ensayo, error, noches sin dormir y raras pero brillantes victorias. Durante este tiempo he probado de todo, desde indicadores sencillos hasta complejos sistemas basados en el aprendizaje automático. Pero una idea me mantenía alerta: ¿y si el mercado no es tan ideal como parece? ¿Y si el caótico parpadeo de las cotizaciones esconde grietas por las que uno puede asomarse a su esencia y, tal vez, sacar algún beneficio? Así es como llegué al análisis de arbitraje, un enfoque que fue para mí un auténtico descubrimiento, casi una revelación.

Hoy quiero compartir mi creación: un sistema para el análisis de los precios justos de divisas con la estimación de arbitraje, escrito en MQL5 para MetaTrader 5. Todo empezó con una simple pregunta que no me dejaba dormir una noche: ¿y si pudiéramos calcular los tipos de cambio "correctos": no los que nos da el mercado, sino los que deberíamos tener? De esta curiosidad nació un panel que no solo pone de relieve las desviaciones de los precios respecto a sus valores "ideales", sino que también busca trayectorias de arbitraje triangulares: justo los momentos en los que el mercado baja la guardia y podemos aprovecharnos de ello.

Convertir la teoría en código resultó ser una auténtica aventura, con muchos escollos, innumerables iteraciones y momentos en los que estuve a punto de rendirme. Hoy le contaré cómo los cálculos simples se convirtieron en un sistema vivo, cómo el mercado me puso a prueba una y otra vez y cómo aprendí a visualizar los resultados para que hablaran por sí solos. Asimismo, verá ejemplos reales de cómo funciona el panel: con números, señales y triángulos de arbitraje. Además, analizaré honestamente sus puntos fuertes y débiles, y compartiré mi experiencia en su adaptación para diferentes estrategias, desde operaciones individuales hasta cuadrículas multidivisa, que para mí se han convertido en un verdadero campo de experimentación.

Mi objetivo no es simplemente presumir del código: quiero ofrecerle una herramienta, algo tangible que pueda coger, retorcer en sus manos, perfeccionar a su gusto. Espero que este panel no solo le inspire nuevas ideas, sino que también le ayude a ver el mercado desde un ángulo diferente: no como un caos, sino como un rompecabezas que puede resolverse.



Los fondos de cobertura de arbitraje y el alcance de las operaciones de arbitraje en divisas

Cuando hablamos de arbitraje en el mercado de divisas, resulta imposible no mencionar a los principales actores en este ámbito: los fondos de cobertura especializados que han convertido el análisis matemático de los desajustes en una industria multimillonaria. Su experiencia y escala de operaciones arrojan luz sobre lo decisivo que puede ser nuestro humilde indicador de arbitraje en las manos adecuadas.

Titanes del arbitraje de divisas

Entre los fondos de cobertura de arbitraje, destacan especialmente algunos grandes actores. Por ejemplo, Millennium Management, con más de 50.000 millones de dólares en activos gestionados, usa un enfoque multiestrategia en el que el arbitraje estadístico en los mercados de divisas supone una de las principales áreas de interés. Sus equipos de analistas cuantitativos desarrollan modelos conceptualmente similares a nuestra matriz de precios justos, pero con un marco matemático y una capacidad de cálculo mucho más sofisticados.

Citadel Securities, Renaissance Technologies y DE Shaw son otros gigantes que usan activamente el arbitraje de divisas. Su ventaja no reside solo en el capital, sino también en la tecnología: acceso directo al mercado interbancario, servidores con latencia mínima (co-location) y canales de comunicación dedicados entre las principales plataformas comerciales. Algunos fondos han invertido cientos de millones de dólares en infraestructuras que reducen la latencia de ejecución de órdenes a microsegundos, un área en la que nuestro indicador de MetaTrader 5 lamentablemente no puede competir.



Fundamentos teóricos del análisis del precio justo y del arbitraje

La idea de analizar los precios justos siempre me ha resultado estimulante, especialmente desde el día en que me pregunté: ¿y si el mercado a veces se equivoca? Años de comercio de divisas me han enseñado que los tipos de cambio no son solo números aleatorios en una pantalla, es una compleja red de interrelaciones, en la que cada par tira de los demás como hilos de un enorme lienzo. Y si aparece un agujero en alguna parte de ese lienzo (una discrepancia), entonces hay una oportunidad de ganar dinero.

Un precio justo es esencialmente el tipo de cambio "ideal" de un par de divisas, que es el que debería darse si el mercado fuera perfectamente racional. Para pares directos como el EURUSD, todo parece sencillo: tomamos Bid y Ask, los promediamos, y ese es el punto de referencia. Pero el mercado de divisas no se limita a las cotizaciones directas. Hay pares cruzados como EURJPY que se pueden calcular a través de los pares básicos. Y entonces me acordé del arbitraje triangular, un concepto antiguo pero aún vivo:

EUR/USD * USD/JPY = EUR/JPY

Si la cotización real de EURJPY no coincide con este cálculo, es que el mercado ha infravalorado o sobrevalorado algo. Recuerdo la primera vez que lo comprobé manualmente, sentado con una calculadora, comparando cotizaciones en una cuenta demo. Las discrepancias eran ínfimas, de fracciones de porcentaje, pero el mero hecho de que existieran encendió en mí, digamos, una chispa. Fue como encontrar una pequeña grieta en la pared que pasa desapercibida a todos, pero a través de la cual puedes asomarte.

Y luego seguí profundizando en esa dirección. Las cotizaciones directas son solo la punta del iceberg. Para comprender la "justedad" de los precios, necesitamos construir una matriz de todas las relaciones monetarias. Imagínese que tenemos 8 divisas: EUR, USD, GBP, JPY, etc. Cada una puede expresarse a través de otra mediante los pares disponibles. Por ejemplo, si tenemos EURUSD y GBPUSD, podemos calcular EURGBP como:

EURGBP = EURUSD / GBPUSD

Y si añadimos pares inversos como USDJPY y JPYUSD, podemos refinar los cálculos hasta el infinito. Me gustaba especialmente la idea del "calibrado dinámico". ¿Y si en lugar de basarnos en un único par, tomáramos todas las cotizaciones disponibles y las "promediásemos" en un único sistema? Por ejemplo, para EURJPY, podemos utilizar no solo EURUSD y USDJPY, sino también otros caminos: EURGBP y GBPJPY, o incluso cadenas más largas a través de CHF o AUD. Es como montar un rompecabezas en el que cada pieza debe encajar perfectamente en su sitio. 

Pero, ¿cómo calcular un par entre todos los demás que contienen las mismas divisas? Tomemos el EURUSD. Tenemos un tipo de cambio directo, digamos 1. 10. Pero podemos comprobarlo a través de cruces: a través de GBP (EURGBP y GBPUSD), a través de JPY (EURJPY y USDJPY), a través de CHF y así sucesivamente. En teoría, todos estos caminos deberían dar el mismo valor. Si no es así, aquí tendremos la señal. Empecé a pensar: ¿y si construimos una matriz de 8x8, donde cada celda sea el tipo de cambio de una divisa frente a otra, y logramos que esta matriz "converja" a una única solución? Sería como resolver un sistema de ecuaciones en el que cada par es una ecuación y las variables son los tipos de cambio. Por ejemplo: 

EURUSD = 1. 10

USDJPY = 150

EURJPY = EURUSD * USDJPY = 165

Si el EURJPY real = 166, entonces habrá un sesgo en alguna parte. Y hay docenas de "ecuaciones" de este tipo, según el número de pares. Podemos tomar la media de todos los caminos, buscar la mediana o aplicar un método iterativo, como en la resolución de sistemas de ecuaciones lineales, para encontrar el "mejor" precio. 

El arbitraje se convirtió así en la segunda pieza de este rompecabezas. El arbitraje triangular consiste en realizar un ciclo de tres pares (EURUSD -> USDJPY -> EURJPY) y volver con beneficios. Pero me hizo pensar: ¿y si buscamos caminos más largos, no solo triángulos? Por ejemplo, ¿con cuatro o cinco pares? La teoría sugería que cuanto más largo fuera el camino, más ruido habría, pero también más interesantes serían los hallazgos. Cada concepto se ponía a prueba en la práctica: pasaba los datos por el terminal, veía dónde divergían los cálculos de la realidad y aprendía algo de ello. 

Para mí se convirtió en algo más que un conjunto de fórmulas; era una filosofía: el mercado supone un organismo vivo lleno de pequeños errores, y si aprendemos a notarlos, podemos vivir en armonía con él. Espero que mis reflexiones le inspiren para profundizar y ver las mismas posibilidades.


Implementación de un panel arbitral: de la idea al código

Convertir la teoría en código siempre supone un reto. He aquí cómo he implementado mi panel de arbitraje en MQL5:

void CalculateFairValues() {
    GetCurrentMarketRates();
    InitializeCurrencyMatrix();
    FillCurrencyMatrixFromDirectRates();
    CalculateCrossRatesArbitrage();
    CalculateDiscrepancies();
    FindArbitrageOpportunities();
    GenerateSignals();
    UpdateDisplay();
}
Esta función es el corazón del sistema. Recopila las cotizaciones actuales, construye una matriz de tipos de cambio justos y busca oportunidades. Recuerdo lo mucho que me peleé con CalculateCrossRatesArbitrage: el mercado no siempre ofrecía pares directos, y tenía que compensarlo con inversiones:
if(!g_market_rates[i].is_direct) {
    double temp_bid = 1.0 / g_market_rates[i].ask;
    double temp_ask = 1.0 / g_market_rates[i].bid;
    g_market_rates[i].bid = temp_bid;
    g_market_rates[i].ask = temp_ask;
}
Para el arbitraje, escribí una lógica aparte:
double CalculateArbitrageProfit(int pair1_idx, int pair2_idx, int pair3_idx, double amount) {
    double result = amount;
    // Логика расчета через Bid/Ask для треугольника
    return result;
}

Este método cuenta el beneficio de un ciclo como EURUSD->USDJPY->EURJPY. Al principio, los resultados eran caóticos: los spreads se comían todos los beneficios. Tuve que añadir un InpArbitrageThreshold para filtrar el ruido.

A continuación me ocupé de la matriz de tipos de cambio justos. InitialiseCurrencyMatrix establece las unidades en la diagonal (EUR/EUR = 1), mientras que FillCurrencyMatrixFromDirectRates la rellena con cotizaciones directas. Pero la verdadera magia está en CalculateCrossRatesArbitrage

void CalculateCrossRatesArbitrage() {
    for(int iterations = 0; iterations < 3; iterations++) {
        for(int i = 0; i < g_currencies_count; i++) {
            for(int j = 0; j < g_currencies_count; j++) {
                if(i == j) continue;
                for(int k = 0; k < g_currencies_count; k++) {
                    if(k == i || k == j) continue;
                    if(g_currency_matrix[i][k] != 0 && g_currency_matrix[k][j] != 0) {
                        double triangleRate = g_currency_matrix[i][k] * g_currency_matrix[k][j];
                        if(g_currency_matrix[i][j] == 0) g_currency_matrix[i][j] = triangleRate;
                        else g_currency_matrix[i][j] = (g_currency_matrix[i][j] * 0.7 + triangleRate * 0.3);
                        g_currency_matrix[j][i] = 1.0 / g_currency_matrix[i][j];
                    }
                }
            }
        }
    }
}

Aquí es donde itero tres veces las divisas, afinando los tipos de cruce mediante triángulos. La idea es sencilla: si tengo EURUSD y USDJPY, calculo EURJPY. Si ya existe un EURJPY directo, tomaremos una media ponderada: 70% del valor antiguo, 30% del valor nuevo. Las tres iteraciones son un compromiso entre precisión y velocidad. Recuerdo haber probado esto en EURJPY: al principio la divergencia era del 0,1%, después de tres ciclos suponía el 0,01%. Pero el mercado no espera, así que me detuve ahí.

¿Y cómo sacar un par de entre todos los pares? Digamos que quiero un EURUSD "justo". Y yo tengo:

  • EURGBP y GBPUSD: EURUSD = EURGBP * GBPUSD
  • EURJPY y USDJPY: EURUSD = EURJPY / USDJPY
  • EURCHF y USDCHF: EURUSD = EURCHF / USDCHF

Podemos tomar todos estos caminos, calcular la media o la mediana. O ir más allá: realizar una convergencia iterativa, como en el método Gauss-Seidel para sistemas de ecuaciones. Por ejemplo:

double CalculatePairFairValue(string base, string quote) {
    int baseIdx = GetCurrencyIndex(base);
    int quoteIdx = GetCurrencyIndex(quote);
    double sum = 0.0;
    int count = 0;
    for(int k = 0; k < g_currencies_count; k++) {
        if(k == baseIdx || k == quoteIdx) continue;
        if(g_currency_matrix[baseIdx][k] != 0 && g_currency_matrix[k][quoteIdx] != 0) {
            sum += g_currency_matrix[baseIdx][k] * g_currency_matrix[k][quoteIdx];
            count++;
        }
        if(g_currency_matrix[k][baseIdx] != 0 && g_currency_matrix[quoteIdx][k] != 0) {
            sum += g_currency_matrix[quoteIdx][k] / g_currency_matrix[k][baseIdx];
            count++;
        }
    }
    return (count > 0) ? sum / count : g_currency_matrix[baseIdx][quoteIdx];
}

Cada fragmento de código supone una lucha con la realidad. Pero cuando vi las primeras señales y triángulos, me di cuenta de que funcionaba.


Interfaz gráfica: visualización de las posibilidades

Sin una visualización clara, el código seguiría siendo solo un conjunto de números. Así que puse toda el alma en la interfaz:

bool CreateGraphics() {
    ObjectCreate(0, "FVPanel_Main", OBJ_RECTANGLE_LABEL, 0, 0, 0);
    ObjectSetInteger(0, "FVPanel_Main", OBJPROP_XDISTANCE, 20);
    ObjectSetInteger(0, "FVPanel_Main", OBJPROP_YDISTANCE, 20);
    ObjectSetInteger(0, "FVPanel_Main", OBJPROP_XSIZE, 900);
    ObjectSetInteger(0, "FVPanel_Main", OBJPROP_YSIZE, 560);
    ObjectSetInteger(0, "FVPanel_Main", OBJPROP_BGCOLOR, InpBgColor);
    CreateText("FVPanel_Header", 220, 30, "АНАЛИЗ СПРАВЕДЛИВЫХ ЦЕН ВАЛЮТ", InpHeaderTextColor, 12, true);
    CreateText("FVPanel_Signals", 60, 60, "СИГНАЛЫ ОТКЛОНЕНИЯ ОТ СПРАВЕДЛИВОЙ ЦЕНЫ", InpBuySignalColor, 10, true);
    CreateText("FVPanel_Arbitrage", 460, 60, "АРБИТРАЖНЫЕ ВОЗМОЖНОСТИ", InpArbitrageColor, 10, true);
    return true;
}

El panel se divide en tres zonas: señales de desviación, caminos de arbitraje y matriz de tipos de cambio. Señales como "EURUSD: +0,04% - VENTA" se resaltan en verde o rojo, mientras que triángulos de arbitraje como "EURUSD->USDJPY->EURJPY: +0,02%" muestran la dirección de las transacciones de un solo vistazo.

La matriz de tipos de cambio es mi favorita. Muestra las 8 divisas en una tabla con las desviaciones de los precios justos resaltadas a color. Recuerdo que me pasaba horas retocando los tipos de letra y las sangrías para que todo quedara perfectamente ordenado.

La actualización opera a través de un temporizador:

void OnTimer() {
    g_timer_counter++;
    if(g_timer_counter >= InpUpdateInterval) {
        CalculateFairValues();
        g_timer_counter = 0;
    }
}

Esto hace que el sistema esté vivo: los datos fluyen en tiempo real, por así decirlo, como el pulso del mercado.


Resultados del rendimiento y evaluación honesta

No he probado el sistema en datos reales todavía: estoy creando un robot que lo utiliza, y este es ya mi 5-6 intento de crear un robot de arbitraje operativo. Aquí tiene un vídeo del funcionamiento del sistema:



Reflexiones sobre precios justos y matrices

¿Cómo obtener los precios justos a partir de matrices? Esto se convirtió en mi obsesión. La matriz 8x8 es como un mapa del mercado. Cada celda es el tipo de cambio de una divisa respecto a otra. Pero los datos contienen ruido: el bróker no da todos los pares, las cotizaciones saltan, los spreads se interponen. Así que empecé a experimentar.

Enfoque 1: Media de los caminos

Para EURUSD, tomamos todos los cruces:

  • EURGBP * GBPUSD
  • EURJPY / USDJPY
  • EURCHF / USDCHF

Calculamos la media. Simple, pero ignora los pesos: después de todo, EURJPY puede ser más líquido que EURCHF.

Enfoque 2: Convergencia iterativa

Como en el método Gauss-Seidel: actualizamos cada celda de la matriz hasta que los cambios son mínimos. El código anterior (CalculateCrossRatesArbitrage) supone una versión simplificada. Pero pensé más a largo plazo: ¿y si añadimos la ponderación según el volumen comercial? ¿O consideramos la volatilidad del par?

Enfoque 3: Minimización de errores

Podemos plantear un problema de optimización: minimizar la suma de cuadrados de las discrepancias entre las cotizaciones actuales y la matriz. Por ejemplo:

Ошибка = Σ (MarketRate[i] - MatrixRate[i])^2

Podemos resolver usando el descenso de gradiente o algo como SVD. Puede que ya resulte largo para MQL5, pero en Python yo lo intentaría.

Enfoque 4: Caminos largos

¿Por qué solo triángulos? Podemos calcular EURUSD a través de la cadena EURGBP->GBPCHF->CHFUSD. Cuantos más caminos haya, más precisión tendremos, pero también más ruido. En la práctica, las cadenas largas se ahogan en los spreads y comisiones como en un pantano....

Estas reflexiones son teóricas por ahora. El mercado real no supone un libro de texto y los precios perfectos siguen siendo un sueño. Pero incluso acercarse a ellos ya representa un paso adelante.


Conclusión

Partiendo de la idea de precios justos, no esperaba llegar a este sistema. Ha sido un viaje de prueba y error, desde fallos en la matriz hasta ciertas frustraciones con el arbitraje. Pero cada paso me ha enseñado algo.

El mercado no es un caos, sino un mecanismo complejo. Los precios justos y el arbitraje son como una lupa a través de la cual se pueden ver sus detalles. La visualización da vida a los datos, mientras que la adaptación los hace flexibles. El panel no es perfecto, pero ya inspira confianza.

Tengo un montón de planes, desde ML hasta pruebas reales. Forex no se queda inmóvil, y yo tampoco. Espero que mi experiencia le inspire para mirar entre los bastidores del mercado y encontrar sus propias "injusticias".

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/17422

Archivos adjuntos |
Sergey Chalyshev
Sergey Chalyshev | 18 mar 2025 en 21:03
Vladimir Gulakov #:

Absolutamente sí, ya se ha comprobado todo. Además, el broker puede ajustar el tamaño del spread a su favor en cualquier momento, cosa que hacen.

Usted está confundiendo a los distribuidores con los corredores.

El corredor no puede ajustar - es un delito penal.

Aprenda la ley.

Vitaly Muzichenko
Vitaly Muzichenko | 18 mar 2025 en 21:09
Sergey Chalyshev #:

Confundes concesionarios con corredores.

Un corredor no puede ajustar - es un delito penal.

Aprenda las reglas del juego.

Aquí también se confunden órdenes y posiciones, nada sorprendente.

Vladimir Gulakov
Vladimir Gulakov | 19 mar 2025 en 17:04
Sergey Chalyshev #:

Confundes concesionarios con corredores.

Un corredor no puede ajustar - es un delito penal.

Aprenda las reglas del juego.

Muy bien, que sea un distribuidor.

pivomoe
pivomoe | 22 mar 2025 en 22:21
Yevgeniy Koshtenko #:

y la discrepancia entre lo sintético y lo real en comparación con la desviación típica suele ser enorme....

¿Puedes darme un ejemplo del registro de una gran discrepancia?

pivomoe
pivomoe | 23 mar 2025 en 11:36
Yevgeniy Koshtenko #:

Habrá un segundo artículo al respecto

He mirado el código del segundo artículo. No está relacionado con el primero de ninguna manera. En ninguna parte del código hay siquiera un intento de entrar por debajo / por encima del precio justo. Es sólo un netter habitual lanzando una hoja de órdenes a merced del destino.

Automatización de estrategias de trading en MQL5 (Parte 7): Creación de un EA para el comercio en cuadrícula con escalado dinámico de lotes Automatización de estrategias de trading en MQL5 (Parte 7): Creación de un EA para el comercio en cuadrícula con escalado dinámico de lotes
En este artículo, creamos un asesor experto de trading con cuadrículas en MQL5 que utiliza el escalado dinámico de lotes. Cubrimos el diseño de la estrategia, la implementación del código y el proceso de backtesting. Por último, compartimos conocimientos clave y mejores prácticas para optimizar el sistema de comercio automatizado.
Cierres parciales condicionales (Parte 1): Creación de la clase base Cierres parciales condicionales (Parte 1): Creación de la clase base
En este artículo implementaremos un nuevo método para la gestión de posiciones, parecido a los cierres parciales "simples" que implementamos anteriormente, pero con una diferencia importante. En lugar de basarse en niveles de takeprofit fijos, este enfoque aplica los cierres parciales al momento de cumplirse cierta condición específica. De ahí su nombre: "Cierres parciales condicionales". En esta primera parte de la implementación en MQL5 veremos cómo funciona esta técnica de gestión de posiciones.
Redes neuronales en el trading: Clusterización doble de series temporales (DUET) Redes neuronales en el trading: Clusterización doble de series temporales (DUET)
El framework DUET ofrece un enfoque innovador del análisis de series temporales, combinando la clusterización temporal y por canales para revelar patrones ocultos en los datos analizados. Esto permite a los modelos adaptarse a los cambios a lo largo del tiempo y mejorar la calidad de las previsiones eliminando el ruido.
Gestor de riesgos profesional remoto para Forex en Python Gestor de riesgos profesional remoto para Forex en Python
Hoy crearemos un gestor de riesgos profesional remoto para Forex en Python, y los desplegaremos en un servidor paso a paso. En el transcurso del artículo entenderemos cómo gestionar programáticamente los riesgos en Forex, y cómo no agotar más nuestro depósito en el mundo de las divisas.