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
Contenido
- Introducción
- Partiendo de nuestra última discusión
- Entendiendo TA-Lib
- Desglose del flujo de trabajo del sistema
- Pruebas y resultados
- Conclusión
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.
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:
| Ventaja | Descripción |
|---|---|
| Automatización | Detecta automáticamente más de 60 patrones de velas japonesas sin necesidad de codificación manual. |
| Eficiencia | Reconocimiento de patrones más rápido, adecuado para análisis en tiempo real. |
| Exactitud | Utiliza 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 MQL5Comprender 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.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18824
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Utilizando redes neuronales en MetaTrader
Herramientas de trading de MQL5 (Parte 5): Creación de una cinta de cotizaciones deslizante para el seguimiento en tiempo real de símbolos
Particularidades del trabajo con números del tipo double en MQL4
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 9): Cruce de dos medias móviles
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso