English Русский 中文 Deutsch 日本語
preview
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 32): Motor de reconocimiento de velas japonesas en Python (II) — Detección con TA-Lib

Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 32): Motor de reconocimiento de velas japonesas en Python (II) — Detección con TA-Lib

MetaTrader 5Sistemas comerciales |
35 0
Christian Benjamin
Christian Benjamin

Contenido



Introducción

En mi artículo anterior, hablé sobre el uso de Python para la detección de patrones de velas japonesas. Comenzamos empleando técnicas de detección manual, en las que cada patrón de velas japonesas se codificó explícitamente. En este enfoque, cada vez que MQL5 envía datos a Python (incluidos los precios de apertura, cierre, máximo, mínimo y marca de tiempo), Python procesa esta información para identificar el patrón mediante cálculos.

En este artículo, llevamos la metodología un paso más allá integrando la biblioteca TA-Lib para Python, que puede detectar automáticamente más de 60 patrones de velas japonesas. Además, lo combinaremos con mplfinance y matplotlib para trazar gráficos de velas japonesas. 

Aquí tienes una descripción general de las principales bibliotecas utilizadas:

from flask import Flask, reque
st, jsonify, send_file
import numpy as np
import pandas as pd
import talib
import json
import matplotlib.pyplot as plt
import mplfinance as mpf

Aclaremos cada parte a continuación.

Flask

from flask import Flask, request, jsonify, send_file

  • Un framework web ligero para Python que se utiliza para crear aplicaciones web o API.
  • La clase principal para crear una instancia de una aplicación Flask.
request: Se utiliza para acceder a los datos de las solicitudes entrantes (por ejemplo, datos enviados mediante POST o GET).

jsonify: Convierte las estructuras de datos de Python al formato JSON para las respuestas.

send_file: Envía archivos desde el servidor al cliente, lo cual resulta útil para descargar imágenes o informes generados.

import numpy as np

  •  NumPy: Una biblioteca fundamental para el cálculo numérico en Python.
  • np: Alias habitual utilizado para NumPy.

Gestiona de forma eficiente operaciones con matrices, funciones matemáticas y la manipulación de datos.

import pandas as pd

  • Pandas: Una potente biblioteca para la manipulación de datos.
  • pd: Alias estándar.

Se utiliza para estructuras de datos como DataFrames, que son ideales para gestionar datos de series temporales, como los datos de gráficos de velas japonesas.

import talib

  • TA-Lib: Biblioteca de análisis técnico en Python.

Proporciona funciones para calcular automáticamente una amplia gama de indicadores técnicos y algoritmos de reconocimiento de patrones de velas japonesas.

import json

  • La biblioteca integrada de Python para trabajar con datos JSON.

Analiza datos JSON de las solicitudes o convierte objetos de Python a JSON para las respuestas.

import matplotlib.pyplot as plt

  • Matplotlib: Una biblioteca de gráficos.
  • pyplot: Un módulo de Matplotlib que ofrece una interfaz similar a la de MATLAB.
  • plt: Alias estándar.
  • Se utiliza para crear gráficos y diagramas estáticos, animados o interactivos, como los gráficos de velas japonesas.

import mplfinance as mpf

  • mplfinance: Una biblioteca especializada en la visualización de datos financieros, especialmente gráficos de velas japonesas y gráficos OHLC.
  • mpf: Alias utilizado por comodidad.
  • Facilita la creación de gráficos financieros con opciones avanzadas de personalización.

A continuación se enumeran las ventajas de utilizar TA-Lib para la detección de patrones:

VentajaDescripción
AutomatizaciónDetecta automáticamente más de 60 patrones de velas japonesas sin necesidad de codificación manual.
EficienciaReconocimiento de patrones más rápido, adecuado para análisis en tiempo real.
ExactitudUtiliza algoritmos probados para una detección de patrones fiable.
Compatibilidad Se integra fácilmente con otras bibliotecas de Python para análisis y visualización.
Facilidad de uso Simplifica la implementación mediante llamadas a funciones directas.


Partiendo de nuestra última discusión

Como ya he mencionado, el sistema anterior era un sistema de reconocimiento de velas japonesas programado manualmente en Python. Vamos a hacer un repaso general de ese sistema.

  • Asesor experto de MetaTrader 5 (el “Orquestador del gráfico”)

En MetaTrader 5, el EA supervisa cada nueva barra y, a continuación, recopila inmediatamente los datos de apertura, máximo, mínimo, cierre y marca de tiempo de las últimas 31 velas. Los agrupa en una carga útil JSON compacta y la envía a través de WebRequest a nuestro servicio local de detección de patrones. Cuando el servidor responde con una lista de nombres de velas japonesas;

EA de MQL5 ➔ (OHLC JSON POST) ➔ Servidor de Python

El EA dibuja automáticamente cada etiqueta en el máximo de la barra, utilizando el tamaño y el color de fuente que hayas elegido, y activa una alerta de MetaTrader 5 cada vez que se detecte un patrón válido. Además, en cada tick se eliminan las etiquetas anteriores para mantener el gráfico impecable, y al eliminarla, se eliminan todos sus objetos, de modo que no queda nada.

El EA de MQL5 dibuja etiquetas y activa alertas

  • Servidor Python Flask: Motor de detección de patrones

En el backend, creé una aplicación Flask que sirve al endpoint /patterns. Ingiere la matriz JSON de datos OHLC, convierte las marcas de tiempo UNIX a fechas y horas UTC y construye un DataFrame de pandas para una indexación fiable. A continuación, aplico mi conjunto de reglas clásicas de patrones de velas japonesas —Doji, Martillo, Envolvente, Harami, Estrella de la Mañana, Estrella de la Tarde y otros— íntegramente en Python, con funciones claras y verificables para cada patrón.

def detect_patterns(df):
    pats = ["None"]*len(df)
    for i in range(len(df)):
        o,h,l,c = df.iloc[i][["OPEN","HIGH","LOW","CLOSE"]]
        body = abs(c-o); rng = max(h-l,1e-6)
        lower = min(o,c)-l; upper = h-max(o,c)
        # doji
        if body/rng <= 0.1:
            pats[i] = "doji"; continue
        # hammer
        if lower>=2*body and upper<=body:
            pats[i] = "hammer"; continue
        # shooting star
        if upper>=2*body and lower<=body:
            pats[i] = "shootingstar"; continue
        # bullish engulfing
        if i>0:
            po,pc = df.iloc[i-1][["OPEN","CLOSE"]]
            if pc<po and c>o and o<=pc and c>=po:
                pats[i] = "bullishengulfing"; continue
        # bearish engulfing
        if i>0:
            po,pc = df.iloc[i-1][["OPEN","CLOSE"]]
            if pc>po and c<o and o>=pc and c<=po:
                pats[i] = "bearishengulfing"; continue
        # harami
        if i>0:
            po,pc = df.iloc[i-1][["OPEN","CLOSE"]]
            if pc<po and o<c and o>pc and c<po:
                pats[i] = "bullishharami"; continue
            if pc>po and o>c and o<pc and c>po:
                pats[i] = "bearishharami"; continue
        # morning star
        if i>1:
            o1,c1 = df.iloc[i-2][["OPEN","CLOSE"]]
            o2,c2 = df.iloc[i-1][["OPEN","CLOSE"]]
            if c1<o1 and abs(c2-o2)<(df.iloc[i-1]["HIGH"]-df.iloc[i-1]["LOW"])*0.3 \
               and c>o2 and c>(o1+c1)/2:
                pats[i] = "morningstar"; continue
        # evening star
        if i>1:
            o1,c1 = df.iloc[i-2][["OPEN","CLOSE"]]
            o2,c2 = df.iloc[i-1][["OPEN","CLOSE"]]
            if c1>o1 and abs(c2-o2)<(df.iloc[i-1]["HIGH"]-df.iloc[i-1]["LOW"])*0.3 \
               and c<o2 and c<(o1+c1)/2:
                pats[i] = "eveningstar"; continue
    return pats

Tras etiquetar cada vela, contabilizo todas las apariciones distintas de «None» en un breve resumen del registro y mido el tiempo de procesamiento en milisegundos. Por último, devuelvo un objeto JSON que contiene tanto la lista completa de nombres de patrones (uno por barra) como el registro. Esta separación permite que el procesamiento intensivo de datos se realice en Python —donde resulta más fácil mantenerlo y ampliarlo—, mientras que MetaTrader 5 sigue centrándose en la entrada y salida de datos de los gráficos y en su visualización.

Servidor Python ➔ (patrones y log JSON) ➔ EA de MQL5


Comprender Ta-Lib

TA-Lib (Technical Analysis Library) es una biblioteca de código abierto ampliamente utilizada por traders, inversores y analistas para realizar cálculos técnicos complejos y desarrollar estrategias de trading. Desarrollado originalmente por Mario Fortier, está escrito en ANSI C y ofrece un conjunto completo de más de 200 indicadores técnicos, incluidos ADX, MACD, RSI, oscilador estocástico, bandas de Bollinger, entre otros, así como la capacidad de reconocer más de 60 patrones de velas japonesas. Su núcleo en C/C++ proporciona una API que también es accesible a través de Python, lo que facilita una integración perfecta en diversas aplicaciones. Desde su lanzamiento inicial en 2001 bajo la licencia BSD, TA-Lib se ha consolidado como una herramienta estable y fiable, con algoritmos que han resistido el paso del tiempo y que siguen utilizándose ampliamente tanto en contextos de código abierto como comerciales.

En este sistema, las extensas funciones de reconocimiento de patrones de TA-Lib se integran en un proceso de análisis basado en Python para automatizar la detección de patrones de velas japonesas. El sistema carga dinámicamente todas las funciones relevantes de patrones de velas japonesas desde TA-Lib, lo que le permite identificar más de 60 patrones diferentes dentro de los datos de mercado entrantes. Al combinar los algoritmos fiables de TA-Lib con una lógica de filtrado personalizada, detecta con precisión las señales alcistas y bajistas, que luego se superponen visualmente en los gráficos de velas japonesas mediante mplfinance. Todo el proceso está integrado en un servicio web Flask, lo que facilita el procesamiento de datos en tiempo real, el reconocimiento de patrones y la visualización. Esta configuración ejemplifica cómo la completa biblioteca de TA-Lib puede utilizarse junto con herramientas modernas de Python para crear sistemas sofisticados y automatizados de análisis de trading que complementan a la perfección las estrategias MQL5.

A continuación se muestra una guía paso a paso sobre cómo instalar TA-Lib en su ordenador.

Paso 1: Descargue e instale "TA-Lib.whl".

  • Para Windows:

Descarga los binarios precompilados de una fuente confiable. Por ejemplo, descarga el archivo .whl adecuado —como TA_Lib‑0.4.0‑cp39‑cp39‑win_amd64.whl—, que es compatible con Python 3.9 en un sistema Windows de 64 bits. El término ".whl apropiado" significa que debe seleccionar el archivo que coincida tanto con su versión de Python como con la arquitectura de su sistema para garantizar la compatibilidad.

Instalar mediante pip:

pip install path\to\your\downloaded\file.whl

  • Para macOS:

Instala la biblioteca C de TA-Lib mediante Homebrew.

brew install ta-lib

  • Para Linux (Debian/Ubuntu):

Instalar dependencias mediante apt

sudo apt-get update
sudo apt-get install libta-lib0-dev

Paso 2: Instale el envoltorio de Python para TA-Lib.

Una vez instalada la biblioteca C, instale el envoltorio de Python:

pip install ta-lib

Paso 3: Verificar la instalación.

Abre una consola de Python y ejecuta:

import talib
print(talib.__version__)

Si no se producen errores y se imprime la versión, la instalación se ha realizado correctamente.

Vamos a echar un vistazo a las velas disponibles en la biblioteca TA-Lib que tengo instalada.

CDL2CROWS
  CDL3BLACKCROWS
  CDL3INSIDE
  CDL3LINESTRIKE
  CDL3OUTSIDE
  CDL3STARSINSOUTH
  CDL3WHITESOLDIERS
  CDLABANDONEDBABY
  CDLADVANCEBLOCK
  CDLBELTHOLD
  CDLBREAKAWAY
  CDLCLOSINGMARUBOZU
  CDLCONCEALBABYSWALL
  CDLCOUNTERATTACK
  CDLDARKCLOUDCOVER
  CDLDOJI
  CDLDOJISTAR
  CDLDRAGONFLYDOJI
  CDLENGULFING
  CDLEVENINGDOJISTAR
  CDLEVENINGSTAR
  CDLGAPSIDESIDEWHITE
  CDLGRAVESTONEDOJI
  CDLHAMMER
  CDLHANGINGMAN
  CDLHARAMI
  CDLHARAMICROSS
  CDLHIGHWAVE
  CDLHIKKAKE
  CDLHIKKAKEMOD
  CDLHOMINGPIGEON
  CDLIDENTICAL3CROWS
  CDLINNECK
  CDLINVERTEDHAMMER
  CDLKICKING
  CDLKICKINGBYLENGTH
  CDLLADDERBOTTOM
  CDLLONGLEGGEDDOJI
  CDLLONGLINE
  CDLMARUBOZU
  CDLMATCHINGLOW
  CDLMATHOLD
  CDLMORNINGDOJISTAR
  CDLMORNINGSTAR
  CDLONNECK
  CDLPIERCING
  CDLRICKSHAWMAN
  CDLRISEFALL3METHODS
  CDLSEPARATINGLINES
  CDLSHOOTINGSTAR
  CDLSHORTLINE
  CDLSPINNINGTOP
  CDLSTALLEDPATTERN
  CDLSTICKSANDWICH
  CDLTAKURI
  CDLTASUKIGAP
  CDLTHRUSTING
  CDLTRISTAR
  CDLUNIQUE3RIVER
  CDLUPSIDEGAP2CROWS
  CDLXSIDEGAP3METHODS

Aunque la mayoría de los traders reconoce patrones clásicos como el Doji o la Envolvente, algunas formaciones menos conocidas pueden revelar cambios sutiles en la psicología del mercado. El Bebé Abandonado es un raro patrón de reversión de tres velas: un Doji queda separado de la vela anterior por un hueco y, a continuación, otro hueco en la dirección opuesta deja las velas «abandonadas», lo que a menudo anticipa un cambio de tendencia contundente. El Stick Sandwich aparece cuando dos velas fuertes del mismo color enmarcan una pequeña vela de color opuesto, y la última cierra al mismo nivel —o muy cerca— del cierre de la primera. Este «sándwich» indica que, pese a un breve movimiento en contra de la tendencia, el impulso original se mantiene intacto. El Tasuki Gap es un patrón de continuación de tres velas en el que un hueco inicial a favor de la tendencia queda parcialmente —pero no por completo— cubierto por la tercera vela, lo que pone de manifiesto la resistencia del mercado a cerrar dicho hueco y confirma la fortaleza de la tendencia predominante.

Otros dos patrones menos frecuentes, Hikkake y Belt Hold, aportan aún más matices al análisis. El Hikkake, cuyo nombre japonés significa «trampa», comienza con una vela interior, seguida de una falsa ruptura que atrae a los traders en una dirección antes de girarse y cerrar más allá del límite opuesto. De este modo, deja atrapados a quienes entraron en el sentido equivocado y acelera el movimiento de la tendencia subyacente. En cambio, el Belt Hold es una vela única y decisiva que actúa contra el movimiento previo: un Belt Hold alcista abre en el mínimo o muy cerca de él durante una tendencia bajista y cierra con fuerza al alza, mientras que un Belt Hold bajista abre cerca del máximo durante una tendencia alcista y cierra con fuerza a la baja. Como los Belt Hold reflejan un cambio brusco de control en una sola vela, sin necesidad de que una vela anterior sea «envolvida», pueden actuar como señales de reversión claras y de gran impacto en niveles clave de soporte o resistencia.

En TA-Lib, cada función de patrones de velas japonesas comienza con el prefijo CDL-, abreviatura de «Candle». Este prefijo indica que la función implementa uno de los numerosos algoritmos clásicos de patrones de velas (por ejemplo, CDLDOJI, CDLENGULFING, CDLHAMMER, etc.). Cuando llamas a una de estas funciones CDL* con tus arrays de apertura, máximo, mínimo y cierre (OHLC), devuelve una serie de valores enteros en la que los valores distintos de cero indican la presencia de ese patrón de velas concreto, mientras que el signo señala su dirección.



Desglose del flujo de trabajo del sistema

En esta sección, te mostraré cómo funciona el sistema, desglosando en detalle tanto los scripts en MQL5 como los de Python. En el centro de nuestra configuración se encuentra un ciclo ajustado de solicitud y respuesta: cada vez que se cierra una nueva vela, el Asesor Experto MQL5 extrae los valores de apertura, máximo, mínimo, cierre y marca de tiempo de las 60 barras más recientes, los empaqueta en una carga útil JSON compacta y la envía mediante POST al servicio Flask local en /patterns.

A continuación, el EA espera la respuesta del servidor, que ahora incluye dos matrices paralelas de longitud 60, una para patrones (nombres de velas japonesas) y otra para señales ("alcistas" o "bajistas"), e inmediatamente analiza ese JSON. En MetaTrader 5, primero elimina las etiquetas existentes y luego recrea objetos de texto en el máximo de cada barra donde apareció un patrón genuino, coloreándolos de verde lima para alcistas y de rojo para bajistas antes de activar una Alerta() descriptiva.

Mientras tanto, el punto final de Flask ingiere el JSON, lo normaliza en un DataFrame de pandas, aplica los más de 60 detectores de patrones de TA-Lib, etiqueta cada barra, determina la tendencia alcista/bajista y devuelve ambos arrays, además de un breve registro y una métrica de tiempo. Esta clara división —MQL5 para la entrada/salida de gráficos y alertas, y Python para la normalización de datos y la lógica de patrones— mantiene a ambos lados enfocados, eficientes y fáciles de extender.

  • Análisis del Asesor Experto MQL5

Cuando se carga el EA por primera vez, registra inmediatamente un mensaje de inicio que le recuerda que debe incluir el punto final de Flask en la lista blanca de la configuración del Asesor Experto de MetaTrader 5. Este paso de configuración, que se realiza una sola vez, garantiza que se permitan las llamadas HTTP posteriores a su servicio local de detección de patrones y evita fallos silenciosos durante la ejecución.

int OnInit()
{
   Log("EA started – allow WebRequest to: " + InpURL);
   return INIT_SUCCEEDED;
}

En cada tick entrante, el EA comprueba la marca de tiempo de la vela actual (índice cero) con respecto a la última barra procesada. Si no se ha cerrado ninguna barra nueva desde la comprobación anterior, simplemente regresa, evitando así el trabajo redundante y asegurando que el análisis de patrones se ejecute solo una vez por cada vela completada.

void OnTick()
{
   datetime bar = iTime(_Symbol, InpTF, 0);
   if(bar == 0 || bar == g_lastBar)
      return;
   g_lastBar = bar;
   // … (continue processing)
}

En cuanto se confirma una nueva barra, el EA recorre en bucle las 60 velas más recientes, extrayendo la apertura, el máximo, el mínimo, el cierre y la marca de tiempo UNIX de cada barra. Estos valores se almacenan temporalmente en matrices de tamaño fijo y, a continuación, se serializan en un objeto JSON compacto que incluye el símbolo, el período de tiempo y los 60 conjuntos de datos y horas de OHLC.

double o[BARS], h[BARS], l[BARS], c[BARS];
long   t[BARS];
for(int i = 0; i < BARS; i++)
{
   int sh = i + 1;
   o[i] = iOpen(_Symbol, InpTF, sh);
   h[i] = iHigh(_Symbol, InpTF, sh);
   l[i] = iLow(_Symbol, InpTF, sh);
   c[i] = iClose(_Symbol, InpTF, sh);
   t[i] = (long)iTime(_Symbol, InpTF, sh);
}
string json = StringFormat(
   "{\"symbol\":\"%s\",\"timeframe\":%d,\"time\":[%s],\"open\":[%s],\"high\":[%s],\"low\":[%s],\"close\":[%s]}",
   _Symbol, InpTF, CSVInt(t), CSV(o), CSV(h), CSV(l), CSV(c)
);
Log("JSON-OUT: " + json);

Una vez ensamblada la carga útil JSON, el EA realiza una solicitud POST a la URL de Flask configurada y espera hasta que se agote el tiempo de espera especificado. Captura cualquier error de red o de permisos, registrando mensajes detallados si la solicitud falla, y, en caso contrario, recibe la respuesta JSON del servidor como una cadena de texto para su posterior procesamiento.

char body[];
StringToCharArray(json, body, 0, StringLen(json));
char reply[];
string hdr = "Content-Type: application/json\r\n", respHdr;
int code = WebRequest("POST", InpURL, hdr, InpTimeout, body, reply, respHdr);
if(code == -1)
{
   Log("WebRequest failed: " + IntegerToString(GetLastError()));
   return;
}
string resp = CharArrayToString(reply, 0, -1, CP_UTF8);
Log("HTTP " + IntegerToString(code) + " RESP: " + resp);

Una vez que la llamada HTTP finaliza, el EA busca en la respuesta dos entradas de matriz: una llamada "patterns" y otra llamada "signals". Utiliza rutinas sencillas de búsqueda y división de cadenas para convertir esas matrices JSON de nuevo en matrices de cadenas MQL5, verificando que cada matriz contenga exactamente 60 elementos antes de continuar.

string patTxt, sigTxt, patt[], sigs[];
if(!ExtractArray(resp, "patterns", patTxt) ||
   !ExtractArray(resp, "signals", sigTxt) ||
   !ParseArray(patTxt, patt) ||
   !ParseArray(sigTxt, sigs) ||
   ArraySize(patt) != BARS ||
   ArraySize(sigs) != BARS)
{
   Log("Malformed patterns or signals");
   return;
}

Para cada una de las 60 barras, el EA inspecciona el patrón correspondiente y las entradas de señal. Cada vez que se detecta un patrón válido (es decir, distinto de «None»), calcula el desplazamiento correcto de la vela, elimina las anotaciones anteriores y crea una nueva etiqueta de texto en el máximo de la vela. Las etiquetas están codificadas por colores —verde lima para las señales alcistas y rojo para las bajistas— y cada una genera una alerta descriptiva en MetaTrader 5 que incluye el símbolo, el periodo temporal, la dirección, el nombre del patrón y la marca de tiempo.

for(int i = 0; i < BARS; i++)
{
   string pat = patt[i], sig = sigs[i];
   if(pat == "" || pat == "None") continue;

   int shift = BARS - i;
   datetime tm = iTime(_Symbol, InpTF, shift);
   double y = iHigh(_Symbol, InpTF, shift);
   string obj = PREFIX + IntegerToString(shift);
   ObjectCreate(0, obj, OBJ_TEXT, 0, tm, y);
   ObjectSetString(0, obj, OBJPROP_TEXT, pat);

   color col = (sig == "bullish" ? clrLime : clrRed);
   ObjectSetInteger(0, obj, OBJPROP_COLOR, col);
   ObjectSetInteger(0, obj, OBJPROP_FONTSIZE, InpFontSize);
   ObjectSetInteger(0, obj, OBJPROP_SELECTABLE, false);

   Alert(StringFormat("%s %s %s pattern '%s' at %s",
                      _Symbol, EnumToString(InpTF), sig, pat,
                      TimeToString(tm, TIME_DATE|TIME_MINUTES)));
}

Cuando se retira el EA o se cierra MetaTrader 5, la rutina de desinicialización recorre todos los objetos del gráfico que tienen como prefijo la etiqueta del EA y los elimina. Esto garantiza que no queden etiquetas obsoletas en el gráfico una vez que el EA ya no esté activo, dejando su espacio de trabajo limpio.

void OnDeinit(const int reason)
{
   for(int i = ObjectsTotal(0,0,-1)-1; i >= 0; i--)
   {
      string n = ObjectName(0, i, 0, -1);
      if(StringFind(n, PREFIX) == 0)
         ObjectDelete(0, n);
   }
   Log("EA removed");
}

  • Análisis del servidor Python Flask

Cuando se inicia el servicio Flask, configura el registro de nivel INFO para registrar cada solicitud entrante y evento interno, tanto en la consola como en un archivo de registro rotativo. Inmediatamente después, el script inspecciona la biblioteca TA-Lib y recopila dinámicamente todas las funciones de patrones de velas japonesas (aquellas con el prefijo "CDL") en un diccionario en memoria. Este descubrimiento dinámico significa que el servicio admite automáticamente todos los patrones de TA-Lib existentes y futuros sin intervención manual, lo que garantiza que cualquier nuevo patrón añadido a TA-Lib esté disponible para su sistema tan pronto como se actualice la biblioteca.

app = Flask(__name__)
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s %(levelname)s %(message)s')
app.logger.setLevel(logging.INFO)

# load all TA‑Lib candlestick functions
CDL_FUNCS = {
    name: getattr(talib, name)
    for name in talib.get_functions() if name.startswith("CDL")
}

Cada vez que el EA envía una petición POST al endpoint /patterns, el servidor lee el contenido bruto de la petición, elimina los bytes nulos que MetaTrader 5 pueda haber añadido y, a continuación, intenta decodificarlo como JSON. Si el análisis del JSON falla o falta alguno de los arrays obligatorios (open, high, low, close o time), el servidor devuelve inmediatamente un error HTTP 400 claro, acompañado de un mensaje descriptivo. Al validar los datos recibidos desde el principio, evitas que información malformada o incompleta se propague por el flujo de análisis y provoque errores difíciles de diagnosticar en etapas posteriores.

@app.route('/patterns', methods=['POST'])
def patterns():
    app.logger.info("Received /patterns request")
    try:
        raw = request.data
        if b'\x00' in raw:
            raw = raw.split(b'\x00', 1)[0]
        data = json.loads(raw.decode('utf-8'))
    except Exception as e:
        return jsonify(error="Invalid JSON", details=str(e)), 400

Una vez que el JSON supera la validación, el servidor extrae las cinco matrices clave —las marcas de tiempo y las cuatro series de precios— y invierte su orden para que el índice 0 se corresponda con la barra más antigua. A continuación, convierte la lista de marcas de tiempo UNIX en un DatetimeIndex de pandas, lo que garantiza que las operaciones posteriores tengan en cuenta la hora. Si alguna matriz tiene una longitud diferente o contiene entradas no numéricas, el servicio detecta la discrepancia de inmediato y devuelve un error, lo que garantiza que solo los datos perfectamente alineados y con el tipo correcto lleguen a la lógica de detección.

try:
    symbol = data.get('symbol', 'Instrument')
    ts     = data.get('time', [])
    open_  = np.array(data['open'][::-1],  dtype=float)
    high   = np.array(data['high'][::-1],  dtype=float)
    low    = np.array(data['low'][::-1],   dtype=float)
    close  = np.array(data['close'][::-1], dtype=float)
    idx    = pd.to_datetime(np.array(ts[::-1], dtype='int64'), unit='s')
    app.logger.info(f"Loaded {len(open_)} bars for {symbol}")
except KeyError as ke:
    return jsonify(error=f"Missing field {ke}"), 400
except Exception as e:
    return jsonify(error="Bad field format", details=str(e)), 400

Una vez que dispone de matrices NumPy limpias, el servicio recorre cada función de velas japonesas de TA-Lib y la aplica a toda la serie. Cualquier resultado distinto de cero se registra como una coincidencia de patrón candidata. Se aplican dos rutinas auxiliares adicionales —una para validar los patrones Harami y otra para refinar los patrones Engulfing— de forma condicional en función de los indicadores de solicitud, aplicando criterios más estrictos cuando sea necesario. El hecho de envolver cada llamada a TA-Lib en un bloque try/except significa que una sola función problemática o una estructura de datos inesperada no puede provocar el fallo de todo el servicio; en su lugar, la rutina simplemente omite esa función y continúa procesando el resto.

n = len(open_)
all_hits = [[] for _ in range(n)]
for name, func in CDL_FUNCS.items():
    try:
        res = func(open_, high, low, close)
    except Exception:
        continue
    for i, v in enumerate(res):
        if v == 0:
            continue
        if name == "CDLHARAMI" and fh and not is_manual_harami(open_, close, i, tol=HARAMI_TOL):
            continue
        if name == "CDLENGULFING" and fe and not is_manual_engulfing(open_, close, i, tol=ENGULF_TOL):
            continue
        all_hits[i].append((name, v))

Dado que pueden detectarse varios patrones en una misma vela, el servicio utiliza una pequeña lista de prioridades configurable —dando preferencia a Envolvente sobre Harami y a Harami sobre Doji— para seleccionar el patrón dominante cuando se producen coincidencias. Si no está presente ninguno de los patrones de prioridad, elige el que tenga la mayor salida absoluta de TA-Lib. El signo numérico del resultado seleccionado se asigna entonces a una etiqueta de "alcista" o "bajista". Este enfoque determinista, con un único patrón por barra, garantiza que cada vela se identifique con una señal clara, lo que simplifica la superposición de gráficos y la lógica de alertas en las fases posteriores.

PRIORITY = ["CDLENGULFING", "CDLHARAMI", "CDLDOJI"]
detected = [None] * n
signals  = [None] * n
for i, hits in enumerate(all_hits):
    if not hits:
        continue
    pick = next(((nm, val) for pat in PRIORITY for nm, val in hits if nm == pat), None)
    if pick is None:
        pick = max(hits, key=lambda x: abs(x[1]))
    nm, val = pick
    detected[i] = nm
    signals[i]  = "bullish" if val > 0 else "bearish"

A continuación, el script crea un DataFrame de pandas a partir de los cuatro arrays de precios y el índice de fecha y hora, y luego añade dos nuevas columnas: una para el nombre del patrón detectado y otra para su señal alcista/bajista. Para prepararse para la anotación visual, calcula un pequeño desplazamiento basado en el rango de precios general y construye una lista de superposiciones de dispersión, colocando flechas verdes hacia arriba justo debajo de los mínimos de las barras para las señales alcistas y flechas rojas hacia abajo justo encima de los máximos de las barras para las señales bajistas. Al fijar las posiciones de los marcadores en relación con el rango dinámico del gráfico, se evita la saturación visual y se garantiza la coherencia entre diferentes instrumentos y marcos temporales.

df = pd.DataFrame({"Open": open_, "High": high, "Low": low, "Close": close}, index=idx)
df["Pattern"] = pd.Series(detected, index=idx).fillna("None")
df["Signal"]  = pd.Series(signals,  index=idx).fillna("")

df.sort_index(inplace=True)
adds = []
price_rng = df["High"].max() - df["Low"].min()
for tstamp, row in df.iterrows():
    if row["Pattern"] == "None":
        continue
    if row["Signal"] == "bullish":
        y, marker, color = row["Low"] - price_rng*0.005, "^", "green"
    else:
        y, marker, color = row["High"] + price_rng*0.005, "v", "red"
    adds.append(mpf.make_addplot(
        [y if i == tstamp else np.nan for i in df.index],
        type="scatter", marker=marker, markersize=80, color=color
    ))

Finalmente, el servicio define un esquema de color personalizado para las velas (por ejemplo, verde para las barras ascendentes y rojo para las descendentes) y genera un gráfico completo de velas japonesas con mplfinance, incorporando los marcadores de dispersión preparados previamente. Se añade una leyenda para indicar tanto la dirección de las velas como los marcadores de patrones, facilitando así su interpretación. La imagen se redimensiona para una claridad óptima, se guarda en un archivo PNG único en el directorio del servidor y, a continuación, se cierra para liberar memoria. La respuesta JSON proporciona las listas de patrones y señales invertidas (de modo que el índice 0 representa, una vez más, la barra más reciente), un resumen de registro opcional y el nombre del archivo del gráfico guardado, listo para su uso inmediato por el EA o su integración en otras herramientas de análisis.

mc = make_marketcolors(up='green', down='red', edge='inherit', wick='inherit')
style = make_mpf_style(marketcolors=mc, base_mpf_style='default')
fig, axes = mpf.plot(df, type="candle", style=style,
                     title=f"{symbol} Patterns", addplot=adds,
                     volume=False, returnfig=True, tight_layout=True)
axes[0].legend(handles=[
    Line2D([0],[0],color='green',lw=4,label='Buy Candle'),
    Line2D([0],[0],color='red',lw=4,label='Sell Candle'),
    Line2D([0],[0],marker='^',linestyle='None',color='green',markersize=12,label='Buy Signal'),
    Line2D([0],[0],marker='v',linestyle='None',color='red',markersize=12,label='Sell Signal')
], loc='upper left', frameon=True)

fname = f"pattern_chart_{uuid.uuid4().hex[:8]}.png"
fig.savefig(path := os.path.join(os.path.dirname(__file__), fname), dpi=100)
plt.close(fig)
app.logger.info(f"Chart saved: {fname}")

return jsonify(
    patterns=[p or "None" for p in detected[::-1]],
    signals =[s or "none" for s in signals [::-1]],
    log=[],
    chart=fname
)


Pruebas y resultados

A continuación se presenta una muestra de los resultados de nuestras pruebas. 

Pruebas en el Step Index

Registros MQL5

La pestaña «Expertos» de MQL5 muestra registros con los datos enviados a Python, los patrones recibidos y el nombre de cada barra detectada en ese momento. Esta información se actualiza al cierre de cada nueva barra, acompañada de una alerta.

2025.07.15 22:56:50.294 Candlestick Label 2 (Step Index,M15)    [CSLAB] JSON-OUT: {"symbol":"Step Index","timeframe":0,"time":[1752558300,1752559200,
1752560100,1752561000,1752561900,1752562800,1752563700,1752564600,1752565500,1752566400,1752567300,1752568200,
1752569100,1752570000,1752570900,1752571800,1752572700,1752573600,1752574500,1752575400,1752576300,1752577200,1752578100,1752579000,
1752579900,1752580800,1752581700,1752582600,1752583500,1752584400,1752585300,1752586200,1752587100,1752588000,1752588900,1752589800,
1752590700,1752591600,1752592500,1752593400,17525943
2025.07.15 22:56:50.804 Candlestick Label 2 (Step Index,M15)    [CSLAB] HTTP 200 RESP: {"chart":"pattern_chart_e06f7a61.png","log":[],"patterns":
["CDLENGULFING","CDLHARAMI","None","CDLCLOSINGMARUBOZU","CDLHARAMI","CDLHIKKAKE","CDLENGULFING","CDLHIGHWAVE","None","CDLLONGLINE",
"CDLHIGHWAVE","CDLSHORTLINE","CDLSHORTLINE","CDLHIKKAKE","CDLHARAMI","CDLENGULFING","CDLDOJI","None","None","None","CDLCLOSINGMARUBOZU",
"None","CDLBELTHOLD","None","CDLHARAMI","CDLBELTHOLD","CDLHIKKAKE","CDLENGULFING","CDLHIKKAKE","CDLHIKKAKE","None","CDLDOJI","CDLHAMMER",
"CDLHARAMI","CDLENGULFING"
2025.07.15 22:56:50.804 Candlestick Label 2 (Step Index,M15)    
2025.07.15 22:56:50.804 Candlestick Label 2 (Step Index,M15)    Alert: Step Index PERIOD_CURRENT bullish pattern 'CDLENGULFING' at 2025.07.15 05:45

Registros de la consola

  • POST /patterns: Un cliente (tu EA de MQL5) ha enviado una solicitud HTTP POST al punto final /patterns.
  • HTTP/1.1: La solicitud utilizó el protocolo HTTP 1.1.
  • 200: El servidor ha respondido con el código de estado 200, que significa «OK»: tu punto final ha procesado la solicitud correctamente.

2025-07-15 22:54:52,246 INFO Received /patterns request
2025-07-15 22:54:52,250 INFO Loaded 60 bars for Step Index
2025-07-15 22:54:52,808 INFO Chart saved: pattern_chart_a77f2eec.png
2025-07-15 22:54:52,810 INFO 127.0.0.1 - - [15/Jul/2025 22:54:52] "POST /patterns HTTP/1.1" 200 -
2025-07-15 22:55:00,040 INFO Received /patterns request

A continuación se muestran los gráficos correspondientes a los mismos patrones detectados, que ilustran el «Step Index» a lo largo del tiempo. Los gráficos se generan utilizando Matplotlib, TA-Lib y mplfinance, y muestran tanto las barras de precios como las señales de velas japonesas identificadas por nuestro sistema:

Velas 

  • Los cuerpos verdes indican los periodos en los que el cierre fue superior a la apertura (velas alcistas).
  • Los cuerpos rojos indican los periodos en los que el cierre fue inferior a la apertura (velas bajistas).

Señales  

  • Los marcadores verdes situados debajo de las barras representan señales de compra (patrones alcistas).
  • Los marcadores rojos situados encima de las barras indican señales de venta (patrones bajistas).

Pruebas con el índice Crash 1000

Registros MQL5

2025.07.15 23:30:00.299 Candlestick Label 2 (Crash 1000 Index,M15)      [CSLAB] JSON-OUT: {"symbol":"Crash 1000 Index","timeframe":0,"time":[1752561000,1752561900,
1752562800,1752563700,1752564600,1752565500,1752566400,1752567300,1752568200,1752569100,1752570000,1752570900,1752571800,1752572700,1752573600,1752574500,1752575400
,1752576300,1752577200,1752578100,1752579000,1752579900,1752580800,1752581700,1752582600,1752583500,1752584400,1752585300,1752586200,1752587100,1752588000,1752588900,
1752589800,1752590700,1752591600,1752592500,1752593400,1752594300,1752595200,1752596100,17
2025.07.15 23:30:02.749 Candlestick Label 2 (Crash 1000 Index,M15)      [CSLAB] HTTP 200 RESP: {"chart":"pattern_chart_ebd72b47.png","log":[],"patterns":
["CDLCLOSINGMARUBOZU","CDLBELTHOLD","CDLBELTHOLD","None","CDLBELTHOLD","CDLHIKKAKE","CDLENGULFING","CDLBELTHOLD","CDLHARAMI","CDLENGULFING","None","CDLBELTHOLD",
"None","CDLBELTHOLD","CDLBELTHOLD","CDLBELTHOLD","None","CDLBELTHOLD","CDLBELTHOLD","None","CDLBELTHOLD","CDLBELTHOLD","CDLBELTHOLD","CDLDOJI","CDLBELTHOLD","None",
"CDLSHORTLINE","CDLSHORTLINE","None","CDLENGULFING","CDLHARAMI","CDLCLOSINGMARUBOZU","None","CDLMATCHINGL
2025.07.15 23:30:02.749 Candlestick Label 2 (Crash 1000 Index,M15)      
2025.07.15 23:30:02.769 Candlestick Label 2 (Crash 1000 Index,M15)      Alert: Crash 1000 Index PERIOD_CURRENT bearish pattern 'CDLCLOSINGMARUBOZU' at 2025.07.15 06:30

Registros de la consola

2025-07-15 23:30:02,719 INFO Chart saved: pattern_chart_ebd72b47.png
2025-07-15 23:30:02,735 INFO 127.0.0.1 - - [15/Jul/2025 23:30:02] "POST /patterns HTTP/1.1" 200 -

Gráfico de patrones de velas japonesas en Python

El gráfico ofrece una visión clara de cómo se manifiesta cada patrón detectado y de lo que ocurre a continuación.


Conclusión

En esta arquitectura, MetaTrader 5 sigue centrándose en sus puntos fuertes —la captura de datos de mercado en tiempo real, la gestión de objetos de gráficos y las alertas nativas—, mientras que un servicio ligero basado en Flask aprovecha TA-Lib y mplfinance para realizar todos los cálculos de patrones de velas japonesas y generar visualizaciones pulidas. Al transmitir sesenta barras de datos OHLC y marcas de tiempo en formato JSON, el EA logra un análisis en menos de un segundo sin aumentar su propio tamaño, y el backend de Python proporciona tanto etiquetas de patrones detalladas como gráficos completos y anotados, listos para su revisión o distribución.

Esta clara separación de responsabilidades da lugar a un sistema verdaderamente modular: puedes perfeccionar las reglas de detección de patrones, ajustar las tolerancias de los filtros de señales o ampliar la lógica de alertas del EA de forma independiente, sin riesgo de contaminación cruzada. La integración del servicio Flask —ya sea en un contenedor de Docker para garantizar una implementación coherente o detrás de una pasarela de API autenticada— mejora aún más la portabilidad y la seguridad.

Te animo a que apliques este marco a tus instrumentos preferidos, experimentes con la superposición de indicadores personalizados e integres los gráficos generados en plataformas de análisis más amplias. Con este enfoque híbrido, podrás aprovechar todo el potencial tanto de MQL5 como de las bibliotecas modernas de Python, transformando la lectura manual de gráficos en un flujo de trabajo automatizado y basado en la precisión.





  
Chart Projector
Analytical Comment
Analytics Master
Analytics Forecaster 
Volatility Navigator
Mean Reversion Signal Reaper
Signal Pulse 
Metrics Board 
External Flow
VWAP
Heikin Ashi  FibVWAP  
RSI DIVERGENCE
Parabolic Stop and Reverse (PSAR) 
Quarters Drawer Script
Intrusion Detector
TrendLoom Tool Quarters Board 
ZigZag Analyzer Correlation Pathfinder Market Structure Flip Detector Tool
Correlation Dashboard  Currency Strength Meter 
PAQ Analysis Tool 
Dual EMA Fractal Breaker
Pin bar, Engulfing and RSI divergence
Liquidity SweepOpening Range Breakout ToolBoom and Crash InterceptorCCI Zer-Line EA
Candlestick RecognitionCandlestick Detection using TA-Lib    

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

Archivos adjuntos |
candlesticks.py (6.76 KB)
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.
Herramientas de trading de MQL5 (Parte 5): Creación de una cinta de cotizaciones deslizante para el seguimiento en tiempo real de símbolos Herramientas de trading de MQL5 (Parte 5): Creación de una cinta de cotizaciones deslizante para el seguimiento en tiempo real de símbolos
En este artículo, desarrollamos una cinta de cotizaciones deslizante en MQL5 para monitorizar varios símbolos en tiempo real, mostrando los precio Bid (oferta), los spreads y las variaciones porcentuales diarias mediante efectos de desplazamiento. Implementamos fuentes, colores y velocidades de desplazamiento personalizables para resaltar eficazmente los movimientos y las tendencias de los precios.
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.
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 9): Cruce de dos medias móviles Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 9): Cruce de dos medias móviles
Este artículo describe el diseño de una estrategia de cruce de medias móviles dobles que utiliza señales de un marco temporal superior (D1) para orientar las entradas en un marco temporal inferior (M15), calculándose los niveles de stop-loss a partir de un marco temporal de riesgo intermedio (H4). Introduce constantes del sistema, enumeraciones personalizadas y la lógica para los modos de seguimiento de tendencias y de retorno a la media, al tiempo que hace hincapié en la modularidad y en la optimización futura mediante un algoritmo genético. Este enfoque permite establecer condiciones flexibles de entrada y salida, con el objetivo de reducir el retraso de la señal y mejorar la sincronización de las operaciones, alineando las entradas en los marcos temporales más cortos con las tendencias de los marcos temporales más largos.